From a5a420d207df40226afbf828c12bd9b4c6e058ef Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 9 Mar 2010 12:25:28 -0500 Subject: [PATCH 01/25] serial: bfin_sport_uart: work around anomaly 05000473 (make 32bit fifo read atomic) We cannot let a 32-bit RX FIFO read be interrupted otherwise a fake RX underflow error might be generated. URL: http://blackfin.uclinux.org/gf/tracker/5145 Signed-off-by: Sonic Zhang Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/serial/bfin_sport_uart.h b/drivers/serial/bfin_sport_uart.h index abe03614e4df..9791fcc80a74 100644 --- a/drivers/serial/bfin_sport_uart.h +++ b/drivers/serial/bfin_sport_uart.h @@ -37,7 +37,21 @@ #define SPORT_GET_TFSDIV(sport) bfin_read16(((sport)->port.membase + OFFSET_TFSDIV)) #define SPORT_GET_TX(sport) bfin_read16(((sport)->port.membase + OFFSET_TX)) #define SPORT_GET_RX(sport) bfin_read16(((sport)->port.membase + OFFSET_RX)) -#define SPORT_GET_RX32(sport) bfin_read32(((sport)->port.membase + OFFSET_RX)) +/* + * If another interrupt fires while doing a 32-bit read from RX FIFO, + * a fake RX underflow error will be generated. So disable interrupts + * to prevent interruption while reading the FIFO. + */ +#define SPORT_GET_RX32(sport) \ +({ \ + unsigned int __ret; \ + if (ANOMALY_05000473) \ + local_irq_disable(); \ + __ret = bfin_read32((sport)->port.membase + OFFSET_RX); \ + if (ANOMALY_05000473) \ + local_irq_enable(); \ + __ret; \ +}) #define SPORT_GET_RCR1(sport) bfin_read16(((sport)->port.membase + OFFSET_RCR1)) #define SPORT_GET_RCR2(sport) bfin_read16(((sport)->port.membase + OFFSET_RCR2)) #define SPORT_GET_RCLKDIV(sport) bfin_read16(((sport)->port.membase + OFFSET_RCLKDIV)) From 3f3a978b9f4a513610b32f16670914006a61067f Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 9 Mar 2010 12:25:29 -0500 Subject: [PATCH 02/25] serial: bfin_sport_uart: shorten the SPORT TX waiting loop The waiting loop to stop SPORT TX from TX interrupt is too long. This may block the SPORT RX interrupts and cause the RX FIFO to overflow. So, do stop sport TX only after the last char in TX FIFO is moved into the shift register. Signed-off-by: Sonic Zhang Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index c88f8ad3ff82..6991c3605f9c 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -271,7 +271,13 @@ static void sport_uart_tx_chars(struct sport_uart_port *up) } if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) { - sport_stop_tx(&up->port); + /* The waiting loop to stop SPORT TX from TX interrupt is + * too long. This may block SPORT RX interrupts and cause + * RX FIFO overflow. So, do stop sport TX only after the last + * char in TX FIFO is moved into the shift register. + */ + if (SPORT_GET_STAT(up) & TXHRE) + sport_stop_tx(&up->port); return; } From 00c1ff8c0fd14e1fa76fb5351ae7d11a513b8497 Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 9 Mar 2010 12:25:30 -0500 Subject: [PATCH 03/25] serial: bfin_sport_uart: remove unused peripheral pin lists All the resources are in the boards files now. Signed-off-by: Sonic Zhang Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.c | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index 6991c3605f9c..5dcf5dc0da3a 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -39,27 +39,6 @@ #include "bfin_sport_uart.h" -#ifdef CONFIG_SERIAL_BFIN_SPORT0_UART -unsigned short bfin_uart_pin_req_sport0[] = - {P_SPORT0_TFS, P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, \ - P_SPORT0_DRPRI, P_SPORT0_RSCLK, P_SPORT0_DRSEC, P_SPORT0_DTSEC, 0}; -#endif -#ifdef CONFIG_SERIAL_BFIN_SPORT1_UART -unsigned short bfin_uart_pin_req_sport1[] = - {P_SPORT1_TFS, P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, \ - P_SPORT1_DRPRI, P_SPORT1_RSCLK, P_SPORT1_DRSEC, P_SPORT1_DTSEC, 0}; -#endif -#ifdef CONFIG_SERIAL_BFIN_SPORT2_UART -unsigned short bfin_uart_pin_req_sport2[] = - {P_SPORT2_TFS, P_SPORT2_DTPRI, P_SPORT2_TSCLK, P_SPORT2_RFS, \ - P_SPORT2_DRPRI, P_SPORT2_RSCLK, P_SPORT2_DRSEC, P_SPORT2_DTSEC, 0}; -#endif -#ifdef CONFIG_SERIAL_BFIN_SPORT3_UART -unsigned short bfin_uart_pin_req_sport3[] = - {P_SPORT3_TFS, P_SPORT3_DTPRI, P_SPORT3_TSCLK, P_SPORT3_RFS, \ - P_SPORT3_DRPRI, P_SPORT3_RSCLK, P_SPORT3_DRSEC, P_SPORT3_DTSEC, 0}; -#endif - struct sport_uart_port { struct uart_port port; int err_irq; From e8126b32f6782d2e275e632a5e51e2433ceda7b0 Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 9 Mar 2010 12:25:31 -0500 Subject: [PATCH 04/25] serial: bfin_sport_uart: add missing mapbase initialization The driver doesn't care about this, but the common serial core wants it. Signed-off-by: Sonic Zhang Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index 5dcf5dc0da3a..49e7e4f79e0a 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -712,6 +712,7 @@ static int __devinit sport_uart_probe(struct platform_device *pdev) ret = -ENXIO; goto out_error_free_peripherals; } + sport->port.mapbase = res->start; sport->port.irq = platform_get_irq(pdev, 0); if (sport->port.irq < 0) { @@ -809,7 +810,7 @@ static int __init sport_uart_init(void) { int ret; - pr_info("Serial: Blackfin uart over sport driver\n"); + pr_info("Blackfin uart over sport driver\n"); ret = uart_register_driver(&sport_uart_reg); if (ret) { From b59588aae8356caf52b653a7c448c3fffdd27628 Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 9 Mar 2010 12:25:32 -0500 Subject: [PATCH 05/25] serial: bfin_sport_uart: rename early platform driver class string Clarifies command line set up for devices between consoles and early devices. Signed-off-by: Sonic Zhang Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index 49e7e4f79e0a..79252664f706 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -527,6 +527,8 @@ struct uart_ops sport_uart_ops = { static struct sport_uart_port *bfin_sport_uart_ports[BFIN_SPORT_UART_MAX_PORTS]; #ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE +#define CLASS_BFIN_SPORT_CONSOLE "bfin-sport-console" + static int __init sport_uart_console_setup(struct console *co, char *options) { @@ -788,7 +790,7 @@ static struct platform_driver sport_uart_driver = { #ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE static __initdata struct early_platform_driver early_sport_uart_driver = { - .class_str = DRV_NAME, + .class_str = CLASS_BFIN_SPORT_CONSOLE, .pdrv = &sport_uart_driver, .requested_id = EARLY_PLATFORM_ID_UNSET, }; @@ -797,7 +799,8 @@ static int __init sport_uart_rs_console_init(void) { early_platform_driver_register(&early_sport_uart_driver, DRV_NAME); - early_platform_driver_probe(DRV_NAME, BFIN_SPORT_UART_MAX_PORTS, 0); + early_platform_driver_probe(CLASS_BFIN_SPORT_CONSOLE, + BFIN_SPORT_UART_MAX_PORTS, 0); register_console(&sport_uart_console); From 1f7d1c85df5b63359e7f7e3fbd1509c1cdd6414f Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 9 Mar 2010 12:25:33 -0500 Subject: [PATCH 06/25] serial: bfin_sport_uart: add support for CTS/RTS via GPIOs Some people need flow control on their ports, so now boards can support that via any GPIOs. Signed-off-by: Sonic Zhang Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/Kconfig | 24 +++++++ drivers/serial/bfin_sport_uart.c | 111 ++++++++++++++++++++++++++++--- drivers/serial/bfin_sport_uart.h | 11 +++ 3 files changed, 135 insertions(+), 11 deletions(-) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 302836a80693..63258305c057 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -1443,24 +1443,48 @@ config SERIAL_BFIN_SPORT0_UART help Enable UART over SPORT0 +config SERIAL_BFIN_SPORT0_UART_CTSRTS + bool "Enable UART over SPORT0 hardware flow control" + depends on SERIAL_BFIN_SPORT0_UART + help + Enable hardware flow control in the driver. + config SERIAL_BFIN_SPORT1_UART bool "Enable UART over SPORT1" depends on SERIAL_BFIN_SPORT help Enable UART over SPORT1 +config SERIAL_BFIN_SPORT1_UART_CTSRTS + bool "Enable UART over SPORT1 hardware flow control" + depends on SERIAL_BFIN_SPORT1_UART + help + Enable hardware flow control in the driver. + config SERIAL_BFIN_SPORT2_UART bool "Enable UART over SPORT2" depends on SERIAL_BFIN_SPORT && (BF54x || BF538 || BF539) help Enable UART over SPORT2 +config SERIAL_BFIN_SPORT2_UART_CTSRTS + bool "Enable UART over SPORT2 hardware flow control" + depends on SERIAL_BFIN_SPORT2_UART + help + Enable hardware flow control in the driver. + config SERIAL_BFIN_SPORT3_UART bool "Enable UART over SPORT3" depends on SERIAL_BFIN_SPORT && (BF54x || BF538 || BF539) help Enable UART over SPORT3 +config SERIAL_BFIN_SPORT3_UART_CTSRTS + bool "Enable UART over SPORT3 hardware flow control" + depends on SERIAL_BFIN_SPORT3_UART + help + Enable hardware flow control in the driver. + config SERIAL_TIMBERDALE tristate "Support for timberdale UART" select SERIAL_CORE diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index 79252664f706..8cb0a40c2baa 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -48,6 +48,10 @@ struct sport_uart_port { unsigned short txmask2; unsigned char stopb; /* unsigned char parib; */ +#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS + int cts_pin; + int rts_pin; +#endif }; static void sport_uart_tx_chars(struct sport_uart_port *up); @@ -198,6 +202,59 @@ static irqreturn_t sport_uart_err_irq(int irq, void *dev_id) return IRQ_HANDLED; } +#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS +static unsigned int sport_get_mctrl(struct uart_port *port) +{ + struct sport_uart_port *up = (struct sport_uart_port *)port; + if (up->cts_pin < 0) + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; + + /* CTS PIN is negative assertive. */ + if (SPORT_UART_GET_CTS(up)) + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; + else + return TIOCM_DSR | TIOCM_CAR; +} + +static void sport_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + struct sport_uart_port *up = (struct sport_uart_port *)port; + if (up->rts_pin < 0) + return; + + /* RTS PIN is negative assertive. */ + if (mctrl & TIOCM_RTS) + SPORT_UART_ENABLE_RTS(up); + else + SPORT_UART_DISABLE_RTS(up); +} + +/* + * Handle any change of modem status signal. + */ +static irqreturn_t sport_mctrl_cts_int(int irq, void *dev_id) +{ + struct sport_uart_port *up = (struct sport_uart_port *)dev_id; + unsigned int status; + + status = sport_get_mctrl(&up->port); + uart_handle_cts_change(&up->port, status & TIOCM_CTS); + + return IRQ_HANDLED; +} +#else +static unsigned int sport_get_mctrl(struct uart_port *port) +{ + pr_debug("%s enter\n", __func__); + return TIOCM_CTS | TIOCM_CD | TIOCM_DSR; +} + +static void sport_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + pr_debug("%s enter\n", __func__); +} +#endif + /* Reqeust IRQ, Setup clock */ static int sport_startup(struct uart_port *port) { @@ -226,6 +283,21 @@ static int sport_startup(struct uart_port *port) goto fail2; } +#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS + if (up->cts_pin >= 0) { + if (request_irq(gpio_to_irq(up->cts_pin), + sport_mctrl_cts_int, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | + IRQF_DISABLED, "BFIN_SPORT_UART_CTS", up)) { + up->cts_pin = -1; + dev_info(port->dev, "Unable to attach BlackFin UART \ + over SPORT CTS interrupt. So, disable it.\n"); + } + } + if (up->rts_pin >= 0) + gpio_direction_output(up->rts_pin, 0); +#endif + return 0; fail2: free_irq(up->port.irq+1, up); @@ -283,17 +355,6 @@ static unsigned int sport_tx_empty(struct uart_port *port) return 0; } -static unsigned int sport_get_mctrl(struct uart_port *port) -{ - pr_debug("%s enter\n", __func__); - return (TIOCM_CTS | TIOCM_CD | TIOCM_DSR); -} - -static void sport_set_mctrl(struct uart_port *port, unsigned int mctrl) -{ - pr_debug("%s enter\n", __func__); -} - static void sport_stop_tx(struct uart_port *port) { struct sport_uart_port *up = (struct sport_uart_port *)port; @@ -364,6 +425,10 @@ static void sport_shutdown(struct uart_port *port) free_irq(up->port.irq, up); free_irq(up->port.irq+1, up); free_irq(up->err_irq, up); +#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS + if (up->cts_pin >= 0) + free_irq(gpio_to_irq(up->cts_pin), up); +#endif } static const char *sport_type(struct uart_port *port) @@ -536,7 +601,11 @@ sport_uart_console_setup(struct console *co, char *options) int baud = 57600; int bits = 8; int parity = 'n'; +# ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS + int flow = 'r'; +# else int flow = 'n'; +# endif /* Check whether an invalid uart number has been specified */ if (co->index < 0 || co->index >= BFIN_SPORT_UART_MAX_PORTS) @@ -729,6 +798,22 @@ static int __devinit sport_uart_probe(struct platform_device *pdev) ret = -ENOENT; goto out_error_unmap; } +#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) + sport->cts_pin = -1; + else + sport->cts_pin = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IO, 1); + if (res == NULL) + sport->rts_pin = -1; + else + sport->rts_pin = res->start; + + if (sport->rts_pin >= 0) + gpio_request(sport->rts_pin, DRV_NAME); +#endif } #ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE @@ -767,6 +852,10 @@ static int __devexit sport_uart_remove(struct platform_device *pdev) if (sport) { uart_remove_one_port(&sport_uart_reg, &sport->port); +#ifdef CONFIG_SERIAL_BFIN_CTSRTS + if (sport->rts_pin >= 0) + gpio_free(sport->rts_pin); +#endif iounmap(sport->port.membase); peripheral_free_list( (unsigned short *)pdev->dev.platform_data); diff --git a/drivers/serial/bfin_sport_uart.h b/drivers/serial/bfin_sport_uart.h index 9791fcc80a74..9ce253e381d2 100644 --- a/drivers/serial/bfin_sport_uart.h +++ b/drivers/serial/bfin_sport_uart.h @@ -72,4 +72,15 @@ #define SPORT_TX_FIFO_SIZE 8 +#define SPORT_UART_GET_CTS(x) gpio_get_value(x->cts_pin) +#define SPORT_UART_DISABLE_RTS(x) gpio_set_value(x->rts_pin, 1) +#define SPORT_UART_ENABLE_RTS(x) gpio_set_value(x->rts_pin, 0) + +#if defined(CONFIG_SERIAL_BFIN_SPORT0_UART_CTSRTS) \ + || defined(CONFIG_SERIAL_BFIN_SPORT1_UART_CTSRTS) \ + || defined(CONFIG_SERIAL_BFIN_SPORT2_UART_CTSRTS) \ + || defined(CONFIG_SERIAL_BFIN_SPORT3_UART_CTSRTS) +# define CONFIG_SERIAL_BFIN_SPORT_CTSRTS +#endif + #endif /* _BFIN_SPORT_UART_H */ From 9498dc95ca6fa6e5b044f39696ab317996fd2fe5 Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 9 Mar 2010 12:25:34 -0500 Subject: [PATCH 07/25] serial: bfin_sport_uart: protect changes to uart_port Common serial API says we need to grab the port lock before modifying the port state to prevent inconsistent state between threads. Signed-off-by: Sonic Zhang Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index 8cb0a40c2baa..5224db2d2793 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -498,6 +498,8 @@ static void sport_set_termios(struct uart_port *port, /* up->parib = 1; */ } + spin_lock_irqsave(&up->port.lock, flags); + port->read_status_mask = OE; if (termios->c_iflag & INPCK) port->read_status_mask |= (FE | PE); @@ -538,8 +540,6 @@ static void sport_set_termios(struct uart_port *port, /* uart baud rate */ port->uartclk = uart_get_baud_rate(port, termios, old, 0, get_sclk()/16); - spin_lock_irqsave(&up->port.lock, flags); - /* Disable UART */ SPORT_PUT_TCR1(up, SPORT_GET_TCR1(up) & ~TSPEN); SPORT_PUT_RCR1(up, SPORT_GET_RCR1(up) & ~RSPEN); From f4d10ca895841ec0090bed81e614b6c731ac7d7d Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 9 Mar 2010 12:25:35 -0500 Subject: [PATCH 08/25] serial: bfin_sport_uart: zero sport_uart_port if allocated dynamically Need to initialize the SPORT state rather than using random memory. Signed-off-by: Sonic Zhang Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index 5224db2d2793..6ee73da6976b 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -746,11 +746,11 @@ static int __devinit sport_uart_probe(struct platform_device *pdev) if (bfin_sport_uart_ports[pdev->id] == NULL) { bfin_sport_uart_ports[pdev->id] = - kmalloc(sizeof(struct sport_uart_port), GFP_KERNEL); + kzalloc(sizeof(struct sport_uart_port), GFP_KERNEL); sport = bfin_sport_uart_ports[pdev->id]; if (!sport) { dev_err(&pdev->dev, - "Fail to kmalloc sport_uart_port\n"); + "Fail to malloc sport_uart_port\n"); return -ENOMEM; } From 60bd940f142741092b95a2e572f81741dc867545 Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Tue, 9 Mar 2010 12:25:36 -0500 Subject: [PATCH 09/25] serial: bfin_sport_uart: drop useless status masks These were all copied over from the Blackfin UART driver, but they don't make sense here because these bits are all specific to the Blackfin UART. Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.c | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index 6ee73da6976b..b9938acb0309 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -500,27 +500,12 @@ static void sport_set_termios(struct uart_port *port, spin_lock_irqsave(&up->port.lock, flags); - port->read_status_mask = OE; - if (termios->c_iflag & INPCK) - port->read_status_mask |= (FE | PE); - if (termios->c_iflag & (BRKINT | PARMRK)) - port->read_status_mask |= BI; + port->read_status_mask = 0; /* * Characters to ignore */ port->ignore_status_mask = 0; - if (termios->c_iflag & IGNPAR) - port->ignore_status_mask |= FE | PE; - if (termios->c_iflag & IGNBRK) { - port->ignore_status_mask |= BI; - /* - * If we're ignoring parity and break indicators, - * ignore overruns too (for real raw support). - */ - if (termios->c_iflag & IGNPAR) - port->ignore_status_mask |= OE; - } /* RX extract mask */ up->rxmask = 0x01 | (((up->csize + up->stopb) * 2 - 1) << 0x8); From 9356c46170e0551f72467c9b0fb9a856a36aa2de Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 9 Mar 2010 12:25:37 -0500 Subject: [PATCH 10/25] serial: bfin_sport_uart: only enable SPORT TX if data is to be sent Rather than always turn on the SPORT TX interrupt, only do it when we've actually queued up data for transmission. This avoids useless interrupt processing. Signed-off-by: Sonic Zhang Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index b9938acb0309..0b459cf1b2eb 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -54,7 +54,7 @@ struct sport_uart_port { #endif }; -static void sport_uart_tx_chars(struct sport_uart_port *up); +static int sport_uart_tx_chars(struct sport_uart_port *up); static void sport_stop_tx(struct uart_port *port); static inline void tx_one_byte(struct sport_uart_port *up, unsigned int value) @@ -307,18 +307,24 @@ static int sport_startup(struct uart_port *port) return ret; } -static void sport_uart_tx_chars(struct sport_uart_port *up) +/* + * sport_uart_tx_chars + * + * ret 1 means need to enable sport. + * ret 0 means do nothing. + */ +static int sport_uart_tx_chars(struct sport_uart_port *up) { struct circ_buf *xmit = &up->port.state->xmit; if (SPORT_GET_STAT(up) & TXF) - return; + return 0; if (up->port.x_char) { tx_one_byte(up, up->port.x_char); up->port.icount.tx++; up->port.x_char = 0; - return; + return 1; } if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) { @@ -329,7 +335,7 @@ static void sport_uart_tx_chars(struct sport_uart_port *up) */ if (SPORT_GET_STAT(up) & TXHRE) sport_stop_tx(&up->port); - return; + return 0; } while(!(SPORT_GET_STAT(up) & TXF) && !uart_circ_empty(xmit)) { @@ -340,6 +346,8 @@ static void sport_uart_tx_chars(struct sport_uart_port *up) if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&up->port); + + return 1; } static unsigned int sport_tx_empty(struct uart_port *port) @@ -361,6 +369,9 @@ static void sport_stop_tx(struct uart_port *port) pr_debug("%s enter\n", __func__); + if (!(SPORT_GET_TCR1(up) & TSPEN)) + return; + /* Although the hold register is empty, last byte is still in shift * register and not sent out yet. So, put a dummy data into TX FIFO. * Then, sport tx stops when last byte is shift out and the dummy @@ -383,11 +394,12 @@ static void sport_start_tx(struct uart_port *port) pr_debug("%s enter\n", __func__); /* Write data into SPORT FIFO before enable SPROT to transmit */ - sport_uart_tx_chars(up); + if (sport_uart_tx_chars(up)) { + /* Enable transmit, then an interrupt will generated */ + SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) | TSPEN)); + SSYNC(); + } - /* Enable transmit, then an interrupt will generated */ - SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) | TSPEN)); - SSYNC(); pr_debug("%s exit\n", __func__); } From 2ce1efc9e3af2d735e7dfca31553676229c8bfcf Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Tue, 9 Mar 2010 12:25:38 -0500 Subject: [PATCH 11/25] serial: bfin_sport_uart: pull in bfin_sport.h for SPORT defines Now that the SPORT MMR defines have been unified, switch over to it. Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index 0b459cf1b2eb..66cda2290de0 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -34,6 +34,7 @@ #include #include +#include #include #include From d671250b983a2ca79daa37ba8915c0602cc2aee8 Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Tue, 9 Mar 2010 12:25:39 -0500 Subject: [PATCH 12/25] serial: bfin_sport_uart: drop the experimental markings Should be stable now ... Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 63258305c057..f41cfceea466 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -1423,8 +1423,8 @@ config SERIAL_SC26XX_CONSOLE Support for Console on SC2681/SC2692 serial ports. config SERIAL_BFIN_SPORT - tristate "Blackfin SPORT emulate UART (EXPERIMENTAL)" - depends on BLACKFIN && EXPERIMENTAL + tristate "Blackfin SPORT emulate UART" + depends on BLACKFIN select SERIAL_CORE help Enable SPORT emulate UART on Blackfin series. From be991593f8e9994697a53d6a53112bfb951a8892 Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Tue, 9 Mar 2010 12:25:40 -0500 Subject: [PATCH 13/25] serial: bfin_sport_uart: drop redundant cpu depends The BF54xM procs imply the related BF54x define, so no need to check both. Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index f41cfceea466..577d2762a4f4 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -1439,7 +1439,7 @@ config SERIAL_BFIN_SPORT_CONSOLE config SERIAL_BFIN_SPORT0_UART bool "Enable UART over SPORT0" - depends on SERIAL_BFIN_SPORT && !(BF542 || BF542M || BF544 || BF544M) + depends on SERIAL_BFIN_SPORT && !(BF542 || BF544) help Enable UART over SPORT0 From 1ff454ef9b1e852e8f4d295a68a715b1dddf233c Mon Sep 17 00:00:00 2001 From: Pavan Savoy Date: Thu, 8 Apr 2010 13:16:52 -0500 Subject: [PATCH 14/25] serial: TTY: new ldiscs for staging Push the max ldiscs by a few number to allow ldiscs to exist in the staging directory and elsewhere. Signed-off-by: Pavan Savoy Signed-off-by: Greg Kroah-Hartman --- include/linux/tty.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/tty.h b/include/linux/tty.h index bb44fa9ae135..7e605e4b3da3 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -23,7 +23,7 @@ */ #define NR_UNIX98_PTY_DEFAULT 4096 /* Default maximum for Unix98 ptys */ #define NR_UNIX98_PTY_MAX (1 << MINORBITS) /* Absolute limit */ -#define NR_LDISCS 21 +#define NR_LDISCS 30 /* line disciplines */ #define N_TTY 0 From e1eaea46bb4020b38a141b84f88565d4603f8dd0 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Fri, 26 Mar 2010 11:32:54 +0000 Subject: [PATCH 15/25] tty: n_gsm line discipline Add an implementation of GSM 0710 MUX. The implementation currently supports - Basic and advanced framing (as either end of the link) - UI or UIH data frames - Adaption layer 1-4 (1 and 2 via tty, 3 and 4 as skbuff lists) - Modem and control messages including the correct retry process - Flow control and exposes the MUX channels as a set of virtual tty devices including modem signals. This is an experimental driver. Signed-off-by: Alan Cox Signed-off-by: Greg Kroah-Hartman --- drivers/char/Kconfig | 9 +- drivers/char/Makefile | 1 + drivers/char/n_gsm.c | 2763 ++++++++++++++++++++++++++++++++++++++++ include/linux/gsmmux.h | 25 + include/linux/tty.h | 1 + 5 files changed, 2798 insertions(+), 1 deletion(-) create mode 100644 drivers/char/n_gsm.c create mode 100644 include/linux/gsmmux.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 3141dd3b6e53..51a57009aec8 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -276,11 +276,18 @@ config N_HDLC Allows synchronous HDLC communications with tty device drivers that support synchronous HDLC such as the Microgate SyncLink adapter. - This driver can only be built as a module ( = code which can be + This driver can be built as a module ( = code which can be inserted in and removed from the running kernel whenever you want). The module will be called n_hdlc. If you want to do that, say M here. +config N_GSM + tristate "GSM MUX line discipline support (EXPERIMENTAL)" + depends on EXPERIMENTAL + help + This line discipline provides support for the GSM MUX protocol and + presents the mux as a set of 61 individual tty devices. + config RISCOM8 tristate "SDL RISCom/8 card support" depends on SERIAL_NONSTANDARD diff --git a/drivers/char/Makefile b/drivers/char/Makefile index f957edf7e45d..d39be4cf1f5d 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_SYNCLINK) += synclink.o obj-$(CONFIG_SYNCLINKMP) += synclinkmp.o obj-$(CONFIG_SYNCLINK_GT) += synclink_gt.o obj-$(CONFIG_N_HDLC) += n_hdlc.o +obj-$(CONFIG_N_GSM) += n_gsm.o obj-$(CONFIG_AMIGA_BUILTIN_SERIAL) += amiserial.o obj-$(CONFIG_SX) += sx.o generic_serial.o obj-$(CONFIG_RIO) += rio/ generic_serial.o diff --git a/drivers/char/n_gsm.c b/drivers/char/n_gsm.c new file mode 100644 index 000000000000..c4161d5e053d --- /dev/null +++ b/drivers/char/n_gsm.c @@ -0,0 +1,2763 @@ +/* + * n_gsm.c GSM 0710 tty multiplexor + * Copyright (c) 2009/10 Intel Corporation + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * * THIS IS A DEVELOPMENT SNAPSHOT IT IS NOT A FINAL RELEASE * + * + * TO DO: + * Mostly done: ioctls for setting modes/timing + * Partly done: hooks so you can pull off frames to non tty devs + * Restart DLCI 0 when it closes ? + * Test basic encoding + * Improve the tx engine + * Resolve tx side locking by adding a queue_head and routing + * all control traffic via it + * General tidy/document + * Review the locking/move to refcounts more (mux now moved to an + * alloc/free model ready) + * Use newest tty open/close port helpers and install hooks + * What to do about power functions ? + * Termios setting and negotiation + * Do we need a 'which mux are you' ioctl to correlate mux and tty sets + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int debug; +module_param(debug, int, 0600); + +#define T1 (HZ/10) +#define T2 (HZ/3) +#define N2 3 + +/* Use long timers for testing at low speed with debug on */ +#ifdef DEBUG_TIMING +#define T1 HZ +#define T2 (2 * HZ) +#endif + +/* Semi-arbitary buffer size limits. 0710 is normally run with 32-64 byte + limits so this is plenty */ +#define MAX_MRU 512 +#define MAX_MTU 512 + +/* + * Each block of data we have queued to go out is in the form of + * a gsm_msg which holds everything we need in a link layer independant + * format + */ + +struct gsm_msg { + struct gsm_msg *next; + u8 addr; /* DLCI address + flags */ + u8 ctrl; /* Control byte + flags */ + unsigned int len; /* Length of data block (can be zero) */ + unsigned char *data; /* Points into buffer but not at the start */ + unsigned char buffer[0]; +}; + +/* + * Each active data link has a gsm_dlci structure associated which ties + * the link layer to an optional tty (if the tty side is open). To avoid + * complexity right now these are only ever freed up when the mux is + * shut down. + * + * At the moment we don't free DLCI objects until the mux is torn down + * this avoid object life time issues but might be worth review later. + */ + +struct gsm_dlci { + struct gsm_mux *gsm; + int addr; + int state; +#define DLCI_CLOSED 0 +#define DLCI_OPENING 1 /* Sending SABM not seen UA */ +#define DLCI_OPEN 2 /* SABM/UA complete */ +#define DLCI_CLOSING 3 /* Sending DISC not seen UA/DM */ + + /* Link layer */ + spinlock_t lock; /* Protects the internal state */ + struct timer_list t1; /* Retransmit timer for SABM and UA */ + int retries; + /* Uplink tty if active */ + struct tty_port port; /* The tty bound to this DLCI if there is one */ + struct kfifo *fifo; /* Queue fifo for the DLCI */ + struct kfifo _fifo; /* For new fifo API porting only */ + int adaption; /* Adaption layer in use */ + u32 modem_rx; /* Our incoming virtual modem lines */ + u32 modem_tx; /* Our outgoing modem lines */ + int dead; /* Refuse re-open */ + /* Flow control */ + int throttled; /* Private copy of throttle state */ + int constipated; /* Throttle status for outgoing */ + /* Packetised I/O */ + struct sk_buff *skb; /* Frame being sent */ + struct sk_buff_head skb_list; /* Queued frames */ + /* Data handling callback */ + void (*data)(struct gsm_dlci *dlci, u8 *data, int len); +}; + +/* DLCI 0, 62/63 are special or reseved see gsmtty_open */ + +#define NUM_DLCI 64 + +/* + * DLCI 0 is used to pass control blocks out of band of the data + * flow (and with a higher link priority). One command can be outstanding + * at a time and we use this structure to manage them. They are created + * and destroyed by the user context, and updated by the receive paths + * and timers + */ + +struct gsm_control { + u8 cmd; /* Command we are issuing */ + u8 *data; /* Data for the command in case we retransmit */ + int len; /* Length of block for retransmission */ + int done; /* Done flag */ + int error; /* Error if any */ +}; + +/* + * Each GSM mux we have is represented by this structure. If we are + * operating as an ldisc then we use this structure as our ldisc + * state. We need to sort out lifetimes and locking with respect + * to the gsm mux array. For now we don't free DLCI objects that + * have been instantiated until the mux itself is terminated. + * + * To consider further: tty open versus mux shutdown. + */ + +struct gsm_mux { + struct tty_struct *tty; /* The tty our ldisc is bound to */ + spinlock_t lock; + + /* Events on the GSM channel */ + wait_queue_head_t event; + + /* Bits for GSM mode decoding */ + + /* Framing Layer */ + unsigned char *buf; + int state; +#define GSM_SEARCH 0 +#define GSM_START 1 +#define GSM_ADDRESS 2 +#define GSM_CONTROL 3 +#define GSM_LEN 4 +#define GSM_DATA 5 +#define GSM_FCS 6 +#define GSM_OVERRUN 7 + unsigned int len; + unsigned int address; + unsigned int count; + int escape; + int encoding; + u8 control; + u8 fcs; + u8 *txframe; /* TX framing buffer */ + + /* Methods for the receiver side */ + void (*receive)(struct gsm_mux *gsm, u8 ch); + void (*error)(struct gsm_mux *gsm, u8 ch, u8 flag); + /* And transmit side */ + int (*output)(struct gsm_mux *mux, u8 *data, int len); + + /* Link Layer */ + unsigned int mru; + unsigned int mtu; + int initiator; /* Did we initiate connection */ + int dead; /* Has the mux been shut down */ + struct gsm_dlci *dlci[NUM_DLCI]; + int constipated; /* Asked by remote to shut up */ + + spinlock_t tx_lock; + unsigned int tx_bytes; /* TX data outstanding */ +#define TX_THRESH_HI 8192 +#define TX_THRESH_LO 2048 + struct gsm_msg *tx_head; /* Pending data packets */ + struct gsm_msg *tx_tail; + + /* Control messages */ + struct timer_list t2_timer; /* Retransmit timer for commands */ + int cretries; /* Command retry counter */ + struct gsm_control *pending_cmd;/* Our current pending command */ + spinlock_t control_lock; /* Protects the pending command */ + + /* Configuration */ + int adaption; /* 1 or 2 supported */ + u8 ftype; /* UI or UIH */ + int t1, t2; /* Timers in 1/100th of a sec */ + int n2; /* Retry count */ + + /* Statistics (not currently exposed) */ + unsigned long bad_fcs; + unsigned long malformed; + unsigned long io_error; + unsigned long bad_size; + unsigned long unsupported; +}; + + +/* + * Mux objects - needed so that we can translate a tty index into the + * relevant mux and DLCI. + */ + +#define MAX_MUX 4 /* 256 minors */ +static struct gsm_mux *gsm_mux[MAX_MUX]; /* GSM muxes */ +static spinlock_t gsm_mux_lock; + +/* + * This section of the driver logic implements the GSM encodings + * both the basic and the 'advanced'. Reliable transport is not + * supported. + */ + +#define CR 0x02 +#define EA 0x01 +#define PF 0x10 + +/* I is special: the rest are ..*/ +#define RR 0x01 +#define UI 0x03 +#define RNR 0x05 +#define REJ 0x09 +#define DM 0x0F +#define SABM 0x2F +#define DISC 0x43 +#define UA 0x63 +#define UIH 0xEF + +/* Channel commands */ +#define CMD_NSC 0x09 +#define CMD_TEST 0x11 +#define CMD_PSC 0x21 +#define CMD_RLS 0x29 +#define CMD_FCOFF 0x31 +#define CMD_PN 0x41 +#define CMD_RPN 0x49 +#define CMD_FCON 0x51 +#define CMD_CLD 0x61 +#define CMD_SNC 0x69 +#define CMD_MSC 0x71 + +/* Virtual modem bits */ +#define MDM_FC 0x01 +#define MDM_RTC 0x02 +#define MDM_RTR 0x04 +#define MDM_IC 0x20 +#define MDM_DV 0x40 + +#define GSM0_SOF 0xF9 +#define GSM1_SOF 0x7E +#define GSM1_ESCAPE 0x7D +#define GSM1_ESCAPE_BITS 0x20 +#define XON 0x11 +#define XOFF 0x13 + +static const struct tty_port_operations gsm_port_ops; + +/* + * CRC table for GSM 0710 + */ + +static const u8 gsm_fcs8[256] = { + 0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75, + 0x0E, 0x9F, 0xED, 0x7C, 0x09, 0x98, 0xEA, 0x7B, + 0x1C, 0x8D, 0xFF, 0x6E, 0x1B, 0x8A, 0xF8, 0x69, + 0x12, 0x83, 0xF1, 0x60, 0x15, 0x84, 0xF6, 0x67, + 0x38, 0xA9, 0xDB, 0x4A, 0x3F, 0xAE, 0xDC, 0x4D, + 0x36, 0xA7, 0xD5, 0x44, 0x31, 0xA0, 0xD2, 0x43, + 0x24, 0xB5, 0xC7, 0x56, 0x23, 0xB2, 0xC0, 0x51, + 0x2A, 0xBB, 0xC9, 0x58, 0x2D, 0xBC, 0xCE, 0x5F, + 0x70, 0xE1, 0x93, 0x02, 0x77, 0xE6, 0x94, 0x05, + 0x7E, 0xEF, 0x9D, 0x0C, 0x79, 0xE8, 0x9A, 0x0B, + 0x6C, 0xFD, 0x8F, 0x1E, 0x6B, 0xFA, 0x88, 0x19, + 0x62, 0xF3, 0x81, 0x10, 0x65, 0xF4, 0x86, 0x17, + 0x48, 0xD9, 0xAB, 0x3A, 0x4F, 0xDE, 0xAC, 0x3D, + 0x46, 0xD7, 0xA5, 0x34, 0x41, 0xD0, 0xA2, 0x33, + 0x54, 0xC5, 0xB7, 0x26, 0x53, 0xC2, 0xB0, 0x21, + 0x5A, 0xCB, 0xB9, 0x28, 0x5D, 0xCC, 0xBE, 0x2F, + 0xE0, 0x71, 0x03, 0x92, 0xE7, 0x76, 0x04, 0x95, + 0xEE, 0x7F, 0x0D, 0x9C, 0xE9, 0x78, 0x0A, 0x9B, + 0xFC, 0x6D, 0x1F, 0x8E, 0xFB, 0x6A, 0x18, 0x89, + 0xF2, 0x63, 0x11, 0x80, 0xF5, 0x64, 0x16, 0x87, + 0xD8, 0x49, 0x3B, 0xAA, 0xDF, 0x4E, 0x3C, 0xAD, + 0xD6, 0x47, 0x35, 0xA4, 0xD1, 0x40, 0x32, 0xA3, + 0xC4, 0x55, 0x27, 0xB6, 0xC3, 0x52, 0x20, 0xB1, + 0xCA, 0x5B, 0x29, 0xB8, 0xCD, 0x5C, 0x2E, 0xBF, + 0x90, 0x01, 0x73, 0xE2, 0x97, 0x06, 0x74, 0xE5, + 0x9E, 0x0F, 0x7D, 0xEC, 0x99, 0x08, 0x7A, 0xEB, + 0x8C, 0x1D, 0x6F, 0xFE, 0x8B, 0x1A, 0x68, 0xF9, + 0x82, 0x13, 0x61, 0xF0, 0x85, 0x14, 0x66, 0xF7, + 0xA8, 0x39, 0x4B, 0xDA, 0xAF, 0x3E, 0x4C, 0xDD, + 0xA6, 0x37, 0x45, 0xD4, 0xA1, 0x30, 0x42, 0xD3, + 0xB4, 0x25, 0x57, 0xC6, 0xB3, 0x22, 0x50, 0xC1, + 0xBA, 0x2B, 0x59, 0xC8, 0xBD, 0x2C, 0x5E, 0xCF +}; + +#define INIT_FCS 0xFF +#define GOOD_FCS 0xCF + +/** + * gsm_fcs_add - update FCS + * @fcs: Current FCS + * @c: Next data + * + * Update the FCS to include c. Uses the algorithm in the specification + * notes. + */ + +static inline u8 gsm_fcs_add(u8 fcs, u8 c) +{ + return gsm_fcs8[fcs ^ c]; +} + +/** + * gsm_fcs_add_block - update FCS for a block + * @fcs: Current FCS + * @c: buffer of data + * @len: length of buffer + * + * Update the FCS to include c. Uses the algorithm in the specification + * notes. + */ + +static inline u8 gsm_fcs_add_block(u8 fcs, u8 *c, int len) +{ + while (len--) + fcs = gsm_fcs8[fcs ^ *c++]; + return fcs; +} + +/** + * gsm_read_ea - read a byte into an EA + * @val: variable holding value + * c: byte going into the EA + * + * Processes one byte of an EA. Updates the passed variable + * and returns 1 if the EA is now completely read + */ + +static int gsm_read_ea(unsigned int *val, u8 c) +{ + /* Add the next 7 bits into the value */ + *val <<= 7; + *val |= c >> 1; + /* Was this the last byte of the EA 1 = yes*/ + return c & EA; +} + +/** + * gsm_encode_modem - encode modem data bits + * @dlci: DLCI to encode from + * + * Returns the correct GSM encoded modem status bits (6 bit field) for + * the current status of the DLCI and attached tty object + */ + +static u8 gsm_encode_modem(const struct gsm_dlci *dlci) +{ + u8 modembits = 0; + /* FC is true flow control not modem bits */ + if (dlci->throttled) + modembits |= MDM_FC; + if (dlci->modem_tx & TIOCM_DTR) + modembits |= MDM_RTC; + if (dlci->modem_tx & TIOCM_RTS) + modembits |= MDM_RTR; + if (dlci->modem_tx & TIOCM_RI) + modembits |= MDM_IC; + if (dlci->modem_tx & TIOCM_CD) + modembits |= MDM_DV; + return modembits; +} + +/** + * gsm_print_packet - display a frame for debug + * @hdr: header to print before decode + * @addr: address EA from the frame + * @cr: C/R bit from the frame + * @control: control including PF bit + * @data: following data bytes + * @dlen: length of data + * + * Displays a packet in human readable format for debugging purposes. The + * style is based on amateur radio LAP-B dump display. + */ + +static void gsm_print_packet(const char *hdr, int addr, int cr, + u8 control, const u8 *data, int dlen) +{ + if (!(debug & 1)) + return; + + printk(KERN_INFO "%s %d) %c: ", hdr, addr, "RC"[cr]); + + switch (control & ~PF) { + case SABM: + printk(KERN_CONT "SABM"); + break; + case UA: + printk(KERN_CONT "UA"); + break; + case DISC: + printk(KERN_CONT "DISC"); + break; + case DM: + printk(KERN_CONT "DM"); + break; + case UI: + printk(KERN_CONT "UI"); + break; + case UIH: + printk(KERN_CONT "UIH"); + break; + default: + if (!(control & 0x01)) { + printk(KERN_CONT "I N(S)%d N(R)%d", + (control & 0x0E) >> 1, (control & 0xE)>> 5); + } else switch (control & 0x0F) { + case RR: + printk("RR(%d)", (control & 0xE0) >> 5); + break; + case RNR: + printk("RNR(%d)", (control & 0xE0) >> 5); + break; + case REJ: + printk("REJ(%d)", (control & 0xE0) >> 5); + break; + default: + printk(KERN_CONT "[%02X]", control); + } + } + + if (control & PF) + printk(KERN_CONT "(P)"); + else + printk(KERN_CONT "(F)"); + + if (dlen) { + int ct = 0; + while (dlen--) { + if (ct % 8 == 0) + printk(KERN_CONT "\n "); + printk(KERN_CONT "%02X ", *data++); + ct++; + } + } + printk(KERN_CONT "\n"); +} + + +/* + * Link level transmission side + */ + +/** + * gsm_stuff_packet - bytestuff a packet + * @ibuf: input + * @obuf: output + * @len: length of input + * + * Expand a buffer by bytestuffing it. The worst case size change + * is doubling and the caller is responsible for handing out + * suitable sized buffers. + */ + +static int gsm_stuff_frame(const u8 *input, u8 *output, int len) +{ + int olen = 0; + while (len--) { + if (*input == GSM1_SOF || *input == GSM1_ESCAPE + || *input == XON || *input == XOFF) { + *output++ = GSM1_ESCAPE; + *output++ = *input++ ^ GSM1_ESCAPE_BITS; + olen++; + } else + *output++ = *input++; + olen++; + } + return olen; +} + +static void hex_packet(const unsigned char *p, int len) +{ + int i; + for (i = 0; i < len; i++) { + if (i && (i % 16) == 0) + printk("\n"); + printk("%02X ", *p++); + } + printk("\n"); +} + +/** + * gsm_send - send a control frame + * @gsm: our GSM mux + * @addr: address for control frame + * @cr: command/response bit + * @control: control byte including PF bit + * + * Format up and transmit a control frame. These do not go via the + * queueing logic as they should be transmitted ahead of data when + * they are needed. + * + * FIXME: Lock versus data TX path + */ + +static void gsm_send(struct gsm_mux *gsm, int addr, int cr, int control) +{ + int len; + u8 cbuf[10]; + u8 ibuf[3]; + + switch (gsm->encoding) { + case 0: + cbuf[0] = GSM0_SOF; + cbuf[1] = (addr << 2) | (cr << 1) | EA; + cbuf[2] = control; + cbuf[3] = EA; /* Length of data = 0 */ + cbuf[4] = 0xFF - gsm_fcs_add_block(INIT_FCS, cbuf + 1, 3); + cbuf[5] = GSM0_SOF; + len = 6; + break; + case 1: + case 2: + /* Control frame + packing (but not frame stuffing) in mode 1 */ + ibuf[0] = (addr << 2) | (cr << 1) | EA; + ibuf[1] = control; + ibuf[2] = 0xFF - gsm_fcs_add_block(INIT_FCS, ibuf, 2); + /* Stuffing may double the size worst case */ + len = gsm_stuff_frame(ibuf, cbuf + 1, 3); + /* Now add the SOF markers */ + cbuf[0] = GSM1_SOF; + cbuf[len + 1] = GSM1_SOF; + /* FIXME: we can omit the lead one in many cases */ + len += 2; + break; + default: + WARN_ON(1); + return; + } + gsm->output(gsm, cbuf, len); + gsm_print_packet("-->", addr, cr, control, NULL, 0); +} + +/** + * gsm_response - send a control response + * @gsm: our GSM mux + * @addr: address for control frame + * @control: control byte including PF bit + * + * Format up and transmit a link level response frame. + */ + +static inline void gsm_response(struct gsm_mux *gsm, int addr, int control) +{ + gsm_send(gsm, addr, 0, control); +} + +/** + * gsm_command - send a control command + * @gsm: our GSM mux + * @addr: address for control frame + * @control: control byte including PF bit + * + * Format up and transmit a link level command frame. + */ + +static inline void gsm_command(struct gsm_mux *gsm, int addr, int control) +{ + gsm_send(gsm, addr, 1, control); +} + +/* Data transmission */ + +#define HDR_LEN 6 /* ADDR CTRL [LEN.2] DATA FCS */ + +/** + * gsm_data_alloc - allocate data frame + * @gsm: GSM mux + * @addr: DLCI address + * @len: length excluding header and FCS + * @ctrl: control byte + * + * Allocate a new data buffer for sending frames with data. Space is left + * at the front for header bytes but that is treated as an implementation + * detail and not for the high level code to use + */ + +static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len, + u8 ctrl) +{ + struct gsm_msg *m = kmalloc(sizeof(struct gsm_msg) + len + HDR_LEN, + GFP_ATOMIC); + if (m == NULL) + return NULL; + m->data = m->buffer + HDR_LEN - 1; /* Allow for FCS */ + m->len = len; + m->addr = addr; + m->ctrl = ctrl; + m->next = NULL; + return m; +} + +/** + * gsm_data_kick - poke the queue + * @gsm: GSM Mux + * + * The tty device has called us to indicate that room has appeared in + * the transmit queue. Ram more data into the pipe if we have any + * + * FIXME: lock against link layer control transmissions + */ + +static void gsm_data_kick(struct gsm_mux *gsm) +{ + struct gsm_msg *msg = gsm->tx_head; + int len; + int skip_sof = 0; + + /* FIXME: We need to apply this solely to data messages */ + if (gsm->constipated) + return; + + while (gsm->tx_head != NULL) { + msg = gsm->tx_head; + if (gsm->encoding != 0) { + gsm->txframe[0] = GSM1_SOF; + len = gsm_stuff_frame(msg->data, + gsm->txframe + 1, msg->len); + gsm->txframe[len + 1] = GSM1_SOF; + len += 2; + } else { + gsm->txframe[0] = GSM0_SOF; + memcpy(gsm->txframe + 1 , msg->data, msg->len); + gsm->txframe[msg->len + 1] = GSM0_SOF; + len = msg->len + 2; + } + + if (debug & 4) { + printk("gsm_data_kick: \n"); + hex_packet(gsm->txframe, len); + } + + if (gsm->output(gsm, gsm->txframe + skip_sof, + len - skip_sof) < 0) + break; + /* FIXME: Can eliminate one SOF in many more cases */ + gsm->tx_head = msg->next; + if (gsm->tx_head == NULL) + gsm->tx_tail = NULL; + gsm->tx_bytes -= msg->len; + kfree(msg); + /* For a burst of frames skip the extra SOF within the + burst */ + skip_sof = 1; + } +} + +/** + * __gsm_data_queue - queue a UI or UIH frame + * @dlci: DLCI sending the data + * @msg: message queued + * + * Add data to the transmit queue and try and get stuff moving + * out of the mux tty if not already doing so. The Caller must hold + * the gsm tx lock. + */ + +static void __gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg) +{ + struct gsm_mux *gsm = dlci->gsm; + u8 *dp = msg->data; + u8 *fcs = dp + msg->len; + + /* Fill in the header */ + if (gsm->encoding == 0) { + if (msg->len < 128) + *--dp = (msg->len << 1) | EA; + else { + *--dp = (msg->len >> 6) | EA; + *--dp = (msg->len & 127) << 1; + } + } + + *--dp = msg->ctrl; + if (gsm->initiator) + *--dp = (msg->addr << 2) | 2 | EA; + else + *--dp = (msg->addr << 2) | EA; + *fcs = gsm_fcs_add_block(INIT_FCS, dp , msg->data - dp); + /* Ugly protocol layering violation */ + if (msg->ctrl == UI || msg->ctrl == (UI|PF)) + *fcs = gsm_fcs_add_block(*fcs, msg->data, msg->len); + *fcs = 0xFF - *fcs; + + gsm_print_packet("Q> ", msg->addr, gsm->initiator, msg->ctrl, + msg->data, msg->len); + + /* Move the header back and adjust the length, also allow for the FCS + now tacked on the end */ + msg->len += (msg->data - dp) + 1; + msg->data = dp; + + /* Add to the actual output queue */ + if (gsm->tx_tail) + gsm->tx_tail->next = msg; + else + gsm->tx_head = msg; + gsm->tx_tail = msg; + gsm->tx_bytes += msg->len; + gsm_data_kick(gsm); +} + +/** + * gsm_data_queue - queue a UI or UIH frame + * @dlci: DLCI sending the data + * @msg: message queued + * + * Add data to the transmit queue and try and get stuff moving + * out of the mux tty if not already doing so. Take the + * the gsm tx lock and dlci lock. + */ + +static void gsm_data_queue(struct gsm_dlci *dlci, struct gsm_msg *msg) +{ + unsigned long flags; + spin_lock_irqsave(&dlci->gsm->tx_lock, flags); + __gsm_data_queue(dlci, msg); + spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags); +} + +/** + * gsm_dlci_data_output - try and push data out of a DLCI + * @gsm: mux + * @dlci: the DLCI to pull data from + * + * Pull data from a DLCI and send it into the transmit queue if there + * is data. Keep to the MRU of the mux. This path handles the usual tty + * interface which is a byte stream with optional modem data. + * + * Caller must hold the tx_lock of the mux. + */ + +static int gsm_dlci_data_output(struct gsm_mux *gsm, struct gsm_dlci *dlci) +{ + struct gsm_msg *msg; + u8 *dp; + int len, size; + int h = dlci->adaption - 1; + + len = kfifo_len(dlci->fifo); + if (len == 0) + return 0; + + /* MTU/MRU count only the data bits */ + if (len > gsm->mtu) + len = gsm->mtu; + + size = len + h; + + msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype); + /* FIXME: need a timer or something to kick this so it can't + get stuck with no work outstanding and no buffer free */ + if (msg == NULL) + return -ENOMEM; + dp = msg->data; + switch (dlci->adaption) { + case 1: /* Unstructured */ + break; + case 2: /* Unstructed with modem bits. Always one byte as we never + send inline break data */ + *dp += gsm_encode_modem(dlci); + len--; + break; + } + WARN_ON(kfifo_out_locked(dlci->fifo, dp , len, &dlci->lock) != len); + __gsm_data_queue(dlci, msg); + /* Bytes of data we used up */ + return size; +} + +/** + * gsm_dlci_data_output_framed - try and push data out of a DLCI + * @gsm: mux + * @dlci: the DLCI to pull data from + * + * Pull data from a DLCI and send it into the transmit queue if there + * is data. Keep to the MRU of the mux. This path handles framed data + * queued as skbuffs to the DLCI. + * + * Caller must hold the tx_lock of the mux. + */ + +static int gsm_dlci_data_output_framed(struct gsm_mux *gsm, + struct gsm_dlci *dlci) +{ + struct gsm_msg *msg; + u8 *dp; + int len, size; + int last = 0, first = 0; + int overhead = 0; + + /* One byte per frame is used for B/F flags */ + if (dlci->adaption == 4) + overhead = 1; + + /* dlci->skb is locked by tx_lock */ + if (dlci->skb == NULL) { + dlci->skb = skb_dequeue(&dlci->skb_list); + if (dlci->skb == NULL) + return 0; + first = 1; + } + len = dlci->skb->len + overhead; + + /* MTU/MRU count only the data bits */ + if (len > gsm->mtu) { + if (dlci->adaption == 3) { + /* Over long frame, bin it */ + kfree_skb(dlci->skb); + dlci->skb = NULL; + return 0; + } + len = gsm->mtu; + } else + last = 1; + + size = len + overhead; + msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype); + + /* FIXME: need a timer or something to kick this so it can't + get stuck with no work outstanding and no buffer free */ + if (msg == NULL) + return -ENOMEM; + dp = msg->data; + + if (dlci->adaption == 4) { /* Interruptible framed (Packetised Data) */ + /* Flag byte to carry the start/end info */ + *dp++ = last << 7 | first << 6 | 1; /* EA */ + len--; + } + memcpy(dp, skb_pull(dlci->skb, len), len); + __gsm_data_queue(dlci, msg); + if (last) + dlci->skb = NULL; + return size; +} + +/** + * gsm_dlci_data_sweep - look for data to send + * @gsm: the GSM mux + * + * Sweep the GSM mux channels in priority order looking for ones with + * data to send. We could do with optimising this scan a bit. We aim + * to fill the queue totally or up to TX_THRESH_HI bytes. Once we hit + * TX_THRESH_LO we get called again + * + * FIXME: We should round robin between groups and in theory you can + * renegotiate DLCI priorities with optional stuff. Needs optimising. + */ + +static void gsm_dlci_data_sweep(struct gsm_mux *gsm) +{ + int len; + /* Priority ordering: We should do priority with RR of the groups */ + int i = 1; + unsigned long flags; + + spin_lock_irqsave(&gsm->tx_lock, flags); + while (i < NUM_DLCI) { + struct gsm_dlci *dlci; + + if (gsm->tx_bytes > TX_THRESH_HI) + break; + dlci = gsm->dlci[i]; + if (dlci == NULL || dlci->constipated) { + i++; + continue; + } + if (dlci->adaption < 3) + len = gsm_dlci_data_output(gsm, dlci); + else + len = gsm_dlci_data_output_framed(gsm, dlci); + if (len < 0) + return; + /* DLCI empty - try the next */ + if (len == 0) + i++; + } + spin_unlock_irqrestore(&gsm->tx_lock, flags); +} + +/** + * gsm_dlci_data_kick - transmit if possible + * @dlci: DLCI to kick + * + * Transmit data from this DLCI if the queue is empty. We can't rely on + * a tty wakeup except when we filled the pipe so we need to fire off + * new data ourselves in other cases. + */ + +static void gsm_dlci_data_kick(struct gsm_dlci *dlci) +{ + unsigned long flags; + + spin_lock_irqsave(&dlci->gsm->tx_lock, flags); + /* If we have nothing running then we need to fire up */ + if (dlci->gsm->tx_bytes == 0) + gsm_dlci_data_output(dlci->gsm, dlci); + else if (dlci->gsm->tx_bytes < TX_THRESH_LO) + gsm_dlci_data_sweep(dlci->gsm); + spin_unlock_irqrestore(&dlci->gsm->tx_lock, flags); +} + +/* + * Control message processing + */ + + +/** + * gsm_control_reply - send a response frame to a control + * @gsm: gsm channel + * @cmd: the command to use + * @data: data to follow encoded info + * @dlen: length of data + * + * Encode up and queue a UI/UIH frame containing our response. + */ + +static void gsm_control_reply(struct gsm_mux *gsm, int cmd, u8 *data, + int dlen) +{ + struct gsm_msg *msg; + msg = gsm_data_alloc(gsm, 0, dlen + 2, gsm->ftype); + msg->data[0] = (cmd & 0xFE) << 1 | EA; /* Clear C/R */ + msg->data[1] = (dlen << 1) | EA; + memcpy(msg->data + 2, data, dlen); + gsm_data_queue(gsm->dlci[0], msg); +} + +/** + * gsm_process_modem - process received modem status + * @tty: virtual tty bound to the DLCI + * @dlci: DLCI to affect + * @modem: modem bits (full EA) + * + * Used when a modem control message or line state inline in adaption + * layer 2 is processed. Sort out the local modem state and throttles + */ + +static void gsm_process_modem(struct tty_struct *tty, struct gsm_dlci *dlci, + u32 modem) +{ + int mlines = 0; + u8 brk = modem >> 6; + + /* Flow control/ready to communicate */ + if (modem & MDM_FC) { + /* Need to throttle our output on this device */ + dlci->constipated = 1; + } + if (modem & MDM_RTC) { + mlines |= TIOCM_DSR | TIOCM_DTR; + dlci->constipated = 0; + gsm_dlci_data_kick(dlci); + } + /* Map modem bits */ + if (modem & MDM_RTR) + mlines |= TIOCM_RTS | TIOCM_CTS; + if (modem & MDM_IC) + mlines |= TIOCM_RI; + if (modem & MDM_DV) + mlines |= TIOCM_CD; + + /* Carrier drop -> hangup */ + if (tty) { + if ((mlines & TIOCM_CD) == 0 && (dlci->modem_rx & TIOCM_CD)) + if (!(tty->termios->c_cflag & CLOCAL)) + tty_hangup(tty); + if (brk & 0x01) + tty_insert_flip_char(tty, 0, TTY_BREAK); + } + dlci->modem_rx = mlines; +} + +/** + * gsm_control_modem - modem status received + * @gsm: GSM channel + * @data: data following command + * @clen: command length + * + * We have received a modem status control message. This is used by + * the GSM mux protocol to pass virtual modem line status and optionally + * to indicate break signals. Unpack it, convert to Linux representation + * and if need be stuff a break message down the tty. + */ + +static void gsm_control_modem(struct gsm_mux *gsm, u8 *data, int clen) +{ + unsigned int addr = 0; + unsigned int modem = 0; + struct gsm_dlci *dlci; + int len = clen; + u8 *dp = data; + struct tty_struct *tty; + + while (gsm_read_ea(&addr, *dp++) == 0) { + len--; + if (len == 0) + return; + } + /* Must be at least one byte following the EA */ + len--; + if (len <= 0) + return; + + addr >>= 1; + /* Closed port, or invalid ? */ + if (addr == 0 || addr >= NUM_DLCI || gsm->dlci[addr] == NULL) + return; + dlci = gsm->dlci[addr]; + + while (gsm_read_ea(&modem, *dp++) == 0) { + len--; + if (len == 0) + return; + } + tty = tty_port_tty_get(&dlci->port); + gsm_process_modem(tty, dlci, modem); + if (tty) { + tty_wakeup(tty); + tty_kref_put(tty); + } + gsm_control_reply(gsm, CMD_MSC, data, clen); +} + +/** + * gsm_control_rls - remote line status + * @gsm: GSM channel + * @data: data bytes + * @clen: data length + * + * The modem sends us a two byte message on the control channel whenever + * it wishes to send us an error state from the virtual link. Stuff + * this into the uplink tty if present + */ + +static void gsm_control_rls(struct gsm_mux *gsm, u8 *data, int clen) +{ + struct tty_struct *tty; + unsigned int addr = 0 ; + u8 bits; + int len = clen; + u8 *dp = data; + + while (gsm_read_ea(&addr, *dp++) == 0) { + len--; + if (len == 0) + return; + } + /* Must be at least one byte following ea */ + len--; + if (len <= 0) + return; + addr >>= 1; + /* Closed port, or invalid ? */ + if (addr == 0 || addr >= NUM_DLCI || gsm->dlci[addr] == NULL) + return; + /* No error ? */ + bits = *dp; + if ((bits & 1) == 0) + return; + /* See if we have an uplink tty */ + tty = tty_port_tty_get(&gsm->dlci[addr]->port); + + if (tty) { + if (bits & 2) + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + if (bits & 4) + tty_insert_flip_char(tty, 0, TTY_PARITY); + if (bits & 8) + tty_insert_flip_char(tty, 0, TTY_FRAME); + tty_flip_buffer_push(tty); + tty_kref_put(tty); + } + gsm_control_reply(gsm, CMD_RLS, data, clen); +} + +static void gsm_dlci_begin_close(struct gsm_dlci *dlci); + +/** + * gsm_control_message - DLCI 0 control processing + * @gsm: our GSM mux + * @command: the command EA + * @data: data beyond the command/length EAs + * @clen: length + * + * Input processor for control messages from the other end of the link. + * Processes the incoming request and queues a response frame or an + * NSC response if not supported + */ + +static void gsm_control_message(struct gsm_mux *gsm, unsigned int command, + u8 *data, int clen) +{ + u8 buf[1]; + switch (command) { + case CMD_CLD: { + struct gsm_dlci *dlci = gsm->dlci[0]; + /* Modem wishes to close down */ + if (dlci) { + dlci->dead = 1; + gsm->dead = 1; + gsm_dlci_begin_close(dlci); + } + } + break; + case CMD_TEST: + /* Modem wishes to test, reply with the data */ + gsm_control_reply(gsm, CMD_TEST, data, clen); + break; + case CMD_FCON: + /* Modem wants us to STFU */ + gsm->constipated = 1; + gsm_control_reply(gsm, CMD_FCON, NULL, 0); + break; + case CMD_FCOFF: + /* Modem can accept data again */ + gsm->constipated = 0; + gsm_control_reply(gsm, CMD_FCOFF, NULL, 0); + /* Kick the link in case it is idling */ + gsm_data_kick(gsm); + break; + case CMD_MSC: + /* Out of band modem line change indicator for a DLCI */ + gsm_control_modem(gsm, data, clen); + break; + case CMD_RLS: + /* Out of band error reception for a DLCI */ + gsm_control_rls(gsm, data, clen); + break; + case CMD_PSC: + /* Modem wishes to enter power saving state */ + gsm_control_reply(gsm, CMD_PSC, NULL, 0); + break; + /* Optional unsupported commands */ + case CMD_PN: /* Parameter negotiation */ + case CMD_RPN: /* Remote port negotation */ + case CMD_SNC: /* Service negotation command */ + default: + /* Reply to bad commands with an NSC */ + buf[0] = command; + gsm_control_reply(gsm, CMD_NSC, buf, 1); + break; + } +} + +/** + * gsm_control_response - process a response to our control + * @gsm: our GSM mux + * @command: the command (response) EA + * @data: data beyond the command/length EA + * @clen: length + * + * Process a response to an outstanding command. We only allow a single + * control message in flight so this is fairly easy. All the clean up + * is done by the caller, we just update the fields, flag it as done + * and return + */ + +static void gsm_control_response(struct gsm_mux *gsm, unsigned int command, + u8 *data, int clen) +{ + struct gsm_control *ctrl; + unsigned long flags; + + spin_lock_irqsave(&gsm->control_lock, flags); + + ctrl = gsm->pending_cmd; + /* Does the reply match our command */ + command |= 1; + if (ctrl != NULL && (command == ctrl->cmd || command == CMD_NSC)) { + /* Our command was replied to, kill the retry timer */ + del_timer(&gsm->t2_timer); + gsm->pending_cmd = NULL; + /* Rejected by the other end */ + if (command == CMD_NSC) + ctrl->error = -EOPNOTSUPP; + ctrl->done = 1; + wake_up(&gsm->event); + } + spin_unlock_irqrestore(&gsm->control_lock, flags); +} + +/** + * gsm_control_transmit - send control packet + * @gsm: gsm mux + * @ctrl: frame to send + * + * Send out a pending control command (called under control lock) + */ + +static void gsm_control_transmit(struct gsm_mux *gsm, struct gsm_control *ctrl) +{ + struct gsm_msg *msg = gsm_data_alloc(gsm, 0, ctrl->len + 1, + gsm->ftype|PF); + if (msg == NULL) + return; + msg->data[0] = (ctrl->cmd << 1) | 2 | EA; /* command */ + memcpy(msg->data + 1, ctrl->data, ctrl->len); + gsm_data_queue(gsm->dlci[0], msg); +} + +/** + * gsm_control_retransmit - retransmit a control frame + * @data: pointer to our gsm object + * + * Called off the T2 timer expiry in order to retransmit control frames + * that have been lost in the system somewhere. The control_lock protects + * us from colliding with another sender or a receive completion event. + * In that situation the timer may still occur in a small window but + * gsm->pending_cmd will be NULL and we just let the timer expire. + */ + +static void gsm_control_retransmit(unsigned long data) +{ + struct gsm_mux *gsm = (struct gsm_mux *)data; + struct gsm_control *ctrl; + unsigned long flags; + spin_lock_irqsave(&gsm->control_lock, flags); + ctrl = gsm->pending_cmd; + if (ctrl) { + gsm->cretries--; + if (gsm->cretries == 0) { + gsm->pending_cmd = NULL; + ctrl->error = -ETIMEDOUT; + ctrl->done = 1; + spin_unlock_irqrestore(&gsm->control_lock, flags); + wake_up(&gsm->event); + return; + } + gsm_control_transmit(gsm, ctrl); + mod_timer(&gsm->t2_timer, jiffies + gsm->t2 * HZ / 100); + } + spin_unlock_irqrestore(&gsm->control_lock, flags); +} + +/** + * gsm_control_send - send a control frame on DLCI 0 + * @gsm: the GSM channel + * @command: command to send including CR bit + * @data: bytes of data (must be kmalloced) + * @len: length of the block to send + * + * Queue and dispatch a control command. Only one command can be + * active at a time. In theory more can be outstanding but the matching + * gets really complicated so for now stick to one outstanding. + */ + +static struct gsm_control *gsm_control_send(struct gsm_mux *gsm, + unsigned int command, u8 *data, int clen) +{ + struct gsm_control *ctrl = kzalloc(sizeof(struct gsm_control), + GFP_KERNEL); + unsigned long flags; + if (ctrl == NULL) + return NULL; +retry: + wait_event(gsm->event, gsm->pending_cmd == NULL); + spin_lock_irqsave(&gsm->control_lock, flags); + if (gsm->pending_cmd != NULL) { + spin_unlock_irqrestore(&gsm->control_lock, flags); + goto retry; + } + ctrl->cmd = command; + ctrl->data = data; + ctrl->len = clen; + gsm->pending_cmd = ctrl; + gsm->cretries = gsm->n2; + mod_timer(&gsm->t2_timer, jiffies + gsm->t2 * HZ / 100); + gsm_control_transmit(gsm, ctrl); + spin_unlock_irqrestore(&gsm->control_lock, flags); + return ctrl; +} + +/** + * gsm_control_wait - wait for a control to finish + * @gsm: GSM mux + * @control: control we are waiting on + * + * Waits for the control to complete or time out. Frees any used + * resources and returns 0 for success, or an error if the remote + * rejected or ignored the request. + */ + +static int gsm_control_wait(struct gsm_mux *gsm, struct gsm_control *control) +{ + int err; + wait_event(gsm->event, control->done == 1); + err = control->error; + kfree(control); + return err; +} + + +/* + * DLCI level handling: Needs krefs + */ + +/* + * State transitions and timers + */ + +/** + * gsm_dlci_close - a DLCI has closed + * @dlci: DLCI that closed + * + * Perform processing when moving a DLCI into closed state. If there + * is an attached tty this is hung up + */ + +static void gsm_dlci_close(struct gsm_dlci *dlci) +{ + del_timer(&dlci->t1); + if (debug & 8) + printk("DLCI %d goes closed.\n", dlci->addr); + dlci->state = DLCI_CLOSED; + if (dlci->addr != 0) { + struct tty_struct *tty = tty_port_tty_get(&dlci->port); + if (tty) { + tty_hangup(tty); + tty_kref_put(tty); + } + kfifo_reset(dlci->fifo); + } else + dlci->gsm->dead = 1; + wake_up(&dlci->gsm->event); + /* A DLCI 0 close is a MUX termination so we need to kick that + back to userspace somehow */ +} + +/** + * gsm_dlci_open - a DLCI has opened + * @dlci: DLCI that opened + * + * Perform processing when moving a DLCI into open state. + */ + +static void gsm_dlci_open(struct gsm_dlci *dlci) +{ + /* Note that SABM UA .. SABM UA first UA lost can mean that we go + open -> open */ + del_timer(&dlci->t1); + /* This will let a tty open continue */ + dlci->state = DLCI_OPEN; + if (debug & 8) + printk("DLCI %d goes open.\n", dlci->addr); + wake_up(&dlci->gsm->event); +} + +/** + * gsm_dlci_t1 - T1 timer expiry + * @dlci: DLCI that opened + * + * The T1 timer handles retransmits of control frames (essentially of + * SABM and DISC). We resend the command until the retry count runs out + * in which case an opening port goes back to closed and a closing port + * is simply put into closed state (any further frames from the other + * end will get a DM response) + */ + +static void gsm_dlci_t1(unsigned long data) +{ + struct gsm_dlci *dlci = (struct gsm_dlci *)data; + struct gsm_mux *gsm = dlci->gsm; + + switch (dlci->state) { + case DLCI_OPENING: + dlci->retries--; + if (dlci->retries) { + gsm_command(dlci->gsm, dlci->addr, SABM|PF); + mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); + } else + gsm_dlci_close(dlci); + break; + case DLCI_CLOSING: + dlci->retries--; + if (dlci->retries) { + gsm_command(dlci->gsm, dlci->addr, DISC|PF); + mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); + } else + gsm_dlci_close(dlci); + break; + } +} + +/** + * gsm_dlci_begin_open - start channel open procedure + * @dlci: DLCI to open + * + * Commence opening a DLCI from the Linux side. We issue SABM messages + * to the modem which should then reply with a UA, at which point we + * will move into open state. Opening is done asynchronously with retry + * running off timers and the responses. + */ + +static void gsm_dlci_begin_open(struct gsm_dlci *dlci) +{ + struct gsm_mux *gsm = dlci->gsm; + if (dlci->state == DLCI_OPEN || dlci->state == DLCI_OPENING) + return; + dlci->retries = gsm->n2; + dlci->state = DLCI_OPENING; + gsm_command(dlci->gsm, dlci->addr, SABM|PF); + mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); +} + +/** + * gsm_dlci_begin_close - start channel open procedure + * @dlci: DLCI to open + * + * Commence closing a DLCI from the Linux side. We issue DISC messages + * to the modem which should then reply with a UA, at which point we + * will move into closed state. Closing is done asynchronously with retry + * off timers. We may also receive a DM reply from the other end which + * indicates the channel was already closed. + */ + +static void gsm_dlci_begin_close(struct gsm_dlci *dlci) +{ + struct gsm_mux *gsm = dlci->gsm; + if (dlci->state == DLCI_CLOSED || dlci->state == DLCI_CLOSING) + return; + dlci->retries = gsm->n2; + dlci->state = DLCI_CLOSING; + gsm_command(dlci->gsm, dlci->addr, DISC|PF); + mod_timer(&dlci->t1, jiffies + gsm->t1 * HZ / 100); +} + +/** + * gsm_dlci_data - data arrived + * @dlci: channel + * @data: block of bytes received + * @len: length of received block + * + * A UI or UIH frame has arrived which contains data for a channel + * other than the control channel. If the relevant virtual tty is + * open we shovel the bits down it, if not we drop them. + */ + +static void gsm_dlci_data(struct gsm_dlci *dlci, u8 *data, int len) +{ + /* krefs .. */ + struct tty_port *port = &dlci->port; + struct tty_struct *tty = tty_port_tty_get(port); + unsigned int modem = 0; + + if (debug & 16) + printk("%d bytes for tty %p\n", len, tty); + if (tty) { + switch (dlci->adaption) { + /* Unsupported types */ + /* Packetised interruptible data */ + case 4: + break; + /* Packetised uininterruptible voice/data */ + case 3: + break; + /* Asynchronous serial with line state in each frame */ + case 2: + while (gsm_read_ea(&modem, *data++) == 0) { + len--; + if (len == 0) + return; + } + gsm_process_modem(tty, dlci, modem); + /* Line state will go via DLCI 0 controls only */ + case 1: + default: + tty_insert_flip_string(tty, data, len); + tty_flip_buffer_push(tty); + } + tty_kref_put(tty); + } +} + +/** + * gsm_dlci_control - data arrived on control channel + * @dlci: channel + * @data: block of bytes received + * @len: length of received block + * + * A UI or UIH frame has arrived which contains data for DLCI 0 the + * control channel. This should contain a command EA followed by + * control data bytes. The command EA contains a command/response bit + * and we divide up the work accordingly. + */ + +static void gsm_dlci_command(struct gsm_dlci *dlci, u8 *data, int len) +{ + /* See what command is involved */ + unsigned int command = 0; + while (len-- > 0) { + if (gsm_read_ea(&command, *data++) == 1) { + int clen = *data++; + len--; + /* FIXME: this is properly an EA */ + clen >>= 1; + /* Malformed command ? */ + if (clen > len) + return; + if (command & 1) + gsm_control_message(dlci->gsm, command, + data, clen); + else + gsm_control_response(dlci->gsm, command, + data, clen); + return; + } + } +} + +/* + * Allocate/Free DLCI channels + */ + +/** + * gsm_dlci_alloc - allocate a DLCI + * @gsm: GSM mux + * @addr: address of the DLCI + * + * Allocate and install a new DLCI object into the GSM mux. + * + * FIXME: review locking races + */ + +static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr) +{ + struct gsm_dlci *dlci = kzalloc(sizeof(struct gsm_dlci), GFP_ATOMIC); + if (dlci == NULL) + return NULL; + spin_lock_init(&dlci->lock); + dlci->fifo = &dlci->_fifo; + if (kfifo_alloc(&dlci->_fifo, 4096, GFP_KERNEL) < 0) { + kfree(dlci); + return NULL; + } + + skb_queue_head_init(&dlci->skb_list); + init_timer(&dlci->t1); + dlci->t1.function = gsm_dlci_t1; + dlci->t1.data = (unsigned long)dlci; + tty_port_init(&dlci->port); + dlci->port.ops = &gsm_port_ops; + dlci->gsm = gsm; + dlci->addr = addr; + dlci->adaption = gsm->adaption; + dlci->state = DLCI_CLOSED; + if (addr) + dlci->data = gsm_dlci_data; + else + dlci->data = gsm_dlci_command; + gsm->dlci[addr] = dlci; + return dlci; +} + +/** + * gsm_dlci_free - release DLCI + * @dlci: DLCI to destroy + * + * Free up a DLCI. Currently to keep the lifetime rules sane we only + * clean up DLCI objects when the MUX closes rather than as the port + * is closed down on both the tty and mux levels. + * + * Can sleep. + */ +static void gsm_dlci_free(struct gsm_dlci *dlci) +{ + struct tty_struct *tty = tty_port_tty_get(&dlci->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + del_timer_sync(&dlci->t1); + dlci->gsm->dlci[dlci->addr] = NULL; + kfifo_free(dlci->fifo); + kfree(dlci); +} + + +/* + * LAPBish link layer logic + */ + +/** + * gsm_queue - a GSM frame is ready to process + * @gsm: pointer to our gsm mux + * + * At this point in time a frame has arrived and been demangled from + * the line encoding. All the differences between the encodings have + * been handled below us and the frame is unpacked into the structures. + * The fcs holds the header FCS but any data FCS must be added here. + */ + +static void gsm_queue(struct gsm_mux *gsm) +{ + struct gsm_dlci *dlci; + u8 cr; + int address; + /* We have to sneak a look at the packet body to do the FCS. + A somewhat layering violation in the spec */ + + if ((gsm->control & ~PF) == UI) + gsm->fcs = gsm_fcs_add_block(gsm->fcs, gsm->buf, gsm->len); + if (gsm->fcs != GOOD_FCS) { + gsm->bad_fcs++; + if (debug & 4) + printk("BAD FCS %02x\n", gsm->fcs); + return; + } + address = gsm->address >> 1; + if (address >= NUM_DLCI) + goto invalid; + + cr = gsm->address & 1; /* C/R bit */ + + gsm_print_packet("<--", address, cr, gsm->control, gsm->buf, gsm->len); + + cr ^= 1 - gsm->initiator; /* Flip so 1 always means command */ + dlci = gsm->dlci[address]; + + switch (gsm->control) { + case SABM|PF: + if (cr == 0) + goto invalid; + if (dlci == NULL) + dlci = gsm_dlci_alloc(gsm, address); + if (dlci == NULL) + return; + if (dlci->dead) + gsm_response(gsm, address, DM); + else { + gsm_response(gsm, address, UA); + gsm_dlci_open(dlci); + } + break; + case DISC|PF: + if (cr == 0) + goto invalid; + if (dlci == NULL || dlci->state == DLCI_CLOSED) { + gsm_response(gsm, address, DM); + return; + } + /* Real close complete */ + gsm_response(gsm, address, UA); + gsm_dlci_close(dlci); + break; + case UA: + case UA|PF: + if (cr == 0 || dlci == NULL) + break; + switch (dlci->state) { + case DLCI_CLOSING: + gsm_dlci_close(dlci); + break; + case DLCI_OPENING: + gsm_dlci_open(dlci); + break; + } + break; + case DM: /* DM can be valid unsolicited */ + case DM|PF: + if (cr) + goto invalid; + if (dlci == NULL) + return; + gsm_dlci_close(dlci); + break; + case UI: + case UI|PF: + case UIH: + case UIH|PF: +#if 0 + if (cr) + goto invalid; +#endif + if (dlci == NULL || dlci->state != DLCI_OPEN) { + gsm_command(gsm, address, DM|PF); + return; + } + dlci->data(dlci, gsm->buf, gsm->len); + break; + default: + goto invalid; + } + return; +invalid: + gsm->malformed++; + return; +} + + +/** + * gsm0_receive - perform processing for non-transparency + * @gsm: gsm data for this ldisc instance + * @c: character + * + * Receive bytes in gsm mode 0 + */ + +static void gsm0_receive(struct gsm_mux *gsm, unsigned char c) +{ + switch (gsm->state) { + case GSM_SEARCH: /* SOF marker */ + if (c == GSM0_SOF) { + gsm->state = GSM_ADDRESS; + gsm->address = 0; + gsm->len = 0; + gsm->fcs = INIT_FCS; + } + break; /* Address EA */ + case GSM_ADDRESS: + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + if (gsm_read_ea(&gsm->address, c)) + gsm->state = GSM_CONTROL; + break; + case GSM_CONTROL: /* Control Byte */ + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + gsm->control = c; + gsm->state = GSM_LEN; + break; + case GSM_LEN: /* Length EA */ + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + if (gsm_read_ea(&gsm->len, c)) { + if (gsm->len > gsm->mru) { + gsm->bad_size++; + gsm->state = GSM_SEARCH; + break; + } + gsm->count = 0; + gsm->state = GSM_DATA; + } + break; + case GSM_DATA: /* Data */ + gsm->buf[gsm->count++] = c; + if (gsm->count == gsm->len) + gsm->state = GSM_FCS; + break; + case GSM_FCS: /* FCS follows the packet */ + gsm->fcs = c; + gsm_queue(gsm); + /* And then back for the next frame */ + gsm->state = GSM_SEARCH; + break; + } +} + +/** + * gsm0_receive - perform processing for non-transparency + * @gsm: gsm data for this ldisc instance + * @c: character + * + * Receive bytes in mode 1 (Advanced option) + */ + +static void gsm1_receive(struct gsm_mux *gsm, unsigned char c) +{ + if (c == GSM1_SOF) { + /* EOF is only valid in frame if we have got to the data state + and received at least one byte (the FCS) */ + if (gsm->state == GSM_DATA && gsm->count) { + /* Extract the FCS */ + gsm->count--; + gsm->fcs = gsm_fcs_add(gsm->fcs, gsm->buf[gsm->count]); + gsm->len = gsm->count; + gsm_queue(gsm); + gsm->state = GSM_START; + return; + } + /* Any partial frame was a runt so go back to start */ + if (gsm->state != GSM_START) { + gsm->malformed++; + gsm->state = GSM_START; + } + /* A SOF in GSM_START means we are still reading idling or + framing bytes */ + return; + } + + if (c == GSM1_ESCAPE) { + gsm->escape = 1; + return; + } + + /* Only an unescaped SOF gets us out of GSM search */ + if (gsm->state == GSM_SEARCH) + return; + + if (gsm->escape) { + c ^= GSM1_ESCAPE_BITS; + gsm->escape = 0; + } + switch (gsm->state) { + case GSM_START: /* First byte after SOF */ + gsm->address = 0; + gsm->state = GSM_ADDRESS; + gsm->fcs = INIT_FCS; + /* Drop through */ + case GSM_ADDRESS: /* Address continuation */ + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + if (gsm_read_ea(&gsm->address, c)) + gsm->state = GSM_CONTROL; + break; + case GSM_CONTROL: /* Control Byte */ + gsm->fcs = gsm_fcs_add(gsm->fcs, c); + gsm->control = c; + gsm->count = 0; + gsm->state = GSM_DATA; + break; + case GSM_DATA: /* Data */ + if (gsm->count > gsm->mru ) { /* Allow one for the FCS */ + gsm->state = GSM_OVERRUN; + gsm->bad_size++; + } else + gsm->buf[gsm->count++] = c; + break; + case GSM_OVERRUN: /* Over-long - eg a dropped SOF */ + break; + } +} + +/** + * gsm_error - handle tty error + * @gsm: ldisc data + * @data: byte received (may be invalid) + * @flag: error received + * + * Handle an error in the receipt of data for a frame. Currently we just + * go back to hunting for a SOF. + * + * FIXME: better diagnostics ? + */ + +static void gsm_error(struct gsm_mux *gsm, + unsigned char data, unsigned char flag) +{ + gsm->state = GSM_SEARCH; + gsm->io_error++; +} + +/** + * gsm_cleanup_mux - generic GSM protocol cleanup + * @gsm: our mux + * + * Clean up the bits of the mux which are the same for all framing + * protocols. Remove the mux from the mux table, stop all the timers + * and then shut down each device hanging up the channels as we go. + */ + +void gsm_cleanup_mux(struct gsm_mux *gsm) +{ + int i; + struct gsm_dlci *dlci = gsm->dlci[0]; + struct gsm_msg *txq; + + gsm->dead = 1; + + spin_lock(&gsm_mux_lock); + for (i = 0; i < MAX_MUX; i++) { + if (gsm_mux[i] == gsm) { + gsm_mux[i] = NULL; + break; + } + } + spin_unlock(&gsm_mux_lock); + WARN_ON(i == MAX_MUX); + + del_timer_sync(&gsm->t2_timer); + /* Now we are sure T2 has stopped */ + if (dlci) { + dlci->dead = 1; + gsm_dlci_begin_close(dlci); + wait_event_interruptible(gsm->event, + dlci->state == DLCI_CLOSED); + } + /* Free up any link layer users */ + for (i = 0; i < NUM_DLCI; i++) + if (gsm->dlci[i]) + gsm_dlci_free(gsm->dlci[i]); + /* Now wipe the queues */ + for (txq = gsm->tx_head; txq != NULL; txq = gsm->tx_head) { + gsm->tx_head = txq->next; + kfree(txq); + } + gsm->tx_tail = NULL; +} +EXPORT_SYMBOL_GPL(gsm_cleanup_mux); + +/** + * gsm_activate_mux - generic GSM setup + * @gsm: our mux + * + * Set up the bits of the mux which are the same for all framing + * protocols. Add the mux to the mux table so it can be opened and + * finally kick off connecting to DLCI 0 on the modem. + */ + +int gsm_activate_mux(struct gsm_mux *gsm) +{ + struct gsm_dlci *dlci; + int i = 0; + + init_timer(&gsm->t2_timer); + gsm->t2_timer.function = gsm_control_retransmit; + gsm->t2_timer.data = (unsigned long)gsm; + init_waitqueue_head(&gsm->event); + spin_lock_init(&gsm->control_lock); + spin_lock_init(&gsm->tx_lock); + + if (gsm->encoding == 0) + gsm->receive = gsm0_receive; + else + gsm->receive = gsm1_receive; + gsm->error = gsm_error; + + spin_lock(&gsm_mux_lock); + for (i = 0; i < MAX_MUX; i++) { + if (gsm_mux[i] == NULL) { + gsm_mux[i] = gsm; + break; + } + } + spin_unlock(&gsm_mux_lock); + if (i == MAX_MUX) + return -EBUSY; + + dlci = gsm_dlci_alloc(gsm, 0); + if (dlci == NULL) + return -ENOMEM; + gsm->dead = 0; /* Tty opens are now permissible */ + return 0; +} +EXPORT_SYMBOL_GPL(gsm_activate_mux); + +/** + * gsm_free_mux - free up a mux + * @mux: mux to free + * + * Dispose of allocated resources for a dead mux. No refcounting + * at present so the mux must be truely dead. + */ +void gsm_free_mux(struct gsm_mux *gsm) +{ + kfree(gsm->txframe); + kfree(gsm->buf); + kfree(gsm); +} +EXPORT_SYMBOL_GPL(gsm_free_mux); + +/** + * gsm_alloc_mux - allocate a mux + * + * Creates a new mux ready for activation. + */ + +struct gsm_mux *gsm_alloc_mux(void) +{ + struct gsm_mux *gsm = kzalloc(sizeof(struct gsm_mux), GFP_KERNEL); + if (gsm == NULL) + return NULL; + gsm->buf = kmalloc(MAX_MRU + 1, GFP_KERNEL); + if (gsm->buf == NULL) { + kfree(gsm); + return NULL; + } + gsm->txframe = kmalloc(2 * MAX_MRU + 2, GFP_KERNEL); + if (gsm->txframe == NULL) { + kfree(gsm->buf); + kfree(gsm); + return NULL; + } + spin_lock_init(&gsm->lock); + + gsm->t1 = T1; + gsm->t2 = T2; + gsm->n2 = N2; + gsm->ftype = UIH; + gsm->initiator = 0; + gsm->adaption = 1; + gsm->encoding = 1; + gsm->mru = 64; /* Default to encoding 1 so these should be 64 */ + gsm->mtu = 64; + gsm->dead = 1; /* Avoid early tty opens */ + + return gsm; +} +EXPORT_SYMBOL_GPL(gsm_alloc_mux); + + + + +/** + * gsmld_output - write to link + * @gsm: our mux + * @data: bytes to output + * @len: size + * + * Write a block of data from the GSM mux to the data channel. This + * will eventually be serialized from above but at the moment isn't. + */ + +static int gsmld_output(struct gsm_mux *gsm, u8 *data, int len) +{ + if (tty_write_room(gsm->tty) < len) { + set_bit(TTY_DO_WRITE_WAKEUP, &gsm->tty->flags); + return -ENOSPC; + } + if (debug & 4) { + printk("-->%d bytes out\n", len); + hex_packet(data, len); + } + gsm->tty->ops->write(gsm->tty, data, len); + return len; +} + +/** + * gsmld_attach_gsm - mode set up + * @tty: our tty structure + * @gsm: our mux + * + * Set up the MUX for basic mode and commence connecting to the + * modem. Currently called from the line discipline set up but + * will need moving to an ioctl path. + */ + +static int gsmld_attach_gsm(struct tty_struct *tty, struct gsm_mux *gsm) +{ + int ret; + + gsm->tty = tty_kref_get(tty); + gsm->output = gsmld_output; + ret = gsm_activate_mux(gsm); + if (ret != 0) + tty_kref_put(gsm->tty); + return ret; +} + + +/** + * gsmld_detach_gsm - stop doing 0710 mux + * @tty: tty atttached to the mux + * @gsm: mux + * + * Shutdown and then clean up the resources used by the line discipline + */ + +static void gsmld_detach_gsm(struct tty_struct *tty, struct gsm_mux *gsm) +{ + WARN_ON(tty != gsm->tty); + gsm_cleanup_mux(gsm); + tty_kref_put(gsm->tty); + gsm->tty = NULL; +} + +static void gsmld_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + struct gsm_mux *gsm = tty->disc_data; + const unsigned char *dp; + char *f; + int i; + char buf[64]; + char flags; + + if (debug & 4) { + printk("Inbytes %dd\n", count); + hex_packet(cp, count); + } + + for (i = count, dp = cp, f = fp; i; i--, dp++) { + flags = *f++; + switch (flags) { + case TTY_NORMAL: + gsm->receive(gsm, *dp); + break; + case TTY_OVERRUN: + case TTY_BREAK: + case TTY_PARITY: + case TTY_FRAME: + gsm->error(gsm, *dp, flags); + break; + default: + printk(KERN_ERR "%s: unknown flag %d\n", + tty_name(tty, buf), flags); + break; + } + } + /* FASYNC if needed ? */ + /* If clogged call tty_throttle(tty); */ +} + +/** + * gsmld_chars_in_buffer - report available bytes + * @tty: tty device + * + * Report the number of characters buffered to be delivered to user + * at this instant in time. + * + * Locking: gsm lock + */ + +static ssize_t gsmld_chars_in_buffer(struct tty_struct *tty) +{ + return 0; +} + +/** + * gsmld_flush_buffer - clean input queue + * @tty: terminal device + * + * Flush the input buffer. Called when the line discipline is + * being closed, when the tty layer wants the buffer flushed (eg + * at hangup). + */ + +static void gsmld_flush_buffer(struct tty_struct *tty) +{ +} + +/** + * gsmld_close - close the ldisc for this tty + * @tty: device + * + * Called from the terminal layer when this line discipline is + * being shut down, either because of a close or becsuse of a + * discipline change. The function will not be called while other + * ldisc methods are in progress. + */ + +static void gsmld_close(struct tty_struct *tty) +{ + struct gsm_mux *gsm = tty->disc_data; + + gsmld_detach_gsm(tty, gsm); + + gsmld_flush_buffer(tty); + /* Do other clean up here */ + gsm_free_mux(gsm); +} + +/** + * gsmld_open - open an ldisc + * @tty: terminal to open + * + * Called when this line discipline is being attached to the + * terminal device. Can sleep. Called serialized so that no + * other events will occur in parallel. No further open will occur + * until a close. + */ + +static int gsmld_open(struct tty_struct *tty) +{ + struct gsm_mux *gsm; + + if (tty->ops->write == NULL) + return -EINVAL; + + /* Attach our ldisc data */ + gsm = gsm_alloc_mux(); + if (gsm == NULL) + return -ENOMEM; + + tty->disc_data = gsm; + tty->receive_room = 65536; + + /* Attach the initial passive connection */ + gsm->encoding = 1; + return gsmld_attach_gsm(tty, gsm); +} + +/** + * gsmld_write_wakeup - asynchronous I/O notifier + * @tty: tty device + * + * Required for the ptys, serial driver etc. since processes + * that attach themselves to the master and rely on ASYNC + * IO must be woken up + */ + +static void gsmld_write_wakeup(struct tty_struct *tty) +{ + struct gsm_mux *gsm = tty->disc_data; + + /* Queue poll */ + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + gsm_data_kick(gsm); + if (gsm->tx_bytes < TX_THRESH_LO) + gsm_dlci_data_sweep(gsm); +} + +/** + * gsmld_read - read function for tty + * @tty: tty device + * @file: file object + * @buf: userspace buffer pointer + * @nr: size of I/O + * + * Perform reads for the line discipline. We are guaranteed that the + * line discipline will not be closed under us but we may get multiple + * parallel readers and must handle this ourselves. We may also get + * a hangup. Always called in user context, may sleep. + * + * This code must be sure never to sleep through a hangup. + */ + +static ssize_t gsmld_read(struct tty_struct *tty, struct file *file, + unsigned char __user *buf, size_t nr) +{ + return -EOPNOTSUPP; +} + +/** + * gsmld_write - write function for tty + * @tty: tty device + * @file: file object + * @buf: userspace buffer pointer + * @nr: size of I/O + * + * Called when the owner of the device wants to send a frame + * itself (or some other control data). The data is transferred + * as-is and must be properly framed and checksummed as appropriate + * by userspace. Frames are either sent whole or not at all as this + * avoids pain user side. + */ + +static ssize_t gsmld_write(struct tty_struct *tty, struct file *file, + const unsigned char *buf, size_t nr) +{ + int space = tty_write_room(tty); + if (space >= nr) + return tty->ops->write(tty, buf, nr); + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + return -ENOBUFS; +} + +/** + * gsmld_poll - poll method for N_GSM0710 + * @tty: terminal device + * @file: file accessing it + * @wait: poll table + * + * Called when the line discipline is asked to poll() for data or + * for special events. This code is not serialized with respect to + * other events save open/close. + * + * This code must be sure never to sleep through a hangup. + * Called without the kernel lock held - fine + */ + +static unsigned int gsmld_poll(struct tty_struct *tty, struct file *file, + poll_table *wait) +{ + unsigned int mask = 0; + struct gsm_mux *gsm = tty->disc_data; + + poll_wait(file, &tty->read_wait, wait); + poll_wait(file, &tty->write_wait, wait); + if (tty_hung_up_p(file)) + mask |= POLLHUP; + if (!tty_is_writelocked(tty) && tty_write_room(tty) > 0) + mask |= POLLOUT | POLLWRNORM; + if (gsm->dead) + mask |= POLLHUP; + return mask; +} + +static int gsmld_config(struct tty_struct *tty, struct gsm_mux *gsm, + struct gsm_config *c) +{ + int need_close = 0; + int need_restart = 0; + + /* Stuff we don't support yet - UI or I frame transport, windowing */ + if ((c->adaption !=1 && c->adaption != 2) || c->k) + return -EOPNOTSUPP; + /* Check the MRU/MTU range looks sane */ + if (c->mru > MAX_MRU || c->mtu > MAX_MTU || c->mru < 8 || c->mtu < 8) + return -EINVAL; + if (c->n2 < 3) + return -EINVAL; + if (c->encapsulation > 1) /* Basic, advanced, no I */ + return -EINVAL; + if (c->initiator > 1) + return -EINVAL; + if (c->i == 0 || c->i > 2) /* UIH and UI only */ + return -EINVAL; + /* + * See what is needed for reconfiguration + */ + + /* Timing fields */ + if (c->t1 != 0 && c->t1 != gsm->t1) + need_restart = 1; + if (c->t2 != 0 && c->t2 != gsm->t2) + need_restart = 1; + if (c->encapsulation != gsm->encoding) + need_restart = 1; + if (c->adaption != gsm->adaption) + need_restart = 1; + /* Requires care */ + if (c->initiator != gsm->initiator) + need_close = 1; + if (c->mru != gsm->mru) + need_restart = 1; + if (c->mtu != gsm->mtu) + need_restart = 1; + + /* + * Close down what is needed, restart and initiate the new + * configuration + */ + + if (need_close || need_restart) { + gsm_dlci_begin_close(gsm->dlci[0]); + /* This will timeout if the link is down due to N2 expiring */ + wait_event_interruptible(gsm->event, + gsm->dlci[0]->state == DLCI_CLOSED); + if (signal_pending(current)) + return -EINTR; + } + if (need_restart) + gsm_cleanup_mux(gsm); + + gsm->initiator = c->initiator; + gsm->mru = c->mru; + gsm->encoding = c->encapsulation; + gsm->adaption = c->adaption; + + if (c->i == 1) + gsm->ftype = UIH; + else if (c->i == 2) + gsm->ftype = UI; + + if (c->t1) + gsm->t1 = c->t1; + if (c->t2) + gsm->t2 = c->t2; + + /* FIXME: We need to separate activation/deactivation from adding + and removing from the mux array */ + if (need_restart) + gsm_activate_mux(gsm); + if (gsm->initiator && need_close) + gsm_dlci_begin_open(gsm->dlci[0]); + return 0; +} + +static int gsmld_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct gsm_config c; + struct gsm_mux *gsm = tty->disc_data; + + switch (cmd) { + case GSMIOC_GETCONF: + memset(&c, 0, sizeof(c)); + c.adaption = gsm->adaption; + c.encapsulation = gsm->encoding; + c.initiator = gsm->initiator; + c.t1 = gsm->t1; + c.t2 = gsm->t2; + c.t3 = 0; /* Not supported */ + c.n2 = gsm->n2; + if (gsm->ftype == UIH) + c.i = 1; + else + c.i = 2; + printk("Ftype %d i %d\n", gsm->ftype, c.i); + c.mru = gsm->mru; + c.mtu = gsm->mtu; + c.k = 0; + if (copy_to_user((void *)arg, &c, sizeof(c))) + return -EFAULT; + return 0; + case GSMIOC_SETCONF: + if (copy_from_user(&c, (void *)arg, sizeof(c))) + return -EFAULT; + return gsmld_config(tty, gsm, &c); + default: + return n_tty_ioctl_helper(tty, file, cmd, arg); + } +} + + +/* Line discipline for real tty */ +struct tty_ldisc_ops tty_ldisc_packet = { + .owner = THIS_MODULE, + .magic = TTY_LDISC_MAGIC, + .name = "n_gsm", + .open = gsmld_open, + .close = gsmld_close, + .flush_buffer = gsmld_flush_buffer, + .chars_in_buffer = gsmld_chars_in_buffer, + .read = gsmld_read, + .write = gsmld_write, + .ioctl = gsmld_ioctl, + .poll = gsmld_poll, + .receive_buf = gsmld_receive_buf, + .write_wakeup = gsmld_write_wakeup +}; + +/* + * Virtual tty side + */ + +#define TX_SIZE 512 + +static int gsmtty_modem_update(struct gsm_dlci *dlci, u8 brk) +{ + u8 modembits[5]; + struct gsm_control *ctrl; + int len = 2; + + if (brk) + len++; + + modembits[0] = len << 1 | EA; /* Data bytes */ + modembits[1] = dlci->addr << 2 | 3; /* DLCI, EA, 1 */ + modembits[2] = gsm_encode_modem(dlci) << 1 | EA; + if (brk) + modembits[3] = brk << 4 | 2 | EA; /* Valid, EA */ + ctrl = gsm_control_send(dlci->gsm, CMD_MSC, modembits, len + 1); + if (ctrl == NULL) + return -ENOMEM; + return gsm_control_wait(dlci->gsm, ctrl); +} + +static int gsm_carrier_raised(struct tty_port *port) +{ + struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port); + /* Not yet open so no carrier info */ + if (dlci->state != DLCI_OPEN) + return 0; + if (debug & 2) + return 1; + return dlci->modem_rx & TIOCM_CD; +} + +static void gsm_dtr_rts(struct tty_port *port, int onoff) +{ + struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port); + unsigned int modem_tx = dlci->modem_tx; + if (onoff) + modem_tx |= TIOCM_DTR | TIOCM_RTS; + else + modem_tx &= ~(TIOCM_DTR | TIOCM_RTS); + if (modem_tx != dlci->modem_tx) { + dlci->modem_tx = modem_tx; + gsmtty_modem_update(dlci, 0); + } +} + +static const struct tty_port_operations gsm_port_ops = { + .carrier_raised = gsm_carrier_raised, + .dtr_rts = gsm_dtr_rts, +}; + + +static int gsmtty_open(struct tty_struct *tty, struct file *filp) +{ + struct gsm_mux *gsm; + struct gsm_dlci *dlci; + struct tty_port *port; + unsigned int line = tty->index; + unsigned int mux = line >> 6; + + line = line & 0x3F; + + if (mux >= MAX_MUX) + return -ENXIO; + /* FIXME: we need to lock gsm_mux for lifetimes of ttys eventually */ + if (gsm_mux[mux] == NULL) + return -EUNATCH; + if (line == 0 || line > 61) /* 62/63 reserved */ + return -ECHRNG; + gsm = gsm_mux[mux]; + if (gsm->dead) + return -EL2HLT; + dlci = gsm->dlci[line]; + if (dlci == NULL) + dlci = gsm_dlci_alloc(gsm, line); + if (dlci == NULL) + return -ENOMEM; + port = &dlci->port; + port->count++; + tty->driver_data = dlci; + tty_port_tty_set(port, tty); + + dlci->modem_rx = 0; + /* We could in theory open and close before we wait - eg if we get + a DM straight back. This is ok as that will have caused a hangup */ + set_bit(ASYNCB_INITIALIZED, &port->flags); + /* Start sending off SABM messages */ + gsm_dlci_begin_open(dlci); + /* And wait for virtual carrier */ + return tty_port_block_til_ready(port, tty, filp); +} + +static void gsmtty_close(struct tty_struct *tty, struct file *filp) +{ + struct gsm_dlci *dlci = tty->driver_data; + if (dlci == NULL) + return; + if (tty_port_close_start(&dlci->port, tty, filp) == 0) + return; + gsm_dlci_begin_close(dlci); + tty_port_close_end(&dlci->port, tty); + tty_port_tty_set(&dlci->port, NULL); +} + +static void gsmtty_hangup(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + tty_port_hangup(&dlci->port); + gsm_dlci_begin_close(dlci); +} + +static int gsmtty_write(struct tty_struct *tty, const unsigned char *buf, + int len) +{ + struct gsm_dlci *dlci = tty->driver_data; + /* Stuff the bytes into the fifo queue */ + int sent = kfifo_in_locked(dlci->fifo, buf, len, &dlci->lock); + /* Need to kick the channel */ + gsm_dlci_data_kick(dlci); + return sent; +} + +static int gsmtty_write_room(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + return TX_SIZE - kfifo_len(dlci->fifo); +} + +static int gsmtty_chars_in_buffer(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + return kfifo_len(dlci->fifo); +} + +static void gsmtty_flush_buffer(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + /* Caution needed: If we implement reliable transport classes + then the data being transmitted can't simply be junked once + it has first hit the stack. Until then we can just blow it + away */ + kfifo_reset(dlci->fifo); + /* Need to unhook this DLCI from the transmit queue logic */ +} + +static void gsmtty_wait_until_sent(struct tty_struct *tty, int timeout) +{ + /* The FIFO handles the queue so the kernel will do the right + thing waiting on chars_in_buffer before calling us. No work + to do here */ +} + +static int gsmtty_tiocmget(struct tty_struct *tty, struct file *filp) +{ + struct gsm_dlci *dlci = tty->driver_data; + return dlci->modem_rx; +} + +static int gsmtty_tiocmset(struct tty_struct *tty, struct file *filp, + unsigned int set, unsigned int clear) +{ + struct gsm_dlci *dlci = tty->driver_data; + unsigned int modem_tx = dlci->modem_tx; + + modem_tx &= clear; + modem_tx |= set; + + if (modem_tx != dlci->modem_tx) { + dlci->modem_tx = modem_tx; + return gsmtty_modem_update(dlci, 0); + } + return 0; +} + + +static int gsmtty_ioctl(struct tty_struct *tty, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +static void gsmtty_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + /* For the moment its fixed. In actual fact the speed information + for the virtual channel can be propogated in both directions by + the RPN control message. This however rapidly gets nasty as we + then have to remap modem signals each way according to whether + our virtual cable is null modem etc .. */ + tty_termios_copy_hw(tty->termios, old); +} + +static void gsmtty_throttle(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + if (tty->termios->c_cflag & CRTSCTS) + dlci->modem_tx &= ~TIOCM_DTR; + dlci->throttled = 1; + /* Send an MSC with DTR cleared */ + gsmtty_modem_update(dlci, 0); +} + +static void gsmtty_unthrottle(struct tty_struct *tty) +{ + struct gsm_dlci *dlci = tty->driver_data; + if (tty->termios->c_cflag & CRTSCTS) + dlci->modem_tx |= TIOCM_DTR; + dlci->throttled = 0; + /* Send an MSC with DTR set */ + gsmtty_modem_update(dlci, 0); +} + +static int gsmtty_break_ctl(struct tty_struct *tty, int state) +{ + struct gsm_dlci *dlci = tty->driver_data; + int encode = 0; /* Off */ + + if (state == -1) /* "On indefinitely" - we can't encode this + properly */ + encode = 0x0F; + else if (state > 0) { + encode = state / 200; /* mS to encoding */ + if (encode > 0x0F) + encode = 0x0F; /* Best effort */ + } + return gsmtty_modem_update(dlci, encode); +} + +static struct tty_driver *gsm_tty_driver; + +/* Virtual ttys for the demux */ +static const struct tty_operations gsmtty_ops = { + .open = gsmtty_open, + .close = gsmtty_close, + .write = gsmtty_write, + .write_room = gsmtty_write_room, + .chars_in_buffer = gsmtty_chars_in_buffer, + .flush_buffer = gsmtty_flush_buffer, + .ioctl = gsmtty_ioctl, + .throttle = gsmtty_throttle, + .unthrottle = gsmtty_unthrottle, + .set_termios = gsmtty_set_termios, + .hangup = gsmtty_hangup, + .wait_until_sent = gsmtty_wait_until_sent, + .tiocmget = gsmtty_tiocmget, + .tiocmset = gsmtty_tiocmset, + .break_ctl = gsmtty_break_ctl, +}; + + + +static int __init gsm_init(void) +{ + /* Fill in our line protocol discipline, and register it */ + int status = tty_register_ldisc(N_GSM0710, &tty_ldisc_packet); + if (status != 0) { + printk(KERN_ERR "n_gsm: can't register line discipline (err = %d)\n", status); + return status; + } + + gsm_tty_driver = alloc_tty_driver(256); + if (!gsm_tty_driver) { + tty_unregister_ldisc(N_GSM0710); + printk(KERN_ERR "gsm_init: tty allocation failed.\n"); + return -EINVAL; + } + gsm_tty_driver->owner = THIS_MODULE; + gsm_tty_driver->driver_name = "gsmtty"; + gsm_tty_driver->name = "gsmtty"; + gsm_tty_driver->major = 0; /* Dynamic */ + gsm_tty_driver->minor_start = 0; + gsm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + gsm_tty_driver->subtype = SERIAL_TYPE_NORMAL; + gsm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV + | TTY_DRIVER_HARDWARE_BREAK; + gsm_tty_driver->init_termios = tty_std_termios; + /* Fixme */ + gsm_tty_driver->init_termios.c_lflag &= ~ECHO; + tty_set_operations(gsm_tty_driver, &gsmtty_ops); + + spin_lock_init(&gsm_mux_lock); + + if (tty_register_driver(gsm_tty_driver)) { + put_tty_driver(gsm_tty_driver); + tty_unregister_ldisc(N_GSM0710); + printk(KERN_ERR "gsm_init: tty registration failed.\n"); + return -EBUSY; + } + printk(KERN_INFO "gsm_init: loaded as %d,%d.\n", gsm_tty_driver->major, gsm_tty_driver->minor_start); + return 0; +} + +static void __exit gsm_exit(void) +{ + int status = tty_unregister_ldisc(N_GSM0710); + if (status != 0) + printk(KERN_ERR "n_gsm: can't unregister line discipline (err = %d)\n", status); + tty_unregister_driver(gsm_tty_driver); + put_tty_driver(gsm_tty_driver); + printk(KERN_INFO "gsm_init: unloaded.\n"); +} + +module_init(gsm_init); +module_exit(gsm_exit); + + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_GSM0710); diff --git a/include/linux/gsmmux.h b/include/linux/gsmmux.h new file mode 100644 index 000000000000..378de4195caf --- /dev/null +++ b/include/linux/gsmmux.h @@ -0,0 +1,25 @@ +#ifndef _LINUX_GSMMUX_H +#define _LINUX_GSMMUX_H + +struct gsm_config +{ + unsigned int adaption; + unsigned int encapsulation; + unsigned int initiator; + unsigned int t1; + unsigned int t2; + unsigned int t3; + unsigned int n2; + unsigned int mru; + unsigned int mtu; + unsigned int k; + unsigned int i; + unsigned int unused[8]; /* Padding for expansion without + breaking stuff */ +}; + +#define GSMIOC_GETCONF _IOR('G', 0, struct gsm_config) +#define GSMIOC_SETCONF _IOW('G', 1, struct gsm_config) + + +#endif diff --git a/include/linux/tty.h b/include/linux/tty.h index 7e605e4b3da3..931078b73226 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -48,6 +48,7 @@ #define N_PPS 18 /* Pulse per Second */ #define N_V253 19 /* Codec control over voice modem */ #define N_CAIF 20 /* CAIF protocol for talking to modems */ +#define N_GSM0710 21 /* GSM 0710 Mux */ /* * This character is the same as _POSIX_VDISABLE: it cannot be used as From 972c196266d5982766ca03590d54e6a0d4de5664 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 3 May 2010 09:08:38 -0700 Subject: [PATCH 16/25] tty: n_gsm: depends on NET n_gsm uses skb functions, so it should depend on NET. n_gsm.c:(.text+0x123d49): undefined reference to `skb_dequeue' n_gsm.c:(.text+0x123d98): undefined reference to `kfree_skb' n_gsm.c:(.text+0x123e1e): undefined reference to `skb_pull' Signed-off-by: Randy Dunlap Signed-off-by: Greg Kroah-Hartman --- drivers/char/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 51a57009aec8..e21175be25d0 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -284,6 +284,7 @@ config N_HDLC config N_GSM tristate "GSM MUX line discipline support (EXPERIMENTAL)" depends on EXPERIMENTAL + depends on NET help This line discipline provides support for the GSM MUX protocol and presents the mux as a set of 61 individual tty devices. From e5bbbb18d872cb4e29815a000485c2c5eebcfaca Mon Sep 17 00:00:00 2001 From: John Linn Date: Wed, 7 Apr 2010 09:32:55 -0600 Subject: [PATCH 17/25] serial: uartlite: move from byte accesses to word accesses Byte accesses for I/O devices in Xilinx IP is going to be less desired in the future such that the driver is being changed to use 32 bit accesses. This change facilitates using the uartlite IP over a PCIe bus which only allows 32 bit accesses. Signed-off-by: John Linn Tested-by: Michal Simek Acked-by: Grant Likely Acked-by: Peter Korsgaard Signed-off-by: Greg Kroah-Hartman --- drivers/serial/uartlite.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/serial/uartlite.c b/drivers/serial/uartlite.c index f0a6c61b17f7..e6639a95d276 100644 --- a/drivers/serial/uartlite.c +++ b/drivers/serial/uartlite.c @@ -86,7 +86,7 @@ static int ulite_receive(struct uart_port *port, int stat) /* stats */ if (stat & ULITE_STATUS_RXVALID) { port->icount.rx++; - ch = readb(port->membase + ULITE_RX); + ch = ioread32be(port->membase + ULITE_RX); if (stat & ULITE_STATUS_PARITY) port->icount.parity++; @@ -131,7 +131,7 @@ static int ulite_transmit(struct uart_port *port, int stat) return 0; if (port->x_char) { - writeb(port->x_char, port->membase + ULITE_TX); + iowrite32be(port->x_char, port->membase + ULITE_TX); port->x_char = 0; port->icount.tx++; return 1; @@ -140,7 +140,7 @@ static int ulite_transmit(struct uart_port *port, int stat) if (uart_circ_empty(xmit) || uart_tx_stopped(port)) return 0; - writeb(xmit->buf[xmit->tail], port->membase + ULITE_TX); + iowrite32be(xmit->buf[xmit->tail], port->membase + ULITE_TX); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE-1); port->icount.tx++; @@ -157,7 +157,7 @@ static irqreturn_t ulite_isr(int irq, void *dev_id) int busy, n = 0; do { - int stat = readb(port->membase + ULITE_STATUS); + int stat = ioread32be(port->membase + ULITE_STATUS); busy = ulite_receive(port, stat); busy |= ulite_transmit(port, stat); n++; @@ -178,7 +178,7 @@ static unsigned int ulite_tx_empty(struct uart_port *port) unsigned int ret; spin_lock_irqsave(&port->lock, flags); - ret = readb(port->membase + ULITE_STATUS); + ret = ioread32be(port->membase + ULITE_STATUS); spin_unlock_irqrestore(&port->lock, flags); return ret & ULITE_STATUS_TXEMPTY ? TIOCSER_TEMT : 0; @@ -201,7 +201,7 @@ static void ulite_stop_tx(struct uart_port *port) static void ulite_start_tx(struct uart_port *port) { - ulite_transmit(port, readb(port->membase + ULITE_STATUS)); + ulite_transmit(port, ioread32be(port->membase + ULITE_STATUS)); } static void ulite_stop_rx(struct uart_port *port) @@ -230,17 +230,17 @@ static int ulite_startup(struct uart_port *port) if (ret) return ret; - writeb(ULITE_CONTROL_RST_RX | ULITE_CONTROL_RST_TX, + iowrite32be(ULITE_CONTROL_RST_RX | ULITE_CONTROL_RST_TX, port->membase + ULITE_CONTROL); - writeb(ULITE_CONTROL_IE, port->membase + ULITE_CONTROL); + iowrite32be(ULITE_CONTROL_IE, port->membase + ULITE_CONTROL); return 0; } static void ulite_shutdown(struct uart_port *port) { - writeb(0, port->membase + ULITE_CONTROL); - readb(port->membase + ULITE_CONTROL); /* dummy */ + iowrite32be(0, port->membase + ULITE_CONTROL); + ioread32be(port->membase + ULITE_CONTROL); /* dummy */ free_irq(port->irq, port); } @@ -352,7 +352,7 @@ static void ulite_console_wait_tx(struct uart_port *port) /* Spin waiting for TX fifo to have space available */ for (i = 0; i < 100000; i++) { - val = readb(port->membase + ULITE_STATUS); + val = ioread32be(port->membase + ULITE_STATUS); if ((val & ULITE_STATUS_TXFULL) == 0) break; cpu_relax(); @@ -362,7 +362,7 @@ static void ulite_console_wait_tx(struct uart_port *port) static void ulite_console_putchar(struct uart_port *port, int ch) { ulite_console_wait_tx(port); - writeb(ch, port->membase + ULITE_TX); + iowrite32be(ch, port->membase + ULITE_TX); } static void ulite_console_write(struct console *co, const char *s, @@ -379,8 +379,8 @@ static void ulite_console_write(struct console *co, const char *s, spin_lock_irqsave(&port->lock, flags); /* save and disable interrupt */ - ier = readb(port->membase + ULITE_STATUS) & ULITE_STATUS_IE; - writeb(0, port->membase + ULITE_CONTROL); + ier = ioread32be(port->membase + ULITE_STATUS) & ULITE_STATUS_IE; + iowrite32be(0, port->membase + ULITE_CONTROL); uart_console_write(port, s, count, ulite_console_putchar); @@ -388,7 +388,7 @@ static void ulite_console_write(struct console *co, const char *s, /* restore interrupt state */ if (ier) - writeb(ULITE_CONTROL_IE, port->membase + ULITE_CONTROL); + iowrite32be(ULITE_CONTROL_IE, port->membase + ULITE_CONTROL); if (locked) spin_unlock_irqrestore(&port->lock, flags); @@ -601,7 +601,7 @@ ulite_of_probe(struct of_device *op, const struct of_device_id *match) id = of_get_property(op->node, "port-number", NULL); - return ulite_assign(&op->dev, id ? *id : -1, res.start+3, irq); + return ulite_assign(&op->dev, id ? *id : -1, res.start, irq); } static int __devexit ulite_of_remove(struct of_device *op) From 44051996230510ccb125cfa552d464950d1767b9 Mon Sep 17 00:00:00 2001 From: Roel Kluin Date: Tue, 27 Apr 2010 14:16:33 -0700 Subject: [PATCH 18/25] serial: two branches the same in timbuart_set_mctrl() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CTS is a read only bit and we are to stop signal RTS if modem line TIOCM_RTS is not set. Thanks for suggestions by Richard Röjfors. Signed-off-by: Roel Kluin Acked-by: Richard Röjfors Cc: Alan Cox Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/serial/timbuart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/serial/timbuart.c b/drivers/serial/timbuart.c index 786ba85c170b..62f389fdc2a3 100644 --- a/drivers/serial/timbuart.c +++ b/drivers/serial/timbuart.c @@ -220,7 +220,7 @@ static void timbuart_set_mctrl(struct uart_port *port, unsigned int mctrl) if (mctrl & TIOCM_RTS) iowrite8(TIMBUART_CTRL_RTS, port->membase + TIMBUART_CTRL); else - iowrite8(TIMBUART_CTRL_RTS, port->membase + TIMBUART_CTRL); + iowrite8(0, port->membase + TIMBUART_CTRL); } static void timbuart_mctrl_check(struct uart_port *port, u32 isr, u32 *ier) From 24cd73a3942f3df214d7953f0cfd65d5085fa583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20R=C3=B6jfors?= Date: Tue, 27 Apr 2010 14:16:34 -0700 Subject: [PATCH 19/25] serial: timbuart: make sure last byte is sent when port is closed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a problem in early versions of the FPGA IP. In certain situations the IP reports that the FIFO is empty, but a byte is still clocked out. If a flush is done at that point the currently clocked byte is canceled. This causes incompatibilities with the upper layers when a port is closed, it waits until the FIFO is empty and then closes the port. During close the FIFO is flushed -> the last byte is not sent properly. Now the FIFO is only flushed if it is reported to be non-empty. Which makes the currently clocked out byte to finish. [akpm@linux-foundation.org: fix build] Signed-off-by: Richard Röjfors Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/serial/timbuart.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/drivers/serial/timbuart.c b/drivers/serial/timbuart.c index 62f389fdc2a3..67ca642713b8 100644 --- a/drivers/serial/timbuart.c +++ b/drivers/serial/timbuart.c @@ -68,12 +68,22 @@ static void timbuart_start_tx(struct uart_port *port) tasklet_schedule(&uart->tasklet); } +static unsigned int timbuart_tx_empty(struct uart_port *port) +{ + u32 isr = ioread32(port->membase + TIMBUART_ISR); + + return (isr & TXBE) ? TIOCSER_TEMT : 0; +} + static void timbuart_flush_buffer(struct uart_port *port) { - u8 ctl = ioread8(port->membase + TIMBUART_CTRL) | TIMBUART_CTRL_FLSHTX; + if (!timbuart_tx_empty(port)) { + u8 ctl = ioread8(port->membase + TIMBUART_CTRL) | + TIMBUART_CTRL_FLSHTX; - iowrite8(ctl, port->membase + TIMBUART_CTRL); - iowrite32(TXBF, port->membase + TIMBUART_ISR); + iowrite8(ctl, port->membase + TIMBUART_CTRL); + iowrite32(TXBF, port->membase + TIMBUART_ISR); + } } static void timbuart_rx_chars(struct uart_port *port) @@ -195,13 +205,6 @@ void timbuart_tasklet(unsigned long arg) dev_dbg(uart->port.dev, "%s leaving\n", __func__); } -static unsigned int timbuart_tx_empty(struct uart_port *port) -{ - u32 isr = ioread32(port->membase + TIMBUART_ISR); - - return (isr & TXBE) ? TIOCSER_TEMT : 0; -} - static unsigned int timbuart_get_mctrl(struct uart_port *port) { u8 cts = ioread8(port->membase + TIMBUART_CTRL); From 5bcd601049c6b2ad52733d4cd2794bfbaf1b9314 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Wed, 5 May 2010 10:35:22 +0200 Subject: [PATCH 20/25] serial: Add driver for the Altera JTAG UART Add an UART driver for the JTAG UART component available as a SOPC (System on Programmable Chip) component for Altera FPGAs. Signed-off-by: Tobias Klauser Signed-off-by: Greg Kroah-Hartman --- drivers/serial/Kconfig | 21 ++ drivers/serial/Makefile | 1 + drivers/serial/altera_jtaguart.c | 504 +++++++++++++++++++++++++++++++ include/linux/altera_jtaguart.h | 16 + include/linux/serial_core.h | 3 + 5 files changed, 545 insertions(+) create mode 100644 drivers/serial/altera_jtaguart.c create mode 100644 include/linux/altera_jtaguart.h diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 577d2762a4f4..f4050e678460 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -1523,4 +1523,25 @@ config SERIAL_GRLIB_GAISLER_APBUART_CONSOLE help Support for running a console on the GRLIB APBUART +config SERIAL_ALTERA_JTAGUART + tristate "Altera JTAG UART support" + select SERIAL_CORE + help + This driver supports the Altera JTAG UART port. + +config SERIAL_ALTERA_JTAGUART_CONSOLE + bool "Altera JTAG UART console support" + depends on SERIAL_ALTERA_JTAGUART=y + select SERIAL_CORE_CONSOLE + help + Enable a Altera JTAG UART port to be the system console. + +config SERIAL_ALTERA_JTAGUART_CONSOLE_BYPASS + bool "Bypass output when no connection" + depends on SERIAL_ALTERA_JTAGUART_CONSOLE + select SERIAL_CORE_CONSOLE + help + Bypass console output and keep going even if there is no + JTAG terminal connection with the host. + endmenu diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 328f107346c4..7f9d225d0430 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -82,3 +82,4 @@ obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o obj-$(CONFIG_SERIAL_QE) += ucc_uart.o obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o obj-$(CONFIG_SERIAL_GRLIB_GAISLER_APBUART) += apbuart.o +obj-$(CONFIG_SERIAL_ALTERA_JTAGUART) += altera_jtaguart.o diff --git a/drivers/serial/altera_jtaguart.c b/drivers/serial/altera_jtaguart.c new file mode 100644 index 000000000000..f9b49b5ff5e1 --- /dev/null +++ b/drivers/serial/altera_jtaguart.c @@ -0,0 +1,504 @@ +/* + * altera_jtaguart.c -- Altera JTAG UART driver + * + * Based on mcf.c -- Freescale ColdFire UART driver + * + * (C) Copyright 2003-2007, Greg Ungerer + * (C) Copyright 2008, Thomas Chou + * (C) Copyright 2010, Tobias Klauser + * + * 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 + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "altera_jtaguart" + +/* + * Altera JTAG UART register definitions according to the Altera JTAG UART + * datasheet: http://www.altera.com/literature/hb/nios2/n2cpu_nii51009.pdf + */ + +#define ALTERA_JTAGUART_SIZE 8 + +#define ALTERA_JTAGUART_DATA_REG 0 + +#define ALTERA_JTAGUART_DATA_DATA_MSK 0x000000FF +#define ALTERA_JTAGUART_DATA_RVALID_MSK 0x00008000 +#define ALTERA_JTAGUART_DATA_RAVAIL_MSK 0xFFFF0000 +#define ALTERA_JTAGUART_DATA_RAVAIL_OFF 16 + +#define ALTERA_JTAGUART_CONTROL_REG 4 + +#define ALTERA_JTAGUART_CONTROL_RE_MSK 0x00000001 +#define ALTERA_JTAGUART_CONTROL_WE_MSK 0x00000002 +#define ALTERA_JTAGUART_CONTROL_RI_MSK 0x00000100 +#define ALTERA_JTAGUART_CONTROL_RI_OFF 8 +#define ALTERA_JTAGUART_CONTROL_WI_MSK 0x00000200 +#define ALTERA_JTAGUART_CONTROL_AC_MSK 0x00000400 +#define ALTERA_JTAGUART_CONTROL_WSPACE_MSK 0xFFFF0000 +#define ALTERA_JTAGUART_CONTROL_WSPACE_OFF 16 + +/* + * Local per-uart structure. + */ +struct altera_jtaguart { + struct uart_port port; + unsigned int sigs; /* Local copy of line sigs */ + unsigned long imr; /* Local IMR mirror */ +}; + +static unsigned int altera_jtaguart_tx_empty(struct uart_port *port) +{ + return (readl(port->membase + ALTERA_JTAGUART_CONTROL_REG) & + ALTERA_JTAGUART_CONTROL_WSPACE_MSK) ? TIOCSER_TEMT : 0; +} + +static unsigned int altera_jtaguart_get_mctrl(struct uart_port *port) +{ + return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS; +} + +static void altera_jtaguart_set_mctrl(struct uart_port *port, unsigned int sigs) +{ +} + +static void altera_jtaguart_start_tx(struct uart_port *port) +{ + struct altera_jtaguart *pp = + container_of(port, struct altera_jtaguart, port); + + pp->imr |= ALTERA_JTAGUART_CONTROL_WE_MSK; + writel(pp->imr, port->membase + ALTERA_JTAGUART_CONTROL_REG); +} + +static void altera_jtaguart_stop_tx(struct uart_port *port) +{ + struct altera_jtaguart *pp = + container_of(port, struct altera_jtaguart, port); + + pp->imr &= ~ALTERA_JTAGUART_CONTROL_WE_MSK; + writel(pp->imr, port->membase + ALTERA_JTAGUART_CONTROL_REG); +} + +static void altera_jtaguart_stop_rx(struct uart_port *port) +{ + struct altera_jtaguart *pp = + container_of(port, struct altera_jtaguart, port); + + pp->imr &= ~ALTERA_JTAGUART_CONTROL_RE_MSK; + writel(pp->imr, port->membase + ALTERA_JTAGUART_CONTROL_REG); +} + +static void altera_jtaguart_break_ctl(struct uart_port *port, int break_state) +{ +} + +static void altera_jtaguart_enable_ms(struct uart_port *port) +{ +} + +static void altera_jtaguart_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + /* Just copy the old termios settings back */ + if (old) + tty_termios_copy_hw(termios, old); +} + +static void altera_jtaguart_rx_chars(struct altera_jtaguart *pp) +{ + struct uart_port *port = &pp->port; + unsigned char ch, flag; + unsigned long status; + + while ((status = readl(port->membase + ALTERA_JTAGUART_DATA_REG)) & + ALTERA_JTAGUART_DATA_RVALID_MSK) { + ch = status & ALTERA_JTAGUART_DATA_DATA_MSK; + flag = TTY_NORMAL; + port->icount.rx++; + + if (uart_handle_sysrq_char(port, ch)) + continue; + uart_insert_char(port, 0, 0, ch, flag); + } + + tty_flip_buffer_push(port->state->port.tty); +} + +static void altera_jtaguart_tx_chars(struct altera_jtaguart *pp) +{ + struct uart_port *port = &pp->port; + struct circ_buf *xmit = &port->state->xmit; + unsigned int pending, count; + + if (port->x_char) { + /* Send special char - probably flow control */ + writel(port->x_char, port->membase + ALTERA_JTAGUART_DATA_REG); + port->x_char = 0; + port->icount.tx++; + return; + } + + pending = uart_circ_chars_pending(xmit); + if (pending > 0) { + count = (readl(port->membase + ALTERA_JTAGUART_CONTROL_REG) & + ALTERA_JTAGUART_CONTROL_WSPACE_MSK) >> + ALTERA_JTAGUART_CONTROL_WSPACE_OFF; + if (count > pending) + count = pending; + if (count > 0) { + pending -= count; + while (count--) { + writel(xmit->buf[xmit->tail], + port->membase + ALTERA_JTAGUART_DATA_REG); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + } + if (pending < WAKEUP_CHARS) + uart_write_wakeup(port); + } + } + + if (pending == 0) { + pp->imr &= ~ALTERA_JTAGUART_CONTROL_WE_MSK; + writel(pp->imr, port->membase + ALTERA_JTAGUART_CONTROL_REG); + } +} + +static irqreturn_t altera_jtaguart_interrupt(int irq, void *data) +{ + struct uart_port *port = data; + struct altera_jtaguart *pp = + container_of(port, struct altera_jtaguart, port); + unsigned int isr; + + isr = (readl(port->membase + ALTERA_JTAGUART_CONTROL_REG) >> + ALTERA_JTAGUART_CONTROL_RI_OFF) & pp->imr; + + spin_lock(&port->lock); + + if (isr & ALTERA_JTAGUART_CONTROL_RE_MSK) + altera_jtaguart_rx_chars(pp); + if (isr & ALTERA_JTAGUART_CONTROL_WE_MSK) + altera_jtaguart_tx_chars(pp); + + spin_unlock(&port->lock); + + return IRQ_RETVAL(isr); +} + +static void altera_jtaguart_config_port(struct uart_port *port, int flags) +{ + port->type = PORT_ALTERA_JTAGUART; + + /* Clear mask, so no surprise interrupts. */ + writel(0, port->membase + ALTERA_JTAGUART_CONTROL_REG); +} + +static int altera_jtaguart_startup(struct uart_port *port) +{ + struct altera_jtaguart *pp = + container_of(port, struct altera_jtaguart, port); + unsigned long flags; + int ret; + + ret = request_irq(port->irq, altera_jtaguart_interrupt, IRQF_DISABLED, + DRV_NAME, port); + if (ret) { + pr_err(DRV_NAME ": unable to attach Altera JTAG UART %d " + "interrupt vector=%d\n", port->line, port->irq); + return ret; + } + + spin_lock_irqsave(&port->lock, flags); + + /* Enable RX interrupts now */ + pp->imr = ALTERA_JTAGUART_CONTROL_RE_MSK; + writel(pp->imr, port->membase + ALTERA_JTAGUART_CONTROL_REG); + + spin_unlock_irqrestore(&port->lock, flags); + + return 0; +} + +static void altera_jtaguart_shutdown(struct uart_port *port) +{ + struct altera_jtaguart *pp = + container_of(port, struct altera_jtaguart, port); + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + + /* Disable all interrupts now */ + pp->imr = 0; + writel(pp->imr, port->membase + ALTERA_JTAGUART_CONTROL_REG); + + spin_unlock_irqrestore(&port->lock, flags); + + free_irq(port->irq, port); +} + +static const char *altera_jtaguart_type(struct uart_port *port) +{ + return (port->type == PORT_ALTERA_JTAGUART) ? "Altera JTAG UART" : NULL; +} + +static int altera_jtaguart_request_port(struct uart_port *port) +{ + /* UARTs always present */ + return 0; +} + +static void altera_jtaguart_release_port(struct uart_port *port) +{ + /* Nothing to release... */ +} + +static int altera_jtaguart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + if (ser->type != PORT_UNKNOWN && ser->type != PORT_ALTERA_JTAGUART) + return -EINVAL; + return 0; +} + +/* + * Define the basic serial functions we support. + */ +static struct uart_ops altera_jtaguart_ops = { + .tx_empty = altera_jtaguart_tx_empty, + .get_mctrl = altera_jtaguart_get_mctrl, + .set_mctrl = altera_jtaguart_set_mctrl, + .start_tx = altera_jtaguart_start_tx, + .stop_tx = altera_jtaguart_stop_tx, + .stop_rx = altera_jtaguart_stop_rx, + .enable_ms = altera_jtaguart_enable_ms, + .break_ctl = altera_jtaguart_break_ctl, + .startup = altera_jtaguart_startup, + .shutdown = altera_jtaguart_shutdown, + .set_termios = altera_jtaguart_set_termios, + .type = altera_jtaguart_type, + .request_port = altera_jtaguart_request_port, + .release_port = altera_jtaguart_release_port, + .config_port = altera_jtaguart_config_port, + .verify_port = altera_jtaguart_verify_port, +}; + +#define ALTERA_JTAGUART_MAXPORTS 1 +static struct altera_jtaguart altera_jtaguart_ports[ALTERA_JTAGUART_MAXPORTS]; + +#if defined(CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE) + +int __init early_altera_jtaguart_setup(struct altera_jtaguart_platform_uart + *platp) +{ + struct uart_port *port; + int i; + + for (i = 0; i < ALTERA_JTAGUART_MAXPORTS && platp[i].mapbase; i++) { + port = &altera_jtaguart_ports[i].port; + + port->line = i; + port->type = PORT_ALTERA_JTAGUART; + port->mapbase = platp[i].mapbase; + port->membase = ioremap(port->mapbase, ALTERA_JTAGUART_SIZE); + port->iotype = SERIAL_IO_MEM; + port->irq = platp[i].irq; + port->flags = ASYNC_BOOT_AUTOCONF; + port->ops = &altera_jtaguart_ops; + } + + return 0; +} + +#if defined(CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE_BYPASS) +static void altera_jtaguart_console_putc(struct console *co, const char c) +{ + struct uart_port *port = &(altera_jtaguart_ports + co->index)->port; + unsigned long status; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + while (((status = readl(port->membase + ALTERA_JTAGUART_CONTROL_REG)) & + ALTERA_JTAGUART_CONTROL_WSPACE_MSK) == 0) { + if ((status & ALTERA_JTAGUART_CONTROL_AC_MSK) == 0) { + spin_unlock_irqrestore(&port->lock, flags); + return; /* no connection activity */ + } + spin_unlock_irqrestore(&port->lock, flags); + cpu_relax(); + spin_lock_irqsave(&port->lock, flags); + } + writel(c, port->membase + ALTERA_JTAGUART_DATA_REG); + spin_unlock_irqrestore(&port->lock, flags); +} +#else +static void altera_jtaguart_console_putc(struct console *co, const char c) +{ + struct uart_port *port = &(altera_jtaguart_ports + co->index)->port; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + while ((readl(port->membase + ALTERA_JTAGUART_CONTROL_REG) & + ALTERA_JTAGUART_CONTROL_WSPACE_MSK) == 0) { + spin_unlock_irqrestore(&port->lock, flags); + cpu_relax(); + spin_lock_irqsave(&port->lock, flags); + } + writel(c, port->membase + ALTERA_JTAGUART_DATA_REG); + spin_unlock_irqrestore(&port->lock, flags); +} +#endif + +static void altera_jtaguart_console_write(struct console *co, const char *s, + unsigned int count) +{ + for (; count; count--, s++) { + altera_jtaguart_console_putc(co, *s); + if (*s == '\n') + altera_jtaguart_console_putc(co, '\r'); + } +} + +static int __init altera_jtaguart_console_setup(struct console *co, + char *options) +{ + struct uart_port *port; + + if (co->index < 0 || co->index >= ALTERA_JTAGUART_MAXPORTS) + return -EINVAL; + port = &altera_jtaguart_ports[co->index].port; + if (port->membase == 0) + return -ENODEV; + return 0; +} + +static struct uart_driver altera_jtaguart_driver; + +static struct console altera_jtaguart_console = { + .name = "ttyJ", + .write = altera_jtaguart_console_write, + .device = uart_console_device, + .setup = altera_jtaguart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &altera_jtaguart_driver, +}; + +static int __init altera_jtaguart_console_init(void) +{ + register_console(&altera_jtaguart_console); + return 0; +} + +console_initcall(altera_jtaguart_console_init); + +#define ALTERA_JTAGUART_CONSOLE (&altera_jtaguart_console) + +#else + +#define ALTERA_JTAGUART_CONSOLE NULL + +#endif /* CONFIG_ALTERA_JTAGUART_CONSOLE */ + +static struct uart_driver altera_jtaguart_driver = { + .owner = THIS_MODULE, + .driver_name = "altera_jtaguart", + .dev_name = "ttyJ", + .major = ALTERA_JTAGUART_MAJOR, + .minor = ALTERA_JTAGUART_MINOR, + .nr = ALTERA_JTAGUART_MAXPORTS, + .cons = ALTERA_JTAGUART_CONSOLE, +}; + +static int __devinit altera_jtaguart_probe(struct platform_device *pdev) +{ + struct altera_jtaguart_platform_uart *platp = pdev->dev.platform_data; + struct uart_port *port; + int i; + + for (i = 0; i < ALTERA_JTAGUART_MAXPORTS && platp[i].mapbase; i++) { + port = &altera_jtaguart_ports[i].port; + + port->line = i; + port->type = PORT_ALTERA_JTAGUART; + port->mapbase = platp[i].mapbase; + port->membase = ioremap(port->mapbase, ALTERA_JTAGUART_SIZE); + port->iotype = SERIAL_IO_MEM; + port->irq = platp[i].irq; + port->ops = &altera_jtaguart_ops; + port->flags = ASYNC_BOOT_AUTOCONF; + + uart_add_one_port(&altera_jtaguart_driver, port); + } + + return 0; +} + +static int __devexit altera_jtaguart_remove(struct platform_device *pdev) +{ + struct uart_port *port; + int i; + + for (i = 0; i < ALTERA_JTAGUART_MAXPORTS; i++) { + port = &altera_jtaguart_ports[i].port; + if (port) + uart_remove_one_port(&altera_jtaguart_driver, port); + } + + return 0; +} + +static struct platform_driver altera_jtaguart_platform_driver = { + .probe = altera_jtaguart_probe, + .remove = __devexit_p(altera_jtaguart_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init altera_jtaguart_init(void) +{ + int rc; + + rc = uart_register_driver(&altera_jtaguart_driver); + if (rc) + return rc; + rc = platform_driver_register(&altera_jtaguart_platform_driver); + if (rc) { + uart_unregister_driver(&altera_jtaguart_driver); + return rc; + } + return 0; +} + +static void __exit altera_jtaguart_exit(void) +{ + platform_driver_unregister(&altera_jtaguart_platform_driver); + uart_unregister_driver(&altera_jtaguart_driver); +} + +module_init(altera_jtaguart_init); +module_exit(altera_jtaguart_exit); + +MODULE_DESCRIPTION("Altera JTAG UART driver"); +MODULE_AUTHOR("Thomas Chou "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/include/linux/altera_jtaguart.h b/include/linux/altera_jtaguart.h new file mode 100644 index 000000000000..953b178a1650 --- /dev/null +++ b/include/linux/altera_jtaguart.h @@ -0,0 +1,16 @@ +/* + * altera_jtaguart.h -- Altera JTAG UART driver defines. + */ + +#ifndef __ALTJUART_H +#define __ALTJUART_H + +#define ALTERA_JTAGUART_MAJOR 204 +#define ALTERA_JTAGUART_MINOR 186 + +struct altera_jtaguart_platform_uart { + unsigned long mapbase; /* Physical address base */ + unsigned int irq; /* Interrupt vector */ +}; + +#endif /* __ALTJUART_H */ diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 78dd1e7120a9..ade1536e1a48 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -182,6 +182,9 @@ /* Aeroflex Gaisler GRLIB APBUART */ #define PORT_APBUART 90 +/* Altera UARTs */ +#define PORT_ALTERA_JTAGUART 91 + #ifdef __KERNEL__ #include From 6b7d8f8b5c43f7bedda750d8a9dab0658da1d2ba Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Wed, 5 May 2010 10:35:23 +0200 Subject: [PATCH 21/25] serial: Add driver for the Altera UART Add an UART driver for the UART component available as a SOPC (System on Programmable Chip) component for Altera FPGAs. Signed-off-by: Tobias Klauser Signed-off-by: Greg Kroah-Hartman --- drivers/serial/Kconfig | 31 ++ drivers/serial/Makefile | 1 + drivers/serial/altera_uart.c | 570 +++++++++++++++++++++++++++++++++++ include/linux/altera_uart.h | 14 + include/linux/serial_core.h | 1 + 5 files changed, 617 insertions(+) create mode 100644 drivers/serial/altera_uart.c create mode 100644 include/linux/altera_uart.h diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index f4050e678460..8b23165bc5dc 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -1544,4 +1544,35 @@ config SERIAL_ALTERA_JTAGUART_CONSOLE_BYPASS Bypass console output and keep going even if there is no JTAG terminal connection with the host. +config SERIAL_ALTERA_UART + tristate "Altera UART support" + select SERIAL_CORE + help + This driver supports the Altera softcore UART port. + +config SERIAL_ALTERA_UART_MAXPORTS + int "Maximum number of Altera UART ports" + depends on SERIAL_ALTERA_UART + default 4 + help + This setting lets you define the maximum number of the Altera + UART ports. The usual default varies from board to board, and + this setting is a way of catering for that. + +config SERIAL_ALTERA_UART_BAUDRATE + int "Default baudrate for Altera UART ports" + depends on SERIAL_ALTERA_UART + default 115200 + help + This setting lets you define what the default baudrate is for the + Altera UART ports. The usual default varies from board to board, + and this setting is a way of catering for that. + +config SERIAL_ALTERA_UART_CONSOLE + bool "Altera UART console support" + depends on SERIAL_ALTERA_UART=y + select SERIAL_CORE_CONSOLE + help + Enable a Altera UART port to be the system console. + endmenu diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 7f9d225d0430..208a85572c32 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -83,3 +83,4 @@ obj-$(CONFIG_SERIAL_QE) += ucc_uart.o obj-$(CONFIG_SERIAL_TIMBERDALE) += timbuart.o obj-$(CONFIG_SERIAL_GRLIB_GAISLER_APBUART) += apbuart.o obj-$(CONFIG_SERIAL_ALTERA_JTAGUART) += altera_jtaguart.o +obj-$(CONFIG_SERIAL_ALTERA_UART) += altera_uart.o diff --git a/drivers/serial/altera_uart.c b/drivers/serial/altera_uart.c new file mode 100644 index 000000000000..bcee156d2f2e --- /dev/null +++ b/drivers/serial/altera_uart.c @@ -0,0 +1,570 @@ +/* + * altera_uart.c -- Altera UART driver + * + * Based on mcf.c -- Freescale ColdFire UART driver + * + * (C) Copyright 2003-2007, Greg Ungerer + * (C) Copyright 2008, Thomas Chou + * (C) Copyright 2010, Tobias Klauser + * + * 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 + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "altera_uart" + +/* + * Altera UART register definitions according to the Nios UART datasheet: + * http://www.altera.com/literature/ds/ds_nios_uart.pdf + */ + +#define ALTERA_UART_SIZE 32 + +#define ALTERA_UART_RXDATA_REG 0 +#define ALTERA_UART_TXDATA_REG 4 +#define ALTERA_UART_STATUS_REG 8 +#define ALTERA_UART_CONTROL_REG 12 +#define ALTERA_UART_DIVISOR_REG 16 +#define ALTERA_UART_EOP_REG 20 + +#define ALTERA_UART_STATUS_PE_MSK 0x0001 /* parity error */ +#define ALTERA_UART_STATUS_FE_MSK 0x0002 /* framing error */ +#define ALTERA_UART_STATUS_BRK_MSK 0x0004 /* break */ +#define ALTERA_UART_STATUS_ROE_MSK 0x0008 /* RX overrun error */ +#define ALTERA_UART_STATUS_TOE_MSK 0x0010 /* TX overrun error */ +#define ALTERA_UART_STATUS_TMT_MSK 0x0020 /* TX shift register state */ +#define ALTERA_UART_STATUS_TRDY_MSK 0x0040 /* TX ready */ +#define ALTERA_UART_STATUS_RRDY_MSK 0x0080 /* RX ready */ +#define ALTERA_UART_STATUS_E_MSK 0x0100 /* exception condition */ +#define ALTERA_UART_STATUS_DCTS_MSK 0x0400 /* CTS logic-level change */ +#define ALTERA_UART_STATUS_CTS_MSK 0x0800 /* CTS logic state */ +#define ALTERA_UART_STATUS_EOP_MSK 0x1000 /* EOP written/read */ + + /* Enable interrupt on... */ +#define ALTERA_UART_CONTROL_PE_MSK 0x0001 /* ...parity error */ +#define ALTERA_UART_CONTROL_FE_MSK 0x0002 /* ...framing error */ +#define ALTERA_UART_CONTROL_BRK_MSK 0x0004 /* ...break */ +#define ALTERA_UART_CONTROL_ROE_MSK 0x0008 /* ...RX overrun */ +#define ALTERA_UART_CONTROL_TOE_MSK 0x0010 /* ...TX overrun */ +#define ALTERA_UART_CONTROL_TMT_MSK 0x0020 /* ...TX shift register empty */ +#define ALTERA_UART_CONTROL_TRDY_MSK 0x0040 /* ...TX ready */ +#define ALTERA_UART_CONTROL_RRDY_MSK 0x0080 /* ...RX ready */ +#define ALTERA_UART_CONTROL_E_MSK 0x0100 /* ...exception*/ + +#define ALTERA_UART_CONTROL_TRBK_MSK 0x0200 /* TX break */ +#define ALTERA_UART_CONTROL_DCTS_MSK 0x0400 /* Interrupt on CTS change */ +#define ALTERA_UART_CONTROL_RTS_MSK 0x0800 /* RTS signal */ +#define ALTERA_UART_CONTROL_EOP_MSK 0x1000 /* Interrupt on EOP */ + +/* + * Local per-uart structure. + */ +struct altera_uart { + struct uart_port port; + unsigned int sigs; /* Local copy of line sigs */ + unsigned short imr; /* Local IMR mirror */ +}; + +static unsigned int altera_uart_tx_empty(struct uart_port *port) +{ + return (readl(port->membase + ALTERA_UART_STATUS_REG) & + ALTERA_UART_STATUS_TMT_MSK) ? TIOCSER_TEMT : 0; +} + +static unsigned int altera_uart_get_mctrl(struct uart_port *port) +{ + struct altera_uart *pp = container_of(port, struct altera_uart, port); + unsigned long flags; + unsigned int sigs; + + spin_lock_irqsave(&port->lock, flags); + sigs = + (readl(port->membase + ALTERA_UART_STATUS_REG) & + ALTERA_UART_STATUS_CTS_MSK) ? TIOCM_CTS : 0; + sigs |= (pp->sigs & TIOCM_RTS); + spin_unlock_irqrestore(&port->lock, flags); + + return sigs; +} + +static void altera_uart_set_mctrl(struct uart_port *port, unsigned int sigs) +{ + struct altera_uart *pp = container_of(port, struct altera_uart, port); + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + pp->sigs = sigs; + if (sigs & TIOCM_RTS) + pp->imr |= ALTERA_UART_CONTROL_RTS_MSK; + else + pp->imr &= ~ALTERA_UART_CONTROL_RTS_MSK; + writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void altera_uart_start_tx(struct uart_port *port) +{ + struct altera_uart *pp = container_of(port, struct altera_uart, port); + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + pp->imr |= ALTERA_UART_CONTROL_TRDY_MSK; + writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void altera_uart_stop_tx(struct uart_port *port) +{ + struct altera_uart *pp = container_of(port, struct altera_uart, port); + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + pp->imr &= ~ALTERA_UART_CONTROL_TRDY_MSK; + writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void altera_uart_stop_rx(struct uart_port *port) +{ + struct altera_uart *pp = container_of(port, struct altera_uart, port); + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + pp->imr &= ~ALTERA_UART_CONTROL_RRDY_MSK; + writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void altera_uart_break_ctl(struct uart_port *port, int break_state) +{ + struct altera_uart *pp = container_of(port, struct altera_uart, port); + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + if (break_state == -1) + pp->imr |= ALTERA_UART_CONTROL_TRBK_MSK; + else + pp->imr &= ~ALTERA_UART_CONTROL_TRBK_MSK; + writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void altera_uart_enable_ms(struct uart_port *port) +{ +} + +static void altera_uart_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + unsigned long flags; + unsigned int baud, baudclk; + + baud = uart_get_baud_rate(port, termios, old, 0, 4000000); + baudclk = port->uartclk / baud; + + if (old) + tty_termios_copy_hw(termios, old); + tty_termios_encode_baud_rate(termios, baud, baud); + + spin_lock_irqsave(&port->lock, flags); + writel(baudclk, port->membase + ALTERA_UART_DIVISOR_REG); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void altera_uart_rx_chars(struct altera_uart *pp) +{ + struct uart_port *port = &pp->port; + unsigned char ch, flag; + unsigned short status; + + while ((status = readl(port->membase + ALTERA_UART_STATUS_REG)) & + ALTERA_UART_STATUS_RRDY_MSK) { + ch = readl(port->membase + ALTERA_UART_RXDATA_REG); + flag = TTY_NORMAL; + port->icount.rx++; + + if (status & ALTERA_UART_STATUS_E_MSK) { + writel(status, port->membase + ALTERA_UART_STATUS_REG); + + if (status & ALTERA_UART_STATUS_BRK_MSK) { + port->icount.brk++; + if (uart_handle_break(port)) + continue; + } else if (status & ALTERA_UART_STATUS_PE_MSK) { + port->icount.parity++; + } else if (status & ALTERA_UART_STATUS_ROE_MSK) { + port->icount.overrun++; + } else if (status & ALTERA_UART_STATUS_FE_MSK) { + port->icount.frame++; + } + + status &= port->read_status_mask; + + if (status & ALTERA_UART_STATUS_BRK_MSK) + flag = TTY_BREAK; + else if (status & ALTERA_UART_STATUS_PE_MSK) + flag = TTY_PARITY; + else if (status & ALTERA_UART_STATUS_FE_MSK) + flag = TTY_FRAME; + } + + if (uart_handle_sysrq_char(port, ch)) + continue; + uart_insert_char(port, status, ALTERA_UART_STATUS_ROE_MSK, ch, + flag); + } + + tty_flip_buffer_push(port->state->port.tty); +} + +static void altera_uart_tx_chars(struct altera_uart *pp) +{ + struct uart_port *port = &pp->port; + struct circ_buf *xmit = &port->state->xmit; + + if (port->x_char) { + /* Send special char - probably flow control */ + writel(port->x_char, port->membase + ALTERA_UART_TXDATA_REG); + port->x_char = 0; + port->icount.tx++; + return; + } + + while (readl(port->membase + ALTERA_UART_STATUS_REG) & + ALTERA_UART_STATUS_TRDY_MSK) { + if (xmit->head == xmit->tail) + break; + writel(xmit->buf[xmit->tail], + port->membase + ALTERA_UART_TXDATA_REG); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (xmit->head == xmit->tail) { + pp->imr &= ~ALTERA_UART_CONTROL_TRDY_MSK; + writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG); + } +} + +static irqreturn_t altera_uart_interrupt(int irq, void *data) +{ + struct uart_port *port = data; + struct altera_uart *pp = container_of(port, struct altera_uart, port); + unsigned int isr; + + isr = readl(port->membase + ALTERA_UART_STATUS_REG) & pp->imr; + if (isr & ALTERA_UART_STATUS_RRDY_MSK) + altera_uart_rx_chars(pp); + if (isr & ALTERA_UART_STATUS_TRDY_MSK) + altera_uart_tx_chars(pp); + return IRQ_RETVAL(isr); +} + +static void altera_uart_config_port(struct uart_port *port, int flags) +{ + port->type = PORT_ALTERA_UART; + + /* Clear mask, so no surprise interrupts. */ + writel(0, port->membase + ALTERA_UART_CONTROL_REG); + /* Clear status register */ + writel(0, port->membase + ALTERA_UART_STATUS_REG); +} + +static int altera_uart_startup(struct uart_port *port) +{ + struct altera_uart *pp = container_of(port, struct altera_uart, port); + unsigned long flags; + int ret; + + ret = request_irq(port->irq, altera_uart_interrupt, IRQF_DISABLED, + DRV_NAME, port); + if (ret) { + pr_err(DRV_NAME ": unable to attach Altera UART %d " + "interrupt vector=%d\n", port->line, port->irq); + return ret; + } + + spin_lock_irqsave(&port->lock, flags); + + /* Enable RX interrupts now */ + pp->imr = ALTERA_UART_CONTROL_RRDY_MSK; + writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG); + + spin_unlock_irqrestore(&port->lock, flags); + + return 0; +} + +static void altera_uart_shutdown(struct uart_port *port) +{ + struct altera_uart *pp = container_of(port, struct altera_uart, port); + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + + /* Disable all interrupts now */ + pp->imr = 0; + writel(pp->imr, port->membase + ALTERA_UART_CONTROL_REG); + + spin_unlock_irqrestore(&port->lock, flags); + + free_irq(port->irq, port); +} + +static const char *altera_uart_type(struct uart_port *port) +{ + return (port->type == PORT_ALTERA_UART) ? "Altera UART" : NULL; +} + +static int altera_uart_request_port(struct uart_port *port) +{ + /* UARTs always present */ + return 0; +} + +static void altera_uart_release_port(struct uart_port *port) +{ + /* Nothing to release... */ +} + +static int altera_uart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + if ((ser->type != PORT_UNKNOWN) && (ser->type != PORT_ALTERA_UART)) + return -EINVAL; + return 0; +} + +/* + * Define the basic serial functions we support. + */ +static struct uart_ops altera_uart_ops = { + .tx_empty = altera_uart_tx_empty, + .get_mctrl = altera_uart_get_mctrl, + .set_mctrl = altera_uart_set_mctrl, + .start_tx = altera_uart_start_tx, + .stop_tx = altera_uart_stop_tx, + .stop_rx = altera_uart_stop_rx, + .enable_ms = altera_uart_enable_ms, + .break_ctl = altera_uart_break_ctl, + .startup = altera_uart_startup, + .shutdown = altera_uart_shutdown, + .set_termios = altera_uart_set_termios, + .type = altera_uart_type, + .request_port = altera_uart_request_port, + .release_port = altera_uart_release_port, + .config_port = altera_uart_config_port, + .verify_port = altera_uart_verify_port, +}; + +static struct altera_uart altera_uart_ports[CONFIG_SERIAL_ALTERA_UART_MAXPORTS]; + +#if defined(CONFIG_SERIAL_ALTERA_UART_CONSOLE) + +int __init early_altera_uart_setup(struct altera_uart_platform_uart *platp) +{ + struct uart_port *port; + int i; + + for (i = 0; i < CONFIG_SERIAL_ALTERA_UART_MAXPORTS && platp[i].mapbase; i++) { + port = &altera_uart_ports[i].port; + + port->line = i; + port->type = PORT_ALTERA_UART; + port->mapbase = platp[i].mapbase; + port->membase = ioremap(port->mapbase, ALTERA_UART_SIZE); + port->iotype = SERIAL_IO_MEM; + port->irq = platp[i].irq; + port->uartclk = platp[i].uartclk; + port->flags = ASYNC_BOOT_AUTOCONF; + port->ops = &altera_uart_ops; + } + + return 0; +} + +static void altera_uart_console_putc(struct console *co, const char c) +{ + struct uart_port *port = &(altera_uart_ports + co->index)->port; + int i; + + for (i = 0; i < 0x10000; i++) { + if (readl(port->membase + ALTERA_UART_STATUS_REG) & + ALTERA_UART_STATUS_TRDY_MSK) + break; + } + writel(c, port->membase + ALTERA_UART_TXDATA_REG); + for (i = 0; i < 0x10000; i++) { + if (readl(port->membase + ALTERA_UART_STATUS_REG) & + ALTERA_UART_STATUS_TRDY_MSK) + break; + } +} + +static void altera_uart_console_write(struct console *co, const char *s, + unsigned int count) +{ + for (; count; count--, s++) { + altera_uart_console_putc(co, *s); + if (*s == '\n') + altera_uart_console_putc(co, '\r'); + } +} + +static int __init altera_uart_console_setup(struct console *co, char *options) +{ + struct uart_port *port; + int baud = CONFIG_SERIAL_ALTERA_UART_BAUDRATE; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + if (co->index < 0 || co->index >= CONFIG_SERIAL_ALTERA_UART_MAXPORTS) + return -EINVAL; + port = &altera_uart_ports[co->index].port; + if (port->membase == 0) + return -ENODEV; + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + + return uart_set_options(port, co, baud, parity, bits, flow); +} + +static struct uart_driver altera_uart_driver; + +static struct console altera_uart_console = { + .name = "ttyS", + .write = altera_uart_console_write, + .device = uart_console_device, + .setup = altera_uart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &altera_uart_driver, +}; + +static int __init altera_uart_console_init(void) +{ + register_console(&altera_uart_console); + return 0; +} + +console_initcall(altera_uart_console_init); + +#define ALTERA_UART_CONSOLE (&altera_uart_console) + +#else + +#define ALTERA_UART_CONSOLE NULL + +#endif /* CONFIG_ALTERA_UART_CONSOLE */ + +/* + * Define the altera_uart UART driver structure. + */ +static struct uart_driver altera_uart_driver = { + .owner = THIS_MODULE, + .driver_name = DRV_NAME, + .dev_name = "ttyS", + .major = TTY_MAJOR, + .minor = 64, + .nr = CONFIG_SERIAL_ALTERA_UART_MAXPORTS, + .cons = ALTERA_UART_CONSOLE, +}; + +static int __devinit altera_uart_probe(struct platform_device *pdev) +{ + struct altera_uart_platform_uart *platp = pdev->dev.platform_data; + struct uart_port *port; + int i; + + for (i = 0; i < CONFIG_SERIAL_ALTERA_UART_MAXPORTS && platp[i].mapbase; i++) { + port = &altera_uart_ports[i].port; + + port->line = i; + port->type = PORT_ALTERA_UART; + port->mapbase = platp[i].mapbase; + port->membase = ioremap(port->mapbase, ALTERA_UART_SIZE); + port->iotype = SERIAL_IO_MEM; + port->irq = platp[i].irq; + port->uartclk = platp[i].uartclk; + port->ops = &altera_uart_ops; + port->flags = ASYNC_BOOT_AUTOCONF; + + uart_add_one_port(&altera_uart_driver, port); + } + + return 0; +} + +static int altera_uart_remove(struct platform_device *pdev) +{ + struct uart_port *port; + int i; + + for (i = 0; i < CONFIG_SERIAL_ALTERA_UART_MAXPORTS; i++) { + port = &altera_uart_ports[i].port; + if (port) + uart_remove_one_port(&altera_uart_driver, port); + } + + return 0; +} + +static struct platform_driver altera_uart_platform_driver = { + .probe = altera_uart_probe, + .remove = __devexit_p(altera_uart_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = NULL, + }, +}; + +static int __init altera_uart_init(void) +{ + int rc; + + rc = uart_register_driver(&altera_uart_driver); + if (rc) + return rc; + rc = platform_driver_register(&altera_uart_platform_driver); + if (rc) { + uart_unregister_driver(&altera_uart_driver); + return rc; + } + return 0; +} + +static void __exit altera_uart_exit(void) +{ + platform_driver_unregister(&altera_uart_platform_driver); + uart_unregister_driver(&altera_uart_driver); +} + +module_init(altera_uart_init); +module_exit(altera_uart_exit); + +MODULE_DESCRIPTION("Altera UART driver"); +MODULE_AUTHOR("Thomas Chou "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/include/linux/altera_uart.h b/include/linux/altera_uart.h new file mode 100644 index 000000000000..8d441064a30d --- /dev/null +++ b/include/linux/altera_uart.h @@ -0,0 +1,14 @@ +/* + * altera_uart.h -- Altera UART driver defines. + */ + +#ifndef __ALTUART_H +#define __ALTUART_H + +struct altera_uart_platform_uart { + unsigned long mapbase; /* Physical address base */ + unsigned int irq; /* Interrupt vector */ + unsigned int uartclk; /* UART clock rate */ +}; + +#endif /* __ALTUART_H */ diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index ade1536e1a48..09d0d2d5a08b 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -184,6 +184,7 @@ /* Altera UARTs */ #define PORT_ALTERA_JTAGUART 91 +#define PORT_ALTERA_UART 92 #ifdef __KERNEL__ From ccc5ca8d4cadc141d6c97ec0213cdd676616a11d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 7 May 2010 19:58:32 +0200 Subject: [PATCH 22/25] tty: fix obsolete comment on tty_insert_flip_string_fixed_flag Comment was not updated when tty_insert_flip_string was generalised. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/char/tty_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/tty_buffer.c b/drivers/char/tty_buffer.c index 7ee52164d474..cc1e9850d655 100644 --- a/drivers/char/tty_buffer.c +++ b/drivers/char/tty_buffer.c @@ -238,7 +238,7 @@ EXPORT_SYMBOL_GPL(tty_buffer_request_room); * @size: size * * Queue a series of bytes to the tty buffering. All the characters - * passed are marked as without error. Returns the number added. + * passed are marked with the supplied flag. Returns the number added. * * Locking: Called functions may take tty->buf.lock */ From e114474cf5c7fe7ab94dd4f5938ffa51c9af77c4 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Tue, 11 May 2010 09:10:23 +0200 Subject: [PATCH 23/25] serial: bfin_sport_uart: Use resource size to fix off-by-one error Use the resource_size function instead of manually calculating the resource size. This actually fixes an off-by-one error. Signed-off-by: Tobias Klauser Signed-off-by: Mike Frysinger Signed-off-by: Greg Kroah-Hartman --- drivers/serial/bfin_sport_uart.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/serial/bfin_sport_uart.c b/drivers/serial/bfin_sport_uart.c index 66cda2290de0..e57fb3d228e2 100644 --- a/drivers/serial/bfin_sport_uart.c +++ b/drivers/serial/bfin_sport_uart.c @@ -774,8 +774,7 @@ static int __devinit sport_uart_probe(struct platform_device *pdev) goto out_error_free_peripherals; } - sport->port.membase = ioremap(res->start, - res->end - res->start); + sport->port.membase = ioremap(res->start, resource_size(res)); if (!sport->port.membase) { dev_err(&pdev->dev, "Cannot map sport IO\n"); ret = -ENXIO; From 26e1e8d1d1cfa914b95b5dab001a6ed898872755 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 7 May 2010 10:30:41 +0200 Subject: [PATCH 24/25] serial: isicomm: handle running out of slots This patch makes it return -ENODEV if we run out of empty slots in the probe function. It's unlikely to happen, but it makes the static checkers happy. Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman --- drivers/char/isicom.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/char/isicom.c b/drivers/char/isicom.c index c1ab303455cf..98310e1aae30 100644 --- a/drivers/char/isicom.c +++ b/drivers/char/isicom.c @@ -1573,11 +1573,16 @@ static int __devinit isicom_probe(struct pci_dev *pdev, dev_info(&pdev->dev, "ISI PCI Card(Device ID 0x%x)\n", ent->device); /* allot the first empty slot in the array */ - for (index = 0; index < BOARD_COUNT; index++) + for (index = 0; index < BOARD_COUNT; index++) { if (isi_card[index].base == 0) { board = &isi_card[index]; break; } + } + if (index == BOARD_COUNT) { + retval = -ENODEV; + goto err_disable; + } board->index = index; board->base = pci_resource_start(pdev, 3); @@ -1624,6 +1629,7 @@ errunrr: errdec: board->base = 0; card_count--; +err_disable: pci_disable_device(pdev); err: return retval; From 0dbb5671921b6dad2396090ef81a3efee1ae7da1 Mon Sep 17 00:00:00 2001 From: Christoph Egger Date: Mon, 17 May 2010 17:25:54 +0200 Subject: [PATCH 25/25] serial: Tidy REMOTE_DEBUG REMOTE_DEBUG does already appear in 2.2 kernel sources but didn't appear as a config Option in the initial git import 2.6.12-rc. It's currently just used in one single place of the linux kernel and should probably be dropped totally Signed-off-by: Christoph Egger Signed-off-by: Greg Kroah-Hartman --- drivers/char/serial167.c | 223 +-------------------------------------- 1 file changed, 1 insertion(+), 222 deletions(-) diff --git a/drivers/char/serial167.c b/drivers/char/serial167.c index 78a62ebe75c7..ecbe479c7d68 100644 --- a/drivers/char/serial167.c +++ b/drivers/char/serial167.c @@ -176,23 +176,6 @@ static void config_setup(struct cyclades_port *); static void show_status(int); #endif -#ifdef CONFIG_REMOTE_DEBUG -static void debug_setup(void); -void queueDebugChar(int c); -int getDebugChar(void); - -#define DEBUG_PORT 1 -#define DEBUG_LEN 256 - -typedef struct { - int in; - int out; - unsigned char buf[DEBUG_LEN]; -} debugq; - -debugq debugiq; -#endif - /* * I have my own version of udelay(), as it is needed when initialising * the chip, before the delay loop has been calibrated. Should probably @@ -515,11 +498,6 @@ static irqreturn_t cd2401_tx_interrupt(int irq, void *dev_id) /* determine the channel and change to that context */ channel = (u_short) (base_addr[CyLICR] >> 2); -#ifdef CONFIG_REMOTE_DEBUG - if (channel == DEBUG_PORT) { - panic("TxInt on debug port!!!"); - } -#endif /* validate the port number (as configured and open) */ if ((channel < 0) || (NR_PORTS <= channel)) { base_addr[CyIER] &= ~(CyTxMpty | CyTxRdy); @@ -634,14 +612,6 @@ static irqreturn_t cd2401_rx_interrupt(int irq, void *dev_id) info->last_active = jiffies; save_cnt = char_count = base_addr[CyRFOC]; -#ifdef CONFIG_REMOTE_DEBUG - if (channel == DEBUG_PORT) { - while (char_count--) { - data = base_addr[CyRDR]; - queueDebugChar(data); - } - } else -#endif /* if there is nowhere to put the data, discard it */ if (info->tty == 0) { while (char_count--) { @@ -2195,9 +2165,7 @@ static int __init serial167_init(void) port_num++; info++; } -#ifdef CONFIG_REMOTE_DEBUG - debug_setup(); -#endif + ret = request_irq(MVME167_IRQ_SER_ERR, cd2401_rxerr_interrupt, 0, "cd2401_errors", cd2401_rxerr_interrupt); if (ret) { @@ -2518,193 +2486,4 @@ static int __init serial167_console_init(void) console_initcall(serial167_console_init); -#ifdef CONFIG_REMOTE_DEBUG -void putDebugChar(int c) -{ - volatile unsigned char *base_addr = (u_char *) BASE_ADDR; - unsigned long flags; - volatile u_char sink; - u_char ier; - int port; - - local_irq_save(flags); - - /* Ensure transmitter is enabled! */ - - port = DEBUG_PORT; - base_addr[CyCAR] = (u_char) port; - while (base_addr[CyCCR]) - ; - base_addr[CyCCR] = CyENB_XMTR; - - ier = base_addr[CyIER]; - base_addr[CyIER] = CyTxMpty; - - while (1) { - if (pcc2chip[PccSCCTICR] & 0x20) { - /* We have a Tx int. Acknowledge it */ - sink = pcc2chip[PccTPIACKR]; - if ((base_addr[CyLICR] >> 2) == port) { - base_addr[CyTDR] = c; - base_addr[CyTEOIR] = 0; - break; - } else - base_addr[CyTEOIR] = CyNOTRANS; - } - } - - base_addr[CyIER] = ier; - - local_irq_restore(flags); -} - -int getDebugChar() -{ - volatile unsigned char *base_addr = (u_char *) BASE_ADDR; - unsigned long flags; - volatile u_char sink; - u_char ier; - int port; - int i, c; - - i = debugiq.out; - if (i != debugiq.in) { - c = debugiq.buf[i]; - if (++i == DEBUG_LEN) - i = 0; - debugiq.out = i; - return c; - } - /* OK, nothing in queue, wait in poll loop */ - - local_irq_save(flags); - - /* Ensure receiver is enabled! */ - - port = DEBUG_PORT; - base_addr[CyCAR] = (u_char) port; -#if 0 - while (base_addr[CyCCR]) - ; - base_addr[CyCCR] = CyENB_RCVR; -#endif - ier = base_addr[CyIER]; - base_addr[CyIER] = CyRxData; - - while (1) { - if (pcc2chip[PccSCCRICR] & 0x20) { - /* We have a Rx int. Acknowledge it */ - sink = pcc2chip[PccRPIACKR]; - if ((base_addr[CyLICR] >> 2) == port) { - int cnt = base_addr[CyRFOC]; - while (cnt-- > 0) { - c = base_addr[CyRDR]; - if (c == 0) - printk - ("!! debug char is null (cnt=%d) !!", - cnt); - else - queueDebugChar(c); - } - base_addr[CyREOIR] = 0; - i = debugiq.out; - if (i == debugiq.in) - panic("Debug input queue empty!"); - c = debugiq.buf[i]; - if (++i == DEBUG_LEN) - i = 0; - debugiq.out = i; - break; - } else - base_addr[CyREOIR] = CyNOTRANS; - } - } - - base_addr[CyIER] = ier; - - local_irq_restore(flags); - - return (c); -} - -void queueDebugChar(int c) -{ - int i; - - i = debugiq.in; - debugiq.buf[i] = c; - if (++i == DEBUG_LEN) - i = 0; - if (i != debugiq.out) - debugiq.in = i; -} - -static void debug_setup() -{ - unsigned long flags; - volatile unsigned char *base_addr = (u_char *) BASE_ADDR; - int i, cflag; - - cflag = B19200; - - local_irq_save(flags); - - for (i = 0; i < 4; i++) { - base_addr[CyCAR] = i; - base_addr[CyLICR] = i << 2; - } - - debugiq.in = debugiq.out = 0; - - base_addr[CyCAR] = DEBUG_PORT; - - /* baud rate */ - i = cflag & CBAUD; - - base_addr[CyIER] = 0; - - base_addr[CyCMR] = CyASYNC; - base_addr[CyLICR] = DEBUG_PORT << 2; - base_addr[CyLIVR] = 0x5c; - - /* tx and rx baud rate */ - - base_addr[CyTCOR] = baud_co[i]; - base_addr[CyTBPR] = baud_bpr[i]; - base_addr[CyRCOR] = baud_co[i] >> 5; - base_addr[CyRBPR] = baud_bpr[i]; - - /* set line characteristics according configuration */ - - base_addr[CySCHR1] = 0; - base_addr[CySCHR2] = 0; - base_addr[CySCRL] = 0; - base_addr[CySCRH] = 0; - base_addr[CyCOR1] = Cy_8_BITS | CyPARITY_NONE; - base_addr[CyCOR2] = 0; - base_addr[CyCOR3] = Cy_1_STOP; - base_addr[CyCOR4] = baud_cor4[i]; - base_addr[CyCOR5] = 0; - base_addr[CyCOR6] = 0; - base_addr[CyCOR7] = 0; - - write_cy_cmd(base_addr, CyINIT_CHAN); - write_cy_cmd(base_addr, CyENB_RCVR); - - base_addr[CyCAR] = DEBUG_PORT; /* !!! Is this needed? */ - - base_addr[CyRTPRL] = 2; - base_addr[CyRTPRH] = 0; - - base_addr[CyMSVR1] = CyRTS; - base_addr[CyMSVR2] = CyDTR; - - base_addr[CyIER] = CyRxData; - - local_irq_restore(flags); - -} /* debug_setup */ - -#endif - MODULE_LICENSE("GPL");