[PATCH 02/14] ARM : SAMSUNG : Add RS485 support.

From: Paul Schilling
Date: Fri Oct 21 2011 - 23:52:06 EST


Add RS485 tranmit/recieve control line capabilities
This RS485 driver uses two methodes to determine if the transmit
should be disabled.

1. It uses a timer and polling to determine when transmit has completed.
2. Optionally uses a token used to terminate the message to be used in
the recieve interrupt to disable the transmit.

Option 1 alone can be used but doesn't garentee that the transmit line
wont be disabled within 1 to 2 character times depending on system load.

This patch adds an additional variable to the serial RS-485 struct so
that a program can change the terminate token value via the IOCTL.

Signed-off-by: Paul Schilling <paul.s.schilling@xxxxxxxxx>
---
arch/arm/plat-samsung/include/plat/regs-serial.h | 7 +
drivers/tty/serial/Kconfig | 16 +
drivers/tty/serial/samsung.c | 738 ++++++++++++++++++++--
drivers/tty/serial/samsung.h | 15 +
include/linux/serial.h | 5 +-
5 files changed, 722 insertions(+), 59 deletions(-)

diff --git a/arch/arm/plat-samsung/include/plat/regs-serial.h b/arch/arm/plat-samsung/include/plat/regs-serial.h
index bac36fa..9618e7d 100644
--- a/arch/arm/plat-samsung/include/plat/regs-serial.h
+++ b/arch/arm/plat-samsung/include/plat/regs-serial.h
@@ -261,6 +261,13 @@ struct s3c2410_uartcfg {

struct s3c24xx_uart_clksrc *clocks;
unsigned int clocks_size;
+
+#ifdef CONFIG_SAMSUNG_HAS_RS485
+ signed int gpio_transmit_en;
+ signed int gpio_receive_en;
+ unsigned char enable_token;
+ unsigned char token;
+#endif
};

/* s3c24xx_uart_devs
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 4dcb37b..dab33c2 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -464,6 +464,22 @@ config SERIAL_SAMSUNG_UARTS
Select the number of available UART ports for the Samsung S3C
serial driver

+config SAMSUNG_HAS_RS485
+ bool "Enable RS485 support for Samsung"
+ depends on SERIAL_SAMSUNG && (MACH_CONDOR2440 || MACH_CONDOR2416 || MACH_MINI2440)
+ default y if (MACH_CONDOR2440 || MACH_CONDOR2416)
+ default n if (MACH_MINI2440)
+
+config SAMSUNG_485_LOW_RES_TIMER
+ bool "Samsung RS-485 use low res timer during transmit"
+ depends on SERIAL_SAMSUNG && SAMSUNG_HAS_RS485
+ default n
+ help
+ Use low resolution jiffies at 200 Hz for timer the RS-485
+ transmitter. This works but doesn't garantee hard realtime.
+ Say no if you need the transmitter off immediatly after sending
+ a string or character.
+
config SERIAL_SAMSUNG_DEBUG
bool "Samsung SoC serial debug"
depends on SERIAL_SAMSUNG && DEBUG_LL
diff --git a/drivers/tty/serial/samsung.c b/drivers/tty/serial/samsung.c
index 6edafb5..bf61579 100644
--- a/drivers/tty/serial/samsung.c
+++ b/drivers/tty/serial/samsung.c
@@ -4,12 +4,16 @@
* Ben Dooks, Copyright (c) 2003-2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
*
+ * Paul Schilling, Copyright (c) 2011 Ecolab Corp.
+ * http://www.ecolab.com/
+ * Added RS-485 support to the Samsung UART driver.
+ *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/

-/* Hote on 2410 error handling
+/* Note on 2410 error handling
*
* The s3c2410 manual has a love/hate affair with the contents of the
* UERSTAT register in the UART blocks, and keeps marking some of the
@@ -17,7 +21,7 @@
* it copes with BREAKs properly, so I am happy to ignore the RESERVED
* feature from the latter versions of the manual.
*
- * If it becomes aparrent that latter versions of the 2410 remove these
+ * If it becomes apparent that latter versions of the 2410 remove these
* bits, then action will have to be taken to differentiate the versions
* and change the policy on BREAK
*
@@ -42,6 +46,8 @@
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
+#include <linux/uaccess.h>
+#include <linux/gpio.h>

#include <asm/irq.h>

@@ -66,11 +72,27 @@
/* flag to ignore all characters coming in */
#define RXSTAT_DUMMY_READ (0x10000000)

+static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS];
+
+
static inline struct s3c24xx_uart_port *to_ourport(struct uart_port *port)
{
return container_of(port, struct s3c24xx_uart_port, port);
}

+static inline struct s3c24xx_uart_info *s3c24xx_port_to_info(struct uart_port *port)
+{
+ return to_ourport(port)->info;
+}
+
+static inline struct s3c2410_uartcfg *s3c24xx_port_to_cfg(struct uart_port *port)
+{
+ if (port->dev == NULL)
+ return NULL;
+
+ return (struct s3c2410_uartcfg *)port->dev->platform_data;
+}
+
/* translate a port to the device name */

static inline const char *s3c24xx_serial_portname(struct uart_port *port)
@@ -83,6 +105,22 @@ static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE);
}

+#ifdef CONFIG_SAMSUNG_HAS_RS485
+/* Get the current transmit fifo count */
+static int s3c24xx_serial_tx_getfifocnt(struct s3c24xx_uart_port *ourport)
+{
+ struct s3c24xx_uart_info *info = ourport->info;
+ unsigned long ufstat = rd_regl(&(ourport->port), S3C2410_UFSTAT);
+
+ /* If FIFO is full then return FIFO size. */
+ if (ufstat & info->tx_fifofull)
+ return info->fifosize;
+
+ /* Else return number of entries in the FIFO. */
+ return (ufstat & info->tx_fifomask) >> info->tx_fifoshift;
+}
+#endif
+
static void s3c24xx_serial_rx_enable(struct uart_port *port)
{
unsigned long flags;
@@ -121,15 +159,205 @@ static void s3c24xx_serial_rx_disable(struct uart_port *port)
spin_unlock_irqrestore(&port->lock, flags);
}

+static void s3c24xx_serial_rx_fifo_enable(
+ struct uart_port *port,
+ unsigned int en)
+{
+ unsigned long flags;
+ unsigned int ucon;
+ static unsigned int last_state = 1;
+/* FIXME */
+ #if 0
+ if (last_state != en) {
+
+ struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ ucon = rd_regl(port, S3C2410_UCON);
+
+ ucon &= ~(S3C2440_UFCON_RXTRIG32 | S3C2410_UCON_RXILEVEL);
+
+ if (en) {
+ ucon |= cfg->ucon;
+ }
+
+ wr_regl(port, S3C2410_UCON, ucon);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+ }
+#endif
+}
+
+#ifdef CONFIG_SAMSUNG_HAS_RS485
+/* Timer function to toggle RTS when using FAST_TIMER */
+#ifdef SAMSUNG485_LOW_RES_TIMER
+ static void rs485_toggle_rts_timer_function(unsigned long _data)
+ {
+ struct uart_port *port = (struct uart_port *)_data;
+ struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
+
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+
+ unsigned long utrstat;
+
+ utrstat = rd_regl(port, S3C2410_UTRSTAT);
+
+ if ((utrstat & S3C2410_UTRSTAT_TXE) ? 1 : 0) {
+ if (cfg->gpio_transmit_en > -1) {
+ gpio_set_value(cfg->gpio_transmit_en, 0);
+ }
+
+ if (cfg->gpio_receive_en > -1) {
+ gpio_set_value(cfg->gpio_receive_en, 0);
+ }
+ } else {
+ /* Set a short timer to toggle RTS */
+ mod_timer(
+ &(ourport->rs485_tx_timer),
+ jiffies + usecs_to_jiffies(
+ ourport->char_time_usec
+ / 10));
+ }
+ }
+#else
+
+
+static enum hrtimer_restart rs485_toggle_rts_timer_function(
+ struct s3c24xx_uart_port *ourport)
+{
+ struct s3c2410_uartcfg *cfg =
+ s3c24xx_port_to_cfg(&(ourport->port));
+
+ unsigned long utrstat;
+ enum hrtimer_restart ret;
+
+ /* Read UART transmit status register */
+ utrstat = rd_regl(&(ourport->port), S3C2410_UTRSTAT);
+
+ /* Check if the UART and shift register is empty*/
+ if ((utrstat & S3C2410_UTRSTAT_TXE) ? 1 : 0) {
+ /* Is the GPIO valid for RS485 transmit enable*/
+ if (cfg->gpio_transmit_en > -1) {
+ /* Request, Set, Free the transmit GPIO*/
+ gpio_set_value(cfg->gpio_transmit_en, 0);
+ }
+
+ /* Is the GPIO valid for the RS485 receive enable*/
+ if (cfg->gpio_receive_en > -1) {
+ /* Request, Set, Free the receive GPIO*/
+ gpio_set_value(cfg->gpio_receive_en, 0);
+ }
+
+ s3c24xx_serial_rx_fifo_enable(&(ourport->port), 1);
+
+ /* The timer has completed its task now we can disable it. */
+ ret = HRTIMER_NORESTART;
+ } else {
+
+ ktime_t kt;
+
+ if (s3c24xx_serial_tx_getfifocnt(ourport) > 1) {
+ kt = ktime_set(0, ourport->char_time_nanosec);
+ hrtimer_forward(&(ourport->hr_rs485_tx_timer),
+ ourport->hr_rs485_tx_timer.base->softirq_time,
+ kt);
+ } else {
+ kt = ktime_set(0, ourport->char_time_nanosec / 2);
+ hrtimer_forward(&(ourport->hr_rs485_tx_timer),
+ ourport->hr_rs485_tx_timer.base->softirq_time,
+ kt);
+ }
+
+ /* Timer will be enabled after the interrupt context */
+ ret = HRTIMER_RESTART;
+ }
+
+ return ret;
+}
+
+/* Uart 0 RS-485 timer callback */
+enum hrtimer_restart rs485_hr_timer_callback_uart0(struct hrtimer *timer)
+{
+
+ return rs485_toggle_rts_timer_function(&s3c24xx_serial_ports[0]);
+}
+
+/* Uart 0 RS-485 timer callback */
+enum hrtimer_restart rs485_hr_timer_callback_uart1(struct hrtimer *timer)
+{
+
+ return rs485_toggle_rts_timer_function(&s3c24xx_serial_ports[1]);
+}
+
+/* Uart 0 RS-485 timer callback */
+#if CONFIG_SERIAL_SAMSUNG_UARTS > 2
+enum hrtimer_restart rs485_hr_timer_callback_uart2(struct hrtimer *timer)
+{
+
+ return rs485_toggle_rts_timer_function(&s3c24xx_serial_ports[2]);
+}
+#endif
+
+/* Uart 0 RS-485 timer callback */
+#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
+enum hrtimer_restart rs485_hr_timer_callback_uart3(struct hrtimer *timer)
+{
+
+ return rs485_toggle_rts_timer_function(&s3c24xx_serial_ports[3]);
+}
+#endif
+
+
+/* Callback array*/
+enum hrtimer_restart (*callback_list[CONFIG_SERIAL_SAMSUNG_UARTS])(struct hrtimer *) = {
+ &rs485_hr_timer_callback_uart0,
+ &rs485_hr_timer_callback_uart1,
+
+#if CONFIG_SERIAL_SAMSUNG_UARTS > 2
+ &rs485_hr_timer_callback_uart2,
+#endif
+
+#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
+ &rs485_hr_timer_callback_uart3,
+#endif
+};
+
+#endif
+#endif /* CONFIG_SAMSUNG_HAS_RS485 */
+
+
static void s3c24xx_serial_stop_tx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);

if (tx_enabled(port)) {
+#ifdef CONFIG_SAMSUNG_HAS_RS485
+ if (ourport->rs485.flags & SER_RS485_ENABLED) {
+#ifdef CONFIG_SAMSUNG_485_LOW_RES_TIMER
+ /* Set a short timer to toggle RTS */
+ mod_timer(&(ourport->rs485_tx_timer),
+ jiffies + usecs_to_jiffies(ourport->char_time_usec * s3c24xx_serial_tx_getfifocnt(ourport)));
+#else
+ ktime_t kt;
+
+ /* Set time struct to one char time. */
+ kt = ktime_set(0, ourport->char_time_nanosec);
+
+ /* Start the high res timer. */
+ hrtimer_start(&(ourport->hr_rs485_tx_timer), kt, HRTIMER_MODE_REL);
+#endif /* CONFIG_SAMSUNG_485_LOW_RES_TIMER */
+
+ s3c24xx_serial_rx_fifo_enable(port, 0);
+
+ }
+#endif /* CONFIG_SAMSUNG_HAS_RS485 */
+
disable_irq_nosync(ourport->tx_irq);
tx_enabled(port) = 0;
- if (port->flags & UPF_CONS_FLOW)
+ if (port->flags & UPF_CONS_FLOW) {
s3c24xx_serial_rx_enable(port);
+ }
}
}

@@ -137,7 +365,42 @@ static void s3c24xx_serial_start_tx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);

+#ifdef CONFIG_SAMSUNG_HAS_RS485
+ hrtimer_try_to_cancel(&(ourport->hr_rs485_tx_timer));
+#endif
+
if (!tx_enabled(port)) {
+#ifdef CONFIG_SAMSUNG_HAS_RS485
+ /* enable RS-485 */
+ if (ourport->rs485.flags & SER_RS485_ENABLED) {
+ struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
+
+ /* Is transmit GPIO valid */
+ if (cfg->gpio_transmit_en > -1) {
+ /* GPIO Request, Config, Direction, Value, and
+ * Free the pin*/
+ WARN_ON(s3c_gpio_cfgpin(cfg->gpio_transmit_en,
+ S3C_GPIO_OUTPUT));
+ gpio_direction_output(cfg->gpio_transmit_en, 1);
+ gpio_set_value(cfg->gpio_transmit_en, 1);
+ }
+
+ /* Is receive GPIO valid */
+ if ((cfg->gpio_receive_en > -1) &&
+ (!(ourport->rs485.flags &
+ SER_RS485_ALWAYS_LISTEN
+ ))) {
+ /* GPIO Request, Config, Direction, Value, and
+ * Free the pin*/
+ WARN_ON(s3c_gpio_cfgpin(cfg->gpio_receive_en,
+ S3C_GPIO_OUTPUT));
+ gpio_direction_output(cfg->gpio_receive_en, 1);
+ gpio_set_value(cfg->gpio_receive_en, 1);
+ }
+
+ }
+#endif /*CONFIG_SAMSUNG_HAS_RS485 */
+
if (port->flags & UPF_CONS_FLOW)
s3c24xx_serial_rx_disable(port);

@@ -162,18 +425,6 @@ static void s3c24xx_serial_enable_ms(struct uart_port *port)
{
}

-static inline struct s3c24xx_uart_info *s3c24xx_port_to_info(struct uart_port *port)
-{
- return to_ourport(port)->info;
-}
-
-static inline struct s3c2410_uartcfg *s3c24xx_port_to_cfg(struct uart_port *port)
-{
- if (port->dev == NULL)
- return NULL;
-
- return (struct s3c2410_uartcfg *)port->dev->platform_data;
-}

static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport,
unsigned long ufstat)
@@ -186,6 +437,99 @@ static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport,
return (ufstat & info->rx_fifomask) >> info->rx_fifoshift;
}

+static inline int
+s3c24xx_serial_getsource(struct uart_port *port, struct s3c24xx_uart_clksrc *c)
+{
+ struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
+
+ return (info->get_clksrc)(port, c);
+}
+
+static inline int
+s3c24xx_serial_setsource(struct uart_port *port, struct s3c24xx_uart_clksrc *c)
+{
+ struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
+
+ return (info->set_clksrc)(port, c);
+}
+
+#ifdef CONFIG_SAMSUNG_HAS_RS485
+static void
+samsung_uart_get_options(struct uart_port *port, int *baud,
+ int *parity, int *stop, int *bits)
+{
+ struct s3c24xx_uart_clksrc clksrc;
+ struct clk *clk;
+ unsigned int ulcon;
+ unsigned int ucon;
+ unsigned int ubrdiv;
+ unsigned long rate;
+
+ ulcon = rd_regl(port, S3C2410_ULCON);
+ ucon = rd_regl(port, S3C2410_UCON);
+ ubrdiv = rd_regl(port, S3C2410_UBRDIV);
+
+ dbg("s3c24xx_serial_get_options: port=%p\n"
+ "registers: ulcon=%08x, ucon=%08x, ubdriv=%08x\n",
+ port, ulcon, ucon, ubrdiv);
+
+ if ((ucon & 0xf) != 0) {
+ /* consider the serial port configured if the tx/rx mode set */
+
+ switch (ulcon & S3C2410_LCON_CSMASK) {
+ case S3C2410_LCON_CS5:
+ *bits = 5;
+ break;
+ case S3C2410_LCON_CS6:
+ *bits = 6;
+ break;
+ case S3C2410_LCON_CS7:
+ *bits = 7;
+ break;
+ default:
+ case S3C2410_LCON_CS8:
+ *bits = 8;
+ break;
+ }
+
+ switch (ulcon & S3C2410_LCON_PMASK) {
+ case S3C2410_LCON_PEVEN:
+ *parity = 'e';
+ break;
+
+ case S3C2410_LCON_PODD:
+ *parity = 'o';
+ break;
+
+ case S3C2410_LCON_PNONE:
+ default:
+ *parity = 'n';
+ }
+
+ if (ulcon | S3C2410_LCON_STOPB) {
+ *stop = 2;
+ } else {
+ *stop = 1;
+ }
+
+ /* now calculate the baud rate */
+
+ s3c24xx_serial_getsource(port, &clksrc);
+
+ clk = clk_get(port->dev, clksrc.name);
+ if (!IS_ERR(clk) && clk != NULL)
+ rate = clk_get_rate(clk) / clksrc.divisor;
+ else
+ rate = 1;
+
+
+ *baud = rate / (16 * (ubrdiv + 1));
+ dbg("calculated baud %d\n", *baud);
+ }
+
+}
+#endif /* CONFIG_SAMSUNG_HAS_RS485 */
+

/* ? - where has parity gone?? */
#define S3C2410_UERSTAT_PARITY (0x1000)
@@ -209,6 +553,33 @@ s3c24xx_serial_rx_chars(int irq, void *dev_id)
uerstat = rd_regl(port, S3C2410_UERSTAT);
ch = rd_regb(port, S3C2410_URXH);

+ if (ourport->rs485.flags & SER_RS485_ENABLED) {
+ struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
+
+#ifdef CONFIG_TEST_485
+ /* Is the GPIO valid for the RS485 receive enable*/
+ if (cfg->gpio_receive_en > -1) {
+ /* Request, Set, Free the receive GPIO*/
+ gpio_set_value(cfg->gpio_receive_en, 1);
+ gpio_set_value(cfg->gpio_receive_en, 0);
+ }
+#endif
+
+ if ((SER_RS485_TOGGLE_ON_TOKEN & ourport->rs485.flags) &&
+ (ourport->rs485.toggle_token == ch)) {
+ /* Is the GPIO valid for RS485
+ * transmit enable*/
+ if (cfg->gpio_transmit_en > -1) {
+ /* Set the transmit GPIO line*/
+
+ gpio_set_value(cfg->gpio_transmit_en, 0);
+
+ }
+
+ s3c24xx_serial_rx_fifo_enable(port, 1);
+ }
+ }
+
if (port->flags & UPF_CONS_FLOW) {
int txe = s3c24xx_serial_txempty_nofifo(port);

@@ -488,21 +859,7 @@ static struct s3c24xx_uart_clksrc tmp_clksrc = {
.divisor = 1,
};

-static inline int
-s3c24xx_serial_getsource(struct uart_port *port, struct s3c24xx_uart_clksrc *c)
-{
- struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
-
- return (info->get_clksrc)(port, c);
-}

-static inline int
-s3c24xx_serial_setsource(struct uart_port *port, struct s3c24xx_uart_clksrc *c)
-{
- struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
-
- return (info->set_clksrc)(port, c);
-}

struct baud_calc {
struct s3c24xx_uart_clksrc *clksrc;
@@ -512,6 +869,41 @@ struct baud_calc {
struct clk *src;
};

+#ifdef CONFIG_SAMSUNG_HAS_RS485
+/* Calculate the char_time depending on baudrate, number of bits etc. */
+static void update_char_time(struct uart_port *port)
+{
+
+ int bits = 8;
+ int baud = 9600;
+ int parity = 'n';
+ int stop = 1;
+
+ samsung_uart_get_options(port, &baud, &parity, &stop, &bits);
+
+ /* calc. number of bits / data byte */
+ /* databits + startbit and 1 stopbit */
+ bits++;
+
+ /* 2 stopbits ? */
+ bits += stop;
+
+ /* is parity enabled. */
+ if ('n' != parity) {
+ bits++;
+ }
+
+#ifdef CONFIG_SAMSUNG_485_LOW_RES_TIMER
+ /* calc timeout */
+ to_ourport(port)->char_time_usec = ((1000000 / baud) * bits) + 1;
+#else
+ /* calc timeout */
+ to_ourport(port)->char_time_nanosec = ((1000000000 / baud) * bits) + 1;
+#endif
+}
+
+#endif /*CONFIG_SAMSUNG_HAS_RS485 */
+
static int s3c24xx_serial_calcbaud(struct baud_calc *calc,
struct uart_port *port,
struct s3c24xx_uart_clksrc *clksrc,
@@ -785,7 +1177,7 @@ static void s3c24xx_serial_set_termios(struct uart_port *port,
port->ignore_status_mask = 0;
if (termios->c_iflag & IGNPAR)
port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
- if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)
+ if ((termios->c_iflag & IGNBRK) && (termios->c_iflag & IGNPAR))
port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;

/*
@@ -795,6 +1187,12 @@ static void s3c24xx_serial_set_termios(struct uart_port *port,
port->ignore_status_mask |= RXSTAT_DUMMY_READ;

spin_unlock_irqrestore(&port->lock, flags);
+
+#ifdef CONFIG_SAMSUNG_HAS_RS485
+ /* Update character time. */
+ update_char_time(port);
+#endif /* CONFIG_SAMSUNG_HAS_RS485 */
+
}

static const char *s3c24xx_serial_type(struct uart_port *port)
@@ -830,7 +1228,7 @@ static void s3c24xx_serial_config_port(struct uart_port *port, int flags)
{
struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);

- if (flags & UART_CONFIG_TYPE &&
+ if ((flags & UART_CONFIG_TYPE) &&
s3c24xx_serial_request_port(port) == 0)
port->type = info->type;
}
@@ -849,6 +1247,73 @@ s3c24xx_serial_verify_port(struct uart_port *port, struct serial_struct *ser)
return 0;
}

+#ifdef CONFIG_SAMSUNG_HAS_RS485
+/*
+ * Enable RS-485 called by IOCTL.
+ */
+static int
+s3c24xx_serial_enable_rs485(struct uart_port *port, struct serial_rs485 *r)
+{
+ struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+
+ /* Is GPIO valid for transmit enable. */
+ if (cfg->gpio_transmit_en > -1) {
+ /* GPIO Request, Config, Direction, Value, and Free the pin*/
+ gpio_set_value(cfg->gpio_transmit_en, 0);
+ }
+
+ /* Is GPIO valid for receive enable. */
+ if (cfg->gpio_receive_en > -1) {
+ /* GPIO Request, Config, Direction, Value, and Free the pin*/
+ gpio_set_value(cfg->gpio_receive_en, 0);
+ }
+
+ ourport->rs485 = *r;
+
+ /* Maximum delay before RTS equal to 1000 */
+ if (ourport->rs485.delay_rts_before_send >= 1000)
+ ourport->rs485.delay_rts_before_send = 1000;
+
+#if 0
+ dev_info(port., "rts: on send = %i, after = %i, enabled = %i",
+ info->rs485.rts_on_send,
+ info->rs485.rts_after_sent,
+ info->rs485.enabled
+ );
+#endif
+
+ return 0;
+}
+
+
+static int s3c24xx_serial_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;
+
+ s3c24xx_serial_enable_rs485(port, &rs485conf);
+ break;
+
+ case TIOCGRS485:
+ if (copy_to_user((struct serial_rs485 *) arg,
+ &(to_ourport(port)->rs485),
+ sizeof(rs485conf)))
+ return -EFAULT;
+ break;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+#endif /*CONFIG_SAMSUNG_HAS_RS485*/

#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE

@@ -877,6 +1342,9 @@ static struct uart_ops s3c24xx_serial_ops = {
.request_port = s3c24xx_serial_request_port,
.config_port = s3c24xx_serial_config_port,
.verify_port = s3c24xx_serial_verify_port,
+#ifdef CONFIG_SAMSUNG_HAS_RS485
+ .ioctl = s3c24xx_serial_ioctl,
+#endif /* CONFIG_SAMSUNG_HAS_RS485 */
};


@@ -955,9 +1423,16 @@ static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS
static inline int s3c24xx_serial_resetport(struct uart_port *port,
struct s3c2410_uartcfg *cfg)
{
+ int ret;
struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);

- return (info->reset_port)(port, cfg);
+ ret = (info->reset_port)(port, cfg);
+
+#ifdef CONFIG_SAMSUNG_HAS_RS485
+ update_char_time(port);
+#endif /* CONFIG_SAMSUNG_HAS_RS485 */
+
+ return ret;
}


@@ -1077,6 +1552,42 @@ static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
port->dev = &platdev->dev;
ourport->info = info;

+#ifdef CONFIG_SAMSUNG_HAS_RS485
+
+ dev_info(port->dev, "port TX GPIO (%d)\n", cfg->gpio_transmit_en);
+ dev_info(port->dev, "port TX GPIO (%d)\n", cfg->gpio_receive_en);
+
+ if (cfg->gpio_transmit_en > -1) {
+ /* Setup 485 as default on capable ports.*/
+ ourport->rs485.flags = (SER_RS485_ENABLED | SER_RS485_ALWAYS_LISTEN |
+ (cfg->enable_token ? SER_RS485_TOGGLE_ON_TOKEN : 0));
+ ourport->rs485.toggle_token = cfg->token;
+
+#ifdef CONFIG_SAMSUNG_485_LOW_RES_TIMER
+ dev_info(&(port->dev), "Enable Lo Res Timer\n");
+ setup_timer(&(ourport->rs485_tx_timer),
+ rs485_toggle_rts_timer_function,
+ (unsigned long)port);
+#else
+ dev_info(port->dev, "Enable Hi Res Timer\n");
+
+ /* Initialize HR timer to relative and monotonic mode. */
+ hrtimer_init(&(ourport->hr_rs485_tx_timer),
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+
+ /* register Callback to appropriate UART */
+ ourport->hr_rs485_tx_timer.function =
+ callback_list[ourport->port.line];
+
+#endif
+
+ dev_info(port->dev, "Port (%d) 485 Enabled\n", ourport->port.line);
+ } else {
+ /* disable 485 on ports inacable of mode. */
+ ourport->rs485.flags = 0;
+ }
+#endif /* CONFIG_SAMSUNG_HAS_RS485*/
+
/* copy the info in from provided structure */
ourport->port.fifosize = info->fifosize;

@@ -1137,6 +1648,55 @@ static ssize_t s3c24xx_serial_show_clksrc(struct device *dev,

static DEVICE_ATTR(clock_source, S_IRUGO, s3c24xx_serial_show_clksrc, NULL);

+
+#ifdef CONFIG_SAMSUNG_HAS_RS485
+
+static ssize_t s3c24xx_serial_show_485_status(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret = 0;
+ struct uart_port *port = s3c24xx_dev_to_port(dev);
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+
+ if (ourport->rs485.flags & SER_RS485_ENABLED) {
+ ret = snprintf(buf, PAGE_SIZE, "[Enabled] Disabled\n");
+ } else {
+ ret = snprintf(buf, PAGE_SIZE, "Enabled [Disabled]\n");
+ }
+
+ return ret;
+}
+
+static ssize_t s3c24xx_serial_set_485_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+
+{
+ struct uart_port *port = s3c24xx_dev_to_port(dev);
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+
+ if (!strncmp(buf, "Enabled", 7)) {
+ ourport->rs485.flags |= SER_RS485_ENABLED;
+ } else if (!strncmp(buf, "Disabled", 8)) {
+ ourport->rs485.flags &= ~SER_RS485_ENABLED;
+ } else {
+ dev_err(port->dev, "unknown string\n");
+
+ return -EINVAL;
+ }
+
+ return count;
+
+}
+
+static DEVICE_ATTR(485_status,
+ S_IRUGO | S_IWUSR,
+ s3c24xx_serial_show_485_status,
+ s3c24xx_serial_set_485_mode);
+
+#endif
+
/* Device driver serial port probe */

static int probe_index;
@@ -1144,8 +1704,10 @@ static int probe_index;
int s3c24xx_serial_probe(struct platform_device *dev,
struct s3c24xx_uart_info *info)
{
- struct s3c24xx_uart_port *ourport;
int ret;
+ struct s3c24xx_uart_port *ourport;
+ struct s3c2410_uartcfg *cfg = s3c24xx_dev_to_cfg(&dev->dev);
+

dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index);

@@ -1170,6 +1732,33 @@ int s3c24xx_serial_probe(struct platform_device *dev,
if (ret < 0)
dev_err(&dev->dev, "failed to add cpufreq notifier\n");

+#ifdef CONFIG_SAMSUNG_HAS_RS485
+
+ ret = device_create_file(&dev->dev, &dev_attr_485_status);
+ if (ret < 0)
+ printk(KERN_ERR "%s: failed to add 485 status attr.\n", __func__);
+
+
+ if (cfg->gpio_transmit_en > -1) {
+ WARN_ON(gpio_request(cfg->gpio_transmit_en, "RS485TX"));
+ WARN_ON(s3c_gpio_cfgpin(cfg->gpio_transmit_en, S3C_GPIO_OUTPUT));
+ gpio_direction_output(cfg->gpio_transmit_en, 0);
+ gpio_set_value(cfg->gpio_transmit_en, 0);
+
+ dev_info(ourport->port.dev, "Initialize TX(%d) GPIO\n", ourport->port.line);
+
+ if (cfg->gpio_receive_en > -1) {
+ WARN_ON(gpio_request(cfg->gpio_receive_en , "RS485RX"));
+ WARN_ON(s3c_gpio_cfgpin(cfg->gpio_receive_en, S3C_GPIO_OUTPUT));
+ gpio_direction_output(cfg->gpio_receive_en, 0);
+ gpio_set_value(cfg->gpio_receive_en, 0);
+
+ dev_info(ourport->port.dev, "Initialize RX(%d) GPIO\n", ourport->port.line);
+ }
+ }
+#endif
+
+
return 0;

probe_err:
@@ -1181,8 +1770,33 @@ EXPORT_SYMBOL_GPL(s3c24xx_serial_probe);
int __devexit s3c24xx_serial_remove(struct platform_device *dev)
{
struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
+ struct s3c2410_uartcfg *cfg = s3c24xx_dev_to_cfg(&dev->dev);

if (port) {
+
+#ifdef CONFIG_SAMSUNG_HAS_RS485
+ if (cfg->gpio_transmit_en > -1) {
+ gpio_free(cfg->gpio_transmit_en);
+
+ dev_info(port->dev,
+ "Uninitialize TX(%d) GPIO\n", port->line);
+
+ if (cfg->gpio_receive_en > -1) {
+ gpio_free(cfg->gpio_receive_en);
+
+ dev_info(port->dev,
+ "Uninitialize RX(%d) GPIO\n",
+ port->line);
+ }
+ }
+
+ /* Delete timer after device is removed. */
+#ifdef CONFIG_SAMSUNG_485_LOW_RES_TIMER
+ del_timer_sync(&(to_ourport(port)->rs485_tx_timer));
+#else
+ hrtimer_cancel(&(to_ourport(port)->hr_rs485_tx_timer));
+#endif
+#endif /* CONFIG_SAMSUNG_HAS_RS485 */
s3c24xx_serial_cpufreq_deregister(to_ourport(port));
device_remove_file(&dev->dev, &dev_attr_clock_source);
uart_remove_one_port(&s3c24xx_uart_drv, port);
@@ -1308,9 +1922,33 @@ s3c24xx_serial_console_write(struct console *co, const char *s,
uart_console_write(cons_uart, s, count, s3c24xx_serial_console_putchar);
}

+
+/* s3c24xx_serial_init_ports
+ *
+ * initialise the serial ports from the machine provided initialisation
+ * data.
+*/
+
+static int s3c24xx_serial_init_ports(struct s3c24xx_uart_info **info)
+{
+ struct s3c24xx_uart_port *ptr = s3c24xx_serial_ports;
+ struct platform_device **platdev_ptr;
+ int i;
+
+ dbg("s3c24xx_serial_init_ports: initialising ports...\n");
+
+ platdev_ptr = s3c24xx_uart_devs;
+
+ for (i = 0; i < CONFIG_SERIAL_SAMSUNG_UARTS; i++, ptr++, platdev_ptr++) {
+ s3c24xx_serial_init_port(ptr, info[i], *platdev_ptr);
+ }
+
+ return 0;
+}
+
static void __init
s3c24xx_serial_get_options(struct uart_port *port, int *baud,
- int *parity, int *bits)
+ int *parity, int *stop, int *bits)
{
struct s3c24xx_uart_clksrc clksrc;
struct clk *clk;
@@ -1360,6 +1998,12 @@ s3c24xx_serial_get_options(struct uart_port *port, int *baud,
*parity = 'n';
}

+ if (ulcon | S3C2410_LCON_STOPB) {
+ *stop = 2;
+ } else {
+ *stop = 1;
+ }
+
/* now calculate the baud rate */

s3c24xx_serial_getsource(port, &clksrc);
@@ -1377,29 +2021,6 @@ s3c24xx_serial_get_options(struct uart_port *port, int *baud,

}

-/* s3c24xx_serial_init_ports
- *
- * initialise the serial ports from the machine provided initialisation
- * data.
-*/
-
-static int s3c24xx_serial_init_ports(struct s3c24xx_uart_info **info)
-{
- struct s3c24xx_uart_port *ptr = s3c24xx_serial_ports;
- struct platform_device **platdev_ptr;
- int i;
-
- dbg("s3c24xx_serial_init_ports: initialising ports...\n");
-
- platdev_ptr = s3c24xx_uart_devs;
-
- for (i = 0; i < CONFIG_SERIAL_SAMSUNG_UARTS; i++, ptr++, platdev_ptr++) {
- s3c24xx_serial_init_port(ptr, info[i], *platdev_ptr);
- }
-
- return 0;
-}
-
static int __init
s3c24xx_serial_console_setup(struct console *co, char *options)
{
@@ -1408,6 +2029,7 @@ s3c24xx_serial_console_setup(struct console *co, char *options)
int bits = 8;
int parity = 'n';
int flow = 'n';
+ int stop = 1;

dbg("s3c24xx_serial_console_setup: co=%p (%d), %s\n",
co, co->index, options);
@@ -1436,7 +2058,7 @@ s3c24xx_serial_console_setup(struct console *co, char *options)
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
else
- s3c24xx_serial_get_options(port, &baud, &parity, &bits);
+ s3c24xx_serial_get_options(port, &baud, &parity, &stop, &bits);

dbg("s3c24xx_serial_console_setup: baud %d\n", baud);

diff --git a/drivers/tty/serial/samsung.h b/drivers/tty/serial/samsung.h
index a69d9a5..f550dc3 100644
--- a/drivers/tty/serial/samsung.h
+++ b/drivers/tty/serial/samsung.h
@@ -4,6 +4,10 @@
* Ben Dooks, Copyright (c) 2003-2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
*
+ * Paul Schilling, Copyright (c) 2011 Ecolab Corp.
+ * Added RS-485 support to the Samsung driver..
+ *
+ *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
@@ -31,6 +35,7 @@ struct s3c24xx_uart_info {

/* uart controls */
int (*reset_port)(struct uart_port *, struct s3c2410_uartcfg *);
+
};

struct s3c24xx_uart_port {
@@ -48,6 +53,16 @@ struct s3c24xx_uart_port {
struct clk *baudclk;
struct uart_port port;

+ struct serial_rs485 rs485; /* RS-485 support */
+
+#ifdef CONFIG_SAMSUNG_485_LOW_RES_TIMER
+ unsigned long char_time_usec; /* The time for 1 char, in micro secs */
+ struct timer_list rs485_tx_timer;
+#else
+ unsigned long char_time_nanosec; /* The time for 1 char, in nano secs */
+ struct hrtimer hr_rs485_tx_timer; /* Timer for RS-485 Receive enable*/
+#endif
+
#ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition;
#endif
diff --git a/include/linux/serial.h b/include/linux/serial.h
index ef91406..826b1c0 100644
--- a/include/linux/serial.h
+++ b/include/linux/serial.h
@@ -211,9 +211,12 @@ struct serial_rs485 {
#define SER_RS485_RTS_ON_SEND (1 << 1)
#define SER_RS485_RTS_AFTER_SEND (1 << 2)
#define SER_RS485_RTS_BEFORE_SEND (1 << 3)
+#define SER_RS485_ALWAYS_LISTEN (1 << 4)
+#define SER_RS485_TOGGLE_ON_TOKEN (1 << 5)
__u32 delay_rts_before_send; /* Milliseconds */
__u32 delay_rts_after_send; /* Milliseconds */
- __u32 padding[5]; /* Memory is cheap, new structs
+ __u32 toggle_token; /* Token used to toggle to receive */
+ __u32 padding[4]; /* Memory is cheap, new structs
are a royal PITA .. */
};

--
1.7.6.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/