Prev: trivial: fix typos concerning "challenge"
Next: [PATCH] usb: gadget: functionfs: stale Makefile entry removed
From: Philippe Langlais on 19 Jul 2010 05:30 UART Features extract from STEricsson U6715 datasheet (arm926 SoC for mobile phone): * Fully compatible with industry standard 16C550 and 16C450 from various manufacturers * RX and TX 64 byte FIFO reduces CPU interrupts * Full double buffering * Modem control signals include CTS, RTS, (and DSR, DTR on UART1 only) * Automatic baud rate selection * Manual or automatic RTS/CTS smart hardware flow control * Programmable serial characteristics: – Baud rate generation (50 to 3.25M baud) – 5, 6, 7 or 8-bit characters – Even, odd or no-parity bit generation and detection – 1, 1.5 or 2 stop bit generation * Independent control of transmit, receive, line status, data set interrupts and FIFOs * Full status-reporting capabilities * Separate DMA signalling for RX and TX * Timed interrupt to spread receive interrupt on known duration * DMA time-out interrupt to allow detection of end of reception * Carkit pulse coding and decoding compliant with USB carkit control interface [40] This UART IP is auto-detected as a 16550A type Clock specificities: It's parent clock depend on baud rate. The UART port can be used before u6xxx clock framework initialization Signed-off-by: Philippe Langlais <philippe.langlais(a)stericsson.com> --- drivers/serial/8250.c | 26 ++++++- drivers/serial/8250_u6.c | 179 +++++++++++++++++++++++++++++++++++++++++++ drivers/serial/Kconfig | 19 +++++ drivers/serial/Makefile | 1 + include/linux/serial_8250.h | 8 ++ 5 files changed, 232 insertions(+), 1 deletions(-) create mode 100644 drivers/serial/8250_u6.c diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c index 607ed7f..269950f 100644 --- a/drivers/serial/8250.c +++ b/drivers/serial/8250.c @@ -199,10 +199,16 @@ static const struct serial8250_config uart_config[] = { }, [PORT_16550A] = { .name = "16550A", +#if defined(CONFIG_SERIAL_8250_U6XXX) + .fifo_size = 64, + .tx_loadsz = 64, + .flags = UART_CAP_FIFO | UART_CAP_AFE, +#else .fifo_size = 16, .tx_loadsz = 16, - .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10, .flags = UART_CAP_FIFO, +#endif + .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10, }, [PORT_CIRRUS] = { .name = "Cirrus", @@ -2268,6 +2274,13 @@ serial8250_set_termios(struct uart_port *port, struct ktermios *termios, /* * Ask the core to calculate the divisor for us. */ +#ifdef CONFIG_SERIAL_8250_CUSTOM_CLOCK + baud = uart_get_baud_rate(port, termios, old, 0, + CONFIG_SERIAL_8250_CUSTOM_MAX_BAUDRATE); + /* Calculate the new uart clock frequency if it is tunable */ + port->uartclk = serial8250_get_custom_clock(port, baud); +#endif + baud = uart_get_baud_rate(port, termios, old, port->uartclk / 16 / 0xffff, port->uartclk / 16); @@ -2298,6 +2311,13 @@ serial8250_set_termios(struct uart_port *port, struct ktermios *termios, up->mcr &= ~UART_MCR_AFE; if (termios->c_cflag & CRTSCTS) up->mcr |= UART_MCR_AFE; +#if defined(CONFIG_SERIAL_8250_U6XXX) + /** + * When AFE is active, let the HW handle the stop/restart TX + * upon CTS change. It reacts much quicker than the SW driver. + */ + port->flags &= ~ASYNC_CTS_FLOW; +#endif } /* @@ -2383,6 +2403,10 @@ serial8250_set_termios(struct uart_port *port, struct ktermios *termios, serial_outp(up, UART_LCR, cval | UART_LCR_DLAB);/* set DLAB */ } +#ifdef CONFIG_SERIAL_8250_CUSTOM_CLOCK + /* set the new uart clock frequency if it is tunable */ + serial8250_set_custom_clock(port); +#endif serial_dl_write(up, quot); /* diff --git a/drivers/serial/8250_u6.c b/drivers/serial/8250_u6.c new file mode 100644 index 0000000..bcc0f98 --- /dev/null +++ b/drivers/serial/8250_u6.c @@ -0,0 +1,179 @@ +/* + * linux/drivers/serial/8250_pnx.c + * + * Copyright (C) ST-Ericsson SA 2010 + * Author: Ludovic Barre <ludovic.barre(a)stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/serial_core.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#include <mach/serial.h> +#include <mach/hardware.h> +#include <mach/clock.h> + +/* Register description for FDIV_CTRL */ +/* UART FDIV_CTRL Register (8 bits) */ +#define UARTX_FDIV_CTRL_OFFSET 0xC00 +/* UART FDIV_M Register (16 bits) */ +#define UARTX_FDIV_M_OFFSET 0xC04 +/* UART FDIV_N Register (16 bits) */ +#define UARTX_FDIV_N_OFFSET 0xC08 + +/* Bits definition for register UARTX_FDIV_CTRL */ +#define UARTX_FDIV_ENABLE_SHIFT 7 +#define UARTX_FDIV_ENABLE_FIELD (0xFFFFFFFF - (0x1UL<<UARTX_FDIV_ENABLE_SHIFT)) +#define UARTX_FDIV_ENABLE_OFF (0x0UL<<UARTX_FDIV_ENABLE_SHIFT) +#define UARTX_FDIV_ENABLE_ON (0x1UL<<UARTX_FDIV_ENABLE_SHIFT) +#define UARTX_FDIV_ENABLE (0x1UL<<UARTX_FDIV_ENABLE_SHIFT) +#define UARTX_CLKSEL_SHIFT 0 +#define UARTX_CLKSEL_FIELD (0xFFFFFFFF - (0x3UL<<UARTX_CLKSEL_SHIFT)) +#define UARTX_CLKSEL_PCLK (0x0UL<<UARTX_CLKSEL_SHIFT) +#define UARTX_CLKSEL_13M (0x1UL<<UARTX_CLKSEL_SHIFT) +#define UARTX_CLKSEL_26M (0x2UL<<UARTX_CLKSEL_SHIFT) +#define UARTX_CLKSEL_3 (0x3UL<<UARTX_CLKSEL_SHIFT) + +/* + * console and pctools has needed to start before serial_init + * (with cgu interface) + */ +static int uart_enable_clock(struct uart_port *port) +{ + u32 v; + v = readl(CGU_GATESC1_REG); + + if (port->irq == IRQ_UART1) + v |= CGU_UART1EN_1; + else if (port->irq == IRQ_UART2) + v |= CGU_UART2EN_1; + + writel(v, CGU_GATESC1_REG); + + return 0; +} + +static int uart_disable_clock(struct uart_port *port) +{ + u32 v; + v = readl(CGU_GATESC1_REG); + + if (port->irq == IRQ_UART1) + v &= ~CGU_UART1EN_0; + else if (port->irq == IRQ_UART2) + v &= ~CGU_UART2EN_0; + + writel(v, CGU_GATESC1_REG); + + return 0; +} + +unsigned int serial8250_enable_clock(struct uart_port *port) +{ + struct u6_uart *uart_u6 = port->private_data; + + if (!uart_u6) + return uart_enable_clock(port); + + if (IS_ERR(uart_u6->uartClk)) { + printk(KERN_WARNING "%s - uart clock failed error:%ld\n", + __func__, PTR_ERR(uart_u6->uartClk)); + return PTR_ERR(uart_u6->uartClk); + } + + if (clk_get_usecount(uart_u6->uartClk) == 0) + clk_enable(uart_u6->uartClk); + return 0; +} + +unsigned int serial8250_disable_clock(struct uart_port *port) +{ + struct u6_uart *uart_u6 = port->private_data; + + if (!uart_u6) + return uart_disable_clock(port); + + if (IS_ERR(uart_u6->uartClk)) { + printk(KERN_WARNING "%s - uart clk error :%ld\n", __func__, + PTR_ERR(uart_u6->uartClk)); + return PTR_ERR(uart_u6->uartClk); + } + if (clk_get_usecount(uart_u6->uartClk) >= 1) + clk_disable(uart_u6->uartClk); + + return 0; +} + +unsigned int serial8250_get_custom_clock(struct uart_port *port, + unsigned int baud) +{ + switch (baud) { + case 3250000: + return 52000000; + case 2000000: + return 32000000; + case 1843200: + return 29491200; + case 921600: + return 14745600; + default: + return 7372800; + } +} + +void serial8250_set_custom_clock(struct uart_port *port) +{ + u32 fdiv_m = 0x5F37; + u32 fdiv_n = 0x3600; + u32 fdiv_ctrl = UARTX_FDIV_ENABLE_ON; + struct u6_uart *uart_u6 = port->private_data; + + switch (port->uartclk) { + case 7372800: /* clk=13MHz */ + fdiv_ctrl |= UARTX_CLKSEL_13M; + break; + case 14745600: /* clk=26MHz */ + fdiv_ctrl |= UARTX_CLKSEL_26M; + break; + case 29491200: /* clk=pclk */ + fdiv_ctrl |= UARTX_CLKSEL_PCLK; + break; + case 32000000: /* clk=pclk */ + fdiv_n = 0x3A98; + fdiv_ctrl |= UARTX_CLKSEL_PCLK; + break; + case 52000000: /* clk=pclk */ + fdiv_n = 0x5F37; + fdiv_ctrl |= UARTX_CLKSEL_PCLK; + break; + } + + if (uart_u6 != NULL && !IS_ERR(uart_u6->uartClk)) { + /* if cgu interface is ready and u6_serial_init */ + struct clk *parentClk; + + if (fdiv_ctrl & UARTX_CLKSEL_26M) + parentClk = clk_get(NULL, "clk26m_ck"); + else if (fdiv_ctrl & UARTX_CLKSEL_PCLK) + parentClk = clk_get(NULL, "pclk2_ck"); + else + parentClk = clk_get(NULL, "clk13m_ck"); + + if (!IS_ERR(parentClk)) { + serial8250_disable_clock(port); + + if (clk_set_parent(uart_u6->uartClk, parentClk) != 0) + printk(KERN_WARNING "%s: set parent failed\n", __func__); + + serial8250_enable_clock(port); + clk_put(parentClk); + } + } + + writel(fdiv_m, port->membase + UARTX_FDIV_M_OFFSET); + writel(fdiv_n, port->membase + UARTX_FDIV_N_OFFSET); + writel(fdiv_ctrl, port->membase + UARTX_FDIV_CTRL_OFFSET); +} diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 45ad290..ce93eae 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -163,6 +163,25 @@ config SERIAL_8250_MANY_PORTS say N here to save some memory. You can also say Y if you have an "intelligent" multiport card such as Cyclades, Digiboards, etc. +config SERIAL_8250_U6XXX + bool + depends on SERIAL_8250_EXTENDED && PLAT_U6XXX + default y + +config SERIAL_8250_CUSTOM_CLOCK + bool "Support serial ports with tunable input clock frequency" + depends on SERIAL_8250_EXTENDED && SERIAL_8250_U6XXX + default y + help + Say Y here if your platform has specific registers to change UART clock frequency. + +config SERIAL_8250_CUSTOM_MAX_BAUDRATE + int "Maximal reachable baudrate" + depends on SERIAL_8250_CUSTOM_CLOCK + default "3250000" + help + The value of the maximal reachable baudrate when tuning UART clock frequency (default value: 3.25MBds). + # # Multi-port serial cards # diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index a5edb49..dca7c9a 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_SERIAL_8250_BOCA) += 8250_boca.o obj-$(CONFIG_SERIAL_8250_EXAR_ST16C554) += 8250_exar_st16c554.o obj-$(CONFIG_SERIAL_8250_HUB6) += 8250_hub6.o obj-$(CONFIG_SERIAL_8250_MCA) += 8250_mca.o +obj-$(CONFIG_SERIAL_8250_U6XXX) += 8250_u6.o obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index fb46aba..3e86ceb 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -72,4 +72,12 @@ extern int serial8250_find_port(struct uart_port *p); extern int serial8250_find_port_for_earlycon(void); extern int setup_early_serial8250_console(char *cmdline); +#ifdef CONFIG_SERIAL_8250_CUSTOM_CLOCK +unsigned int serial8250_enable_clock(struct uart_port *port); +unsigned int serial8250_disable_clock(struct uart_port *port); +unsigned int serial8250_get_custom_clock(struct uart_port *port, + unsigned int baud); +void serial8250_set_custom_clock(struct uart_port *port); +#endif + #endif -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo(a)vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/ |