Commit b6e49138 authored by Fabian Godehardt's avatar Fabian Godehardt Committed by Linus Torvalds

imx: serial: add IrDA support to serial driver

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: default avatarFabian Godehardt <fg@emlix.com>
Signed-off-by: default avatarOskar Schirmer <os@emlix.com>
Signed-off-by: default avatarAlan Cox <alan@linux.intel.com>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 534fca06
...@@ -20,11 +20,16 @@ ...@@ -20,11 +20,16 @@
#define ASMARM_ARCH_UART_H #define ASMARM_ARCH_UART_H
#define IMXUART_HAVE_RTSCTS (1<<0) #define IMXUART_HAVE_RTSCTS (1<<0)
#define IMXUART_IRDA (1<<1)
struct imxuart_platform_data { struct imxuart_platform_data {
int (*init)(struct platform_device *pdev); int (*init)(struct platform_device *pdev);
int (*exit)(struct platform_device *pdev); int (*exit)(struct platform_device *pdev);
unsigned int flags; 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 #endif
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
* Author: Sascha Hauer <sascha@saschahauer.de> * Author: Sascha Hauer <sascha@saschahauer.de>
* Copyright (C) 2004 Pengutronix * 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 * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 2 of the License, or
...@@ -41,6 +44,7 @@ ...@@ -41,6 +44,7 @@
#include <linux/serial_core.h> #include <linux/serial_core.h>
#include <linux/serial.h> #include <linux/serial.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/delay.h>
#include <linux/rational.h> #include <linux/rational.h>
#include <asm/io.h> #include <asm/io.h>
...@@ -149,6 +153,7 @@ ...@@ -149,6 +153,7 @@
#define UCR4_DREN (1<<0) /* Recv data ready interrupt enable */ #define UCR4_DREN (1<<0) /* Recv data ready interrupt enable */
#define UFCR_RXTL_SHF 0 /* Receiver trigger level shift */ #define UFCR_RXTL_SHF 0 /* Receiver trigger level shift */
#define UFCR_RFDIV (7<<7) /* Reference freq divider mask */ #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 UFCR_TXTL_SHF 10 /* Transmitter trigger level shift */
#define USR1_PARITYERR (1<<15) /* Parity error interrupt flag */ #define USR1_PARITYERR (1<<15) /* Parity error interrupt flag */
#define USR1_RTSS (1<<14) /* RTS pin status */ #define USR1_RTSS (1<<14) /* RTS pin status */
...@@ -213,9 +218,19 @@ struct imx_port { ...@@ -213,9 +218,19 @@ struct imx_port {
unsigned int old_status; unsigned int old_status;
int txirq,rxirq,rtsirq; int txirq,rxirq,rtsirq;
unsigned int have_rtscts:1; 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; 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. * Handle any change of modem status signal since we were last called.
*/ */
...@@ -269,6 +284,48 @@ static void imx_stop_tx(struct uart_port *port) ...@@ -269,6 +284,48 @@ static void imx_stop_tx(struct uart_port *port)
struct imx_port *sport = (struct imx_port *)port; struct imx_port *sport = (struct imx_port *)port;
unsigned long temp; 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); temp = readl(sport->port.membase + UCR1);
writel(temp & ~UCR1_TXMPTYEN, sport->port.membase + UCR1); writel(temp & ~UCR1_TXMPTYEN, sport->port.membase + UCR1);
} }
...@@ -324,9 +381,30 @@ static void imx_start_tx(struct uart_port *port) ...@@ -324,9 +381,30 @@ static void imx_start_tx(struct uart_port *port)
struct imx_port *sport = (struct imx_port *)port; struct imx_port *sport = (struct imx_port *)port;
unsigned long temp; 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); temp = readl(sport->port.membase + UCR1);
writel(temp | UCR1_TXMPTYEN, 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) if (readl(sport->port.membase + UTS) & UTS_TXEMPTY)
imx_transmit_buffer(sport); imx_transmit_buffer(sport);
} }
...@@ -536,12 +614,7 @@ static int imx_setup_ufcr(struct imx_port *sport, unsigned int mode) ...@@ -536,12 +614,7 @@ static int imx_setup_ufcr(struct imx_port *sport, unsigned int mode)
if(!ufcr_rfdiv) if(!ufcr_rfdiv)
ufcr_rfdiv = 1; ufcr_rfdiv = 1;
if(ufcr_rfdiv >= 7) val |= UFCR_RFDIV_REG(ufcr_rfdiv);
ufcr_rfdiv = 6;
else
ufcr_rfdiv = 6 - ufcr_rfdiv;
val |= UFCR_RFDIV & (ufcr_rfdiv << 7);
writel(val, sport->port.membase + UFCR); writel(val, sport->port.membase + UFCR);
...@@ -560,8 +633,24 @@ static int imx_startup(struct uart_port *port) ...@@ -560,8 +633,24 @@ static int imx_startup(struct uart_port *port)
* requesting IRQs * requesting IRQs
*/ */
temp = readl(sport->port.membase + UCR4); temp = readl(sport->port.membase + UCR4);
if (USE_IRDA(sport))
temp |= UCR4_IRSC;
writel(temp & ~UCR4_DREN, sport->port.membase + UCR4); 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 * Allocate the IRQ(s) i.MX1 has three interrupts whereas later
* chips only have one interrupt. * chips only have one interrupt.
...@@ -577,12 +666,16 @@ static int imx_startup(struct uart_port *port) ...@@ -577,12 +666,16 @@ static int imx_startup(struct uart_port *port)
if (retval) if (retval)
goto error_out2; goto error_out2;
retval = request_irq(sport->rtsirq, imx_rtsint, /* do not use RTS IRQ on IrDA */
(sport->rtsirq < MAX_INTERNAL_IRQ) ? 0 : if (!USE_IRDA(sport)) {
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, retval = request_irq(sport->rtsirq, imx_rtsint,
DRIVER_NAME, sport); (sport->rtsirq < MAX_INTERNAL_IRQ) ? 0 :
if (retval) IRQF_TRIGGER_FALLING |
goto error_out3; IRQF_TRIGGER_RISING,
DRIVER_NAME, sport);
if (retval)
goto error_out3;
}
} else { } else {
retval = request_irq(sport->port.irq, imx_int, 0, retval = request_irq(sport->port.irq, imx_int, 0,
DRIVER_NAME, sport); DRIVER_NAME, sport);
...@@ -599,18 +692,49 @@ static int imx_startup(struct uart_port *port) ...@@ -599,18 +692,49 @@ static int imx_startup(struct uart_port *port)
temp = readl(sport->port.membase + UCR1); temp = readl(sport->port.membase + UCR1);
temp |= UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN; temp |= UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN;
if (USE_IRDA(sport)) {
temp |= UCR1_IREN;
temp &= ~(UCR1_RTSDEN);
}
writel(temp, sport->port.membase + UCR1); writel(temp, sport->port.membase + UCR1);
temp = readl(sport->port.membase + UCR2); temp = readl(sport->port.membase + UCR2);
temp |= (UCR2_RXEN | UCR2_TXEN); temp |= (UCR2_RXEN | UCR2_TXEN);
writel(temp, sport->port.membase + UCR2); 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 #if defined CONFIG_ARCH_MX2 || defined CONFIG_ARCH_MX3
temp = readl(sport->port.membase + UCR3); temp = readl(sport->port.membase + UCR3);
temp |= UCR3_RXDMUXSEL; temp |= UCR3_RXDMUXSEL;
writel(temp, sport->port.membase + UCR3); writel(temp, sport->port.membase + UCR3);
#endif #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 * Enable modem status interrupts
*/ */
...@@ -618,6 +742,16 @@ static int imx_startup(struct uart_port *port) ...@@ -618,6 +742,16 @@ static int imx_startup(struct uart_port *port)
imx_enable_ms(&sport->port); imx_enable_ms(&sport->port);
spin_unlock_irqrestore(&sport->port.lock,flags); 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; return 0;
error_out3: error_out3:
...@@ -639,6 +773,13 @@ static void imx_shutdown(struct uart_port *port) ...@@ -639,6 +773,13 @@ static void imx_shutdown(struct uart_port *port)
temp &= ~(UCR2_TXEN); temp &= ~(UCR2_TXEN);
writel(temp, sport->port.membase + UCR2); 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. * Stop our timer.
*/ */
...@@ -648,7 +789,8 @@ static void imx_shutdown(struct uart_port *port) ...@@ -648,7 +789,8 @@ static void imx_shutdown(struct uart_port *port)
* Free the interrupts * Free the interrupts
*/ */
if (sport->txirq > 0) { 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->txirq, sport);
free_irq(sport->rxirq, sport); free_irq(sport->rxirq, sport);
} else } else
...@@ -660,6 +802,9 @@ static void imx_shutdown(struct uart_port *port) ...@@ -660,6 +802,9 @@ static void imx_shutdown(struct uart_port *port)
temp = readl(sport->port.membase + UCR1); temp = readl(sport->port.membase + UCR1);
temp &= ~(UCR1_TXMPTYEN | UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN); temp &= ~(UCR1_TXMPTYEN | UCR1_RRDYEN | UCR1_RTSDEN | UCR1_UARTEN);
if (USE_IRDA(sport))
temp &= ~(UCR1_IREN);
writel(temp, sport->port.membase + UCR1); writel(temp, sport->port.membase + UCR1);
} }
...@@ -768,11 +913,19 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios, ...@@ -768,11 +913,19 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
sport->port.membase + UCR2); sport->port.membase + UCR2);
old_txrxen &= (UCR2_TXEN | UCR2_RXEN); old_txrxen &= (UCR2_TXEN | UCR2_RXEN);
div = sport->port.uartclk / (baud * 16); if (USE_IRDA(sport)) {
if (div > 7) /*
div = 7; * use maximum available submodule frequency to
if (!div) * avoid missing short pulses due to low sampling rate
*/
div = 1; 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, rational_best_approximation(16 * div * baud, sport->port.uartclk,
1 << 16, 1 << 16, &num, &denom); 1 << 16, 1 << 16, &num, &denom);
...@@ -781,8 +934,7 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios, ...@@ -781,8 +934,7 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios,
denom -= 1; denom -= 1;
ufcr = readl(sport->port.membase + UFCR); ufcr = readl(sport->port.membase + UFCR);
ufcr = (ufcr & (~UFCR_RFDIV)) | ufcr = (ufcr & (~UFCR_RFDIV)) | UFCR_RFDIV_REG(div);
(div << 7);
writel(ufcr, sport->port.membase + UFCR); writel(ufcr, sport->port.membase + UFCR);
writel(num, sport->port.membase + UBIR); writel(num, sport->port.membase + UBIR);
...@@ -1141,6 +1293,11 @@ static int serial_imx_probe(struct platform_device *pdev) ...@@ -1141,6 +1293,11 @@ static int serial_imx_probe(struct platform_device *pdev)
if (pdata && (pdata->flags & IMXUART_HAVE_RTSCTS)) if (pdata && (pdata->flags & IMXUART_HAVE_RTSCTS))
sport->have_rtscts = 1; sport->have_rtscts = 1;
#ifdef CONFIG_IRDA
if (pdata && (pdata->flags & IMXUART_IRDA))
sport->use_irda = 1;
#endif
if (pdata->init) { if (pdata->init) {
ret = pdata->init(pdev); ret = pdata->init(pdev);
if (ret) if (ret)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment