Commit | Line | Data |
---|---|---|
be8c8fac UKK |
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"); |