diff options
Diffstat (limited to 'drivers/gpio/gpio-siox.c')
-rw-r--r-- | drivers/gpio/gpio-siox.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-siox.c b/drivers/gpio/gpio-siox.c new file mode 100644 index 000000000000..571b2a81c6de --- /dev/null +++ b/drivers/gpio/gpio-siox.c | |||
@@ -0,0 +1,293 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Copyright (C) 2015-2018 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de> | ||
4 | */ | ||
5 | |||
6 | #include <linux/module.h> | ||
7 | #include <linux/siox.h> | ||
8 | #include <linux/gpio/driver.h> | ||
9 | #include <linux/of.h> | ||
10 | |||
11 | struct gpio_siox_ddata { | ||
12 | struct gpio_chip gchip; | ||
13 | struct irq_chip ichip; | ||
14 | struct mutex lock; | ||
15 | u8 setdata[1]; | ||
16 | u8 getdata[3]; | ||
17 | |||
18 | spinlock_t irqlock; | ||
19 | u32 irq_enable; | ||
20 | u32 irq_status; | ||
21 | u32 irq_type[20]; | ||
22 | }; | ||
23 | |||
24 | /* | ||
25 | * Note that this callback only sets the value that is clocked out in the next | ||
26 | * cycle. | ||
27 | */ | ||
28 | static int gpio_siox_set_data(struct siox_device *sdevice, u8 status, u8 buf[]) | ||
29 | { | ||
30 | struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev); | ||
31 | |||
32 | mutex_lock(&ddata->lock); | ||
33 | buf[0] = ddata->setdata[0]; | ||
34 | mutex_unlock(&ddata->lock); | ||
35 | |||
36 | return 0; | ||
37 | } | ||
38 | |||
39 | static int gpio_siox_get_data(struct siox_device *sdevice, const u8 buf[]) | ||
40 | { | ||
41 | struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev); | ||
42 | size_t offset; | ||
43 | u32 trigger; | ||
44 | |||
45 | mutex_lock(&ddata->lock); | ||
46 | |||
47 | spin_lock_irq(&ddata->irqlock); | ||
48 | |||
49 | for (offset = 0; offset < 12; ++offset) { | ||
50 | unsigned int bitpos = 11 - offset; | ||
51 | unsigned int gpiolevel = buf[bitpos / 8] & (1 << bitpos % 8); | ||
52 | unsigned int prev_level = | ||
53 | ddata->getdata[bitpos / 8] & (1 << (bitpos % 8)); | ||
54 | u32 irq_type = ddata->irq_type[offset]; | ||
55 | |||
56 | if (gpiolevel) { | ||
57 | if ((irq_type & IRQ_TYPE_LEVEL_HIGH) || | ||
58 | ((irq_type & IRQ_TYPE_EDGE_RISING) && !prev_level)) | ||
59 | ddata->irq_status |= 1 << offset; | ||
60 | } else { | ||
61 | if ((irq_type & IRQ_TYPE_LEVEL_LOW) || | ||
62 | ((irq_type & IRQ_TYPE_EDGE_FALLING) && prev_level)) | ||
63 | ddata->irq_status |= 1 << offset; | ||
64 | } | ||
65 | } | ||
66 | |||
67 | trigger = ddata->irq_status & ddata->irq_enable; | ||
68 | |||
69 | spin_unlock_irq(&ddata->irqlock); | ||
70 | |||
71 | ddata->getdata[0] = buf[0]; | ||
72 | ddata->getdata[1] = buf[1]; | ||
73 | ddata->getdata[2] = buf[2]; | ||
74 | |||
75 | mutex_unlock(&ddata->lock); | ||
76 | |||
77 | for (offset = 0; offset < 12; ++offset) { | ||
78 | if (trigger & (1 << offset)) { | ||
79 | struct irq_domain *irqdomain = ddata->gchip.irq.domain; | ||
80 | unsigned int irq = irq_find_mapping(irqdomain, offset); | ||
81 | |||
82 | /* | ||
83 | * Conceptually handle_nested_irq should call the flow | ||
84 | * handler of the irq chip. But it doesn't, so we have | ||
85 | * to clean the irq_status here. | ||
86 | */ | ||
87 | spin_lock_irq(&ddata->irqlock); | ||
88 | ddata->irq_status &= ~(1 << offset); | ||
89 | spin_unlock_irq(&ddata->irqlock); | ||
90 | |||
91 | handle_nested_irq(irq); | ||
92 | } | ||
93 | } | ||
94 | |||
95 | return 0; | ||
96 | } | ||
97 | |||
98 | static void gpio_siox_irq_ack(struct irq_data *d) | ||
99 | { | ||
100 | struct irq_chip *ic = irq_data_get_irq_chip(d); | ||
101 | struct gpio_siox_ddata *ddata = | ||
102 | container_of(ic, struct gpio_siox_ddata, ichip); | ||
103 | |||
104 | spin_lock_irq(&ddata->irqlock); | ||
105 | ddata->irq_status &= ~(1 << d->hwirq); | ||
106 | spin_unlock_irq(&ddata->irqlock); | ||
107 | } | ||
108 | |||
109 | static void gpio_siox_irq_mask(struct irq_data *d) | ||
110 | { | ||
111 | struct irq_chip *ic = irq_data_get_irq_chip(d); | ||
112 | struct gpio_siox_ddata *ddata = | ||
113 | container_of(ic, struct gpio_siox_ddata, ichip); | ||
114 | |||
115 | spin_lock_irq(&ddata->irqlock); | ||
116 | ddata->irq_enable &= ~(1 << d->hwirq); | ||
117 | spin_unlock_irq(&ddata->irqlock); | ||
118 | } | ||
119 | |||
120 | static void gpio_siox_irq_unmask(struct irq_data *d) | ||
121 | { | ||
122 | struct irq_chip *ic = irq_data_get_irq_chip(d); | ||
123 | struct gpio_siox_ddata *ddata = | ||
124 | container_of(ic, struct gpio_siox_ddata, ichip); | ||
125 | |||
126 | spin_lock_irq(&ddata->irqlock); | ||
127 | ddata->irq_enable |= 1 << d->hwirq; | ||
128 | spin_unlock_irq(&ddata->irqlock); | ||
129 | } | ||
130 | |||
131 | static int gpio_siox_irq_set_type(struct irq_data *d, u32 type) | ||
132 | { | ||
133 | struct irq_chip *ic = irq_data_get_irq_chip(d); | ||
134 | struct gpio_siox_ddata *ddata = | ||
135 | container_of(ic, struct gpio_siox_ddata, ichip); | ||
136 | |||
137 | spin_lock_irq(&ddata->irqlock); | ||
138 | ddata->irq_type[d->hwirq] = type; | ||
139 | spin_unlock_irq(&ddata->irqlock); | ||
140 | |||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | static int gpio_siox_get(struct gpio_chip *chip, unsigned int offset) | ||
145 | { | ||
146 | struct gpio_siox_ddata *ddata = | ||
147 | container_of(chip, struct gpio_siox_ddata, gchip); | ||
148 | int ret; | ||
149 | |||
150 | mutex_lock(&ddata->lock); | ||
151 | |||
152 | if (offset >= 12) { | ||
153 | unsigned int bitpos = 19 - offset; | ||
154 | |||
155 | ret = ddata->setdata[0] & (1 << bitpos); | ||
156 | } else { | ||
157 | unsigned int bitpos = 11 - offset; | ||
158 | |||
159 | ret = ddata->getdata[bitpos / 8] & (1 << (bitpos % 8)); | ||
160 | } | ||
161 | |||
162 | mutex_unlock(&ddata->lock); | ||
163 | |||
164 | return ret; | ||
165 | } | ||
166 | |||
167 | static void gpio_siox_set(struct gpio_chip *chip, | ||
168 | unsigned int offset, int value) | ||
169 | { | ||
170 | struct gpio_siox_ddata *ddata = | ||
171 | container_of(chip, struct gpio_siox_ddata, gchip); | ||
172 | u8 mask = 1 << (19 - offset); | ||
173 | |||
174 | mutex_lock(&ddata->lock); | ||
175 | |||
176 | if (value) | ||
177 | ddata->setdata[0] |= mask; | ||
178 | else | ||
179 | ddata->setdata[0] &= ~mask; | ||
180 | |||
181 | mutex_unlock(&ddata->lock); | ||
182 | } | ||
183 | |||
184 | static int gpio_siox_direction_input(struct gpio_chip *chip, | ||
185 | unsigned int offset) | ||
186 | { | ||
187 | if (offset >= 12) | ||
188 | return -EINVAL; | ||
189 | |||
190 | return 0; | ||
191 | } | ||
192 | |||
193 | static int gpio_siox_direction_output(struct gpio_chip *chip, | ||
194 | unsigned int offset, int value) | ||
195 | { | ||
196 | if (offset < 12) | ||
197 | return -EINVAL; | ||
198 | |||
199 | gpio_siox_set(chip, offset, value); | ||
200 | return 0; | ||
201 | } | ||
202 | |||
203 | static int gpio_siox_get_direction(struct gpio_chip *chip, unsigned int offset) | ||
204 | { | ||
205 | if (offset < 12) | ||
206 | return 1; /* input */ | ||
207 | else | ||
208 | return 0; /* output */ | ||
209 | } | ||
210 | |||
211 | static int gpio_siox_probe(struct siox_device *sdevice) | ||
212 | { | ||
213 | struct gpio_siox_ddata *ddata; | ||
214 | int ret; | ||
215 | |||
216 | ddata = devm_kzalloc(&sdevice->dev, sizeof(*ddata), GFP_KERNEL); | ||
217 | if (!ddata) | ||
218 | return -ENOMEM; | ||
219 | |||
220 | dev_set_drvdata(&sdevice->dev, ddata); | ||
221 | |||
222 | mutex_init(&ddata->lock); | ||
223 | spin_lock_init(&ddata->irqlock); | ||
224 | |||
225 | ddata->gchip.base = -1; | ||
226 | ddata->gchip.can_sleep = 1; | ||
227 | ddata->gchip.parent = &sdevice->dev; | ||
228 | ddata->gchip.owner = THIS_MODULE; | ||
229 | ddata->gchip.get = gpio_siox_get; | ||
230 | ddata->gchip.set = gpio_siox_set; | ||
231 | ddata->gchip.direction_input = gpio_siox_direction_input; | ||
232 | ddata->gchip.direction_output = gpio_siox_direction_output; | ||
233 | ddata->gchip.get_direction = gpio_siox_get_direction; | ||
234 | ddata->gchip.ngpio = 20; | ||
235 | |||
236 | ddata->ichip.name = "siox-gpio"; | ||
237 | ddata->ichip.irq_ack = gpio_siox_irq_ack; | ||
238 | ddata->ichip.irq_mask = gpio_siox_irq_mask; | ||
239 | ddata->ichip.irq_unmask = gpio_siox_irq_unmask; | ||
240 | ddata->ichip.irq_set_type = gpio_siox_irq_set_type; | ||
241 | |||
242 | ret = gpiochip_add(&ddata->gchip); | ||
243 | if (ret) { | ||
244 | dev_err(&sdevice->dev, | ||
245 | "Failed to register gpio chip (%d)\n", ret); | ||
246 | goto err_gpiochip; | ||
247 | } | ||
248 | |||
249 | ret = gpiochip_irqchip_add(&ddata->gchip, &ddata->ichip, | ||
250 | 0, handle_level_irq, IRQ_TYPE_EDGE_RISING); | ||
251 | if (ret) { | ||
252 | dev_err(&sdevice->dev, | ||
253 | "Failed to register irq chip (%d)\n", ret); | ||
254 | err_gpiochip: | ||
255 | gpiochip_remove(&ddata->gchip); | ||
256 | } | ||
257 | |||
258 | return ret; | ||
259 | } | ||
260 | |||
261 | static int gpio_siox_remove(struct siox_device *sdevice) | ||
262 | { | ||
263 | struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev); | ||
264 | |||
265 | gpiochip_remove(&ddata->gchip); | ||
266 | return 0; | ||
267 | } | ||
268 | |||
269 | static struct siox_driver gpio_siox_driver = { | ||
270 | .probe = gpio_siox_probe, | ||
271 | .remove = gpio_siox_remove, | ||
272 | .set_data = gpio_siox_set_data, | ||
273 | .get_data = gpio_siox_get_data, | ||
274 | .driver = { | ||
275 | .name = "gpio-siox", | ||
276 | }, | ||
277 | }; | ||
278 | |||
279 | static int __init gpio_siox_init(void) | ||
280 | { | ||
281 | return siox_driver_register(&gpio_siox_driver); | ||
282 | } | ||
283 | module_init(gpio_siox_init); | ||
284 | |||
285 | static void __exit gpio_siox_exit(void) | ||
286 | { | ||
287 | siox_driver_unregister(&gpio_siox_driver); | ||
288 | } | ||
289 | module_exit(gpio_siox_exit); | ||
290 | |||
291 | MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>"); | ||
292 | MODULE_DESCRIPTION("SIOX gpio driver"); | ||
293 | MODULE_LICENSE("GPL v2"); | ||