xref: /linux/drivers/tty/serial/serial_mctrl_gpio.c (revision cbd90e746148339ddbf6b980438b0adad990321e)
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>
15d9948267SStefan Roese #include <linux/property.h>
1684130aacSRichard Genoud 
1784130aacSRichard Genoud #include "serial_mctrl_gpio.h"
1884130aacSRichard Genoud 
1984130aacSRichard Genoud struct mctrl_gpios {
20ce59e48fSUwe Kleine-König 	struct uart_port *port;
2184130aacSRichard Genoud 	struct gpio_desc *gpio[UART_GPIO_MAX];
22ce59e48fSUwe Kleine-König 	int irq[UART_GPIO_MAX];
23ce59e48fSUwe Kleine-König 	unsigned int mctrl_prev;
24ce59e48fSUwe Kleine-König 	bool mctrl_on;
2584130aacSRichard Genoud };
2684130aacSRichard Genoud 
2784130aacSRichard Genoud static const struct {
2884130aacSRichard Genoud 	const char *name;
2984130aacSRichard Genoud 	unsigned int mctrl;
304ad8e34dSAndy Shevchenko 	enum gpiod_flags flags;
3184130aacSRichard Genoud } mctrl_gpios_desc[UART_GPIO_MAX] = {
324ad8e34dSAndy Shevchenko 	{ "cts", TIOCM_CTS, GPIOD_IN, },
334ad8e34dSAndy Shevchenko 	{ "dsr", TIOCM_DSR, GPIOD_IN, },
344ad8e34dSAndy Shevchenko 	{ "dcd", TIOCM_CD,  GPIOD_IN, },
354ad8e34dSAndy Shevchenko 	{ "rng", TIOCM_RNG, GPIOD_IN, },
364ad8e34dSAndy Shevchenko 	{ "rts", TIOCM_RTS, GPIOD_OUT_LOW, },
374ad8e34dSAndy Shevchenko 	{ "dtr", TIOCM_DTR, GPIOD_OUT_LOW, },
3884130aacSRichard Genoud };
3984130aacSRichard Genoud 
404ad8e34dSAndy Shevchenko static bool mctrl_gpio_flags_is_dir_out(unsigned int idx)
414ad8e34dSAndy Shevchenko {
424ad8e34dSAndy Shevchenko 	return mctrl_gpios_desc[idx].flags & GPIOD_FLAGS_BIT_DIR_OUT;
434ad8e34dSAndy Shevchenko }
444ad8e34dSAndy Shevchenko 
4584130aacSRichard Genoud void mctrl_gpio_set(struct mctrl_gpios *gpios, unsigned int mctrl)
4684130aacSRichard Genoud {
4784130aacSRichard Genoud 	enum mctrl_gpio_idx i;
48834296a3SRojhalat Ibrahim 	struct gpio_desc *desc_array[UART_GPIO_MAX];
49b9762bebSJanusz Krzysztofik 	DECLARE_BITMAP(values, UART_GPIO_MAX);
50834296a3SRojhalat Ibrahim 	unsigned int count = 0;
5184130aacSRichard Genoud 
52434be0aeSYegor Yefremov 	if (gpios == NULL)
53434be0aeSYegor Yefremov 		return;
54434be0aeSYegor Yefremov 
5584130aacSRichard Genoud 	for (i = 0; i < UART_GPIO_MAX; i++)
564ad8e34dSAndy Shevchenko 		if (gpios->gpio[i] && mctrl_gpio_flags_is_dir_out(i)) {
57834296a3SRojhalat Ibrahim 			desc_array[count] = gpios->gpio[i];
58b9762bebSJanusz Krzysztofik 			__assign_bit(count, values,
59b9762bebSJanusz Krzysztofik 				     mctrl & mctrl_gpios_desc[i].mctrl);
60834296a3SRojhalat Ibrahim 			count++;
61834296a3SRojhalat Ibrahim 		}
6277588c14SJanusz Krzysztofik 	gpiod_set_array_value(count, desc_array, NULL, values);
6384130aacSRichard Genoud }
6484130aacSRichard Genoud EXPORT_SYMBOL_GPL(mctrl_gpio_set);
6584130aacSRichard Genoud 
6684130aacSRichard Genoud struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios,
6784130aacSRichard Genoud 				      enum mctrl_gpio_idx gidx)
6884130aacSRichard Genoud {
6937e3ab00SAdam Ford 	if (gpios == NULL)
7037e3ab00SAdam Ford 		return NULL;
7137e3ab00SAdam Ford 
7284130aacSRichard Genoud 	return gpios->gpio[gidx];
7384130aacSRichard Genoud }
7484130aacSRichard Genoud EXPORT_SYMBOL_GPL(mctrl_gpio_to_gpiod);
7584130aacSRichard Genoud 
7684130aacSRichard Genoud unsigned int mctrl_gpio_get(struct mctrl_gpios *gpios, unsigned int *mctrl)
7784130aacSRichard Genoud {
7884130aacSRichard Genoud 	enum mctrl_gpio_idx i;
7984130aacSRichard Genoud 
80434be0aeSYegor Yefremov 	if (gpios == NULL)
81434be0aeSYegor Yefremov 		return *mctrl;
82434be0aeSYegor Yefremov 
8384130aacSRichard Genoud 	for (i = 0; i < UART_GPIO_MAX; i++) {
844ad8e34dSAndy Shevchenko 		if (gpios->gpio[i] && !mctrl_gpio_flags_is_dir_out(i)) {
8584130aacSRichard Genoud 			if (gpiod_get_value(gpios->gpio[i]))
8684130aacSRichard Genoud 				*mctrl |= mctrl_gpios_desc[i].mctrl;
8784130aacSRichard Genoud 			else
8884130aacSRichard Genoud 				*mctrl &= ~mctrl_gpios_desc[i].mctrl;
8984130aacSRichard Genoud 		}
9084130aacSRichard Genoud 	}
9184130aacSRichard Genoud 
9284130aacSRichard Genoud 	return *mctrl;
9384130aacSRichard Genoud }
9484130aacSRichard Genoud EXPORT_SYMBOL_GPL(mctrl_gpio_get);
9584130aacSRichard Genoud 
96bf5cee68SYegor Yefremov unsigned int
97bf5cee68SYegor Yefremov mctrl_gpio_get_outputs(struct mctrl_gpios *gpios, unsigned int *mctrl)
98bf5cee68SYegor Yefremov {
99bf5cee68SYegor Yefremov 	enum mctrl_gpio_idx i;
100bf5cee68SYegor Yefremov 
101434be0aeSYegor Yefremov 	if (gpios == NULL)
102434be0aeSYegor Yefremov 		return *mctrl;
103434be0aeSYegor Yefremov 
104bf5cee68SYegor Yefremov 	for (i = 0; i < UART_GPIO_MAX; i++) {
1054ad8e34dSAndy Shevchenko 		if (gpios->gpio[i] && mctrl_gpio_flags_is_dir_out(i)) {
106bf5cee68SYegor Yefremov 			if (gpiod_get_value(gpios->gpio[i]))
107bf5cee68SYegor Yefremov 				*mctrl |= mctrl_gpios_desc[i].mctrl;
108bf5cee68SYegor Yefremov 			else
109bf5cee68SYegor Yefremov 				*mctrl &= ~mctrl_gpios_desc[i].mctrl;
110bf5cee68SYegor Yefremov 		}
111bf5cee68SYegor Yefremov 	}
112bf5cee68SYegor Yefremov 
113bf5cee68SYegor Yefremov 	return *mctrl;
114bf5cee68SYegor Yefremov }
115bf5cee68SYegor Yefremov EXPORT_SYMBOL_GPL(mctrl_gpio_get_outputs);
116bf5cee68SYegor Yefremov 
1177d8c70d8SUwe Kleine-König struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev, unsigned int idx)
11884130aacSRichard Genoud {
11984130aacSRichard Genoud 	struct mctrl_gpios *gpios;
12084130aacSRichard Genoud 	enum mctrl_gpio_idx i;
12184130aacSRichard Genoud 
12284130aacSRichard Genoud 	gpios = devm_kzalloc(dev, sizeof(*gpios), GFP_KERNEL);
12384130aacSRichard Genoud 	if (!gpios)
12484130aacSRichard Genoud 		return ERR_PTR(-ENOMEM);
12584130aacSRichard Genoud 
12684130aacSRichard Genoud 	for (i = 0; i < UART_GPIO_MAX; i++) {
127d9948267SStefan Roese 		char *gpio_str;
128d9948267SStefan Roese 		bool present;
129d9948267SStefan Roese 
130d9948267SStefan Roese 		/* Check if GPIO property exists and continue if not */
131d9948267SStefan Roese 		gpio_str = kasprintf(GFP_KERNEL, "%s-gpios",
132d9948267SStefan Roese 				     mctrl_gpios_desc[i].name);
133d9948267SStefan Roese 		if (!gpio_str)
134d9948267SStefan Roese 			continue;
135d9948267SStefan Roese 
136d9948267SStefan Roese 		present = device_property_present(dev, gpio_str);
137d9948267SStefan Roese 		kfree(gpio_str);
138d9948267SStefan Roese 		if (!present)
139d9948267SStefan Roese 			continue;
14084130aacSRichard Genoud 
1411d267ea6SUwe Kleine-König 		gpios->gpio[i] =
1421d267ea6SUwe Kleine-König 			devm_gpiod_get_index_optional(dev,
1431d267ea6SUwe Kleine-König 						      mctrl_gpios_desc[i].name,
1444ad8e34dSAndy Shevchenko 						      idx,
1454ad8e34dSAndy Shevchenko 						      mctrl_gpios_desc[i].flags);
1461d267ea6SUwe Kleine-König 
1471d267ea6SUwe Kleine-König 		if (IS_ERR(gpios->gpio[i]))
14813bc2bb9SFabio Estevam 			return ERR_CAST(gpios->gpio[i]);
14984130aacSRichard Genoud 	}
15084130aacSRichard Genoud 
15184130aacSRichard Genoud 	return gpios;
15284130aacSRichard Genoud }
1537d8c70d8SUwe Kleine-König EXPORT_SYMBOL_GPL(mctrl_gpio_init_noauto);
15484130aacSRichard Genoud 
155ce59e48fSUwe Kleine-König #define MCTRL_ANY_DELTA (TIOCM_RI | TIOCM_DSR | TIOCM_CD | TIOCM_CTS)
156ce59e48fSUwe Kleine-König static irqreturn_t mctrl_gpio_irq_handle(int irq, void *context)
157ce59e48fSUwe Kleine-König {
158ce59e48fSUwe Kleine-König 	struct mctrl_gpios *gpios = context;
159ce59e48fSUwe Kleine-König 	struct uart_port *port = gpios->port;
160ce59e48fSUwe Kleine-König 	u32 mctrl = gpios->mctrl_prev;
161ce59e48fSUwe Kleine-König 	u32 mctrl_diff;
162d11df618SYegor Yefremov 	unsigned long flags;
163ce59e48fSUwe Kleine-König 
164ce59e48fSUwe Kleine-König 	mctrl_gpio_get(gpios, &mctrl);
165ce59e48fSUwe Kleine-König 
166d11df618SYegor Yefremov 	spin_lock_irqsave(&port->lock, flags);
167d11df618SYegor Yefremov 
168ce59e48fSUwe Kleine-König 	mctrl_diff = mctrl ^ gpios->mctrl_prev;
169ce59e48fSUwe Kleine-König 	gpios->mctrl_prev = mctrl;
170ce59e48fSUwe Kleine-König 
171ce59e48fSUwe Kleine-König 	if (mctrl_diff & MCTRL_ANY_DELTA && port->state != NULL) {
172ce59e48fSUwe Kleine-König 		if ((mctrl_diff & mctrl) & TIOCM_RI)
173ce59e48fSUwe Kleine-König 			port->icount.rng++;
174ce59e48fSUwe Kleine-König 
175ce59e48fSUwe Kleine-König 		if ((mctrl_diff & mctrl) & TIOCM_DSR)
176ce59e48fSUwe Kleine-König 			port->icount.dsr++;
177ce59e48fSUwe Kleine-König 
178ce59e48fSUwe Kleine-König 		if (mctrl_diff & TIOCM_CD)
179ce59e48fSUwe Kleine-König 			uart_handle_dcd_change(port, mctrl & TIOCM_CD);
180ce59e48fSUwe Kleine-König 
181ce59e48fSUwe Kleine-König 		if (mctrl_diff & TIOCM_CTS)
182ce59e48fSUwe Kleine-König 			uart_handle_cts_change(port, mctrl & TIOCM_CTS);
183ce59e48fSUwe Kleine-König 
184ce59e48fSUwe Kleine-König 		wake_up_interruptible(&port->state->port.delta_msr_wait);
185ce59e48fSUwe Kleine-König 	}
186ce59e48fSUwe Kleine-König 
187d11df618SYegor Yefremov 	spin_unlock_irqrestore(&port->lock, flags);
188d11df618SYegor Yefremov 
189ce59e48fSUwe Kleine-König 	return IRQ_HANDLED;
190ce59e48fSUwe Kleine-König }
191ce59e48fSUwe Kleine-König 
192ce59e48fSUwe Kleine-König struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx)
193ce59e48fSUwe Kleine-König {
194ce59e48fSUwe Kleine-König 	struct mctrl_gpios *gpios;
195ce59e48fSUwe Kleine-König 	enum mctrl_gpio_idx i;
196ce59e48fSUwe Kleine-König 
197ce59e48fSUwe Kleine-König 	gpios = mctrl_gpio_init_noauto(port->dev, idx);
198ce59e48fSUwe Kleine-König 	if (IS_ERR(gpios))
199ce59e48fSUwe Kleine-König 		return gpios;
200ce59e48fSUwe Kleine-König 
201ce59e48fSUwe Kleine-König 	gpios->port = port;
202ce59e48fSUwe Kleine-König 
203ce59e48fSUwe Kleine-König 	for (i = 0; i < UART_GPIO_MAX; ++i) {
204ce59e48fSUwe Kleine-König 		int ret;
205ce59e48fSUwe Kleine-König 
2064ad8e34dSAndy Shevchenko 		if (!gpios->gpio[i] || mctrl_gpio_flags_is_dir_out(i))
207ce59e48fSUwe Kleine-König 			continue;
208ce59e48fSUwe Kleine-König 
209ce59e48fSUwe Kleine-König 		ret = gpiod_to_irq(gpios->gpio[i]);
210*cbd90e74SYueHaibing 		if (ret < 0) {
211ce59e48fSUwe Kleine-König 			dev_err(port->dev,
212ce59e48fSUwe Kleine-König 				"failed to find corresponding irq for %s (idx=%d, err=%d)\n",
213ce59e48fSUwe Kleine-König 				mctrl_gpios_desc[i].name, idx, ret);
214ce59e48fSUwe Kleine-König 			return ERR_PTR(ret);
215ce59e48fSUwe Kleine-König 		}
216ce59e48fSUwe Kleine-König 		gpios->irq[i] = ret;
217ce59e48fSUwe Kleine-König 
218ce59e48fSUwe Kleine-König 		/* irqs should only be enabled in .enable_ms */
219ce59e48fSUwe Kleine-König 		irq_set_status_flags(gpios->irq[i], IRQ_NOAUTOEN);
220ce59e48fSUwe Kleine-König 
221ce59e48fSUwe Kleine-König 		ret = devm_request_irq(port->dev, gpios->irq[i],
222ce59e48fSUwe Kleine-König 				       mctrl_gpio_irq_handle,
223ce59e48fSUwe Kleine-König 				       IRQ_TYPE_EDGE_BOTH, dev_name(port->dev),
224ce59e48fSUwe Kleine-König 				       gpios);
225ce59e48fSUwe Kleine-König 		if (ret) {
226ce59e48fSUwe Kleine-König 			/* alternatively implement polling */
227ce59e48fSUwe Kleine-König 			dev_err(port->dev,
228ce59e48fSUwe Kleine-König 				"failed to request irq for %s (idx=%d, err=%d)\n",
229ce59e48fSUwe Kleine-König 				mctrl_gpios_desc[i].name, idx, ret);
230ce59e48fSUwe Kleine-König 			return ERR_PTR(ret);
231ce59e48fSUwe Kleine-König 		}
232ce59e48fSUwe Kleine-König 	}
233ce59e48fSUwe Kleine-König 
234ce59e48fSUwe Kleine-König 	return gpios;
235ce59e48fSUwe Kleine-König }
2364f71a2e0SUwe Kleine-König EXPORT_SYMBOL_GPL(mctrl_gpio_init);
237ce59e48fSUwe Kleine-König 
23884130aacSRichard Genoud void mctrl_gpio_free(struct device *dev, struct mctrl_gpios *gpios)
23984130aacSRichard Genoud {
24084130aacSRichard Genoud 	enum mctrl_gpio_idx i;
24184130aacSRichard Genoud 
242434be0aeSYegor Yefremov 	if (gpios == NULL)
243434be0aeSYegor Yefremov 		return;
244434be0aeSYegor Yefremov 
245ce59e48fSUwe Kleine-König 	for (i = 0; i < UART_GPIO_MAX; i++) {
246ce59e48fSUwe Kleine-König 		if (gpios->irq[i])
247ce59e48fSUwe Kleine-König 			devm_free_irq(gpios->port->dev, gpios->irq[i], gpios);
248ce59e48fSUwe Kleine-König 
249445df7ffSUwe Kleine-König 		if (gpios->gpio[i])
25084130aacSRichard Genoud 			devm_gpiod_put(dev, gpios->gpio[i]);
251ce59e48fSUwe Kleine-König 	}
25284130aacSRichard Genoud 	devm_kfree(dev, gpios);
25384130aacSRichard Genoud }
25484130aacSRichard Genoud EXPORT_SYMBOL_GPL(mctrl_gpio_free);
255ce59e48fSUwe Kleine-König 
256ce59e48fSUwe Kleine-König void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios)
257ce59e48fSUwe Kleine-König {
258ce59e48fSUwe Kleine-König 	enum mctrl_gpio_idx i;
259ce59e48fSUwe Kleine-König 
260434be0aeSYegor Yefremov 	if (gpios == NULL)
261434be0aeSYegor Yefremov 		return;
262434be0aeSYegor Yefremov 
263ce59e48fSUwe Kleine-König 	/* .enable_ms may be called multiple times */
264ce59e48fSUwe Kleine-König 	if (gpios->mctrl_on)
265ce59e48fSUwe Kleine-König 		return;
266ce59e48fSUwe Kleine-König 
267ce59e48fSUwe Kleine-König 	gpios->mctrl_on = true;
268ce59e48fSUwe Kleine-König 
269ce59e48fSUwe Kleine-König 	/* get initial status of modem lines GPIOs */
270ce59e48fSUwe Kleine-König 	mctrl_gpio_get(gpios, &gpios->mctrl_prev);
271ce59e48fSUwe Kleine-König 
272ce59e48fSUwe Kleine-König 	for (i = 0; i < UART_GPIO_MAX; ++i) {
273ce59e48fSUwe Kleine-König 		if (!gpios->irq[i])
274ce59e48fSUwe Kleine-König 			continue;
275ce59e48fSUwe Kleine-König 
276ce59e48fSUwe Kleine-König 		enable_irq(gpios->irq[i]);
277ce59e48fSUwe Kleine-König 	}
278ce59e48fSUwe Kleine-König }
279ce59e48fSUwe Kleine-König EXPORT_SYMBOL_GPL(mctrl_gpio_enable_ms);
280ce59e48fSUwe Kleine-König 
281ce59e48fSUwe Kleine-König void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios)
282ce59e48fSUwe Kleine-König {
283ce59e48fSUwe Kleine-König 	enum mctrl_gpio_idx i;
284ce59e48fSUwe Kleine-König 
285434be0aeSYegor Yefremov 	if (gpios == NULL)
286434be0aeSYegor Yefremov 		return;
287434be0aeSYegor Yefremov 
288ce59e48fSUwe Kleine-König 	if (!gpios->mctrl_on)
289ce59e48fSUwe Kleine-König 		return;
290ce59e48fSUwe Kleine-König 
291ce59e48fSUwe Kleine-König 	gpios->mctrl_on = false;
292ce59e48fSUwe Kleine-König 
293ce59e48fSUwe Kleine-König 	for (i = 0; i < UART_GPIO_MAX; ++i) {
294ce59e48fSUwe Kleine-König 		if (!gpios->irq[i])
295ce59e48fSUwe Kleine-König 			continue;
296ce59e48fSUwe Kleine-König 
297ce59e48fSUwe Kleine-König 		disable_irq(gpios->irq[i]);
298ce59e48fSUwe Kleine-König 	}
299ce59e48fSUwe Kleine-König }
3004f71a2e0SUwe Kleine-König EXPORT_SYMBOL_GPL(mctrl_gpio_disable_ms);
30182a3f87fSRomain Izard 
30282a3f87fSRomain Izard MODULE_LICENSE("GPL");
303