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

Commit e8faff73 authored by Claudio Scordino's avatar Claudio Scordino Committed by Russell King
Browse files

ARM: 6092/1: atmel_serial: support for RS485 communications



Final version of the patch that adds support for RS485 communications to the atmel_serial driver.

The patch has been already sent and discussed on both linux-kernel and linux-arm-kernel mailing lists several times.

Many people collaborated to improve and test the code:

Tested-by: default avatarSebastian Heutling <Sebastian.Heutling@who-ing.de>
Tested-by: default avatarBernhard Roth <br@pwrnet.de>
Reviewed-by: default avatarRyan Mallon <ryan@bluewatersys.com>
Signed-off-by: default avatarClaudio Scordino <claudio@evidence.eu.com>
Signed-off-by: default avatarMichael Trimarchi <michael@evidence.eu.com>
Signed-off-by: default avatarRick Bronson <rick@efn.org>
Signed-off-by: default avatarSebastian Heutling <Sebastian.Heutling@who-ing.de>
Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
parent aec9562f
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -53,6 +53,9 @@
#define TIOCGPTN	_IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */
#define TIOCSPTLCK	_IOW('T',0x31, int)  /* Lock/unlock Pty */

#define TIOCGRS485      0x542E
#define TIOCSRS485      0x542F

#define FIONCLEX	0x5450  /* these numbers need to be adjusted. */
#define FIOCLEX		0x5451
#define FIOASYNC	0x5452
+5 −3
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@
#include <linux/usb/atmel_usba_udc.h>
#include <linux/atmel-mci.h>
#include <sound/atmel-ac97c.h>
#include <linux/serial.h>

 /* USB Device */
struct at91_udc_data {
@@ -145,7 +146,8 @@ extern void __init __deprecated at91_init_serial(struct at91_uart_config *config
struct atmel_uart_data {
	short			use_dma_tx;	/* use transmit DMA? */
	short			use_dma_rx;	/* use receive DMA? */
	void __iomem	*regs;		/* virtual base address, if any */
	void __iomem		*regs;		/* virt. base address, if any */
	struct serial_rs485	rs485;		/* rs485 settings */
};
extern void __init at91_add_device_serial(void);

+173 −34
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@
#include <linux/dma-mapping.h>
#include <linux/atmel_pdc.h>
#include <linux/atmel_serial.h>
#include <linux/uaccess.h>

#include <asm/io.h>

@@ -59,6 +60,9 @@

#include <linux/serial_core.h>

static void atmel_start_rx(struct uart_port *port);
static void atmel_stop_rx(struct uart_port *port);

#ifdef CONFIG_SERIAL_ATMEL_TTYAT

/* Use device name ttyAT, major 204 and minor 154-169.  This is necessary if we
@@ -93,6 +97,7 @@
#define UART_GET_BRGR(port)	__raw_readl((port)->membase + ATMEL_US_BRGR)
#define UART_PUT_BRGR(port,v)	__raw_writel(v, (port)->membase + ATMEL_US_BRGR)
#define UART_PUT_RTOR(port,v)	__raw_writel(v, (port)->membase + ATMEL_US_RTOR)
#define UART_PUT_TTGR(port, v)	__raw_writel(v, (port)->membase + ATMEL_US_TTGR)

 /* PDC registers */
#define UART_PUT_PTCR(port,v)	__raw_writel(v, (port)->membase + ATMEL_PDC_PTCR)
@@ -147,6 +152,9 @@ struct atmel_uart_port {
	unsigned int		irq_status_prev;

	struct circ_buf		rx_ring;

	struct serial_rs485	rs485;		/* rs485 settings */
	unsigned int		tx_done_mask;
};

static struct atmel_uart_port atmel_ports[ATMEL_MAX_UART];
@@ -187,6 +195,46 @@ static bool atmel_use_dma_tx(struct uart_port *port)
}
#endif

/* Enable or disable the rs485 support */
void atmel_config_rs485(struct uart_port *port, struct serial_rs485 *rs485conf)
{
	struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
	unsigned int mode;

	spin_lock(&port->lock);

	/* Disable interrupts */
	UART_PUT_IDR(port, atmel_port->tx_done_mask);

	mode = UART_GET_MR(port);

	/* Resetting serial mode to RS232 (0x0) */
	mode &= ~ATMEL_US_USMODE;

	atmel_port->rs485 = *rs485conf;

	if (rs485conf->flags & SER_RS485_ENABLED) {
		dev_dbg(port->dev, "Setting UART to RS485\n");
		atmel_port->tx_done_mask = ATMEL_US_TXEMPTY;
		UART_PUT_TTGR(port, rs485conf->delay_rts_before_send);
		mode |= ATMEL_US_USMODE_RS485;
	} else {
		dev_dbg(port->dev, "Setting UART to RS232\n");
		if (atmel_use_dma_tx(port))
			atmel_port->tx_done_mask = ATMEL_US_ENDTX |
				ATMEL_US_TXBUFE;
		else
			atmel_port->tx_done_mask = ATMEL_US_TXRDY;
	}
	UART_PUT_MR(port, mode);

	/* Enable interrupts */
	UART_PUT_IER(port, atmel_port->tx_done_mask);

	spin_unlock(&port->lock);

}

/*
 * Return TIOCSER_TEMT when transmitter FIFO and Shift register is empty.
 */
@@ -202,6 +250,7 @@ static void atmel_set_mctrl(struct uart_port *port, u_int mctrl)
{
	unsigned int control = 0;
	unsigned int mode;
	struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);

#ifdef CONFIG_ARCH_AT91RM9200
	if (cpu_is_at91rm9200()) {
@@ -236,6 +285,17 @@ static void atmel_set_mctrl(struct uart_port *port, u_int mctrl)
		mode |= ATMEL_US_CHMODE_LOC_LOOP;
	else
		mode |= ATMEL_US_CHMODE_NORMAL;

	/* Resetting serial mode to RS232 (0x0) */
	mode &= ~ATMEL_US_USMODE;

	if (atmel_port->rs485.flags & SER_RS485_ENABLED) {
		dev_dbg(port->dev, "Setting UART to RS485\n");
		UART_PUT_TTGR(port, atmel_port->rs485.delay_rts_before_send);
		mode |= ATMEL_US_USMODE_RS485;
	} else {
		dev_dbg(port->dev, "Setting UART to RS232\n");
	}
	UART_PUT_MR(port, mode);
}

@@ -268,12 +328,17 @@ static u_int atmel_get_mctrl(struct uart_port *port)
 */
static void atmel_stop_tx(struct uart_port *port)
{
	struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);

	if (atmel_use_dma_tx(port)) {
		/* disable PDC transmit */
		UART_PUT_PTCR(port, ATMEL_PDC_TXTDIS);
		UART_PUT_IDR(port, ATMEL_US_ENDTX | ATMEL_US_TXBUFE);
	} else
		UART_PUT_IDR(port, ATMEL_US_TXRDY);
	}
	/* Disable interrupts */
	UART_PUT_IDR(port, atmel_port->tx_done_mask);

	if (atmel_port->rs485.flags & SER_RS485_ENABLED)
		atmel_start_rx(port);
}

/*
@@ -281,17 +346,39 @@ static void atmel_stop_tx(struct uart_port *port)
 */
static void atmel_start_tx(struct uart_port *port)
{
	struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);

	if (atmel_use_dma_tx(port)) {
		if (UART_GET_PTSR(port) & ATMEL_PDC_TXTEN)
			/* The transmitter is already running.  Yes, we
			   really need this.*/
			return;

		UART_PUT_IER(port, ATMEL_US_ENDTX | ATMEL_US_TXBUFE);
		if (atmel_port->rs485.flags & SER_RS485_ENABLED)
			atmel_stop_rx(port);

		/* re-enable PDC transmit */
		UART_PUT_PTCR(port, ATMEL_PDC_TXTEN);
	} else
		UART_PUT_IER(port, ATMEL_US_TXRDY);
	}
	/* Enable interrupts */
	UART_PUT_IER(port, atmel_port->tx_done_mask);
}

/*
 * start receiving - port is in process of being opened.
 */
static void atmel_start_rx(struct uart_port *port)
{
	UART_PUT_CR(port, ATMEL_US_RSTSTA);  /* reset status and receiver */

	if (atmel_use_dma_rx(port)) {
		/* enable PDC controller */
		UART_PUT_IER(port, ATMEL_US_ENDRX | ATMEL_US_TIMEOUT |
			port->read_status_mask);
		UART_PUT_PTCR(port, ATMEL_PDC_RXTEN);
	} else {
		UART_PUT_IER(port, ATMEL_US_RXRDY);
	}
}

/*
@@ -302,10 +389,12 @@ static void atmel_stop_rx(struct uart_port *port)
	if (atmel_use_dma_rx(port)) {
		/* disable PDC receive */
		UART_PUT_PTCR(port, ATMEL_PDC_RXTDIS);
		UART_PUT_IDR(port, ATMEL_US_ENDRX | ATMEL_US_TIMEOUT);
	} else
		UART_PUT_IDR(port, ATMEL_US_ENDRX | ATMEL_US_TIMEOUT |
			port->read_status_mask);
	} else {
		UART_PUT_IDR(port, ATMEL_US_RXRDY);
	}
}

/*
 * Enable modem status interrupts
@@ -428,8 +517,9 @@ static void atmel_rx_chars(struct uart_port *port)
static void atmel_tx_chars(struct uart_port *port)
{
	struct circ_buf *xmit = &port->state->xmit;
	struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);

	if (port->x_char && UART_GET_CSR(port) & ATMEL_US_TXRDY) {
	if (port->x_char && UART_GET_CSR(port) & atmel_port->tx_done_mask) {
		UART_PUT_CHAR(port, port->x_char);
		port->icount.tx++;
		port->x_char = 0;
@@ -437,7 +527,7 @@ static void atmel_tx_chars(struct uart_port *port)
	if (uart_circ_empty(xmit) || uart_tx_stopped(port))
		return;

	while (UART_GET_CSR(port) & ATMEL_US_TXRDY) {
	while (UART_GET_CSR(port) & atmel_port->tx_done_mask) {
		UART_PUT_CHAR(port, xmit->buf[xmit->tail]);
		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
		port->icount.tx++;
@@ -449,7 +539,8 @@ static void atmel_tx_chars(struct uart_port *port)
		uart_write_wakeup(port);

	if (!uart_circ_empty(xmit))
		UART_PUT_IER(port, ATMEL_US_TXRDY);
		/* Enable interrupts */
		UART_PUT_IER(port, atmel_port->tx_done_mask);
}

/*
@@ -501,20 +592,12 @@ atmel_handle_transmit(struct uart_port *port, unsigned int pending)
{
	struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);

	if (atmel_use_dma_tx(port)) {
		/* PDC transmit */
		if (pending & (ATMEL_US_ENDTX | ATMEL_US_TXBUFE)) {
			UART_PUT_IDR(port, ATMEL_US_ENDTX | ATMEL_US_TXBUFE);
			tasklet_schedule(&atmel_port->tasklet);
		}
	} else {
		/* Interrupt transmit */
		if (pending & ATMEL_US_TXRDY) {
			UART_PUT_IDR(port, ATMEL_US_TXRDY);
	if (pending & atmel_port->tx_done_mask) {
		/* Either PDC or interrupt transmission */
		UART_PUT_IDR(port, atmel_port->tx_done_mask);
		tasklet_schedule(&atmel_port->tasklet);
	}
}
}

/*
 * status flags interrupt handler.
@@ -590,9 +673,15 @@ static void atmel_tx_dma(struct uart_port *port)

		UART_PUT_TPR(port, pdc->dma_addr + xmit->tail);
		UART_PUT_TCR(port, count);
		/* re-enable PDC transmit and interrupts */
		/* re-enable PDC transmit */
		UART_PUT_PTCR(port, ATMEL_PDC_TXTEN);
		UART_PUT_IER(port, ATMEL_US_ENDTX | ATMEL_US_TXBUFE);
		/* Enable interrupts */
		UART_PUT_IER(port, atmel_port->tx_done_mask);
	} else {
		if (atmel_port->rs485.flags & SER_RS485_ENABLED) {
			/* DMA done, stop TX, start RX for RS485 */
			atmel_start_rx(port);
		}
	}

	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
@@ -1017,6 +1106,7 @@ static void atmel_set_termios(struct uart_port *port, struct ktermios *termios,
{
	unsigned long flags;
	unsigned int mode, imr, quot, baud;
	struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);

	/* Get current mode register */
	mode = UART_GET_MR(port) & ~(ATMEL_US_USCLKS | ATMEL_US_CHRL
@@ -1115,6 +1205,17 @@ static void atmel_set_termios(struct uart_port *port, struct ktermios *termios,
	/* disable receiver and transmitter */
	UART_PUT_CR(port, ATMEL_US_TXDIS | ATMEL_US_RXDIS);

	/* Resetting serial mode to RS232 (0x0) */
	mode &= ~ATMEL_US_USMODE;

	if (atmel_port->rs485.flags & SER_RS485_ENABLED) {
		dev_dbg(port->dev, "Setting UART to RS485\n");
		UART_PUT_TTGR(port, atmel_port->rs485.delay_rts_before_send);
		mode |= ATMEL_US_USMODE_RS485;
	} else {
		dev_dbg(port->dev, "Setting UART to RS232\n");
	}

	/* set the parity, stop bits and data size */
	UART_PUT_MR(port, mode);

@@ -1231,6 +1332,35 @@ static void atmel_poll_put_char(struct uart_port *port, unsigned char ch)
}
#endif

static int
atmel_ioctl(struct uart_port *port, unsigned int cmd, unsigned long arg)
{
	struct serial_rs485 rs485conf;

	switch (cmd) {
	case TIOCSRS485:
		if (copy_from_user(&rs485conf, (struct serial_rs485 *) arg,
					sizeof(rs485conf)))
			return -EFAULT;

		atmel_config_rs485(port, &rs485conf);
		break;

	case TIOCGRS485:
		if (copy_to_user((struct serial_rs485 *) arg,
					&(to_atmel_uart_port(port)->rs485),
					sizeof(rs485conf)))
			return -EFAULT;
		break;

	default:
		return -ENOIOCTLCMD;
	}
	return 0;
}



static struct uart_ops atmel_pops = {
	.tx_empty	= atmel_tx_empty,
	.set_mctrl	= atmel_set_mctrl,
@@ -1250,6 +1380,7 @@ static struct uart_ops atmel_pops = {
	.config_port	= atmel_config_port,
	.verify_port	= atmel_verify_port,
	.pm		= atmel_serial_pm,
	.ioctl		= atmel_ioctl,
#ifdef CONFIG_CONSOLE_POLL
	.poll_get_char	= atmel_poll_get_char,
	.poll_put_char	= atmel_poll_put_char,
@@ -1271,7 +1402,6 @@ static void __devinit atmel_init_port(struct atmel_uart_port *atmel_port,
	port->fifosize		= 1;
	port->line		= pdev->id;
	port->dev		= &pdev->dev;

	port->mapbase	= pdev->resource[0].start;
	port->irq	= pdev->resource[1].start;

@@ -1299,8 +1429,16 @@ static void __devinit atmel_init_port(struct atmel_uart_port *atmel_port,

	atmel_port->use_dma_rx = data->use_dma_rx;
	atmel_port->use_dma_tx = data->use_dma_tx;
	if (atmel_use_dma_tx(port))
	atmel_port->rs485	= data->rs485;
	/* Use TXEMPTY for interrupt when rs485 else TXRDY or ENDTX|TXBUFE */
	if (atmel_port->rs485.flags & SER_RS485_ENABLED)
		atmel_port->tx_done_mask = ATMEL_US_TXEMPTY;
	else if (atmel_use_dma_tx(port)) {
		port->fifosize = PDC_BUFFER_SIZE;
		atmel_port->tx_done_mask = ATMEL_US_ENDTX | ATMEL_US_TXBUFE;
	} else {
		atmel_port->tx_done_mask = ATMEL_US_TXRDY;
	}
}

/*
@@ -1334,6 +1472,7 @@ static void atmel_console_putchar(struct uart_port *port, int ch)
static void atmel_console_write(struct console *co, const char *s, u_int count)
{
	struct uart_port *port = &atmel_ports[co->index].uart;
	struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
	unsigned int status, imr;
	unsigned int pdc_tx;

@@ -1341,7 +1480,7 @@ static void atmel_console_write(struct console *co, const char *s, u_int count)
	 * First, save IMR and then disable interrupts
	 */
	imr = UART_GET_IMR(port);
	UART_PUT_IDR(port, ATMEL_US_RXRDY | ATMEL_US_TXRDY);
	UART_PUT_IDR(port, ATMEL_US_RXRDY | atmel_port->tx_done_mask);

	/* Store PDC transmit status and disable it */
	pdc_tx = UART_GET_PTSR(port) & ATMEL_PDC_TXTEN;