Commit | Line | Data |
---|---|---|
84130aac RG |
1 | /* |
2 | * Helpers for controlling modem lines via GPIO | |
3 | * | |
4 | * Copyright (C) 2014 Paratronic S.A. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
84130aac RG |
15 | */ |
16 | ||
17 | #include <linux/err.h> | |
18 | #include <linux/device.h> | |
ce59e48f | 19 | #include <linux/irq.h> |
84130aac | 20 | #include <linux/gpio/consumer.h> |
93b88774 | 21 | #include <linux/termios.h> |
ce59e48f | 22 | #include <linux/serial_core.h> |
82a3f87f | 23 | #include <linux/module.h> |
84130aac RG |
24 | |
25 | #include "serial_mctrl_gpio.h" | |
26 | ||
27 | struct mctrl_gpios { | |
ce59e48f | 28 | struct uart_port *port; |
84130aac | 29 | struct gpio_desc *gpio[UART_GPIO_MAX]; |
ce59e48f UKK |
30 | int irq[UART_GPIO_MAX]; |
31 | unsigned int mctrl_prev; | |
32 | bool mctrl_on; | |
84130aac RG |
33 | }; |
34 | ||
35 | static const struct { | |
36 | const char *name; | |
37 | unsigned int mctrl; | |
38 | bool dir_out; | |
39 | } mctrl_gpios_desc[UART_GPIO_MAX] = { | |
40 | { "cts", TIOCM_CTS, false, }, | |
41 | { "dsr", TIOCM_DSR, false, }, | |
42 | { "dcd", TIOCM_CD, false, }, | |
43 | { "rng", TIOCM_RNG, false, }, | |
44 | { "rts", TIOCM_RTS, true, }, | |
45 | { "dtr", TIOCM_DTR, true, }, | |
84130aac RG |
46 | }; |
47 | ||
48 | void mctrl_gpio_set(struct mctrl_gpios *gpios, unsigned int mctrl) | |
49 | { | |
50 | enum mctrl_gpio_idx i; | |
834296a3 RI |
51 | struct gpio_desc *desc_array[UART_GPIO_MAX]; |
52 | int value_array[UART_GPIO_MAX]; | |
53 | unsigned int count = 0; | |
84130aac | 54 | |
434be0ae YY |
55 | if (gpios == NULL) |
56 | return; | |
57 | ||
84130aac | 58 | for (i = 0; i < UART_GPIO_MAX; i++) |
445df7ff | 59 | if (gpios->gpio[i] && mctrl_gpios_desc[i].dir_out) { |
834296a3 RI |
60 | desc_array[count] = gpios->gpio[i]; |
61 | value_array[count] = !!(mctrl & mctrl_gpios_desc[i].mctrl); | |
62 | count++; | |
63 | } | |
3fff99bc | 64 | gpiod_set_array_value(count, desc_array, value_array); |
84130aac RG |
65 | } |
66 | EXPORT_SYMBOL_GPL(mctrl_gpio_set); | |
67 | ||
68 | struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios, | |
69 | enum mctrl_gpio_idx gidx) | |
70 | { | |
9e9f079c | 71 | return gpios->gpio[gidx]; |
84130aac RG |
72 | } |
73 | EXPORT_SYMBOL_GPL(mctrl_gpio_to_gpiod); | |
74 | ||
75 | unsigned int mctrl_gpio_get(struct mctrl_gpios *gpios, unsigned int *mctrl) | |
76 | { | |
77 | enum mctrl_gpio_idx i; | |
78 | ||
434be0ae YY |
79 | if (gpios == NULL) |
80 | return *mctrl; | |
81 | ||
84130aac | 82 | for (i = 0; i < UART_GPIO_MAX; i++) { |
9e9f079c | 83 | if (gpios->gpio[i] && !mctrl_gpios_desc[i].dir_out) { |
84130aac RG |
84 | if (gpiod_get_value(gpios->gpio[i])) |
85 | *mctrl |= mctrl_gpios_desc[i].mctrl; | |
86 | else | |
87 | *mctrl &= ~mctrl_gpios_desc[i].mctrl; | |
88 | } | |
89 | } | |
90 | ||
91 | return *mctrl; | |
92 | } | |
93 | EXPORT_SYMBOL_GPL(mctrl_gpio_get); | |
94 | ||
bf5cee68 YY |
95 | unsigned int |
96 | mctrl_gpio_get_outputs(struct mctrl_gpios *gpios, unsigned int *mctrl) | |
97 | { | |
98 | enum mctrl_gpio_idx i; | |
99 | ||
434be0ae YY |
100 | if (gpios == NULL) |
101 | return *mctrl; | |
102 | ||
bf5cee68 YY |
103 | for (i = 0; i < UART_GPIO_MAX; i++) { |
104 | if (gpios->gpio[i] && mctrl_gpios_desc[i].dir_out) { | |
105 | if (gpiod_get_value(gpios->gpio[i])) | |
106 | *mctrl |= mctrl_gpios_desc[i].mctrl; | |
107 | else | |
108 | *mctrl &= ~mctrl_gpios_desc[i].mctrl; | |
109 | } | |
110 | } | |
111 | ||
112 | return *mctrl; | |
113 | } | |
114 | EXPORT_SYMBOL_GPL(mctrl_gpio_get_outputs); | |
115 | ||
7d8c70d8 | 116 | struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev, unsigned int idx) |
84130aac RG |
117 | { |
118 | struct mctrl_gpios *gpios; | |
119 | enum mctrl_gpio_idx i; | |
84130aac RG |
120 | |
121 | gpios = devm_kzalloc(dev, sizeof(*gpios), GFP_KERNEL); | |
122 | if (!gpios) | |
123 | return ERR_PTR(-ENOMEM); | |
124 | ||
125 | for (i = 0; i < UART_GPIO_MAX; i++) { | |
1d267ea6 | 126 | enum gpiod_flags flags; |
84130aac RG |
127 | |
128 | if (mctrl_gpios_desc[i].dir_out) | |
1d267ea6 | 129 | flags = GPIOD_OUT_LOW; |
84130aac | 130 | else |
1d267ea6 UKK |
131 | flags = GPIOD_IN; |
132 | ||
133 | gpios->gpio[i] = | |
134 | devm_gpiod_get_index_optional(dev, | |
135 | mctrl_gpios_desc[i].name, | |
136 | idx, flags); | |
137 | ||
138 | if (IS_ERR(gpios->gpio[i])) | |
13bc2bb9 | 139 | return ERR_CAST(gpios->gpio[i]); |
84130aac RG |
140 | } |
141 | ||
142 | return gpios; | |
143 | } | |
7d8c70d8 | 144 | EXPORT_SYMBOL_GPL(mctrl_gpio_init_noauto); |
84130aac | 145 | |
ce59e48f UKK |
146 | #define MCTRL_ANY_DELTA (TIOCM_RI | TIOCM_DSR | TIOCM_CD | TIOCM_CTS) |
147 | static irqreturn_t mctrl_gpio_irq_handle(int irq, void *context) | |
148 | { | |
149 | struct mctrl_gpios *gpios = context; | |
150 | struct uart_port *port = gpios->port; | |
151 | u32 mctrl = gpios->mctrl_prev; | |
152 | u32 mctrl_diff; | |
d11df618 | 153 | unsigned long flags; |
ce59e48f UKK |
154 | |
155 | mctrl_gpio_get(gpios, &mctrl); | |
156 | ||
d11df618 YY |
157 | spin_lock_irqsave(&port->lock, flags); |
158 | ||
ce59e48f UKK |
159 | mctrl_diff = mctrl ^ gpios->mctrl_prev; |
160 | gpios->mctrl_prev = mctrl; | |
161 | ||
162 | if (mctrl_diff & MCTRL_ANY_DELTA && port->state != NULL) { | |
163 | if ((mctrl_diff & mctrl) & TIOCM_RI) | |
164 | port->icount.rng++; | |
165 | ||
166 | if ((mctrl_diff & mctrl) & TIOCM_DSR) | |
167 | port->icount.dsr++; | |
168 | ||
169 | if (mctrl_diff & TIOCM_CD) | |
170 | uart_handle_dcd_change(port, mctrl & TIOCM_CD); | |
171 | ||
172 | if (mctrl_diff & TIOCM_CTS) | |
173 | uart_handle_cts_change(port, mctrl & TIOCM_CTS); | |
174 | ||
175 | wake_up_interruptible(&port->state->port.delta_msr_wait); | |
176 | } | |
177 | ||
d11df618 YY |
178 | spin_unlock_irqrestore(&port->lock, flags); |
179 | ||
ce59e48f UKK |
180 | return IRQ_HANDLED; |
181 | } | |
182 | ||
183 | struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx) | |
184 | { | |
185 | struct mctrl_gpios *gpios; | |
186 | enum mctrl_gpio_idx i; | |
187 | ||
188 | gpios = mctrl_gpio_init_noauto(port->dev, idx); | |
189 | if (IS_ERR(gpios)) | |
190 | return gpios; | |
191 | ||
192 | gpios->port = port; | |
193 | ||
194 | for (i = 0; i < UART_GPIO_MAX; ++i) { | |
195 | int ret; | |
196 | ||
197 | if (!gpios->gpio[i] || mctrl_gpios_desc[i].dir_out) | |
198 | continue; | |
199 | ||
200 | ret = gpiod_to_irq(gpios->gpio[i]); | |
201 | if (ret <= 0) { | |
202 | dev_err(port->dev, | |
203 | "failed to find corresponding irq for %s (idx=%d, err=%d)\n", | |
204 | mctrl_gpios_desc[i].name, idx, ret); | |
205 | return ERR_PTR(ret); | |
206 | } | |
207 | gpios->irq[i] = ret; | |
208 | ||
209 | /* irqs should only be enabled in .enable_ms */ | |
210 | irq_set_status_flags(gpios->irq[i], IRQ_NOAUTOEN); | |
211 | ||
212 | ret = devm_request_irq(port->dev, gpios->irq[i], | |
213 | mctrl_gpio_irq_handle, | |
214 | IRQ_TYPE_EDGE_BOTH, dev_name(port->dev), | |
215 | gpios); | |
216 | if (ret) { | |
217 | /* alternatively implement polling */ | |
218 | dev_err(port->dev, | |
219 | "failed to request irq for %s (idx=%d, err=%d)\n", | |
220 | mctrl_gpios_desc[i].name, idx, ret); | |
221 | return ERR_PTR(ret); | |
222 | } | |
223 | } | |
224 | ||
225 | return gpios; | |
226 | } | |
4f71a2e0 | 227 | EXPORT_SYMBOL_GPL(mctrl_gpio_init); |
ce59e48f | 228 | |
84130aac RG |
229 | void mctrl_gpio_free(struct device *dev, struct mctrl_gpios *gpios) |
230 | { | |
231 | enum mctrl_gpio_idx i; | |
232 | ||
434be0ae YY |
233 | if (gpios == NULL) |
234 | return; | |
235 | ||
ce59e48f UKK |
236 | for (i = 0; i < UART_GPIO_MAX; i++) { |
237 | if (gpios->irq[i]) | |
238 | devm_free_irq(gpios->port->dev, gpios->irq[i], gpios); | |
239 | ||
445df7ff | 240 | if (gpios->gpio[i]) |
84130aac | 241 | devm_gpiod_put(dev, gpios->gpio[i]); |
ce59e48f | 242 | } |
84130aac RG |
243 | devm_kfree(dev, gpios); |
244 | } | |
245 | EXPORT_SYMBOL_GPL(mctrl_gpio_free); | |
ce59e48f UKK |
246 | |
247 | void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios) | |
248 | { | |
249 | enum mctrl_gpio_idx i; | |
250 | ||
434be0ae YY |
251 | if (gpios == NULL) |
252 | return; | |
253 | ||
ce59e48f UKK |
254 | /* .enable_ms may be called multiple times */ |
255 | if (gpios->mctrl_on) | |
256 | return; | |
257 | ||
258 | gpios->mctrl_on = true; | |
259 | ||
260 | /* get initial status of modem lines GPIOs */ | |
261 | mctrl_gpio_get(gpios, &gpios->mctrl_prev); | |
262 | ||
263 | for (i = 0; i < UART_GPIO_MAX; ++i) { | |
264 | if (!gpios->irq[i]) | |
265 | continue; | |
266 | ||
267 | enable_irq(gpios->irq[i]); | |
268 | } | |
269 | } | |
270 | EXPORT_SYMBOL_GPL(mctrl_gpio_enable_ms); | |
271 | ||
272 | void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios) | |
273 | { | |
274 | enum mctrl_gpio_idx i; | |
275 | ||
434be0ae YY |
276 | if (gpios == NULL) |
277 | return; | |
278 | ||
ce59e48f UKK |
279 | if (!gpios->mctrl_on) |
280 | return; | |
281 | ||
282 | gpios->mctrl_on = false; | |
283 | ||
284 | for (i = 0; i < UART_GPIO_MAX; ++i) { | |
285 | if (!gpios->irq[i]) | |
286 | continue; | |
287 | ||
288 | disable_irq(gpios->irq[i]); | |
289 | } | |
290 | } | |
4f71a2e0 | 291 | EXPORT_SYMBOL_GPL(mctrl_gpio_disable_ms); |
82a3f87f RI |
292 | |
293 | MODULE_LICENSE("GPL"); |