Skip to content

Commit

Permalink
imx: serial: add IrDA support to serial driver
Browse files Browse the repository at this point in the history
Using the iMX serial driver with an IrDA device
needs extra peripheral settings and specific
timing depending on the transmitter circuitry used.

Signed-off-by: Fabian Godehardt <fg@emlix.com>
Signed-off-by: Oskar Schirmer <os@emlix.com>
Signed-off-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
ef-ge authored and torvalds committed Jun 11, 2009
1 parent 534fca0 commit b6e4913
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 19 deletions.
5 changes: 5 additions & 0 deletions arch/arm/plat-mxc/include/mach/imx-uart.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@
#define ASMARM_ARCH_UART_H

#define IMXUART_HAVE_RTSCTS (1<<0)
#define IMXUART_IRDA (1<<1)

struct imxuart_platform_data {
int (*init)(struct platform_device *pdev);
int (*exit)(struct platform_device *pdev);
unsigned int flags;
void (*irda_enable)(int enable);
unsigned int irda_inv_rx:1;
unsigned int irda_inv_tx:1;
unsigned short transceiver_delay;
};

#endif
195 changes: 176 additions & 19 deletions drivers/serial/imx.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
* Author: Sascha Hauer <sascha@saschahauer.de>
* Copyright (C) 2004 Pengutronix
*
* Copyright (C) 2009 emlix GmbH
* Author: Fabian Godehardt (added IrDA support for iMX)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
Expand Down Expand Up @@ -41,6 +44,7 @@
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/rational.h>

#include <asm/io.h>
Expand Down Expand Up @@ -149,6 +153,7 @@
#define UCR4_DREN (1<<0) /* Recv data ready interrupt enable */
#define UFCR_RXTL_SHF 0 /* Receiver trigger level shift */
#define UFCR_RFDIV (7<<7) /* Reference freq divider mask */
#define UFCR_RFDIV_REG(x) (((x) < 7 ? 6 - (x) : 6) << 7)
#define UFCR_TXTL_SHF 10 /* Transmitter trigger level shift */
#define USR1_PARITYERR (1<<15) /* Parity error interrupt flag */
#define USR1_RTSS (1<<14) /* RTS pin status */
Expand Down Expand Up @@ -213,9 +218,19 @@ struct imx_port {
unsigned int old_status;
int txirq,rxirq,rtsirq;
unsigned int have_rtscts:1;
unsigned int use_irda:1;
unsigned int irda_inv_rx:1;
unsigned int irda_inv_tx:1;
unsigned short trcv_delay; /* transceiver delay */
struct clk *clk;
};

#ifdef CONFIG_IRDA
#define USE_IRDA(sport) ((sport)->use_irda)
#else
#define USE_IRDA(sport) (0)
#endif

/*
* Handle any change of modem status signal since we were last called.
*/
Expand Down Expand Up @@ -269,6 +284,48 @@ static void imx_stop_tx(struct uart_port *port)
struct imx_port *sport = (struct imx_port *)port;
unsigned long temp;

if (USE_IRDA(sport)) {
/* half duplex - wait for end of transmission */
int n = 256;
while ((--n > 0) &&
!(readl(sport->port.membase + USR2) & USR2_TXDC)) {
udelay(5);
barrier();
}
/*
* irda transceiver - wait a bit more to avoid
* cutoff, hardware dependent
*/
udelay(sport->trcv_delay);

/*
* half duplex - reactivate receive mode,
* flush receive pipe echo crap
*/
if (readl(sport->port.membase + USR2) & USR2_TXDC) {
temp = readl(sport->port.membase + UCR1);
temp &= ~(UCR1_TXMPTYEN | UCR1_TRDYEN);
writel(temp, sport->port.membase + UCR1);

temp = readl(sport->port.membase + UCR4);
temp &= ~(UCR4_TCEN);
writel(temp, sport->port.membase + UCR4);

while (readl(sport->port.membase + URXD0) &
URXD_CHARRDY)
barrier();

temp = readl(sport->port.membase + UCR1);
temp |= UCR1_RRDYEN;
writel(temp, sport->port.membase + UCR1);

temp = readl(sport->port.membase + UCR4);
temp |= UCR4_DREN;
writel(temp, sport->port.membase + UCR4);
}
return;
}

temp = readl(sport->port.membase + UCR1);
writel(temp & ~UCR1_TXMPTYEN, sport->port.membase + UCR1);
}
Expand Down Expand Up @@ -324,9 +381,30 @@ static void imx_start_tx(struct uart_port *port)
struct imx_port *sport = (struct imx_port *)port;
unsigned long temp;

if (USE_IRDA(sport)) {
/* half duplex in IrDA mode; have to disable receive mode */
temp = readl(sport->port.membase + UCR4);
temp &= ~(UCR4_DREN);
writel(temp, sport->port.membase + UCR4);

temp = readl(sport->port.membase + UCR1);
temp &= ~(UCR1_RRDYEN);
writel(temp, sport->port.membase + UCR1);
}

temp = readl(sport->port.membase + UCR1);
writel(temp | UCR1_TXMPTYEN, sport->port.membase + UCR1);

if (USE_IRDA(sport)) {
temp = readl(sport->port.membase + UCR1);
temp |= UCR1_TRDYEN;
writel(temp, sport->port.membase + UCR1);

temp = readl(sport->port.membase + UCR4);
temp |= UCR4_TCEN;
writel(temp, sport->port.membase + UCR4);
}

if (readl(sport->port.membase + UTS) & UTS_TXEMPTY)
imx_transmit_buffer(sport);
}
Expand Down Expand Up @@ -536,12 +614,7 @@ static int imx_setup_ufcr(struct imx_port *sport, unsigned int mode)
if(!ufcr_rfdiv)
ufcr_rfdiv = 1;

if(ufcr_rfdiv >= 7)
ufcr_rfdiv = 6;
else
ufcr_rfdiv = 6 - ufcr_rfdiv;

val |= UFCR_RFDIV & (ufcr_rfdiv << 7);
val |= UFCR_RFDIV_REG(ufcr_rfdiv);

writel(val, sport->port.membase + UFCR);

Expand All @@ -560,8 +633,24 @@ static int imx_startup(struct uart_port *port)
* requesting IRQs
*/
temp = readl(sport->port.membase + UCR4);

if (USE_IRDA(sport))
temp |= UCR4_IRSC;

writel(temp & ~UCR4_DREN, sport->port.membase + UCR4);

if (USE_IRDA(sport)) {
/* reset fifo's and state machines */
int i = 100;
temp = readl(sport->port.membase + UCR2);
temp &= ~UCR2_SRST;
writel(temp, sport->port.membase + UCR2);
while (!(readl(sport->port.membase + UCR2) & UCR2_SRST) &&
(--i > 0)) {
udelay(1);
}
}

/*
* Allocate the IRQ(s) i.MX1 has three interrupts whereas later
* chips only have one interrupt.
Expand All @@ -577,12 +666,16 @@ static int imx_startup(struct uart_port *port)
if (retval)
goto error_out2;

retval = request_irq(sport->rtsirq, imx_rtsint,
(sport->rtsirq < MAX_INTERNAL_IRQ) ? 0 :
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
DRIVER_NAME, sport);
if (retval)
goto error_out3;
/* do not use RTS IRQ on IrDA */
if (!USE_IRDA(sport)) {
retval = request_irq(sport->rtsirq, imx_rtsint,
(sport->rtsirq < MAX_INTERNAL_IRQ) ? 0 :
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING,
DRIVER_NAME, sport);
if (retval)
goto error_out3;
}
} else {
retval = request_irq(sport->port.irq, imx_int, 0,
DRIVER_NAME, sport);
Expand All @@ -599,25 +692,66 @@ static int imx_startup(struct uart_port *port)

temp = readl(sport->port.membase + UCR1);
temp |= UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN;

if (USE_IRDA(sport)) {
temp |= UCR1_IREN;
temp &= ~(UCR1_RTSDEN);
}

writel(temp, sport->port.membase + UCR1);

temp = readl(sport->port.membase + UCR2);
temp |= (UCR2_RXEN | UCR2_TXEN);
writel(temp, sport->port.membase + UCR2);

if (USE_IRDA(sport)) {
/* clear RX-FIFO */
int i = 64;
while ((--i > 0) &&
(readl(sport->port.membase + URXD0) & URXD_CHARRDY)) {
barrier();
}
}

#if defined CONFIG_ARCH_MX2 || defined CONFIG_ARCH_MX3
temp = readl(sport->port.membase + UCR3);
temp |= UCR3_RXDMUXSEL;
writel(temp, sport->port.membase + UCR3);
#endif

if (USE_IRDA(sport)) {
temp = readl(sport->port.membase + UCR4);
if (sport->irda_inv_rx)
temp |= UCR4_INVR;
else
temp &= ~(UCR4_INVR);
writel(temp | UCR4_DREN, sport->port.membase + UCR4);

temp = readl(sport->port.membase + UCR3);
if (sport->irda_inv_tx)
temp |= UCR3_INVT;
else
temp &= ~(UCR3_INVT);
writel(temp, sport->port.membase + UCR3);
}

/*
* Enable modem status interrupts
*/
spin_lock_irqsave(&sport->port.lock,flags);
imx_enable_ms(&sport->port);
spin_unlock_irqrestore(&sport->port.lock,flags);

if (USE_IRDA(sport)) {
struct imxuart_platform_data *pdata;
pdata = sport->port.dev->platform_data;
sport->irda_inv_rx = pdata->irda_inv_rx;
sport->irda_inv_tx = pdata->irda_inv_tx;
sport->trcv_delay = pdata->transceiver_delay;
if (pdata->irda_enable)
pdata->irda_enable(1);
}

return 0;

error_out3:
Expand All @@ -639,6 +773,13 @@ static void imx_shutdown(struct uart_port *port)
temp &= ~(UCR2_TXEN);
writel(temp, sport->port.membase + UCR2);

if (USE_IRDA(sport)) {
struct imxuart_platform_data *pdata;
pdata = sport->port.dev->platform_data;
if (pdata->irda_enable)
pdata->irda_enable(0);
}

/*
* Stop our timer.
*/
Expand All @@ -648,7 +789,8 @@ static void imx_shutdown(struct uart_port *port)
* Free the interrupts
*/
if (sport->txirq > 0) {
free_irq(sport->rtsirq, sport);
if (!USE_IRDA(sport))
free_irq(sport->rtsirq, sport);
free_irq(sport->txirq, sport);
free_irq(sport->rxirq, sport);
} else
Expand All @@ -660,6 +802,9 @@ static void imx_shutdown(struct uart_port *port)

temp = readl(sport->port.membase + UCR1);
temp &= ~(UCR1_TXMPTYEN | UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN);
if (USE_IRDA(sport))
temp &= ~(UCR1_IREN);

writel(temp, sport->port.membase + UCR1);
}

Expand Down Expand Up @@ -768,11 +913,19 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
sport->port.membase + UCR2);
old_txrxen &= (UCR2_TXEN | UCR2_RXEN);

div = sport->port.uartclk / (baud * 16);
if (div > 7)
div = 7;
if (!div)
if (USE_IRDA(sport)) {
/*
* use maximum available submodule frequency to
* avoid missing short pulses due to low sampling rate
*/
div = 1;
} else {
div = sport->port.uartclk / (baud * 16);
if (div > 7)
div = 7;
if (!div)
div = 1;
}

rational_best_approximation(16 * div * baud, sport->port.uartclk,
1 << 16, 1 << 16, &num, &denom);
Expand All @@ -781,8 +934,7 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
denom -= 1;

ufcr = readl(sport->port.membase + UFCR);
ufcr = (ufcr & (~UFCR_RFDIV)) |
(div << 7);
ufcr = (ufcr & (~UFCR_RFDIV)) | UFCR_RFDIV_REG(div);
writel(ufcr, sport->port.membase + UFCR);

writel(num, sport->port.membase + UBIR);
Expand Down Expand Up @@ -1141,6 +1293,11 @@ static int serial_imx_probe(struct platform_device *pdev)
if (pdata && (pdata->flags & IMXUART_HAVE_RTSCTS))
sport->have_rtscts = 1;

#ifdef CONFIG_IRDA
if (pdata && (pdata->flags & IMXUART_IRDA))
sport->use_irda = 1;
#endif

if (pdata->init) {
ret = pdata->init(pdev);
if (ret)
Expand Down

0 comments on commit b6e4913

Please sign in to comment.