[RFC PATCH 2/9] HSI: Low Level Driver core

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

Signed-off-by: Sebastien Jan <s-jan@xxxxxx>
Signed-off-by: Carlos Chinea <carlos.chinea@xxxxxxxxx>
---
 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.chinea@xxxxxxxxx>
+ * Author: Sebastien JAN <s-jan@xxxxxx>
+ *
+ * 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.chinea@xxxxxxxxx>
+ * Author: Sebastien JAN <s-jan@xxxxxx>
+ *
+ * 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.chinea@xxxxxxxxx>
+ * Author: Sebastien JAN <s-jan@xxxxxx>
+ *
+ * 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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux