Driver core implementation (driver registration, ...)
Driver types definitions
HSI bus implementation

Signed-off-by: Sebastien Jan <s-...@ti.com>
Signed-off-by: Carlos Chinea <carlos.chi...@nokia.com>
---
 drivers/hsi/hsi_driver.c     |  644 ++++++++++++++++++++++++++++++++++++++++++
 drivers/hsi/hsi_driver.h     |  308 ++++++++++++++++++++
 drivers/hsi/hsi_driver_bus.c |  182 ++++++++++++
 3 files changed, 1134 insertions(+), 0 deletions(-)
 create mode 100644 drivers/hsi/hsi_driver.c
 create mode 100644 drivers/hsi/hsi_driver.h
 create mode 100644 drivers/hsi/hsi_driver_bus.c

diff --git a/drivers/hsi/hsi_driver.c b/drivers/hsi/hsi_driver.c
new file mode 100644
index 0000000..4abaf08
--- /dev/null
+++ b/drivers/hsi/hsi_driver.c
@@ -0,0 +1,644 @@
+/*
+ * hsi_driver.c
+ *
+ * Implements HSI module interface, initialization, and PM related functions.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chi...@nokia.com>
+ * Author: Sebastien JAN <s-...@ti.com>
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/list.h>
+#include <mach/clock.h>
+#include "hsi_driver.h"
+
+#define        HSI_DRIVER_VERSION      "0.1"
+#define HSI_RESETDONE_TIMEOUT  10      /* 10 ms */
+#define HSI_RESETDONE_RETRIES  20      /* => max 200 ms waiting for reset */
+
+/* NOTE: Function called in interrupt context */
+int hsi_port_event_handler(struct hsi_port *p, unsigned int event, void *arg)
+{
+       struct hsi_channel *hsi_channel;
+       int ch;
+
+       if (event == HSI_EVENT_HSR_DATAAVAILABLE) {
+               /* The data-available event is channel-specific and must not be
+                * broadcasted
+                */
+               hsi_channel = p->hsi_channel + (int)arg;
+               read_lock(&hsi_channel->rw_lock);
+               if ((hsi_channel->dev) && (hsi_channel->port_event))
+                       hsi_channel->port_event(hsi_channel->dev, event, arg);
+               read_unlock(&hsi_channel->rw_lock);
+       } else {
+               for (ch = 0; ch < p->max_ch; ch++) {
+                       hsi_channel = p->hsi_channel + ch;
+                       read_lock(&hsi_channel->rw_lock);
+                       if ((hsi_channel->dev) && (hsi_channel->port_event))
+                               hsi_channel->port_event(hsi_channel->dev,
+                                                               event, arg);
+                       read_unlock(&hsi_channel->rw_lock);
+               }
+       }
+       return 0;
+}
+
+static int hsi_clk_event(struct notifier_block *nb, unsigned long event,
+                        void *data)
+{
+/* TODO - implement support for clocks changes
+       switch (event) {
+       case CLK_PRE_RATE_CHANGE:
+               break;
+       case CLK_ABORT_RATE_CHANGE:
+               break;
+       case CLK_POST_RATE_CHANGE:
+               break;
+       default:
+               break;
+       }
+*/
+
+       /*
+        * TODO: At this point we may emit a port event warning about the
+        * clk frequency change to the upper layers.
+        */
+       return NOTIFY_DONE;
+}
+
+static void hsi_dev_release(struct device *dev)
+{
+       /* struct device kfree is already made in unregister_hsi_devices().
+        * Registering this function is necessary to avoid an error from
+        * the device_release() function.
+        */
+}
+
+static int __init reg_hsi_dev_ch(struct hsi_dev *hsi_ctrl, unsigned int p,
+                                unsigned int ch)
+{
+       struct hsi_device *dev;
+       struct hsi_port *port = &hsi_ctrl->hsi_port[p];
+       int err;
+
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+
+       dev->n_ctrl = hsi_ctrl->id;
+       dev->n_p = p;
+       dev->n_ch = ch;
+       dev->ch = &port->hsi_channel[ch];
+       dev->device.bus = &hsi_bus_type;
+       dev->device.parent = hsi_ctrl->dev;
+       dev->device.release = hsi_dev_release;
+       if (dev->n_ctrl < 0)
+               dev_set_name(&dev->device, "omap_hsi-p%u.c%u", p, ch);
+       else
+               dev_set_name(&dev->device, "omap_hsi%d-p%u.c%u", dev->n_ctrl, p,
+                            ch);
+
+       err = device_register(&dev->device);
+       if (err >= 0) {
+               write_lock_bh(&port->hsi_channel[ch].rw_lock);
+               port->hsi_channel[ch].dev = dev;
+               write_unlock_bh(&port->hsi_channel[ch].rw_lock);
+       } else {
+               kfree(dev);
+       }
+       return err;
+}
+
+static int __init register_hsi_devices(struct hsi_dev *hsi_ctrl)
+{
+       int port;
+       int ch;
+       int err;
+
+       for (port = 0; port < hsi_ctrl->max_p; port++)
+               for (ch = 0; ch < hsi_ctrl->hsi_port[port].max_ch; ch++) {
+                       err = reg_hsi_dev_ch(hsi_ctrl, port, ch);
+                       if (err < 0)
+                               return err;
+               }
+
+       return 0;
+}
+
+static int __init hsi_softreset(struct hsi_dev *hsi_ctrl)
+{
+       int ind = 0;
+       void __iomem *base = hsi_ctrl->base;
+       u32 status;
+
+       hsi_outl_or(HSI_SOFTRESET, base, HSI_SYS_SYSCONFIG_REG);
+
+       status = hsi_inl(base, HSI_SYS_SYSSTATUS_REG);
+       while ((!(status & HSI_RESETDONE)) && (ind < HSI_RESETDONE_RETRIES)) {
+               set_current_state(TASK_UNINTERRUPTIBLE);
+               schedule_timeout(msecs_to_jiffies(HSI_RESETDONE_TIMEOUT));
+               status = hsi_inl(base, HSI_SYS_SYSSTATUS_REG);
+               ind++;
+       }
+
+       if (ind >= HSI_RESETDONE_RETRIES)
+               return -EIO;
+
+       /* Reseting GDD */
+       hsi_outl_or(HSI_SWRESET, base, HSI_GDD_GRST_REG);
+
+       return 0;
+}
+
+static void __init set_hsi_ports_default(struct hsi_dev *hsi_ctrl,
+                                        struct platform_device *pd)
+{
+       struct port_ctx *cfg;
+       struct hsi_platform_data *pdata = pd->dev.platform_data;
+       unsigned int port = 0;
+       void __iomem *base = hsi_ctrl->base;
+       struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+
+       for (port = 1; port <= pdata->num_ports; port++) {
+               cfg = &pdata->ctx.pctx[port - 1];
+               hsi_outl(cfg->hst.mode | cfg->hst.flow | HSI_MODE_WAKE_CTRL_SW,
+                        base, HSI_HST_MODE_REG(port));
+               if (!hsi_driver_device_is_hsi(pdev))
+                       hsi_outl(cfg->hst.frame_size, base,
+                                                HSI_HST_FRAMESIZE_REG(port));
+               hsi_outl(cfg->hst.divisor, base, HSI_HST_DIVISOR_REG(port));
+               hsi_outl(cfg->hst.channels, base, HSI_HST_CHANNELS_REG(port));
+               hsi_outl(cfg->hst.arb_mode, base, HSI_HST_ARBMODE_REG(port));
+
+               hsi_outl(cfg->hsr.mode | cfg->hsr.flow, base,
+                        HSI_HSR_MODE_REG(port));
+               hsi_outl(cfg->hsr.frame_size, base,
+                        HSI_HSR_FRAMESIZE_REG(port));
+               hsi_outl(cfg->hsr.channels, base, HSI_HSR_CHANNELS_REG(port));
+               if (hsi_driver_device_is_hsi(pdev))
+                       hsi_outl(cfg->hsr.divisor, base,
+                                               HSI_HSR_DIVISOR_REG(port));
+               hsi_outl(cfg->hsr.timeout, base, HSI_HSR_COUNTERS_REG(port));
+       }
+
+       if (hsi_driver_device_is_hsi(pdev)) {
+               /* SW strategy for HSI fifo management can be changed here */
+               hsi_fifo_mapping(hsi_ctrl, HSI_FIFO_MAPPING_DEFAULT);
+       }
+}
+
+static int __init hsi_port_channels_init(struct hsi_port *port)
+{
+       struct hsi_channel *ch;
+       unsigned int ch_i;
+
+       for (ch_i = 0; ch_i < port->max_ch; ch_i++) {
+               ch = &port->hsi_channel[ch_i];
+               ch->channel_number = ch_i;
+               rwlock_init(&ch->rw_lock);
+               ch->flags = 0;
+               ch->hsi_port = port;
+               ch->read_data.addr = NULL;
+               ch->read_data.size = 0;
+               ch->read_data.lch = -1;
+               ch->write_data.addr = NULL;
+               ch->write_data.size = 0;
+               ch->write_data.lch = -1;
+               ch->dev = NULL;
+               ch->read_done = NULL;
+               ch->write_done = NULL;
+               ch->port_event = NULL;
+       }
+
+       return 0;
+}
+
+static void hsi_ports_exit(struct hsi_dev *hsi_ctrl, unsigned int max_ports)
+{
+       struct hsi_port *hsi_p;
+       unsigned int port;
+
+       for (port = 0; port < max_ports; port++) {
+               hsi_p = &hsi_ctrl->hsi_port[port];
+               hsi_mpu_exit(hsi_p);
+               hsi_cawake_exit(hsi_p);
+       }
+}
+
+static int __init hsi_request_mpu_irq(struct hsi_port *hsi_p)
+{
+       struct hsi_dev *hsi_ctrl = hsi_p->hsi_controller;
+       struct platform_device *pd = to_platform_device(hsi_ctrl->dev);
+       struct resource *mpu_irq;
+
+       if (hsi_driver_device_is_hsi(pd))
+               mpu_irq = platform_get_resource(pd, IORESOURCE_IRQ,
+                                       hsi_p->port_number - 1);
+       else /* SSI support 2 IRQs per port */
+               mpu_irq = platform_get_resource(pd, IORESOURCE_IRQ,
+                                       (hsi_p->port_number - 1) * 2);
+
+       if (!mpu_irq) {
+               dev_err(hsi_ctrl->dev, "HSI misses info for MPU IRQ on"
+                       " port %d\n", hsi_p->port_number);
+               return -ENXIO;
+       }
+       hsi_p->n_irq = 0;       /* We only use one irq line */
+       hsi_p->irq = mpu_irq->start;
+       return hsi_mpu_init(hsi_p, mpu_irq->name);
+}
+
+static int __init hsi_request_cawake_irq(struct hsi_port *hsi_p)
+{
+       struct hsi_dev *hsi_ctrl = hsi_p->hsi_controller;
+       struct platform_device *pd = to_platform_device(hsi_ctrl->dev);
+       struct resource *cawake_irq;
+
+       if (hsi_driver_device_is_hsi(pd)) {
+               hsi_p->cawake_gpio = -1;
+               return 0;
+       } else {
+               cawake_irq = platform_get_resource(pd, IORESOURCE_IRQ,
+                                          4 + hsi_p->port_number);
+       }
+
+       if (!cawake_irq) {
+               dev_err(hsi_ctrl->dev, "SSI device misses info for CAWAKE"
+                       "IRQ on port %d\n", hsi_p->port_number);
+               return -ENXIO;
+       }
+
+       if (cawake_irq->flags & IORESOURCE_UNSET) {
+               dev_info(hsi_ctrl->dev, "No CAWAKE GPIO support\n");
+               hsi_p->cawake_gpio = -1;
+               return 0;
+       }
+
+       hsi_p->cawake_gpio_irq = cawake_irq->start;
+       hsi_p->cawake_gpio = irq_to_gpio(cawake_irq->start);
+       return hsi_cawake_init(hsi_p, cawake_irq->name);
+}
+
+static int __init hsi_ports_init(struct hsi_dev *hsi_ctrl)
+{
+       struct platform_device *pd = to_platform_device(hsi_ctrl->dev);
+       struct hsi_platform_data *pdata = pd->dev.platform_data;
+       struct hsi_port *hsi_p;
+       unsigned int port;
+       int err;
+
+       for (port = 0; port < hsi_ctrl->max_p; port++) {
+               hsi_p = &hsi_ctrl->hsi_port[port];
+               hsi_p->port_number = port + 1;
+               hsi_p->hsi_controller = hsi_ctrl;
+               hsi_p->max_ch = hsi_driver_device_is_hsi(pd) ?
+                               HSI_CHANNELS_MAX : HSI_SSI_CHANNELS_MAX;
+               hsi_p->max_ch = min(hsi_p->max_ch, (u8) HSI_PORT_MAX_CH);
+               hsi_p->irq = 0;
+               hsi_p->counters_on = 1;
+               hsi_p->reg_counters = pdata->ctx.pctx[port].hsr.timeout;
+               spin_lock_init(&hsi_p->lock);
+               err = hsi_port_channels_init(&hsi_ctrl->hsi_port[port]);
+               if (err < 0)
+                       goto rback1;
+               err = hsi_request_mpu_irq(hsi_p);
+               if (err < 0)
+                       goto rback2;
+               err = hsi_request_cawake_irq(hsi_p);
+               if (err < 0)
+                       goto rback3;
+       }
+       return 0;
+rback3:
+       hsi_mpu_exit(hsi_p);
+rback2:
+       hsi_ports_exit(hsi_ctrl, port + 1);
+rback1:
+       return err;
+}
+
+static int __init hsi_request_gdd_irq(struct hsi_dev *hsi_ctrl)
+{
+       struct platform_device *pd = to_platform_device(hsi_ctrl->dev);
+       struct resource *gdd_irq;
+
+       if (hsi_driver_device_is_hsi(pd))
+               gdd_irq = platform_get_resource(pd, IORESOURCE_IRQ, 2);
+       else
+               gdd_irq = platform_get_resource(pd, IORESOURCE_IRQ, 4);
+
+       if (!gdd_irq) {
+               dev_err(hsi_ctrl->dev, "HSI has no GDD IRQ resource\n");
+               return -ENXIO;
+       }
+
+       hsi_ctrl->gdd_irq = gdd_irq->start;
+       return hsi_gdd_init(hsi_ctrl, gdd_irq->name);
+}
+
+static void __init hsi_init_gdd_chan_count(struct hsi_dev *hsi_ctrl)
+{
+       struct platform_device *pd = to_platform_device(hsi_ctrl->dev);
+       struct resource *gdd_chan_count;
+       int i;
+
+       gdd_chan_count = platform_get_resource(pd, IORESOURCE_DMA, 0);
+
+       if (!gdd_chan_count) {
+               dev_warn(hsi_ctrl->dev, "HSI device has no GDD channel count "
+                                       "resource (use 8 as default)\n");
+               hsi_ctrl->gdd_chan_count = 8;
+       } else {
+               hsi_ctrl->gdd_chan_count = gdd_chan_count->start;
+               /* Check that the number of channels is power of 2 */
+               for (i = 0; i < 16; i++) {
+                       if (hsi_ctrl->gdd_chan_count == (1 << i))
+                               break;
+               }
+               if (i >= 16)
+                       dev_err(hsi_ctrl->dev, "The Number of DMA channels "
+                                       "shall be a power of 2! (=%d)\n",
+                                       hsi_ctrl->gdd_chan_count);
+       }
+}
+
+static int __init hsi_controller_init(struct hsi_dev *hsi_ctrl,
+                                     struct platform_device *pd)
+{
+       struct hsi_platform_data *pdata = pd->dev.platform_data;
+       struct resource *mem, *ioarea;
+       int err;
+
+       mem = platform_get_resource(pd, IORESOURCE_MEM, 0);
+       if (!mem) {
+               dev_err(&pd->dev, "HSI device does not have "
+                       "HSI IO memory region information\n");
+               return -ENXIO;
+       }
+
+       ioarea = devm_request_mem_region(&pd->dev, mem->start,
+                                        (mem->end - mem->start) + 1,
+                                        dev_name(&pd->dev));
+       if (!ioarea) {
+               dev_err(&pd->dev, "Unable to request HSI IO mem region\n");
+               return -EBUSY;
+       }
+
+       hsi_ctrl->phy_base = mem->start;
+       hsi_ctrl->base = devm_ioremap(&pd->dev, mem->start,
+                                     (mem->end - mem->start) + 1);
+       if (!hsi_ctrl->base) {
+               dev_err(&pd->dev, "Unable to ioremap HSI base IO address\n");
+               return -ENXIO;
+       }
+
+       hsi_ctrl->id = pd->id;
+       if (pdata->num_ports > HSI_MAX_PORTS) {
+               dev_err(&pd->dev, "The HSI driver does not support enough "
+               "ports!\n");
+               return -ENXIO;
+       }
+       hsi_ctrl->max_p = pdata->num_ports;
+       hsi_ctrl->dev = &pd->dev;
+       spin_lock_init(&hsi_ctrl->lock);
+       hsi_ctrl->hsi_clk = clk_get(&pd->dev, "hsi_clk");
+       hsi_init_gdd_chan_count(hsi_ctrl);
+
+       if (IS_ERR(hsi_ctrl->hsi_clk)) {
+               dev_err(hsi_ctrl->dev, "Unable to get HSI clocks\n");
+               return PTR_ERR(hsi_ctrl->hsi_clk);
+       }
+
+       if (pdata->clk_notifier_register) {
+               hsi_ctrl->hsi_nb.notifier_call = hsi_clk_event;
+               hsi_ctrl->hsi_nb.priority = INT_MAX; /* Let's try to be first */
+               err = pdata->clk_notifier_register(hsi_ctrl->hsi_clk,
+                                                  &hsi_ctrl->hsi_nb);
+               if (err < 0)
+                       goto rback1;
+       }
+
+       err = hsi_ports_init(hsi_ctrl);
+       if (err < 0)
+               goto rback2;
+
+       err = hsi_request_gdd_irq(hsi_ctrl);
+       if (err < 0)
+               goto rback3;
+
+       return 0;
+rback3:
+       hsi_ports_exit(hsi_ctrl, hsi_ctrl->max_p);
+rback2:
+       if (pdata->clk_notifier_unregister)
+               pdata->clk_notifier_unregister(hsi_ctrl->hsi_clk,
+                                              &hsi_ctrl->hsi_nb);
+rback1:
+       clk_put(hsi_ctrl->hsi_clk);
+       dev_err(&pd->dev, "Error on hsi_controller initialization\n");
+       return err;
+}
+
+static void hsi_controller_exit(struct hsi_dev *hsi_ctrl)
+{
+       struct hsi_platform_data *pdata = hsi_ctrl->dev->platform_data;
+
+       hsi_gdd_exit(hsi_ctrl);
+       hsi_ports_exit(hsi_ctrl, hsi_ctrl->max_p);
+       if (pdata->clk_notifier_unregister)
+               pdata->clk_notifier_unregister(hsi_ctrl->hsi_clk,
+                                              &hsi_ctrl->hsi_nb);
+       clk_put(hsi_ctrl->hsi_clk);
+}
+
+static int __init hsi_probe(struct platform_device *pd)
+{
+       struct hsi_platform_data *pdata = pd->dev.platform_data;
+       struct hsi_dev *hsi_ctrl;
+       u32 revision;
+       int err;
+
+       dev_dbg(&pd->dev, "The platform device probed is an %s\n",
+                       hsi_driver_device_is_hsi(pd) ? "HSI" : "SSI");
+
+       if (!pdata) {
+               pr_err(LOG_NAME "No platform_data found on hsi device\n");
+               return -ENXIO;
+       }
+
+       hsi_ctrl = kzalloc(sizeof(*hsi_ctrl), GFP_KERNEL);
+       if (hsi_ctrl == NULL) {
+               dev_err(&pd->dev, "Could not allocate memory for"
+                       " struct hsi_dev\n");
+               return -ENOMEM;
+       }
+
+       platform_set_drvdata(pd, hsi_ctrl);
+       err = hsi_controller_init(hsi_ctrl, pd);
+       if (err < 0) {
+               dev_err(&pd->dev, "Could not initialize hsi controller:"
+                       " %d\n", err);
+               goto rollback1;
+       }
+
+       clk_enable(hsi_ctrl->hsi_clk);
+
+       err = hsi_softreset(hsi_ctrl);
+       if (err < 0)
+               goto rollback2;
+
+       /* Set default PM settings */
+       hsi_outl((HSI_AUTOIDLE | HSI_SIDLEMODE_SMART | HSI_MIDLEMODE_SMART),
+                hsi_ctrl->base, HSI_SYS_SYSCONFIG_REG);
+       hsi_outl(HSI_CLK_AUTOGATING_ON, hsi_ctrl->base, HSI_GDD_GCR_REG);
+
+       /* Configure HSI ports */
+       set_hsi_ports_default(hsi_ctrl, pd);
+
+       /* Gather info from registers for the driver.(REVISION) */
+       revision = hsi_inl(hsi_ctrl->base, HSI_SYS_REVISION_REG);
+       if (hsi_driver_device_is_hsi(pd))
+               dev_info(hsi_ctrl->dev, "HSI Hardware REVISION 0x%x\n",
+                                                               revision);
+       else
+               dev_info(hsi_ctrl->dev, "SSI Hardware REVISION %d.%d\n",
+                                       (revision & HSI_SSI_REV_MAJOR) >> 4,
+                                       (revision & HSI_SSI_REV_MINOR));
+
+
+       err = hsi_debug_add_ctrl(hsi_ctrl);
+       if (err < 0)
+               goto rollback2;
+
+       err = register_hsi_devices(hsi_ctrl);
+       if (err < 0)
+               goto rollback3;
+
+       clk_disable(hsi_ctrl->hsi_clk);
+
+       return err;
+
+rollback3:
+       hsi_debug_remove_ctrl(hsi_ctrl);
+rollback2:
+       clk_disable(hsi_ctrl->hsi_clk);
+       hsi_controller_exit(hsi_ctrl);
+rollback1:
+       kfree(hsi_ctrl);
+       return err;
+}
+
+static void __exit unregister_hsi_devices(struct hsi_dev *hsi_ctrl)
+{
+       struct hsi_port *hsi_p;
+       struct hsi_device *device;
+       unsigned int port;
+       unsigned int ch;
+
+       for (port = 0; port < hsi_ctrl->max_p; port++) {
+               hsi_p = &hsi_ctrl->hsi_port[port];
+               for (ch = 0; ch < hsi_p->max_ch; ch++) {
+                       device = hsi_p->hsi_channel[ch].dev;
+                       hsi_close(device);
+                       device_unregister(&device->device);
+                       kfree(device);
+               }
+       }
+}
+
+static int __exit hsi_remove(struct platform_device *pd)
+{
+       struct hsi_dev *hsi_ctrl = platform_get_drvdata(pd);
+
+       if (!hsi_ctrl)
+               return 0;
+
+       unregister_hsi_devices(hsi_ctrl);
+       hsi_debug_remove_ctrl(hsi_ctrl);
+       hsi_controller_exit(hsi_ctrl);
+       kfree(hsi_ctrl);
+
+       return 0;
+}
+
+int hsi_driver_device_is_hsi(struct platform_device *dev)
+{
+       struct platform_device_id *id = platform_get_device_id(dev);
+       return (id->driver_data == HSI_DRV_DEVICE_HSI);
+}
+
+/* List of devices supported by this driver */
+static struct platform_device_id hsi_id_table[] = {
+       { "omap_hsi",   HSI_DRV_DEVICE_HSI },
+       { "omap_ssi",   HSI_DRV_DEVICE_SSI },
+       { },
+};
+MODULE_DEVICE_TABLE(platform, hsi_id_table);
+
+static struct platform_driver hsi_pdriver = {
+       .probe = hsi_probe,
+       .remove = __exit_p(hsi_remove),
+       .driver = {
+                  .name = "omap_hsi",
+                  .owner = THIS_MODULE,
+                  },
+       .id_table = hsi_id_table
+};
+
+static int __init hsi_driver_init(void)
+{
+       int err = 0;
+
+       pr_info("HSI DRIVER Version " HSI_DRIVER_VERSION "\n");
+
+       hsi_bus_init();
+       err = hsi_debug_init();
+       if (err < 0) {
+               pr_err(LOG_NAME "HSI Debugfs failed %d\n", err);
+               goto rback1;
+       }
+       err = platform_driver_probe(&hsi_pdriver, hsi_probe);
+       if (err < 0) {
+               pr_err(LOG_NAME "Platform DRIVER register FAILED: %d\n", err);
+               goto rback2;
+       }
+
+       return 0;
+rback2:
+       hsi_debug_exit();
+rback1:
+       hsi_bus_exit();
+       return err;
+}
+
+static void __exit hsi_driver_exit(void)
+{
+       platform_driver_unregister(&hsi_pdriver);
+       hsi_debug_exit();
+       hsi_bus_exit();
+
+       pr_info("HSI DRIVER removed\n");
+}
+
+module_init(hsi_driver_init);
+module_exit(hsi_driver_exit);
+
+MODULE_ALIAS("platform:omap_hsi");
+MODULE_AUTHOR("Carlos Chinea / Nokia");
+MODULE_AUTHOR("Sebastien JAN / Texas Instruments");
+MODULE_DESCRIPTION("MIPI High-speed Synchronous Serial Interface (HSI) 
Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hsi/hsi_driver.h b/drivers/hsi/hsi_driver.h
new file mode 100644
index 0000000..711297a
--- /dev/null
+++ b/drivers/hsi/hsi_driver.h
@@ -0,0 +1,308 @@
+/*
+ * hsi_driver.h
+ *
+ * Header file for the HSI driver low level interface.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chi...@nokia.com>
+ * Author: Sebastien JAN <s-...@ti.com>
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#ifndef __HSI_DRIVER_H__
+#define __HSI_DRIVER_H__
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/clk.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/notifier.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include <mach/hsi.h>
+#include <linux/hsi_driver_if.h>
+
+/* Channel states */
+#define        HSI_CH_OPEN             0x01
+#define HSI_CH_RX_POLL         0x10
+
+/*
+ * The number of channels handled by the driver in the ports, or the highest
+ * port channel number (+1) used. (MAX:8 for SSI; 16 for HSI)
+ * Reducing this value optimizes the driver memory footprint.
+ */
+#define HSI_PORT_MAX_CH                4
+
+#define LOG_NAME               "OMAP HSI: "
+
+/* SW strategies for FIFO mapping */
+enum {
+       HSI_FIFO_MAPPING_UNDEF = 0,
+       HSI_FIFO_MAPPING_SSI, /* 8 FIFOs per port (SSI compatible mode) */
+       HSI_FIFO_MAPPING_ALL_PORT1, /* ALL FIFOs mapped on 1st port */
+};
+#define HSI_FIFO_MAPPING_DEFAULT       HSI_FIFO_MAPPING_SSI
+
+/* Device identifying constants */
+enum {
+       HSI_DRV_DEVICE_HSI,
+       HSI_DRV_DEVICE_SSI
+};
+
+/**
+ * struct hsi_data - HSI buffer descriptor
+ * @addr: pointer to the buffer where to send or receive data
+ * @size: size in words (32 bits) of the buffer
+ * @lch: associated GDD (DMA) logical channel number, if any
+ */
+struct hsi_data {
+       u32 *addr;
+       unsigned int size;
+       int lch;
+};
+
+/**
+ * struct hsi_channel - HSI channel data
+ * @read_data: Incoming HSI buffer descriptor
+ * @write_data: Outgoing HSI buffer descriptor
+ * @hsi_port: Reference to port where the channel belongs to
+ * @flags: Tracks if channel has been open
+ * @channel_number: HSI channel number
+ * @rw_lock: Read/Write lock to serialize access to callback and hsi_device
+ * @dev: Reference to the associated hsi_device channel
+ * @write_done: Callback to signal TX completed.
+ * @read_done: Callback to signal RX completed.
+ * @port_event: Callback to signal port events (RX Error, HWBREAK, CAWAKE ...)
+ */
+struct hsi_channel {
+       struct hsi_data read_data;
+       struct hsi_data write_data;
+       struct hsi_port *hsi_port;
+       u8 flags;
+       u8 channel_number;
+       rwlock_t rw_lock;
+       struct hsi_device *dev;
+       void (*write_done)(struct hsi_device *dev, unsigned int size);
+       void (*read_done)(struct hsi_device *dev, unsigned int size);
+       void (*port_event)(struct hsi_device *dev, unsigned int event,
+                                                               void *arg);
+};
+
+/**
+ * struct hsi_port - hsi port driver data
+ * @hsi_channel: Array of channels in the port
+ * @hsi_controller: Reference to the HSI controller
+ * @port_number: port number
+ * @max_ch: maximum number of channels supported on the port
+ * @n_irq: HSI irq line use to handle interrupts (0 or 1)
+ * @irq: IRQ number
+ * @cawake_gpio: GPIO number for cawake line (-1 if none)
+ * @cawake_gpio_irq: IRQ number for cawake gpio events
+ * @counters_on: indicates if the HSR counters are in use or not
+ * @reg_counters: stores the previous counters values when deactivated
+ * @lock: Serialize access to the port registers and internal data
+ * @hsi_tasklet: Bottom half for interrupts
+ * @cawake_tasklet: Bottom half for cawake events
+ */
+struct hsi_port {
+       struct hsi_channel hsi_channel[HSI_PORT_MAX_CH];
+       struct hsi_dev *hsi_controller;
+       u8 flags;
+       u8 port_number;
+       u8 max_ch;
+       u8 n_irq;
+       int irq;
+       int cawake_gpio;
+       int cawake_gpio_irq;
+       int counters_on;
+       unsigned long reg_counters;
+       spinlock_t lock; /* access to the port registers and internal data */
+       struct tasklet_struct hsi_tasklet;
+       struct tasklet_struct cawake_tasklet;
+};
+
+/**
+ * struct hsi_dev - hsi controller driver data
+ * @hsi_port: Array of hsi ports enabled in the controller
+ * @id: HSI controller platform id number
+ * @max_p: Number of ports enabled in the controller
+ * @hsi_clk: Reference to the HSI custom clock
+ * @base: HSI registers base virtual address
+ * @phy_base: HSI registers base physical address
+ * @lock: Serializes access to internal data and regs
+ * @cawake_clk_enable: Tracks if a cawake event has enable the clocks
+ * @gdd_irq: GDD (DMA) irq number
+ * @fifo_mapping_strategy: Selected strategy for fifo to ports/channels mapping
+ * @gdd_usecount: Holds the number of ongoning DMA transfers
+ * @last_gdd_lch: Last used GDD logical channel
+ * @gdd_chan-count: Number of available DMA channels on the device (must be ^2)
+ * @set_min_bus_tput: (PM) callback to set minimun bus throuput
+ * @clk_notifier_register: (PM) callabck for DVFS support
+ * @clk_notifier_unregister: (PM) callabck for DVFS support
+ * @hsi_nb: (PM) Notification block for DVFS notification chain
+ * @hsi_gdd_tasklet: Bottom half for DMA transfers
+ * @dir: debugfs base directory
+ * @dev: Reference to the HSI platform device
+ */
+struct hsi_dev {
+       struct hsi_port hsi_port[HSI_MAX_PORTS];
+       int id;
+       u8 max_p;
+       struct clk *hsi_clk;
+       void __iomem *base;
+       unsigned long phy_base;
+       spinlock_t lock; /* Serializes access to internal data and regs */
+       unsigned int cawake_clk_enable:1;
+       int gdd_irq;
+       unsigned int fifo_mapping_strategy;
+       unsigned int gdd_usecount;
+       unsigned int last_gdd_lch;
+       unsigned int gdd_chan_count;
+       void (*set_min_bus_tput)(struct device *dev, u8 agent_id,
+                                               unsigned long r);
+       int (*clk_notifier_register)(struct clk *clk,
+                                       struct notifier_block *nb);
+       int (*clk_notifier_unregister)(struct clk *clk,
+                                       struct notifier_block *nb);
+       struct notifier_block hsi_nb;
+       struct tasklet_struct hsi_gdd_tasklet;
+#ifdef CONFIG_DEBUG_FS
+       struct dentry *dir;
+#endif
+       struct device *dev;
+};
+
+/* HSI Bus */
+extern struct bus_type hsi_bus_type;
+
+int hsi_port_event_handler(struct hsi_port *p, unsigned int event, void *arg);
+int hsi_bus_init(void);
+void hsi_bus_exit(void);
+/* End HSI Bus */
+
+void hsi_reset_ch_read(struct hsi_channel *ch);
+void hsi_reset_ch_write(struct hsi_channel *ch);
+
+int hsi_driver_read_interrupt(struct hsi_channel *hsi_channel, u32 *data);
+int hsi_driver_write_interrupt(struct hsi_channel *hsi_channel, u32 *data);
+int hsi_driver_read_dma(struct hsi_channel *hsi_channel, u32 *data,
+                       unsigned int count);
+int hsi_driver_write_dma(struct hsi_channel *hsi_channel, u32 *data,
+                       unsigned int count);
+
+void hsi_driver_cancel_write_interrupt(struct hsi_channel *ch);
+void hsi_driver_disable_read_interrupt(struct hsi_channel *ch);
+void hsi_driver_cancel_read_interrupt(struct hsi_channel *ch);
+void hsi_driver_cancel_write_dma(struct hsi_channel *ch);
+void hsi_driver_cancel_read_dma(struct hsi_channel *ch);
+
+int hsi_driver_device_is_hsi(struct platform_device *dev);
+
+int hsi_mpu_init(struct hsi_port *hsi_p, const char *irq_name);
+void hsi_mpu_exit(struct hsi_port *hsi_p);
+
+int hsi_gdd_init(struct hsi_dev *hsi_ctrl, const char *irq_name);
+void hsi_gdd_exit(struct hsi_dev *hsi_ctrl);
+
+int hsi_cawake_init(struct hsi_port *port, const char *irq_name);
+void hsi_cawake_exit(struct hsi_port *port);
+
+int hsi_fifo_get_id(struct hsi_dev *hsi_ctrl, unsigned int channel,
+                                                       unsigned int port);
+int hsi_fifo_get_chan(struct hsi_dev *hsi_ctrl, unsigned int fifo,
+                               unsigned int *channel, unsigned int *port);
+int __init hsi_fifo_mapping(struct hsi_dev *hsi_ctrl, unsigned int mtype);
+long hsi_hst_bufstate_f_reg(struct hsi_dev *hsi_ctrl,
+                               unsigned int port, unsigned int channel);
+long hsi_hsr_bufstate_f_reg(struct hsi_dev *hsi_ctrl,
+                               unsigned int port, unsigned int channel);
+long hsi_hst_buffer_reg(struct hsi_dev *hsi_ctrl,
+                               unsigned int port, unsigned int channel);
+long hsi_hsr_buffer_reg(struct hsi_dev *hsi_ctrl,
+                               unsigned int port, unsigned int channel);
+
+#ifdef CONFIG_DEBUG_FS
+int hsi_debug_init(void);
+void hsi_debug_exit(void);
+int hsi_debug_add_ctrl(struct hsi_dev *hsi_ctrl);
+void hsi_debug_remove_ctrl(struct hsi_dev *hsi_ctrl);
+#else
+#define        hsi_debug_add_ctrl(hsi_ctrl)    0
+#define        hsi_debug_remove_ctrl(hsi_ctrl)
+#define        hsi_debug_init()                0
+#define        hsi_debug_exit()
+#endif /* CONFIG_DEBUG_FS */
+
+static inline unsigned int hsi_cawake(struct hsi_port *port)
+{
+       return gpio_get_value(port->cawake_gpio);
+}
+
+static inline struct hsi_channel *ctrl_get_ch(struct hsi_dev *hsi_ctrl,
+                                       unsigned int port, unsigned int channel)
+{
+       return &hsi_ctrl->hsi_port[port - 1].hsi_channel[channel];
+}
+
+/* HSI IO access */
+static inline u32 hsi_inl(void __iomem *base, u32 offset)
+{
+       return inl((unsigned int) base + offset);
+}
+
+static inline void hsi_outl(u32 data, void __iomem *base, u32 offset)
+{
+       outl(data, (unsigned int) base + offset);
+}
+
+static inline void hsi_outl_or(u32 data, void __iomem *base, u32 offset)
+{
+       u32 tmp = hsi_inl(base, offset);
+       hsi_outl((tmp | data), base, offset);
+}
+
+static inline void hsi_outl_and(u32 data, void __iomem *base, u32 offset)
+{
+       u32 tmp = hsi_inl(base, offset);
+       hsi_outl((tmp & data), base, offset);
+}
+
+static inline u16 hsi_inw(void __iomem *base, u32 offset)
+{
+       return inw((unsigned int) base + offset);
+}
+
+static inline void hsi_outw(u16 data, void __iomem *base, u32 offset)
+{
+       outw(data, (unsigned int) base + offset);
+}
+
+static inline void hsi_outw_or(u16 data, void __iomem *base, u32 offset)
+{
+       u16 tmp = hsi_inw(base, offset);
+       hsi_outw((tmp | data), base, offset);
+}
+
+static inline void hsi_outw_and(u16 data, void __iomem *base, u32 offset)
+{
+       u16 tmp = hsi_inw(base, offset);
+       hsi_outw((tmp & data), base, offset);
+}
+
+#endif /* __HSI_DRIVER_H__ */
diff --git a/drivers/hsi/hsi_driver_bus.c b/drivers/hsi/hsi_driver_bus.c
new file mode 100644
index 0000000..3fb9d6e
--- /dev/null
+++ b/drivers/hsi/hsi_driver_bus.c
@@ -0,0 +1,182 @@
+/*
+ * hsi_driver_bus.c
+ *
+ * Implements an HSI bus, device and driver interface.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chi...@nokia.com>
+ * Author: Sebastien JAN <s-...@ti.com>
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <linux/device.h>
+#include "hsi_driver.h"
+
+#define HSI_PREFIX             "hsi:"
+
+struct bus_type hsi_bus_type;
+
+static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
+                                                               char *buf)
+{
+       return snprintf(buf, PAGE_SIZE + 1, "%s%s\n", HSI_PREFIX,
+                                                               dev_name(dev));
+}
+
+static struct device_attribute hsi_dev_attrs[] = {
+       __ATTR_RO(modalias),
+       __ATTR_NULL,
+};
+
+static int hsi_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+       add_uevent_var(env, "MODALIAS=%s%s", HSI_PREFIX, dev_name(dev));
+       return 0;
+}
+
+static int hsi_bus_match(struct device *device, struct device_driver *driver)
+{
+       struct hsi_device *dev = to_hsi_device(device);
+       struct hsi_device_driver *drv = to_hsi_device_driver(driver);
+
+       if (!test_bit(dev->n_ctrl, &drv->ctrl_mask))
+               return 0;
+
+       if (!test_bit(dev->n_ch, &drv->ch_mask[dev->n_p]))
+               return 0;
+
+       return 1;
+}
+
+int hsi_bus_unreg_dev(struct device *device, void *p)
+{
+       device->release(device);
+       device_unregister(device);
+
+       return 0;
+}
+
+int __init hsi_bus_init(void)
+{
+       return bus_register(&hsi_bus_type);
+}
+
+void hsi_bus_exit(void)
+{
+       bus_for_each_dev(&hsi_bus_type, NULL, NULL, hsi_bus_unreg_dev);
+       bus_unregister(&hsi_bus_type);
+}
+
+static int hsi_driver_probe(struct device *dev)
+{
+       struct hsi_device_driver *drv = to_hsi_device_driver(dev->driver);
+
+       if (!drv->probe)
+               return -ENODEV;
+
+       return drv->probe(to_hsi_device(dev));
+}
+
+static int hsi_driver_remove(struct device *dev)
+{
+       struct hsi_device_driver *drv;
+       int ret;
+
+       if (!dev->driver)
+               return 0;
+
+       drv = to_hsi_device_driver(dev->driver);
+       if (drv->remove) {
+               ret = drv->remove(to_hsi_device(dev));
+       } else {
+               dev->driver = NULL;
+               ret = 0;
+       }
+
+       return ret;
+}
+
+static int hsi_driver_suspend(struct device *dev, pm_message_t mesg)
+{
+       struct hsi_device_driver *drv = to_hsi_device_driver(dev->driver);
+
+       if (!dev->driver)
+               return 0;
+
+       drv = to_hsi_device_driver(dev->driver);
+       if (!drv->suspend)
+               return 0;
+
+       return drv->suspend(to_hsi_device(dev), mesg);
+}
+
+static int hsi_driver_resume(struct device *dev)
+{
+       struct hsi_device_driver *drv;
+
+       if (!dev->driver)
+               return 0;
+
+       drv = to_hsi_device_driver(dev->driver);
+       if (!drv->resume)
+               return 0;
+
+       return drv->resume(to_hsi_device(dev));
+}
+
+struct bus_type hsi_bus_type = {
+       .name           = "hsi",
+       .dev_attrs      = hsi_dev_attrs,
+       .match          = hsi_bus_match,
+       .uevent         = hsi_bus_uevent,
+       .probe          = hsi_driver_probe,
+       .remove         = hsi_driver_remove,
+       .suspend        = hsi_driver_suspend,
+       .resume         = hsi_driver_resume,
+};
+
+/**
+ * hsi_register_driver - Register HSI device driver
+ * @driver - reference to the HSI device driver.
+ */
+int hsi_register_driver(struct hsi_device_driver *driver)
+{
+       int ret = 0;
+
+       if (driver == NULL)
+               return -EINVAL;
+
+       driver->driver.bus = &hsi_bus_type;
+
+       ret = driver_register(&driver->driver);
+
+       if (ret == 0)
+               pr_debug("hsi: driver %s registered\n", driver->driver.name);
+
+       return ret;
+}
+EXPORT_SYMBOL(hsi_register_driver);
+
+/**
+ * hsi_unregister_driver - Unregister HSI device driver
+ * @driver - reference to the HSI device driver.
+ */
+void hsi_unregister_driver(struct hsi_device_driver *driver)
+{
+       if (driver == NULL)
+               return;
+
+       driver_unregister(&driver->driver);
+
+       pr_debug("hsi: driver %s unregistered\n", driver->driver.name);
+}
+EXPORT_SYMBOL(hsi_unregister_driver);
-- 
1.6.0.4

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to