aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/pcmcia/Kconfig4
-rw-r--r--drivers/pcmcia/Makefile1
-rw-r--r--drivers/pcmcia/bcm63xx_pcmcia.c536
-rw-r--r--drivers/pcmcia/bcm63xx_pcmcia.h60
-rw-r--r--drivers/serial/Kconfig19
-rw-r--r--drivers/serial/Makefile1
-rw-r--r--drivers/serial/bcm63xx_uart.c890
7 files changed, 1511 insertions, 0 deletions
diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig
index fbf965b31c1..17f38a781d4 100644
--- a/drivers/pcmcia/Kconfig
+++ b/drivers/pcmcia/Kconfig
@@ -192,6 +192,10 @@ config PCMCIA_AU1X00
tristate "Au1x00 pcmcia support"
depends on SOC_AU1X00 && PCMCIA
+config PCMCIA_BCM63XX
+ tristate "bcm63xx pcmcia support"
+ depends on BCM63XX && PCMCIA
+
config PCMCIA_SA1100
tristate "SA1100 support"
depends on ARM && ARCH_SA1100 && PCMCIA
diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile
index 3247828aa20..a03a38acd77 100644
--- a/drivers/pcmcia/Makefile
+++ b/drivers/pcmcia/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_PCMCIA_SA1111) += sa11xx_core.o sa1111_cs.o
obj-$(CONFIG_M32R_PCC) += m32r_pcc.o
obj-$(CONFIG_M32R_CFC) += m32r_cfc.o
obj-$(CONFIG_PCMCIA_AU1X00) += au1x00_ss.o
+obj-$(CONFIG_PCMCIA_BCM63XX) += bcm63xx_pcmcia.o
obj-$(CONFIG_PCMCIA_VRC4171) += vrc4171_card.o
obj-$(CONFIG_PCMCIA_VRC4173) += vrc4173_cardu.o
obj-$(CONFIG_OMAP_CF) += omap_cf.o
diff --git a/drivers/pcmcia/bcm63xx_pcmcia.c b/drivers/pcmcia/bcm63xx_pcmcia.c
new file mode 100644
index 00000000000..bc88a3b19bb
--- /dev/null
+++ b/drivers/pcmcia/bcm63xx_pcmcia.c
@@ -0,0 +1,536 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+
+#include <bcm63xx_regs.h>
+#include <bcm63xx_io.h>
+#include "bcm63xx_pcmcia.h"
+
+#define PFX "bcm63xx_pcmcia: "
+
+#ifdef CONFIG_CARDBUS
+/* if cardbus is used, platform device needs reference to actual pci
+ * device */
+static struct pci_dev *bcm63xx_cb_dev;
+#endif
+
+/*
+ * read/write helper for pcmcia regs
+ */
+static inline u32 pcmcia_readl(struct bcm63xx_pcmcia_socket *skt, u32 off)
+{
+ return bcm_readl(skt->base + off);
+}
+
+static inline void pcmcia_writel(struct bcm63xx_pcmcia_socket *skt,
+ u32 val, u32 off)
+{
+ bcm_writel(val, skt->base + off);
+}
+
+/*
+ * This callback should (re-)initialise the socket, turn on status
+ * interrupts and PCMCIA bus, and wait for power to stabilise so that
+ * the card status signals report correctly.
+ *
+ * Hardware cannot do that.
+ */
+static int bcm63xx_pcmcia_sock_init(struct pcmcia_socket *sock)
+{
+ return 0;
+}
+
+/*
+ * This callback should remove power on the socket, disable IRQs from
+ * the card, turn off status interrupts, and disable the PCMCIA bus.
+ *
+ * Hardware cannot do that.
+ */
+static int bcm63xx_pcmcia_suspend(struct pcmcia_socket *sock)
+{
+ return 0;
+}
+
+/*
+ * Implements the set_socket() operation for the in-kernel PCMCIA
+ * service (formerly SS_SetSocket in Card Services). We more or
+ * less punt all of this work and let the kernel handle the details
+ * of power configuration, reset, &c. We also record the value of
+ * `state' in order to regurgitate it to the PCMCIA core later.
+ */
+static int bcm63xx_pcmcia_set_socket(struct pcmcia_socket *sock,
+ socket_state_t *state)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+ unsigned long flags;
+ u32 val;
+
+ skt = sock->driver_data;
+
+ spin_lock_irqsave(&skt->lock, flags);
+
+ /* note: hardware cannot control socket power, so we will
+ * always report SS_POWERON */
+
+ /* apply socket reset */
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+ if (state->flags & SS_RESET)
+ val |= PCMCIA_C1_RESET_MASK;
+ else
+ val &= ~PCMCIA_C1_RESET_MASK;
+
+ /* reverse reset logic for cardbus card */
+ if (skt->card_detected && (skt->card_type & CARD_CARDBUS))
+ val ^= PCMCIA_C1_RESET_MASK;
+
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+
+ /* keep requested state for event reporting */
+ skt->requested_state = *state;
+
+ spin_unlock_irqrestore(&skt->lock, flags);
+
+ return 0;
+}
+
+/*
+ * identity cardtype from VS[12] input, CD[12] input while only VS2 is
+ * floating, and CD[12] input while only VS1 is floating
+ */
+enum {
+ IN_VS1 = (1 << 0),
+ IN_VS2 = (1 << 1),
+ IN_CD1_VS2H = (1 << 2),
+ IN_CD2_VS2H = (1 << 3),
+ IN_CD1_VS1H = (1 << 4),
+ IN_CD2_VS1H = (1 << 5),
+};
+
+static const u8 vscd_to_cardtype[] = {
+
+ /* VS1 float, VS2 float */
+ [IN_VS1 | IN_VS2] = (CARD_PCCARD | CARD_5V),
+
+ /* VS1 grounded, VS2 float */
+ [IN_VS2] = (CARD_PCCARD | CARD_5V | CARD_3V),
+
+ /* VS1 grounded, VS2 grounded */
+ [0] = (CARD_PCCARD | CARD_5V | CARD_3V | CARD_XV),
+
+ /* VS1 tied to CD1, VS2 float */
+ [IN_VS1 | IN_VS2 | IN_CD1_VS1H] = (CARD_CARDBUS | CARD_3V),
+
+ /* VS1 grounded, VS2 tied to CD2 */
+ [IN_VS2 | IN_CD2_VS2H] = (CARD_CARDBUS | CARD_3V | CARD_XV),
+
+ /* VS1 tied to CD2, VS2 grounded */
+ [IN_VS1 | IN_CD2_VS1H] = (CARD_CARDBUS | CARD_3V | CARD_XV | CARD_YV),
+
+ /* VS1 float, VS2 grounded */
+ [IN_VS1] = (CARD_PCCARD | CARD_XV),
+
+ /* VS1 float, VS2 tied to CD2 */
+ [IN_VS1 | IN_VS2 | IN_CD2_VS2H] = (CARD_CARDBUS | CARD_3V),
+
+ /* VS1 float, VS2 tied to CD1 */
+ [IN_VS1 | IN_VS2 | IN_CD1_VS2H] = (CARD_CARDBUS | CARD_XV | CARD_YV),
+
+ /* VS1 tied to CD2, VS2 float */
+ [IN_VS1 | IN_VS2 | IN_CD2_VS1H] = (CARD_CARDBUS | CARD_YV),
+
+ /* VS2 grounded, VS1 is tied to CD1, CD2 is grounded */
+ [IN_VS1 | IN_CD1_VS1H] = 0, /* ignore cardbay */
+};
+
+/*
+ * poll hardware to check card insertion status
+ */
+static unsigned int __get_socket_status(struct bcm63xx_pcmcia_socket *skt)
+{
+ unsigned int stat;
+ u32 val;
+
+ stat = 0;
+
+ /* check CD for card presence */
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+
+ if (!(val & PCMCIA_C1_CD1_MASK) && !(val & PCMCIA_C1_CD2_MASK))
+ stat |= SS_DETECT;
+
+ /* if new insertion, detect cardtype */
+ if ((stat & SS_DETECT) && !skt->card_detected) {
+ unsigned int stat = 0;
+
+ /* float VS1, float VS2 */
+ val |= PCMCIA_C1_VS1OE_MASK;
+ val |= PCMCIA_C1_VS2OE_MASK;
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+
+ /* wait for output to stabilize and read VS[12] */
+ udelay(10);
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+ stat |= (val & PCMCIA_C1_VS1_MASK) ? IN_VS1 : 0;
+ stat |= (val & PCMCIA_C1_VS2_MASK) ? IN_VS2 : 0;
+
+ /* drive VS1 low, float VS2 */
+ val &= ~PCMCIA_C1_VS1OE_MASK;
+ val |= PCMCIA_C1_VS2OE_MASK;
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+
+ /* wait for output to stabilize and read CD[12] */
+ udelay(10);
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+ stat |= (val & PCMCIA_C1_CD1_MASK) ? IN_CD1_VS2H : 0;
+ stat |= (val & PCMCIA_C1_CD2_MASK) ? IN_CD2_VS2H : 0;
+
+ /* float VS1, drive VS2 low */
+ val |= PCMCIA_C1_VS1OE_MASK;
+ val &= ~PCMCIA_C1_VS2OE_MASK;
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+
+ /* wait for output to stabilize and read CD[12] */
+ udelay(10);
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+ stat |= (val & PCMCIA_C1_CD1_MASK) ? IN_CD1_VS1H : 0;
+ stat |= (val & PCMCIA_C1_CD2_MASK) ? IN_CD2_VS1H : 0;
+
+ /* guess cardtype from all this */
+ skt->card_type = vscd_to_cardtype[stat];
+ if (!skt->card_type)
+ dev_err(&skt->socket.dev, "unsupported card type\n");
+
+ /* drive both VS pin to 0 again */
+ val &= ~(PCMCIA_C1_VS1OE_MASK | PCMCIA_C1_VS2OE_MASK);
+
+ /* enable correct logic */
+ val &= ~(PCMCIA_C1_EN_PCMCIA_MASK | PCMCIA_C1_EN_CARDBUS_MASK);
+ if (skt->card_type & CARD_PCCARD)
+ val |= PCMCIA_C1_EN_PCMCIA_MASK;
+ else
+ val |= PCMCIA_C1_EN_CARDBUS_MASK;
+
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+ }
+ skt->card_detected = (stat & SS_DETECT) ? 1 : 0;
+
+ /* report card type/voltage */
+ if (skt->card_type & CARD_CARDBUS)
+ stat |= SS_CARDBUS;
+ if (skt->card_type & CARD_3V)
+ stat |= SS_3VCARD;
+ if (skt->card_type & CARD_XV)
+ stat |= SS_XVCARD;
+ stat |= SS_POWERON;
+
+ if (gpio_get_value(skt->pd->ready_gpio))
+ stat |= SS_READY;
+
+ return stat;
+}
+
+/*
+ * core request to get current socket status
+ */
+static int bcm63xx_pcmcia_get_status(struct pcmcia_socket *sock,
+ unsigned int *status)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+
+ skt = sock->driver_data;
+
+ spin_lock_bh(&skt->lock);
+ *status = __get_socket_status(skt);
+ spin_unlock_bh(&skt->lock);
+
+ return 0;
+}
+
+/*
+ * socket polling timer callback
+ */
+static void bcm63xx_pcmcia_poll(unsigned long data)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+ unsigned int stat, events;
+
+ skt = (struct bcm63xx_pcmcia_socket *)data;
+
+ spin_lock_bh(&skt->lock);
+
+ stat = __get_socket_status(skt);
+
+ /* keep only changed bits, and mask with required one from the
+ * core */
+ events = (stat ^ skt->old_status) & skt->requested_state.csc_mask;
+ skt->old_status = stat;
+ spin_unlock_bh(&skt->lock);
+
+ if (events)
+ pcmcia_parse_events(&skt->socket, events);
+
+ mod_timer(&skt->timer,
+ jiffies + msecs_to_jiffies(BCM63XX_PCMCIA_POLL_RATE));
+}
+
+static int bcm63xx_pcmcia_set_io_map(struct pcmcia_socket *sock,
+ struct pccard_io_map *map)
+{
+ /* this doesn't seem to be called by pcmcia layer if static
+ * mapping is used */
+ return 0;
+}
+
+static int bcm63xx_pcmcia_set_mem_map(struct pcmcia_socket *sock,
+ struct pccard_mem_map *map)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+ struct resource *res;
+
+ skt = sock->driver_data;
+ if (map->flags & MAP_ATTRIB)
+ res = skt->attr_res;
+ else
+ res = skt->common_res;
+
+ map->static_start = res->start + map->card_start;
+ return 0;
+}
+
+static struct pccard_operations bcm63xx_pcmcia_operations = {
+ .init = bcm63xx_pcmcia_sock_init,
+ .suspend = bcm63xx_pcmcia_suspend,
+ .get_status = bcm63xx_pcmcia_get_status,
+ .set_socket = bcm63xx_pcmcia_set_socket,
+ .set_io_map = bcm63xx_pcmcia_set_io_map,
+ .set_mem_map = bcm63xx_pcmcia_set_mem_map,
+};
+
+/*
+ * register pcmcia socket to core
+ */
+static int __devinit bcm63xx_drv_pcmcia_probe(struct platform_device *pdev)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+ struct pcmcia_socket *sock;
+ struct resource *res, *irq_res;
+ unsigned int regmem_size = 0, iomem_size = 0;
+ u32 val;
+ int ret;
+
+ skt = kzalloc(sizeof(*skt), GFP_KERNEL);
+ if (!skt)
+ return -ENOMEM;
+ spin_lock_init(&skt->lock);
+ sock = &skt->socket;
+ sock->driver_data = skt;
+
+ /* make sure we have all resources we need */
+ skt->common_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ skt->attr_res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ skt->pd = pdev->dev.platform_data;
+ if (!skt->common_res || !skt->attr_res || !irq_res || !skt->pd) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* remap pcmcia registers */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regmem_size = resource_size(res);
+ if (!request_mem_region(res->start, regmem_size, "bcm63xx_pcmcia")) {
+ ret = -EINVAL;
+ goto err;
+ }
+ skt->reg_res = res;
+
+ skt->base = ioremap(res->start, regmem_size);
+ if (!skt->base) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* remap io registers */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+ iomem_size = resource_size(res);
+ skt->io_base = ioremap(res->start, iomem_size);
+ if (!skt->io_base) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* resources are static */
+ sock->resource_ops = &pccard_static_ops;
+ sock->ops = &bcm63xx_pcmcia_operations;
+ sock->owner = THIS_MODULE;
+ sock->dev.parent = &pdev->dev;
+ sock->features = SS_CAP_STATIC_MAP | SS_CAP_PCCARD;
+ sock->io_offset = (unsigned long)skt->io_base;
+ sock->pci_irq = irq_res->start;
+
+#ifdef CONFIG_CARDBUS
+ sock->cb_dev = bcm63xx_cb_dev;
+ if (bcm63xx_cb_dev)
+ sock->features |= SS_CAP_CARDBUS;
+#endif
+
+ /* assume common & attribute memory have the same size */
+ sock->map_size = resource_size(skt->common_res);
+
+ /* initialize polling timer */
+ setup_timer(&skt->timer, bcm63xx_pcmcia_poll, (unsigned long)skt);
+
+ /* initialize pcmcia control register, drive VS[12] to 0,
+ * leave CB IDSEL to the old value since it is set by the PCI
+ * layer */
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+ val &= PCMCIA_C1_CBIDSEL_MASK;
+ val |= PCMCIA_C1_EN_PCMCIA_GPIO_MASK;
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+
+ /*
+ * Hardware has only one set of timings registers, not one for
+ * each memory access type, so we configure them for the
+ * slowest one: attribute memory.
+ */
+ val = PCMCIA_C2_DATA16_MASK;
+ val |= 10 << PCMCIA_C2_RWCOUNT_SHIFT;
+ val |= 6 << PCMCIA_C2_INACTIVE_SHIFT;
+ val |= 3 << PCMCIA_C2_SETUP_SHIFT;
+ val |= 3 << PCMCIA_C2_HOLD_SHIFT;
+ pcmcia_writel(skt, val, PCMCIA_C2_REG);
+
+ ret = pcmcia_register_socket(sock);
+ if (ret)
+ goto err;
+
+ /* start polling socket */
+ mod_timer(&skt->timer,
+ jiffies + msecs_to_jiffies(BCM63XX_PCMCIA_POLL_RATE));
+
+ platform_set_drvdata(pdev, skt);
+ return 0;
+
+err:
+ if (skt->io_base)
+ iounmap(skt->io_base);
+ if (skt->base)
+ iounmap(skt->base);
+ if (skt->reg_res)
+ release_mem_region(skt->reg_res->start, regmem_size);
+ kfree(skt);
+ return ret;
+}
+
+static int __devexit bcm63xx_drv_pcmcia_remove(struct platform_device *pdev)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+ struct resource *res;
+
+ skt = platform_get_drvdata(pdev);
+ del_timer_sync(&skt->timer);
+ iounmap(skt->base);
+ iounmap(skt->io_base);
+ res = skt->reg_res;
+ release_mem_region(res->start, resource_size(res));
+ kfree(skt);
+ return 0;
+}
+
+struct platform_driver bcm63xx_pcmcia_driver = {
+ .probe = bcm63xx_drv_pcmcia_probe,
+ .remove = __devexit_p(bcm63xx_drv_pcmcia_remove),
+ .driver = {
+ .name = "bcm63xx_pcmcia",
+ .owner = THIS_MODULE,
+ },
+};
+
+#ifdef CONFIG_CARDBUS
+static int __devinit bcm63xx_cb_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ /* keep pci device */
+ bcm63xx_cb_dev = dev;
+ return platform_driver_register(&bcm63xx_pcmcia_driver);
+}
+
+static void __devexit bcm63xx_cb_exit(struct pci_dev *dev)
+{
+ platform_driver_unregister(&bcm63xx_pcmcia_driver);
+ bcm63xx_cb_dev = NULL;
+}
+
+static struct pci_device_id bcm63xx_cb_table[] = {
+ {
+ .vendor = PCI_VENDOR_ID_BROADCOM,
+ .device = BCM6348_CPU_ID,
+ .subvendor = PCI_VENDOR_ID_BROADCOM,
+ .subdevice = PCI_ANY_ID,
+ .class = PCI_CLASS_BRIDGE_CARDBUS << 8,
+ .class_mask = ~0,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_BROADCOM,
+ .device = BCM6358_CPU_ID,
+ .subvendor = PCI_VENDOR_ID_BROADCOM,
+ .subdevice = PCI_ANY_ID,
+ .class = PCI_CLASS_BRIDGE_CARDBUS << 8,
+ .class_mask = ~0,
+ },
+
+ { },
+};
+
+MODULE_DEVICE_TABLE(pci, bcm63xx_cb_table);
+
+static struct pci_driver bcm63xx_cardbus_driver = {
+ .name = "bcm63xx_cardbus",
+ .id_table = bcm63xx_cb_table,
+ .probe = bcm63xx_cb_probe,
+ .remove = __devexit_p(bcm63xx_cb_exit),
+};
+#endif
+
+/*
+ * if cardbus support is enabled, register our platform device after
+ * our fake cardbus bridge has been registered
+ */
+static int __init bcm63xx_pcmcia_init(void)
+{
+#ifdef CONFIG_CARDBUS
+ return pci_register_driver(&bcm63xx_cardbus_driver);
+#else
+ return platform_driver_register(&bcm63xx_pcmcia_driver);
+#endif
+}
+
+static void __exit bcm63xx_pcmcia_exit(void)
+{
+#ifdef CONFIG_CARDBUS
+ return pci_unregister_driver(&bcm63xx_cardbus_driver);
+#else
+ platform_driver_unregister(&bcm63xx_pcmcia_driver);
+#endif
+}
+
+module_init(bcm63xx_pcmcia_init);
+module_exit(bcm63xx_pcmcia_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>");
+MODULE_DESCRIPTION("Linux PCMCIA Card Services: bcm63xx Socket Controller");
diff --git a/drivers/pcmcia/bcm63xx_pcmcia.h b/drivers/pcmcia/bcm63xx_pcmcia.h
new file mode 100644
index 00000000000..ed957399d86
--- /dev/null
+++ b/drivers/pcmcia/bcm63xx_pcmcia.h
@@ -0,0 +1,60 @@
+#ifndef BCM63XX_PCMCIA_H_
+#define BCM63XX_PCMCIA_H_
+
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <pcmcia/ss.h>
+#include <bcm63xx_dev_pcmcia.h>
+
+/* socket polling rate in ms */
+#define BCM63XX_PCMCIA_POLL_RATE 500
+
+enum {
+ CARD_CARDBUS = (1 << 0),
+ CARD_PCCARD = (1 << 1),
+ CARD_5V = (1 << 2),
+ CARD_3V = (1 << 3),
+ CARD_XV = (1 << 4),
+ CARD_YV = (1 << 5),
+};
+
+struct bcm63xx_pcmcia_socket {
+ struct pcmcia_socket socket;
+
+ /* platform specific data */
+ struct bcm63xx_pcmcia_platform_data *pd;
+
+ /* all regs access are protected by this spinlock */
+ spinlock_t lock;
+
+ /* pcmcia registers resource */
+ struct resource *reg_res;
+
+ /* base remapped address of registers */
+ void __iomem *base;
+
+ /* whether a card is detected at the moment */
+ int card_detected;
+
+ /* type of detected card (mask of above enum) */
+ u8 card_type;
+
+ /* keep last socket status to implement event reporting */
+ unsigned int old_status;
+
+ /* backup of requested socket state */
+ socket_state_t requested_state;
+
+ /* timer used for socket status polling */
+ struct timer_list timer;
+
+ /* attribute/common memory resources */
+ struct resource *attr_res;
+ struct resource *common_res;
+ struct resource *io_res;
+
+ /* base address of io memory */
+ void __iomem *io_base;
+};
+
+#endif /* BCM63XX_PCMCIA_H_ */
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 03422ce878c..e70712044a7 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -1458,4 +1458,23 @@ config SERIAL_TIMBERDALE
---help---
Add support for UART controller on timberdale.
+config SERIAL_BCM63XX
+ tristate "bcm63xx serial port support"
+ select SERIAL_CORE
+ depends on BCM63XX
+ help
+ If you have a bcm63xx CPU, you can enable its onboard
+ serial port by enabling this options.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bcm963xx_uart.
+
+config SERIAL_BCM63XX_CONSOLE
+ bool "Console on bcm63xx serial port"
+ depends on SERIAL_BCM63XX=y
+ select SERIAL_CORE_CONSOLE
+ help
+ If you have enabled the serial port on the bcm63xx CPU
+ you can make it the console by answering Y to this option.
+
endmenu
diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
index 97f6fcc8b43..d21d5dd5d04 100644
--- a/drivers/serial/Makefile
+++ b/drivers/serial/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
obj-$(CONFIG_SERIAL_PXA) += pxa.o
obj-$(CONFIG_SERIAL_PNX8XXX) += pnx8xxx_uart.o
obj-$(CONFIG_SERIAL_SA1100) += sa1100.o
+obj-$(CONFIG_SERIAL_BCM63XX) += bcm63xx_uart.o
obj-$(CONFIG_SERIAL_BFIN) += bfin_5xx.o
obj-$(CONFIG_SERIAL_BFIN_SPORT) += bfin_sport_uart.o
obj-$(CONFIG_SERIAL_SAMSUNG) += samsung.o
diff --git a/drivers/serial/bcm63xx_uart.c b/drivers/serial/bcm63xx_uart.c
new file mode 100644
index 00000000000..beddaa6e906
--- /dev/null
+++ b/drivers/serial/bcm63xx_uart.c
@@ -0,0 +1,890 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Derived from many drivers using generic_serial interface.
+ *
+ * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
+ *
+ * Serial driver for BCM63xx integrated UART.
+ *
+ * Hardware flow control was _not_ tested since I only have RX/TX on
+ * my board.
+ */
+
+#if defined(CONFIG_SERIAL_BCM63XX_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
+#define SUPPORT_SYSRQ
+#endif
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/console.h>
+#include <linux/clk.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/sysrq.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+
+#include <bcm63xx_clk.h>
+#include <bcm63xx_irq.h>
+#include <bcm63xx_regs.h>
+#include <bcm63xx_io.h>
+
+#define BCM63XX_NR_UARTS 1
+
+static struct uart_port ports[BCM63XX_NR_UARTS];
+
+/*
+ * rx interrupt mask / stat
+ *
+ * mask:
+ * - rx fifo full
+ * - rx fifo above threshold
+ * - rx fifo not empty for too long
+ */
+#define UART_RX_INT_MASK (UART_IR_MASK(UART_IR_RXOVER) | \
+ UART_IR_MASK(UART_IR_RXTHRESH) | \
+ UART_IR_MASK(UART_IR_RXTIMEOUT))
+
+#define UART_RX_INT_STAT (UART_IR_STAT(UART_IR_RXOVER) | \
+ UART_IR_STAT(UART_IR_RXTHRESH) | \
+ UART_IR_STAT(UART_IR_RXTIMEOUT))
+
+/*
+ * tx interrupt mask / stat
+ *
+ * mask:
+ * - tx fifo empty
+ * - tx fifo below threshold
+ */
+#define UART_TX_INT_MASK (UART_IR_MASK(UART_IR_TXEMPTY) | \
+ UART_IR_MASK(UART_IR_TXTRESH))
+
+#define UART_TX_INT_STAT (UART_IR_STAT(UART_IR_TXEMPTY) | \
+ UART_IR_STAT(UART_IR_TXTRESH))
+
+/*
+ * external input interrupt
+ *
+ * mask: any edge on CTS, DCD
+ */
+#define UART_EXTINP_INT_MASK (UART_EXTINP_IRMASK(UART_EXTINP_IR_CTS) | \
+ UART_EXTINP_IRMASK(UART_EXTINP_IR_DCD))
+
+/*
+ * handy uart register accessor
+ */
+static inline unsigned int bcm_uart_readl(struct uart_port *port,
+ unsigned int offset)
+{
+ return bcm_readl(port->membase + offset);
+}
+
+static inline void bcm_uart_writel(struct uart_port *port,
+ unsigned int value, unsigned int offset)
+{
+ bcm_writel(value, port->membase + offset);
+}
+
+/*
+ * serial core request to check if uart tx fifo is empty
+ */
+static unsigned int bcm_uart_tx_empty(struct uart_port *port)
+{
+ unsigned int val;
+
+ val = bcm_uart_readl(port, UART_IR_REG);
+ return (val & UART_IR_STAT(UART_IR_TXEMPTY)) ? 1 : 0;
+}
+
+/*
+ * serial core request to set RTS and DTR pin state and loopback mode
+ */
+static void bcm_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+ unsigned int val;
+
+ val = bcm_uart_readl(port, UART_MCTL_REG);
+ val &= ~(UART_MCTL_DTR_MASK | UART_MCTL_RTS_MASK);
+ /* invert of written value is reflected on the pin */
+ if (!(mctrl & TIOCM_DTR))
+ val |= UART_MCTL_DTR_MASK;
+ if (!(mctrl & TIOCM_RTS))
+ val |= UART_MCTL_RTS_MASK;
+ bcm_uart_writel(port, val, UART_MCTL_REG);
+
+ val = bcm_uart_readl(port, UART_CTL_REG);
+ if (mctrl & TIOCM_LOOP)
+ val |= UART_CTL_LOOPBACK_MASK;
+ else
+ val &= ~UART_CTL_LOOPBACK_MASK;
+ bcm_uart_writel(port, val, UART_CTL_REG);
+}
+
+/*
+ * serial core request to return RI, CTS, DCD and DSR pin state
+ */
+static unsigned int bcm_uart_get_mctrl(struct uart_port *port)
+{
+ unsigned int val, mctrl;
+
+ mctrl = 0;
+ val = bcm_uart_readl(port, UART_EXTINP_REG);
+ if (val & UART_EXTINP_RI_MASK)
+ mctrl |= TIOCM_RI;
+ if (val & UART_EXTINP_CTS_MASK)
+ mctrl |= TIOCM_CTS;
+ if (val & UART_EXTINP_DCD_MASK)
+ mctrl |= TIOCM_CD;
+ if (val & UART_EXTINP_DSR_MASK)
+ mctrl |= TIOCM_DSR;
+ return mctrl;
+}
+
+/*
+ * serial core request to disable tx ASAP (used for flow control)
+ */
+static void bcm_uart_stop_tx(struct uart_port *port)
+{
+ unsigned int val;
+
+ val = bcm_uart_readl(port, UART_CTL_REG);
+ val &= ~(UART_CTL_TXEN_MASK);
+ bcm_uart_writel(port, val, UART_CTL_REG);
+
+ val = bcm_uart_readl(port, UART_IR_REG);
+ val &= ~UART_TX_INT_MASK;
+ bcm_uart_writel(port, val, UART_IR_REG);
+}
+
+/*
+ * serial core request to (re)enable tx
+ */
+static void bcm_uart_start_tx(struct uart_port *port)
+{
+ unsigned int val;
+
+ val = bcm_uart_readl(port, UART_IR_REG);
+ val |= UART_TX_INT_MASK;
+ bcm_uart_writel(port, val, UART_IR_REG);
+
+ val = bcm_uart_readl(port, UART_CTL_REG);
+ val |= UART_CTL_TXEN_MASK;
+ bcm_uart_writel(port, val, UART_CTL_REG);
+}
+
+/*
+ * serial core request to stop rx, called before port shutdown
+ */
+static void bcm_uart_stop_rx(struct uart_port *port)
+{
+ unsigned int val;
+
+ val = bcm_uart_readl(port, UART_IR_REG);
+ val &= ~UART_RX_INT_MASK;
+ bcm_uart_writel(port, val, UART_IR_REG);
+}
+
+/*
+ * serial core request to enable modem status interrupt reporting
+ */
+static void bcm_uart_enable_ms(struct uart_port *port)
+{
+ unsigned int val;
+
+ val = bcm_uart_readl(port, UART_IR_REG);
+ val |= UART_IR_MASK(UART_IR_EXTIP);
+ bcm_uart_writel(port, val, UART_IR_REG);
+}
+
+/*
+ * serial core request to start/stop emitting break char
+ */
+static void bcm_uart_break_ctl(struct uart_port *port, int ctl)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ val = bcm_uart_readl(port, UART_CTL_REG);
+ if (ctl)
+ val |= UART_CTL_XMITBRK_MASK;
+ else
+ val &= ~UART_CTL_XMITBRK_MASK;
+ bcm_uart_writel(port, val, UART_CTL_REG);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+/*
+ * return port type in string format
+ */
+static const char *bcm_uart_type(struct uart_port *port)
+{
+ return (port->type == PORT_BCM63XX) ? "bcm63xx_uart" : NULL;
+}
+
+/*
+ * read all chars in rx fifo and send them to core
+ */
+static void bcm_uart_do_rx(struct uart_port *port)
+{
+ struct tty_struct *tty;
+ unsigned int max_count;
+
+ /* limit number of char read in interrupt, should not be
+ * higher than fifo size anyway since we're much faster than
+ * serial port */
+ max_count = 32;
+ tty = port->info->port.tty;
+ do {
+ unsigned int iestat, c, cstat;
+ char flag;
+
+ /* get overrun/fifo empty information from ier
+ * register */
+ iestat = bcm_uart_readl(port, UART_IR_REG);
+ if (!(iestat & UART_IR_STAT(UART_IR_RXNOTEMPTY)))
+ break;
+
+ cstat = c = bcm_uart_readl(port, UART_FIFO_REG);
+ port->icount.rx++;
+ flag = TTY_NORMAL;
+ c &= 0xff;
+
+ if (unlikely((cstat & UART_FIFO_ANYERR_MASK))) {
+ /* do stats first */
+ if (cstat & UART_FIFO_BRKDET_MASK) {
+ port->icount.brk++;
+ if (uart_handle_break(port))
+ continue;
+ }
+
+ if (cstat & UART_FIFO_PARERR_MASK)
+ port->icount.parity++;
+ if (cstat & UART_FIFO_FRAMEERR_MASK)
+ port->icount.frame++;
+
+ /* update flag wrt read_status_mask */
+ cstat &= port->read_status_mask;
+ if (cstat & UART_FIFO_BRKDET_MASK)
+ flag = TTY_BREAK;
+ if (cstat & UART_FIFO_FRAMEERR_MASK)
+ flag = TTY_FRAME;
+ if (cstat & UART_FIFO_PARERR_MASK)
+ flag = TTY_PARITY;
+ }
+
+ if (uart_handle_sysrq_char(port, c))
+ continue;
+
+ if (unlikely(iestat & UART_IR_STAT(UART_IR_RXOVER))) {
+ port->icount.overrun++;
+ tty_insert_flip_char(tty, 0, TTY_OVERRUN);
+ }
+
+ if ((cstat & port->ignore_status_mask) == 0)
+ tty_insert_flip_char(tty, c, flag);
+
+ } while (--max_count);
+
+ tty_flip_buffer_push(tty);
+}
+
+/*
+ * fill tx fifo with chars to send, stop when fifo is about to be full
+ * or when all chars have been sent.
+ */
+static void bcm_uart_do_tx(struct uart_port *port)
+{
+ struct circ_buf *xmit;
+ unsigned int val, max_count;
+
+ if (port->x_char) {
+ bcm_uart_writel(port, port->x_char, UART_FIFO_REG);
+ port->icount.tx++;
+ port->x_char = 0;
+ return;
+ }
+
+ if (uart_tx_stopped(port)) {
+ bcm_uart_stop_tx(port);
+ return;
+ }
+
+ xmit = &port->info->xmit;
+ if (uart_circ_empty(xmit))
+ goto txq_empty;
+
+ val = bcm_uart_readl(port, UART_MCTL_REG);
+ val = (val & UART_MCTL_TXFIFOFILL_MASK) >> UART_MCTL_TXFIFOFILL_SHIFT;
+ max_count = port->fifosize - val;
+
+ while (max_count--) {
+ unsigned int c;
+
+ c = xmit->buf[xmit->tail];
+ bcm_uart_writel(port, c, UART_FIFO_REG);
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+ port->icount.tx++;
+ if (uart_circ_empty(xmit))
+ break;
+ }
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(port);
+
+ if (uart_circ_empty(xmit))
+ goto txq_empty;
+ return;
+
+txq_empty:
+ /* nothing to send, disable transmit interrupt */
+ val = bcm_uart_readl(port, UART_IR_REG);
+ val &= ~UART_TX_INT_MASK;
+ bcm_uart_writel(port, val, UART_IR_REG);
+ return;
+}
+
+/*
+ * process uart interrupt
+ */
+static irqreturn_t bcm_uart_interrupt(int irq, void *dev_id)
+{
+ struct uart_port *port;
+ unsigned int irqstat;
+
+ port = dev_id;
+ spin_lock(&port->lock);
+
+ irqstat = bcm_uart_readl(port, UART_IR_REG);
+ if (irqstat & UART_RX_INT_STAT)
+ bcm_uart_do_rx(port);
+
+ if (irqstat & UART_TX_INT_STAT)
+ bcm_uart_do_tx(port);
+
+ if (irqstat & UART_IR_MASK(UART_IR_EXTIP)) {
+ unsigned int estat;
+
+ estat = bcm_uart_readl(port, UART_EXTINP_REG);
+ if (estat & UART_EXTINP_IRSTAT(UART_EXTINP_IR_CTS))
+ uart_handle_cts_change(port,
+ estat & UART_EXTINP_CTS_MASK);
+ if (estat & UART_EXTINP_IRSTAT(UART_EXTINP_IR_DCD))
+ uart_handle_dcd_change(port,
+ estat & UART_EXTINP_DCD_MASK);
+ }
+
+ spin_unlock(&port->lock);
+ return IRQ_HANDLED;
+}
+
+/*
+ * enable rx & tx operation on uart
+ */
+static void bcm_uart_enable(struct uart_port *port)
+{
+ unsigned int val;
+
+ val = bcm_uart_readl(port, UART_CTL_REG);
+ val |= (UART_CTL_BRGEN_MASK | UART_CTL_TXEN_MASK | UART_CTL_RXEN_MASK);
+ bcm_uart_writel(port, val, UART_CTL_REG);
+}
+
+/*
+ * disable rx & tx operation on uart
+ */
+static void bcm_uart_disable(struct uart_port *port)
+{
+ unsigned int val;
+
+ val = bcm_uart_readl(port, UART_CTL_REG);
+ val &= ~(UART_CTL_BRGEN_MASK | UART_CTL_TXEN_MASK |
+ UART_CTL_RXEN_MASK);
+ bcm_uart_writel(port, val, UART_CTL_REG);
+}
+
+/*
+ * clear all unread data in rx fifo and unsent data in tx fifo
+ */
+static void bcm_uart_flush(struct uart_port *port)
+{
+ unsigned int val;
+
+ /* empty rx and tx fifo */
+ val = bcm_uart_readl(port, UART_CTL_REG);
+ val |= UART_CTL_RSTRXFIFO_MASK | UART_CTL_RSTTXFIFO_MASK;
+ bcm_uart_writel(port, val, UART_CTL_REG);
+
+ /* read any pending char to make sure all irq status are
+ * cleared */
+ (void)bcm_uart_readl(port, UART_FIFO_REG);
+}
+
+/*
+ * serial core request to initialize uart and start rx operation
+ */
+static int bcm_uart_startup(struct uart_port *port)
+{
+ unsigned int val;
+ int ret;
+
+ /* mask all irq and flush port */
+ bcm_uart_disable(port);
+ bcm_uart_writel(port, 0, UART_IR_REG);
+ bcm_uart_flush(port);
+
+ /* clear any pending external input interrupt */
+ (void)bcm_uart_readl(port, UART_EXTINP_REG);
+
+ /* set rx/tx fifo thresh to fifo half size */
+ val = bcm_uart_readl(port, UART_MCTL_REG);
+ val &= ~(UART_MCTL_RXFIFOTHRESH_MASK | UART_MCTL_TXFIFOTHRESH_MASK);
+ val |= (port->fifosize / 2) << UART_MCTL_RXFIFOTHRESH_SHIFT;
+ val |= (port->fifosize / 2) << UART_MCTL_TXFIFOTHRESH_SHIFT;
+ bcm_uart_writel(port, val, UART_MCTL_REG);
+
+ /* set rx fifo timeout to 1 char time */
+ val = bcm_uart_readl(port, UART_CTL_REG);
+ val &= ~UART_CTL_RXTMOUTCNT_MASK;
+ val |= 1 << UART_CTL_RXTMOUTCNT_SHIFT;
+ bcm_uart_writel(port, val, UART_CTL_REG);
+
+ /* report any edge on dcd and cts */
+ val = UART_EXTINP_INT_MASK;
+ val |= UART_EXTINP_DCD_NOSENSE_MASK;
+ val |= UART_EXTINP_CTS_NOSENSE_MASK;
+ bcm_uart_writel(port, val, UART_EXTINP_REG);
+
+ /* register irq and enable rx interrupts */
+ ret = request_irq(port->irq, bcm_uart_interrupt, 0,
+ bcm_uart_type(port), port);
+ if (ret)
+ return ret;
+ bcm_uart_writel(port, UART_RX_INT_MASK, UART_IR_REG);
+ bcm_uart_enable(port);
+ return 0;
+}
+
+/*
+ * serial core request to flush & disable uart
+ */
+static void bcm_uart_shutdown(struct uart_port *port)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ bcm_uart_writel(port, 0, UART_IR_REG);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ bcm_uart_disable(port);
+ bcm_uart_flush(port);
+ free_irq(port->irq, port);
+}
+
+/*
+ * serial core request to change current uart setting
+ */
+static void bcm_uart_set_termios(struct uart_port *port,
+ struct ktermios *new,
+ struct ktermios *old)
+{
+ unsigned int ctl, baud, quot, ier;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ /* disable uart while changing speed */
+ bcm_uart_disable(port);
+ bcm_uart_flush(port);
+
+ /* update Control register */
+ ctl = bcm_uart_readl(port, UART_CTL_REG);
+ ctl &= ~UART_CTL_BITSPERSYM_MASK;
+
+ switch (new->c_cflag & CSIZE) {
+ case CS5:
+ ctl |= (0 << UART_CTL_BITSPERSYM_SHIFT);
+ break;
+ case CS6:
+ ctl |= (1 << UART_CTL_BITSPERSYM_SHIFT);
+ break;
+ case CS7:
+ ctl |= (2 << UART_CTL_BITSPERSYM_SHIFT);
+ break;
+ default:
+ ctl |= (3 << UART_CTL_BITSPERSYM_SHIFT);
+ break;
+ }
+
+ ctl &= ~UART_CTL_STOPBITS_MASK;
+ if (new->c_cflag & CSTOPB)
+ ctl |= UART_CTL_STOPBITS_2;
+ else
+ ctl |= UART_CTL_STOPBITS_1;
+
+ ctl &= ~(UART_CTL_RXPAREN_MASK | UART_CTL_TXPAREN_MASK);
+ if (new->c_cflag & PARENB)
+ ctl |= (UART_CTL_RXPAREN_MASK | UART_CTL_TXPAREN_MASK);
+ ctl &= ~(UART_CTL_RXPAREVEN_MASK | UART_CTL_TXPAREVEN_MASK);
+ if (new->c_cflag & PARODD)
+ ctl |= (UART_CTL_RXPAREVEN_MASK | UART_CTL_TXPAREVEN_MASK);
+ bcm_uart_writel(port, ctl, UART_CTL_REG);
+
+ /* update Baudword register */
+ baud = uart_get_baud_rate(port, new, old, 0, port->uartclk / 16);
+ quot = uart_get_divisor(port, baud) - 1;
+ bcm_uart_writel(port, quot, UART_BAUD_REG);
+
+ /* update Interrupt register */
+ ier = bcm_uart_readl(port, UART_IR_REG);
+
+ ier &= ~UART_IR_MASK(UART_IR_EXTIP);
+ if (UART_ENABLE_MS(port, new->c_cflag))
+ ier |= UART_IR_MASK(UART_IR_EXTIP);
+
+ bcm_uart_writel(port, ier, UART_IR_REG);
+
+ /* update read/ignore mask */
+ port->read_status_mask = UART_FIFO_VALID_MASK;
+ if (new->c_iflag & INPCK) {
+ port->read_status_mask |= UART_FIFO_FRAMEERR_MASK;
+ port->read_status_mask |= UART_FIFO_PARERR_MASK;
+ }
+ if (new->c_iflag & (BRKINT))
+ port->read_status_mask |= UART_FIFO_BRKDET_MASK;
+
+ port->ignore_status_mask = 0;
+ if (new->c_iflag & IGNPAR)
+ port->ignore_status_mask |= UART_FIFO_PARERR_MASK;
+ if (new->c_iflag & IGNBRK)
+ port->ignore_status_mask |= UART_FIFO_BRKDET_MASK;
+ if (!(new->c_cflag & CREAD))
+ port->ignore_status_mask |= UART_FIFO_VALID_MASK;
+
+ uart_update_timeout(port, new->c_cflag, baud);
+ bcm_uart_enable(port);
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+/*
+ * serial core request to claim uart iomem
+ */
+static int bcm_uart_request_port(struct uart_port *port)
+{
+ unsigned int size;
+
+ size = RSET_UART_SIZE;
+ if (!request_mem_region(port->mapbase, size, "bcm63xx")) {
+ dev_err(port->dev, "Memory region busy\n");
+ return -EBUSY;
+ }
+
+ port->membase = ioremap(port->mapbase, size);
+ if (!port->membase) {
+ dev_err(port->dev, "Unable to map registers\n");
+ release_mem_region(port->mapbase, size);
+ return -EBUSY;
+ }
+ return 0;
+}
+
+/*
+ * serial core request to release uart iomem
+ */
+static void bcm_uart_release_port(struct uart_port *port)
+{
+ release_mem_region(port->mapbase, RSET_UART_SIZE);
+ iounmap(port->membase);
+}
+
+/*
+ * serial core request to do any port required autoconfiguration
+ */
+static void bcm_uart_config_port(struct uart_port *port, int flags)
+{
+ if (flags & UART_CONFIG_TYPE) {
+ if (bcm_uart_request_port(port))
+ return;
+ port->type = PORT_BCM63XX;
+ }
+}
+
+/*
+ * serial core request to check that port information in serinfo are
+ * suitable
+ */
+static int bcm_uart_verify_port(struct uart_port *port,
+ struct serial_struct *serinfo)
+{
+ if (port->type != PORT_BCM63XX)
+ return -EINVAL;
+ if (port->irq != serinfo->irq)
+ return -EINVAL;
+ if (port->iotype != serinfo->io_type)
+ return -EINVAL;
+ if (port->mapbase != (unsigned long)serinfo->iomem_base)
+ return -EINVAL;
+ return 0;
+}
+
+/* serial core callbacks */
+static struct uart_ops bcm_uart_ops = {
+ .tx_empty = bcm_uart_tx_empty,
+ .get_mctrl = bcm_uart_get_mctrl,
+ .set_mctrl = bcm_uart_set_mctrl,
+ .start_tx = bcm_uart_start_tx,
+ .stop_tx = bcm_uart_stop_tx,
+ .stop_rx = bcm_uart_stop_rx,
+ .enable_ms = bcm_uart_enable_ms,
+ .break_ctl = bcm_uart_break_ctl,
+ .startup = bcm_uart_startup,
+ .shutdown = bcm_uart_shutdown,
+ .set_termios = bcm_uart_set_termios,
+ .type = bcm_uart_type,
+ .release_port = bcm_uart_release_port,
+ .request_port = bcm_uart_request_port,
+ .config_port = bcm_uart_config_port,
+ .verify_port = bcm_uart_verify_port,
+};
+
+
+
+#ifdef CONFIG_SERIAL_BCM63XX_CONSOLE
+static inline void wait_for_xmitr(struct uart_port *port)
+{
+ unsigned int tmout;
+
+ /* Wait up to 10ms for the character(s) to be sent. */
+ tmout = 10000;
+ while (--tmout) {
+ unsigned int val;
+
+ val = bcm_uart_readl(port, UART_IR_REG);
+ if (val & UART_IR_STAT(UART_IR_TXEMPTY))
+ break;
+ udelay(1);
+ }
+
+ /* Wait up to 1s for flow control if necessary */
+ if (port->flags & UPF_CONS_FLOW) {
+ tmout = 1000000;
+ while (--tmout) {
+ unsigned int val;
+
+ val = bcm_uart_readl(port, UART_EXTINP_REG);
+ if (val & UART_EXTINP_CTS_MASK)
+ break;
+ udelay(1);
+ }
+ }
+}
+
+/*
+ * output given char
+ */
+static void bcm_console_putchar(struct uart_port *port, int ch)
+{
+ wait_for_xmitr(port);
+ bcm_uart_writel(port, ch, UART_FIFO_REG);
+}
+
+/*
+ * console core request to output given string
+ */
+static void bcm_console_write(struct console *co, const char *s,
+ unsigned int count)
+{
+ struct uart_port *port;
+ unsigned long flags;
+ int locked;
+
+ port = &ports[co->index];
+
+ local_irq_save(flags);
+ if (port->sysrq) {
+ /* bcm_uart_interrupt() already took the lock */
+ locked = 0;
+ } else if (oops_in_progress) {
+ locked = spin_trylock(&port->lock);
+ } else {
+ spin_lock(&port->lock);
+ locked = 1;
+ }
+
+ /* call helper to deal with \r\n */
+ uart_console_write(port, s, count, bcm_console_putchar);
+
+ /* and wait for char to be transmitted */
+ wait_for_xmitr(port);
+
+ if (locked)
+ spin_unlock(&port->lock);
+ local_irq_restore(flags);
+}
+
+/*
+ * console core request to setup given console, find matching uart
+ * port and setup it.
+ */
+static int bcm_console_setup(struct console *co, char *options)
+{
+ struct uart_port *port;
+ int baud = 9600;
+ int bits = 8;
+ int parity = 'n';
+ int flow = 'n';
+
+ if (co->index < 0 || co->index >= BCM63XX_NR_UARTS)
+ return -EINVAL;
+ port = &ports[co->index];
+ if (!port->membase)
+ 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 bcm_uart_driver;
+
+static struct console bcm63xx_console = {
+ .name = "ttyS",
+ .write = bcm_console_write,
+ .device = uart_console_device,
+ .setup = bcm_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+ .data = &bcm_uart_driver,
+};
+
+static int __init bcm63xx_console_init(void)
+{
+ register_console(&bcm63xx_console);
+ return 0;
+}
+
+console_initcall(bcm63xx_console_init);
+
+#define BCM63XX_CONSOLE (&bcm63xx_console)
+#else
+#define BCM63XX_CONSOLE NULL
+#endif /* CONFIG_SERIAL_BCM63XX_CONSOLE */
+
+static struct uart_driver bcm_uart_driver = {
+ .owner = THIS_MODULE,
+ .driver_name = "bcm63xx_uart",
+ .dev_name = "ttyS",
+ .major = TTY_MAJOR,
+ .minor = 64,
+ .nr = 1,
+ .cons = BCM63XX_CONSOLE,
+};
+
+/*
+ * platform driver probe/remove callback
+ */
+static int __devinit bcm_uart_probe(struct platform_device *pdev)
+{
+ struct resource *res_mem, *res_irq;
+ struct uart_port *port;
+ struct clk *clk;
+ int ret;
+
+ if (pdev->id < 0 || pdev->id >= BCM63XX_NR_UARTS)
+ return -EINVAL;
+
+ if (ports[pdev->id].membase)
+ return -EBUSY;
+
+ res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res_mem)
+ return -ENODEV;
+
+ res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res_irq)
+ return -ENODEV;
+
+ clk = clk_get(&pdev->dev, "periph");
+ if (IS_ERR(clk))
+ return -ENODEV;
+
+ port = &ports[pdev->id];
+ memset(port, 0, sizeof(*port));
+ port->iotype = UPIO_MEM;
+ port->mapbase = res_mem->start;
+ port->irq = res_irq->start;
+ port->ops = &bcm_uart_ops;
+ port->flags = UPF_BOOT_AUTOCONF;
+ port->dev = &pdev->dev;
+ port->fifosize = 16;
+ port->uartclk = clk_get_rate(clk) / 2;
+ clk_put(clk);
+
+ ret = uart_add_one_port(&bcm_uart_driver, port);
+ if (ret) {
+ kfree(port);
+ return ret;
+ }
+ platform_set_drvdata(pdev, port);
+ return 0;
+}
+
+static int __devexit bcm_uart_remove(struct platform_device *pdev)
+{
+ struct uart_port *port;
+
+ port = platform_get_drvdata(pdev);
+ uart_remove_one_port(&bcm_uart_driver, port);
+ platform_set_drvdata(pdev, NULL);
+ /* mark port as free */
+ ports[pdev->id].membase = 0;
+ return 0;
+}
+
+/*
+ * platform driver stuff
+ */
+static struct platform_driver bcm_uart_platform_driver = {
+ .probe = bcm_uart_probe,
+ .remove = __devexit_p(bcm_uart_remove),
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "bcm63xx_uart",
+ },
+};
+
+static int __init bcm_uart_init(void)
+{
+ int ret;
+
+ ret = uart_register_driver(&bcm_uart_driver);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&bcm_uart_platform_driver);
+ if (ret)
+ uart_unregister_driver(&bcm_uart_driver);
+
+ return ret;
+}
+
+static void __exit bcm_uart_exit(void)
+{
+ platform_driver_unregister(&bcm_uart_platform_driver);
+ uart_unregister_driver(&bcm_uart_driver);
+}
+
+module_init(bcm_uart_init);
+module_exit(bcm_uart_exit);
+
+MODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>");
+MODULE_DESCRIPTION("Broadcom 63<xx integrated uart driver");
+MODULE_LICENSE("GPL");