1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Helpers for controlling modem lines via GPIO |
4 | * |
5 | * Copyright (C) 2014 Paratronic S.A. |
6 | */ |
7 | |
8 | #include <linux/err.h> |
9 | #include <linux/device.h> |
10 | #include <linux/irq.h> |
11 | #include <linux/gpio/consumer.h> |
12 | #include <linux/termios.h> |
13 | #include <linux/serial_core.h> |
14 | #include <linux/module.h> |
15 | #include <linux/property.h> |
16 | |
17 | #include "serial_mctrl_gpio.h" |
18 | |
19 | struct mctrl_gpios { |
20 | struct uart_port *port; |
21 | struct gpio_desc *gpio[UART_GPIO_MAX]; |
22 | int irq[UART_GPIO_MAX]; |
23 | unsigned int mctrl_prev; |
24 | bool mctrl_on; |
25 | }; |
26 | |
27 | static const struct { |
28 | const char *name; |
29 | unsigned int mctrl; |
30 | enum gpiod_flags flags; |
31 | } mctrl_gpios_desc[UART_GPIO_MAX] = { |
32 | { "cts" , TIOCM_CTS, GPIOD_IN, }, |
33 | { "dsr" , TIOCM_DSR, GPIOD_IN, }, |
34 | { "dcd" , TIOCM_CD, GPIOD_IN, }, |
35 | { "rng" , TIOCM_RNG, GPIOD_IN, }, |
36 | { "rts" , TIOCM_RTS, GPIOD_OUT_LOW, }, |
37 | { "dtr" , TIOCM_DTR, GPIOD_OUT_LOW, }, |
38 | }; |
39 | |
40 | static bool mctrl_gpio_flags_is_dir_out(unsigned int idx) |
41 | { |
42 | return mctrl_gpios_desc[idx].flags & GPIOD_FLAGS_BIT_DIR_OUT; |
43 | } |
44 | |
45 | /** |
46 | * mctrl_gpio_set - set gpios according to mctrl state |
47 | * @gpios: gpios to set |
48 | * @mctrl: state to set |
49 | * |
50 | * Set the gpios according to the mctrl state. |
51 | */ |
52 | void mctrl_gpio_set(struct mctrl_gpios *gpios, unsigned int mctrl) |
53 | { |
54 | enum mctrl_gpio_idx i; |
55 | struct gpio_desc *desc_array[UART_GPIO_MAX]; |
56 | DECLARE_BITMAP(values, UART_GPIO_MAX); |
57 | unsigned int count = 0; |
58 | |
59 | if (gpios == NULL) |
60 | return; |
61 | |
62 | for (i = 0; i < UART_GPIO_MAX; i++) |
63 | if (gpios->gpio[i] && mctrl_gpio_flags_is_dir_out(idx: i)) { |
64 | desc_array[count] = gpios->gpio[i]; |
65 | __assign_bit(nr: count, addr: values, |
66 | value: mctrl & mctrl_gpios_desc[i].mctrl); |
67 | count++; |
68 | } |
69 | gpiod_set_array_value(array_size: count, desc_array, NULL, value_bitmap: values); |
70 | } |
71 | EXPORT_SYMBOL_GPL(mctrl_gpio_set); |
72 | |
73 | /** |
74 | * mctrl_gpio_to_gpiod - obtain gpio_desc of modem line index |
75 | * @gpios: gpios to look into |
76 | * @gidx: index of the modem line |
77 | * Returns: the gpio_desc structure associated to the modem line index |
78 | */ |
79 | struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios, |
80 | enum mctrl_gpio_idx gidx) |
81 | { |
82 | if (gpios == NULL) |
83 | return NULL; |
84 | |
85 | return gpios->gpio[gidx]; |
86 | } |
87 | EXPORT_SYMBOL_GPL(mctrl_gpio_to_gpiod); |
88 | |
89 | /** |
90 | * mctrl_gpio_get - update mctrl with the gpios values. |
91 | * @gpios: gpios to get the info from |
92 | * @mctrl: mctrl to set |
93 | * Returns: modified mctrl (the same value as in @mctrl) |
94 | * |
95 | * Update mctrl with the gpios values. |
96 | */ |
97 | unsigned int mctrl_gpio_get(struct mctrl_gpios *gpios, unsigned int *mctrl) |
98 | { |
99 | enum mctrl_gpio_idx i; |
100 | |
101 | if (gpios == NULL) |
102 | return *mctrl; |
103 | |
104 | for (i = 0; i < UART_GPIO_MAX; i++) { |
105 | if (gpios->gpio[i] && !mctrl_gpio_flags_is_dir_out(idx: i)) { |
106 | if (gpiod_get_value(desc: gpios->gpio[i])) |
107 | *mctrl |= mctrl_gpios_desc[i].mctrl; |
108 | else |
109 | *mctrl &= ~mctrl_gpios_desc[i].mctrl; |
110 | } |
111 | } |
112 | |
113 | return *mctrl; |
114 | } |
115 | EXPORT_SYMBOL_GPL(mctrl_gpio_get); |
116 | |
117 | unsigned int |
118 | mctrl_gpio_get_outputs(struct mctrl_gpios *gpios, unsigned int *mctrl) |
119 | { |
120 | enum mctrl_gpio_idx i; |
121 | |
122 | if (gpios == NULL) |
123 | return *mctrl; |
124 | |
125 | for (i = 0; i < UART_GPIO_MAX; i++) { |
126 | if (gpios->gpio[i] && mctrl_gpio_flags_is_dir_out(idx: i)) { |
127 | if (gpiod_get_value(desc: gpios->gpio[i])) |
128 | *mctrl |= mctrl_gpios_desc[i].mctrl; |
129 | else |
130 | *mctrl &= ~mctrl_gpios_desc[i].mctrl; |
131 | } |
132 | } |
133 | |
134 | return *mctrl; |
135 | } |
136 | EXPORT_SYMBOL_GPL(mctrl_gpio_get_outputs); |
137 | |
138 | struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev, unsigned int idx) |
139 | { |
140 | struct mctrl_gpios *gpios; |
141 | enum mctrl_gpio_idx i; |
142 | |
143 | gpios = devm_kzalloc(dev, size: sizeof(*gpios), GFP_KERNEL); |
144 | if (!gpios) |
145 | return ERR_PTR(error: -ENOMEM); |
146 | |
147 | for (i = 0; i < UART_GPIO_MAX; i++) { |
148 | char *gpio_str; |
149 | bool present; |
150 | |
151 | /* Check if GPIO property exists and continue if not */ |
152 | gpio_str = kasprintf(GFP_KERNEL, fmt: "%s-gpios" , |
153 | mctrl_gpios_desc[i].name); |
154 | if (!gpio_str) |
155 | continue; |
156 | |
157 | present = device_property_present(dev, propname: gpio_str); |
158 | kfree(objp: gpio_str); |
159 | if (!present) |
160 | continue; |
161 | |
162 | gpios->gpio[i] = |
163 | devm_gpiod_get_index_optional(dev, |
164 | con_id: mctrl_gpios_desc[i].name, |
165 | index: idx, |
166 | flags: mctrl_gpios_desc[i].flags); |
167 | |
168 | if (IS_ERR(ptr: gpios->gpio[i])) |
169 | return ERR_CAST(ptr: gpios->gpio[i]); |
170 | } |
171 | |
172 | return gpios; |
173 | } |
174 | EXPORT_SYMBOL_GPL(mctrl_gpio_init_noauto); |
175 | |
176 | #define MCTRL_ANY_DELTA (TIOCM_RI | TIOCM_DSR | TIOCM_CD | TIOCM_CTS) |
177 | static irqreturn_t mctrl_gpio_irq_handle(int irq, void *context) |
178 | { |
179 | struct mctrl_gpios *gpios = context; |
180 | struct uart_port *port = gpios->port; |
181 | u32 mctrl = gpios->mctrl_prev; |
182 | u32 mctrl_diff; |
183 | unsigned long flags; |
184 | |
185 | mctrl_gpio_get(gpios, &mctrl); |
186 | |
187 | uart_port_lock_irqsave(up: port, flags: &flags); |
188 | |
189 | mctrl_diff = mctrl ^ gpios->mctrl_prev; |
190 | gpios->mctrl_prev = mctrl; |
191 | |
192 | if (mctrl_diff & MCTRL_ANY_DELTA && port->state != NULL) { |
193 | if ((mctrl_diff & mctrl) & TIOCM_RI) |
194 | port->icount.rng++; |
195 | |
196 | if ((mctrl_diff & mctrl) & TIOCM_DSR) |
197 | port->icount.dsr++; |
198 | |
199 | if (mctrl_diff & TIOCM_CD) |
200 | uart_handle_dcd_change(uport: port, active: mctrl & TIOCM_CD); |
201 | |
202 | if (mctrl_diff & TIOCM_CTS) |
203 | uart_handle_cts_change(uport: port, active: mctrl & TIOCM_CTS); |
204 | |
205 | wake_up_interruptible(&port->state->port.delta_msr_wait); |
206 | } |
207 | |
208 | uart_port_unlock_irqrestore(up: port, flags); |
209 | |
210 | return IRQ_HANDLED; |
211 | } |
212 | |
213 | /** |
214 | * mctrl_gpio_init - initialize uart gpios |
215 | * @port: port to initialize gpios for |
216 | * @idx: index of the gpio in the @port's device |
217 | * |
218 | * This will get the {cts,rts,...}-gpios from device tree if they are present |
219 | * and request them, set direction etc, and return an allocated structure. |
220 | * `devm_*` functions are used, so there's no need to call mctrl_gpio_free(). |
221 | * As this sets up the irq handling, make sure to not handle changes to the |
222 | * gpio input lines in your driver, too. |
223 | */ |
224 | struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx) |
225 | { |
226 | struct mctrl_gpios *gpios; |
227 | enum mctrl_gpio_idx i; |
228 | |
229 | gpios = mctrl_gpio_init_noauto(port->dev, idx); |
230 | if (IS_ERR(ptr: gpios)) |
231 | return gpios; |
232 | |
233 | gpios->port = port; |
234 | |
235 | for (i = 0; i < UART_GPIO_MAX; ++i) { |
236 | int ret; |
237 | |
238 | if (!gpios->gpio[i] || mctrl_gpio_flags_is_dir_out(idx: i)) |
239 | continue; |
240 | |
241 | ret = gpiod_to_irq(desc: gpios->gpio[i]); |
242 | if (ret < 0) { |
243 | dev_err(port->dev, |
244 | "failed to find corresponding irq for %s (idx=%d, err=%d)\n" , |
245 | mctrl_gpios_desc[i].name, idx, ret); |
246 | return ERR_PTR(error: ret); |
247 | } |
248 | gpios->irq[i] = ret; |
249 | |
250 | /* irqs should only be enabled in .enable_ms */ |
251 | irq_set_status_flags(irq: gpios->irq[i], set: IRQ_NOAUTOEN); |
252 | |
253 | ret = devm_request_irq(dev: port->dev, irq: gpios->irq[i], |
254 | handler: mctrl_gpio_irq_handle, |
255 | irqflags: IRQ_TYPE_EDGE_BOTH, devname: dev_name(dev: port->dev), |
256 | dev_id: gpios); |
257 | if (ret) { |
258 | /* alternatively implement polling */ |
259 | dev_err(port->dev, |
260 | "failed to request irq for %s (idx=%d, err=%d)\n" , |
261 | mctrl_gpios_desc[i].name, idx, ret); |
262 | return ERR_PTR(error: ret); |
263 | } |
264 | } |
265 | |
266 | return gpios; |
267 | } |
268 | EXPORT_SYMBOL_GPL(mctrl_gpio_init); |
269 | |
270 | /** |
271 | * mctrl_gpio_free - explicitly free uart gpios |
272 | * @dev: uart port's device |
273 | * @gpios: gpios structure to be freed |
274 | * |
275 | * This will free the requested gpios in mctrl_gpio_init(). As `devm_*` |
276 | * functions are used, there's generally no need to call this function. |
277 | */ |
278 | void mctrl_gpio_free(struct device *dev, struct mctrl_gpios *gpios) |
279 | { |
280 | enum mctrl_gpio_idx i; |
281 | |
282 | if (gpios == NULL) |
283 | return; |
284 | |
285 | for (i = 0; i < UART_GPIO_MAX; i++) { |
286 | if (gpios->irq[i]) |
287 | devm_free_irq(dev: gpios->port->dev, irq: gpios->irq[i], dev_id: gpios); |
288 | |
289 | if (gpios->gpio[i]) |
290 | devm_gpiod_put(dev, desc: gpios->gpio[i]); |
291 | } |
292 | devm_kfree(dev, p: gpios); |
293 | } |
294 | EXPORT_SYMBOL_GPL(mctrl_gpio_free); |
295 | |
296 | /** |
297 | * mctrl_gpio_enable_ms - enable irqs and handling of changes to the ms lines |
298 | * @gpios: gpios to enable |
299 | */ |
300 | void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios) |
301 | { |
302 | enum mctrl_gpio_idx i; |
303 | |
304 | if (gpios == NULL) |
305 | return; |
306 | |
307 | /* .enable_ms may be called multiple times */ |
308 | if (gpios->mctrl_on) |
309 | return; |
310 | |
311 | gpios->mctrl_on = true; |
312 | |
313 | /* get initial status of modem lines GPIOs */ |
314 | mctrl_gpio_get(gpios, &gpios->mctrl_prev); |
315 | |
316 | for (i = 0; i < UART_GPIO_MAX; ++i) { |
317 | if (!gpios->irq[i]) |
318 | continue; |
319 | |
320 | enable_irq(irq: gpios->irq[i]); |
321 | } |
322 | } |
323 | EXPORT_SYMBOL_GPL(mctrl_gpio_enable_ms); |
324 | |
325 | /** |
326 | * mctrl_gpio_disable_ms - disable irqs and handling of changes to the ms lines |
327 | * @gpios: gpios to disable |
328 | */ |
329 | void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios) |
330 | { |
331 | enum mctrl_gpio_idx i; |
332 | |
333 | if (gpios == NULL) |
334 | return; |
335 | |
336 | if (!gpios->mctrl_on) |
337 | return; |
338 | |
339 | gpios->mctrl_on = false; |
340 | |
341 | for (i = 0; i < UART_GPIO_MAX; ++i) { |
342 | if (!gpios->irq[i]) |
343 | continue; |
344 | |
345 | disable_irq(irq: gpios->irq[i]); |
346 | } |
347 | } |
348 | EXPORT_SYMBOL_GPL(mctrl_gpio_disable_ms); |
349 | |
350 | void mctrl_gpio_enable_irq_wake(struct mctrl_gpios *gpios) |
351 | { |
352 | enum mctrl_gpio_idx i; |
353 | |
354 | if (!gpios) |
355 | return; |
356 | |
357 | if (!gpios->mctrl_on) |
358 | return; |
359 | |
360 | for (i = 0; i < UART_GPIO_MAX; ++i) { |
361 | if (!gpios->irq[i]) |
362 | continue; |
363 | |
364 | enable_irq_wake(irq: gpios->irq[i]); |
365 | } |
366 | } |
367 | EXPORT_SYMBOL_GPL(mctrl_gpio_enable_irq_wake); |
368 | |
369 | void mctrl_gpio_disable_irq_wake(struct mctrl_gpios *gpios) |
370 | { |
371 | enum mctrl_gpio_idx i; |
372 | |
373 | if (!gpios) |
374 | return; |
375 | |
376 | if (!gpios->mctrl_on) |
377 | return; |
378 | |
379 | for (i = 0; i < UART_GPIO_MAX; ++i) { |
380 | if (!gpios->irq[i]) |
381 | continue; |
382 | |
383 | disable_irq_wake(irq: gpios->irq[i]); |
384 | } |
385 | } |
386 | EXPORT_SYMBOL_GPL(mctrl_gpio_disable_irq_wake); |
387 | |
388 | MODULE_LICENSE("GPL" ); |
389 | |