Commit | Line | Data |
---|---|---|
7944d3b7 YZ |
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; | |
3feb70a6 | 29 | unsigned int inten_offset; |
7944d3b7 YZ |
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(bval, 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(bval, 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, 1); | |
67 | spin_unlock_irqrestore(&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, value); | |
79 | loongson_commit_direction(lgpio, pin, 0); | |
80 | spin_unlock_irqrestore(&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(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(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, value); | |
116 | spin_unlock_irqrestore(&lgpio->lock, flags); | |
117 | } | |
118 | ||
119 | static int loongson_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) | |
120 | { | |
3feb70a6 | 121 | unsigned int u; |
7944d3b7 | 122 | struct platform_device *pdev = to_platform_device(chip->parent); |
3feb70a6 YZ |
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(lgpio->reg_base + lgpio->chip_data->inten_offset + (offset / 32) * 4); | |
128 | u |= BIT(offset % 32); | |
129 | writel(u, lgpio->reg_base + lgpio->chip_data->inten_offset + (offset / 32) * 4); | |
130 | } else { | |
131 | writeb(1, lgpio->reg_base + lgpio->chip_data->inten_offset + offset); | |
132 | } | |
7944d3b7 YZ |
133 | |
134 | return platform_get_irq(pdev, offset); | |
135 | } | |
136 | ||
137 | static int loongson_gpio_init(struct device *dev, struct loongson_gpio_chip *lgpio, | |
3feb70a6 | 138 | void __iomem *reg_base) |
7944d3b7 YZ |
139 | { |
140 | int ret; | |
141 | u32 ngpios; | |
142 | ||
143 | lgpio->reg_base = reg_base; | |
7944d3b7 YZ |
144 | if (lgpio->chip_data->mode == BIT_CTRL_MODE) { |
145 | ret = bgpio_init(&lgpio->chip, dev, 8, | |
146 | lgpio->reg_base + lgpio->chip_data->in_offset, | |
147 | lgpio->reg_base + lgpio->chip_data->out_offset, | |
148 | NULL, NULL, | |
149 | lgpio->reg_base + lgpio->chip_data->conf_offset, | |
150 | 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; | |
3feb70a6 YZ |
162 | device_property_read_u32(dev, "ngpios", &ngpios); |
163 | lgpio->chip.ngpio = ngpios; | |
7944d3b7 YZ |
164 | spin_lock_init(&lgpio->lock); |
165 | } | |
166 | ||
7944d3b7 | 167 | lgpio->chip.label = lgpio->chip_data->label; |
3feb70a6 YZ |
168 | lgpio->chip.can_sleep = false; |
169 | if (lgpio->chip_data->inten_offset) | |
170 | lgpio->chip.to_irq = loongson_gpio_to_irq; | |
7944d3b7 YZ |
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; | |
7944d3b7 YZ |
179 | struct device *dev = &pdev->dev; |
180 | ||
181 | lgpio = devm_kzalloc(dev, 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, 0); | |
188 | if (IS_ERR(reg_base)) | |
189 | return PTR_ERR(reg_base); | |
190 | ||
3feb70a6 | 191 | return loongson_gpio_init(dev, lgpio, reg_base); |
7944d3b7 YZ |
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, | |
3feb70a6 YZ |
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, | |
7944d3b7 YZ |
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 | }, | |
3feb70a6 YZ |
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 | }, | |
7944d3b7 YZ |
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 | }, | |
3feb70a6 YZ |
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 | }, | |
7944d3b7 YZ |
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", | |
7944d3b7 YZ |
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"); |