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 | |
84130aac | 55 | for (i = 0; i < UART_GPIO_MAX; i++) |
445df7ff | 56 | if (gpios->gpio[i] && mctrl_gpios_desc[i].dir_out) { |
834296a3 RI |
57 | desc_array[count] = gpios->gpio[i]; |
58 | value_array[count] = !!(mctrl & mctrl_gpios_desc[i].mctrl); | |
59 | count++; | |
60 | } | |
3fff99bc | 61 | gpiod_set_array_value(count, desc_array, value_array); |
84130aac RG |
62 | } |
63 | EXPORT_SYMBOL_GPL(mctrl_gpio_set); | |
64 | ||
65 | struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios, | |
66 | enum mctrl_gpio_idx gidx) | |
67 | { | |
9e9f079c | 68 | return gpios->gpio[gidx]; |
84130aac RG |
69 | } |
70 | EXPORT_SYMBOL_GPL(mctrl_gpio_to_gpiod); | |
71 | ||
72 | unsigned int mctrl_gpio_get(struct mctrl_gpios *gpios, unsigned int *mctrl) | |
73 | { | |
74 | enum mctrl_gpio_idx i; | |
75 | ||
84130aac | 76 | for (i = 0; i < UART_GPIO_MAX; i++) { |
9e9f079c | 77 | if (gpios->gpio[i] && !mctrl_gpios_desc[i].dir_out) { |
84130aac RG |
78 | if (gpiod_get_value(gpios->gpio[i])) |
79 | *mctrl |= mctrl_gpios_desc[i].mctrl; | |
80 | else | |
81 | *mctrl &= ~mctrl_gpios_desc[i].mctrl; | |
82 | } | |
83 | } | |
84 | ||
85 | return *mctrl; | |
86 | } | |
87 | EXPORT_SYMBOL_GPL(mctrl_gpio_get); | |
88 | ||
7d8c70d8 | 89 | struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev, unsigned int idx) |
84130aac RG |
90 | { |
91 | struct mctrl_gpios *gpios; | |
92 | enum mctrl_gpio_idx i; | |
84130aac RG |
93 | |
94 | gpios = devm_kzalloc(dev, sizeof(*gpios), GFP_KERNEL); | |
95 | if (!gpios) | |
96 | return ERR_PTR(-ENOMEM); | |
97 | ||
98 | for (i = 0; i < UART_GPIO_MAX; i++) { | |
1d267ea6 | 99 | enum gpiod_flags flags; |
84130aac RG |
100 | |
101 | if (mctrl_gpios_desc[i].dir_out) | |
1d267ea6 | 102 | flags = GPIOD_OUT_LOW; |
84130aac | 103 | else |
1d267ea6 UKK |
104 | flags = GPIOD_IN; |
105 | ||
106 | gpios->gpio[i] = | |
107 | devm_gpiod_get_index_optional(dev, | |
108 | mctrl_gpios_desc[i].name, | |
109 | idx, flags); | |
110 | ||
111 | if (IS_ERR(gpios->gpio[i])) | |
13bc2bb9 | 112 | return ERR_CAST(gpios->gpio[i]); |
84130aac RG |
113 | } |
114 | ||
115 | return gpios; | |
116 | } | |
7d8c70d8 | 117 | EXPORT_SYMBOL_GPL(mctrl_gpio_init_noauto); |
84130aac | 118 | |
ce59e48f UKK |
119 | #define MCTRL_ANY_DELTA (TIOCM_RI | TIOCM_DSR | TIOCM_CD | TIOCM_CTS) |
120 | static irqreturn_t mctrl_gpio_irq_handle(int irq, void *context) | |
121 | { | |
122 | struct mctrl_gpios *gpios = context; | |
123 | struct uart_port *port = gpios->port; | |
124 | u32 mctrl = gpios->mctrl_prev; | |
125 | u32 mctrl_diff; | |
d11df618 | 126 | unsigned long flags; |
ce59e48f UKK |
127 | |
128 | mctrl_gpio_get(gpios, &mctrl); | |
129 | ||
d11df618 YY |
130 | spin_lock_irqsave(&port->lock, flags); |
131 | ||
ce59e48f UKK |
132 | mctrl_diff = mctrl ^ gpios->mctrl_prev; |
133 | gpios->mctrl_prev = mctrl; | |
134 | ||
135 | if (mctrl_diff & MCTRL_ANY_DELTA && port->state != NULL) { | |
136 | if ((mctrl_diff & mctrl) & TIOCM_RI) | |
137 | port->icount.rng++; | |
138 | ||
139 | if ((mctrl_diff & mctrl) & TIOCM_DSR) | |
140 | port->icount.dsr++; | |
141 | ||
142 | if (mctrl_diff & TIOCM_CD) | |
143 | uart_handle_dcd_change(port, mctrl & TIOCM_CD); | |
144 | ||
145 | if (mctrl_diff & TIOCM_CTS) | |
146 | uart_handle_cts_change(port, mctrl & TIOCM_CTS); | |
147 | ||
148 | wake_up_interruptible(&port->state->port.delta_msr_wait); | |
149 | } | |
150 | ||
d11df618 YY |
151 | spin_unlock_irqrestore(&port->lock, flags); |
152 | ||
ce59e48f UKK |
153 | return IRQ_HANDLED; |
154 | } | |
155 | ||
156 | struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx) | |
157 | { | |
158 | struct mctrl_gpios *gpios; | |
159 | enum mctrl_gpio_idx i; | |
160 | ||
161 | gpios = mctrl_gpio_init_noauto(port->dev, idx); | |
162 | if (IS_ERR(gpios)) | |
163 | return gpios; | |
164 | ||
165 | gpios->port = port; | |
166 | ||
167 | for (i = 0; i < UART_GPIO_MAX; ++i) { | |
168 | int ret; | |
169 | ||
170 | if (!gpios->gpio[i] || mctrl_gpios_desc[i].dir_out) | |
171 | continue; | |
172 | ||
173 | ret = gpiod_to_irq(gpios->gpio[i]); | |
174 | if (ret <= 0) { | |
175 | dev_err(port->dev, | |
176 | "failed to find corresponding irq for %s (idx=%d, err=%d)\n", | |
177 | mctrl_gpios_desc[i].name, idx, ret); | |
178 | return ERR_PTR(ret); | |
179 | } | |
180 | gpios->irq[i] = ret; | |
181 | ||
182 | /* irqs should only be enabled in .enable_ms */ | |
183 | irq_set_status_flags(gpios->irq[i], IRQ_NOAUTOEN); | |
184 | ||
185 | ret = devm_request_irq(port->dev, gpios->irq[i], | |
186 | mctrl_gpio_irq_handle, | |
187 | IRQ_TYPE_EDGE_BOTH, dev_name(port->dev), | |
188 | gpios); | |
189 | if (ret) { | |
190 | /* alternatively implement polling */ | |
191 | dev_err(port->dev, | |
192 | "failed to request irq for %s (idx=%d, err=%d)\n", | |
193 | mctrl_gpios_desc[i].name, idx, ret); | |
194 | return ERR_PTR(ret); | |
195 | } | |
196 | } | |
197 | ||
198 | return gpios; | |
199 | } | |
4f71a2e0 | 200 | EXPORT_SYMBOL_GPL(mctrl_gpio_init); |
ce59e48f | 201 | |
84130aac RG |
202 | void mctrl_gpio_free(struct device *dev, struct mctrl_gpios *gpios) |
203 | { | |
204 | enum mctrl_gpio_idx i; | |
205 | ||
ce59e48f UKK |
206 | for (i = 0; i < UART_GPIO_MAX; i++) { |
207 | if (gpios->irq[i]) | |
208 | devm_free_irq(gpios->port->dev, gpios->irq[i], gpios); | |
209 | ||
445df7ff | 210 | if (gpios->gpio[i]) |
84130aac | 211 | devm_gpiod_put(dev, gpios->gpio[i]); |
ce59e48f | 212 | } |
84130aac RG |
213 | devm_kfree(dev, gpios); |
214 | } | |
215 | EXPORT_SYMBOL_GPL(mctrl_gpio_free); | |
ce59e48f UKK |
216 | |
217 | void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios) | |
218 | { | |
219 | enum mctrl_gpio_idx i; | |
220 | ||
221 | /* .enable_ms may be called multiple times */ | |
222 | if (gpios->mctrl_on) | |
223 | return; | |
224 | ||
225 | gpios->mctrl_on = true; | |
226 | ||
227 | /* get initial status of modem lines GPIOs */ | |
228 | mctrl_gpio_get(gpios, &gpios->mctrl_prev); | |
229 | ||
230 | for (i = 0; i < UART_GPIO_MAX; ++i) { | |
231 | if (!gpios->irq[i]) | |
232 | continue; | |
233 | ||
234 | enable_irq(gpios->irq[i]); | |
235 | } | |
236 | } | |
237 | EXPORT_SYMBOL_GPL(mctrl_gpio_enable_ms); | |
238 | ||
239 | void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios) | |
240 | { | |
241 | enum mctrl_gpio_idx i; | |
242 | ||
243 | if (!gpios->mctrl_on) | |
244 | return; | |
245 | ||
246 | gpios->mctrl_on = false; | |
247 | ||
248 | for (i = 0; i < UART_GPIO_MAX; ++i) { | |
249 | if (!gpios->irq[i]) | |
250 | continue; | |
251 | ||
252 | disable_irq(gpios->irq[i]); | |
253 | } | |
254 | } | |
4f71a2e0 | 255 | EXPORT_SYMBOL_GPL(mctrl_gpio_disable_ms); |
82a3f87f RI |
256 | |
257 | MODULE_LICENSE("GPL"); |