1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Loongson GPIO Support |
4 | * |
5 | * Copyright (C) 2022-2023 Loongson Technology Corporation Limited |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/init.h> |
10 | #include <linux/module.h> |
11 | #include <linux/spinlock.h> |
12 | #include <linux/err.h> |
13 | #include <linux/gpio/driver.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/bitops.h> |
16 | #include <asm/types.h> |
17 | |
18 | enum loongson_gpio_mode { |
19 | BIT_CTRL_MODE, |
20 | BYTE_CTRL_MODE, |
21 | }; |
22 | |
23 | struct loongson_gpio_chip_data { |
24 | const char *label; |
25 | enum loongson_gpio_mode mode; |
26 | unsigned int conf_offset; |
27 | unsigned int out_offset; |
28 | unsigned int in_offset; |
29 | unsigned int inten_offset; |
30 | }; |
31 | |
32 | struct loongson_gpio_chip { |
33 | struct gpio_chip chip; |
34 | struct fwnode_handle *fwnode; |
35 | spinlock_t lock; |
36 | void __iomem *reg_base; |
37 | const struct loongson_gpio_chip_data *chip_data; |
38 | }; |
39 | |
40 | static inline struct loongson_gpio_chip *to_loongson_gpio_chip(struct gpio_chip *chip) |
41 | { |
42 | return container_of(chip, struct loongson_gpio_chip, chip); |
43 | } |
44 | |
45 | static inline void loongson_commit_direction(struct loongson_gpio_chip *lgpio, unsigned int pin, |
46 | int input) |
47 | { |
48 | u8 bval = input ? 1 : 0; |
49 | |
50 | writeb(val: bval, addr: lgpio->reg_base + lgpio->chip_data->conf_offset + pin); |
51 | } |
52 | |
53 | static void loongson_commit_level(struct loongson_gpio_chip *lgpio, unsigned int pin, int high) |
54 | { |
55 | u8 bval = high ? 1 : 0; |
56 | |
57 | writeb(val: bval, addr: lgpio->reg_base + lgpio->chip_data->out_offset + pin); |
58 | } |
59 | |
60 | static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned int pin) |
61 | { |
62 | unsigned long flags; |
63 | struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip); |
64 | |
65 | spin_lock_irqsave(&lgpio->lock, flags); |
66 | loongson_commit_direction(lgpio, pin, input: 1); |
67 | spin_unlock_irqrestore(lock: &lgpio->lock, flags); |
68 | |
69 | return 0; |
70 | } |
71 | |
72 | static int loongson_gpio_direction_output(struct gpio_chip *chip, unsigned int pin, int value) |
73 | { |
74 | unsigned long flags; |
75 | struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip); |
76 | |
77 | spin_lock_irqsave(&lgpio->lock, flags); |
78 | loongson_commit_level(lgpio, pin, high: value); |
79 | loongson_commit_direction(lgpio, pin, input: 0); |
80 | spin_unlock_irqrestore(lock: &lgpio->lock, flags); |
81 | |
82 | return 0; |
83 | } |
84 | |
85 | static int loongson_gpio_get(struct gpio_chip *chip, unsigned int pin) |
86 | { |
87 | u8 bval; |
88 | int val; |
89 | struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip); |
90 | |
91 | bval = readb(addr: lgpio->reg_base + lgpio->chip_data->in_offset + pin); |
92 | val = bval & 1; |
93 | |
94 | return val; |
95 | } |
96 | |
97 | static int loongson_gpio_get_direction(struct gpio_chip *chip, unsigned int pin) |
98 | { |
99 | u8 bval; |
100 | struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip); |
101 | |
102 | bval = readb(addr: lgpio->reg_base + lgpio->chip_data->conf_offset + pin); |
103 | if (bval & 1) |
104 | return GPIO_LINE_DIRECTION_IN; |
105 | |
106 | return GPIO_LINE_DIRECTION_OUT; |
107 | } |
108 | |
109 | static void loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int value) |
110 | { |
111 | unsigned long flags; |
112 | struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip); |
113 | |
114 | spin_lock_irqsave(&lgpio->lock, flags); |
115 | loongson_commit_level(lgpio, pin, high: value); |
116 | spin_unlock_irqrestore(lock: &lgpio->lock, flags); |
117 | } |
118 | |
119 | static int loongson_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) |
120 | { |
121 | unsigned int u; |
122 | struct platform_device *pdev = to_platform_device(chip->parent); |
123 | struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip); |
124 | |
125 | if (lgpio->chip_data->mode == BIT_CTRL_MODE) { |
126 | /* Get the register index from offset then multiply by bytes per register */ |
127 | u = readl(addr: lgpio->reg_base + lgpio->chip_data->inten_offset + (offset / 32) * 4); |
128 | u |= BIT(offset % 32); |
129 | writel(val: u, addr: lgpio->reg_base + lgpio->chip_data->inten_offset + (offset / 32) * 4); |
130 | } else { |
131 | writeb(val: 1, addr: lgpio->reg_base + lgpio->chip_data->inten_offset + offset); |
132 | } |
133 | |
134 | return platform_get_irq(pdev, offset); |
135 | } |
136 | |
137 | static int loongson_gpio_init(struct device *dev, struct loongson_gpio_chip *lgpio, |
138 | void __iomem *reg_base) |
139 | { |
140 | int ret; |
141 | u32 ngpios; |
142 | |
143 | lgpio->reg_base = reg_base; |
144 | if (lgpio->chip_data->mode == BIT_CTRL_MODE) { |
145 | ret = bgpio_init(gc: &lgpio->chip, dev, sz: 8, |
146 | dat: lgpio->reg_base + lgpio->chip_data->in_offset, |
147 | set: lgpio->reg_base + lgpio->chip_data->out_offset, |
148 | NULL, NULL, |
149 | dirin: lgpio->reg_base + lgpio->chip_data->conf_offset, |
150 | flags: 0); |
151 | if (ret) { |
152 | dev_err(dev, "unable to init generic GPIO\n" ); |
153 | return ret; |
154 | } |
155 | } else { |
156 | lgpio->chip.direction_input = loongson_gpio_direction_input; |
157 | lgpio->chip.get = loongson_gpio_get; |
158 | lgpio->chip.get_direction = loongson_gpio_get_direction; |
159 | lgpio->chip.direction_output = loongson_gpio_direction_output; |
160 | lgpio->chip.set = loongson_gpio_set; |
161 | lgpio->chip.parent = dev; |
162 | device_property_read_u32(dev, propname: "ngpios" , val: &ngpios); |
163 | lgpio->chip.ngpio = ngpios; |
164 | spin_lock_init(&lgpio->lock); |
165 | } |
166 | |
167 | lgpio->chip.label = lgpio->chip_data->label; |
168 | lgpio->chip.can_sleep = false; |
169 | if (lgpio->chip_data->inten_offset) |
170 | lgpio->chip.to_irq = loongson_gpio_to_irq; |
171 | |
172 | return devm_gpiochip_add_data(dev, &lgpio->chip, lgpio); |
173 | } |
174 | |
175 | static int loongson_gpio_probe(struct platform_device *pdev) |
176 | { |
177 | void __iomem *reg_base; |
178 | struct loongson_gpio_chip *lgpio; |
179 | struct device *dev = &pdev->dev; |
180 | |
181 | lgpio = devm_kzalloc(dev, size: sizeof(*lgpio), GFP_KERNEL); |
182 | if (!lgpio) |
183 | return -ENOMEM; |
184 | |
185 | lgpio->chip_data = device_get_match_data(dev); |
186 | |
187 | reg_base = devm_platform_ioremap_resource(pdev, index: 0); |
188 | if (IS_ERR(ptr: reg_base)) |
189 | return PTR_ERR(ptr: reg_base); |
190 | |
191 | return loongson_gpio_init(dev, lgpio, reg_base); |
192 | } |
193 | |
194 | static const struct loongson_gpio_chip_data loongson_gpio_ls2k_data = { |
195 | .label = "ls2k_gpio" , |
196 | .mode = BIT_CTRL_MODE, |
197 | .conf_offset = 0x0, |
198 | .in_offset = 0x20, |
199 | .out_offset = 0x10, |
200 | .inten_offset = 0x30, |
201 | }; |
202 | |
203 | static const struct loongson_gpio_chip_data loongson_gpio_ls2k0500_data0 = { |
204 | .label = "ls2k0500_gpio" , |
205 | .mode = BIT_CTRL_MODE, |
206 | .conf_offset = 0x0, |
207 | .in_offset = 0x8, |
208 | .out_offset = 0x10, |
209 | .inten_offset = 0xb0, |
210 | }; |
211 | |
212 | static const struct loongson_gpio_chip_data loongson_gpio_ls2k0500_data1 = { |
213 | .label = "ls2k0500_gpio" , |
214 | .mode = BIT_CTRL_MODE, |
215 | .conf_offset = 0x0, |
216 | .in_offset = 0x8, |
217 | .out_offset = 0x10, |
218 | .inten_offset = 0x98, |
219 | }; |
220 | |
221 | static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data0 = { |
222 | .label = "ls2k2000_gpio" , |
223 | .mode = BIT_CTRL_MODE, |
224 | .conf_offset = 0x0, |
225 | .in_offset = 0xc, |
226 | .out_offset = 0x8, |
227 | }; |
228 | |
229 | static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data1 = { |
230 | .label = "ls2k2000_gpio" , |
231 | .mode = BIT_CTRL_MODE, |
232 | .conf_offset = 0x0, |
233 | .in_offset = 0x20, |
234 | .out_offset = 0x10, |
235 | }; |
236 | |
237 | static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data2 = { |
238 | .label = "ls2k2000_gpio" , |
239 | .mode = BIT_CTRL_MODE, |
240 | .conf_offset = 0x84, |
241 | .in_offset = 0x88, |
242 | .out_offset = 0x80, |
243 | }; |
244 | |
245 | static const struct loongson_gpio_chip_data loongson_gpio_ls3a5000_data = { |
246 | .label = "ls3a5000_gpio" , |
247 | .mode = BIT_CTRL_MODE, |
248 | .conf_offset = 0x0, |
249 | .in_offset = 0xc, |
250 | .out_offset = 0x8, |
251 | }; |
252 | |
253 | static const struct loongson_gpio_chip_data loongson_gpio_ls7a_data = { |
254 | .label = "ls7a_gpio" , |
255 | .mode = BYTE_CTRL_MODE, |
256 | .conf_offset = 0x800, |
257 | .in_offset = 0xa00, |
258 | .out_offset = 0x900, |
259 | }; |
260 | |
261 | static const struct of_device_id loongson_gpio_of_match[] = { |
262 | { |
263 | .compatible = "loongson,ls2k-gpio" , |
264 | .data = &loongson_gpio_ls2k_data, |
265 | }, |
266 | { |
267 | .compatible = "loongson,ls2k0500-gpio0" , |
268 | .data = &loongson_gpio_ls2k0500_data0, |
269 | }, |
270 | { |
271 | .compatible = "loongson,ls2k0500-gpio1" , |
272 | .data = &loongson_gpio_ls2k0500_data1, |
273 | }, |
274 | { |
275 | .compatible = "loongson,ls2k2000-gpio0" , |
276 | .data = &loongson_gpio_ls2k2000_data0, |
277 | }, |
278 | { |
279 | .compatible = "loongson,ls2k2000-gpio1" , |
280 | .data = &loongson_gpio_ls2k2000_data1, |
281 | }, |
282 | { |
283 | .compatible = "loongson,ls2k2000-gpio2" , |
284 | .data = &loongson_gpio_ls2k2000_data2, |
285 | }, |
286 | { |
287 | .compatible = "loongson,ls3a5000-gpio" , |
288 | .data = &loongson_gpio_ls3a5000_data, |
289 | }, |
290 | { |
291 | .compatible = "loongson,ls7a-gpio" , |
292 | .data = &loongson_gpio_ls7a_data, |
293 | }, |
294 | {} |
295 | }; |
296 | MODULE_DEVICE_TABLE(of, loongson_gpio_of_match); |
297 | |
298 | static const struct acpi_device_id loongson_gpio_acpi_match[] = { |
299 | { |
300 | .id = "LOON0002" , |
301 | .driver_data = (kernel_ulong_t)&loongson_gpio_ls7a_data, |
302 | }, |
303 | { |
304 | .id = "LOON0007" , |
305 | .driver_data = (kernel_ulong_t)&loongson_gpio_ls3a5000_data, |
306 | }, |
307 | { |
308 | .id = "LOON000A" , |
309 | .driver_data = (kernel_ulong_t)&loongson_gpio_ls2k2000_data0, |
310 | }, |
311 | { |
312 | .id = "LOON000B" , |
313 | .driver_data = (kernel_ulong_t)&loongson_gpio_ls2k2000_data1, |
314 | }, |
315 | { |
316 | .id = "LOON000C" , |
317 | .driver_data = (kernel_ulong_t)&loongson_gpio_ls2k2000_data2, |
318 | }, |
319 | {} |
320 | }; |
321 | MODULE_DEVICE_TABLE(acpi, loongson_gpio_acpi_match); |
322 | |
323 | static struct platform_driver loongson_gpio_driver = { |
324 | .driver = { |
325 | .name = "loongson-gpio" , |
326 | .of_match_table = loongson_gpio_of_match, |
327 | .acpi_match_table = loongson_gpio_acpi_match, |
328 | }, |
329 | .probe = loongson_gpio_probe, |
330 | }; |
331 | |
332 | static int __init loongson_gpio_setup(void) |
333 | { |
334 | return platform_driver_register(&loongson_gpio_driver); |
335 | } |
336 | postcore_initcall(loongson_gpio_setup); |
337 | |
338 | MODULE_DESCRIPTION("Loongson gpio driver" ); |
339 | MODULE_LICENSE("GPL" ); |
340 | |