summaryrefslogtreecommitdiffstats
path: root/drivers/serial
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2013-04-04 23:23:19 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2013-04-04 23:23:19 +0200
commit0a637bcc3c66c647f52970ddd1dbae2d749dc969 (patch)
tree55ccc56a785cac9a3b990130f70316869ee77b47 /drivers/serial
parentdd9f6d08a2736bf8ddc0004779c563a4c3a77cef (diff)
parentc6ef15b3879607a9e31964fc8942f95c253122f9 (diff)
downloadbarebox-0a637bcc3c66c647f52970ddd1dbae2d749dc969.tar.gz
barebox-0a637bcc3c66c647f52970ddd1dbae2d749dc969.tar.xz
Merge branch 'for-next/zynq'
Conflicts: arch/arm/Makefile
Diffstat (limited to 'drivers/serial')
-rw-r--r--drivers/serial/Kconfig5
-rw-r--r--drivers/serial/Makefile1
-rw-r--r--drivers/serial/serial_cadence.c307
3 files changed, 313 insertions, 0 deletions
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 48bea603e8..11fc155074 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -117,4 +117,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 4a23aefddb..93790b5349 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -16,3 +16,4 @@ 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_AUART) += serial_auart.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 0000000000..c29c391023
--- /dev/null
+++ b/drivers/serial/serial_cadence.c
@@ -0,0 +1,307 @@
+/*
+ * (c) 2012 Steffen Trumtrar <s.trumtrar@pengutronix.de>
+ *
+ * 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 <common.h>
+#include <driver.h>
+#include <init.h>
+#include <malloc.h>
+#include <notifier.h>
+#include <io.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+
+#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);