[PATCH] U6715 platform serial driver It's a generic driver for all U6XXX platform
Philippe Langlais
philippe.langlais at stericsson.com
Mon Aug 2 04:54:21 EDT 2010
Clock specificity:
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 at stericsson.com>
---
arch/arm/plat-u6xxx/Makefile | 2 +-
arch/arm/plat-u6xxx/include/mach/serial.h | 23 +++
arch/arm/plat-u6xxx/serial.c | 268 +++++++++++++++++++++++++++++
3 files changed, 292 insertions(+), 1 deletions(-)
create mode 100644 arch/arm/plat-u6xxx/include/mach/serial.h
create mode 100644 arch/arm/plat-u6xxx/serial.c
diff --git a/arch/arm/plat-u6xxx/Makefile b/arch/arm/plat-u6xxx/Makefile
index 3d6898e..f12068c 100644
--- a/arch/arm/plat-u6xxx/Makefile
+++ b/arch/arm/plat-u6xxx/Makefile
@@ -3,6 +3,6 @@
#
# Common support
-obj-y := io.o irq.o clock.o gpio.o
+obj-y := io.o irq.o clock.o gpio.o serial.o
obj-$(CONFIG_U6_MTU_TIMER) += timer.o
diff --git a/arch/arm/plat-u6xxx/include/mach/serial.h b/arch/arm/plat-u6xxx/include/mach/serial.h
new file mode 100644
index 0000000..321e406
--- /dev/null
+++ b/arch/arm/plat-u6xxx/include/mach/serial.h
@@ -0,0 +1,23 @@
+/*
+ * linux/arch/arm/plat-u6xxx/include/mach/serial.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Author: Ludovic Barre <ludovic.barre at stericsson.com> for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+
+#ifndef __SERIAL_H
+#define __SERIAL_H
+
+#include <linux/clk.h>
+
+struct u6_uart {
+ struct clk *uartClk;
+ char uart_name[7];
+};
+
+extern unsigned int u6_serial8250_enable_clock(struct uart_port *port);
+extern unsigned int u6_serial8250_disable_clock(struct uart_port *port);
+
+#endif /* __SERIAL_H */
diff --git a/arch/arm/plat-u6xxx/serial.c b/arch/arm/plat-u6xxx/serial.c
new file mode 100644
index 0000000..85dbbb6
--- /dev/null
+++ b/arch/arm/plat-u6xxx/serial.c
@@ -0,0 +1,268 @@
+/*
+ * linux/arch/arm/plat-u6xxx/serial.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors: Ludovic Barre <ludovic.barre at stericsson.com> for ST-Ericsson.
+ * Philippe Langlais <philippe.langlais at stericsson.com> for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ * U6 cpu type detection
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/serial_8250.h>
+#include <linux/slab.h>
+
+#include <mach/hardware.h>
+#include <mach/serial.h>
+#include <mach/clock.h>
+
+#define U6_MAX_BAUDRATE 3250000 /* 3.25MBds is the maximum baudrate supported */
+#define BASE_BAUD 7372800
+
+/* 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
+ * (without 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 u6_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)) {
+ pr_warning("%s - uart clock failed error:%ld\n",
+ __func__, PTR_ERR(uart_u6->uartClk));
+ return PTR_ERR(uart_u6->uartClk);
+ }
+
+ clk_enable(uart_u6->uartClk);
+
+ return 0;
+}
+
+unsigned int u6_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)) {
+ pr_warning("%s - uart clk error :%ld\n", __func__,
+ PTR_ERR(uart_u6->uartClk));
+ return PTR_ERR(uart_u6->uartClk);
+ }
+ clk_disable(uart_u6->uartClk);
+
+ return 0;
+}
+
+static unsigned int u6_get_setdiv_clock(struct uart_port *port,
+ unsigned int baud)
+{
+ unsigned int uartclk;
+ u32 fdiv_m = 0x5F37;
+ u32 fdiv_n = 0x3600;
+ u32 fdiv_ctrl = UARTX_FDIV_ENABLE_ON;
+ struct u6_uart *uart_u6 = port->private_data;
+
+ /* Compute uart clock from baudrate */
+ if (baud > 2000000)
+ uartclk = 52000000;
+ else if (baud > 1843200)
+ uartclk = 32000000;
+ else if (baud > 921600)
+ uartclk = 29491200;
+ else if (baud > 460800)
+ uartclk = 14745600;
+ else
+ uartclk = 7372800;
+
+ /* Set divisors & parent clock accordingly */
+ switch (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 clock 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)) {
+ u6_serial8250_disable_clock(port);
+
+ if (clk_set_parent(uart_u6->uartClk, parentClk) != 0)
+ pr_warning("%s: set parent failed\n", __func__);
+
+ u6_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);
+ return uartclk;
+}
+
+static void u6_set_termios(struct uart_port *port,
+ struct ktermios *termios, struct ktermios *old)
+{
+ unsigned int baud;
+
+ baud = uart_get_baud_rate(port, termios, old, 0, U6_MAX_BAUDRATE);
+ /* Calculate the new uart clock frequency & set divisors */
+ port->uartclk = u6_get_setdiv_clock(port, baud);
+ /* Call standard 8250 set_termios() */
+ serial8250_do_set_termios(port, termios, old);
+}
+
+/*
+ * Internal UARTs need to be initialized for the 8250 autoconfig to work
+ * properly.
+ */
+static struct plat_serial8250_port serial_platform_data[] = {
+ {
+ .membase = U6_IO_ADDRESS(UART1_BASE),
+ .mapbase = UART1_BASE,
+ .irq = IRQ_UART1,
+ .flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST,
+ .iotype = UPIO_MEM,
+ .regshift = 2,
+ .uartclk = BASE_BAUD,
+ .set_termios = u6_set_termios,
+ },
+ {
+ .membase = U6_IO_ADDRESS(UART2_BASE),
+ .mapbase = UART2_BASE,
+ .irq = IRQ_UART2,
+ .flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST,
+ .iotype = UPIO_MEM,
+ .regshift = 2,
+ .uartclk = BASE_BAUD,
+ .set_termios = u6_set_termios,
+ },
+ /* void declaration needed by serial core */
+ {
+ .flags = 0
+ },
+};
+
+static struct platform_device serial_device = {
+ .name = "serial8250",
+ .id = 0,
+ .dev = {.platform_data = serial_platform_data,},
+};
+
+static int __init u6_serial_init(void)
+{
+ int i;
+ struct u6_uart *uart_u6;
+
+ /* supress void element from loop */
+ for (i = 0; i < (ARRAY_SIZE(serial_platform_data) - 1); i++) {
+ struct clk *uartClk = NULL;
+
+ /* Allocation of u6 struct on uart X */
+ uart_u6 = kzalloc(sizeof(*uart_u6), GFP_KERNEL);
+ if (!uart_u6)
+ continue;
+
+ if (serial_platform_data[i].irq == IRQ_UART1) {
+ uartClk = clk_get(NULL, "UART1");
+ strcpy(uart_u6->uart_name, "UART1");
+ } else if (serial_platform_data[i].irq == IRQ_UART2) {
+ uartClk = clk_get(NULL, "UART2");
+ strcpy(uart_u6->uart_name, "UART2");
+ }
+ if (!IS_ERR(uartClk) && uartClk != NULL) {
+ uart_u6->uartClk = uartClk;
+ serial_platform_data[i].private_data = uart_u6;
+ /* Clock Uart for autoconfigure detection */
+ clk_enable(uartClk);
+ } else {
+ pr_warning("%s - get uart clock failed error:%ld\n",
+ __func__, PTR_ERR(uartClk));
+ kfree(uart_u6);
+ continue;
+ }
+ }
+ return platform_device_register(&serial_device);
+}
+
+arch_initcall(u6_serial_init);
--
1.7.1
More information about the linux-arm-kernel
mailing list