xref: /linux/drivers/tty/serial/serial_mctrl_gpio.c (revision b9762bebc6332b40c33e03dea03e30fa12d9e3ed)
1e3b3d0f5SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0+
284130aacSRichard Genoud /*
384130aacSRichard Genoud  * Helpers for controlling modem lines via GPIO
484130aacSRichard Genoud  *
584130aacSRichard Genoud  * Copyright (C) 2014 Paratronic S.A.
684130aacSRichard Genoud  */
784130aacSRichard Genoud 
884130aacSRichard Genoud #include <linux/err.h>
984130aacSRichard Genoud #include <linux/device.h>
10ce59e48fSUwe Kleine-König #include <linux/irq.h>
1184130aacSRichard Genoud #include <linux/gpio/consumer.h>
1293b88774SAlexander Shiyan #include <linux/termios.h>
13ce59e48fSUwe Kleine-König #include <linux/serial_core.h>
1482a3f87fSRomain Izard #include <linux/module.h>
1584130aacSRichard Genoud 
1684130aacSRichard Genoud #include "serial_mctrl_gpio.h"
1784130aacSRichard Genoud 
1884130aacSRichard Genoud struct mctrl_gpios {
19ce59e48fSUwe Kleine-König 	struct uart_port *port;
2084130aacSRichard Genoud 	struct gpio_desc *gpio[UART_GPIO_MAX];
21ce59e48fSUwe Kleine-König 	int irq[UART_GPIO_MAX];
22ce59e48fSUwe Kleine-König 	unsigned int mctrl_prev;
23ce59e48fSUwe Kleine-König 	bool mctrl_on;
2484130aacSRichard Genoud };
2584130aacSRichard Genoud 
2684130aacSRichard Genoud static const struct {
2784130aacSRichard Genoud 	const char *name;
2884130aacSRichard Genoud 	unsigned int mctrl;
2984130aacSRichard Genoud 	bool dir_out;
3084130aacSRichard Genoud } mctrl_gpios_desc[UART_GPIO_MAX] = {
3184130aacSRichard Genoud 	{ "cts", TIOCM_CTS, false, },
3284130aacSRichard Genoud 	{ "dsr", TIOCM_DSR, false, },
3384130aacSRichard Genoud 	{ "dcd", TIOCM_CD, false, },
3484130aacSRichard Genoud 	{ "rng", TIOCM_RNG, false, },
3584130aacSRichard Genoud 	{ "rts", TIOCM_RTS, true, },
3684130aacSRichard Genoud 	{ "dtr", TIOCM_DTR, true, },
3784130aacSRichard Genoud };
3884130aacSRichard Genoud 
3984130aacSRichard Genoud void mctrl_gpio_set(struct mctrl_gpios *gpios, unsigned int mctrl)
4084130aacSRichard Genoud {
4184130aacSRichard Genoud 	enum mctrl_gpio_idx i;
42834296a3SRojhalat Ibrahim 	struct gpio_desc *desc_array[UART_GPIO_MAX];
43*b9762bebSJanusz Krzysztofik 	DECLARE_BITMAP(values, UART_GPIO_MAX);
44834296a3SRojhalat Ibrahim 	unsigned int count = 0;
4584130aacSRichard Genoud 
46434be0aeSYegor Yefremov 	if (gpios == NULL)
47434be0aeSYegor Yefremov 		return;
48434be0aeSYegor Yefremov 
4984130aacSRichard Genoud 	for (i = 0; i < UART_GPIO_MAX; i++)
50445df7ffSUwe Kleine-König 		if (gpios->gpio[i] && mctrl_gpios_desc[i].dir_out) {
51834296a3SRojhalat Ibrahim 			desc_array[count] = gpios->gpio[i];
52*b9762bebSJanusz Krzysztofik 			__assign_bit(count, values,
53*b9762bebSJanusz Krzysztofik 				     mctrl & mctrl_gpios_desc[i].mctrl);
54834296a3SRojhalat Ibrahim 			count++;
55834296a3SRojhalat Ibrahim 		}
56*b9762bebSJanusz Krzysztofik 	gpiod_set_array_value(count, desc_array, values);
5784130aacSRichard Genoud }
5884130aacSRichard Genoud EXPORT_SYMBOL_GPL(mctrl_gpio_set);
5984130aacSRichard Genoud 
6084130aacSRichard Genoud struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios,
6184130aacSRichard Genoud 				      enum mctrl_gpio_idx gidx)
6284130aacSRichard Genoud {
6384130aacSRichard Genoud 	return gpios->gpio[gidx];
6484130aacSRichard Genoud }
6584130aacSRichard Genoud EXPORT_SYMBOL_GPL(mctrl_gpio_to_gpiod);
6684130aacSRichard Genoud 
6784130aacSRichard Genoud unsigned int mctrl_gpio_get(struct mctrl_gpios *gpios, unsigned int *mctrl)
6884130aacSRichard Genoud {
6984130aacSRichard Genoud 	enum mctrl_gpio_idx i;
7084130aacSRichard Genoud 
71434be0aeSYegor Yefremov 	if (gpios == NULL)
72434be0aeSYegor Yefremov 		return *mctrl;
73434be0aeSYegor Yefremov 
7484130aacSRichard Genoud 	for (i = 0; i < UART_GPIO_MAX; i++) {
759e9f079cSUwe Kleine-König 		if (gpios->gpio[i] && !mctrl_gpios_desc[i].dir_out) {
7684130aacSRichard Genoud 			if (gpiod_get_value(gpios->gpio[i]))
7784130aacSRichard Genoud 				*mctrl |= mctrl_gpios_desc[i].mctrl;
7884130aacSRichard Genoud 			else
7984130aacSRichard Genoud 				*mctrl &= ~mctrl_gpios_desc[i].mctrl;
8084130aacSRichard Genoud 		}
8184130aacSRichard Genoud 	}
8284130aacSRichard Genoud 
8384130aacSRichard Genoud 	return *mctrl;
8484130aacSRichard Genoud }
8584130aacSRichard Genoud EXPORT_SYMBOL_GPL(mctrl_gpio_get);
8684130aacSRichard Genoud 
87bf5cee68SYegor Yefremov unsigned int
88bf5cee68SYegor Yefremov mctrl_gpio_get_outputs(struct mctrl_gpios *gpios, unsigned int *mctrl)
89bf5cee68SYegor Yefremov {
90bf5cee68SYegor Yefremov 	enum mctrl_gpio_idx i;
91bf5cee68SYegor Yefremov 
92434be0aeSYegor Yefremov 	if (gpios == NULL)
93434be0aeSYegor Yefremov 		return *mctrl;
94434be0aeSYegor Yefremov 
95bf5cee68SYegor Yefremov 	for (i = 0; i < UART_GPIO_MAX; i++) {
96bf5cee68SYegor Yefremov 		if (gpios->gpio[i] && mctrl_gpios_desc[i].dir_out) {
97bf5cee68SYegor Yefremov 			if (gpiod_get_value(gpios->gpio[i]))
98bf5cee68SYegor Yefremov 				*mctrl |= mctrl_gpios_desc[i].mctrl;
99bf5cee68SYegor Yefremov 			else
100bf5cee68SYegor Yefremov 				*mctrl &= ~mctrl_gpios_desc[i].mctrl;
101bf5cee68SYegor Yefremov 		}
102bf5cee68SYegor Yefremov 	}
103bf5cee68SYegor Yefremov 
104bf5cee68SYegor Yefremov 	return *mctrl;
105bf5cee68SYegor Yefremov }
106bf5cee68SYegor Yefremov EXPORT_SYMBOL_GPL(mctrl_gpio_get_outputs);
107bf5cee68SYegor Yefremov 
1087d8c70d8SUwe Kleine-König struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev, unsigned int idx)
10984130aacSRichard Genoud {
11084130aacSRichard Genoud 	struct mctrl_gpios *gpios;
11184130aacSRichard Genoud 	enum mctrl_gpio_idx i;
11284130aacSRichard Genoud 
11384130aacSRichard Genoud 	gpios = devm_kzalloc(dev, sizeof(*gpios), GFP_KERNEL);
11484130aacSRichard Genoud 	if (!gpios)
11584130aacSRichard Genoud 		return ERR_PTR(-ENOMEM);
11684130aacSRichard Genoud 
11784130aacSRichard Genoud 	for (i = 0; i < UART_GPIO_MAX; i++) {
1181d267ea6SUwe Kleine-König 		enum gpiod_flags flags;
11984130aacSRichard Genoud 
12084130aacSRichard Genoud 		if (mctrl_gpios_desc[i].dir_out)
1211d267ea6SUwe Kleine-König 			flags = GPIOD_OUT_LOW;
12284130aacSRichard Genoud 		else
1231d267ea6SUwe Kleine-König 			flags = GPIOD_IN;
1241d267ea6SUwe Kleine-König 
1251d267ea6SUwe Kleine-König 		gpios->gpio[i] =
1261d267ea6SUwe Kleine-König 			devm_gpiod_get_index_optional(dev,
1271d267ea6SUwe Kleine-König 						      mctrl_gpios_desc[i].name,
1281d267ea6SUwe Kleine-König 						      idx, flags);
1291d267ea6SUwe Kleine-König 
1301d267ea6SUwe Kleine-König 		if (IS_ERR(gpios->gpio[i]))
13113bc2bb9SFabio Estevam 			return ERR_CAST(gpios->gpio[i]);
13284130aacSRichard Genoud 	}
13384130aacSRichard Genoud 
13484130aacSRichard Genoud 	return gpios;
13584130aacSRichard Genoud }
1367d8c70d8SUwe Kleine-König EXPORT_SYMBOL_GPL(mctrl_gpio_init_noauto);
13784130aacSRichard Genoud 
138ce59e48fSUwe Kleine-König #define MCTRL_ANY_DELTA (TIOCM_RI | TIOCM_DSR | TIOCM_CD | TIOCM_CTS)
139ce59e48fSUwe Kleine-König static irqreturn_t mctrl_gpio_irq_handle(int irq, void *context)
140ce59e48fSUwe Kleine-König {
141ce59e48fSUwe Kleine-König 	struct mctrl_gpios *gpios = context;
142ce59e48fSUwe Kleine-König 	struct uart_port *port = gpios->port;
143ce59e48fSUwe Kleine-König 	u32 mctrl = gpios->mctrl_prev;
144ce59e48fSUwe Kleine-König 	u32 mctrl_diff;
145d11df618SYegor Yefremov 	unsigned long flags;
146ce59e48fSUwe Kleine-König 
147ce59e48fSUwe Kleine-König 	mctrl_gpio_get(gpios, &mctrl);
148ce59e48fSUwe Kleine-König 
149d11df618SYegor Yefremov 	spin_lock_irqsave(&port->lock, flags);
150d11df618SYegor Yefremov 
151ce59e48fSUwe Kleine-König 	mctrl_diff = mctrl ^ gpios->mctrl_prev;
152ce59e48fSUwe Kleine-König 	gpios->mctrl_prev = mctrl;
153ce59e48fSUwe Kleine-König 
154ce59e48fSUwe Kleine-König 	if (mctrl_diff & MCTRL_ANY_DELTA && port->state != NULL) {
155ce59e48fSUwe Kleine-König 		if ((mctrl_diff & mctrl) & TIOCM_RI)
156ce59e48fSUwe Kleine-König 			port->icount.rng++;
157ce59e48fSUwe Kleine-König 
158ce59e48fSUwe Kleine-König 		if ((mctrl_diff & mctrl) & TIOCM_DSR)
159ce59e48fSUwe Kleine-König 			port->icount.dsr++;
160ce59e48fSUwe Kleine-König 
161ce59e48fSUwe Kleine-König 		if (mctrl_diff & TIOCM_CD)
162ce59e48fSUwe Kleine-König 			uart_handle_dcd_change(port, mctrl & TIOCM_CD);
163ce59e48fSUwe Kleine-König 
164ce59e48fSUwe Kleine-König 		if (mctrl_diff & TIOCM_CTS)
165ce59e48fSUwe Kleine-König 			uart_handle_cts_change(port, mctrl & TIOCM_CTS);
166ce59e48fSUwe Kleine-König 
167ce59e48fSUwe Kleine-König 		wake_up_interruptible(&port->state->port.delta_msr_wait);
168ce59e48fSUwe Kleine-König 	}
169ce59e48fSUwe Kleine-König 
170d11df618SYegor Yefremov 	spin_unlock_irqrestore(&port->lock, flags);
171d11df618SYegor Yefremov 
172ce59e48fSUwe Kleine-König 	return IRQ_HANDLED;
173ce59e48fSUwe Kleine-König }
174ce59e48fSUwe Kleine-König 
175ce59e48fSUwe Kleine-König struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx)
176ce59e48fSUwe Kleine-König {
177ce59e48fSUwe Kleine-König 	struct mctrl_gpios *gpios;
178ce59e48fSUwe Kleine-König 	enum mctrl_gpio_idx i;
179ce59e48fSUwe Kleine-König 
180ce59e48fSUwe Kleine-König 	gpios = mctrl_gpio_init_noauto(port->dev, idx);
181ce59e48fSUwe Kleine-König 	if (IS_ERR(gpios))
182ce59e48fSUwe Kleine-König 		return gpios;
183ce59e48fSUwe Kleine-König 
184ce59e48fSUwe Kleine-König 	gpios->port = port;
185ce59e48fSUwe Kleine-König 
186ce59e48fSUwe Kleine-König 	for (i = 0; i < UART_GPIO_MAX; ++i) {
187ce59e48fSUwe Kleine-König 		int ret;
188ce59e48fSUwe Kleine-König 
189ce59e48fSUwe Kleine-König 		if (!gpios->gpio[i] || mctrl_gpios_desc[i].dir_out)
190ce59e48fSUwe Kleine-König 			continue;
191ce59e48fSUwe Kleine-König 
192ce59e48fSUwe Kleine-König 		ret = gpiod_to_irq(gpios->gpio[i]);
193ce59e48fSUwe Kleine-König 		if (ret <= 0) {
194ce59e48fSUwe Kleine-König 			dev_err(port->dev,
195ce59e48fSUwe Kleine-König 				"failed to find corresponding irq for %s (idx=%d, err=%d)\n",
196ce59e48fSUwe Kleine-König 				mctrl_gpios_desc[i].name, idx, ret);
197ce59e48fSUwe Kleine-König 			return ERR_PTR(ret);
198ce59e48fSUwe Kleine-König 		}
199ce59e48fSUwe Kleine-König 		gpios->irq[i] = ret;
200ce59e48fSUwe Kleine-König 
201ce59e48fSUwe Kleine-König 		/* irqs should only be enabled in .enable_ms */
202ce59e48fSUwe Kleine-König 		irq_set_status_flags(gpios->irq[i], IRQ_NOAUTOEN);
203ce59e48fSUwe Kleine-König 
204ce59e48fSUwe Kleine-König 		ret = devm_request_irq(port->dev, gpios->irq[i],
205ce59e48fSUwe Kleine-König 				       mctrl_gpio_irq_handle,
206ce59e48fSUwe Kleine-König 				       IRQ_TYPE_EDGE_BOTH, dev_name(port->dev),
207ce59e48fSUwe Kleine-König 				       gpios);
208ce59e48fSUwe Kleine-König 		if (ret) {
209ce59e48fSUwe Kleine-König 			/* alternatively implement polling */
210ce59e48fSUwe Kleine-König 			dev_err(port->dev,
211ce59e48fSUwe Kleine-König 				"failed to request irq for %s (idx=%d, err=%d)\n",
212ce59e48fSUwe Kleine-König 				mctrl_gpios_desc[i].name, idx, ret);
213ce59e48fSUwe Kleine-König 			return ERR_PTR(ret);
214ce59e48fSUwe Kleine-König 		}
215ce59e48fSUwe Kleine-König 	}
216ce59e48fSUwe Kleine-König 
217ce59e48fSUwe Kleine-König 	return gpios;
218ce59e48fSUwe Kleine-König }
2194f71a2e0SUwe Kleine-König EXPORT_SYMBOL_GPL(mctrl_gpio_init);
220ce59e48fSUwe Kleine-König 
22184130aacSRichard Genoud void mctrl_gpio_free(struct device *dev, struct mctrl_gpios *gpios)
22284130aacSRichard Genoud {
22384130aacSRichard Genoud 	enum mctrl_gpio_idx i;
22484130aacSRichard Genoud 
225434be0aeSYegor Yefremov 	if (gpios == NULL)
226434be0aeSYegor Yefremov 		return;
227434be0aeSYegor Yefremov 
228ce59e48fSUwe Kleine-König 	for (i = 0; i < UART_GPIO_MAX; i++) {
229ce59e48fSUwe Kleine-König 		if (gpios->irq[i])
230ce59e48fSUwe Kleine-König 			devm_free_irq(gpios->port->dev, gpios->irq[i], gpios);
231ce59e48fSUwe Kleine-König 
232445df7ffSUwe Kleine-König 		if (gpios->gpio[i])
23384130aacSRichard Genoud 			devm_gpiod_put(dev, gpios->gpio[i]);
234ce59e48fSUwe Kleine-König 	}
23584130aacSRichard Genoud 	devm_kfree(dev, gpios);
23684130aacSRichard Genoud }
23784130aacSRichard Genoud EXPORT_SYMBOL_GPL(mctrl_gpio_free);
238ce59e48fSUwe Kleine-König 
239ce59e48fSUwe Kleine-König void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios)
240ce59e48fSUwe Kleine-König {
241ce59e48fSUwe Kleine-König 	enum mctrl_gpio_idx i;
242ce59e48fSUwe Kleine-König 
243434be0aeSYegor Yefremov 	if (gpios == NULL)
244434be0aeSYegor Yefremov 		return;
245434be0aeSYegor Yefremov 
246ce59e48fSUwe Kleine-König 	/* .enable_ms may be called multiple times */
247ce59e48fSUwe Kleine-König 	if (gpios->mctrl_on)
248ce59e48fSUwe Kleine-König 		return;
249ce59e48fSUwe Kleine-König 
250ce59e48fSUwe Kleine-König 	gpios->mctrl_on = true;
251ce59e48fSUwe Kleine-König 
252ce59e48fSUwe Kleine-König 	/* get initial status of modem lines GPIOs */
253ce59e48fSUwe Kleine-König 	mctrl_gpio_get(gpios, &gpios->mctrl_prev);
254ce59e48fSUwe Kleine-König 
255ce59e48fSUwe Kleine-König 	for (i = 0; i < UART_GPIO_MAX; ++i) {
256ce59e48fSUwe Kleine-König 		if (!gpios->irq[i])
257ce59e48fSUwe Kleine-König 			continue;
258ce59e48fSUwe Kleine-König 
259ce59e48fSUwe Kleine-König 		enable_irq(gpios->irq[i]);
260ce59e48fSUwe Kleine-König 	}
261ce59e48fSUwe Kleine-König }
262ce59e48fSUwe Kleine-König EXPORT_SYMBOL_GPL(mctrl_gpio_enable_ms);
263ce59e48fSUwe Kleine-König 
264ce59e48fSUwe Kleine-König void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios)
265ce59e48fSUwe Kleine-König {
266ce59e48fSUwe Kleine-König 	enum mctrl_gpio_idx i;
267ce59e48fSUwe Kleine-König 
268434be0aeSYegor Yefremov 	if (gpios == NULL)
269434be0aeSYegor Yefremov 		return;
270434be0aeSYegor Yefremov 
271ce59e48fSUwe Kleine-König 	if (!gpios->mctrl_on)
272ce59e48fSUwe Kleine-König 		return;
273ce59e48fSUwe Kleine-König 
274ce59e48fSUwe Kleine-König 	gpios->mctrl_on = false;
275ce59e48fSUwe Kleine-König 
276ce59e48fSUwe Kleine-König 	for (i = 0; i < UART_GPIO_MAX; ++i) {
277ce59e48fSUwe Kleine-König 		if (!gpios->irq[i])
278ce59e48fSUwe Kleine-König 			continue;
279ce59e48fSUwe Kleine-König 
280ce59e48fSUwe Kleine-König 		disable_irq(gpios->irq[i]);
281ce59e48fSUwe Kleine-König 	}
282ce59e48fSUwe Kleine-König }
2834f71a2e0SUwe Kleine-König EXPORT_SYMBOL_GPL(mctrl_gpio_disable_ms);
28482a3f87fSRomain Izard 
28582a3f87fSRomain Izard MODULE_LICENSE("GPL");
286