linux-mips
[Top] [All Lists]

[PATCH 4/8] add support for the TI VLYNQ bus

To: Andrew Morton <akpm@linux-foundation.org>, "Linux-MIPS" <linux-mips@linux-mips.org>, Ralf Baechle <ralf@linux-mips.org>, linux-kernel@vger.kernel.org
Subject: [PATCH 4/8] add support for the TI VLYNQ bus
From: Florian Fainelli <florian@openwrt.org>
Date: Thu, 4 Jun 2009 16:17:51 +0200
Dkim-signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:sender:from:date:subject :mime-version:x-uid:x-length:to:content-type :content-transfer-encoding:content-disposition:message-id; bh=nDkEXiuNoDsDqoV0b/UE9K+MZ8PzkxxgNDZkiXzaZh0=; b=PWl/nLIuo+RfSkcnGg9h9KQEPxj67nF/27xsENgvNpnyQ60pvrk0C/HK3u2Qc3B3RO mFlWZvCMty91ECe/5EpTj3y/OH8fGP1aRotCJ0Ek+K2KCOVve7AYs3zp77E4RuPSJ4FL 3AJeWnE5p4H6FzB+j831Epx5cJYm4v35Sp3iI=
Domainkey-signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=sender:from:date:subject:mime-version:x-uid:x-length:to :content-type:content-transfer-encoding:content-disposition :message-id; b=C/Yx0k2KcI0BVBS4sjDfQZYLhms+LTikDw2tbRpls6sYSoKSVyuwPpgpIX/lYBBaGx EAmUlBjSI8dLcavut4Sd1dRXfywme5o/KLBVQQnKW3NMCb+T5c22lgOaUACW38C3Iu9r GOD120BZa1n1GBT7e9foeaGT/QgfTrF8yncv8=
Original-recipient: rfc822;linux-mips@linux-mips.org
Sender: linux-mips-bounce@linux-mips.org
This patch adds support for the TI VLYNQ high-speed,
serial and packetized bus. This bus allows external
devices to be connected to the System-on-Chip and
appear in the main system memory just like any memory
mapped peripheral. It is widely used in TI's networking
and mutlimedia SoC, including the AR7 SoC.

Changes from v1:
- added link to TI's datasheet
- remove vlynq_read/write_reg accessors to use readl/writel
- fixed get_device/put_device usage
- added documentation
- fixed needlessly global symbols

Signed-off-by: Eugene Konev <ejka@imfi.kspu.ru>
Signed-off-by: Florian Fainelli <florian@openwrt.org>
---
diff --git a/MAINTAINERS b/MAINTAINERS
index cf4abdd..91928d6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6155,6 +6155,14 @@ F:       drivers/net/macvlan.c
 F:     include/linux/if_*vlan.h
 F:     net/8021q/
 
+VLYNQ BUS
+P:     Florian Fainelli
+M:     florian@openwrt.org
+L:     openwrt-devel@lists.openwrt.org
+S:     Maintained
+F:     drivers/vlynq/vlynq.c
+F:     include/linux/vlynq.h
+
 VOLTAGE AND CURRENT REGULATOR FRAMEWORK
 P:     Liam Girdwood
 M:     lrg@slimlogic.co.uk
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 00cf955..a442c8f 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -104,6 +104,8 @@ source "drivers/auxdisplay/Kconfig"
 
 source "drivers/uio/Kconfig"
 
+source "drivers/vlynq/Kconfig"
+
 source "drivers/xen/Kconfig"
 
 source "drivers/staging/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 1266ead..0d39303 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -105,5 +105,6 @@ obj-$(CONFIG_PPC_PS3)               += ps3/
 obj-$(CONFIG_OF)               += of/
 obj-$(CONFIG_SSB)              += ssb/
 obj-$(CONFIG_VIRTIO)           += virtio/
+obj-$(CONFIG_VLYNQ)            += vlynq/
 obj-$(CONFIG_STAGING)          += staging/
 obj-y                          += platform/
diff --git a/drivers/vlynq/Kconfig b/drivers/vlynq/Kconfig
new file mode 100644
index 0000000..f654221
--- /dev/null
+++ b/drivers/vlynq/Kconfig
@@ -0,0 +1,20 @@
+menu "TI VLYNQ"
+
+config VLYNQ
+       bool "TI VLYNQ bus support"
+       depends on AR7 && EXPERIMENTAL
+       help
+         Support for Texas Instruments(R) VLYNQ bus.
+         The VLYNQ bus is a high-speed, serial and packetized
+         data bus which allows external peripherals of a SoC
+         to appear into the system's main memory.
+
+         If unsure, say N
+
+config VLYNQ_DEBUG
+       bool "VLYNQ bus debug"
+       depends on VLYNQ && KERNEL_DEBUG
+       help
+         Turn on VLYNQ bus debugging.
+
+endmenu
diff --git a/drivers/vlynq/Makefile b/drivers/vlynq/Makefile
new file mode 100644
index 0000000..b3f6114
--- /dev/null
+++ b/drivers/vlynq/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for kernel vlynq drivers
+#
+
+obj-$(CONFIG_VLYNQ) += vlynq.o
diff --git a/drivers/vlynq/vlynq.c b/drivers/vlynq/vlynq.c
new file mode 100644
index 0000000..3b304dc
--- /dev/null
+++ b/drivers/vlynq/vlynq.c
@@ -0,0 +1,814 @@
+/*
+ * Copyright (C) 2006, 2007 Eugene Konev <ejka@openwrt.org>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Parts of the VLYNQ specification can be found here:
+ * http://www.ti.com/litv/pdf/sprue36a 
+ */
+
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+
+#include <linux/vlynq.h>
+
+#define VLYNQ_CTRL_PM_ENABLE           0x80000000
+#define VLYNQ_CTRL_CLOCK_INT           0x00008000
+#define VLYNQ_CTRL_CLOCK_DIV(x)                (((x) & 7) << 16)
+#define VLYNQ_CTRL_INT_LOCAL           0x00004000
+#define VLYNQ_CTRL_INT_ENABLE          0x00002000
+#define VLYNQ_CTRL_INT_VECTOR(x)       (((x) & 0x1f) << 8)
+#define VLYNQ_CTRL_INT2CFG             0x00000080
+#define VLYNQ_CTRL_RESET               0x00000001
+
+#define VLYNQ_CTRL_CLOCK_MASK          (0x7 << 16)
+
+#define VLYNQ_INT_OFFSET               0x00000014
+#define VLYNQ_REMOTE_OFFSET            0x00000080
+
+#define VLYNQ_STATUS_LINK              0x00000001
+#define VLYNQ_STATUS_LERROR            0x00000080
+#define VLYNQ_STATUS_RERROR            0x00000100
+
+#define VINT_ENABLE                    0x00000100
+#define VINT_TYPE_EDGE                 0x00000080
+#define VINT_LEVEL_LOW                 0x00000040
+#define VINT_VECTOR(x)                 ((x) & 0x1f)
+#define VINT_OFFSET(irq)               (8 * ((irq) % 4))
+
+#define VLYNQ_AUTONEGO_V2              0x00010000
+
+struct vlynq_regs {
+       u32 revision;
+       u32 control;
+       u32 status;
+       u32 int_prio;
+       u32 int_status;
+       u32 int_pending;
+       u32 int_ptr;
+       u32 tx_offset;
+       struct vlynq_mapping rx_mapping[4];
+       u32 chip;
+       u32 autonego;
+       u32 unused[6];
+       u32 int_device[8];
+};
+
+#ifdef VLYNQ_DEBUG
+static void vlynq_dump_regs(struct vlynq_device *dev)
+{
+       int i;
+
+       printk(KERN_DEBUG "VLYNQ local=%p remote=%p\n",
+                       dev->local, dev->remote);
+       for (i = 0; i < 32; i++) {
+               printk(KERN_DEBUG "VLYNQ: local %d: %08x\n",
+                       i + 1, ((u32 *)dev->local)[i]);
+               printk(KERN_DEBUG "VLYNQ: remote %d: %08x\n",
+                       i + 1, ((u32 *)dev->remote)[i]);
+       }
+}
+
+static void vlynq_dump_mem(u32 *base, int count)
+{
+       int i;
+
+       for (i = 0; i < (count + 3) / 4; i++) {
+               if (i % 4 == 0)
+                       printk(KERN_DEBUG "\nMEM[0x%04x]:", i * 4);
+               printk(KERN_DEBUG " 0x%08x", *(base + i));
+       }
+       printk(KERN_DEBUG "\n");
+}
+#endif
+
+/* Check the VLYNQ link status with a given device */
+static int vlynq_linked(struct vlynq_device *dev)
+{
+       int i;
+
+       for (i = 0; i < 100; i++)
+               if (readl(&dev->local->status) & VLYNQ_STATUS_LINK)
+                       return 1;
+               else
+                       cpu_relax();
+
+       return 0;
+}
+
+static void vlynq_reset(struct vlynq_device *dev)
+{
+       writel(readl(&dev->local->control) | VLYNQ_CTRL_RESET,
+                       &dev->local->control);
+
+       /* Wait for the devices to finish resetting */
+       msleep(5);
+
+       /* Remove reset bit */
+       writel(readl(&dev->local->control) & ~VLYNQ_CTRL_RESET,
+                       &dev->local->control);
+
+       /* Give some time for the devices to settle */
+       msleep(5);
+}
+
+static void vlynq_irq_unmask(unsigned int irq)
+{
+       u32 val;
+       struct vlynq_device *dev = get_irq_chip_data(irq);
+       int virq;
+
+       BUG_ON(!dev);
+       virq = irq - dev->irq_start;
+       val = readl(&dev->remote->int_device[virq >> 2]);
+       val |= (VINT_ENABLE | virq) << VINT_OFFSET(virq);
+       writel(val, &dev->remote->int_device[virq >> 2]);
+}
+
+static void vlynq_irq_mask(unsigned int irq)
+{
+       u32 val;
+       struct vlynq_device *dev = get_irq_chip_data(irq);
+       int virq;
+
+       BUG_ON(!dev);
+       virq = irq - dev->irq_start;
+       val = readl(&dev->remote->int_device[virq >> 2]);
+       val &= ~(VINT_ENABLE << VINT_OFFSET(virq));
+       writel(val, &dev->remote->int_device[virq >> 2]);
+}
+
+static int vlynq_irq_type(unsigned int irq, unsigned int flow_type)
+{
+       u32 val;
+       struct vlynq_device *dev = get_irq_chip_data(irq);
+       int virq;
+
+       BUG_ON(!dev);
+       virq = irq - dev->irq_start;
+       val = readl(&dev->remote->int_device[virq >> 2]);
+       switch (flow_type & IRQ_TYPE_SENSE_MASK) {
+       case IRQ_TYPE_EDGE_RISING:
+       case IRQ_TYPE_EDGE_FALLING:
+       case IRQ_TYPE_EDGE_BOTH:
+               val |= VINT_TYPE_EDGE << VINT_OFFSET(virq);
+               val &= ~(VINT_LEVEL_LOW << VINT_OFFSET(virq));
+               break;
+       case IRQ_TYPE_LEVEL_HIGH:
+               val &= ~(VINT_TYPE_EDGE << VINT_OFFSET(virq));
+               val &= ~(VINT_LEVEL_LOW << VINT_OFFSET(virq));
+               break;
+       case IRQ_TYPE_LEVEL_LOW:
+               val &= ~(VINT_TYPE_EDGE << VINT_OFFSET(virq));
+               val |= VINT_LEVEL_LOW << VINT_OFFSET(virq);
+               break;
+       default:
+               return -EINVAL;
+       }
+       writel(val, &dev->remote->int_device[virq >> 2]);
+       return 0;
+}
+
+static void vlynq_local_ack(unsigned int irq)
+{
+       struct vlynq_device *dev = get_irq_chip_data(irq);
+
+       u32 status = readl(&dev->local->status);
+
+       pr_debug("%s: local status: 0x%08x\n",
+                      dev_name(&dev->dev), status);
+       writel(status, &dev->local->status);
+}
+
+static void vlynq_remote_ack(unsigned int irq)
+{
+       struct vlynq_device *dev = get_irq_chip_data(irq);
+
+       u32 status = readl(&dev->remote->status);
+
+       pr_debug("%s: remote status: 0x%08x\n",
+                      dev_name(&dev->dev), status);
+       writel(status, &dev->remote->status);
+}
+
+static irqreturn_t vlynq_irq(int irq, void *dev_id)
+{
+       struct vlynq_device *dev = dev_id;
+       u32 status;
+       int virq = 0;
+
+       status = readl(&dev->local->int_status);
+       writel(status, &dev->local->int_status);
+
+       if (unlikely(!status))
+               spurious_interrupt();
+
+       while (status) {
+               if (status & 1)
+                       do_IRQ(dev->irq_start + virq);
+               status >>= 1;
+               virq++;
+       }
+
+       return IRQ_HANDLED;
+}
+
+static struct irq_chip vlynq_irq_chip = {
+       .name = "vlynq",
+       .unmask = vlynq_irq_unmask,
+       .mask = vlynq_irq_mask,
+       .set_type = vlynq_irq_type,
+};
+
+static struct irq_chip vlynq_local_chip = {
+       .name = "vlynq local error",
+       .unmask = vlynq_irq_unmask,
+       .mask = vlynq_irq_mask,
+       .ack = vlynq_local_ack,
+};
+
+static struct irq_chip vlynq_remote_chip = {
+       .name = "vlynq local error",
+       .unmask = vlynq_irq_unmask,
+       .mask = vlynq_irq_mask,
+       .ack = vlynq_remote_ack,
+};
+
+static int vlynq_setup_irq(struct vlynq_device *dev)
+{
+       u32 val;
+       int i, virq;
+
+       if (dev->local_irq == dev->remote_irq) {
+               printk(KERN_ERR
+                      "%s: local vlynq irq should be different from remote\n",
+                      dev_name(&dev->dev));
+               return -EINVAL;
+       }
+
+       /* Clear local and remote error bits */
+       writel(readl(&dev->local->status), &dev->local->status);
+       writel(readl(&dev->remote->status), &dev->remote->status);
+
+       /* Now setup interrupts */
+       val = VLYNQ_CTRL_INT_VECTOR(dev->local_irq);
+       val |= VLYNQ_CTRL_INT_ENABLE | VLYNQ_CTRL_INT_LOCAL |
+               VLYNQ_CTRL_INT2CFG;
+       val |= readl(&dev->local->control);
+       writel(VLYNQ_INT_OFFSET, &dev->local->int_ptr);
+       writel(val, &dev->local->control);
+
+       val = VLYNQ_CTRL_INT_VECTOR(dev->remote_irq);
+       val |= VLYNQ_CTRL_INT_ENABLE;
+       val |= readl(&dev->remote->control);
+       writel(VLYNQ_INT_OFFSET, &dev->remote->int_ptr);
+       writel(val, &dev->remote->int_ptr);
+       writel(val, &dev->remote->control);
+
+       for (i = dev->irq_start; i <= dev->irq_end; i++) {
+               virq = i - dev->irq_start;
+               if (virq == dev->local_irq) {
+                       set_irq_chip_and_handler(i, &vlynq_local_chip,
+                                                handle_level_irq);
+                       set_irq_chip_data(i, dev);
+               } else if (virq == dev->remote_irq) {
+                       set_irq_chip_and_handler(i, &vlynq_remote_chip,
+                                                handle_level_irq);
+                       set_irq_chip_data(i, dev);
+               } else {
+                       set_irq_chip_and_handler(i, &vlynq_irq_chip,
+                                                handle_simple_irq);
+                       set_irq_chip_data(i, dev);
+                       writel(0, &dev->remote->int_device[virq >> 2]);
+               }
+       }
+
+       if (request_irq(dev->irq, vlynq_irq, IRQF_SHARED, "vlynq", dev)) {
+               printk(KERN_ERR "%s: request_irq failed\n",
+                                       dev_name(&dev->dev));
+               return -EAGAIN;
+       }
+
+       return 0;
+}
+
+static void vlynq_device_release(struct device *dev)
+{
+       struct vlynq_device *vdev = to_vlynq_device(dev);
+       kfree(vdev);
+}
+
+static int vlynq_device_match(struct device *dev,
+                             struct device_driver *drv)
+{
+       struct vlynq_device *vdev = to_vlynq_device(dev);
+       struct vlynq_driver *vdrv = to_vlynq_driver(drv);
+       struct vlynq_device_id *ids = vdrv->id_table;
+
+       while (ids->id) {
+               if (ids->id == vdev->dev_id) {
+                       vdev->divisor = ids->divisor;
+                       vlynq_set_drvdata(vdev, ids);
+                       printk(KERN_INFO "Driver found for VLYNQ "
+                               "device: %08x\n", vdev->dev_id);
+                       return 1;
+               }
+               printk(KERN_DEBUG "Not using the %08x VLYNQ device's driver"
+                       " for VLYNQ device: %08x\n", ids->id, vdev->dev_id);
+               ids++;
+       }
+       return 0;
+}
+
+static int vlynq_device_probe(struct device *dev)
+{
+       struct vlynq_device *vdev = to_vlynq_device(dev);
+       struct vlynq_driver *drv = to_vlynq_driver(dev->driver);
+       struct vlynq_device_id *id = vlynq_get_drvdata(vdev);
+       int result = -ENODEV;
+
+       if (drv->probe)
+               result = drv->probe(vdev, id);
+       if (result)
+               put_device(dev);
+       return result;
+}
+
+static int vlynq_device_remove(struct device *dev)
+{
+       struct vlynq_driver *drv = to_vlynq_driver(dev->driver);
+
+       if (drv->remove)
+               drv->remove(to_vlynq_device(dev));
+       
+       return 0;
+}
+
+int __vlynq_register_driver(struct vlynq_driver *driver, struct module *owner)
+{
+       driver->driver.name = driver->name;
+       driver->driver.bus = &vlynq_bus_type;
+       return driver_register(&driver->driver);
+}
+EXPORT_SYMBOL(__vlynq_register_driver);
+
+void vlynq_unregister_driver(struct vlynq_driver *driver)
+{
+       driver_unregister(&driver->driver);
+}
+EXPORT_SYMBOL(vlynq_unregister_driver);
+
+/* 
+ * A VLYNQ remote device can clock the VLYNQ bus master
+ * using a dedicated clock line. In that case, both the
+ * remove device and the bus master should have the same
+ * serial clock dividers configured. Iterate through the
+ * 8 possible dividers until we actually link with the
+ * device.
+ */
+static int __vlynq_try_remote(struct vlynq_device *dev)
+{
+       int i;
+
+       vlynq_reset(dev);
+       for (i = dev->dev_id ? vlynq_rdiv2 : vlynq_rdiv8; dev->dev_id ?
+                       i <= vlynq_rdiv8 : i >= vlynq_rdiv2;
+               dev->dev_id ? i++ : i--) {
+
+               if (!vlynq_linked(dev))
+                       break;
+               
+               writel((readl(&dev->remote->control) &
+                               ~VLYNQ_CTRL_CLOCK_MASK) |
+                               VLYNQ_CTRL_CLOCK_INT |
+                               VLYNQ_CTRL_CLOCK_DIV(i - vlynq_rdiv1),
+                               &dev->remote->control);
+               writel((readl(&dev->local->control)
+                               & ~(VLYNQ_CTRL_CLOCK_INT |
+                               VLYNQ_CTRL_CLOCK_MASK)) |
+                               VLYNQ_CTRL_CLOCK_DIV(i - vlynq_rdiv1),
+                               &dev->local->control);
+
+               if (vlynq_linked(dev)) {
+                       printk(KERN_DEBUG
+                               "%s: using remote clock divisor %d\n",
+                               dev_name(&dev->dev), i - vlynq_rdiv1 + 1);
+                       dev->divisor = i;
+                       return 0;
+               } else {
+                       vlynq_reset(dev);
+               }
+       }
+
+       return -ENODEV;
+}
+
+/* 
+ * A VLYNQ remote device can be clocked by the VLYNQ bus
+ * master using a dedicated clock line. In that case, only
+ * the bus master configures the serial clock divider.
+ * Iterate through the 8 possible dividers until we
+ * actually get a link with the device.
+ */
+static int __vlynq_try_local(struct vlynq_device *dev)
+{
+       int i;
+
+       vlynq_reset(dev);
+
+       for (i = dev->dev_id ? vlynq_ldiv2 : vlynq_ldiv8; dev->dev_id ?
+                       i <= vlynq_ldiv8 : i >= vlynq_ldiv2;
+               dev->dev_id ? i++ : i--) {
+
+               writel((readl(&dev->local->control) &
+                               ~VLYNQ_CTRL_CLOCK_MASK) |
+                               VLYNQ_CTRL_CLOCK_INT |
+                               VLYNQ_CTRL_CLOCK_DIV(i - vlynq_ldiv1),
+                               &dev->local->control);
+
+               if (vlynq_linked(dev)) {
+                       printk(KERN_DEBUG
+                               "%s: using local clock divisor %d\n",
+                               dev_name(&dev->dev), i - vlynq_ldiv1 + 1);
+                       dev->divisor = i;
+                       return 0;
+               } else {
+                       vlynq_reset(dev);
+               }
+       }
+
+       return -ENODEV;
+}
+
+/*
+ * When using external clocking method, serial clock
+ * is supplied by an external oscillator, therefore we
+ * should mask the local clock bit in the clock control
+ * register for both the bus master and the remote device.
+ */
+static int __vlynq_try_external(struct vlynq_device *dev)
+{
+       vlynq_reset(dev);
+       if (!vlynq_linked(dev))
+               return -ENODEV;
+
+       writel((readl(&dev->remote->control) & 
+                       ~VLYNQ_CTRL_CLOCK_INT),
+                       &dev->remote->control);
+
+       writel((readl(&dev->local->control) &
+                       ~VLYNQ_CTRL_CLOCK_INT),
+                       &dev->local->control);
+
+       if (vlynq_linked(dev)) {
+               printk(KERN_DEBUG "%s: using external clock\n",
+                       dev_name(&dev->dev));
+                       dev->divisor = vlynq_div_external;
+               return 0;
+       }
+
+       return -ENODEV;
+}
+
+static int __vlynq_enable_device(struct vlynq_device *dev)
+{
+       int result;
+       struct plat_vlynq_ops *ops = dev->dev.platform_data;
+
+       result = ops->on(dev);
+       if (result)
+               return result;
+
+       switch (dev->divisor) {
+       case vlynq_div_external:
+       case vlynq_div_auto:
+               /* When the device is brought from reset it should have clock
+                * generation negotiated by hardware.
+                * Check which device is generating clocks and perform setup
+                * accordingly */
+               if (vlynq_linked(dev) && readl(&dev->remote->control) &
+                  VLYNQ_CTRL_CLOCK_INT) {
+                       if (!__vlynq_try_remote(dev) ||
+                               !__vlynq_try_local(dev)  ||
+                               !__vlynq_try_external(dev))
+                               return 0;
+               } else {
+                       if (!__vlynq_try_external(dev) ||
+                               !__vlynq_try_local(dev)    ||
+                               !__vlynq_try_remote(dev))
+                               return 0;
+               }
+               break;
+       case vlynq_ldiv1:
+       case vlynq_ldiv2:
+       case vlynq_ldiv3:
+       case vlynq_ldiv4:
+       case vlynq_ldiv5:
+       case vlynq_ldiv6:
+       case vlynq_ldiv7:
+       case vlynq_ldiv8:
+               writel(VLYNQ_CTRL_CLOCK_INT |
+                       VLYNQ_CTRL_CLOCK_DIV(dev->divisor -
+                       vlynq_ldiv1), &dev->local->control);
+               writel(0, &dev->remote->control);
+               if (vlynq_linked(dev)) {
+                       printk(KERN_DEBUG
+                               "%s: using local clock divisor %d\n",
+                               dev_name(&dev->dev),
+                               dev->divisor - vlynq_ldiv1 + 1);
+                       return 0;
+               }
+               break;
+       case vlynq_rdiv1:
+       case vlynq_rdiv2:
+       case vlynq_rdiv3:
+       case vlynq_rdiv4:
+       case vlynq_rdiv5:
+       case vlynq_rdiv6:
+       case vlynq_rdiv7:
+       case vlynq_rdiv8:
+               writel(0, &dev->local->control);
+               writel(VLYNQ_CTRL_CLOCK_INT |
+                       VLYNQ_CTRL_CLOCK_DIV(dev->divisor -
+                       vlynq_rdiv1), &dev->remote->control);
+               if (vlynq_linked(dev)) {
+                       printk(KERN_DEBUG
+                               "%s: using remote clock divisor %d\n",
+                               dev_name(&dev->dev),
+                               dev->divisor - vlynq_rdiv1 + 1);
+                       return 0;
+               }
+               break;
+       }
+
+       ops->off(dev);
+       return -ENODEV;
+}
+
+int vlynq_enable_device(struct vlynq_device *dev)
+{
+       struct plat_vlynq_ops *ops = dev->dev.platform_data;
+       int result = -ENODEV;
+
+       result = __vlynq_enable_device(dev);
+       if (result)
+               return result;
+
+       result = vlynq_setup_irq(dev);
+       if (result)
+               ops->off(dev);
+
+       dev->enabled = !result;
+       return result;
+}
+EXPORT_SYMBOL(vlynq_enable_device);
+
+
+void vlynq_disable_device(struct vlynq_device *dev)
+{
+       struct plat_vlynq_ops *ops = dev->dev.platform_data;
+
+       dev->enabled = 0;
+       free_irq(dev->irq, dev);
+       ops->off(dev);
+}
+EXPORT_SYMBOL(vlynq_disable_device);
+
+int vlynq_set_local_mapping(struct vlynq_device *dev, u32 tx_offset,
+                           struct vlynq_mapping *mapping)
+{
+       int i;
+
+       if (!dev->enabled)
+               return -ENXIO;
+
+       writel(tx_offset, &dev->local->tx_offset);
+       for (i = 0; i < 4; i++) {
+               writel(mapping[i].offset, &dev->local->rx_mapping[i].offset);
+               writel(mapping[i].size, &dev->local->rx_mapping[i].size);
+       }
+       return 0;
+}
+EXPORT_SYMBOL(vlynq_set_local_mapping);
+
+int vlynq_set_remote_mapping(struct vlynq_device *dev, u32 tx_offset,
+                            struct vlynq_mapping *mapping)
+{
+       int i;
+
+       if (!dev->enabled)
+               return -ENXIO;
+
+       writel(tx_offset, &dev->remote->tx_offset);
+       for (i = 0; i < 4; i++) {
+               writel(mapping[i].offset, &dev->remote->rx_mapping[i].offset);
+               writel(mapping[i].size, &dev->remote->rx_mapping[i].size);
+       }
+       return 0;
+}
+EXPORT_SYMBOL(vlynq_set_remote_mapping);
+
+int vlynq_set_local_irq(struct vlynq_device *dev, int virq)
+{
+       int irq = dev->irq_start + virq;
+       if (dev->enabled)
+               return -EBUSY;
+
+       if ((irq < dev->irq_start) || (irq > dev->irq_end))
+               return -EINVAL;
+
+       if (virq == dev->remote_irq)
+               return -EINVAL;
+
+       dev->local_irq = virq;
+
+       return 0;
+}
+EXPORT_SYMBOL(vlynq_set_local_irq);
+
+int vlynq_set_remote_irq(struct vlynq_device *dev, int virq)
+{
+       int irq = dev->irq_start + virq;
+       if (dev->enabled)
+               return -EBUSY;
+
+       if ((irq < dev->irq_start) || (irq > dev->irq_end))
+               return -EINVAL;
+
+       if (virq == dev->local_irq)
+               return -EINVAL;
+
+       dev->remote_irq = virq;
+
+       return 0;
+}
+EXPORT_SYMBOL(vlynq_set_remote_irq);
+
+static int vlynq_probe(struct platform_device *pdev)
+{
+       struct vlynq_device *dev;
+       struct resource *regs_res, *mem_res, *irq_res;
+       int len, result;
+
+       regs_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
+       if (!regs_res)
+               return -ENODEV;
+
+       mem_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mem");
+       if (!mem_res)
+               return -ENODEV;
+
+       irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "devirq");
+       if (!irq_res)
+               return -ENODEV;
+
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (!dev) {
+               printk(KERN_ERR
+                      "vlynq: failed to allocate device structure\n");
+               return -ENOMEM;
+       }
+
+       dev->id = pdev->id;
+       dev->dev.bus = &vlynq_bus_type;
+       dev->dev.parent = &pdev->dev;
+       dev_set_name(&dev->dev, "vlynq%d", dev->id);
+       dev->dev.platform_data = pdev->dev.platform_data;
+       dev->dev.release = vlynq_device_release;
+
+       dev->regs_start = regs_res->start;
+       dev->regs_end = regs_res->end;
+       dev->mem_start = mem_res->start;
+       dev->mem_end = mem_res->end;
+
+       len = regs_res->end - regs_res->start;
+       if (!request_mem_region(regs_res->start, len, dev_name(&dev->dev))) {
+               printk(KERN_ERR "%s: Can't request vlynq registers\n",
+                      dev_name(&dev->dev));
+               result = -ENXIO;
+               goto fail_request;
+       }
+
+       dev->local = ioremap(regs_res->start, len);
+       if (!dev->local) {
+               printk(KERN_ERR "%s: Can't remap vlynq registers\n",
+                      dev_name(&dev->dev));
+               result = -ENXIO;
+               goto fail_remap;
+       }
+
+       dev->remote = (struct vlynq_regs *)((void *)dev->local +
+                                           VLYNQ_REMOTE_OFFSET);
+
+       dev->irq = platform_get_irq_byname(pdev, "irq");
+       dev->irq_start = irq_res->start;
+       dev->irq_end = irq_res->end;
+       dev->local_irq = dev->irq_end - dev->irq_start;
+       dev->remote_irq = dev->local_irq - 1;
+
+       if (device_register(&dev->dev))
+               goto fail_register;
+       platform_set_drvdata(pdev, dev);
+
+       printk(KERN_INFO "%s: regs 0x%p, irq %d, mem 0x%p\n",
+              dev_name(&dev->dev), (void *)dev->regs_start, dev->irq,
+              (void *)dev->mem_start);
+
+       dev->dev_id = 0;
+       dev->divisor = vlynq_div_auto;
+       result = __vlynq_enable_device(dev);
+       if (result == 0) {
+               dev->dev_id = readl(&dev->remote->chip);
+               ((struct plat_vlynq_ops *)(dev->dev.platform_data))->off(dev);
+       }
+       if (dev->dev_id)
+               printk(KERN_INFO "Found a VLYNQ device: %08x\n", dev->dev_id);
+
+       return 0;
+
+fail_register:
+       iounmap(dev->local);
+fail_remap:
+fail_request:
+       release_mem_region(regs_res->start, len);
+       kfree(dev);
+       return result;
+}
+
+static int vlynq_remove(struct platform_device *pdev)
+{
+       struct vlynq_device *dev = platform_get_drvdata(pdev);
+
+       device_unregister(&dev->dev);
+       iounmap(dev->local);
+       release_mem_region(dev->regs_start, dev->regs_end - dev->regs_start);
+
+       kfree(dev);
+
+       return 0;
+}
+
+static struct platform_driver vlynq_platform_driver = {
+       .driver.name = "vlynq",
+       .probe = vlynq_probe,
+       .remove = __devexit_p(vlynq_remove),
+};
+
+struct bus_type vlynq_bus_type = {
+       .name = "vlynq",
+       .match = vlynq_device_match,
+       .probe = vlynq_device_probe,
+       .remove = vlynq_device_remove,
+};
+EXPORT_SYMBOL(vlynq_bus_type);
+
+static int __devinit vlynq_init(void)
+{
+       int res = 0;
+
+       res = bus_register(&vlynq_bus_type);
+       if (res)
+               goto fail_bus;
+
+       res = platform_driver_register(&vlynq_platform_driver);
+       if (res)
+               goto fail_platform;
+
+       return 0;
+
+fail_platform:
+       bus_unregister(&vlynq_bus_type);
+fail_bus:
+       return res;
+}
+
+static void __devexit vlynq_exit(void)
+{
+       platform_driver_unregister(&vlynq_platform_driver);
+       bus_unregister(&vlynq_bus_type);
+}
+
+module_init(vlynq_init);
+module_exit(vlynq_exit);
diff --git a/include/linux/vlynq.h b/include/linux/vlynq.h
new file mode 100644
index 0000000..8f6a958
--- /dev/null
+++ b/include/linux/vlynq.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2006, 2007 Eugene Konev <ejka@openwrt.org>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __VLYNQ_H__
+#define __VLYNQ_H__
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/types.h>
+
+#define VLYNQ_NUM_IRQS 32
+
+struct vlynq_mapping {
+       u32 size;
+       u32 offset;
+};
+
+enum vlynq_divisor {
+       vlynq_div_auto = 0,
+       vlynq_ldiv1,
+       vlynq_ldiv2,
+       vlynq_ldiv3,
+       vlynq_ldiv4,
+       vlynq_ldiv5,
+       vlynq_ldiv6,
+       vlynq_ldiv7,
+       vlynq_ldiv8,
+       vlynq_rdiv1,
+       vlynq_rdiv2,
+       vlynq_rdiv3,
+       vlynq_rdiv4,
+       vlynq_rdiv5,
+       vlynq_rdiv6,
+       vlynq_rdiv7,
+       vlynq_rdiv8,
+       vlynq_div_external
+};
+
+struct vlynq_device_id {
+       u32 id;
+       enum vlynq_divisor divisor;
+       unsigned long driver_data;
+};
+
+struct vlynq_regs;
+struct vlynq_device {
+       u32 id, dev_id;
+       int local_irq;
+       int remote_irq;
+       enum vlynq_divisor divisor;
+       u32 regs_start, regs_end;
+       u32 mem_start, mem_end;
+       u32 irq_start, irq_end;
+       int irq;
+       int enabled;
+       struct vlynq_regs *local;
+       struct vlynq_regs *remote;
+       struct device dev;
+};
+
+struct vlynq_driver {
+       char *name;
+       struct vlynq_device_id *id_table;
+       int (*probe)(struct vlynq_device *dev, struct vlynq_device_id *id);
+       void (*remove)(struct vlynq_device *dev);
+       struct device_driver driver;
+};
+
+struct plat_vlynq_ops {
+       int (*on)(struct vlynq_device *dev);
+       void (*off)(struct vlynq_device *dev);
+};
+
+static inline struct vlynq_driver *to_vlynq_driver(struct device_driver *drv)
+{
+       return container_of(drv, struct vlynq_driver, driver);
+}
+
+static inline struct vlynq_device *to_vlynq_device(struct device *device)
+{
+       return container_of(device, struct vlynq_device, dev);
+}
+
+extern struct bus_type vlynq_bus_type;
+
+extern int __vlynq_register_driver(struct vlynq_driver *driver,
+                                  struct module *owner);
+
+static inline int vlynq_register_driver(struct vlynq_driver *driver)
+{
+       return __vlynq_register_driver(driver, THIS_MODULE);
+}
+
+static inline void *vlynq_get_drvdata(struct vlynq_device *dev)
+{
+       return dev_get_drvdata(&dev->dev);
+}
+
+static inline void vlynq_set_drvdata(struct vlynq_device *dev, void *data)
+{
+       dev_set_drvdata(&dev->dev, data);
+}
+
+static inline u32 vlynq_mem_start(struct vlynq_device *dev)
+{
+       return dev->mem_start;
+}
+
+static inline u32 vlynq_mem_end(struct vlynq_device *dev)
+{
+       return dev->mem_end;
+}
+
+static inline u32 vlynq_mem_len(struct vlynq_device *dev)
+{
+       return dev->mem_end - dev->mem_start + 1;
+}
+
+static inline int vlynq_virq_to_irq(struct vlynq_device *dev, int virq)
+{
+       int irq = dev->irq_start + virq;
+       if ((irq < dev->irq_start) || (irq > dev->irq_end))
+               return -EINVAL;
+
+       return irq;
+}
+
+static inline int vlynq_irq_to_virq(struct vlynq_device *dev, int irq)
+{
+       if ((irq < dev->irq_start) || (irq > dev->irq_end))
+               return -EINVAL;
+
+       return irq - dev->irq_start;
+}
+
+extern void vlynq_unregister_driver(struct vlynq_driver *driver);
+extern int vlynq_enable_device(struct vlynq_device *dev);
+extern void vlynq_disable_device(struct vlynq_device *dev);
+extern int vlynq_set_local_mapping(struct vlynq_device *dev, u32 tx_offset,
+                                  struct vlynq_mapping *mapping);
+extern int vlynq_set_remote_mapping(struct vlynq_device *dev, u32 tx_offset,
+                                   struct vlynq_mapping *mapping);
+extern int vlynq_set_local_irq(struct vlynq_device *dev, int virq);
+extern int vlynq_set_remote_irq(struct vlynq_device *dev, int virq);
+
+#endif /* __VLYNQ_H__ */

<Prev in Thread] Current Thread [Next in Thread>
  • [PATCH 4/8] add support for the TI VLYNQ bus, Florian Fainelli <=