Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f9e42397 authored by Janusz Uzycki's avatar Janusz Uzycki Committed by Greg Kroah-Hartman
Browse files

serial: mxs-auart: add interrupts for modem control lines



Handle CTS/DSR/RI/DCD GPIO interrupts in mxs-auart.

Signed-off-by: default avatarJanusz Uzycki <j.uzycki@elproma.com.pl>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 7c573d7e
Loading
Loading
Loading
Loading
+172 −2
Original line number Diff line number Diff line
@@ -42,7 +42,10 @@

#include <asm/cacheflush.h>

#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/err.h>
#include <linux/irq.h>
#include "serial_mctrl_gpio.h"

#define MXS_AUART_PORTS 5
@@ -146,6 +149,7 @@ struct mxs_auart_port {
#define MXS_AUART_DMA_RX_READY	3  /* bit 3 */
#define MXS_AUART_RTSCTS	4  /* bit 4 */
	unsigned long flags;
	unsigned int mctrl_prev;
	enum mxs_auart_type devtype;

	unsigned int irq;
@@ -163,6 +167,8 @@ struct mxs_auart_port {
	void *rx_dma_buf;

	struct mctrl_gpios	*gpios;
	int			gpio_irq[UART_GPIO_MAX];
	bool			ms_irq_enabled;
};

static struct platform_device_id mxs_auart_devtype[] = {
@@ -427,6 +433,29 @@ static void mxs_auart_set_mctrl(struct uart_port *u, unsigned mctrl)
	mctrl_gpio_set(s->gpios, mctrl);
}

#define MCTRL_ANY_DELTA        (TIOCM_RI | TIOCM_DSR | TIOCM_CD | TIOCM_CTS)
static u32 mxs_auart_modem_status(struct mxs_auart_port *s, u32 mctrl)
{
	u32 mctrl_diff;

	mctrl_diff = mctrl ^ s->mctrl_prev;
	s->mctrl_prev = mctrl;
	if (mctrl_diff & MCTRL_ANY_DELTA && s->ms_irq_enabled &&
						s->port.state != NULL) {
		if (mctrl_diff & TIOCM_RI)
			s->port.icount.rng++;
		if (mctrl_diff & TIOCM_DSR)
			s->port.icount.dsr++;
		if (mctrl_diff & TIOCM_CD)
			uart_handle_dcd_change(&s->port, mctrl & TIOCM_CD);
		if (mctrl_diff & TIOCM_CTS)
			uart_handle_cts_change(&s->port, mctrl & TIOCM_CTS);

		wake_up_interruptible(&s->port.state->port.delta_msr_wait);
	}
	return mctrl;
}

static u32 mxs_auart_get_mctrl(struct uart_port *u)
{
	struct mxs_auart_port *s = to_auart_port(u);
@@ -439,6 +468,64 @@ static u32 mxs_auart_get_mctrl(struct uart_port *u)
	return mctrl_gpio_get(s->gpios, &mctrl);
}

/*
 * Enable modem status interrupts
 */
static void mxs_auart_enable_ms(struct uart_port *port)
{
	struct mxs_auart_port *s = to_auart_port(port);

	/*
	 * Interrupt should not be enabled twice
	 */
	if (s->ms_irq_enabled)
		return;

	s->ms_irq_enabled = true;

	if (s->gpio_irq[UART_GPIO_CTS] >= 0)
		enable_irq(s->gpio_irq[UART_GPIO_CTS]);
	/* TODO: enable AUART_INTR_CTSMIEN otherwise */

	if (s->gpio_irq[UART_GPIO_DSR] >= 0)
		enable_irq(s->gpio_irq[UART_GPIO_DSR]);

	if (s->gpio_irq[UART_GPIO_RI] >= 0)
		enable_irq(s->gpio_irq[UART_GPIO_RI]);

	if (s->gpio_irq[UART_GPIO_DCD] >= 0)
		enable_irq(s->gpio_irq[UART_GPIO_DCD]);
}

/*
 * Disable modem status interrupts
 */
static void mxs_auart_disable_ms(struct uart_port *port)
{
	struct mxs_auart_port *s = to_auart_port(port);

	/*
	 * Interrupt should not be disabled twice
	 */
	if (!s->ms_irq_enabled)
		return;

	s->ms_irq_enabled = false;

	if (s->gpio_irq[UART_GPIO_CTS] >= 0)
		disable_irq(s->gpio_irq[UART_GPIO_CTS]);
	/* TODO: disable AUART_INTR_CTSMIEN otherwise */

	if (s->gpio_irq[UART_GPIO_DSR] >= 0)
		disable_irq(s->gpio_irq[UART_GPIO_DSR]);

	if (s->gpio_irq[UART_GPIO_RI] >= 0)
		disable_irq(s->gpio_irq[UART_GPIO_RI]);

	if (s->gpio_irq[UART_GPIO_DCD] >= 0)
		disable_irq(s->gpio_irq[UART_GPIO_DCD]);
}

static int mxs_auart_dma_prep_rx(struct mxs_auart_port *s);
static void dma_rx_callback(void *arg)
{
@@ -689,6 +776,12 @@ static void mxs_auart_settermios(struct uart_port *u,
			dev_err(s->dev, "We can not start up the DMA.\n");
		}
	}

	/* CTS flow-control and modem-status interrupts */
	if (UART_ENABLE_MS(u, termios->c_cflag))
		mxs_auart_enable_ms(u);
	else
		mxs_auart_disable_ms(u);
}

static irqreturn_t mxs_auart_irq_handle(int irq, void *context)
@@ -706,8 +799,18 @@ static irqreturn_t mxs_auart_irq_handle(int irq, void *context)
		| AUART_INTR_CTSMIS),
			s->port.membase + AUART_INTR_CLR);

	/*
	 * Dealing with GPIO interrupt
	 */
	if (irq == s->gpio_irq[UART_GPIO_CTS] ||
	    irq == s->gpio_irq[UART_GPIO_DCD] ||
	    irq == s->gpio_irq[UART_GPIO_DSR] ||
	    irq == s->gpio_irq[UART_GPIO_RI])
		mxs_auart_modem_status(s,
				mctrl_gpio_get(s->gpios, &s->mctrl_prev));

	if (istat & AUART_INTR_CTSMIS) {
		if (CTS_AT_AUART())
		if (CTS_AT_AUART() && s->ms_irq_enabled)
			uart_handle_cts_change(&s->port,
					stat & AUART_STAT_CTS);
		writel(AUART_INTR_CTSMIS,
@@ -770,6 +873,10 @@ static int mxs_auart_startup(struct uart_port *u)
	 */
	writel(AUART_LINECTRL_FEN, u->membase + AUART_LINECTRL_SET);

	/* get initial status of modem lines */
	mctrl_gpio_get(s->gpios, &s->mctrl_prev);

	s->ms_irq_enabled = false;
	return 0;
}

@@ -777,6 +884,8 @@ static void mxs_auart_shutdown(struct uart_port *u)
{
	struct mxs_auart_port *s = to_auart_port(u);

	mxs_auart_disable_ms(u);

	if (auart_dma_enabled(s))
		mxs_auart_dma_exit(s);

@@ -833,6 +942,7 @@ static struct uart_ops mxs_auart_ops = {
	.start_tx       = mxs_auart_start_tx,
	.stop_tx	= mxs_auart_stop_tx,
	.stop_rx	= mxs_auart_stop_rx,
	.enable_ms      = mxs_auart_enable_ms,
	.break_ctl      = mxs_auart_break_ctl,
	.set_mctrl	= mxs_auart_set_mctrl,
	.get_mctrl      = mxs_auart_get_mctrl,
@@ -1035,6 +1145,9 @@ static int serial_mxs_probe_dt(struct mxs_auart_port *s,

static bool mxs_auart_init_gpios(struct mxs_auart_port *s, struct device *dev)
{
	enum mctrl_gpio_idx i;
	struct gpio_desc *gpiod;

	s->gpios = mctrl_gpio_init(dev, 0);
	if (IS_ERR_OR_NULL(s->gpios))
		return false;
@@ -1047,9 +1160,54 @@ static bool mxs_auart_init_gpios(struct mxs_auart_port *s, struct device *dev)
		clear_bit(MXS_AUART_RTSCTS, &s->flags);
	}

	for (i = 0; i < UART_GPIO_MAX; i++) {
		gpiod = mctrl_gpio_to_gpiod(s->gpios, i);
		if (gpiod && (gpiod_get_direction(gpiod) == GPIOF_DIR_IN))
			s->gpio_irq[i] = gpiod_to_irq(gpiod);
		else
			s->gpio_irq[i] = -EINVAL;
	}

	return true;
}

static void mxs_auart_free_gpio_irq(struct mxs_auart_port *s)
{
	enum mctrl_gpio_idx i;

	for (i = 0; i < UART_GPIO_MAX; i++)
		if (s->gpio_irq[i] >= 0)
			free_irq(s->gpio_irq[i], s);
}

static int mxs_auart_request_gpio_irq(struct mxs_auart_port *s)
{
	int *irq = s->gpio_irq;
	enum mctrl_gpio_idx i;
	int err = 0;

	for (i = 0; (i < UART_GPIO_MAX) && !err; i++) {
		if (irq[i] < 0)
			continue;

		irq_set_status_flags(irq[i], IRQ_NOAUTOEN);
		err = request_irq(irq[i], mxs_auart_irq_handle,
				IRQ_TYPE_EDGE_BOTH, dev_name(s->dev), s);
		if (err)
			dev_err(s->dev, "%s - Can't get %d irq\n",
				__func__, irq[i]);
	}

	/*
	 * If something went wrong, rollback.
	 */
	while (err && (--i >= 0))
		if (irq[i] >= 0)
			free_irq(irq[i], s);

	return err;
}

static int mxs_auart_probe(struct platform_device *pdev)
{
	const struct of_device_id *of_id =
@@ -1097,6 +1255,8 @@ static int mxs_auart_probe(struct platform_device *pdev)
	s->port.type = PORT_IMX;
	s->port.dev = s->dev = &pdev->dev;

	s->mctrl_prev = 0;

	s->irq = platform_get_irq(pdev, 0);
	s->port.irq = s->irq;
	ret = request_irq(s->irq, mxs_auart_irq_handle, 0, dev_name(&pdev->dev), s);
@@ -1109,13 +1269,20 @@ static int mxs_auart_probe(struct platform_device *pdev)
		dev_err(&pdev->dev,
			"Failed to initialize GPIOs. The serial port may not work as expected\n");

	/*
	 * Get the GPIO lines IRQ
	 */
	ret = mxs_auart_request_gpio_irq(s);
	if (ret)
		goto out_free_irq;

	auart_port[s->port.line] = s;

	mxs_auart_reset(&s->port);

	ret = uart_add_one_port(&auart_driver, &s->port);
	if (ret)
		goto out_free_irq;
		goto out_free_gpio_irq;

	version = readl(s->port.membase + AUART_VERSION);
	dev_info(&pdev->dev, "Found APPUART %d.%d.%d\n",
@@ -1124,6 +1291,8 @@ static int mxs_auart_probe(struct platform_device *pdev)

	return 0;

out_free_gpio_irq:
	mxs_auart_free_gpio_irq(s);
out_free_irq:
	auart_port[pdev->id] = NULL;
	free_irq(s->irq, s);
@@ -1143,6 +1312,7 @@ static int mxs_auart_remove(struct platform_device *pdev)

	auart_port[pdev->id] = NULL;

	mxs_auart_free_gpio_irq(s);
	clk_put(s->clk);
	free_irq(s->irq, s);
	kfree(s);