From mboxrd@z Thu Jan 1 00:00:00 1970 Return-path: Received: from metis.ext.pengutronix.de ([2001:6f8:1178:4:290:27ff:fe1d:cc33]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1UKPv5-00037I-Dj for barebox@lists.infradead.org; Tue, 26 Mar 2013 09:11:53 +0000 From: Steffen Trumtrar Date: Tue, 26 Mar 2013 10:11:22 +0100 Message-Id: <1364289086-2241-2-git-send-email-s.trumtrar@pengutronix.de> In-Reply-To: <1364289086-2241-1-git-send-email-s.trumtrar@pengutronix.de> References: <1364289086-2241-1-git-send-email-s.trumtrar@pengutronix.de> List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: "barebox" Errors-To: barebox-bounces+u.kleine-koenig=pengutronix.de@lists.infradead.org Subject: [PATCH v3 1/5] serial: Add driver for Cadence UART To: barebox@lists.infradead.org Cc: Steffen Trumtrar Support for Cadence UART core. Signed-off-by: Steffen Trumtrar --- Changes since v2: - remove superfluous Kconfig option - add help text to Kconfig option drivers/serial/Kconfig | 5 + drivers/serial/Makefile | 1 + drivers/serial/serial_cadence.c | 307 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 drivers/serial/serial_cadence.c diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index f61d670..73cb9f8 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -113,4 +113,9 @@ config DRIVER_SERIAL_OMAP4_USBBOOT help Enable this to get console support over the usb bus used to boot an OMAP4 +config DRIVER_SERIAL_CADENCE + bool "Cadence UART driver" + help + Say Y here if you have a Cadence serial IP core. + endmenu diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 893e282..963a7df 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_DRIVER_SERIAL_ALTERA) += serial_altera.o obj-$(CONFIG_DRIVER_SERIAL_ALTERA_JTAG) += serial_altera_jtag.o obj-$(CONFIG_DRIVER_SERIAL_PXA) += serial_pxa.o obj-$(CONFIG_DRIVER_SERIAL_OMAP4_USBBOOT) += serial_omap4_usbboot.o +obj-$(CONFIG_DRIVER_SERIAL_CADENCE) += serial_cadence.o diff --git a/drivers/serial/serial_cadence.c b/drivers/serial/serial_cadence.c new file mode 100644 index 0000000..c29c391 --- /dev/null +++ b/drivers/serial/serial_cadence.c @@ -0,0 +1,307 @@ +/* + * (c) 2012 Steffen Trumtrar + * + * 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. + * + * 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. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define CADENCE_UART_CONTROL 0x00 +#define CADENCE_UART_MODE 0x04 +#define CADENCE_UART_BAUD_GEN 0x18 +#define CADENCE_UART_CHANNEL_STS 0x2C +#define CADENCE_UART_RXTXFIFO 0x30 +#define CADENCE_UART_BAUD_DIV 0x34 + +#define CADENCE_CTRL_RXRES (1 << 0) +#define CADENCE_CTRL_TXRES (1 << 1) +#define CADENCE_CTRL_RXEN (1 << 2) +#define CADENCE_CTRL_RXDIS (1 << 3) +#define CADENCE_CTRL_TXEN (1 << 4) +#define CADENCE_CTRL_TXDIS (1 << 5) +#define CADENCE_CTRL_RSTTO (1 << 6) +#define CADENCE_CTRL_STTBRK (1 << 7) +#define CADENCE_CTRL_STPBRK (1 << 8) + +#define CADENCE_MODE_CLK_REF (0 << 0) +#define CADENCE_MODE_CLK_REF_DIV (1 << 0) +#define CADENCE_MODE_CHRL_6 (3 << 1) +#define CADENCE_MODE_CHRL_7 (2 << 1) +#define CADENCE_MODE_CHRL_8 (0 << 1) +#define CADENCE_MODE_PAR_EVEN (0 << 3) +#define CADENCE_MODE_PAR_ODD (1 << 3) +#define CADENCE_MODE_PAR_SPACE (2 << 3) +#define CADENCE_MODE_PAR_MARK (3 << 3) +#define CADENCE_MODE_PAR_NONE (4 << 3) + +#define CADENCE_STS_REMPTY (1 << 1) +#define CADENCE_STS_RFUL (1 << 2) +#define CADENCE_STS_TEMPTY (1 << 3) +#define CADENCE_STS_TFUL (1 << 4) + +/* + * create default values for different platforms + */ +struct cadence_serial_devtype_data { + u32 ctrl; + u32 mode; +}; + +static struct cadence_serial_devtype_data cadence_r1p08_data = { + .ctrl = CADENCE_CTRL_RXEN | CADENCE_CTRL_TXEN, + .mode = CADENCE_MODE_CLK_REF | CADENCE_MODE_CHRL_8 | CADENCE_MODE_PAR_NONE, +}; + +struct cadence_serial_priv { + struct console_device cdev; + int baudrate; + struct notifier_block notify; + void __iomem *regs; + struct clk *clk; + struct cadence_serial_devtype_data *devtype; +}; + +static int cadence_serial_reset(struct console_device *cdev) +{ + struct cadence_serial_priv *priv = container_of(cdev, + struct cadence_serial_priv, cdev); + + /* Soft-Reset Tx/Rx paths */ + writel(CADENCE_CTRL_RXRES | CADENCE_CTRL_TXRES, priv->regs + + CADENCE_UART_CONTROL); + + while (readl(priv->regs + CADENCE_UART_CONTROL) & + (CADENCE_CTRL_RXRES | CADENCE_CTRL_TXRES)) + ; + + return 0; +} + +static int cadence_serial_setbaudrate(struct console_device *cdev, int baudrate) +{ + struct cadence_serial_priv *priv = container_of(cdev, + struct cadence_serial_priv, cdev); + unsigned int gen, div; + int calc_rate; + unsigned long clk; + int error; + int val; + + clk = clk_get_rate(priv->clk); + priv->baudrate = baudrate; + + /* disable transmitter and receiver */ + val = readl(priv->regs + CADENCE_UART_CONTROL); + val &= ~CADENCE_CTRL_TXEN & ~CADENCE_CTRL_RXEN; + writel(val, priv->regs + CADENCE_UART_CONTROL); + + /* + * clk + * rate = ----------- + * gen*(div+1) + */ + + for (div = 4; div < 256; div++) { + gen = clk / (baudrate * (div + 1)); + + if (gen < 1 || gen > 65535) + continue; + + calc_rate = clk / (gen * (div + 1)); + error = baudrate - calc_rate; + if (error < 0) + error *= -1; + if (((error * 100) / baudrate) < 3) + break; + } + + writel(gen, priv->regs + CADENCE_UART_BAUD_GEN); + writel(div, priv->regs + CADENCE_UART_BAUD_DIV); + + /* Soft-Reset Tx/Rx paths */ + writel(CADENCE_CTRL_RXRES | CADENCE_CTRL_TXRES, priv->regs + + CADENCE_UART_CONTROL); + + while (readl(priv->regs + CADENCE_UART_CONTROL) & + (CADENCE_CTRL_RXRES | CADENCE_CTRL_TXRES)) + ; + + /* Enable UART */ + writel(priv->devtype->ctrl, priv->regs + CADENCE_UART_CONTROL); + + return 0; +} + +static int cadence_serial_init_port(struct console_device *cdev) +{ + struct cadence_serial_priv *priv = container_of(cdev, + struct cadence_serial_priv, cdev); + + cadence_serial_reset(cdev); + + /* Enable UART */ + writel(priv->devtype->ctrl, priv->regs + CADENCE_UART_CONTROL); + writel(priv->devtype->mode, priv->regs + CADENCE_UART_MODE); + + return 0; +} + +static void cadence_serial_putc(struct console_device *cdev, char c) +{ + struct cadence_serial_priv *priv = container_of(cdev, + struct cadence_serial_priv, cdev); + + while ((readl(priv->regs + CADENCE_UART_CHANNEL_STS) & + CADENCE_STS_TFUL) != 0) + ; + + writel(c, priv->regs + CADENCE_UART_RXTXFIFO); +} + +static int cadence_serial_tstc(struct console_device *cdev) +{ + struct cadence_serial_priv *priv = container_of(cdev, + struct cadence_serial_priv, cdev); + + return ((readl(priv->regs + CADENCE_UART_CHANNEL_STS) & + CADENCE_STS_REMPTY) == 0); +} + +static int cadence_serial_getc(struct console_device *cdev) +{ + struct cadence_serial_priv *priv = container_of(cdev, + struct cadence_serial_priv, cdev); + + while (!cadence_serial_tstc(cdev)) + ; + + return readl(priv->regs + CADENCE_UART_RXTXFIFO); +} + +static void cadence_serial_flush(struct console_device *cdev) +{ + struct cadence_serial_priv *priv = container_of(cdev, + struct cadence_serial_priv, cdev); + + while ((readl(priv->regs + CADENCE_UART_CHANNEL_STS) & + CADENCE_STS_TEMPTY) != 0) + ; +} + +static int cadence_clocksource_clock_change(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cadence_serial_priv *priv = container_of(nb, + struct cadence_serial_priv, notify); + + cadence_serial_setbaudrate(&priv->cdev, priv->baudrate); + + return 0; +} + +static int cadence_serial_probe(struct device_d *dev) +{ + struct console_device *cdev; + struct cadence_serial_priv *priv; + struct cadence_serial_devtype_data *devtype; + int ret; + + ret = dev_get_drvdata(dev, (unsigned long *)&devtype); + if (ret) + return ret; + + priv = xzalloc(sizeof(*priv)); + priv->devtype = devtype; + cdev = &priv->cdev; + dev->priv = priv; + + priv->clk = clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + ret = -ENODEV; + goto err_free; + } + + if (devtype->mode & CADENCE_MODE_CLK_REF_DIV) + clk_set_rate(priv->clk, clk_get_rate(priv->clk) / 8); + + priv->regs = dev_request_mem_region(dev, 0); + if (!priv->regs) { + ret = -EBUSY; + goto err_free; + } + + cdev->dev = dev; + cdev->f_caps = CONSOLE_STDIN | CONSOLE_STDOUT | CONSOLE_STDERR; + cdev->tstc = cadence_serial_tstc; + cdev->putc = cadence_serial_putc; + cdev->getc = cadence_serial_getc; + cdev->flush = cadence_serial_flush; + cdev->setbrg = cadence_serial_setbaudrate; + + cadence_serial_init_port(cdev); + + console_register(cdev); + priv->notify.notifier_call = cadence_clocksource_clock_change; + clock_register_client(&priv->notify); + + return 0; + +err_free: + free(priv); + return ret; +} + +static void cadence_serial_remove(struct device_d *dev) +{ + struct cadence_serial_priv *priv = dev->priv; + + console_unregister(&priv->cdev); + free(priv); +} + +static __maybe_unused struct of_device_id cadence_serial_dt_ids[] = { + { + .compatible = "xlnx,xuartps", + .data = (unsigned long)&cadence_r1p08_data, + }, { + /* sentinel */ + } +}; + +static struct platform_device_id cadence_serial_ids[] = { + { + .name = "cadence-uart", + .driver_data = (unsigned long)&cadence_r1p08_data, + }, { + /* sentinel */ + }, +}; + +static struct driver_d cadence_serial_driver = { + .name = "cadence_serial", + .probe = cadence_serial_probe, + .remove = cadence_serial_remove, + .of_compatible = DRV_OF_COMPAT(cadence_serial_dt_ids), + .id_table = cadence_serial_ids, +}; + +static int cadence_serial_init(void) +{ + return platform_driver_register(&cadence_serial_driver); +} +console_initcall(cadence_serial_init); -- 1.8.2.rc2 _______________________________________________ barebox mailing list barebox@lists.infradead.org http://lists.infradead.org/mailman/listinfo/barebox