* [RFC PATCH 1/9] HSI: Low Level Driver interface
2009-10-30 9:59 [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Sebastien Jan
@ 2009-10-30 10:00 ` Sebastien Jan
2009-10-30 12:49 ` Felipe Balbi
2009-10-30 10:00 ` [RFC PATCH 2/9] HSI: Low Level Driver core Sebastien Jan
` (8 subsequent siblings)
9 siblings, 1 reply; 15+ messages in thread
From: Sebastien Jan @ 2009-10-30 10:00 UTC (permalink / raw)
To: linux-omap; +Cc: Sebastien Jan, Carlos Chinea
Driver kernel interface headers and implementation
Driver Makefile and configuration integration
Signed-off-by: Sebastien Jan <s-jan@ti.com>
Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com>
---
drivers/Makefile | 1 +
drivers/hsi/Kconfig | 44 +++
drivers/hsi/Makefile | 14 +
drivers/hsi/hsi_driver_if.c | 657 +++++++++++++++++++++++++++++++++++++++++
include/linux/hsi_driver_if.h | 180 +++++++++++
5 files changed, 896 insertions(+), 0 deletions(-)
create mode 100644 drivers/hsi/Kconfig
create mode 100644 drivers/hsi/Makefile
create mode 100644 drivers/hsi/hsi_driver_if.c
create mode 100644 include/linux/hsi_driver_if.h
diff --git a/drivers/Makefile b/drivers/Makefile
index 086857c..05b4190 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -111,3 +111,4 @@ obj-$(CONFIG_VLYNQ) += vlynq/
obj-$(CONFIG_STAGING) += staging/
obj-y += platform/
obj-y += ieee802154/
+obj-$(CONFIG_OMAP_HSI) += hsi/
diff --git a/drivers/hsi/Kconfig b/drivers/hsi/Kconfig
new file mode 100644
index 0000000..e10b522
--- /dev/null
+++ b/drivers/hsi/Kconfig
@@ -0,0 +1,44 @@
+#
+# OMAP HSI driver configuration
+#
+menuconfig HSI
+ bool "HSI support"
+ default n
+ help
+ The MIPI HSI is a High Speed Synchronous Serial Interface and is
+ defined for communication between two Integrated Circuits (the
+ typical scenario is an application IC and cellular modem IC
+ communication). Data transaction model is peer-to-peer. MIPI HSI
+ physical layer provides logical channeling and several modes of
+ operation.
+
+if HSI
+
+config OMAP_HSI
+ tristate "OMAP HSI hardware driver"
+ depends on (ARCH_OMAP34XX || ARCH_OMAP4)
+ default n
+ ---help---
+ If you say Y here, you will enable the OMAP HSI hardware driver.
+
+ If unsure, say N.
+
+ Note that the HSI driver supports either:
+ - the OMAP MIPI HSI device
+ - the OMAP SSI device
+
+choice
+ prompt "Selected device support file"
+ depends on OMAP_HSI && y
+ default OMAP_HSI_DEVICE
+ ---help---
+ Adds the device support for one of the devices handled by the HSI
+ driver.
+
+config OMAP_HSI_DEVICE
+ bool "HSI (OMAP MIPI HSI)"
+
+config OMAP_SSI_DEVICE
+ bool "SSI (OMAP SSI)"
+
+endchoice
diff --git a/drivers/hsi/Makefile b/drivers/hsi/Makefile
new file mode 100644
index 0000000..a7f579b
--- /dev/null
+++ b/drivers/hsi/Makefile
@@ -0,0 +1,14 @@
+#
+# Makefile for HSI drivers
+#
+EXTRA_CFLAGS :=
+
+omap_hsi-objs := hsi_driver.o hsi_driver_dma.o hsi_driver_int.o \
+ hsi_driver_if.o hsi_driver_bus.o hsi_driver_gpio.o \
+ hsi_driver_fifo.o
+
+ifeq ($(CONFIG_DEBUG_FS), y)
+ omap_hsi-objs += hsi_driver_debugfs.o
+endif
+
+obj-$(CONFIG_OMAP_HSI) += omap_hsi.o
diff --git a/drivers/hsi/hsi_driver_if.c b/drivers/hsi/hsi_driver_if.c
new file mode 100644
index 0000000..fb0035d
--- /dev/null
+++ b/drivers/hsi/hsi_driver_if.c
@@ -0,0 +1,657 @@
+/*
+ * hsi_driver_if.c
+ *
+ * Implements HSI hardware driver interfaces for the upper layers.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@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 "hsi_driver.h"
+
+#define NOT_SET (-1)
+
+/* Manage HSR divisor update
+ * A special divisor value allows switching to auto-divisor mode in Rx
+ * (but with error counters deactivated). This function implements the
+ * the transitions to/from this mode.
+ */
+int hsi_set_rx_divisor(struct hsi_port *sport, u32 divisor)
+{
+ struct hsi_dev *hsi_ctrl = sport->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ int port = sport->port_number;
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+
+ if (divisor == NOT_SET)
+ return 0;
+
+ if (hsi_driver_device_is_hsi(pdev)) {
+ if (divisor == HSI_HSR_DIVISOR_AUTO && sport->counters_on) {
+ /* auto mode: deactivate counters + set divisor = 0 */
+ sport->reg_counters = hsi_inl(base,
+ HSI_HSR_COUNTERS_REG(port));
+ sport->counters_on = 0;
+ hsi_outl(0, base, HSI_HSR_COUNTERS_REG(port));
+ hsi_outl(0, base, HSI_HSR_DIVISOR_REG(port));
+ dev_dbg(hsi_ctrl->dev, "Switched to HSR auto mode\n");
+ } else if (divisor != HSI_HSR_DIVISOR_AUTO) {
+ /* Divisor set mode: use counters */
+ if (!sport->counters_on) {
+ /* Leave auto mode: restore counters */
+ hsi_outl(sport->reg_counters, base,
+ HSI_HSR_COUNTERS_REG(port));
+ sport->counters_on = 1;
+ dev_dbg(hsi_ctrl->dev, "Left HSR auto mode. "
+ "Counters=0x%lx\n", sport->reg_counters);
+ }
+ hsi_outl(divisor, base, HSI_HSR_DIVISOR_REG(port));
+ }
+ } else {
+ if (divisor == HSI_HSR_DIVISOR_AUTO && sport->counters_on) {
+ /* auto mode: deactivate timeout */
+ sport->reg_counters = hsi_inl(base,
+ HSI_HSR_COUNTERS_REG(port));
+ sport->counters_on = 0;
+ hsi_outl(0, base, HSI_HSR_COUNTERS_REG(port));
+ dev_dbg(hsi_ctrl->dev, "Deactivated SSR timeout\n");
+ } else if (divisor == HSI_SSR_DIVISOR_USE_TIMEOUT &&
+ !sport->counters_on){
+ /* Leave auto mode: restore timeout */
+ hsi_outl(sport->reg_counters, base,
+ HSI_HSR_COUNTERS_REG(port));
+ sport->counters_on = 1;
+ dev_dbg(hsi_ctrl->dev, "Re-activated SSR timeout; "
+ "timeout=0x%lx\n", sport->reg_counters);
+ }
+ }
+
+ return 0;
+}
+
+int hsi_set_rx(struct hsi_port *sport, struct hsr_ctx *cfg)
+{
+ struct hsi_dev *hsi_ctrl = sport->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ int port = sport->port_number;
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+
+ if (((cfg->mode & HSI_MODE_VAL_MASK) != HSI_MODE_STREAM) &&
+ ((cfg->mode & HSI_MODE_VAL_MASK) != HSI_MODE_FRAME) &&
+ ((cfg->mode & HSI_MODE_VAL_MASK) != HSI_MODE_SLEEP) &&
+ (cfg->mode != NOT_SET))
+ return -EINVAL;
+
+ if (hsi_driver_device_is_hsi(pdev)) {
+ if (
+ ((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_SYNCHRONIZED) &&
+ ((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_PIPELINED) &&
+ (cfg->flow != NOT_SET))
+ return -EINVAL;
+ } else {
+ if (
+ ((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_SYNCHRONIZED) &&
+ (cfg->flow != NOT_SET))
+ return -EINVAL;
+ }
+
+ if ((cfg->frame_size > HSI_FRAMESIZE_MAX) &&
+ (cfg->frame_size != NOT_SET))
+ return -EINVAL;
+
+ if ((cfg->channels == 0) ||
+ ((cfg->channels > sport->max_ch) &&
+ (cfg->channels != NOT_SET)))
+ return -EINVAL;
+
+ if (hsi_driver_device_is_hsi(pdev)) {
+ if ((cfg->divisor > HSI_MAX_RX_DIVISOR) &&
+ (cfg->divisor != HSI_HSR_DIVISOR_AUTO) &&
+ (cfg->divisor != NOT_SET))
+ return -EINVAL;
+ }
+
+ if ((cfg->mode != NOT_SET) &&
+ (cfg->flow != NOT_SET))
+ hsi_outl(cfg->mode | cfg->flow, base, HSI_HSR_MODE_REG(port));
+
+ if (cfg->frame_size != NOT_SET)
+ hsi_outl(cfg->frame_size, base, HSI_HSR_FRAMESIZE_REG(port));
+
+ if (cfg->channels != NOT_SET) {
+ if ((cfg->channels & (-cfg->channels)) ^ cfg->channels)
+ return -EINVAL;
+ else
+ hsi_outl(cfg->channels, base,
+ HSI_HSR_CHANNELS_REG(port));
+ }
+
+ return hsi_set_rx_divisor(sport, cfg->divisor);
+}
+
+void hsi_get_rx(struct hsi_port *sport, struct hsr_ctx *cfg)
+{
+ struct hsi_dev *hsi_ctrl = sport->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ int port = sport->port_number;
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+
+ cfg->mode = hsi_inl(base, HSI_HSR_MODE_REG(port)) & HSI_MODE_VAL_MASK;
+ cfg->flow = hsi_inl(base, HSI_HSR_MODE_REG(port)) & HSI_FLOW_VAL_MASK;
+ cfg->frame_size = hsi_inl(base, HSI_HSR_FRAMESIZE_REG(port));
+ cfg->channels = hsi_inl(base, HSI_HSR_CHANNELS_REG(port));
+ if (hsi_driver_device_is_hsi(pdev))
+ cfg->divisor = hsi_inl(base, HSI_HSR_DIVISOR_REG(port));
+}
+
+int hsi_set_tx(struct hsi_port *sport, struct hst_ctx *cfg)
+{
+ struct hsi_dev *hsi_ctrl = sport->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ int port = sport->port_number;
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+ unsigned int max_divisor = hsi_driver_device_is_hsi(pdev) ?
+ HSI_MAX_TX_DIVISOR : HSI_SSI_MAX_TX_DIVISOR;
+
+ if (((cfg->mode & HSI_MODE_VAL_MASK) != HSI_MODE_STREAM) &&
+ ((cfg->mode & HSI_MODE_VAL_MASK) != HSI_MODE_FRAME) &&
+ (cfg->mode != NOT_SET))
+ return -EINVAL;
+
+ if (hsi_driver_device_is_hsi(pdev)) {
+ if (
+ ((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_SYNCHRONIZED) &&
+ ((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_PIPELINED) &&
+ (cfg->flow != NOT_SET))
+ return -EINVAL;
+ } else {
+ if (
+ ((cfg->flow & HSI_FLOW_VAL_MASK) != HSI_FLOW_SYNCHRONIZED) &&
+ (cfg->flow != NOT_SET))
+ return -EINVAL;
+ }
+
+ if ((cfg->frame_size > HSI_FRAMESIZE_MAX) &&
+ (cfg->frame_size != NOT_SET))
+ return -EINVAL;
+
+ if ((cfg->channels == 0) ||
+ ((cfg->channels > sport->max_ch) &&
+ (cfg->channels != NOT_SET)))
+ return -EINVAL;
+
+ if ((cfg->divisor > max_divisor) && (cfg->divisor != NOT_SET))
+ return -EINVAL;
+
+ if ((cfg->arb_mode != HSI_ARBMODE_ROUNDROBIN) &&
+ (cfg->arb_mode != HSI_ARBMODE_PRIORITY) &&
+ (cfg->mode != NOT_SET))
+ return -EINVAL;
+
+ if ((cfg->mode != NOT_SET) &&
+ (cfg->flow != NOT_SET))
+ hsi_outl(cfg->mode | cfg->flow | HSI_MODE_WAKE_CTRL_SW,
+ base, HSI_HST_MODE_REG(port));
+
+ if (cfg->frame_size != NOT_SET)
+ hsi_outl(cfg->frame_size, base, HSI_HST_FRAMESIZE_REG(port));
+
+ if (cfg->channels != NOT_SET) {
+ if ((cfg->channels & (-cfg->channels)) ^ cfg->channels)
+ return -EINVAL;
+ else
+ hsi_outl(cfg->channels, base,
+ HSI_HST_CHANNELS_REG(port));
+ }
+
+ if (cfg->divisor != NOT_SET)
+ hsi_outl(cfg->divisor, base, HSI_HST_DIVISOR_REG(port));
+
+ if (cfg->arb_mode != NOT_SET)
+ hsi_outl(cfg->arb_mode, base, HSI_HST_ARBMODE_REG(port));
+
+ return 0;
+}
+
+void hsi_get_tx(struct hsi_port *sport, struct hst_ctx *cfg)
+{
+ struct hsi_dev *hsi_ctrl = sport->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ int port = sport->port_number;
+
+ cfg->mode = hsi_inl(base, HSI_HST_MODE_REG(port)) & HSI_MODE_VAL_MASK;
+ cfg->flow = hsi_inl(base, HSI_HST_MODE_REG(port)) & HSI_FLOW_VAL_MASK;
+ cfg->frame_size = hsi_inl(base, HSI_HST_FRAMESIZE_REG(port));
+ cfg->channels = hsi_inl(base, HSI_HST_CHANNELS_REG(port));
+ cfg->divisor = hsi_inl(base, HSI_HST_DIVISOR_REG(port));
+ cfg->arb_mode = hsi_inl(base, HSI_HST_ARBMODE_REG(port));
+}
+
+/**
+ * hsi_open - open a hsi device channel.
+ * @dev - Reference to the hsi device channel to be openned.
+ *
+ * Returns 0 on success, -EINVAL on bad parameters, -EBUSY if is already opened.
+ */
+int hsi_open(struct hsi_device *dev)
+{
+ struct hsi_channel *ch;
+ struct hsi_port *port;
+ struct hsi_dev *hsi_ctrl;
+
+ if (!dev || !dev->ch) {
+ pr_err(LOG_NAME "Wrong HSI device %p\n", dev);
+ return -EINVAL;
+ }
+
+ ch = dev->ch;
+ if (!ch->read_done || !ch->write_done) {
+ dev_err(&dev->device, "Trying to open with no (read/write) "
+ "callbacks registered\n");
+ return -EINVAL;
+ }
+ port = ch->hsi_port;
+ hsi_ctrl = port->hsi_controller;
+ spin_lock_bh(&hsi_ctrl->lock);
+ if (ch->flags & HSI_CH_OPEN) {
+ dev_err(&dev->device, "Port %d Channel %d already OPENED\n",
+ dev->n_p, dev->n_ch);
+ spin_unlock_bh(&hsi_ctrl->lock);
+ return -EBUSY;
+ }
+ clk_enable(hsi_ctrl->hsi_clk);
+ ch->flags |= HSI_CH_OPEN;
+
+ hsi_outl_or(HSI_ERROROCCURED | HSI_BREAKDETECTED, hsi_ctrl->base,
+ HSI_SYS_MPU_ENABLE_REG(port->port_number, port->n_irq));
+ /* NOTE: error and break are port events and do not need to be
+ * enabled for HSI extended enable register */
+
+ clk_disable(hsi_ctrl->hsi_clk);
+ spin_unlock_bh(&hsi_ctrl->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(hsi_open);
+
+/**
+ * hsi_write - write data into the hsi device channel
+ * @dev - reference to the hsi device channel to write into.
+ * @addr - pointer to a 32-bit word data to be written.
+ * @size - number of 32-bit word to be written.
+ *
+ * Return 0 on sucess, a negative value on failure.
+ * A success value only indicates that the request has been accepted.
+ * Transfer is only completed when the write_done callback is called.
+ *
+ */
+int hsi_write(struct hsi_device *dev, u32 *addr, unsigned int size)
+{
+ struct hsi_channel *ch;
+ int err;
+
+ if (unlikely(!dev || !dev->ch || !addr || (size <= 0))) {
+ dev_err(&dev->device, "Wrong paramenters "
+ "hsi_device %p data %p count %d", dev, addr, size);
+ return -EINVAL;
+ }
+ if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) {
+ dev_err(&dev->device, "HSI device NOT open\n");
+ return -EINVAL;
+ }
+
+ ch = dev->ch;
+ spin_lock_bh(&ch->hsi_port->hsi_controller->lock);
+ ch->write_data.addr = addr;
+ ch->write_data.size = size;
+
+ if (size == 1)
+ err = hsi_driver_write_interrupt(ch, addr);
+ else
+ err = hsi_driver_write_dma(ch, addr, size);
+
+ if (unlikely(err < 0)) {
+ ch->write_data.addr = NULL;
+ ch->write_data.size = 0;
+ }
+ spin_unlock_bh(&ch->hsi_port->hsi_controller->lock);
+
+ return err;
+
+}
+EXPORT_SYMBOL(hsi_write);
+
+/**
+ * hsi_read - read data from the hsi device channel
+ * @dev - hsi device channel reference to read data from.
+ * @addr - pointer to a 32-bit word data to store the data.
+ * @size - number of 32-bit word to be stored.
+ *
+ * Return 0 on sucess, a negative value on failure.
+ * A success value only indicates that the request has been accepted.
+ * Data is only available in the buffer when the read_done callback is called.
+ *
+ */
+int hsi_read(struct hsi_device *dev, u32 *addr, unsigned int size)
+{
+ struct hsi_channel *ch;
+ int err;
+
+ if (unlikely(!dev || !dev->ch || !addr || (size <= 0))) {
+ dev_err(&dev->device, "Wrong paramenters "
+ "hsi_device %p data %p count %d", dev, addr, size);
+ return -EINVAL;
+ }
+ if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) {
+ dev_err(&dev->device, "HSI device NOT open\n");
+ return -EINVAL;
+ }
+
+ ch = dev->ch;
+ spin_lock_bh(&ch->hsi_port->hsi_controller->lock);
+ ch->read_data.addr = addr;
+ ch->read_data.size = size;
+
+ if (size == 1)
+ err = hsi_driver_read_interrupt(ch, addr);
+ else
+ err = hsi_driver_read_dma(ch, addr, size);
+
+ if (unlikely(err < 0)) {
+ ch->read_data.addr = NULL;
+ ch->read_data.size = 0;
+ }
+ spin_unlock_bh(&ch->hsi_port->hsi_controller->lock);
+
+ return err;
+}
+EXPORT_SYMBOL(hsi_read);
+
+void __hsi_write_cancel(struct hsi_channel *ch)
+{
+ if (ch->write_data.size == 1)
+ hsi_driver_cancel_write_interrupt(ch);
+ else if (ch->write_data.size > 1)
+ hsi_driver_cancel_write_dma(ch);
+
+}
+/**
+ * hsi_write_cancel - Cancel pending write request.
+ * @dev - hsi device channel where to cancel the pending write.
+ *
+ * write_done() callback will not be called after sucess of this function.
+ */
+void hsi_write_cancel(struct hsi_device *dev)
+{
+ if (unlikely(!dev || !dev->ch)) {
+ pr_err(LOG_NAME "Wrong HSI device %p\n", dev);
+ return;
+ }
+ if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) {
+ dev_err(&dev->device, "HSI device NOT open\n");
+ return;
+ }
+
+ spin_lock_bh(&dev->ch->hsi_port->hsi_controller->lock);
+ __hsi_write_cancel(dev->ch);
+ spin_unlock_bh(&dev->ch->hsi_port->hsi_controller->lock);
+}
+EXPORT_SYMBOL(hsi_write_cancel);
+
+void __hsi_read_cancel(struct hsi_channel *ch)
+{
+ if (ch->read_data.size == 1)
+ hsi_driver_cancel_read_interrupt(ch);
+ else if (ch->read_data.size > 1)
+ hsi_driver_cancel_read_dma(ch);
+}
+
+/**
+ * hsi_read_cancel - Cancel pending read request.
+ * @dev - hsi device channel where to cancel the pending read.
+ *
+ * read_done() callback will not be called after sucess of this function.
+ */
+void hsi_read_cancel(struct hsi_device *dev)
+{
+ if (unlikely(!dev || !dev->ch)) {
+ pr_err(LOG_NAME "Wrong HSI device %p\n", dev);
+ return;
+ }
+
+ if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) {
+ dev_err(&dev->device, "HSI device NOT open\n");
+ return;
+ }
+
+ spin_lock_bh(&dev->ch->hsi_port->hsi_controller->lock);
+ __hsi_read_cancel(dev->ch);
+ spin_unlock_bh(&dev->ch->hsi_port->hsi_controller->lock);
+
+}
+EXPORT_SYMBOL(hsi_read_cancel);
+
+/**
+ * hsi_poll - HSI poll
+ * @dev - hsi device channel reference to apply the I/O control
+ * (or port associated to it)
+ *
+ * Return 0 on sucess, a negative value on failure.
+ *
+ */
+int hsi_poll(struct hsi_device *dev)
+{
+ struct hsi_channel *ch;
+ int err;
+
+ if (unlikely(!dev || !dev->ch))
+ return -EINVAL;
+
+ if (unlikely(!(dev->ch->flags & HSI_CH_OPEN))) {
+ dev_err(&dev->device, "HSI device NOT open\n");
+ return -EINVAL;
+ }
+
+ ch = dev->ch;
+ spin_lock_bh(&ch->hsi_port->hsi_controller->lock);
+ ch->flags |= HSI_CH_RX_POLL;
+ err = hsi_driver_read_interrupt(ch, NULL);
+ spin_unlock_bh(&ch->hsi_port->hsi_controller->lock);
+
+ return err;
+}
+EXPORT_SYMBOL(hsi_poll);
+
+
+/**
+ * hsi_ioctl - HSI I/O control
+ * @dev - hsi device channel reference to apply the I/O control
+ * (or port associated to it)
+ * @command - HSI I/O control command
+ * @arg - parameter associated to the control command. NULL, if no parameter.
+ *
+ * Return 0 on sucess, a negative value on failure.
+ *
+ */
+int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg)
+{
+ struct hsi_channel *ch;
+ struct hsi_dev *hsi_ctrl;
+ void __iomem *base;
+ unsigned int port, channel;
+ u32 wake;
+ int err = 0;
+
+ if (unlikely((!dev) ||
+ (!dev->ch) ||
+ (!dev->ch->hsi_port) ||
+ (!dev->ch->hsi_port->hsi_controller)) ||
+ (!(dev->ch->flags & HSI_CH_OPEN))) {
+ pr_err(LOG_NAME "HSI IOCTL Invalid parameter\n");
+ return -EINVAL;
+ }
+
+ ch = dev->ch;
+ hsi_ctrl = ch->hsi_port->hsi_controller;
+ port = ch->hsi_port->port_number;
+ channel = ch->channel_number;
+ base = hsi_ctrl->base;
+ clk_enable(hsi_ctrl->hsi_clk);
+
+ switch (command) {
+ case HSI_IOCTL_WAKE_UP:
+ /* We only claim once the wake line per channel */
+ wake = hsi_inl(base, HSI_SYS_WAKE_REG(port));
+ if (!(wake & HSI_WAKE(channel))) {
+ clk_enable(hsi_ctrl->hsi_clk);
+ hsi_outl(HSI_WAKE(channel), base,
+ HSI_SYS_SET_WAKE_REG(port));
+ }
+ break;
+ case HSI_IOCTL_WAKE_DOWN:
+ wake = hsi_inl(base, HSI_SYS_WAKE_REG(port));
+ if ((wake & HSI_WAKE(channel))) {
+ hsi_outl(HSI_WAKE(channel), base,
+ HSI_SYS_CLEAR_WAKE_REG(port));
+ clk_disable(hsi_ctrl->hsi_clk);
+ }
+ break;
+ case HSI_IOCTL_SEND_BREAK:
+ hsi_outl(1, base, HSI_HST_BREAK_REG(port));
+ break;
+ case HSI_IOCTL_WAKE:
+ if (arg == NULL)
+ err = -EINVAL;
+ else
+ *(u32 *)arg = hsi_inl(base, HSI_SYS_WAKE_REG(port));
+ break;
+ case HSI_IOCTL_FLUSH_RX:
+ hsi_outl(0, base, HSI_HSR_RXSTATE_REG(port));
+ break;
+ case HSI_IOCTL_FLUSH_TX:
+ hsi_outl(0, base, HSI_HST_TXSTATE_REG(port));
+ break;
+ case HSI_IOCTL_CAWAKE:
+ if (!arg) {
+ err = -EINVAL;
+ goto out;
+ }
+ if (dev->ch->hsi_port->cawake_gpio < 0) {
+ err = -ENODEV;
+ goto out;
+ }
+ *(unsigned int *)arg = hsi_cawake(dev->ch->hsi_port);
+ break;
+ case HSI_IOCTL_SET_RX:
+ if (!arg) {
+ err = -EINVAL;
+ goto out;
+ }
+ err = hsi_set_rx(dev->ch->hsi_port, (struct hsr_ctx *)arg);
+ break;
+ case HSI_IOCTL_GET_RX:
+ if (!arg) {
+ err = -EINVAL;
+ goto out;
+ }
+ hsi_get_rx(dev->ch->hsi_port, (struct hsr_ctx *)arg);
+ break;
+ case HSI_IOCTL_SET_TX:
+ if (!arg) {
+ err = -EINVAL;
+ goto out;
+ }
+ err = hsi_set_tx(dev->ch->hsi_port, (struct hst_ctx *)arg);
+ break;
+ case HSI_IOCTL_GET_TX:
+ if (!arg) {
+ err = -EINVAL;
+ goto out;
+ }
+ hsi_get_tx(dev->ch->hsi_port, (struct hst_ctx *)arg);
+ break;
+ default:
+ err = -ENOIOCTLCMD;
+ break;
+ }
+out:
+ clk_disable(hsi_ctrl->hsi_clk);
+
+ return err;
+}
+EXPORT_SYMBOL(hsi_ioctl);
+
+/**
+ * hsi_close - close given hsi device channel
+ * @dev - reference to hsi device channel.
+ */
+void hsi_close(struct hsi_device *dev)
+{
+ if (!dev || !dev->ch) {
+ pr_err(LOG_NAME "Trying to close wrong HSI device %p\n", dev);
+ return;
+ }
+
+ spin_lock_bh(&dev->ch->hsi_port->hsi_controller->lock);
+ if (dev->ch->flags & HSI_CH_OPEN) {
+ dev->ch->flags &= ~HSI_CH_OPEN;
+ __hsi_write_cancel(dev->ch);
+ __hsi_read_cancel(dev->ch);
+ }
+ spin_unlock_bh(&dev->ch->hsi_port->hsi_controller->lock);
+}
+EXPORT_SYMBOL(hsi_close);
+
+/**
+ * hsi_set_read_cb - register read_done() callback.
+ * @dev - reference to hsi device channel where the callback is associated to.
+ * @read_cb - callback to signal read transfer completed.
+ *
+ * NOTE: Write callback must be only set when channel is not open !
+ */
+void hsi_set_read_cb(struct hsi_device *dev,
+ void (*read_cb)(struct hsi_device *dev, unsigned int size))
+{
+ dev->ch->read_done = read_cb;
+}
+EXPORT_SYMBOL(hsi_set_read_cb);
+
+/**
+ * hsi_set_read_cb - register write_done() callback.
+ * @dev - reference to hsi device channel where the callback is associated to.
+ * @write_cb - callback to signal read transfer completed.
+ *
+ * NOTE: Read callback must be only set when channel is not open !
+ */
+void hsi_set_write_cb(struct hsi_device *dev,
+ void (*write_cb)(struct hsi_device *dev, unsigned int size))
+{
+ dev->ch->write_done = write_cb;
+}
+EXPORT_SYMBOL(hsi_set_write_cb);
+
+/**
+ * hsi_set_port_event_cb - register port_event callback.
+ * @dev - reference to hsi device channel where the callback is associated to.
+ * @port_event_cb - callback to signal events from the channel port.
+ */
+void hsi_set_port_event_cb(struct hsi_device *dev,
+ void (*port_event_cb)(struct hsi_device *dev,
+ unsigned int event, void *arg))
+{
+ write_lock_bh(&dev->ch->rw_lock);
+ dev->ch->port_event = port_event_cb;
+ write_unlock_bh(&dev->ch->rw_lock);
+}
+EXPORT_SYMBOL(hsi_set_port_event_cb);
diff --git a/include/linux/hsi_driver_if.h b/include/linux/hsi_driver_if.h
new file mode 100644
index 0000000..9d9ae69
--- /dev/null
+++ b/include/linux/hsi_driver_if.h
@@ -0,0 +1,180 @@
+/*
+ * hsi_driver_if.h
+ *
+ * Header 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@nokia.com>
+ * Author: Sebastien JAN <s-jan@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_IF_H__
+#define __HSI_DRIVER_IF_H__
+
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/notifier.h>
+
+/* The number of ports handled by the driver (MAX:2). Reducing this value
+ * optimizes the driver memory footprint.
+ */
+#define HSI_MAX_PORTS 1
+
+/* bit-field definition for allowed controller IDs and channels */
+#define ANY_HSI_CONTROLLER -1
+
+/* HSR special divisor values set to control the auto-divisor Rx mode */
+#define HSI_HSR_DIVISOR_AUTO 0x1000 /* Activate auto Rx */
+#define HSI_SSR_DIVISOR_USE_TIMEOUT 0x1001 /* De-activate auto-Rx for SSI */
+
+enum {
+ HSI_EVENT_BREAK_DETECTED = 0,
+ HSI_EVENT_ERROR,
+ HSI_EVENT_PRE_SPEED_CHANGE,
+ HSI_EVENT_POST_SPEED_CHANGE,
+ HSI_EVENT_CAWAKE_UP,
+ HSI_EVENT_CAWAKE_DOWN,
+ HSI_EVENT_HSR_DATAAVAILABLE,
+};
+
+enum {
+ HSI_IOCTL_WAKE_UP,
+ HSI_IOCTL_WAKE_DOWN,
+ HSI_IOCTL_SEND_BREAK,
+ HSI_IOCTL_WAKE,
+ HSI_IOCTL_FLUSH_RX,
+ HSI_IOCTL_FLUSH_TX,
+ HSI_IOCTL_CAWAKE,
+ HSI_IOCTL_SET_RX,
+ HSI_IOCTL_GET_RX,
+ HSI_IOCTL_SET_TX,
+ HSI_IOCTL_GET_TX,
+};
+
+/* Forward references */
+struct hsi_device;
+struct hsi_channel;
+
+/* DPS */
+struct hst_ctx {
+ u32 mode;
+ u32 flow;
+ u32 frame_size;
+ u32 divisor;
+ u32 arb_mode;
+ u32 channels;
+};
+
+struct hsr_ctx {
+ u32 mode;
+ u32 flow;
+ u32 frame_size;
+ u32 divisor;
+ u32 timeout;
+ u32 channels;
+};
+
+struct port_ctx {
+ u32 sys_mpu_enable[2];
+ struct hst_ctx hst;
+ struct hsr_ctx hsr;
+};
+
+/**
+ * struct ctrl_ctx - hsi controller regs context
+ * @loss_count: hsi last loss count
+ * @sysconfig: keeps sysconfig reg state
+ * @gdd_gcr: keeps gcr reg state
+ * @pctx: array of port context
+ */
+struct ctrl_ctx {
+ int loss_count;
+ u32 sysconfig;
+ u32 gdd_gcr;
+ struct port_ctx *pctx;
+};
+/* END DPS */
+
+struct hsi_platform_data {
+ 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);
+ u8 num_ports;
+ struct ctrl_ctx ctx;
+};
+
+/**
+ * struct hsi_device - HSI device object
+ * @n_ctrl: associated HSI controller platform id number
+ * @n_p: port number
+ * @n_ch: channel number
+ * @modalias: [to be removed]
+ * @ch: channel descriptor
+ * @device: associated device
+*/
+struct hsi_device {
+ int n_ctrl;
+ unsigned int n_p;
+ unsigned int n_ch;
+ struct hsi_channel *ch;
+ struct device device;
+};
+
+#define to_hsi_device(dev) container_of(dev, struct hsi_device, device)
+
+/**
+ * struct hsi_device_driver - HSI driver instance container
+ * @ctrl_mask: bit-field indicating the supported HSI device ids
+ * @ch_mask: bit-field indicating enabled channels for this port
+ * @probe: probe callback (driver registering)
+ * @remove: remove callback (driver un-registering)
+ * @suspend: suspend callback
+ * @resume: resume callback
+ * @driver: associated device_driver object
+*/
+struct hsi_device_driver {
+ unsigned long ctrl_mask;
+ unsigned long ch_mask[HSI_MAX_PORTS];
+ int (*probe)(struct hsi_device *dev);
+ int (*remove)(struct hsi_device *dev);
+ int (*suspend)(struct hsi_device *dev,
+ pm_message_t mesg);
+ int (*resume)(struct hsi_device *dev);
+ struct device_driver driver;
+};
+
+#define to_hsi_device_driver(drv) container_of(drv, \
+ struct hsi_device_driver, \
+ driver)
+
+int hsi_register_driver(struct hsi_device_driver *driver);
+void hsi_unregister_driver(struct hsi_device_driver *driver);
+int hsi_open(struct hsi_device *dev);
+int hsi_write(struct hsi_device *dev, u32 *addr, unsigned int size);
+void hsi_write_cancel(struct hsi_device *dev);
+int hsi_read(struct hsi_device *dev, u32 *addr, unsigned int size);
+void hsi_read_cancel(struct hsi_device *dev);
+int hsi_poll(struct hsi_device *dev);
+int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg);
+void hsi_close(struct hsi_device *dev);
+void hsi_set_read_cb(struct hsi_device *dev,
+ void (*read_cb)(struct hsi_device *dev, unsigned int size));
+void hsi_set_write_cb(struct hsi_device *dev,
+ void (*write_cb)(struct hsi_device *dev, unsigned int size));
+void hsi_set_port_event_cb(struct hsi_device *dev,
+ void (*port_event_cb)(struct hsi_device *dev,
+ unsigned int event, void *arg));
+#endif /* __HSI_DRIVER_IF_H__ */
--
1.6.0.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [RFC PATCH 1/9] HSI: Low Level Driver interface
2009-10-30 10:00 ` [RFC PATCH 1/9] HSI: Low Level Driver interface Sebastien Jan
@ 2009-10-30 12:49 ` Felipe Balbi
0 siblings, 0 replies; 15+ messages in thread
From: Felipe Balbi @ 2009-10-30 12:49 UTC (permalink / raw)
To: ext Sebastien Jan
Cc: linux-omap@vger.kernel.org, Chinea Carlos (Nokia-D/Helsinki)
Hi,
On Fri, Oct 30, 2009 at 11:00:00AM +0100, ext Sebastien Jan wrote:
> diff --git a/drivers/Makefile b/drivers/Makefile
> index 086857c..05b4190 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -111,3 +111,4 @@ obj-$(CONFIG_VLYNQ) += vlynq/
> obj-$(CONFIG_STAGING) += staging/
> obj-y += platform/
> obj-y += ieee802154/
> +obj-$(CONFIG_OMAP_HSI) += hsi/
not omap specific.
> diff --git a/drivers/hsi/Kconfig b/drivers/hsi/Kconfig
> new file mode 100644
> index 0000000..e10b522
> --- /dev/null
> +++ b/drivers/hsi/Kconfig
> @@ -0,0 +1,44 @@
> +#
> +# OMAP HSI driver configuration
this shouldn't be omap specific. The comment is misleading.
> diff --git a/drivers/hsi/Makefile b/drivers/hsi/Makefile
> new file mode 100644
> index 0000000..a7f579b
> --- /dev/null
> +++ b/drivers/hsi/Makefile
> @@ -0,0 +1,14 @@
> +#
> +# Makefile for HSI drivers
> +#
> +EXTRA_CFLAGS :=
> +
> +omap_hsi-objs := hsi_driver.o hsi_driver_dma.o hsi_driver_int.o \
> + hsi_driver_if.o hsi_driver_bus.o hsi_driver_gpio.o \
> + hsi_driver_fifo.o
this should a more generic bus driver and it shouldn't know about how
the hardware handles e.g. reset signals.
What I suggest you to do is:
Bus Layer
hsi.ko -> MIPI HSI Bus Driver (like i2c-core.c or usbcore, etc etc)
HW Layer
hsi-omap.ko-> OMAP HSI Controller (like i2c-omap.c)
Clients Layer
any protocol driver using the HSI bus
> diff --git a/drivers/hsi/hsi_driver_if.c b/drivers/hsi/hsi_driver_if.c
> new file mode 100644
> index 0000000..fb0035d
> --- /dev/null
> +++ b/drivers/hsi/hsi_driver_if.c
> @@ -0,0 +1,657 @@
> +/*
> + * hsi_driver_if.c
> + *
> + * Implements HSI hardware driver interfaces for the upper layers.
> + *
> + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
> + * Copyright (C) 2009 Texas Instruments, Inc.
> + *
> + * Author: Carlos Chinea <carlos.chinea@nokia.com>
> + * Author: Sebastien JAN <s-jan@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 "hsi_driver.h"
should be including something like:
#include <linux/hsi/driver.h>
or even
#include <linux/hsi.h>
> +int hsi_register_driver(struct hsi_device_driver *driver);
> +void hsi_unregister_driver(struct hsi_device_driver *driver);
> +int hsi_open(struct hsi_device *dev);
> +int hsi_write(struct hsi_device *dev, u32 *addr, unsigned int size);
> +void hsi_write_cancel(struct hsi_device *dev);
> +int hsi_read(struct hsi_device *dev, u32 *addr, unsigned int size);
> +void hsi_read_cancel(struct hsi_device *dev);
> +int hsi_poll(struct hsi_device *dev);
> +int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg);
> +void hsi_close(struct hsi_device *dev);
are these used on some file_operations structure ?
they should be implemented by the client driver if when it needs instead
of letting the bus driver dictacte so much of the design.
> +void hsi_set_read_cb(struct hsi_device *dev,
> + void (*read_cb)(struct hsi_device *dev, unsigned int size));
> +void hsi_set_write_cb(struct hsi_device *dev,
> + void (*write_cb)(struct hsi_device *dev, unsigned int size));
> +void hsi_set_port_event_cb(struct hsi_device *dev,
> + void (*port_event_cb)(struct hsi_device *dev,
> + unsigned int event, void *arg));
I wouldn't do it like this. If this is what I'm guessing, you're using
these functions to be sure when a transfer has completed like ?
I remember suggesting this to be done more like the usb gadget
framework:
struct hsi_request {
void *buf;
dma_addr_t dma;
unsigned length;
unsigned direction;
void (*complete)(struct hsi_device *hsi, struct
hsi_channel *channel, struct
hsi_request *req);
};
then, you send the data and when you get a complete irq, you call
request->complete();
maybe that would be better ??
--
balbi
^ permalink raw reply [flat|nested] 15+ messages in thread
* [RFC PATCH 2/9] HSI: Low Level Driver core
2009-10-30 9:59 [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Sebastien Jan
2009-10-30 10:00 ` [RFC PATCH 1/9] HSI: Low Level Driver interface Sebastien Jan
@ 2009-10-30 10:00 ` Sebastien Jan
2009-10-30 12:56 ` Felipe Balbi
2009-10-30 10:00 ` [RFC PATCH 3/9] HSI: Low Level Driver device management Sebastien Jan
` (7 subsequent siblings)
9 siblings, 1 reply; 15+ messages in thread
From: Sebastien Jan @ 2009-10-30 10:00 UTC (permalink / raw)
To: linux-omap; +Cc: Sebastien Jan, Carlos Chinea
Driver core implementation (driver registration, ...)
Driver types definitions
HSI bus implementation
Signed-off-by: Sebastien Jan <s-jan@ti.com>
Signed-off-by: Carlos Chinea <carlos.chinea@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.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@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.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@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.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@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
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [RFC PATCH 2/9] HSI: Low Level Driver core
2009-10-30 10:00 ` [RFC PATCH 2/9] HSI: Low Level Driver core Sebastien Jan
@ 2009-10-30 12:56 ` Felipe Balbi
0 siblings, 0 replies; 15+ messages in thread
From: Felipe Balbi @ 2009-10-30 12:56 UTC (permalink / raw)
To: ext Sebastien Jan
Cc: linux-omap@vger.kernel.org, Chinea Carlos (Nokia-D/Helsinki)
Hi,
On Fri, Oct 30, 2009 at 11:00:01AM +0100, ext Sebastien Jan wrote:
> +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;
> +}
this will make a client's probe() function to be called twice, no ??
you should/could have a hsi_new_dummy() like on i2c ??
> +static int __init hsi_request_mpu_irq(struct hsi_port *hsi_p)
this should be done by hsi-omap.ko driver.
> +static int __init hsi_request_cawake_irq(struct hsi_port *hsi_p)
ditto.
> +static int __init hsi_ports_init(struct hsi_dev *hsi_ctrl)
ditto.
> +static int __init hsi_request_gdd_irq(struct hsi_dev *hsi_ctrl)
ditto.
> +static int __init hsi_controller_init(struct hsi_dev *hsi_ctrl,
> + struct platform_device *pd)
ditto.
--
balbi
^ permalink raw reply [flat|nested] 15+ messages in thread
* [RFC PATCH 3/9] HSI: Low Level Driver device management
2009-10-30 9:59 [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Sebastien Jan
2009-10-30 10:00 ` [RFC PATCH 1/9] HSI: Low Level Driver interface Sebastien Jan
2009-10-30 10:00 ` [RFC PATCH 2/9] HSI: Low Level Driver core Sebastien Jan
@ 2009-10-30 10:00 ` Sebastien Jan
2009-10-30 10:00 ` [RFC PATCH 4/9] HSI: Low Level Driver debugfs support Sebastien Jan
` (6 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Sebastien Jan @ 2009-10-30 10:00 UTC (permalink / raw)
To: linux-omap; +Cc: Sebastien Jan, Carlos Chinea
Low-level managment of interrupts, DMA, GPIO (SSI) and FIFO (HSI).
Signed-off-by: Sebastien Jan <s-jan@ti.com>
Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com>
---
drivers/hsi/hsi_driver_dma.c | 469 +++++++++++++++++++++++++++++++++++++++++
drivers/hsi/hsi_driver_fifo.c | 289 +++++++++++++++++++++++++
drivers/hsi/hsi_driver_gpio.c | 79 +++++++
drivers/hsi/hsi_driver_int.c | 339 +++++++++++++++++++++++++++++
4 files changed, 1176 insertions(+), 0 deletions(-)
create mode 100644 drivers/hsi/hsi_driver_dma.c
create mode 100644 drivers/hsi/hsi_driver_fifo.c
create mode 100644 drivers/hsi/hsi_driver_gpio.c
create mode 100644 drivers/hsi/hsi_driver_int.c
diff --git a/drivers/hsi/hsi_driver_dma.c b/drivers/hsi/hsi_driver_dma.c
new file mode 100644
index 0000000..4514e98
--- /dev/null
+++ b/drivers/hsi/hsi_driver_dma.c
@@ -0,0 +1,469 @@
+/*
+ * hsi_driver_dma.c
+ *
+ * Implements HSI low level interface driver functionality with DMA support.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@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/dma-mapping.h>
+#include "hsi_driver.h"
+
+#define HSI_SYNC_WRITE 0
+#define HSI_SYNC_READ 1
+#define HSI_L3_TPUT 13428 /* 13428 KiB/s => ~110 Mbit/s*/
+
+static unsigned char hsi_sync_table[2][2][8] = {
+ {
+ {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
+ {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00}
+ }, {
+ {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17},
+ {0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}
+ }
+};
+
+/**
+ * hsi_get_free_lch - Get a free GDD(DMA)logical channel
+ * @hsi_ctrl- HSI controller of the GDD.
+ *
+ * Needs to be called holding the hsi_controller lock
+ *
+ * Return a free logical channel number. If there is no free lch
+ * then returns an out of range value
+ */
+static unsigned int hsi_get_free_lch(struct hsi_dev *hsi_ctrl)
+{
+ unsigned int enable_reg;
+ unsigned int i;
+ unsigned int lch = hsi_ctrl->last_gdd_lch;
+
+ enable_reg = hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ for (i = 1; i <= hsi_ctrl->gdd_chan_count; i++) {
+ lch = (lch + i) & (hsi_ctrl->gdd_chan_count - 1);
+ if (!(enable_reg & HSI_GDD_LCH(lch))) {
+ hsi_ctrl->last_gdd_lch = lch;
+ return lch;
+ }
+ }
+
+ return lch;
+}
+
+/**
+ * hsi_driver_write_dma - Program GDD [DMA] to write data from memory to
+ * the hsi channel buffer.
+ * @hsi_channel - pointer to the hsi_channel to write data to.
+ * @data - 32-bit word pointer to the data.
+ * @size - Number of 32bit words to be transfered.
+ *
+ * hsi_controller lock must be held before calling this function.
+ *
+ * Return 0 on success and < 0 on error.
+ */
+int hsi_driver_write_dma(struct hsi_channel *hsi_channel, u32 *data,
+ unsigned int size)
+{
+ struct hsi_dev *hsi_ctrl = hsi_channel->hsi_port->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int port = hsi_channel->hsi_port->port_number;
+ unsigned int channel = hsi_channel->channel_number;
+ unsigned int sync;
+ long buff_offset;
+ int lch;
+ dma_addr_t dma_data;
+ dma_addr_t s_addr;
+ u16 tmp;
+
+ if ((size < 1) || (data == NULL))
+ return -EINVAL;
+
+ clk_enable(hsi_ctrl->hsi_clk);
+
+ lch = hsi_get_free_lch(hsi_ctrl);
+ if (lch >= hsi_ctrl->gdd_chan_count) {
+ dev_err(hsi_ctrl->dev, "No free GDD logical channels.\n");
+ clk_disable(hsi_ctrl->hsi_clk);
+ return -EBUSY; /* No free GDD logical channels. */
+ }
+
+ /* NOTE: Getting a free gdd logical channel and
+ * reserve it must be done atomicaly. */
+ hsi_channel->write_data.lch = lch;
+
+ /* Sync is required for SSI but not for HSI */
+ sync = hsi_sync_table[HSI_SYNC_WRITE][port - 1][channel];
+
+ dma_data = dma_map_single(hsi_ctrl->dev, data, size * 4,
+ DMA_TO_DEVICE);
+
+ tmp = HSI_SRC_SINGLE_ACCESS0 |
+ HSI_SRC_MEMORY_PORT |
+ HSI_DST_SINGLE_ACCESS0 |
+ HSI_DST_PERIPHERAL_PORT |
+ HSI_DATA_TYPE_S32;
+ hsi_outw(tmp, base, HSI_GDD_CSDP_REG(lch));
+
+ tmp = HSI_SRC_AMODE_POSTINC | HSI_DST_AMODE_CONST | sync;
+ hsi_outw(tmp, base, HSI_GDD_CCR_REG(lch));
+
+ hsi_outw((HSI_BLOCK_IE | HSI_TOUT_IE), base, HSI_GDD_CICR_REG(lch));
+
+ buff_offset = hsi_hst_buffer_reg(hsi_ctrl, port, channel);
+ if (buff_offset < 0)
+ return buff_offset;
+ s_addr = hsi_ctrl->phy_base + buff_offset;
+
+ hsi_outl(s_addr, base, HSI_GDD_CDSA_REG(lch));
+
+ hsi_outl(dma_data, base, HSI_GDD_CSSA_REG(lch));
+ hsi_outw(size, base, HSI_GDD_CEN_REG(lch));
+
+ hsi_outl_or(HSI_GDD_LCH(lch), base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ hsi_outw_or(HSI_CCR_ENABLE, base, HSI_GDD_CCR_REG(lch));
+
+ return 0;
+}
+
+/**
+ * hsi_driver_read_dma - Program GDD [DMA] to write data to memory from
+ * the hsi channel buffer.
+ * @hsi_channel - pointer to the hsi_channel to read data from.
+ * @data - 32-bit word pointer where to store the incoming data.
+ * @size - Number of 32bit words to be transfered to the buffer.
+ *
+ * hsi_controller lock must be held before calling this function.
+ *
+ * Return 0 on success and < 0 on error.
+ */
+int hsi_driver_read_dma(struct hsi_channel *hsi_channel, u32 *data,
+ unsigned int count)
+{
+ struct hsi_dev *hsi_ctrl = hsi_channel->hsi_port->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int port = hsi_channel->hsi_port->port_number;
+ unsigned int channel = hsi_channel->channel_number;
+ unsigned int sync;
+ unsigned int lch;
+ long buff_offset;
+ dma_addr_t dma_data;
+ dma_addr_t d_addr;
+ u16 tmp;
+
+ clk_enable(hsi_ctrl->hsi_clk);
+ lch = hsi_get_free_lch(hsi_ctrl);
+ if (lch >= hsi_ctrl->gdd_chan_count) {
+ dev_err(hsi_ctrl->dev, "No free GDD logical channels.\n");
+ clk_disable(hsi_ctrl->hsi_clk);
+ return -EBUSY; /* No free GDD logical channels. */
+ }
+
+ /* When DMA is used for Rx, disable the Rx Interrupt.
+ * (else DATAAVAILLABLE event would get triggered on first
+ * received data word)
+ * (By default, Rx interrupt is active for polling feature)
+ */
+ hsi_driver_disable_read_interrupt(hsi_channel);
+
+ /*
+ * NOTE: Gettting a free gdd logical channel and
+ * reserve it must be done atomicaly.
+ */
+ hsi_channel->read_data.lch = lch;
+
+ /* Sync is required for SSI but not for HSI */
+ sync = hsi_sync_table[HSI_SYNC_READ][port - 1][channel];
+
+ dma_data = dma_map_single(hsi_ctrl->dev, data, count * 4,
+ DMA_FROM_DEVICE);
+
+ tmp = HSI_DST_SINGLE_ACCESS0 |
+ HSI_DST_MEMORY_PORT |
+ HSI_SRC_SINGLE_ACCESS0 |
+ HSI_SRC_PERIPHERAL_PORT |
+ HSI_DATA_TYPE_S32;
+ hsi_outw(tmp, base, HSI_GDD_CSDP_REG(lch));
+
+ tmp = HSI_DST_AMODE_POSTINC | HSI_SRC_AMODE_CONST | sync;
+ hsi_outw(tmp, base, HSI_GDD_CCR_REG(lch));
+
+ hsi_outw((HSI_BLOCK_IE | HSI_TOUT_IE), base, HSI_GDD_CICR_REG(lch));
+
+ buff_offset = hsi_hsr_buffer_reg(hsi_ctrl, port, channel);
+ if (buff_offset < 0)
+ return buff_offset;
+ d_addr = hsi_ctrl->phy_base + buff_offset;
+
+ hsi_outl(d_addr, base, HSI_GDD_CSSA_REG(lch));
+
+ hsi_outl(dma_data, base, HSI_GDD_CDSA_REG(lch));
+ hsi_outw(count, base, HSI_GDD_CEN_REG(lch));
+
+ hsi_outl_or(HSI_GDD_LCH(lch), base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ hsi_outw_or(HSI_CCR_ENABLE, base, HSI_GDD_CCR_REG(lch));
+
+ return 0;
+}
+
+void hsi_driver_cancel_write_dma(struct hsi_channel *hsi_ch)
+{
+ int lch = hsi_ch->write_data.lch;
+ unsigned int port = hsi_ch->hsi_port->port_number;
+ unsigned int channel = hsi_ch->channel_number;
+ struct hsi_dev *hsi_ctrl = hsi_ch->hsi_port->hsi_controller;
+ u32 ccr;
+ long buff_offset;
+
+ if (lch < 0)
+ return;
+
+ clk_enable(hsi_ctrl->hsi_clk);
+ ccr = hsi_inw(hsi_ctrl->base, HSI_GDD_CCR_REG(lch));
+ if (!(ccr & HSI_CCR_ENABLE)) {
+ dev_dbg(&hsi_ch->dev->device, LOG_NAME "Write cancel on not "
+ "enabled logical channel %d CCR REG 0x%08X\n", lch, ccr);
+ clk_disable(hsi_ctrl->hsi_clk);
+ return;
+ }
+
+ hsi_outw_and(~HSI_CCR_ENABLE, hsi_ctrl->base, HSI_GDD_CCR_REG(lch));
+ hsi_outl_and(~HSI_GDD_LCH(lch), hsi_ctrl->base,
+ HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ hsi_outl(HSI_GDD_LCH(lch), hsi_ctrl->base,
+ HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+
+ buff_offset = hsi_hst_bufstate_f_reg(hsi_ctrl, port, channel);
+ if (buff_offset >= 0)
+ hsi_outl_and(~HSI_BUFSTATE_CHANNEL(channel), hsi_ctrl->base,
+ buff_offset);
+
+ hsi_reset_ch_write(hsi_ch);
+ clk_disable(hsi_ctrl->hsi_clk);
+ clk_disable(hsi_ctrl->hsi_clk); /* FIXME - check if can be removed */
+}
+
+void hsi_driver_cancel_read_dma(struct hsi_channel *hsi_ch)
+{
+ int lch = hsi_ch->read_data.lch;
+ struct hsi_dev *hsi_ctrl = hsi_ch->hsi_port->hsi_controller;
+ unsigned int port = hsi_ch->hsi_port->port_number;
+ unsigned int channel = hsi_ch->channel_number;
+ u32 reg;
+ long buff_offset;
+
+ if (lch < 0)
+ return;
+
+ /* DMA transfer is over, re-enable default mode
+ * (Interrupts for polling feature)
+ */
+ hsi_driver_read_interrupt(hsi_ch, NULL);
+
+ clk_enable(hsi_ctrl->hsi_clk);
+ reg = hsi_inw(hsi_ctrl->base, HSI_GDD_CCR_REG(lch));
+ if (!(reg & HSI_CCR_ENABLE)) {
+ dev_dbg(&hsi_ch->dev->device, LOG_NAME "Read cancel on not "
+ "enable logical channel %d CCR REG 0x%08X\n", lch, reg);
+ clk_disable(hsi_ctrl->hsi_clk);
+ return;
+ }
+
+ hsi_outw_and(~HSI_CCR_ENABLE, hsi_ctrl->base, HSI_GDD_CCR_REG(lch));
+ hsi_outl_and(~HSI_GDD_LCH(lch), hsi_ctrl->base,
+ HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ hsi_outl(HSI_GDD_LCH(lch), hsi_ctrl->base,
+ HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+
+ buff_offset = hsi_hsr_bufstate_f_reg(hsi_ctrl, port, channel);
+ if (buff_offset >= 0)
+ hsi_outl_and(~HSI_BUFSTATE_CHANNEL(channel), hsi_ctrl->base,
+ buff_offset);
+
+ hsi_reset_ch_read(hsi_ch);
+ clk_disable(hsi_ctrl->hsi_clk);
+ clk_disable(hsi_ctrl->hsi_clk); /* FIXME - check if can be removed */
+}
+
+/**
+ * hsi_get_info_from_gdd_lch - Retrieve channels information from DMA channel
+ * @hsi_ctrl - HSI device control structure
+ * @lch - DMA logical channel
+ * @port - HSI port
+ * @channel - HSI channel
+ * @is_read_path - channel is used for reading
+ *
+ * Updates the port, channel and is_read_path parameters depending on the
+ * lch DMA channel status.
+ *
+ * Return 0 on success and < 0 on error.
+ */
+int hsi_get_info_from_gdd_lch(struct hsi_dev *hsi_ctrl, unsigned int lch,
+ unsigned int *port, unsigned int *channel, unsigned int *is_read_path)
+{
+ int i_ports;
+ int i_chans;
+ int err = -1;
+
+ for (i_ports = 0; i_ports < HSI_MAX_PORTS; i_ports++)
+ for (i_chans = 0; i_chans < HSI_PORT_MAX_CH; i_chans++)
+ if (hsi_ctrl->hsi_port[i_ports].
+ hsi_channel[i_chans].read_data.lch == lch) {
+ *is_read_path = 1;
+ *port = i_ports + 1;
+ *channel = i_chans;
+ err = 0;
+ goto get_info_bk;
+ } else if (hsi_ctrl->hsi_port[i_ports].
+ hsi_channel[i_chans].write_data.lch == lch) {
+ *is_read_path = 0;
+ *port = i_ports + 1;
+ *channel = i_chans;
+ err = 0;
+ goto get_info_bk;
+ }
+get_info_bk:
+ return err;
+}
+
+static void do_hsi_gdd_lch(struct hsi_dev *hsi_ctrl, unsigned int gdd_lch)
+{
+ void __iomem *base = hsi_ctrl->base;
+ struct hsi_channel *ch;
+ unsigned int port;
+ unsigned int channel;
+ unsigned int is_read_path;
+ u32 gdd_csr;
+ dma_addr_t dma_h;
+ size_t size;
+
+ if (hsi_get_info_from_gdd_lch(hsi_ctrl, gdd_lch, &port, &channel,
+ &is_read_path) < 0) {
+ dev_err(hsi_ctrl->dev, "Unable to match the DMA channel %d with"
+ " an HSI channel\n", gdd_lch);
+ return;
+ }
+/* FIXME: to remove when validated: */
+ else {
+ dev_dbg(hsi_ctrl->dev, "DMA event on gdd_lch=%d => port=%d, "
+ "channel=%d, read=%d\n", gdd_lch, port, channel,
+ is_read_path);
+ }
+
+ spin_lock(&hsi_ctrl->lock);
+
+ hsi_outl_and(~HSI_GDD_LCH(gdd_lch), base,
+ HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ gdd_csr = hsi_inw(base, HSI_GDD_CSR_REG(gdd_lch));
+
+ if (!(gdd_csr & HSI_CSR_TOUT)) {
+ if (is_read_path) { /* Read path */
+ dma_h = hsi_inl(base, HSI_GDD_CDSA_REG(gdd_lch));
+ size = hsi_inw(base, HSI_GDD_CEN_REG(gdd_lch)) * 4;
+ dma_sync_single_for_cpu(hsi_ctrl->dev, dma_h, size,
+ DMA_FROM_DEVICE);
+ dma_unmap_single(hsi_ctrl->dev, dma_h, size,
+ DMA_FROM_DEVICE);
+ ch = ctrl_get_ch(hsi_ctrl, port, channel);
+ hsi_reset_ch_read(ch);
+ /* DMA transfer is over, re-enable default mode
+ * (interrupts for polling feature)
+ */
+ hsi_driver_read_interrupt(ch, NULL);
+ spin_unlock(&hsi_ctrl->lock);
+ ch->read_done(ch->dev, size);
+ } else {
+ dma_h = hsi_inl(base, HSI_GDD_CSSA_REG(gdd_lch));
+ size = hsi_inw(base, HSI_GDD_CEN_REG(gdd_lch)) * 4;
+ dma_unmap_single(hsi_ctrl->dev, dma_h, size,
+ DMA_TO_DEVICE);
+ ch = ctrl_get_ch(hsi_ctrl, port, channel);
+ hsi_reset_ch_write(ch);
+ spin_unlock(&hsi_ctrl->lock);
+ ch->write_done(ch->dev, size);
+ }
+ } else {
+ dev_err(hsi_ctrl->dev, "Error on GDD transfer "
+ "on gdd channel %d\n", gdd_lch);
+ spin_unlock(&hsi_ctrl->lock);
+ hsi_port_event_handler(&hsi_ctrl->hsi_port[port - 1],
+ HSI_EVENT_ERROR, NULL);
+ }
+
+ /* Decrease clk usecount which was increased in
+ * hsi_driver_{read,write}_dma() */
+ clk_disable(hsi_ctrl->hsi_clk);
+}
+
+static void do_hsi_gdd_tasklet(unsigned long device)
+{
+ struct hsi_dev *hsi_ctrl = (struct hsi_dev *)device;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int gdd_lch = 0;
+ u32 status_reg = 0;
+ u32 lch_served = 0;
+ unsigned int gdd_max_count = hsi_ctrl->gdd_chan_count;
+
+ clk_enable(hsi_ctrl->hsi_clk);
+
+ status_reg = hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+
+ for (gdd_lch = 0; gdd_lch < gdd_max_count; gdd_lch++) {
+ if (status_reg & HSI_GDD_LCH(gdd_lch)) {
+ do_hsi_gdd_lch(hsi_ctrl, gdd_lch);
+ lch_served |= HSI_GDD_LCH(gdd_lch);
+ }
+ }
+
+ hsi_outl(lch_served, base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+
+ status_reg = hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+ status_reg &= hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ clk_disable(hsi_ctrl->hsi_clk);
+
+ if (status_reg)
+ tasklet_hi_schedule(&hsi_ctrl->hsi_gdd_tasklet);
+ else
+ enable_irq(hsi_ctrl->gdd_irq);
+}
+
+static irqreturn_t hsi_gdd_mpu_handler(int irq, void *hsi_controller)
+{
+ struct hsi_dev *hsi_ctrl = hsi_controller;
+
+ tasklet_hi_schedule(&hsi_ctrl->hsi_gdd_tasklet);
+ disable_irq_nosync(hsi_ctrl->gdd_irq);
+
+ return IRQ_HANDLED;
+}
+
+int __init hsi_gdd_init(struct hsi_dev *hsi_ctrl, const char *irq_name)
+{
+ tasklet_init(&hsi_ctrl->hsi_gdd_tasklet, do_hsi_gdd_tasklet,
+ (unsigned long)hsi_ctrl);
+ if (request_irq(hsi_ctrl->gdd_irq, hsi_gdd_mpu_handler, IRQF_DISABLED,
+ irq_name, hsi_ctrl) < 0) {
+ dev_err(hsi_ctrl->dev, "FAILED to request GDD IRQ %d\n",
+ hsi_ctrl->gdd_irq);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+void hsi_gdd_exit(struct hsi_dev *hsi_ctrl)
+{
+ tasklet_disable(&hsi_ctrl->hsi_gdd_tasklet);
+ free_irq(hsi_ctrl->gdd_irq, hsi_ctrl);
+}
diff --git a/drivers/hsi/hsi_driver_fifo.c b/drivers/hsi/hsi_driver_fifo.c
new file mode 100644
index 0000000..33b0f7c
--- /dev/null
+++ b/drivers/hsi/hsi_driver_fifo.c
@@ -0,0 +1,289 @@
+/*
+ * hsi_driver_fifo.c
+ *
+ * Implements HSI module fifo management.
+ *
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Sebastien JAN <s-jan@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"
+
+/**
+ * hsi_fifo_get_id - Get fifo index corresponding to (port, channel)
+ * @hsi_ctrl - HSI controler data
+ * @channel - channel used
+ * @port - HSI port used
+ *
+ * Returns the fifo index associated to the provided (port, channel).
+ * Notes: 1) The fifo <=> (port, channel) correspondance depends on the selected
+ * SW strategy for channels mapping (fifo management).
+ * 2) the mapping is identical for Read and Write path.
+ * This exclusively applies to HSI devices.
+ */
+int hsi_fifo_get_id(struct hsi_dev *hsi_ctrl, unsigned int channel,
+ unsigned int port)
+{
+ int fifo_index = 0;
+ int err = 0;
+
+ if (unlikely(channel >= HSI_CHANNELS_MAX || port < 1 || port > 2)) {
+ err = -EINVAL;
+ goto fifo_id_bk;
+ }
+
+ if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_ALL_PORT1) {
+ if (unlikely(port != 1)) {
+ err = -EINVAL;
+ goto fifo_id_bk;
+ } else {
+ fifo_index = channel;
+ }
+ } else if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_SSI) {
+ if (unlikely(channel >= 8)) {
+ err = -EINVAL;
+ goto fifo_id_bk;
+ } else {
+ fifo_index = channel + 8 * (port - 1);
+ }
+ } else {
+ err = -EPERM;
+ goto fifo_id_bk;
+ }
+
+fifo_id_bk:
+ if (unlikely(err < 0)) {
+ dev_err(hsi_ctrl->dev, "Cannot map a fifo to the requested "
+ "params: channel:%d, port:%d; ERR=%d\n", channel, port,
+ fifo_index);
+ fifo_index = err;
+ }
+
+ return fifo_index;
+}
+
+/**
+ * hsi_fifo_get_chan - Get (port, channel) from a fifo index
+ * @hsi_ctrl - HSI controler data
+ * @fifo - HSI fifo used (0..HSI_HST_FIFO_COUNT)
+ * @channel - related channel if any (0..)
+ * @port - related port if any (1..2)
+ *
+ * Returns 0 in case of success, and errocode (< 0) else
+ * Notes: 1) The fifo <=> (port, channel) correspondance depends on the selected
+ * SW strategy for channels mapping (fifo management).
+ * 2) the mapping is identical for Read and Write path.
+ * This exclusively applies to HSI devices.
+ */
+int hsi_fifo_get_chan(struct hsi_dev *hsi_ctrl, unsigned int fifo,
+ unsigned int *channel, unsigned int *port)
+{
+ int err = 0;
+
+ if (unlikely(fifo >= HSI_HST_FIFO_COUNT)) {
+ err = -EINVAL;
+ goto fifo_id_bk;
+ }
+
+ if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_ALL_PORT1) {
+ *channel = fifo;
+ *port = 1;
+ } else if (hsi_ctrl->fifo_mapping_strategy == HSI_FIFO_MAPPING_SSI) {
+ if (fifo < 8) {
+ *channel = fifo;
+ *port = 1;
+ } else {
+ *channel = fifo - 8;
+ *port = 2;
+ }
+ } else {
+ err = -EPERM;
+ goto fifo_id_bk;
+ }
+
+fifo_id_bk:
+ if (unlikely(err < 0))
+ dev_err(hsi_ctrl->dev, "Cannot map a channel / port to the "
+ "requested params: fifo:%d; ERR=%d\n", fifo, err);
+
+ return err;
+}
+
+/**
+ * hsi_fifo_mapping - Configures the HSI FIFO mapping registers.
+ * @hsi_ctrl - HSI controler data
+ * @mtype - mapping strategy
+ *
+ * Returns 0 in case of success, and errocode (< 0) else
+ * Configures the HSI FIFO mapping registers. Several mapping strategies are
+ * proposed.
+ * Note: The mapping is identical for Read and Write path.
+ * This exclusively applies to HSI devices.
+ */
+int __init hsi_fifo_mapping(struct hsi_dev *hsi_ctrl,
+ unsigned int mtype)
+{
+ int err = 0;
+ void __iomem *base = hsi_ctrl->base;
+ int i;
+ unsigned int channel, port;
+
+ if (mtype == HSI_FIFO_MAPPING_ALL_PORT1) {
+ channel = 0;
+ for (i = 0; i < HSI_HST_FIFO_COUNT; i++) {
+ hsi_outl(HSI_MAPPING_ENABLE |
+ (channel << HSI_MAPPING_CH_NUMBER_OFFSET) |
+ (0 << HSI_MAPPING_PORT_NUMBER_OFFSET) |
+ HSI_HST_MAPPING_THRESH_VALUE,
+ base, HSI_HST_MAPPING_FIFO_REG(i));
+ hsi_outl(HSI_MAPPING_ENABLE |
+ (channel << HSI_MAPPING_CH_NUMBER_OFFSET) |
+ (0 << HSI_MAPPING_PORT_NUMBER_OFFSET),
+ base, HSI_HSR_MAPPING_FIFO_REG(i));
+ channel++;
+ }
+ hsi_ctrl->fifo_mapping_strategy = HSI_FIFO_MAPPING_ALL_PORT1;
+ } else if (mtype == HSI_FIFO_MAPPING_SSI) {
+ channel = 0;
+ port = 0;
+ for (i = 0; i < HSI_HST_FIFO_COUNT; i++) {
+ hsi_outl(HSI_MAPPING_ENABLE |
+ (channel << HSI_MAPPING_CH_NUMBER_OFFSET) |
+ (port << HSI_MAPPING_PORT_NUMBER_OFFSET) |
+ HSI_HST_MAPPING_THRESH_VALUE,
+ base, HSI_HST_MAPPING_FIFO_REG(i));
+ hsi_outl(HSI_MAPPING_ENABLE |
+ (channel << HSI_MAPPING_CH_NUMBER_OFFSET) |
+ (port << HSI_MAPPING_PORT_NUMBER_OFFSET),
+ base, HSI_HSR_MAPPING_FIFO_REG(i));
+ channel++;
+ if (channel == 8) {
+ channel = 0;
+ port = 1;
+ }
+ }
+
+ hsi_ctrl->fifo_mapping_strategy = HSI_FIFO_MAPPING_DEFAULT;
+ } else {
+ hsi_ctrl->fifo_mapping_strategy = HSI_FIFO_MAPPING_UNDEF;
+ dev_err(hsi_ctrl->dev, "Bad Fifo strategy request\n");
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+/**
+ * hsi_hst_bufstate_f_reg - Return the proper HSI_HST_BUFSTATE register offset
+ * @hsi_ctrl - HSI controler data
+ * @port - HSI port used
+ * @channel - channel used
+ *
+ * Returns the HSI_HST_BUFSTATE register offset
+ * Note: indexing of BUFSTATE registers is different on SSI and HSI:
+ * On SSI: it is linked to the ports
+ * On HSI: it is linked to the FIFOs (and depend on the SW strategy)
+ */
+long hsi_hst_bufstate_f_reg(struct hsi_dev *hsi_ctrl,
+ unsigned int port, unsigned int channel) {
+ int fifo;
+ if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) {
+ fifo = hsi_fifo_get_id(hsi_ctrl, channel, port);
+ if (fifo < 0)
+ return fifo;
+ else
+ return HSI_HST_BUFSTATE_FIFO_REG(fifo);
+ } else {
+ return HSI_HST_BUFSTATE_REG(port);
+ }
+}
+
+/**
+ * hsi_hsr_bufstate_f_reg - Return the proper HSI_HSR_BUFSTATE register offset
+ * @hsi_ctrl - HSI controler data
+ * @port - HSI port used
+ * @channel - channel used
+ *
+ * Returns the HSI_HSR_BUFSTATE register offset
+ * Note: indexing of BUFSTATE registers is different on SSI and HSI:
+ * On SSI: it is linked to the ports
+ * On HSI: it is linked to the FIFOs (and depend on the SW strategy)
+ */
+long hsi_hsr_bufstate_f_reg(struct hsi_dev *hsi_ctrl,
+ unsigned int port, unsigned int channel) {
+ int fifo;
+ if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) {
+ fifo = hsi_fifo_get_id(hsi_ctrl, channel, port);
+ if (fifo < 0)
+ return fifo;
+ else
+ return HSI_HSR_BUFSTATE_FIFO_REG(fifo);
+ } else {
+ return HSI_HSR_BUFSTATE_REG(port);
+ }
+}
+
+/**
+ * hsi_hst_buffer_f_reg - Return the proper HSI_HST_BUFFER register offset
+ * @hsi_ctrl - HSI controler data
+ * @port - HSI port used
+ * @channel - channel used
+ *
+ * Returns the HSI_HST_BUFFER register offset
+ * Note: indexing of BUFFER registers is different on SSI and HSI:
+ * On SSI: it is linked to the ports
+ * On HSI: it is linked to the FIFOs (and depend on the SW strategy)
+ */
+long hsi_hst_buffer_reg(struct hsi_dev *hsi_ctrl,
+ unsigned int port, unsigned int channel) {
+ int fifo;
+ if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) {
+ fifo = hsi_fifo_get_id(hsi_ctrl, channel, port);
+ if (unlikely(fifo < 0))
+ return fifo;
+ else
+ return HSI_HST_BUFFER_FIFO_REG(fifo);
+ } else {
+ return HSI_HST_BUFFER_CH_REG(port, channel);
+ }
+}
+
+/**
+ * hsi_hsr_buffer_f_reg - Return the proper HSI_HSR_BUFFER register offset
+ * @hsi_ctrl - HSI controler data
+ * @port - HSI port used
+ * @channel - channel used
+ *
+ * Returns the HSI_HSR_BUFFER register offset
+ * Note: indexing of BUFFER registers is different on SSI and HSI:
+ * On SSI: it is linked to the ports
+ * On HSI: it is linked to the FIFOs (and depend on the SW strategy)
+ */
+long hsi_hsr_buffer_reg(struct hsi_dev *hsi_ctrl,
+ unsigned int port, unsigned int channel) {
+ int fifo;
+ if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) {
+ fifo = hsi_fifo_get_id(hsi_ctrl, channel, port);
+ if (fifo < 0)
+ return fifo;
+ else
+ return HSI_HSR_BUFFER_FIFO_REG(fifo);
+ } else {
+ return HSI_HSR_BUFFER_CH_REG(port, channel);
+ }
+}
+
diff --git a/drivers/hsi/hsi_driver_gpio.c b/drivers/hsi/hsi_driver_gpio.c
new file mode 100644
index 0000000..2410973
--- /dev/null
+++ b/drivers/hsi/hsi_driver_gpio.c
@@ -0,0 +1,79 @@
+/*
+ * hsi_driver_gpio.c
+ *
+ * Implements HSI GPIO related functionality. (i.e: wake lines management)
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@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/gpio.h>
+#include "hsi_driver.h"
+
+static void do_hsi_cawake_tasklet(unsigned long hsi_p)
+{
+ struct hsi_port *port = (struct hsi_port *)hsi_p;
+ struct hsi_dev *hsi_ctrl = port->hsi_controller;
+
+ if (hsi_cawake(port)) {
+ if (!hsi_ctrl->cawake_clk_enable) {
+ hsi_ctrl->cawake_clk_enable = 1;
+ clk_enable(hsi_ctrl->hsi_clk);
+ }
+ hsi_port_event_handler(port, HSI_EVENT_CAWAKE_UP, NULL);
+ } else {
+ hsi_port_event_handler(port, HSI_EVENT_CAWAKE_DOWN, NULL);
+ if (hsi_ctrl->cawake_clk_enable) {
+ hsi_ctrl->cawake_clk_enable = 0;
+ clk_disable(hsi_ctrl->hsi_clk);
+ }
+ }
+}
+
+static irqreturn_t hsi_cawake_isr(int irq, void *hsi_p)
+{
+ struct hsi_port *port = hsi_p;
+
+ tasklet_hi_schedule(&port->cawake_tasklet);
+
+ return IRQ_HANDLED;
+}
+
+int __init hsi_cawake_init(struct hsi_port *port, const char *irq_name)
+{
+ tasklet_init(&port->cawake_tasklet, do_hsi_cawake_tasklet,
+ (unsigned long)port);
+
+ if (request_irq(port->cawake_gpio_irq, hsi_cawake_isr,
+ IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ irq_name, port) < 0) {
+ dev_err(port->hsi_controller->dev,
+ "FAILED to request %s GPIO IRQ %d on port %d\n",
+ irq_name, port->cawake_gpio_irq, port->port_number);
+ return -EBUSY;
+ }
+ enable_irq_wake(port->cawake_gpio_irq);
+
+ return 0;
+}
+
+void hsi_cawake_exit(struct hsi_port *port)
+{
+ if (port->cawake_gpio < 0)
+ return; /* Nothing to do */
+
+ disable_irq_wake(port->cawake_gpio_irq);
+ tasklet_kill(&port->cawake_tasklet);
+ free_irq(port->cawake_gpio_irq, port);
+}
diff --git a/drivers/hsi/hsi_driver_int.c b/drivers/hsi/hsi_driver_int.c
new file mode 100644
index 0000000..d04daf7
--- /dev/null
+++ b/drivers/hsi/hsi_driver_int.c
@@ -0,0 +1,339 @@
+/*
+ * hsi_driver_int.c
+ *
+ * Implements HSI interrupt functionality.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@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 "hsi_driver.h"
+
+void hsi_reset_ch_read(struct hsi_channel *ch)
+{
+ ch->read_data.addr = NULL;
+ ch->read_data.size = 0;
+ ch->read_data.lch = -1;
+}
+
+void hsi_reset_ch_write(struct hsi_channel *ch)
+{
+ ch->write_data.addr = NULL;
+ ch->write_data.size = 0;
+ ch->write_data.lch = -1;
+}
+
+int hsi_driver_write_interrupt(struct hsi_channel *ch, u32 *data)
+{
+ struct hsi_port *p = ch->hsi_port;
+ unsigned int port = p->port_number;
+ unsigned int channel = ch->channel_number;
+
+ clk_enable(p->hsi_controller->hsi_clk);
+
+ hsi_outl_or(HSI_HST_DATAACCEPT(channel), p->hsi_controller->base,
+ HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel));
+
+ return 0;
+}
+
+int hsi_driver_read_interrupt(struct hsi_channel *ch, u32 *data)
+{
+ struct hsi_port *p = ch->hsi_port;
+ unsigned int port = p->port_number;
+ unsigned int channel = ch->channel_number;
+
+ clk_enable(p->hsi_controller->hsi_clk);
+
+ hsi_outl_or(HSI_HSR_DATAAVAILABLE(channel), p->hsi_controller->base,
+ HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel));
+
+ clk_disable(p->hsi_controller->hsi_clk);
+
+ return 0;
+}
+
+void hsi_driver_cancel_write_interrupt(struct hsi_channel *ch)
+{
+ struct hsi_port *p = ch->hsi_port;
+ unsigned int port = p->port_number;
+ unsigned int channel = ch->channel_number;
+ void __iomem *base = p->hsi_controller->base;
+ u32 enable;
+ long buff_offset;
+
+ clk_enable(p->hsi_controller->hsi_clk);
+
+ enable = hsi_inl(base,
+ HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel));
+
+ if (!(enable & HSI_HST_DATAACCEPT(channel))) {
+ dev_dbg(&ch->dev->device, LOG_NAME "Write cancel on not "
+ "enabled channel %d ENABLE REG 0x%08X", channel, enable);
+ clk_disable(p->hsi_controller->hsi_clk);
+ return;
+ }
+
+ hsi_outl_and(~HSI_HST_DATAACCEPT(channel), base,
+ HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel));
+
+ buff_offset = hsi_hst_bufstate_f_reg(p->hsi_controller, port, channel);
+ if (buff_offset >= 0)
+ hsi_outl_and(~HSI_BUFSTATE_CHANNEL(channel), base,
+ buff_offset);
+
+ hsi_reset_ch_write(ch);
+
+ clk_disable(p->hsi_controller->hsi_clk);
+ clk_disable(p->hsi_controller->hsi_clk); /* FIXME - can be removed? */
+}
+
+void hsi_driver_disable_read_interrupt(struct hsi_channel *ch)
+{
+ struct hsi_port *p = ch->hsi_port;
+ unsigned int port = p->port_number;
+ unsigned int channel = ch->channel_number;
+ void __iomem *base = p->hsi_controller->base;
+
+ clk_enable(p->hsi_controller->hsi_clk);
+
+ hsi_outl_and(~HSI_HSR_DATAAVAILABLE(channel), base,
+ HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel));
+
+ clk_disable(p->hsi_controller->hsi_clk);
+}
+
+void hsi_driver_cancel_read_interrupt(struct hsi_channel *ch)
+{
+ struct hsi_port *p = ch->hsi_port;
+ unsigned int port = p->port_number;
+ unsigned int channel = ch->channel_number;
+ void __iomem *base = p->hsi_controller->base;
+
+ clk_enable(p->hsi_controller->hsi_clk);
+
+ hsi_outl_and(~HSI_HSR_DATAAVAILABLE(channel), base,
+ HSI_SYS_MPU_ENABLE_CH_REG(port, p->n_irq, channel));
+
+ hsi_reset_ch_read(ch);
+
+ clk_disable(p->hsi_controller->hsi_clk);
+}
+
+static void do_channel_tx(struct hsi_channel *ch)
+{
+ struct hsi_dev *hsi_ctrl = ch->hsi_port->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int n_ch;
+ unsigned int n_p;
+ unsigned int irq;
+ long buff_offset;
+
+ n_ch = ch->channel_number;
+ n_p = ch->hsi_port->port_number;
+ irq = ch->hsi_port->n_irq;
+
+ spin_lock(&hsi_ctrl->lock);
+
+ if (ch->write_data.addr == NULL) {
+ hsi_outl_and(~HSI_HST_DATAACCEPT(n_ch), base,
+ HSI_SYS_MPU_ENABLE_CH_REG(n_p, irq, n_ch));
+ hsi_reset_ch_write(ch);
+ spin_unlock(&hsi_ctrl->lock);
+ clk_disable(hsi_ctrl->hsi_clk);
+ (*ch->write_done)(ch->dev, 4);
+ } else {
+ buff_offset = hsi_hst_buffer_reg(hsi_ctrl, n_p, n_ch);
+ if (buff_offset >= 0) {
+ hsi_outl(*(ch->write_data.addr), base, buff_offset);
+ ch->write_data.addr = NULL;
+ }
+ spin_unlock(&hsi_ctrl->lock);
+ }
+}
+
+static void do_channel_rx(struct hsi_channel *ch)
+{
+ struct hsi_dev *hsi_ctrl = ch->hsi_port->hsi_controller;
+ void __iomem *base = ch->hsi_port->hsi_controller->base;
+ unsigned int n_ch;
+ unsigned int n_p;
+ unsigned int irq;
+ long buff_offset;
+ int rx_poll = 0;
+ int data_read = 0;
+
+ n_ch = ch->channel_number;
+ n_p = ch->hsi_port->port_number;
+ irq = ch->hsi_port->n_irq;
+
+ spin_lock(&hsi_ctrl->lock);
+
+ if (ch->flags & HSI_CH_RX_POLL)
+ rx_poll = 1;
+
+ if (ch->read_data.addr) {
+ buff_offset = hsi_hsr_buffer_reg(hsi_ctrl, n_p, n_ch);
+ if (buff_offset >= 0) {
+ data_read = 1;
+ *(ch->read_data.addr) = hsi_inl(base, buff_offset);
+ }
+ }
+
+ hsi_outl_and(~HSI_HSR_DATAAVAILABLE(n_ch), base,
+ HSI_SYS_MPU_ENABLE_CH_REG(n_p, irq, n_ch));
+ hsi_reset_ch_read(ch);
+
+ spin_unlock(&hsi_ctrl->lock);
+
+ if (rx_poll)
+ hsi_port_event_handler(ch->hsi_port,
+ HSI_EVENT_HSR_DATAAVAILABLE, (void *)n_ch);
+
+ if (data_read)
+ (*ch->read_done)(ch->dev, 4);
+}
+
+/**
+ * hsi_driver_int_proc - check all channels / ports for interrupts events
+ * @hsi_ctrl - HSI controler data
+ * @status_offset: interrupt status register offset
+ * @enable_offset: interrupt enable regiser offset
+ * @start: interrupt index to start on
+ * @stop: interrupt index to stop on
+ *
+ * This function calls the related processing functions and triggered events
+*/
+static void hsi_driver_int_proc(struct hsi_port *pport,
+ unsigned long status_offset, unsigned long enable_offset,
+ unsigned int start, unsigned int stop)
+{
+ struct hsi_dev *hsi_ctrl = pport->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int port = pport->port_number;
+ unsigned int channel;
+ u32 status_reg;
+ u32 hsr_err_reg;
+ u32 channels_served = 0;
+
+ status_reg = hsi_inl(base, status_offset);
+ status_reg &= hsi_inl(base, enable_offset);
+
+ for (channel = start; channel < stop; channel++) {
+ if (status_reg & HSI_HST_DATAACCEPT(channel)) {
+
+ do_channel_tx(&pport->hsi_channel[channel]);
+ channels_served |= HSI_HST_DATAACCEPT(channel);
+ }
+
+ if (status_reg & HSI_HSR_DATAAVAILABLE(channel)) {
+ do_channel_rx(&pport->hsi_channel[channel]);
+ channels_served |= HSI_HSR_DATAAVAILABLE(channel);
+ }
+ }
+
+ if (status_reg & HSI_BREAKDETECTED) {
+ dev_info(hsi_ctrl->dev, "Hardware BREAK on port %d\n", port);
+ hsi_outl(0, base, HSI_HSR_BREAK_REG(port));
+ hsi_port_event_handler(pport, HSI_EVENT_BREAK_DETECTED, NULL);
+ channels_served |= HSI_BREAKDETECTED;
+ }
+
+ if (status_reg & HSI_ERROROCCURED) {
+ hsr_err_reg = hsi_inl(base, HSI_HSR_ERROR_REG(port));
+ dev_err(hsi_ctrl->dev, "HSI ERROR Port %d: 0x%x\n",
+ port, hsr_err_reg);
+ hsi_outl(hsr_err_reg, base, HSI_HSR_ERRORACK_REG(port));
+ if (hsr_err_reg) /* ignore spurious errors */
+ hsi_port_event_handler(pport, HSI_EVENT_ERROR, NULL);
+ else
+ dev_dbg(hsi_ctrl->dev, "Spurious HSI error!\n");
+
+ channels_served |= HSI_ERROROCCURED;
+ }
+
+ hsi_outl(channels_served, base, status_offset);
+}
+
+static void do_hsi_tasklet(unsigned long hsi_port)
+{
+ struct hsi_port *pport = (struct hsi_port *)hsi_port;
+ struct hsi_dev *hsi_ctrl = pport->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int port = pport->port_number;
+ unsigned int irq = pport->n_irq;
+ u32 status_reg;
+ struct platform_device *pd = to_platform_device(hsi_ctrl->dev);
+
+ clk_enable(hsi_ctrl->hsi_clk);
+
+ hsi_driver_int_proc(pport,
+ HSI_SYS_MPU_STATUS_REG(port, irq),
+ HSI_SYS_MPU_ENABLE_REG(port, irq),
+ 0, min(pport->max_ch, (u8) HSI_SSI_CHANNELS_MAX));
+
+ if (pport->max_ch > HSI_SSI_CHANNELS_MAX)
+ hsi_driver_int_proc(pport,
+ HSI_SYS_MPU_U_STATUS_REG(port, irq),
+ HSI_SYS_MPU_U_ENABLE_REG(port, irq),
+ HSI_SSI_CHANNELS_MAX, pport->max_ch);
+
+ status_reg = hsi_inl(base, HSI_SYS_MPU_STATUS_REG(port, irq)) &
+ hsi_inl(base, HSI_SYS_MPU_ENABLE_REG(port, irq));
+
+ if (hsi_driver_device_is_hsi(pd))
+ status_reg |=
+ (hsi_inl(base, HSI_SYS_MPU_U_STATUS_REG(port, irq)) &
+ hsi_inl(base, HSI_SYS_MPU_U_ENABLE_REG(port, irq)));
+
+ clk_disable(hsi_ctrl->hsi_clk);
+
+ if (status_reg)
+ tasklet_hi_schedule(&pport->hsi_tasklet);
+ else
+ enable_irq(pport->irq);
+}
+
+static irqreturn_t hsi_mpu_handler(int irq, void *hsi_port)
+{
+ struct hsi_port *p = hsi_port;
+
+ tasklet_hi_schedule(&p->hsi_tasklet);
+ disable_irq_nosync(p->irq);
+
+ return IRQ_HANDLED;
+}
+
+int __init hsi_mpu_init(struct hsi_port *hsi_p, const char *irq_name)
+{
+ int err;
+
+ tasklet_init(&hsi_p->hsi_tasklet, do_hsi_tasklet,
+ (unsigned long)hsi_p);
+ err = request_irq(hsi_p->irq, hsi_mpu_handler, IRQF_DISABLED,
+ irq_name, hsi_p);
+ if (err < 0) {
+ dev_err(hsi_p->hsi_controller->dev, "FAILED to MPU request"
+ " IRQ (%d) on port %d", hsi_p->irq, hsi_p->port_number);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+void hsi_mpu_exit(struct hsi_port *hsi_p)
+{
+ tasklet_disable(&hsi_p->hsi_tasklet);
+ free_irq(hsi_p->irq, hsi_p);
+}
--
1.6.0.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* [RFC PATCH 4/9] HSI: Low Level Driver debugfs support
2009-10-30 9:59 [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Sebastien Jan
` (2 preceding siblings ...)
2009-10-30 10:00 ` [RFC PATCH 3/9] HSI: Low Level Driver device management Sebastien Jan
@ 2009-10-30 10:00 ` Sebastien Jan
2009-10-30 10:00 ` [RFC PATCH 5/9] HSI: Low Level Driver documentation Sebastien Jan
` (5 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Sebastien Jan @ 2009-10-30 10:00 UTC (permalink / raw)
To: linux-omap; +Cc: Sebastien Jan, Carlos Chinea
All registers dump, and counters configuration
Signed-off-by: Sebastien Jan <s-jan@ti.com>
Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com>
---
drivers/hsi/hsi_driver_debugfs.c | 486 ++++++++++++++++++++++++++++++++++++++
1 files changed, 486 insertions(+), 0 deletions(-)
create mode 100644 drivers/hsi/hsi_driver_debugfs.c
diff --git a/drivers/hsi/hsi_driver_debugfs.c b/drivers/hsi/hsi_driver_debugfs.c
new file mode 100644
index 0000000..d600a88
--- /dev/null
+++ b/drivers/hsi/hsi_driver_debugfs.c
@@ -0,0 +1,486 @@
+/*
+ * hsi_driver_debugfs.c
+ *
+ * Implements HSI debugfs.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@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/list.h>
+#include <mach/clock.h>
+#include <linux/uaccess.h>
+#include <linux/ctype.h>
+#include "hsi_driver.h"
+
+#define HSI_DIR_NAME_SIZE 64
+
+static struct dentry *hsi_dir;
+
+static int hsi_debug_show(struct seq_file *m, void *p)
+{
+ struct hsi_dev *hsi_ctrl = m->private;
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+
+ clk_enable(hsi_ctrl->hsi_clk);
+
+ seq_printf(m, "REVISION\t: 0x%08x\n",
+ hsi_inl(hsi_ctrl->base, HSI_SYS_REVISION_REG));
+ if (hsi_driver_device_is_hsi(pdev))
+ seq_printf(m, "HWINFO\t\t: 0x%08x\n",
+ hsi_inl(hsi_ctrl->base, HSI_SYS_HWINFO_REG));
+ seq_printf(m, "SYSCONFIG\t: 0x%08x\n",
+ hsi_inl(hsi_ctrl->base, HSI_SYS_SYSCONFIG_REG));
+ seq_printf(m, "SYSSTATUS\t: 0x%08x\n",
+ hsi_inl(hsi_ctrl->base, HSI_SYS_SYSSTATUS_REG));
+
+ clk_disable(hsi_ctrl->hsi_clk);
+ return 0;
+}
+
+static int hsi_debug_port_show(struct seq_file *m, void *p)
+{
+ struct hsi_port *hsi_port = m->private;
+ struct hsi_dev *hsi_ctrl = hsi_port->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int port = hsi_port->port_number;
+ int ch, fifo;
+ long buff_offset;
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+
+ clk_enable(hsi_ctrl->hsi_clk);
+
+ if (hsi_port->cawake_gpio >= 0)
+ seq_printf(m, "CAWAKE\t\t: %d\n", hsi_cawake(hsi_port));
+
+ seq_printf(m, "WAKE\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_SYS_WAKE_REG(port)));
+ seq_printf(m, "MPU_ENABLE_IRQ%d\t: 0x%08x\n", hsi_port->n_irq,
+ hsi_inl(base, HSI_SYS_MPU_ENABLE_REG(port, hsi_port->n_irq)));
+ seq_printf(m, "MPU_STATUS_IRQ%d\t: 0x%08x\n", hsi_port->n_irq,
+ hsi_inl(base, HSI_SYS_MPU_STATUS_REG(port, hsi_port->n_irq)));
+ if (hsi_driver_device_is_hsi(pdev)) {
+ seq_printf(m, "MPU_U_ENABLE_IRQ%d\t: 0x%08x\n",
+ hsi_port->n_irq,
+ hsi_inl(base, HSI_SYS_MPU_U_ENABLE_REG(port,
+ hsi_port->n_irq)));
+ seq_printf(m, "MPU_U_STATUS_IRQ%d\t: 0x%08x\n",
+ hsi_port->n_irq,
+ hsi_inl(base, HSI_SYS_MPU_U_STATUS_REG(port,
+ hsi_port->n_irq)));
+ }
+ /* HST */
+ seq_printf(m, "\nHST\n===\n");
+ seq_printf(m, "MODE\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_HST_MODE_REG(port)));
+ seq_printf(m, "FRAMESIZE\t: 0x%08x\n",
+ hsi_inl(base, HSI_HST_FRAMESIZE_REG(port)));
+ seq_printf(m, "DIVISOR\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_HST_DIVISOR_REG(port)));
+ seq_printf(m, "CHANNELS\t: 0x%08x\n",
+ hsi_inl(base, HSI_HST_CHANNELS_REG(port)));
+ seq_printf(m, "ARBMODE\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_HST_ARBMODE_REG(port)));
+ seq_printf(m, "TXSTATE\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_HST_TXSTATE_REG(port)));
+ if (hsi_driver_device_is_hsi(pdev)) {
+ seq_printf(m, "BUFSTATE P1\t: 0x%08x\n",
+ hsi_inl(base, HSI_HST_BUFSTATE_REG(1)));
+ seq_printf(m, "BUFSTATE P2\t: 0x%08x\n",
+ hsi_inl(base, HSI_HST_BUFSTATE_REG(2)));
+ } else {
+ seq_printf(m, "BUFSTATE\t: 0x%08x\n",
+ hsi_inl(base, HSI_HST_BUFSTATE_REG(port)));
+ }
+ seq_printf(m, "BREAK\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_HST_BREAK_REG(port)));
+ for (ch = 0; ch < hsi_port->max_ch; ch++) {
+ buff_offset = hsi_hst_buffer_reg(hsi_ctrl, port, ch);
+ if (buff_offset >= 0)
+ seq_printf(m, "BUFFER_CH%d\t: 0x%08x\n", ch,
+ hsi_inl(base, buff_offset));
+ }
+ if (hsi_driver_device_is_hsi(pdev)) {
+ for (fifo = 0; fifo < HSI_HST_FIFO_COUNT; fifo++) {
+ seq_printf(m, "FIFO MAPPING%d\t: 0x%08x\n", fifo,
+ hsi_inl(base, HSI_HST_MAPPING_FIFO_REG(fifo)));
+ }
+ }
+ /* HSR */
+ seq_printf(m, "\nHSR\n===\n");
+ seq_printf(m, "MODE\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_MODE_REG(port)));
+ seq_printf(m, "FRAMESIZE\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_FRAMESIZE_REG(port)));
+ seq_printf(m, "CHANNELS\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_CHANNELS_REG(port)));
+ seq_printf(m, "COUNTERS\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_COUNTERS_REG(port)));
+ seq_printf(m, "RXSTATE\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_RXSTATE_REG(port)));
+ if (hsi_driver_device_is_hsi(pdev)) {
+ seq_printf(m, "BUFSTATE P1\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_BUFSTATE_REG(1)));
+ seq_printf(m, "BUFSTATE P2\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_BUFSTATE_REG(2)));
+ } else {
+ seq_printf(m, "BUFSTATE\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_BUFSTATE_REG(port)));
+ }
+ seq_printf(m, "BREAK\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_BREAK_REG(port)));
+ seq_printf(m, "ERROR\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_ERROR_REG(port)));
+ seq_printf(m, "ERRORACK\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_ERRORACK_REG(port)));
+ for (ch = 0; ch < hsi_port->max_ch; ch++) {
+ buff_offset = hsi_hsr_buffer_reg(hsi_ctrl, port, ch);
+ if (buff_offset >= 0)
+ seq_printf(m, "BUFFER_CH%d\t: 0x%08x\n", ch,
+ hsi_inl(base, buff_offset));
+ }
+ if (hsi_driver_device_is_hsi(pdev)) {
+ for (fifo = 0; fifo < HSI_HSR_FIFO_COUNT; fifo++) {
+ seq_printf(m, "FIFO MAPPING%d\t: 0x%08x\n", fifo,
+ hsi_inl(base,
+ HSI_HSR_MAPPING_FIFO_REG(fifo)));
+ }
+ seq_printf(m, "DLL\t: 0x%08x\n",
+ hsi_inl(base, HSI_HSR_DLL_REG(port)));
+ seq_printf(m, "DIVISOR\t: 0x%08x\n",
+ hsi_inl(base,
+ HSI_HSR_DIVISOR_REG(port)));
+ }
+ clk_disable(hsi_ctrl->hsi_clk);
+ return 0;
+}
+
+static int hsi_debug_gdd_show(struct seq_file *m, void *p)
+{
+ struct hsi_dev *hsi_ctrl = m->private;
+ void __iomem *base = hsi_ctrl->base;
+ int lch;
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+
+ clk_enable(hsi_ctrl->hsi_clk);
+
+ seq_printf(m, "GDD_MPU_STATUS\t: 0x%08x\n",
+ hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG));
+ seq_printf(m, "GDD_MPU_ENABLE\t: 0x%08x\n\n",
+ hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG));
+
+ if (!hsi_driver_device_is_hsi(pdev)) {
+ seq_printf(m, "HW_ID\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_SSI_GDD_HW_ID_REG));
+ seq_printf(m, "PPORT_ID\t: 0x%08x\n",
+ hsi_inl(base, HSI_SSI_GDD_PPORT_ID_REG));
+ seq_printf(m, "MPORT_ID\t: 0x%08x\n",
+ hsi_inl(base, HSI_SSI_GDD_MPORT_ID_REG));
+ seq_printf(m, "TEST\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_SSI_GDD_TEST_REG));
+ }
+
+ seq_printf(m, "GCR\t\t: 0x%08x\n", hsi_inl(base, HSI_GDD_GCR_REG));
+
+ for (lch = 0; lch < hsi_ctrl->gdd_chan_count; lch++) {
+ seq_printf(m, "\nGDD LCH %d\n=========\n", lch);
+ seq_printf(m, "CSDP\t\t: 0x%04x\n",
+ hsi_inw(base, HSI_GDD_CSDP_REG(lch)));
+ seq_printf(m, "CCR\t\t: 0x%04x\n",
+ hsi_inw(base, HSI_GDD_CCR_REG(lch)));
+ seq_printf(m, "CICR\t\t: 0x%04x\n",
+ hsi_inw(base, HSI_GDD_CICR_REG(lch)));
+ seq_printf(m, "CSR\t\t: 0x%04x\n",
+ hsi_inw(base, HSI_GDD_CSR_REG(lch)));
+ seq_printf(m, "CSSA\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_GDD_CSSA_REG(lch)));
+ seq_printf(m, "CDSA\t\t: 0x%08x\n",
+ hsi_inl(base, HSI_GDD_CDSA_REG(lch)));
+ seq_printf(m, "CEN\t\t: 0x%04x\n",
+ hsi_inw(base, HSI_GDD_CEN_REG(lch)));
+ seq_printf(m, "CSAC\t\t: 0x%04x\n",
+ hsi_inw(base, HSI_GDD_CSAC_REG(lch)));
+ seq_printf(m, "CDAC\t\t: 0x%04x\n",
+ hsi_inw(base, HSI_GDD_CDAC_REG(lch)));
+ if (!hsi_driver_device_is_hsi(pdev))
+ seq_printf(m, "CLNK_CTRL\t: 0x%04x\n",
+ hsi_inw(base, HSI_SSI_GDD_CLNK_CTRL_REG(lch)));
+ }
+
+ clk_disable(hsi_ctrl->hsi_clk);
+ return 0;
+}
+
+
+static int hsi_port_counters_open(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return 0;
+}
+
+static int hsi_port_counters_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static loff_t hsi_port_counters_seek(struct file *file, loff_t off, int whence)
+{
+ return 0;
+}
+
+static ssize_t hsi_port_counters_read(struct file *filep, char __user *buff,
+ size_t count, loff_t *offp)
+{
+ ssize_t ret;
+ struct hsi_port *hsi_port = filep->private_data;
+ struct hsi_dev *hsi_ctrl = hsi_port->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int port = hsi_port->port_number;
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+ char str[50];
+ unsigned long reg;
+
+ if (*offp > 0) {
+ ret = 0;
+ goto hsi_cnt_rd_bk;
+ }
+
+ clk_enable(hsi_ctrl->hsi_clk);
+ reg = hsi_inl(base, HSI_HSR_COUNTERS_REG(port));
+ clk_disable(hsi_ctrl->hsi_clk);
+
+ if (hsi_driver_device_is_hsi(pdev)) {
+ sprintf(str, "FT:%d, TB:%d, FB:%d\n",
+ (int) (reg & HSI_COUNTERS_FT_MASK) >> HSI_COUNTERS_FT_OFFSET,
+ (int) (reg & HSI_COUNTERS_TB_MASK) >> HSI_COUNTERS_TB_OFFSET,
+ (int) (reg & HSI_COUNTERS_FB_MASK) >> HSI_COUNTERS_FB_OFFSET);
+ } else {
+ sprintf(str, "timeout:%d\n", (int) reg);
+ }
+
+ ret = strlen(str);
+ if (copy_to_user((void __user *)buff, str, ret)) {
+ dev_err(hsi_ctrl->dev, "copy_to_user failed\n");
+ ret = 0;
+ } else {
+ *offp = ret;
+ }
+
+hsi_cnt_rd_bk:
+ return ret;
+}
+
+/*
+ * Split the buffer `buf' into space-separated words.
+ * Return the number of words or <0 on error.
+ */
+static int hsi_debug_tokenize(char *buf, char *words[], int maxwords)
+{
+ int nwords = 0;
+
+ while (*buf) {
+ char *end;
+
+ /* Skip leading whitespace */
+ while (*buf && isspace(*buf))
+ buf++;
+ if (!*buf)
+ break; /* oh, it was trailing whitespace */
+
+ /* Run `end' over a word */
+ for (end = buf ; *end && !isspace(*end) ; end++)
+ ;
+ /* `buf' is the start of the word, `end' is one past the end */
+
+ if (nwords == maxwords)
+ return -EINVAL; /* ran out of words[] before bytes */
+ if (*end)
+ *end++ = '\0'; /* terminate the word */
+ words[nwords++] = buf;
+ buf = end;
+ }
+ return nwords;
+}
+
+static ssize_t hsi_port_counters_write(struct file *filep,
+ const char __user *buff, size_t count, loff_t *offp)
+{
+ ssize_t ret;
+ struct hsi_port *hsi_port = filep->private_data;
+ struct hsi_dev *hsi_ctrl = hsi_port->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int port = hsi_port->port_number;
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+#define MAXWORDS 4
+ int nwords;
+ char *words[MAXWORDS];
+ char tmpbuf[256];
+ unsigned long reg, ft, tb, fb;
+
+ if (count == 0)
+ return 0;
+ if (count > sizeof(tmpbuf)-1)
+ return -E2BIG;
+ if (copy_from_user(tmpbuf, buff, count))
+ return -EFAULT;
+ tmpbuf[count] = '\0';
+ dev_dbg(hsi_ctrl->dev, "%s: read %d bytes from userspace\n",
+ __func__, (int)count);
+
+ nwords = hsi_debug_tokenize(tmpbuf, words, MAXWORDS);
+ if (nwords < 0) {
+ dev_warn(hsi_ctrl->dev,
+ "HSI counters write usage: echo <values> > counters\n");
+ return -EINVAL;
+ }
+
+ clk_enable(hsi_ctrl->hsi_clk);
+ if (hsi_driver_device_is_hsi(pdev)) {
+ if (nwords != 3) {
+ dev_warn(hsi_ctrl->dev, "HSI counters write usage: "
+ "echo \"FT TB FB\" > counters\n");
+ ret = -EINVAL;
+ goto hsi_cnt_w_bk1;
+ }
+ strict_strtoul(words[0], 0, &ft);
+ strict_strtoul(words[1], 0, &tb);
+ strict_strtoul(words[2], 0, &fb);
+ reg = ((ft << HSI_COUNTERS_FT_OFFSET & HSI_COUNTERS_FT_MASK) |
+ (tb << HSI_COUNTERS_TB_OFFSET & HSI_COUNTERS_TB_MASK) |
+ (fb << HSI_COUNTERS_FB_OFFSET & HSI_COUNTERS_FB_MASK));
+ } else {
+ if (nwords != 1) {
+ dev_warn(hsi_ctrl->dev, "HSI counters write usage: "
+ "echo \"timeout\" > counters\n");
+ ret = -EINVAL;
+ goto hsi_cnt_w_bk1;
+ }
+ strict_strtoul(words[0], 0, ®);
+ }
+ hsi_outl(reg, base, HSI_HSR_COUNTERS_REG(port));
+ ret = count;
+ *offp += count;
+
+hsi_cnt_w_bk1:
+ clk_disable(hsi_ctrl->hsi_clk);
+
+ return ret;
+}
+
+static int hsi_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, hsi_debug_show, inode->i_private);
+}
+
+static int hsi_port_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, hsi_debug_port_show, inode->i_private);
+}
+
+static int hsi_gdd_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, hsi_debug_gdd_show, inode->i_private);
+}
+
+static const struct file_operations hsi_regs_fops = {
+ .open = hsi_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations hsi_port_regs_fops = {
+ .open = hsi_port_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations hsi_port_counters_fops = {
+ .open = hsi_port_counters_open,
+ .read = hsi_port_counters_read,
+ .write = hsi_port_counters_write,
+ .llseek = hsi_port_counters_seek,
+ .release = hsi_port_counters_release,
+};
+
+static const struct file_operations hsi_gdd_regs_fops = {
+ .open = hsi_gdd_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int __init hsi_debug_add_ctrl(struct hsi_dev *hsi_ctrl)
+{
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+ unsigned char dir_name[HSI_DIR_NAME_SIZE];
+ struct dentry *dir;
+ unsigned int port;
+
+ if (pdev->id < 0) {
+ hsi_ctrl->dir = debugfs_create_dir(pdev->name, hsi_dir);
+ } else {
+ snprintf(dir_name, sizeof(dir_name), "%s%d", pdev->name,
+ pdev->id);
+ hsi_ctrl->dir = debugfs_create_dir(dir_name, hsi_dir);
+ }
+ if (IS_ERR(hsi_ctrl->dir))
+ return PTR_ERR(hsi_ctrl->dir);
+
+ debugfs_create_file("regs", S_IRUGO, hsi_ctrl->dir, hsi_ctrl,
+ &hsi_regs_fops);
+
+ for (port = 0; port < hsi_ctrl->max_p; port++) {
+ snprintf(dir_name, sizeof(dir_name), "port%d", port + 1);
+ dir = debugfs_create_dir(dir_name, hsi_ctrl->dir);
+ if (IS_ERR(dir))
+ goto rback;
+ debugfs_create_file("regs", S_IRUGO, dir,
+ &hsi_ctrl->hsi_port[port], &hsi_port_regs_fops);
+ debugfs_create_file("counters", S_IRUGO | S_IWUGO, dir,
+ &hsi_ctrl->hsi_port[port],
+ &hsi_port_counters_fops);
+ }
+
+ dir = debugfs_create_dir("gdd", hsi_ctrl->dir);
+ if (IS_ERR(dir))
+ goto rback;
+ debugfs_create_file("regs", S_IRUGO, dir, hsi_ctrl, &hsi_gdd_regs_fops);
+
+ return 0;
+rback:
+ debugfs_remove_recursive(hsi_ctrl->dir);
+ return PTR_ERR(dir);
+}
+
+void hsi_debug_remove_ctrl(struct hsi_dev *hsi_ctrl)
+{
+ debugfs_remove_recursive(hsi_ctrl->dir);
+}
+
+int __init hsi_debug_init(void)
+{
+ hsi_dir = debugfs_create_dir("hsi", NULL);
+ if (IS_ERR(hsi_dir))
+ return PTR_ERR(hsi_dir);
+
+ return 0;
+}
+
+void hsi_debug_exit(void)
+{
+ debugfs_remove_recursive(hsi_dir);
+}
--
1.6.0.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* [RFC PATCH 5/9] HSI: Low Level Driver documentation
2009-10-30 9:59 [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Sebastien Jan
` (3 preceding siblings ...)
2009-10-30 10:00 ` [RFC PATCH 4/9] HSI: Low Level Driver debugfs support Sebastien Jan
@ 2009-10-30 10:00 ` Sebastien Jan
2009-10-30 10:00 ` [RFC PATCH 6/9] HSI: character driver interface Sebastien Jan
` (4 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Sebastien Jan @ 2009-10-30 10:00 UTC (permalink / raw)
To: linux-omap; +Cc: Sebastien Jan, Carlos Chinea
Signed-off-by: Sebastien Jan <s-jan@ti.com>
Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com>
---
Documentation/hsi/hsi.txt | 319 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 319 insertions(+), 0 deletions(-)
create mode 100644 Documentation/hsi/hsi.txt
diff --git a/Documentation/hsi/hsi.txt b/Documentation/hsi/hsi.txt
new file mode 100644
index 0000000..0890cff
--- /dev/null
+++ b/Documentation/hsi/hsi.txt
@@ -0,0 +1,319 @@
+OMAP HSI API's How To
+=====================
+
+The MIPI High Speed Synchronous Serial Interface (HSI) is a high speed
+communication interface that is used for connecting OMAP to a cellular modem
+engine.
+It is specified by the MIPI alliance (www.mipi.org). An introduction to the
+MIPI HSI working group can be found here: http://www.mipi.org/wgoverview.shtml
+
+The HSI interface supports full duplex communication over multiple channels and
+is capable of reaching speeds up to 200 Mbit/s.
+
+The OMAP HSI driver supports both OMAP MIPI HSI (as defined in
+MIPI documentation mipi_HSI-PL_specification_v01-01-00a.pdf) and OMAP SSI
+devices through different device files, and a generic SW driver.
+
+Please refer to the MIPI specifications for more details on this device.
+
+
+I OMAP HSI driver API overview
+-----------------------------
+
+A) HSI Bus, HSI channels and protocol drivers overview.
+
+The OMAP HSI driver implements the low-level support for the HSI device. It
+abstracts device specifics and provides a simple interface inside the kernel
+for data transmission on the HSI channels.
+The OMAP HSI driver does not implement any communication protocol.
+The SW layers using the OMAP HSI driver may implement a communication protocol
+if required, and are commonly called 'protocol drivers' in this document.
+
+The OMAP HSI abstracts the concept of HSI channels by creating an HSI bus and
+attaching HSI channel devices to it. (see Figure 1)
+
+Protocol drivers will then claim one or more HSI channels, after registering
+with the OMAP HSI driver.
+
+ +---------------------+ +----------------+
+ + HSI channel device + + HSI protocol +
+ + (omap_hsi.pX-cY) + <-------+ driver +
+ +---------------------+ +----------------+
+ | |
+(/sys/bus/hsi/devices/omap_hsi.pX-cy) (/sys/bus/hsi/drivers/hsi_protocol)
+ | |
++----------------------------------------------------------------+
++ HSI bus +
++----------------------------------------------------------------+
+
+ Figure 1.
+
+(NOTE: omap_hsi.pX-cY represents the HSI channel Y on port X from the omap_hsi
+device)
+
+B) Data transfers
+
+The OMAP HSI driver exports an asynchronous interface for sending and receiving
+data over the HSI channels. Protocol drivers will register a set of read and
+write completion callbacks for each HSI channel they use.
+
+Protocol drivers call hsi_write/hsi_read functions to signal the OMAP HSI driver
+that is willing to write/read data to/from a channel. Transfers are completed
+only when the OMAP HSI driver calls the completion callback.
+
+An HSI channel can simultaneously have both a read and a write request
+pending, however, requests cannot be queued.
+
+It is safe to call hsi_write/hsi_read functions inside the callback functions.
+In fact, a protocol driver should normally re-issue the read request from within
+the read callback, in order to not miss any incoming messages.
+
+Note on read / write operations:
+A read or write is performed using a HSI internal DMA channel, unless the size
+of data to transmit is one 32bits Word, where the transmission is directly
+managed through interrupts.
+
+C) Error handling
+
+HSI is a multi channel interface but the channels share the same physical wires.
+Therefore, any transmission error potentially affects all the protocol drivers
+that sit on top of the HSI driver. Whenever an error occurs, it is broadcast
+to all protocol drivers.
+
+Errors are signaled to the protocol drivers through the port_event callback.
+
+Completion callbacks functions are only called when a transfer has succeess.
+
+D) Supported modes of operation
+
+The driver supports stream and frame transmission modes and synchronized and
+pipelined data flows.
+The driver implements the HSI support for the core MPU and not for the
+potential co-processors.
+
+
+II OMAP HSI API's
+-----------------
+
+A) Include
+
+#include<linux/hsi_driver_if.h>
+
+B) int hsi_register_driver(struct hsi_device_driver *driver);
+
+ Description: Register an HSI protocol driver
+
+ Parameter: A protocol driver declaration (see struct hsi_device_driver)
+
+C) void hsi_unregister_driver(struct hsi_device_driver *driver);
+
+ Description: Unregister an HSI protocol driver
+
+ Parameter: A protocol driver declaration (see struct hsi_device_driver)
+
+D) int hsi_open(struct hsi_device *dev);
+
+ Description: Open an HSI device channel
+
+ Parameter: The HSI channel
+
+E) int hsi_write(struct hsi_device *dev, u32 *data, unsigned int count);
+
+ Description: Send data through an HSI channel. The transfer is only completed
+ when the write_complete callback is called
+
+ Parameters:
+ - dev: HSI channel
+ - data: pointer to the data to send
+ - count: number of 32-bit words to be sent
+
+F) void hsi_write_cancel(struct hsi_device *dev);
+
+ Description: Cancel current pending write operation
+
+ Parameters: HSI channel
+
+G) int hsi_read(struct hsi_device *dev, u32 *data, unsigned int count);
+
+ Description: Receive data through an HSI channel. The transfer is only
+ completed when the read_complete callback is called
+
+ Parameters:
+ - dev: HSI channel
+ - data: pointer where to store the data
+ - count: number of 32-bit words to be read
+
+H) void hsi_read_cancel(struct hsi_device *dev);
+
+ Description: Cancel current pending read operation
+
+ Parameters: HSI channel
+
+I) int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg);
+
+ Description: Apply some control command to the port associated to the given
+ HSI channel
+
+ Parameters:
+ - dev: HSI channel
+ - command: command to execute
+ - arg: parameter for the control command
+
+ Commands:
+ - HSI_IOCTL_WAKE_UP:
+ Description: Set HSI wakeup line for the channel
+ Parameters: None
+ - HSI_IOCTL_WAKE_DOWN:
+ Description: Unset HSI wakeup line for the channel
+ Parameters: None
+ - HSI_IOCTL_SEND_BREAK:
+ Description: Send a HW BREAK frame in FRAME mode
+ Parameters: None
+ - HSI_IOCTL_WAKE:
+ Description: Get HST WAKE line status
+ Parameters: Pointer to a u32 variable to return result
+ (Result: 0 means wakeline DOWN, other result means wakeline UP)
+ - HSI_IOCTL_FLUSH_RX:
+ Description: Force the HSR to idle state
+ Parameters: None
+ - HSI_IOCTL_FLUSH_TX:
+ Description: Force the HST to idle state
+ Parameters: None
+ - HSI_IOCTL_CAWAKE:
+ Description: Get CAWAKE (HSR) line status
+ Parameters: Pointer to a u32 variable to return result
+ (Result: 0 means wakeline DOWN, other result means wakeline UP)
+ - HSI_IOCTL_SET_RX:
+ Description: Set HSR configuration
+ Parameters: Pointer to a hsr_ctx structure describing
+ configurable HSR parameters (mode, frame size, channels,
+ data flow type, bit-rate divisor)
+ Notes:
+ HSI: A special value (0x1000) can be passed as bit-rate divisor
+ to request the HSR so switch to auto-divisor mode (in this mode,
+ the HSR can receive at any speed, but the error detection is
+ deactivated). To exit this RX auto-divisor mode, a new divisor
+ must be programmed for HSI (can be 0), and the error-detection
+ is re-enabled.
+ SSI: The same special 0x1000 value is used to deactivate the SSR
+ timeout counter. This counter can be re-enabled by programming
+ the 0x1001 value as bit-rate divisor. The SSI does not accept
+ any other SSR bit-rate divisor values.
+ - HSI_IOCTL_GET_RX:
+ Description: Get HSR configuration
+ Parameters: Pointer to a Hsr_ctx structure describing
+ configurable HSR parameters (mode, frame size, channels,
+ data flow type, bit-rate divisor)
+ - HSI_IOCTL_SET_TX:
+ Description: Set HST configuration
+ Parameters: Pointer to a hst_ctx structure describing
+ configurable HST parameters (mode, frame size, divisor,
+ arb_mode, channels, data flow type)
+ - HSI_IOCTL_GET_TX:
+ Description: Get HST configuration
+ Parameters: Pointer to a hst_ctx structure describing
+ configurable SSR parameters (mode, frame size, divisor,
+ arb_mode, channels, data flow type)
+ Note that the Rx and Tx transmission speeds are configured through the
+ bit-rate divisor parameters. The HSI driver user shall take care of the
+ functional clock provided to the HSI and program the divisors
+ accordingly. Depending on the required speed and HSI device, some
+ constraints on OPP may have to be handled. This shall be managed by the
+ HSI driver user.
+
+J) void hsi_close(struct hsi_device *dev);
+
+ Description: Close an HSI channel
+
+ Parameters: The HSI channel to close
+
+K) Callback configuration functions:
+void hsi_set_read_cb(struct hsi_device *dev,
+ void (*read_cb)(struct hsi_device *dev));
+void hsi_set_write_cb(struct hsi_device *dev,
+ void (*write_cb)(struct hsi_device *dev));
+void hsi_set_port_event_cb(struct hsi_device *dev,
+ void (*port_event_cb)(struct hsi_device *dev,
+ unsigned int event, void *arg));
+
+ Description: Set the read, write and port-event callbacks for the HSI channel.
+ These functions are usually called in the probe function of the HSI protocol
+ driver to set completion callbacks for the asynchronous read and write
+ transfer, and manage the other HSI events.
+
+ Parameters:
+ - dev: HSI channel
+ - read_cb: Pointer to a callback function to signal that a read transfer
+ is completed
+ - write_cb: Pointer to a callback function to signal that a write
+ transfer is completed
+ - port_event_cb: Pointer to a callback function to signal that a HSI
+ event has happened (events can be: Break frame detected, error,
+ cawake-up, cawake-down or HSR-data-available).
+
+
+L) struct hsi_device_driver
+
+Description: Protocol drivers pass this struct to the hsi_register_driver
+function in order to register with the OMAP HSI driver. Among other things it
+tells the OMAP HSI driver which channels the protocol driver wants to allocate
+for its use
+
+Declaration:
+struct hsi_device_driver {
+ unsigned long ctrl_mask;
+ unsigned long ch_mask[HSI_MAX_PORTS];
+ void (*port_event) (struct hsi_device *dev,
+ unsigned int event, void *arg);
+ int (*probe)(struct hsi_device *dev);
+ int (*remove)(struct hsi_device *dev);
+ int (*suspend)(struct hsi_device *dev,
+ pm_message_t mesg);
+ int (*resume)(struct hsi_device *dev);
+ struct device_driver driver;
+};
+
+Fields description:
+ ctrl_mask: HSI block ids to use
+ ch_mask[HSI_MAX_PORTS]: HSI channels to use
+ port_event: Function callback for notifying HSI events
+ (i.e.: error transfer)
+ Parameters:
+ dev: HSI channel
+ event: Event code
+ event: Event argument
+ probe: Probe function
+ Parameters: HSI channel
+ remove: Remove function
+ Parameters: HSI channel
+
+Example:
+
+static struct hsi_device_driver hsi_protocol_driver = {
+ .ctrl_mask = ANY_HSI_CONTROLLER,
+ .ch_mask[0] = CHANNEL(0) | CHANNEL(1),
+ .port_event = port_event_callback,
+ .probe = hsi_proto_probe,
+ .remove = __devexit_p(hsi_proto_remove),
+ .driver = {
+ .name = "hsi_protocol",
+ },
+};
+
+
+III) FUTURE WORK
+----------------
+ - Power management update and validation on OMAP 3430
+ - Clocks and power management implementation on OMAP 4430
+ - Validation on OMAP 4430
+ - hsi-char documentation
+
+
+=================================================
+Acknowledgements: The OMAP HSI driver is based on OMAP SSI driver, written by
+Carlos Chinea <carlos.chinea@nokia.com>.
+
+Contact: Sebastien Jan <s-jan@ti.com>
+
+Copyright (C) 2008 Nokia Corporation.
+Copyright (C) 2009 Texas Instruments, Inc.
--
1.6.0.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* [RFC PATCH 6/9] HSI: character driver interface
2009-10-30 9:59 [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Sebastien Jan
` (4 preceding siblings ...)
2009-10-30 10:00 ` [RFC PATCH 5/9] HSI: Low Level Driver documentation Sebastien Jan
@ 2009-10-30 10:00 ` Sebastien Jan
2009-10-30 10:00 ` [RFC PATCH 7/9] HSI: character driver low level interface Sebastien Jan
` (3 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Sebastien Jan @ 2009-10-30 10:00 UTC (permalink / raw)
To: linux-omap; +Cc: Sebastien Jan, Andras Domokos
Character driver header and interface implementation
Integration in the build and configuration
Signed-off-by: Sebastien Jan <s-jan@ti.com>
Signed-off-by: Andras Domokos <andras.domokos@nokia.com>
---
drivers/Kconfig | 2 +
drivers/hsi/Kconfig | 14 ++
drivers/hsi/Makefile | 3 +
drivers/hsi/hsi-char.c | 526 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/hsi/hsi-char.h | 31 +++
include/linux/hsi_char.h | 71 +++++++
6 files changed, 647 insertions(+), 0 deletions(-)
create mode 100644 drivers/hsi/hsi-char.c
create mode 100644 drivers/hsi/hsi-char.h
create mode 100644 include/linux/hsi_char.h
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 48bbdbe..8fc1c36 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -52,6 +52,8 @@ source "drivers/i2c/Kconfig"
source "drivers/spi/Kconfig"
+source "drivers/hsi/Kconfig"
+
source "drivers/pps/Kconfig"
source "drivers/gpio/Kconfig"
diff --git a/drivers/hsi/Kconfig b/drivers/hsi/Kconfig
index e10b522..79e0e02 100644
--- a/drivers/hsi/Kconfig
+++ b/drivers/hsi/Kconfig
@@ -42,3 +42,17 @@ config OMAP_SSI_DEVICE
bool "SSI (OMAP SSI)"
endchoice
+
+#
+# OMAP HSI char device kernel configuration
+#
+
+config HSI_CHAR
+ tristate "HSI character driver"
+ depends on OMAP_HSI
+ ---help---
+ If you say Y here, you will enable the HSI character driver.
+ This driver provides a simple character device interface for
+ serial communication over the HSI bus.
+
+endif # HSI
diff --git a/drivers/hsi/Makefile b/drivers/hsi/Makefile
index a7f579b..81270ec 100644
--- a/drivers/hsi/Makefile
+++ b/drivers/hsi/Makefile
@@ -11,4 +11,7 @@ ifeq ($(CONFIG_DEBUG_FS), y)
omap_hsi-objs += hsi_driver_debugfs.o
endif
+hsi_char-objs := hsi-char.o hsi-if.o
+
obj-$(CONFIG_OMAP_HSI) += omap_hsi.o
+obj-$(CONFIG_HSI_CHAR) += hsi_char.o
diff --git a/drivers/hsi/hsi-char.c b/drivers/hsi/hsi-char.c
new file mode 100644
index 0000000..29fbd73
--- /dev/null
+++ b/drivers/hsi/hsi-char.c
@@ -0,0 +1,526 @@
+/*
+ * hsi-char.c
+ *
+ * HSI character device driver, implements the character device
+ * interface.
+ *
+ * Copyright (C) 2009 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Andras Domokos <andras.domokos@nokia.com>
+ * Author: Sebastien JAN <s-jan@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/errno.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/miscdevice.h>
+#include <linux/file.h>
+#include <linux/mm.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/poll.h>
+#include <asm/mach-types.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+
+#include <mach/hsi.h>
+#include <linux/hsi_driver_if.h>
+#include <linux/hsi_char.h>
+
+#include "hsi-char.h"
+
+#define DRIVER_VERSION "0.1.0"
+
+static unsigned int port = 1;
+module_param(port, uint, 1);
+MODULE_PARM_DESC(port, "HSI port to be probed");
+
+static unsigned int channels_map[HSI_MAX_CHAR_DEVS] = {1};
+module_param_array(channels_map, uint, NULL, 0);
+MODULE_PARM_DESC(channels_map, "HSI channels to be probed");
+
+dev_t hsi_char_dev;
+
+struct char_queue {
+ struct list_head list;
+ u32 *data;
+ unsigned int count;
+};
+
+struct hsi_char {
+ unsigned int opened;
+ int poll_event;
+ struct list_head rx_queue;
+ struct list_head tx_queue;
+ spinlock_t lock; /* Serialize access to driver data and API */
+ struct fasync_struct *async_queue;
+ wait_queue_head_t rx_wait;
+ wait_queue_head_t tx_wait;
+ wait_queue_head_t poll_wait;
+};
+
+static struct hsi_char hsi_char_data[HSI_MAX_CHAR_DEVS];
+
+void if_notify(int ch, struct hsi_event *ev)
+{
+ struct char_queue *entry;
+
+ pr_debug("%s, ev = {0x%x, 0x%p, %u}\n", __func__, ev->event, ev->data,
+ ev->count);
+
+ spin_lock(&hsi_char_data[ch].lock);
+
+ if (!hsi_char_data[ch].opened) {
+ pr_debug("%s, device not opened\n!", __func__);
+ spin_unlock(&hsi_char_data[ch].lock);
+ return;
+ }
+
+ switch (HSI_EV_TYPE(ev->event)) {
+ case HSI_EV_IN:
+ entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
+ if (!entry) {
+ pr_err("HSI-CHAR: entry allocation failed.\n");
+ spin_unlock(&hsi_char_data[ch].lock);
+ return;
+ }
+ entry->data = ev->data;
+ entry->count = ev->count;
+ list_add_tail(&entry->list, &hsi_char_data[ch].rx_queue);
+ spin_unlock(&hsi_char_data[ch].lock);
+ pr_debug("%s, HSI_EV_IN\n", __func__);
+ wake_up_interruptible(&hsi_char_data[ch].rx_wait);
+ break;
+ case HSI_EV_OUT:
+ entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
+ if (!entry) {
+ pr_err("HSI-CHAR: entry allocation failed.\n");
+ spin_unlock(&hsi_char_data[ch].lock);
+ return;
+ }
+ entry->data = ev->data;
+ entry->count = ev->count;
+ hsi_char_data[ch].poll_event |= (POLLOUT | POLLWRNORM);
+ list_add_tail(&entry->list, &hsi_char_data[ch].tx_queue);
+ spin_unlock(&hsi_char_data[ch].lock);
+ pr_debug("%s, HSI_EV_OUT\n", __func__);
+ wake_up_interruptible(&hsi_char_data[ch].tx_wait);
+ break;
+ case HSI_EV_EXCEP:
+ hsi_char_data[ch].poll_event |= POLLPRI;
+ spin_unlock(&hsi_char_data[ch].lock);
+ pr_debug("%s, HSI_EV_EXCEP\n", __func__);
+ wake_up_interruptible(&hsi_char_data[ch].poll_wait);
+ break;
+ case HSI_EV_AVAIL:
+ hsi_char_data[ch].poll_event |= (POLLIN | POLLRDNORM);
+ spin_unlock(&hsi_char_data[ch].lock);
+ pr_debug("%s, HSI_EV_AVAIL\n", __func__);
+ wake_up_interruptible(&hsi_char_data[ch].poll_wait);
+ break;
+ default:
+ spin_unlock(&hsi_char_data[ch].lock);
+ break;
+ }
+}
+
+
+static int hsi_char_fasync(int fd, struct file *file, int on)
+{
+ int ch = (int)file->private_data;
+ if (fasync_helper(fd, file, on, &hsi_char_data[ch].async_queue) >= 0)
+ return 0;
+ else
+ return -EIO;
+}
+
+
+static unsigned int hsi_char_poll(struct file *file, poll_table *wait)
+{
+ int ch = (int)file->private_data;
+ unsigned int ret = 0;
+
+ /*printk(KERN_DEBUG "%s\n", __func__);*/
+
+ poll_wait(file, &hsi_char_data[ch].poll_wait, wait);
+ poll_wait(file, &hsi_char_data[ch].tx_wait, wait);
+ spin_lock_bh(&hsi_char_data[ch].lock);
+ ret = hsi_char_data[ch].poll_event;
+ spin_unlock_bh(&hsi_char_data[ch].lock);
+
+ pr_debug("%s, ret = 0x%x\n", __func__, ret);
+ return ret;
+}
+
+
+static ssize_t hsi_char_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int ch = (int)file->private_data;
+ DECLARE_WAITQUEUE(wait, current);
+ u32 *data;
+ unsigned int data_len;
+ struct char_queue *entry;
+ ssize_t ret;
+
+ /*printk(KERN_DEBUG "%s, count = %d\n", __func__, count);*/
+
+ /* only 32bit data is supported for now */
+ if ((count < 4) || (count & 3))
+ return -EINVAL;
+
+ data = kmalloc(count, GFP_ATOMIC);
+
+ ret = if_hsi_read(ch, data, count);
+ if (ret < 0) {
+ kfree(data);
+ goto out2;
+ }
+
+ add_wait_queue(&hsi_char_data[ch].rx_wait, &wait);
+
+ for ( ; ; ) {
+ data = NULL;
+ data_len = 0;
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ spin_lock_bh(&hsi_char_data[ch].lock);
+ if (!list_empty(&hsi_char_data[ch].rx_queue)) {
+ entry = list_entry(hsi_char_data[ch].rx_queue.next,
+ struct char_queue, list);
+ data = entry->data;
+ data_len = entry->count;
+ list_del(&entry->list);
+ kfree(entry);
+ }
+ spin_unlock_bh(&hsi_char_data[ch].lock);
+
+ pr_debug("%s, data = 0x%p, data_len = %d\n",
+ __func__, data, data_len);
+
+ if (data_len) {
+ pr_debug("%s, RX finished\n", __func__);
+ spin_lock_bh(&hsi_char_data[ch].lock);
+ hsi_char_data[ch].poll_event &= ~(POLLIN | POLLRDNORM);
+ if_hsi_poll(ch);
+ spin_unlock_bh(&hsi_char_data[ch].lock);
+ break;
+ } else if (file->f_flags & O_NONBLOCK) {
+ pr_debug("%s, O_NONBLOCK\n", __func__);
+ ret = -EAGAIN;
+ goto out;
+ } else if (signal_pending(current)) {
+ pr_debug("%s, ERESTARTSYS\n", __func__);
+ ret = -EAGAIN;
+ if_hsi_cancel_read(ch);
+ /* goto out; */
+ break;
+ }
+
+ /*printk(KERN_DEBUG "%s, going to sleep...\n", __func__);*/
+ schedule();
+ /*printk(KERN_DEBUG "%s, woke up\n", __func__);*/
+ }
+
+ if (data_len) {
+ ret = copy_to_user((void __user *)buf, data, data_len);
+ if (!ret)
+ ret = data_len;
+ }
+
+ kfree(data);
+
+out:
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&hsi_char_data[ch].rx_wait, &wait);
+
+out2:
+ /*printk(KERN_DEBUG "%s, ret = %d\n", __func__, ret);*/
+ return ret;
+}
+
+static ssize_t hsi_char_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int ch = (int)file->private_data;
+ DECLARE_WAITQUEUE(wait, current);
+ u32 *data;
+ unsigned int data_len = 0;
+ struct char_queue *entry;
+ ssize_t ret;
+
+ /*printk(KERN_DEBUG "%s, count = %d\n", __func__, count);*/
+
+ /* only 32bit data is supported for now */
+ if ((count < 4) || (count & 3))
+ return -EINVAL;
+
+ data = kmalloc(count, GFP_ATOMIC);
+
+ if (copy_from_user(data, (void __user *)buf, count)) {
+ ret = -EFAULT;
+ kfree(data);
+ } else {
+ ret = count;
+ }
+
+ spin_lock_bh(&hsi_char_data[ch].lock);
+ ret = if_hsi_write(ch, data, count);
+ if (ret < 0) {
+ spin_unlock_bh(&hsi_char_data[ch].lock);
+ kfree(data);
+ goto out2;
+ }
+ hsi_char_data[ch].poll_event &= ~(POLLOUT | POLLWRNORM);
+ spin_unlock_bh(&hsi_char_data[ch].lock);
+
+ add_wait_queue(&hsi_char_data[ch].tx_wait, &wait);
+
+ for ( ; ; ) {
+ data = NULL;
+ data_len = 0;
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ spin_lock_bh(&hsi_char_data[ch].lock);
+ if (!list_empty(&hsi_char_data[ch].tx_queue)) {
+ entry = list_entry(hsi_char_data[ch].tx_queue.next,
+ struct char_queue, list);
+ data = entry->data;
+ data_len = entry->count;
+ list_del(&entry->list);
+ kfree(entry);
+ }
+ spin_unlock_bh(&hsi_char_data[ch].lock);
+
+ if (data_len) {
+ pr_debug("%s, TX finished\n", __func__);
+ ret = data_len;
+ break;
+ } else if (file->f_flags & O_NONBLOCK) {
+ pr_debug("%s, O_NONBLOCK\n", __func__);
+ ret = -EAGAIN;
+ goto out;
+ } else if (signal_pending(current)) {
+ pr_debug("%s, ERESTARTSYS\n", __func__);
+ ret = -ERESTARTSYS;
+ goto out;
+ }
+
+ /*printk(KERN_DEBUG "%s, going to sleep...\n", __func__);*/
+ schedule();
+ /*printk(KERN_DEBUG "%s, woke up\n", __func__);*/
+ }
+
+ kfree(data);
+
+out:
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&hsi_char_data[ch].tx_wait, &wait);
+
+out2:
+ /*printk(KERN_DEBUG "%s, ret = %d\n", __func__, ret);*/
+ return ret;
+}
+
+static int hsi_char_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int ch = (int)file->private_data;
+ unsigned int state;
+ struct hsi_rx_config rx_cfg;
+ struct hsi_tx_config tx_cfg;
+ int ret = 0;
+
+ pr_debug("%s, ch = %d, cmd = 0x%08x\n", __func__, ch, cmd);
+
+ switch (cmd) {
+ case CS_SEND_BREAK:
+ if_hsi_send_break(ch);
+ break;
+ case CS_FLUSH_RX:
+ if_hsi_flush_rx(ch);
+ break;
+ case CS_FLUSH_TX:
+ if_hsi_flush_tx(ch);
+ break;
+ case CS_SET_WAKELINE:
+ if (copy_from_user(&state, (void __user *)arg,
+ sizeof(state)))
+ ret = -EFAULT;
+ else
+ if_hsi_set_wakeline(ch, state);
+ break;
+ case CS_GET_WAKELINE:
+ if_hsi_get_wakeline(ch, &state);
+ if (copy_to_user((void __user *)arg, &state, sizeof(state)))
+ ret = -EFAULT;
+ break;
+ case CS_SET_RX:
+ if (copy_from_user(&rx_cfg, (void __user *)arg,
+ sizeof(rx_cfg)))
+ ret = -EFAULT;
+ else
+ ret = if_hsi_set_rx(ch, &rx_cfg);
+ break;
+ case CS_GET_RX:
+ if_hsi_get_rx(ch, &rx_cfg);
+ if (copy_to_user((void __user *)arg, &rx_cfg, sizeof(rx_cfg)))
+ ret = -EFAULT;
+ break;
+ case CS_SET_TX:
+ if (copy_from_user(&tx_cfg, (void __user *)arg,
+ sizeof(tx_cfg)))
+ ret = -EFAULT;
+ else
+ ret = if_hsi_set_tx(ch, &tx_cfg);
+ break;
+ case CS_GET_TX:
+ if_hsi_get_tx(ch, &tx_cfg);
+ if (copy_to_user((void __user *)arg, &tx_cfg, sizeof(tx_cfg)))
+ ret = -EFAULT;
+ break;
+ default:
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+
+ return ret;
+}
+
+static int hsi_char_open(struct inode *inode, struct file *file)
+{
+ int ret = 0, ch = iminor(inode);
+
+ pr_debug("%s, ch = %d, channels_map[%d] = %d\n", __func__, ch, ch,
+ channels_map[ch]);
+
+ if (!channels_map[ch])
+ return -ENODEV;
+
+ spin_lock_bh(&hsi_char_data[ch].lock);
+
+ if (hsi_char_data[ch].opened) {
+ spin_unlock_bh(&hsi_char_data[ch].lock);
+ return -EBUSY;
+ }
+
+ file->private_data = (void *)ch;
+ hsi_char_data[ch].opened++;
+ hsi_char_data[ch].poll_event = (POLLOUT | POLLWRNORM);
+ spin_unlock_bh(&hsi_char_data[ch].lock);
+
+ ret = if_hsi_start(ch);
+
+ return ret;
+}
+
+static int hsi_char_release(struct inode *inode, struct file *file)
+{
+ int ch = (int)file->private_data;
+ struct char_queue *entry;
+ struct list_head *cursor, *next;
+
+ pr_debug("%s, ch = %d\n", __func__, ch);
+
+ if_hsi_stop(ch);
+ spin_lock_bh(&hsi_char_data[ch].lock);
+ hsi_char_data[ch].opened--;
+
+ if (!list_empty(&hsi_char_data[ch].rx_queue)) {
+ list_for_each_safe(cursor, next, &hsi_char_data[ch].rx_queue) {
+ entry = list_entry(cursor, struct char_queue, list);
+ list_del(&entry->list);
+ kfree(entry);
+ }
+ }
+
+ if (!list_empty(&hsi_char_data[ch].tx_queue)) {
+ list_for_each_safe(cursor, next, &hsi_char_data[ch].tx_queue) {
+ entry = list_entry(cursor, struct char_queue, list);
+ list_del(&entry->list);
+ kfree(entry);
+ }
+ }
+
+ spin_unlock_bh(&hsi_char_data[ch].lock);
+
+ return 0;
+}
+
+static const struct file_operations hsi_char_fops = {
+ .owner = THIS_MODULE,
+ .read = hsi_char_read,
+ .write = hsi_char_write,
+ .poll = hsi_char_poll,
+ .ioctl = hsi_char_ioctl,
+ .open = hsi_char_open,
+ .release = hsi_char_release,
+ .fasync = hsi_char_fasync,
+};
+
+static struct cdev hsi_char_cdev;
+
+static int __init hsi_char_init(void)
+{
+ char devname[] = "hsi_char";
+ int ret, i;
+
+ pr_info("HSI character device version " DRIVER_VERSION "\n");
+
+ for (i = 0; i < HSI_MAX_CHAR_DEVS; i++) {
+ init_waitqueue_head(&hsi_char_data[i].rx_wait);
+ init_waitqueue_head(&hsi_char_data[i].tx_wait);
+ init_waitqueue_head(&hsi_char_data[i].poll_wait);
+ spin_lock_init(&hsi_char_data[i].lock);
+ hsi_char_data[i].opened = 0;
+ INIT_LIST_HEAD(&hsi_char_data[i].rx_queue);
+ INIT_LIST_HEAD(&hsi_char_data[i].tx_queue);
+ }
+
+ /*printk(KERN_DEBUG "%s, devname = %s\n", __func__, devname);*/
+
+ ret = if_hsi_init(port, channels_map);
+ if (ret)
+ return ret;
+
+ ret = alloc_chrdev_region(&hsi_char_dev, 0, HSI_MAX_CHAR_DEVS, devname);
+ if (ret < 0) {
+ pr_err("HSI character driver: Failed to register\n");
+ return ret;
+ }
+
+ cdev_init(&hsi_char_cdev, &hsi_char_fops);
+ cdev_add(&hsi_char_cdev, hsi_char_dev, HSI_MAX_CHAR_DEVS);
+
+ return 0;
+}
+
+static void __exit hsi_char_exit(void)
+{
+ cdev_del(&hsi_char_cdev);
+ unregister_chrdev_region(hsi_char_dev, HSI_MAX_CHAR_DEVS);
+ if_hsi_exit();
+}
+
+MODULE_AUTHOR("Andras Domokos <andras.domokos@nokia.com>");
+MODULE_AUTHOR("Sebatien Jan <s-jan@ti.com> / Texas Instruments");
+MODULE_DESCRIPTION("HSI character device");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(hsi_char_init);
+module_exit(hsi_char_exit);
diff --git a/drivers/hsi/hsi-char.h b/drivers/hsi/hsi-char.h
new file mode 100644
index 0000000..d37018f
--- /dev/null
+++ b/drivers/hsi/hsi-char.h
@@ -0,0 +1,31 @@
+/*
+ * hsi-char.h
+ *
+ * HSI character driver private declaration header file.
+ *
+ * Copyright (C) 2009 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Andras Domokos <andras.domokos@nokia.com>
+ * Author: Sebastien JAN <s-jan@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_CHAR_H
+#define _HSI_CHAR_H
+
+#include "hsi-if.h"
+
+/* how many char devices would be created at most */
+#define HSI_MAX_CHAR_DEVS 8
+
+void if_notify(int ch, struct hsi_event *ev);
+
+#endif /* _HSI_CHAR_H */
diff --git a/include/linux/hsi_char.h b/include/linux/hsi_char.h
new file mode 100644
index 0000000..de42c6c
--- /dev/null
+++ b/include/linux/hsi_char.h
@@ -0,0 +1,71 @@
+/*
+ * hsi_char.h
+ *
+ * HSI character driver public declaration header file.
+ *
+ * Copyright (C) 2009 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Andras Domokos <andras.domokos@nokia.com>
+ * Author: Sebastien JAN <s-jan@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_CHAR_H
+#define HSI_CHAR_H
+
+#define HSI_CHAR_BASE 'S'
+#define CS_IOW(num, dtype) _IOW(HSI_CHAR_BASE, num, dtype)
+#define CS_IOR(num, dtype) _IOR(HSI_CHAR_BASE, num, dtype)
+#define CS_IOWR(num, dtype) _IOWR(HSI_CHAR_BASE, num, dtype)
+#define CS_IO(num) _IO(HSI_CHAR_BASE, num)
+
+#define CS_SEND_BREAK CS_IO(1)
+#define CS_FLUSH_RX CS_IO(2)
+#define CS_FLUSH_TX CS_IO(3)
+#define CS_BOOTSTRAP CS_IO(4)
+#define CS_SET_WAKELINE CS_IOW(5, unsigned int)
+#define CS_GET_WAKELINE CS_IOR(6, unsigned int)
+#define CS_SET_RX CS_IOW(7, struct hsi_rx_config)
+#define CS_GET_RX CS_IOW(8, struct hsi_rx_config)
+#define CS_SET_TX CS_IOW(9, struct hsi_tx_config)
+#define CS_GET_TX CS_IOW(10, struct hsi_tx_config)
+
+#define HSI_MODE_SLEEP 0
+#define HSI_MODE_STREAM 1
+#define HSI_MODE_FRAME 2
+
+#define HSI_FLOW_SYNCHRONIZED (0 << 2)
+#define HSI_FLOW_PIPELINED (1 << 2) /* only for HSI */
+
+#define HSI_ARBMODE_RR 0
+#define HSI_ARBMODE_PRIO 1
+
+#define WAKE_UP 0
+#define WAKE_DOWN 1
+
+struct hsi_tx_config {
+ u32 mode;
+ u32 flow;
+ u32 frame_size;
+ u32 channels;
+ u32 divisor;
+ u32 arb_mode;
+};
+
+struct hsi_rx_config {
+ u32 mode;
+ u32 flow;
+ u32 frame_size;
+ u32 channels;
+ u32 divisor; /* not used for SSI */
+};
+
+#endif /* HSI_CHAR_H */
--
1.6.0.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* [RFC PATCH 7/9] HSI: character driver low level interface
2009-10-30 9:59 [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Sebastien Jan
` (5 preceding siblings ...)
2009-10-30 10:00 ` [RFC PATCH 6/9] HSI: character driver interface Sebastien Jan
@ 2009-10-30 10:00 ` Sebastien Jan
2009-10-30 10:00 ` [RFC PATCH 8/9] HSI: HSI device support Sebastien Jan
` (2 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Sebastien Jan @ 2009-10-30 10:00 UTC (permalink / raw)
To: linux-omap; +Cc: Sebastien Jan, Andras Domokos
Character driver interface with omap_hsi driver (LLD)
Signed-off-by: Sebastien Jan <s-jan@ti.com>
Signed-off-by: Andras Domokos <andras.domokos@nokia.com>
---
drivers/hsi/hsi-if.c | 642 ++++++++++++++++++++++++++++++++++++++++++++++++++
drivers/hsi/hsi-if.h | 65 +++++
2 files changed, 707 insertions(+), 0 deletions(-)
create mode 100644 drivers/hsi/hsi-if.c
create mode 100644 drivers/hsi/hsi-if.h
diff --git a/drivers/hsi/hsi-if.c b/drivers/hsi/hsi-if.c
new file mode 100644
index 0000000..2c9ba0c
--- /dev/null
+++ b/drivers/hsi/hsi-if.c
@@ -0,0 +1,642 @@
+/*
+ * hsi-if.c
+ *
+ * Part of the HSI character driver, implements the HSI interface.
+ *
+ * Copyright (C) 2009 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Andras Domokos <andras.domokos@nokia.com>
+ * Author: Sebastien JAN <s-jan@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/errno.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <asm/mach-types.h>
+#include <linux/ioctl.h>
+#include <linux/delay.h>
+#include <linux/ktime.h>
+#include <linux/bitmap.h>
+
+#include <linux/hsi_driver_if.h>
+#include <linux/hsi_char.h>
+
+#include "hsi-char.h"
+#include "hsi-if.h"
+
+#define HSI_CHANNEL_STATE_UNAVAIL (1 << 0)
+#define HSI_CHANNEL_STATE_READING (1 << 1)
+#define HSI_CHANNEL_STATE_WRITING (1 << 2)
+
+#define PORT1 0
+#define PORT2 1
+
+#define RXCONV(dst, src) \
+ do { \
+ (dst)->mode = (src)->mode; \
+ (dst)->flow = (src)->flow; \
+ (dst)->frame_size = (src)->frame_size; \
+ (dst)->channels = (src)->channels; \
+ (dst)->divisor = (src)->divisor; \
+ } while (0)
+
+#define TXCONV(dst, src) \
+ do { \
+ (dst)->mode = (src)->mode; \
+ (dst)->flow = (src)->flow; \
+ (dst)->frame_size = (src)->frame_size; \
+ (dst)->channels = (src)->channels; \
+ (dst)->divisor = (src)->divisor; \
+ (dst)->arb_mode = (src)->arb_mode; \
+ } while (0)
+
+struct if_hsi_channel {
+ struct hsi_device *dev;
+ unsigned int channel_id;
+ u32 *tx_data;
+ unsigned int tx_count;
+ u32 *rx_data;
+ unsigned int rx_count;
+ unsigned int opened;
+ unsigned int state;
+ spinlock_t lock; /* Serializes access to channel data */
+};
+
+struct if_hsi_iface {
+ struct if_hsi_channel channels[HSI_MAX_CHAR_DEVS];
+ int bootstrap;
+ unsigned long init_chan_map;
+ spinlock_t lock; /* Serializes access to HSI functional interface */
+};
+
+static void if_hsi_port_event(struct hsi_device *dev, unsigned int event,
+ void *arg);
+static int __devinit if_hsi_probe(struct hsi_device *dev);
+static int __devexit if_hsi_remove(struct hsi_device *dev);
+
+static struct hsi_device_driver if_hsi_char_driver = {
+ .ctrl_mask = ANY_HSI_CONTROLLER,
+ .probe = if_hsi_probe,
+ .remove = __devexit_p(if_hsi_remove),
+ .driver = {
+ .name = "hsi_char"
+ },
+};
+
+static struct if_hsi_iface hsi_iface;
+
+static int if_hsi_read_on(int ch, u32 *data, unsigned int count)
+{
+ struct if_hsi_channel *channel;
+ int ret;
+
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+
+ spin_lock(&channel->lock);
+ if (channel->state & HSI_CHANNEL_STATE_READING) {
+ pr_err("Read still pending on channel %d\n", ch);
+ spin_unlock(&channel->lock);
+ return -EBUSY;
+ }
+ channel->state |= HSI_CHANNEL_STATE_READING;
+ channel->rx_data = data;
+ channel->rx_count = count;
+ spin_unlock(&channel->lock);
+
+ ret = hsi_read(channel->dev, data, count/4);
+ dev_dbg(&channel->dev->device, "%s, ch = %d, ret = %d\n", __func__, ch,
+ ret);
+
+ return ret;
+}
+
+static void if_hsi_read_done(struct hsi_device *dev, unsigned int size)
+{
+ struct if_hsi_channel *channel;
+ struct hsi_event ev;
+
+ channel = &hsi_iface.channels[dev->n_ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, dev->n_ch);
+ spin_lock(&channel->lock);
+ channel->state &= ~HSI_CHANNEL_STATE_READING;
+ ev.event = HSI_EV_IN;
+ ev.data = channel->rx_data;
+ ev.count = size;
+ spin_unlock(&channel->lock);
+ if_notify(dev->n_ch, &ev);
+}
+
+int if_hsi_read(int ch, u32 *data, unsigned int count)
+{
+ int ret = 0;
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ spin_lock_bh(&hsi_iface.lock);
+ ret = if_hsi_read_on(ch, data, count);
+ spin_unlock_bh(&hsi_iface.lock);
+ return ret;
+}
+
+int if_hsi_poll(int ch)
+{
+ struct if_hsi_channel *channel;
+ int ret = 0;
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ spin_lock_bh(&hsi_iface.lock);
+ ret = hsi_poll(channel->dev);
+ spin_unlock_bh(&hsi_iface.lock);
+ return ret;
+}
+
+static int if_hsi_write_on(int ch, u32 *address, unsigned int count)
+{
+ struct if_hsi_channel *channel;
+ int ret;
+
+ channel = &hsi_iface.channels[ch];
+
+ spin_lock(&channel->lock);
+ if (channel->state & HSI_CHANNEL_STATE_WRITING) {
+ pr_err("Write still pending on channel %d\n", ch);
+ spin_unlock(&channel->lock);
+ return -EBUSY;
+ }
+
+ channel->tx_data = address;
+ channel->tx_count = count;
+ channel->state |= HSI_CHANNEL_STATE_WRITING;
+ spin_unlock(&channel->lock);
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ ret = hsi_write(channel->dev, address, count/4);
+ return ret;
+}
+
+static void if_hsi_write_done(struct hsi_device *dev, unsigned int size)
+{
+ struct if_hsi_channel *channel;
+ struct hsi_event ev;
+
+ channel = &hsi_iface.channels[dev->n_ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, dev->n_ch);
+
+ spin_lock(&channel->lock);
+ channel->state &= ~HSI_CHANNEL_STATE_WRITING;
+ ev.event = HSI_EV_OUT;
+ ev.data = channel->tx_data;
+ ev.count = size;
+ spin_unlock(&channel->lock);
+ if_notify(dev->n_ch, &ev);
+}
+
+int if_hsi_write(int ch, u32 *data, unsigned int count)
+{
+ int ret = 0;
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ spin_lock_bh(&hsi_iface.lock);
+ ret = if_hsi_write_on(ch, data, count);
+ spin_unlock_bh(&hsi_iface.lock);
+ return ret;
+}
+
+void if_hsi_send_break(int ch)
+{
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ spin_lock_bh(&hsi_iface.lock);
+ hsi_ioctl(channel->dev, HSI_IOCTL_SEND_BREAK, NULL);
+ spin_unlock_bh(&hsi_iface.lock);
+}
+
+void if_hsi_flush_rx(int ch)
+{
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ spin_lock_bh(&hsi_iface.lock);
+ hsi_ioctl(channel->dev, HSI_IOCTL_FLUSH_RX, NULL);
+ spin_unlock_bh(&hsi_iface.lock);
+}
+
+void if_hsi_flush_ch(int ch)
+{
+ /* FIXME - Check the purpose of this function */
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ spin_lock(&channel->lock);
+ spin_unlock(&channel->lock);
+}
+
+void if_hsi_flush_tx(int ch)
+{
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ spin_lock_bh(&hsi_iface.lock);
+ hsi_ioctl(channel->dev, HSI_IOCTL_FLUSH_TX, NULL);
+ spin_unlock_bh(&hsi_iface.lock);
+}
+
+void if_hsi_get_wakeline(int ch, unsigned int *state)
+{
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ spin_lock_bh(&hsi_iface.lock);
+ hsi_ioctl(channel->dev, HSI_IOCTL_WAKE, state);
+ spin_unlock_bh(&hsi_iface.lock);
+}
+
+void if_hsi_set_wakeline(int ch, unsigned int state)
+{
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ spin_lock_bh(&hsi_iface.lock);
+ hsi_ioctl(channel->dev, state, NULL);
+ spin_unlock_bh(&hsi_iface.lock);
+}
+
+int if_hsi_set_rx(int ch, struct hsi_rx_config *cfg)
+{
+ int ret;
+ struct if_hsi_channel *channel;
+ struct hsr_ctx ctx;
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ RXCONV(&ctx, cfg);
+ spin_lock_bh(&hsi_iface.lock);
+ ret = hsi_ioctl(channel->dev, HSI_IOCTL_SET_RX, &ctx);
+ spin_unlock_bh(&hsi_iface.lock);
+ return ret;
+}
+
+void if_hsi_get_rx(int ch, struct hsi_rx_config *cfg)
+{
+ struct if_hsi_channel *channel;
+ struct hsr_ctx ctx;
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ spin_lock_bh(&hsi_iface.lock);
+ hsi_ioctl(channel->dev, HSI_IOCTL_GET_RX, &ctx);
+ RXCONV(cfg, &ctx);
+ spin_unlock_bh(&hsi_iface.lock);
+}
+
+int if_hsi_set_tx(int ch, struct hsi_tx_config *cfg)
+{
+ int ret;
+ struct if_hsi_channel *channel;
+ struct hst_ctx ctx;
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ TXCONV(&ctx, cfg);
+ spin_lock_bh(&hsi_iface.lock);
+ ret = hsi_ioctl(channel->dev, HSI_IOCTL_SET_TX, &ctx);
+ spin_unlock_bh(&hsi_iface.lock);
+ return ret;
+}
+
+void if_hsi_get_tx(int ch, struct hsi_tx_config *cfg)
+{
+ struct if_hsi_channel *channel;
+ struct hst_ctx ctx;
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ spin_lock_bh(&hsi_iface.lock);
+ hsi_ioctl(channel->dev, HSI_IOCTL_GET_TX, &ctx);
+ TXCONV(cfg, &ctx);
+ spin_unlock_bh(&hsi_iface.lock);
+}
+
+void if_hsi_cancel_read(int ch)
+{
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ spin_lock(&channel->lock);
+ if (channel->state & HSI_CHANNEL_STATE_READING)
+ hsi_read_cancel(channel->dev);
+ channel->state &= ~HSI_CHANNEL_STATE_READING;
+ spin_unlock(&channel->lock);
+}
+
+void if_hsi_cancel_write(int ch)
+{
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ spin_lock(&channel->lock);
+ if (channel->state & HSI_CHANNEL_STATE_WRITING)
+ hsi_write_cancel(channel->dev);
+ channel->state &= ~HSI_CHANNEL_STATE_WRITING;
+ spin_unlock(&channel->lock);
+}
+
+static int if_hsi_openchannel(struct if_hsi_channel *channel)
+{
+ int ret = 0;
+
+ spin_lock(&channel->lock);
+
+ if (channel->state == HSI_CHANNEL_STATE_UNAVAIL)
+ return -ENODEV;
+
+ if (channel->opened) {
+ ret = -EBUSY;
+ goto leave;
+ }
+
+ if (!channel->dev) {
+ pr_err("Channel %d is not ready??\n",
+ channel->channel_id);
+ ret = -ENODEV;
+ goto leave;
+ }
+
+ ret = hsi_open(channel->dev);
+ if (ret < 0) {
+ pr_err("Could not open channel %d\n",
+ channel->channel_id);
+ goto leave;
+ }
+
+ channel->opened = 1;
+
+leave:
+ spin_unlock(&channel->lock);
+ return ret;
+}
+
+
+static int if_hsi_closechannel(struct if_hsi_channel *channel)
+{
+ int ret = 0;
+
+ spin_lock(&channel->lock);
+
+ if (!channel->opened)
+ goto leave;
+
+ if (!channel->dev) {
+ pr_err("Channel %d is not ready??\n",
+ channel->channel_id);
+ ret = -ENODEV;
+ goto leave;
+ }
+
+ /* Stop any pending read/write */
+ if (channel->state & HSI_CHANNEL_STATE_READING) {
+ hsi_read_cancel(channel->dev);
+ channel->state &= ~HSI_CHANNEL_STATE_READING;
+ }
+ if (channel->state & HSI_CHANNEL_STATE_WRITING) {
+ hsi_write_cancel(channel->dev);
+ channel->state &= ~HSI_CHANNEL_STATE_WRITING;
+ }
+
+ hsi_close(channel->dev);
+
+ channel->opened = 0;
+leave:
+ spin_unlock(&channel->lock);
+ return ret;
+}
+
+
+int if_hsi_start(int ch)
+{
+ struct if_hsi_channel *channel;
+ int ret = 0;
+
+ channel = &hsi_iface.channels[ch];
+ dev_dbg(&channel->dev->device, "%s, ch = %d\n", __func__, ch);
+ spin_lock_bh(&hsi_iface.lock);
+ channel->state = 0;
+ ret = if_hsi_openchannel(channel);
+ if (ret < 0) {
+ pr_err("Could not open channel %d\n", ch);
+ spin_unlock_bh(&hsi_iface.lock);
+ goto error;
+ }
+ spin_unlock_bh(&hsi_iface.lock);
+ if_hsi_poll(ch);
+
+error:
+ return ret;
+}
+
+void if_hsi_stop(int ch)
+{
+ struct if_hsi_channel *channel;
+ channel = &hsi_iface.channels[ch];
+ if_hsi_set_wakeline(ch, 1);
+ spin_lock_bh(&hsi_iface.lock);
+ if_hsi_closechannel(channel);
+ spin_unlock_bh(&hsi_iface.lock);
+}
+
+static int __devinit if_hsi_probe(struct hsi_device *dev)
+{
+ struct if_hsi_channel *channel;
+ unsigned long *address;
+ int ret = -ENXIO, port;
+
+ for (port = 0; port < HSI_MAX_PORTS; port++) {
+ if (if_hsi_char_driver.ch_mask[port])
+ break;
+ }
+
+ if (port == HSI_MAX_PORTS)
+ return -ENXIO;
+
+ if (dev->n_ch >= HSI_MAX_CHAR_DEVS) {
+ pr_err("HSI char driver cannot handle channel %d\n", dev->n_ch);
+ return -ENXIO;
+ }
+
+ address = &if_hsi_char_driver.ch_mask[port];
+
+ spin_lock_bh(&hsi_iface.lock);
+ if (test_bit(dev->n_ch, address) && (dev->n_p == port)) {
+ hsi_set_read_cb(dev, if_hsi_read_done);
+ hsi_set_write_cb(dev, if_hsi_write_done);
+ hsi_set_port_event_cb(dev, if_hsi_port_event);
+ channel = &hsi_iface.channels[dev->n_ch];
+ channel->dev = dev;
+ channel->state = 0;
+ ret = 0;
+ hsi_iface.init_chan_map ^= (1 << dev->n_ch);
+ }
+ spin_unlock_bh(&hsi_iface.lock);
+
+ return ret;
+}
+
+static int __devexit if_hsi_remove(struct hsi_device *dev)
+{
+ struct if_hsi_channel *channel;
+ unsigned long *address;
+ int ret = -ENXIO, port;
+
+ for (port = 0; port < HSI_MAX_PORTS; port++) {
+ if (if_hsi_char_driver.ch_mask[port])
+ break;
+ }
+
+ if (port == HSI_MAX_PORTS)
+ return -ENXIO;
+
+ address = &if_hsi_char_driver.ch_mask[port];
+
+ spin_lock_bh(&hsi_iface.lock);
+ if (test_bit(dev->n_ch, address) && (dev->n_p == port)) {
+ hsi_set_read_cb(dev, NULL);
+ hsi_set_write_cb(dev, NULL);
+ channel = &hsi_iface.channels[dev->n_ch];
+ channel->dev = NULL;
+ channel->state = HSI_CHANNEL_STATE_UNAVAIL;
+ ret = 0;
+ }
+ spin_unlock_bh(&hsi_iface.lock);
+
+ return ret;
+}
+
+static void if_hsi_port_event(struct hsi_device *dev, unsigned int event,
+ void *arg)
+{
+ struct hsi_event ev;
+ int i;
+
+ ev.event = HSI_EV_EXCEP;
+ ev.data = (u32 *)0;
+ ev.count = 0;
+
+ switch (event) {
+ case HSI_EVENT_BREAK_DETECTED:
+ pr_debug("%s, HWBREAK detected\n", __func__);
+ ev.data = (u32 *)HSI_HWBREAK;
+ spin_lock_bh(&hsi_iface.lock);
+ for (i = 0; i < HSI_MAX_CHAR_DEVS; i++) {
+ if (hsi_iface.channels[i].opened)
+ if_notify(i, &ev);
+ }
+ spin_unlock_bh(&hsi_iface.lock);
+ break;
+ case HSI_EVENT_HSR_DATAAVAILABLE:
+ i = (int)arg;
+ pr_debug("%s, HSI_EVENT_HSR_DATAAVAILABLE channel = %d\n",
+ __func__, i);
+ ev.event = HSI_EV_AVAIL;
+ spin_lock_bh(&hsi_iface.lock);
+ if (hsi_iface.channels[i].opened)
+ if_notify(i, &ev);
+ spin_unlock_bh(&hsi_iface.lock);
+ break;
+ case HSI_EVENT_CAWAKE_UP:
+ pr_debug("%s, CAWAKE up\n", __func__);
+ break;
+ case HSI_EVENT_CAWAKE_DOWN:
+ pr_debug("%s, CAWAKE down\n", __func__);
+ break;
+ case HSI_EVENT_ERROR:
+ pr_debug("%s, HSI ERROR occured\n", __func__);
+ break;
+ default:
+ pr_warning("%s, Unknown event(%d)\n", __func__, event);
+ break;
+ }
+}
+
+int __init if_hsi_init(unsigned int port, unsigned int *channels_map)
+{
+ struct if_hsi_channel *channel;
+ int i, ret = 0;
+
+ port -= 1;
+ if (port >= HSI_MAX_PORTS)
+ return -EINVAL;
+
+ hsi_iface.bootstrap = 1;
+ spin_lock_init(&hsi_iface.lock);
+
+ for (i = 0; i < HSI_MAX_PORTS; i++)
+ if_hsi_char_driver.ch_mask[i] = 0;
+
+ for (i = 0; i < HSI_MAX_CHAR_DEVS; i++) {
+ channel = &hsi_iface.channels[i];
+ channel->dev = NULL;
+ channel->opened = 0;
+ channel->state = 0;
+ channel->channel_id = i;
+ spin_lock_init(&channel->lock);
+ channel->state = HSI_CHANNEL_STATE_UNAVAIL;
+ }
+
+ for (i = 0; (i < HSI_MAX_CHAR_DEVS) && channels_map[i]; i++) {
+ pr_debug("%s, port = %d, channels_map[i] = %d\n", __func__,
+ port, channels_map[i]);
+ if ((channels_map[i] - 1) < HSI_MAX_CHAR_DEVS)
+ if_hsi_char_driver.ch_mask[port] |=
+ (1 << ((channels_map[i] - 1)));
+ else {
+ pr_err("Channel %d cannot be handled by the HSI "
+ "driver.\n", channels_map[i]);
+ return -EINVAL;
+ }
+
+ }
+ hsi_iface.init_chan_map = if_hsi_char_driver.ch_mask[port];
+
+ ret = hsi_register_driver(&if_hsi_char_driver);
+ if (ret)
+ pr_err("Error while registering HSI driver %d", ret);
+
+ if (hsi_iface.init_chan_map) {
+ ret = -ENXIO;
+ pr_err("HSI: Some channels could not be registered (out of "
+ "range or already registered?)\n");
+ }
+ return ret;
+}
+
+int __exit if_hsi_exit(void)
+{
+ struct if_hsi_channel *channel;
+ unsigned long *address;
+ int i, port;
+
+ for (port = 0; port < HSI_MAX_PORTS; port++) {
+ if (if_hsi_char_driver.ch_mask[port])
+ break;
+ }
+
+ if (port == HSI_MAX_PORTS)
+ return -ENXIO;
+
+ address = &if_hsi_char_driver.ch_mask[port];
+
+ for (i = 0; i < HSI_MAX_CHAR_DEVS; i++) {
+ channel = &hsi_iface.channels[i];
+ if (channel->opened) {
+ if_hsi_set_wakeline(i, 1);
+ if_hsi_closechannel(channel);
+ }
+ }
+ hsi_unregister_driver(&if_hsi_char_driver);
+ return 0;
+}
diff --git a/drivers/hsi/hsi-if.h b/drivers/hsi/hsi-if.h
new file mode 100644
index 0000000..d64610d
--- /dev/null
+++ b/drivers/hsi/hsi-if.h
@@ -0,0 +1,65 @@
+/*
+ * hsi-if.h
+ *
+ * Part of the HSI character driver, private headers.
+ *
+ * Copyright (C) 2009 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Andras Domokos <andras.domokos@nokia.com>
+ * Author: Sebastien JAN <s-jan@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_IF_H
+#define _HSI_IF_H
+
+#define HSI_EV_MASK (0xffff << 0)
+#define HSI_EV_TYPE_MASK (0x0f << 16)
+#define HSI_EV_IN (0x01 << 16)
+#define HSI_EV_OUT (0x02 << 16)
+#define HSI_EV_EXCEP (0x03 << 16)
+#define HSI_EV_AVAIL (0x04 << 16)
+#define HSI_EV_TYPE(event) ((event) & HSI_EV_TYPE_MASK)
+
+#define HSI_HWBREAK 1
+#define HSI_ERROR 2
+
+struct hsi_event {
+ unsigned int event;
+ u32 *data;
+ unsigned int count;
+};
+
+int if_hsi_init(unsigned int port, unsigned int *channels_map);
+int if_hsi_exit(void);
+
+int if_hsi_start(int ch);
+void if_hsi_stop(int ch);
+
+void if_hsi_send_break(int ch);
+void if_hsi_flush_rx(int ch);
+void if_hsi_flush_tx(int ch);
+void if_hsi_bootstrap(int ch);
+void if_hsi_set_wakeline(int ch, unsigned int state);
+void if_hsi_get_wakeline(int ch, unsigned int *state);
+int if_hsi_set_rx(int ch, struct hsi_rx_config *cfg);
+void if_hsi_get_rx(int ch, struct hsi_rx_config *cfg);
+int if_hsi_set_tx(int ch, struct hsi_tx_config *cfg);
+void if_hsi_get_tx(int ch, struct hsi_tx_config *cfg);
+
+int if_hsi_read(int ch, u32 *data, unsigned int count);
+int if_hsi_poll(int ch);
+int if_hsi_write(int ch, u32 *data, unsigned int count);
+
+void if_hsi_cancel_read(int ch);
+void if_hsi_cancel_write(int ch);
+
+#endif /* _HSI_IF_H */
--
1.6.0.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* [RFC PATCH 8/9] HSI: HSI device support
2009-10-30 9:59 [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Sebastien Jan
` (6 preceding siblings ...)
2009-10-30 10:00 ` [RFC PATCH 7/9] HSI: character driver low level interface Sebastien Jan
@ 2009-10-30 10:00 ` Sebastien Jan
2009-10-30 10:00 ` [RFC PATCH 9/9] HSI: SSI device support and integration on 3430SDP platform Sebastien Jan
2009-10-30 12:57 ` [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Felipe Balbi
9 siblings, 0 replies; 15+ messages in thread
From: Sebastien Jan @ 2009-10-30 10:00 UTC (permalink / raw)
To: linux-omap; +Cc: Sebastien Jan, Carlos Chinea
Common registers definition for HSI like devices (common to MIPI HSI and SSI
for OMAP platforms)
HSI device definition
Signed-off-by: Sebastien Jan <s-jan@ti.com>
Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com>
---
arch/arm/mach-omap2/Makefile | 1 +
arch/arm/mach-omap2/hsi.c | 497 +++++++++++++++++++++++++++++++++
arch/arm/plat-omap/include/mach/hsi.h | 432 ++++++++++++++++++++++++++++
3 files changed, 930 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-omap2/hsi.c
create mode 100644 arch/arm/plat-omap/include/mach/hsi.h
diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile
index 6b7702f..3de1c20 100644
--- a/arch/arm/mach-omap2/Makefile
+++ b/arch/arm/mach-omap2/Makefile
@@ -87,3 +87,4 @@ obj-y += $(onenand-m) $(onenand-y)
smc91x-$(CONFIG_SMC91X) := gpmc-smc91x.o
obj-y += $(smc91x-m) $(smc91x-y)
+obj-$(CONFIG_OMAP_HSI_DEVICE) += hsi.o
diff --git a/arch/arm/mach-omap2/hsi.c b/arch/arm/mach-omap2/hsi.c
new file mode 100644
index 0000000..a920934
--- /dev/null
+++ b/arch/arm/mach-omap2/hsi.c
@@ -0,0 +1,497 @@
+/*
+ * arch/arm/mach-omap2/hsi.c
+ *
+ * HSI device definition
+ *
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Sebastien JAN <s-jan@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/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/jiffies.h>
+#include <linux/notifier.h>
+#include <mach/clock.h>
+#include <mach/hsi.h>
+#include <linux/hsi_driver_if.h>
+#include "clock.h"
+#include <asm/clkdev.h>
+#include <mach/mux.h>
+
+#define hsi_inl(p) inl((unsigned long) p)
+#define hsi_outl(v, p) outl(v, (unsigned long) p)
+
+/**
+ * struct hsi_internal_clk - Generic virtual hsi clock
+ * @clk: clock data
+ * @nb: notfier block for the DVFS notification chain
+ * @childs: Array of HSI FCK and ICK clocks
+ * @n_childs: Number of clocks in childs array
+ * @rate_change: Tracks if we are in the middle of a clock rate change
+ * @pdev: Reference to the HSI platform device associated to the clock
+ * @drv_nb: Reference to driver nb, use to propagate the DVFS notification
+ */
+struct hsi_internal_clk {
+ struct clk clk;
+ struct notifier_block nb;
+
+ struct clk **childs;
+ int n_childs;
+
+ unsigned int rate_change:1;
+
+ struct platform_device *pdev;
+ struct notifier_block *drv_nb;
+};
+
+static void hsi_set_mode(struct platform_device *pdev, u32 mode)
+{
+ struct hsi_platform_data *pdata = pdev->dev.platform_data;
+ void __iomem *base = OMAP2_IO_ADDRESS(pdev->resource[0].start);
+ int port;
+
+ for (port = 1; port <= pdata->num_ports; port++) {
+ /* FIXME - to update: need read/modify/write or something else:
+ * this register now also contains flow and wake ctrl
+ */
+ hsi_outl(mode, base + HSI_HST_MODE_REG(port));
+ hsi_outl(mode, base + HSI_HSR_MODE_REG(port));
+ }
+}
+
+static void hsi_save_mode(struct platform_device *pdev)
+{
+ struct hsi_platform_data *pdata = pdev->dev.platform_data;
+ void __iomem *base = OMAP2_IO_ADDRESS(pdev->resource[0].start);
+ struct port_ctx *p;
+ int port;
+
+ for (port = 1; port <= pdata->num_ports; port++) {
+ p = &pdata->ctx.pctx[port - 1];
+ p->hst.mode = hsi_inl(base + HSI_HST_MODE_REG(port));
+ p->hsr.mode = hsi_inl(base + HSI_HSR_MODE_REG(port));
+ }
+}
+
+static void hsi_restore_mode(struct platform_device *pdev)
+{
+ struct hsi_platform_data *pdata = pdev->dev.platform_data;
+ void __iomem *base = OMAP2_IO_ADDRESS(pdev->resource[0].start);
+ struct port_ctx *p;
+ int port;
+
+ for (port = 1; port <= pdata->num_ports; port++) {
+ p = &pdata->ctx.pctx[port - 1];
+ hsi_outl(p->hst.mode, base + HSI_HST_MODE_REG(port));
+ hsi_outl(p->hsr.mode, base + HSI_HSR_MODE_REG(port));
+ }
+}
+
+static int hsi_clk_event(struct notifier_block *nb, unsigned long event,
+ void *data)
+{
+/* FIXME
+ struct hsi_internal_clk *hsi_clk =
+ container_of(nb, struct hsi_internal_clk, nb);
+ switch (event) {
+ case CLK_PRE_RATE_CHANGE:
+ hsi_clk->drv_nb->notifier_call(hsi_clk->drv_nb, event, data);
+ hsi_clk->rate_change = 1;
+ if (hsi_clk->clk.usecount > 0) {
+ hsi_save_mode(hsi_clk->pdev);
+ hsi_set_mode(hsi_clk->pdev, HSI_MODE_SLEEP);
+ }
+ break;
+ case CLK_ABORT_RATE_CHANGE:
+ case CLK_POST_RATE_CHANGE:
+ if ((hsi_clk->clk.usecount > 0) && (hsi_clk->rate_change))
+ hsi_restore_mode(hsi_clk->pdev);
+
+ hsi_clk->rate_change = 0;
+ hsi_clk->drv_nb->notifier_call(hsi_clk->drv_nb, event, data);
+ break;
+ default:
+ break;
+ }
+*/
+ return NOTIFY_DONE;
+}
+
+static int hsi_clk_notifier_register(struct clk *clk, struct notifier_block *nb)
+{
+/* FIXME : clock functions not handled yet on OMAP4
+ struct hsi_internal_clk *hsi_clk;
+ if (!clk || !nb) {
+ return -EINVAL;
+ }
+ hsi_clk = container_of(clk, struct hsi_internal_clk, clk);
+ hsi_clk->drv_nb = nb;
+ hsi_clk->nb.priority = nb->priority;
+*/
+ /* NOTE: We only want notifications from the functional clock */
+/* FIXME
+ return clk_notifier_register(hsi_clk->childs[1], &hsi_clk->nb);
+*/
+ pr_debug("%s called\n", __func__);
+ return 0;
+}
+
+static int hsi_clk_notifier_unregister(struct clk *clk,
+ struct notifier_block *nb)
+{
+/* FIXME : clock functions not handled yet on OMAP4
+ struct hsi_internal_clk *hsi_clk;
+
+ if (!clk || !nb)
+ return -EINVAL;
+
+ hsi_clk = container_of(clk, struct hsi_internal_clk, clk);
+ hsi_clk->drv_nb = NULL;
+
+ return clk_notifier_unregister(hsi_clk->childs[1], &hsi_clk->nb);
+*/
+ pr_debug(KERN_DEBUG "%s called", __func__);
+ return 0;
+}
+
+static void hsi_save_ctx(struct platform_device *pdev)
+{
+ struct hsi_platform_data *pdata = pdev->dev.platform_data;
+ void __iomem *base = OMAP2_IO_ADDRESS(pdev->resource[0].start);
+ struct port_ctx *p;
+ int port;
+
+/* FIXME - implement PM support
+ pdata->ctx.loss_count =
+ omap_pm_get_dev_context_loss_count(&pdev->dev);
+*/
+ pdata->ctx.sysconfig = hsi_inl(base + HSI_SYS_SYSCONFIG_REG);
+ pdata->ctx.gdd_gcr = hsi_inl(base + HSI_GDD_GCR_REG);
+ for (port = 1; port <= pdata->num_ports; port++) {
+ p = &pdata->ctx.pctx[port - 1];
+ p->sys_mpu_enable[0] = hsi_inl(base +
+ HSI_SYS_MPU_ENABLE_REG(port, 0));
+ p->sys_mpu_enable[1] = hsi_inl(base +
+ HSI_SYS_MPU_U_ENABLE_REG(port, 0));
+ p->hst.frame_size = hsi_inl(base +
+ HSI_HST_FRAMESIZE_REG(port));
+ p->hst.divisor = hsi_inl(base + HSI_HST_DIVISOR_REG(port));
+ p->hst.channels = hsi_inl(base + HSI_HST_CHANNELS_REG(port));
+ p->hst.arb_mode = hsi_inl(base + HSI_HST_ARBMODE_REG(port));
+ p->hsr.frame_size = hsi_inl(base +
+ HSI_HSR_FRAMESIZE_REG(port));
+/*FIXME - check this register*/
+ p->hsr.timeout = hsi_inl(base + HSI_HSR_COUNTERS_REG(port));
+ p->hsr.channels = hsi_inl(base + HSI_HSR_CHANNELS_REG(port));
+ }
+}
+
+static void hsi_restore_ctx(struct platform_device *pdev)
+{
+ struct hsi_platform_data *pdata = pdev->dev.platform_data;
+ void __iomem *base = OMAP2_IO_ADDRESS(pdev->resource[0].start);
+ struct port_ctx *p;
+ int port;
+/* FIXME - implement PM support
+ int loss_count;
+
+ loss_count = omap_pm_get_dev_context_loss_count(&pdev->dev);
+
+ if (loss_count == pdata->ctx.loss_count)
+ return;
+*/
+ hsi_outl(pdata->ctx.sysconfig, base + HSI_SYS_SYSCONFIG_REG);
+ hsi_outl(pdata->ctx.gdd_gcr, base + HSI_GDD_GCR_REG);
+ for (port = 1; port <= pdata->num_ports; port++) {
+ p = &pdata->ctx.pctx[port - 1];
+ hsi_outl(p->sys_mpu_enable[0], base +
+ HSI_SYS_MPU_ENABLE_REG(port, 0));
+ hsi_outl(p->sys_mpu_enable[1], base +
+ HSI_SYS_MPU_U_ENABLE_REG(port, 0));
+ hsi_outl(p->hst.frame_size, base +
+ HSI_HST_FRAMESIZE_REG(port));
+ hsi_outl(p->hst.divisor, base + HSI_HST_DIVISOR_REG(port));
+ hsi_outl(p->hst.channels, base + HSI_HST_CHANNELS_REG(port));
+ hsi_outl(p->hst.arb_mode, base + HSI_HST_ARBMODE_REG(port));
+ hsi_outl(p->hsr.frame_size, base +
+ HSI_HSR_FRAMESIZE_REG(port));
+/* FIXME - check this register */
+ hsi_outl(p->hsr.timeout, base + HSI_HSR_COUNTERS_REG(port));
+ hsi_outl(p->hsr.channels, base + HSI_HSR_CHANNELS_REG(port));
+ }
+}
+
+static void hsi_pdev_release(struct device *dev)
+{
+}
+
+/*
+ * NOTE: We abuse a little bit the struct port_ctx to use it also for
+ * initialization.
+ */
+static struct port_ctx hsi_port_ctx[] = {
+ [0] = {
+ .hst.mode = HSI_MODE_FRAME,
+ .hst.flow = HSI_FLOW_SYNCHRONIZED,
+ .hst.frame_size = HSI_FRAMESIZE_DEFAULT,
+ .hst.divisor = 1,
+ .hst.channels = HSI_CHANNELS_DEFAULT,
+ .hst.arb_mode = HSI_ARBMODE_ROUNDROBIN,
+ .hsr.mode = HSI_MODE_FRAME,
+ .hsr.flow = HSI_FLOW_SYNCHRONIZED,
+ .hsr.frame_size = HSI_FRAMESIZE_DEFAULT,
+ .hsr.channels = HSI_CHANNELS_DEFAULT,
+ .hsr.divisor = 0,
+ .hsr.timeout = HSI_COUNTERS_FT_DEFAULT |
+ HSI_COUNTERS_TB_DEFAULT |
+ HSI_COUNTERS_FB_DEFAULT,
+ },
+};
+
+static struct hsi_platform_data hsi_pdata = {
+ .num_ports = ARRAY_SIZE(hsi_port_ctx),
+ .ctx.pctx = hsi_port_ctx,
+ .clk_notifier_register = hsi_clk_notifier_register,
+ .clk_notifier_unregister = hsi_clk_notifier_unregister,
+};
+
+static struct resource hsi_resources[] = {
+ [0] = {
+ .start = 0x4A058000,
+ .end = 0x4A05b950,
+ .name = "omap_hsi_iomem",
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = INT_44XX_HSI_1_IRQ0,
+ .end = INT_44XX_HSI_1_IRQ0,
+ .name = "hsi_p1_mpu_irq0",
+ .flags = IORESOURCE_IRQ,
+ },
+ [2] = {
+ .start = INT_44XX_HSI_2_IRQ1,
+ .end = INT_44XX_HSI_2_IRQ1,
+ .name = "hsi_p2_mpu_irq0",
+ .flags = IORESOURCE_IRQ,
+ },
+ [3] = {
+ .start = INT_44XX_HSI_1_DMAIRQ,
+ .end = INT_44XX_HSI_1_DMAIRQ,
+ .name = "hsi_gdd",
+ .flags = IORESOURCE_IRQ,
+ },
+ [4] = {
+ .start = 16, /* DMA channels available */
+ .end = 16,
+ .name = "hsi_gdd_chan_count",
+ .flags = IORESOURCE_DMA,
+ },
+};
+
+static struct platform_device hsi_pdev = {
+ .name = "omap_hsi",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(hsi_resources),
+ .resource = hsi_resources,
+ .dev = {
+ .release = hsi_pdev_release,
+ .platform_data = &hsi_pdata,
+ },
+};
+
+#ifdef CONFIG_OMAP_HSI_POWER_MANAGEMENT
+#define __HSI_CLK_FIX__
+#ifdef __HSI_CLK_FIX__
+/*
+ * FIXME: TO BE REMOVED.
+ * This hack allows us to ensure that clocks are stable before accehsing
+ * HSI controller registers. To be removed when PM functionalty is in place.
+ */
+static int check_hsi_active(void)
+{
+ u32 reg;
+ unsigned long dl = jiffies + msecs_to_jiffies(500);
+ void __iomem *cm_idlest1 = OMAP2_IO_ADDRESS(0x48004a20);
+
+ reg = inl(cm_idlest1);
+ while ((!(reg & 0x01)) && (time_before(jiffies, dl)))
+ reg = inl(cm_idlest1);
+
+ if (!(reg & 0x01)) { /* HST */
+ pr_err("HSI is still in STANDBY ! (BUG !?)\n");
+ return -1;
+ }
+
+ return 0;
+}
+#endif /* __HSI_CLK_FIX__ */
+#endif
+
+static int hsi_clk_init(struct hsi_internal_clk *hsi_clk)
+{
+/* FIXME - update clock names on OMAP4*/
+ const char *clk_names[] = {};
+
+ int i;
+ int j;
+
+ hsi_clk->n_childs = ARRAY_SIZE(clk_names);
+ hsi_clk->childs = kzalloc(hsi_clk->n_childs * sizeof(*hsi_clk->childs),
+ GFP_KERNEL);
+ if (!hsi_clk->childs)
+ return -ENOMEM;
+
+ for (i = 0; i < hsi_clk->n_childs; i++) {
+ hsi_clk->childs[i] = clk_get(&hsi_clk->pdev->dev, clk_names[i]);
+ if (IS_ERR(hsi_clk->childs[i])) {
+ pr_err("Unable to get HSI clock: %s", clk_names[i]);
+ for (j = i - 1; j >= 0; j--)
+ clk_put(hsi_clk->childs[j]);
+ return -ENODEV;
+ }
+ }
+
+ return 0;
+}
+
+
+
+static int hsi_clk_enable(struct clk *clk)
+{
+ struct hsi_internal_clk *hsi_clk =
+ container_of(clk, struct hsi_internal_clk, clk);
+/* FIXME - implement PM support
+ int err;
+ int i;
+
+ for (i = 0; i < hsi_clk->n_childs; i++) {
+ err = omap2_clk_enable(hsi_clk->childs[i]);
+ if (unlikely(err < 0))
+ goto rollback;
+ }
+*/
+#ifdef __HSI_CLK_FIX__
+ /*
+ * FIXME: To be removed
+ * Wait until the HSI controller has the clocks stable
+ */
+ check_hsi_active();
+#endif
+ hsi_restore_ctx(hsi_clk->pdev);
+ if (!hsi_clk->rate_change)
+ hsi_restore_mode(hsi_clk->pdev);
+
+ return 0;
+/* FIXME - implement PM support
+rollback:
+ pr_err("Error on HSI clk child %d\n", i);
+ for (i = i - 1; i >= 0; i--)
+ omap2_clk_disable(hsi_clk->childs[i]);
+ return err;
+*/
+}
+
+static void hsi_clk_disable(struct clk *clk)
+{
+ struct hsi_internal_clk *hsi_clk =
+ container_of(clk, struct hsi_internal_clk, clk);
+/* int i;*/
+
+ if (!hsi_clk->rate_change) {
+ hsi_save_mode(hsi_clk->pdev);
+ hsi_set_mode(hsi_clk->pdev, HSI_MODE_SLEEP);
+ }
+ /* Save ctx in all ports */
+ hsi_save_ctx(hsi_clk->pdev);
+
+/* FIXME - implement PM support
+ for (i = 0; i < hsi_clk->n_childs; i++)
+ omap2_clk_disable(hsi_clk->childs[i]);
+*/
+}
+
+static const int omap44xx_hsi_pins[] = {
+ AE18_4430_HSI1_CAWAKE,
+ AG19_4430_HSI1_CADATA,
+ AF19_4430_HSI1_CAFLAG,
+ AE19_4430_HSI1_ACREADY,
+ AF18_4430_HSI1_ACWAKE,
+ AG18_4430_HSI1_ACDATA,
+ AE17_4430_HSI1_ACFLAG,
+ AF17_4430_HSI1_CAREADY,
+};
+
+/* Mux settings for OMAP4430 */
+void omap_hsi_mux_setup(void)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(omap44xx_hsi_pins); i++)
+ omap_cfg_reg(omap44xx_hsi_pins[i]);
+ pr_debug("Pin muxing for HSI support done\n");
+}
+
+static const struct clkops clkops_hsi = {
+ .enable = hsi_clk_enable,
+ .disable = hsi_clk_disable,
+};
+
+static struct hsi_internal_clk hsi_clock = {
+ .clk = {
+ .name = "hsi_clk",
+ .id = -1,
+ .clkdm_name = "core_l4_clkdm",
+ .ops = &clkops_hsi,
+ },
+ .nb = {
+ .notifier_call = hsi_clk_event,
+ .priority = INT_MAX,
+ },
+ .pdev = &hsi_pdev,
+};
+
+/* FIXME - implement PM support
+static struct clk_lookup hsi_lk = {
+ .dev_id = NULL,
+ .con_id = "hsi_clk",
+ .clk = &hsi_clock.clk,
+};
+*/
+
+static int __init omap_hsi_init(void)
+{
+ int err;
+ struct clk *hsi_clk = &hsi_clock.clk;
+
+ hsi_clk_init(&hsi_clock);
+ clk_preinit(hsi_clk);
+/* FIXME - implement PM support
+ clkdev_add(&hsi_lk);
+*/
+ clk_register(hsi_clk);
+/* FIXME - implement PM support
+ omap2_init_clk_clkdm(hsi_clk);
+*/
+ err = platform_device_register(&hsi_pdev);
+ if (err < 0) {
+ pr_err("Unable to register HSI platform device: %d\n", err);
+ return err;
+ }
+
+ omap_hsi_mux_setup();
+
+ pr_info("HSI: device registered\n");
+ return 0;
+}
+subsys_initcall(omap_hsi_init);
diff --git a/arch/arm/plat-omap/include/mach/hsi.h b/arch/arm/plat-omap/include/mach/hsi.h
new file mode 100644
index 0000000..0b7f5ad
--- /dev/null
+++ b/arch/arm/plat-omap/include/mach/hsi.h
@@ -0,0 +1,432 @@
+/*
+ * /mach/hsi.h
+ *
+ * Hardware definitions for HSI and SSI.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@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.
+ */
+
+/* NOTE: This file defines the registers address offsets for both the
+ * SSI and HSI devices. Most of the registers share the same offset between
+ * these devices.
+ * When common or HSI only, the constants are name HSI*. Else the SSI specific
+ * constants are name HSI_SSI*
+ */
+
+#ifndef __HSI_H__
+#define __HSI_H__
+
+#define HSI_PORT_OFFSET 0x1000
+
+/*
+ * GDD base addr : 0x48059000 (SSI)
+ * GDD base addr : 0x4A059000 (HSI)
+ */
+#define HSI_GDD_OFFSET 0x1000
+#define HSI_GDD_BASE HSI_GDD_OFFSET /* 0x9000 */
+
+/*
+ * HST base addr:
+ * port 1: 0x4805a000 (SSI) - 0x4A05a000 (HSI)
+ * port 2: 0x4805b000 (SSI) - 0x4a05b000 (HSI)
+ */
+#define HSI_HST_OFFSET 0x2000
+#define HSI_HST_BASE(port) (HSI_HST_OFFSET + ((port - 1) *\
+ (HSI_PORT_OFFSET)))
+/*
+ * HSR base addr:
+ * port 1: 0x4805a800 (SSI) - 0x4A05a800 (HSI)
+ * port 2: 0x4805b800 (SSI) - 0x4A05b800 (HSI)
+ */
+#define HSI_HSR_OFFSET 0x2800
+#define HSI_HSR_BASE(port) (HSI_HSR_OFFSET + ((port - 1) *\
+ (HSI_PORT_OFFSET)))
+/*
+ * HSI SYS registers
+ */
+#define HSI_SYS_REVISION_REG 0x0000
+#define HSI_SSI_REV_MASK 0x000000ff
+#define HSI_SSI_REV_MAJOR 0xf0
+#define HSI_SSI_REV_MINOR 0x0f
+
+#define HSI_SYS_SYSCONFIG_REG 0x0010
+#define HSI_AUTOIDLE (1 << 0)
+#define HSI_SOFTRESET (1 << 1)
+#define HSI_FREE_EMU (1 << 2) /* Only for HSI */
+#define HSI_SIDLEMODE_FORCE 0
+#define HSI_SIDLEMODE_NO (1 << 3)
+#define HSI_SIDLEMODE_SMART (1 << 4)
+#define HSI_SIDLEMODE_MASK 0x00000018
+#define HSI_MIDLEMODE_FORCE 0
+#define HSI_MIDLEMODE_NO (1 << 12)
+#define HSI_MIDLEMODE_SMART (1 << 13)
+#define HSI_MIDLEMODE_MASK 0x00003000
+
+#define HSI_SYS_SYSSTATUS_REG 0x0014
+#define HSI_RESETDONE 1
+
+#define HSI_SYS_MPU_STATUS_BASE 0x0808
+#define HSI_SYS_MPU_STATUS_PORT_OFFSET 0x10
+#define HSI_SYS_MPU_STATUS_IRQ_OFFSET 8
+
+#define HSI_SYS_MPU_STATUS_REG(port, irq) \
+ (HSI_SYS_MPU_STATUS_BASE + \
+ (((port - 1) * HSI_SYS_MPU_STATUS_PORT_OFFSET) +\
+ (irq * HSI_SYS_MPU_STATUS_IRQ_OFFSET)))
+
+#define HSI_SYS_MPU_ENABLE_BASE 0x080c
+#define HSI_SYS_MPU_ENABLE_PORT_OFFSET 0x10
+#define HSI_SYS_MPU_ENABLE_IRQ_OFFSET 8
+
+#define HSI_SYS_MPU_ENABLE_REG(port, irq) \
+ (HSI_SYS_MPU_ENABLE_BASE + \
+ (((port - 1) * HSI_SYS_MPU_ENABLE_PORT_OFFSET) +\
+ (irq * HSI_SYS_MPU_ENABLE_IRQ_OFFSET)))
+#define HSI_HST_DATAACCEPT(channel) ((channel < 8) ? \
+ (1 << channel) : (1 << (channel - 8)))
+#define HSI_HSR_DATAAVAILABLE(channel) (channel < 8 ? \
+ (1 << (channel + 8)) : (1 << (channel - 8 + 8)))
+#define HSI_HSR_DATAOVERRUN(channel) (channel < 8 ? \
+ (1 << (channel + 16)) : (1 << (channel - 8 + 16)))
+#define HSI_ERROROCCURED (1 << 24)
+#define HSI_BREAKDETECTED (1 << 25)
+
+#define HSI_SYS_GDD_MPU_IRQ_STATUS_REG 0x0800
+#define HSI_SYS_GDD_MPU_IRQ_ENABLE_REG 0x0804
+#define HSI_GDD_LCH(channel) (1 << channel)
+
+#define HSI_SYS_WAKE_OFFSET 0x10
+#define HSI_SYS_WAKE_BASE 0x0c00
+#define HSI_SYS_WAKE_REG(port) (HSI_SYS_WAKE_BASE +\
+ ((port - 1) * HSI_SYS_WAKE_OFFSET))
+#define HSI_SYS_CLEAR_WAKE_BASE 0x0c04
+#define HSI_SYS_CLEAR_WAKE_REG(port) (HSI_SYS_CLEAR_WAKE_BASE +\
+ ((port - 1) * HSI_SYS_WAKE_OFFSET))
+#define HSI_SYS_SET_WAKE_BASE 0x0c08
+#define HSI_SYS_SET_WAKE_REG(port) (HSI_SYS_SET_WAKE_BASE +\
+ ((port - 1) * HSI_SYS_WAKE_OFFSET))
+# define HSI_SSI_WAKE_MASK 0xff /* for SSI */
+# define HSI_WAKE_MASK 0xffff /* for HSI */
+# define HSI_WAKE_4_WIRES (0 << 16)
+# define HSI_WAKE_READY_LVL_0 (0 << 17)
+# define HSI_WAKE(channel) (1 << channel | HSI_WAKE_4_WIRES |\
+ HSI_WAKE_READY_LVL_0)
+
+#define HSI_SYS_HWINFO_REG 0x0004 /* only for HSI */
+
+/* Additional registers definitions (for channels 8 .. 15) for HSI */
+#define HSI_SYS_MPU_U_STATUS_BASE 0x0408
+#define HSI_SYS_MPU_U_STATUS_REG(port, irq) \
+ (HSI_SYS_MPU_U_STATUS_BASE + \
+ (((port - 1) * HSI_SYS_MPU_STATUS_PORT_OFFSET) +\
+ (irq * HSI_SYS_MPU_STATUS_IRQ_OFFSET)))
+
+#define HSI_SYS_MPU_U_ENABLE_BASE 0x040c
+#define HSI_SYS_MPU_U_ENABLE_REG(port, irq) \
+ (HSI_SYS_MPU_U_ENABLE_BASE + \
+ (((port - 1) * HSI_SYS_MPU_ENABLE_PORT_OFFSET) +\
+ (irq * HSI_SYS_MPU_ENABLE_IRQ_OFFSET)))
+
+/*
+ * HSI HST registers
+ */
+#define HSI_HST_ID_REG(port) (HSI_HST_BASE(port) + 0x0000)
+
+#define HSI_HST_MODE_REG(port) (HSI_HST_BASE(port) + 0x0004)
+#define HSI_MODE_VAL_MASK 3
+#define HSI_MODE_SLEEP 0
+#define HSI_MODE_STREAM 1
+#define HSI_MODE_FRAME 2
+#define HSI_SSI_MODE_MULTIPOINTS 3 /* SSI only */
+#define HSI_FLOW_VAL_MASK (3 << 2) /* HSI only */
+#define HSI_FLOW_SYNCHRONIZED (0 << 2) /* HSI only */
+#define HSI_FLOW_PIPELINED (1 << 2) /* HSI only */
+#define HSI_FLOW_REAL_TIME (2 << 2) /* HSI only */
+#define HSI_MODE_WAKE_CTRL_AUTO (1 << 4) /* HSI only */
+#define HSI_MODE_WAKE_CTRL_SW (0 << 4) /* HSI only */
+
+#define HSI_HST_FRAMESIZE_REG(port) (HSI_HST_BASE(port) + 0x0008)
+#define HSI_FRAMESIZE_DEFAULT 31
+#define HSI_FRAMESIZE_MAX 0x1f
+
+#define HSI_HST_TXSTATE_REG(port) (HSI_HST_BASE(port) + 0x000c)
+#define TXSTATE_IDLE 0
+
+#define HSI_HST_BUFSTATE_REG(port) (HSI_HST_BASE(port) + 0x0010)
+#define HSI_HST_BUFSTATE_FIFO_REG(fifo) ((fifo < 8) ? \
+ HSI_HST_BUFSTATE_REG(1) : \
+ HSI_HST_BUFSTATE_REG(2))
+#define HSI_BUFSTATE_CHANNEL(channel) (channel < 8 ? \
+ (1 << channel) : (1 << (channel - 8)))
+
+#define HSI_HST_DIVISOR_REG(port) (HSI_HST_BASE(port) + 0x0018)
+#define HSI_DIVISOR_DEFAULT 1
+#define HSI_SSI_MAX_TX_DIVISOR 0x7f /* for SSI */
+#define HSI_MAX_TX_DIVISOR 0xff /* for HSI */
+
+#define HSI_HST_BREAK_REG(port) (HSI_HST_BASE(port) + 0x0020)
+#define HSI_HST_CHANNELS_REG(port) (HSI_HST_BASE(port) + 0x0024)
+#define HSI_CHANNELS_DEFAULT 4
+#define HSI_SSI_CHANNELS_MAX 8 /* for SSI */
+#define HSI_CHANNELS_MAX 16 /* for HSI */
+
+#define HSI_HST_ARBMODE_REG(port) (HSI_HST_BASE(port) + 0x0028)
+#define HSI_ARBMODE_ROUNDROBIN 0
+#define HSI_ARBMODE_PRIORITY 1
+
+#define HSI_HST_BUFFER_BASE(port) (HSI_HST_BASE(port) + 0x0080)
+#define HSI_HST_BUFFER_CH_REG(port, channel) (HSI_HST_BUFFER_BASE(port) +\
+ (channel * 4))
+#define HSI_HST_BUFFER_FIFO_REG(fifo) ((fifo < 8) ? \
+ (HSI_HST_BUFFER_CH_REG(1, fifo)) : \
+ (HSI_HST_BUFFER_CH_REG(2, fifo - 8)))
+
+#define HSI_HST_SWAPBUF_BASE(port) (HSI_HST_BASE(port) + 0x00c0)
+#define HSI_HST_SWAPBUF_CH_REG(port, channel) (HSI_HST_SWAPBUF_BASE(port) +\
+ (channel * 4))
+
+/* Additional registers for HSI */
+#define HSI_HST_FIFO_COUNT 16
+#define HSI_HST_MAPPING_FIFO_REG(fifo) (HSI_HST_BASE(1) + 0x0100 +\
+ (fifo * 4))
+#define HSI_MAPPING_ENABLE 1
+#define HSI_MAPPING_CH_NUMBER_OFFSET 1
+#define HSI_MAPPING_PORT_NUMBER_OFFSET 7
+#define HSI_HST_MAPPING_THRESH_OFFSET 10
+#define HSI_HST_MAPPING_THRESH_VALUE (0x0 << HSI_HST_MAPPING_THRESH_OFFSET)
+
+/*
+ * HSI HSR registers
+ */
+#define HSI_HSR_ID_REG(port) (HSI_HSR_BASE(port) + 0x0000)
+
+#define HSI_HSR_MODE_REG(port) (HSI_HSR_BASE(port) + 0x0004)
+
+#define HSI_HSR_FRAMESIZE_REG(port) (HSI_HSR_BASE(port) + 0x0008)
+
+#define HSI_HSR_RXSTATE_REG(port) (HSI_HSR_BASE(port) + 0x000c)
+
+#define HSI_HSR_BUFSTATE_REG(port) (HSI_HSR_BASE(port) + 0x0010)
+#define HSI_HSR_BUFSTATE_FIFO_REG(fifo) ((fifo < 8) ? \
+ HSI_HSR_BUFSTATE_REG(1) : \
+ HSI_HSR_BUFSTATE_REG(2))
+
+#define HSI_HSR_BREAK_REG(port) (HSI_HSR_BASE(port) + 0x001c)
+
+#define HSI_HSR_ERROR_REG(port) (HSI_HSR_BASE(port) + 0x0020)
+#define HSI_HSR_ERROR_SIG 1
+#define HSI_HSR_ERROR_FTE (1 << 1) /* HSI only */
+#define HSI_HSR_ERROR_TBE (1 << 4) /* HSI only */
+#define HSI_HSR_ERROR_RME (1 << 7) /* HSI only */
+#define HSI_HSR_ERROR_TME (1 << 11) /* HSI only */
+
+#define HSI_HSR_ERRORACK_REG(port) (HSI_HSR_BASE(port) + 0x0024)
+
+#define HSI_HSR_CHANNELS_REG(port) (HSI_HSR_BASE(port) + 0x0028)
+
+#define HSI_HSR_OVERRUN_REG(port) (HSI_HSR_BASE(port) + 0x002c)
+
+#define HSI_HSR_OVERRUNACK_REG(port) (HSI_HSR_BASE(port) + 0x0030)
+
+#define HSI_HSR_COUNTERS_REG(port) (HSI_HSR_BASE(port) + 0x0034)
+#define HSI_TIMEOUT_DEFAULT 0 /* SSI only */
+#define HSI_SSI_RX_TIMEOUT_MAX 0x1ff /* SSI only */
+#define HSI_COUNTERS_FT_MASK 0x000fffff /* HSI only */
+#define HSI_COUNTERS_TB_MASK 0x00f00000 /* HSI only */
+#define HSI_COUNTERS_FB_MASK 0xff000000 /* HSI only */
+#define HSI_COUNTERS_FT_OFFSET 0 /* HSI only */
+#define HSI_COUNTERS_TB_OFFSET 20 /* HSI only */
+#define HSI_COUNTERS_FB_OFFSET 24 /* HSI only */
+/* Default FT value: 2 x max_bits_per_frame + 20% margin */
+#define HSI_COUNTERS_FT_DEFAULT (90 << HSI_COUNTERS_FT_OFFSET)
+#define HSI_COUNTERS_TB_DEFAULT (6 << HSI_COUNTERS_TB_OFFSET)
+#define HSI_COUNTERS_FB_DEFAULT (8 << HSI_COUNTERS_FB_OFFSET)
+
+#define HSI_HSR_BUFFER_BASE(port) (HSI_HSR_BASE(port) + 0x0080)
+#define HSI_HSR_BUFFER_CH_REG(port, channel) (HSI_HSR_BUFFER_BASE(port) +\
+ (channel * 4))
+#define HSI_HSR_BUFFER_FIFO_REG(fifo) ((fifo < 8) ? \
+ (HSI_HSR_BUFFER_CH_REG(1, fifo)) : \
+ (HSI_HSR_BUFFER_CH_REG(2, fifo - 8)))
+
+#define HSI_HSR_SWAPBUF_BASE(port) (HSI_HSR_BASE(port) + 0x00c0)
+#define HSI_HSR_SWAPBUF_CH_REG(port, channel) (HSI_HSR_SWAPBUF_BASE +\
+ (channel * 4))
+
+/* Additional registers for HSI */
+#define HSI_HSR_FIFO_COUNT 16
+#define HSI_HSR_MAPPING_FIFO_REG(fifo) (HSI_HSR_BASE(1) + 0x0100 +\
+ (fifo * 4))
+#define HSI_HSR_MAPPING_WORDS_MASK (0xf << 10)
+
+#define HSI_HSR_DLL_REG(port) (HSI_HSR_BASE(port) + 0x0144)
+#define HSI_HSR_DLL_COCHRE 1
+#define HSI_HSR_DLL_COCHGR (1 << 4)
+#define HSI_HSR_DLL_INCO_MASK 0x0003ff00
+#define HSI_HSR_DLL_INCO_OFFSET 8
+
+#define HSI_HSR_DIVISOR_REG(port) (HSI_HSR_BASE(port) + 0x014C)
+#define HSI_HSR_DIVISOR_MASK 0xff
+#define HSI_MAX_RX_DIVISOR 0xff
+
+
+/*
+ * HSI GDD registers
+ */
+#define HSI_SSI_GDD_HW_ID_REG (HSI_GDD_BASE + 0x0000)
+
+#define HSI_SSI_GDD_PPORT_ID_REG (HSI_GDD_BASE + 0x0010)
+
+#define HSI_SSI_GDD_MPORT_ID_REG (HSI_GDD_BASE + 0x0014)
+
+#define HSI_SSI_GDD_PPORT_SR_REG (HSI_GDD_BASE + 0x0020)
+#define HSI_PPORT_ACTIVE_LCH_NUMBER_MASK 0xff
+
+#define HSI_GDD_MPORT_SR_REG (HSI_GDD_BASE + 0x0024)
+#define HSI_SSI_MPORT_ACTIVE_LCH_NUMBER_MASK 0xff
+
+#define HSI_SSI_GDD_TEST_REG (HSI_GDD_BASE + 0x0040)
+#define HSI_SSI_TEST 1
+
+#define HSI_GDD_GCR_REG (HSI_GDD_BASE + 0x0100)
+#define HSI_CLK_AUTOGATING_ON (1 << 3)
+#define HSI_SWITCH_OFF (1 << 0)
+
+#define HSI_GDD_GRST_REG (HSI_GDD_BASE + 0x0200)
+#define HSI_SWRESET 1
+
+#define HSI_GDD_CSDP_BASE (HSI_GDD_BASE + 0x0800)
+#define HSI_GDD_CSDP_OFFSET 0x40
+#define HSI_GDD_CSDP_REG(channel) (HSI_GDD_CSDP_BASE +\
+ (channel * HSI_GDD_CSDP_OFFSET))
+#define HSI_DST_BURST_EN_MASK 0xc000
+#define HSI_DST_SINGLE_ACCESS0 0
+#define HSI_DST_SINGLE_ACCESS (1 << 14)
+#define HSI_DST_BURST_4X32_BIT (2 << 14)
+#define HSI_DST_BURST_8x32_BIT (3 << 14)
+
+#define HSI_DST_MASK 0x1e00
+#define HSI_DST_MEMORY_PORT (8 << 9)
+#define HSI_DST_PERIPHERAL_PORT (9 << 9)
+
+#define HSI_SRC_BURST_EN_MASK 0x0180
+#define HSI_SRC_SINGLE_ACCESS0 0
+#define HSI_SRC_SINGLE_ACCESS (1 << 7)
+#define HSI_SRC_BURST_4x32_BIT (2 << 7)
+#define HSI_SRC_BURST_8x32_BIT (3 << 7)
+
+#define HSI_SRC_MASK 0x003c
+#define HSI_SRC_MEMORY_PORT (8 << 2)
+#define HSI_SRC_PERIPHERAL_PORT (9 << 2)
+
+#define HSI_DATA_TYPE_MASK 3
+#define HSI_DATA_TYPE_S32 2
+
+#define HSI_GDD_CCR_BASE (HSI_GDD_BASE + 0x0802)
+#define HSI_GDD_CCR_OFFSET 0x40
+#define HSI_GDD_CCR_REG(channel) (HSI_GDD_CCR_BASE +\
+ (channel * HSI_GDD_CCR_OFFSET))
+#define HSI_DST_AMODE_MASK (3 << 14)
+#define HSI_DST_AMODE_CONST 0
+#define HSI_DST_AMODE_POSTINC (1 << 14)
+
+#define HSI_SRC_AMODE_MASK (3 << 12)
+#define HSI_SRC_AMODE_CONST 0
+#define HSI_SRC_AMODE_POSTINC (1 << 12)
+
+#define HSI_CCR_ENABLE (1 << 7)
+
+#define HSI_CCR_SYNC_MASK 0x001f /* only for SSI */
+
+#define HSI_GDD_CICR_BASE (HSI_GDD_BASE + 0x0804)
+#define HSI_GDD_CICR_OFFSET 0x40
+#define HSI_GDD_CICR_REG(channel) (HSI_GDD_CICR_BASE +\
+ (channel * HSI_GDD_CICR_OFFSET))
+#define HSI_BLOCK_IE (1 << 5)
+#define HSI_HALF_IE (1 << 2)
+#define HSI_TOUT_IE (1 << 0)
+
+#define HSI_GDD_CSR_BASE (HSI_GDD_BASE + 0x0806)
+#define HSI_GDD_CSR_OFFSET 0x40
+#define HSI_GDD_CSR_REG(channel) (HSI_GDD_CSR_BASE +\
+ (channel * HSI_GDD_CSR_OFFSET))
+#define HSI_CSR_SYNC (1 << 6)
+#define HSI_CSR_BLOCK (1 << 5)
+#define HSI_CSR_HALF (1 << 2)
+#define HSI_CSR_TOUT (1 << 0)
+
+#define HSI_GDD_CSSA_BASE (HSI_GDD_BASE + 0x0808)
+#define HSI_GDD_CSSA_OFFSET 0x40
+#define HSI_GDD_CSSA_REG(channel) (HSI_GDD_CSSA_BASE +\
+ (channel * HSI_GDD_CSSA_OFFSET))
+
+#define HSI_GDD_CDSA_BASE (HSI_GDD_BASE + 0x080c)
+#define HSI_GDD_CDSA_OFFSET 0x40
+#define HSI_GDD_CDSA_REG(channel) (HSI_GDD_CDSA_BASE +\
+ (channel * HSI_GDD_CDSA_OFFSET))
+
+#define HSI_GDD_CEN_BASE (HSI_GDD_BASE + 0x0810)
+#define HSI_GDD_CEN_OFFSET 0x40
+#define HSI_GDD_CEN_REG(channel) (HSI_GDD_CEN_BASE +\
+ (channel * HSI_GDD_CEN_OFFSET))
+
+#define HSI_GDD_CSAC_BASE (HSI_GDD_BASE + 0x0818)
+#define HSI_GDD_CSAC_OFFSET 0x40
+#define HSI_GDD_CSAC_REG(channel) (HSI_GDD_CSAC_BASE +\
+ (channel * HSI_GDD_CSAC_OFFSET))
+
+#define HSI_GDD_CDAC_BASE (HSI_GDD_BASE + 0x081a)
+#define HSI_GDD_CDAC_OFFSET 0x40
+#define HSI_GDD_CDAC_REG(channel) (HSI_GDD_CDAC_BASE +\
+ (channel * HSI_GDD_CDAC_OFFSET))
+
+#define HSI_SSI_GDD_CLNK_CTRL_BASE (HSI_GDD_BASE + 0x0828)
+#define HSI_SSI_GDD_CLNK_CTRL_OFFSET 0x40
+#define HSI_SSI_GDD_CLNK_CTRL_REG(channel) (HSI_SSI_GDD_CLNK_CTRL_BASE +\
+ (channel * HSI_SSI_GDD_CLNK_CTRL_OFFSET))
+
+#define HSI_SSI_ENABLE_LNK (1 << 15)
+#define HSI_SSI_STOP_LNK (1 << 14)
+#define HSI_SSI_NEXT_CH_ID_MASK 0xf
+
+/*
+ * HSI Helpers
+ */
+#define HSI_SYS_MPU_ENABLE_CH_REG(port, irq, channel) \
+ ((channel < HSI_SSI_CHANNELS_MAX) ? \
+ HSI_SYS_MPU_ENABLE_REG(port, irq) : \
+ HSI_SYS_MPU_U_ENABLE_REG(port, irq))
+
+/**
+ * struct omap_ssi_config - SSI board configuration
+ * @num_ports: Number of ports in use
+ * @cawake_line: Array of cawake gpio lines
+ */
+struct omap_ssi_board_config {
+ unsigned int num_ports;
+ int cawake_gpio[2];
+};
+extern int omap_ssi_config(struct omap_ssi_board_config *ssi_config);
+
+/**
+ * struct omap_hsi_config - HSI board configuration
+ * @num_ports: Number of ports in use
+ */
+struct omap_hsi_board_config {
+ unsigned int num_ports;
+};
+extern int omap_hsi_config(struct omap_hsi_board_config *hsi_config);
+
+#endif /* __HSI_H__ */
--
1.6.0.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* [RFC PATCH 9/9] HSI: SSI device support and integration on 3430SDP platform
2009-10-30 9:59 [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Sebastien Jan
` (7 preceding siblings ...)
2009-10-30 10:00 ` [RFC PATCH 8/9] HSI: HSI device support Sebastien Jan
@ 2009-10-30 10:00 ` Sebastien Jan
2009-10-30 12:57 ` [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Felipe Balbi
9 siblings, 0 replies; 15+ messages in thread
From: Sebastien Jan @ 2009-10-30 10:00 UTC (permalink / raw)
To: linux-omap; +Cc: Sebastien Jan, Carlos Chinea
SSI device definition
Integration on 3430 SDP (pin muxing, platform init)
Signed-off-by: Sebastien Jan <s-jan@ti.com>
Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com>
---
arch/arm/mach-omap2/Makefile | 1 +
arch/arm/mach-omap2/board-3430sdp.c | 12 +
arch/arm/mach-omap2/mux.c | 17 +
arch/arm/mach-omap2/ssi.c | 530 +++++++++++++++++++++++++++++++++
arch/arm/plat-omap/include/mach/mux.h | 10 +
5 files changed, 570 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-omap2/ssi.c
diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile
index 3de1c20..34d3e7f 100644
--- a/arch/arm/mach-omap2/Makefile
+++ b/arch/arm/mach-omap2/Makefile
@@ -87,4 +87,5 @@ obj-y += $(onenand-m) $(onenand-y)
smc91x-$(CONFIG_SMC91X) := gpmc-smc91x.o
obj-y += $(smc91x-m) $(smc91x-y)
+obj-$(CONFIG_OMAP_SSI_DEVICE) += ssi.o
obj-$(CONFIG_OMAP_HSI_DEVICE) += hsi.o
diff --git a/arch/arm/mach-omap2/board-3430sdp.c b/arch/arm/mach-omap2/board-3430sdp.c
index 2dfab17..f982fd1 100644
--- a/arch/arm/mach-omap2/board-3430sdp.c
+++ b/arch/arm/mach-omap2/board-3430sdp.c
@@ -40,6 +40,7 @@
#include <mach/control.h>
#include <mach/keypad.h>
#include <mach/gpmc-smc91x.h>
+#include <mach/hsi.h>
#include "sdram-qimonda-hyb18m512160af-6.h"
#include "mmc-twl4030.h"
@@ -496,6 +497,13 @@ static struct ehci_hcd_omap_platform_data ehci_pdata __initconst = {
.reset_gpio_port[2] = -EINVAL
};
+#ifdef CONFIG_OMAP_SSI_DEVICE
+static struct omap_ssi_board_config ssi_board_config = {
+ .num_ports = 1,
+ .cawake_gpio = { 151 },
+};
+#endif
+
static void __init omap_3430sdp_init(void)
{
omap3430_i2c_init();
@@ -513,6 +521,10 @@ static void __init omap_3430sdp_init(void)
board_smc91x_init();
enable_board_wakeup_source();
usb_ehci_init(&ehci_pdata);
+
+#ifdef CONFIG_OMAP_SSI_DEVICE
+ omap_ssi_config(&ssi_board_config);
+#endif
}
static void __init omap_3430sdp_map_io(void)
diff --git a/arch/arm/mach-omap2/mux.c b/arch/arm/mach-omap2/mux.c
index b5fac32..8464034 100644
--- a/arch/arm/mach-omap2/mux.c
+++ b/arch/arm/mach-omap2/mux.c
@@ -451,6 +451,23 @@ MUX_CFG_34XX("AD2_3430_USB3FS_PHY_MM3_TXDAT", 0x188,
MUX_CFG_34XX("AC1_3430_USB3FS_PHY_MM3_TXEN_N", 0x18a,
OMAP34XX_MUX_MODE6 | OMAP34XX_PIN_OUTPUT)
+/* SSI support */
+MUX_CFG_34XX("AA8_3430_SSI1_DAT_TX", 0x17c,
+ OMAP34XX_MUX_MODE1) /* AC Data */
+MUX_CFG_34XX("AA9_3430_SSI1_FLAG_TX", 0x17e,
+ OMAP34XX_MUX_MODE1) /* AC Flag */
+MUX_CFG_34XX("W8_3430_SSI1_RDY_TX", 0x180,
+ OMAP34XX_MUX_MODE1 | OMAP3_INPUT_EN) /* CA Ready */
+MUX_CFG_34XX("Y8_3430_GPIO_151", 0x182,
+ OMAP34XX_MUX_MODE4 | OMAP3_INPUT_EN) /* CA Wake */
+MUX_CFG_34XX("AE1_3430_SSI1_DAT_RX", 0x184,
+ OMAP34XX_MUX_MODE1 | OMAP3_INPUT_EN) /* CA Data */
+MUX_CFG_34XX("AD1_3430_SSI1_FLAG_RX", 0x186,
+ OMAP34XX_MUX_MODE1 | OMAP3_INPUT_EN) /* CA Flag */
+MUX_CFG_34XX("AD2_3430_SSI1_RDY_RX", 0x188,
+ OMAP34XX_MUX_MODE1) /* AC Ready */
+MUX_CFG_34XX("AC1_3430_SSI1_WAKE", 0x18a,
+ OMAP34XX_MUX_MODE1) /* AC Wake */
/* 34XX GPIO - bidirectional, unless the name has an "_OUT" suffix.
* (Always specify PIN_INPUT, except for names suffixed by "_OUT".)
diff --git a/arch/arm/mach-omap2/ssi.c b/arch/arm/mach-omap2/ssi.c
new file mode 100644
index 0000000..676254a
--- /dev/null
+++ b/arch/arm/mach-omap2/ssi.c
@@ -0,0 +1,530 @@
+/*
+ * arch/arm/mach-omap2/ssi.c
+ *
+ * SSI device definition
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@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/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/jiffies.h>
+#include <linux/notifier.h>
+#include <mach/clock.h>
+#include <mach/hsi.h>
+#include <linux/hsi_driver_if.h>
+#include "clock.h"
+#include <asm/clkdev.h>
+#include <mach/mux.h>
+
+#define hsi_inl(p) inl((unsigned long) p)
+#define hsi_outl(v, p) outl(v, (unsigned long) p)
+
+/**
+ * struct ssi_internal_clk - Generic virtual ssi clock
+ * @clk: clock data
+ * @nb: notfier block for the DVFS notification chain
+ * @childs: Array of SSI FCK and ICK clocks
+ * @n_childs: Number of clocks in childs array
+ * @rate_change: Tracks if we are in the middle of a clock rate change
+ * @pdev: Reference to the SSI platform device associated to the clock
+ * @drv_nb: Reference to driver nb, use to propagate the DVFS notification
+ */
+struct ssi_internal_clk {
+ struct clk clk;
+ struct notifier_block nb;
+
+ struct clk **childs;
+ int n_childs;
+
+ unsigned int rate_change:1;
+
+ struct platform_device *pdev;
+ struct notifier_block *drv_nb;
+};
+
+static void ssi_set_mode(struct platform_device *pdev, u32 mode)
+{
+ struct hsi_platform_data *pdata = pdev->dev.platform_data;
+ void __iomem *base = OMAP2_IO_ADDRESS(pdev->resource[0].start);
+ int port;
+
+ for (port = 1; port <= pdata->num_ports; port++) {
+ outl(mode, (unsigned int) base + HSI_HST_MODE_REG(port));
+ outl(mode, (unsigned int) base + HSI_HSR_MODE_REG(port));
+ }
+}
+
+static void ssi_save_mode(struct platform_device *pdev)
+{
+ struct hsi_platform_data *pdata = pdev->dev.platform_data;
+ void __iomem *base = OMAP2_IO_ADDRESS(pdev->resource[0].start);
+ struct port_ctx *p;
+ int port;
+
+ for (port = 1; port <= pdata->num_ports; port++) {
+ p = &pdata->ctx.pctx[port - 1];
+ p->hst.mode = hsi_inl(base + HSI_HST_MODE_REG(port));
+ p->hsr.mode = hsi_inl(base + HSI_HSR_MODE_REG(port));
+ }
+}
+
+static void ssi_restore_mode(struct platform_device *pdev)
+{
+ struct hsi_platform_data *pdata = pdev->dev.platform_data;
+ void __iomem *base = OMAP2_IO_ADDRESS(pdev->resource[0].start);
+ struct port_ctx *p;
+ int port;
+
+ for (port = 1; port <= pdata->num_ports; port++) {
+ p = &pdata->ctx.pctx[port - 1];
+ hsi_outl(p->hst.mode, base + HSI_HST_MODE_REG(port));
+ hsi_outl(p->hsr.mode, base + HSI_HSR_MODE_REG(port));
+ }
+}
+
+static int ssi_clk_event(struct notifier_block *nb, unsigned long event,
+ void *data)
+{
+/* TODO: implement clock change support
+ struct ssi_internal_clk *ssi_clk =
+ container_of(nb, struct ssi_internal_clk, nb);
+ switch (event) {
+ case CLK_PRE_RATE_CHANGE:
+ ssi_clk->drv_nb->notifier_call(ssi_clk->drv_nb, event, data);
+ ssi_clk->rate_change = 1;
+ if (ssi_clk->clk.usecount > 0) {
+ ssi_save_mode(ssi_clk->pdev);
+ ssi_set_mode(ssi_clk->pdev, HSI_MODE_SLEEP);
+ }
+ break;
+ case CLK_ABORT_RATE_CHANGE:
+ case CLK_POST_RATE_CHANGE:
+ if ((ssi_clk->clk.usecount > 0) && (ssi_clk->rate_change))
+ ssi_restore_mode(ssi_clk->pdev);
+
+ ssi_clk->rate_change = 0;
+ ssi_clk->drv_nb->notifier_call(ssi_clk->drv_nb, event, data);
+ break;
+ default:
+ break;
+ }
+*/
+
+ return NOTIFY_DONE;
+}
+
+static int ssi_clk_notifier_register(struct clk *clk, struct notifier_block *nb)
+{
+ struct ssi_internal_clk *ssi_clk;
+
+ if (!clk || !nb)
+ return -EINVAL;
+
+ ssi_clk = container_of(clk, struct ssi_internal_clk, clk);
+ ssi_clk->drv_nb = nb;
+ ssi_clk->nb.priority = nb->priority;
+ /* NOTE: We only want notifications from the functional clock */
+/* TODO: implement clock change support
+ return clk_notifier_register(ssi_clk->childs[1], &ssi_clk->nb);
+*/
+ pr_debug("%s called\n", __func__);
+ return 0;
+}
+
+static int ssi_clk_notifier_unregister(struct clk *clk,
+ struct notifier_block *nb)
+{
+ struct ssi_internal_clk *ssi_clk;
+
+ if (!clk || !nb)
+ return -EINVAL;
+
+ ssi_clk = container_of(clk, struct ssi_internal_clk, clk);
+ ssi_clk->drv_nb = NULL;
+/* TODO: implement clock change support
+ return clk_notifier_unregister(ssi_clk->childs[1], &ssi_clk->nb);
+*/
+ pr_debug(KERN_DEBUG "%s called", __func__);
+ return 0;
+}
+
+static void ssi_save_ctx(struct platform_device *pdev)
+{
+ struct hsi_platform_data *pdata = pdev->dev.platform_data;
+ void __iomem *base = OMAP2_IO_ADDRESS(pdev->resource[0].start);
+ struct port_ctx *p;
+ int port;
+
+/* TODO: update support for omap_pm_get_dev_context_loss_count
+ pdata->ctx.loss_count =
+ omap_pm_get_dev_context_loss_count(&pdev->dev);
+*/
+ pdata->ctx.sysconfig = hsi_inl(base + HSI_SYS_SYSCONFIG_REG);
+ pdata->ctx.gdd_gcr = hsi_inl(base + HSI_GDD_GCR_REG);
+ for (port = 1; port <= pdata->num_ports; port++) {
+ p = &pdata->ctx.pctx[port - 1];
+ p->sys_mpu_enable[0] = hsi_inl(base +
+ HSI_SYS_MPU_ENABLE_REG(port, 0));
+ p->sys_mpu_enable[1] = hsi_inl(base +
+ HSI_SYS_MPU_ENABLE_REG(port, 1));
+ p->hst.frame_size = hsi_inl(base +
+ HSI_HST_FRAMESIZE_REG(port));
+ p->hst.divisor = hsi_inl(base + HSI_HST_DIVISOR_REG(port));
+ p->hst.channels = hsi_inl(base + HSI_HST_CHANNELS_REG(port));
+ p->hst.arb_mode = hsi_inl(base + HSI_HST_ARBMODE_REG(port));
+ p->hsr.frame_size = hsi_inl(base +
+ HSI_HSR_FRAMESIZE_REG(port));
+ p->hsr.timeout = hsi_inl(base + HSI_HSR_COUNTERS_REG(port));
+ p->hsr.channels = hsi_inl(base + HSI_HSR_CHANNELS_REG(port));
+ }
+}
+
+static void ssi_restore_ctx(struct platform_device *pdev)
+{
+ struct hsi_platform_data *pdata = pdev->dev.platform_data;
+ void __iomem *base = OMAP2_IO_ADDRESS(pdev->resource[0].start);
+ struct port_ctx *p;
+ int port;
+/* TODO: update support for omap_pm_get_dev_context_loss_count
+ int loss_count;
+
+ loss_count = omap_pm_get_dev_context_loss_count(&pdev->dev);
+ if (loss_count == pdata->ctx.loss_count)
+ return;
+*/
+ hsi_outl(pdata->ctx.sysconfig, base + HSI_SYS_SYSCONFIG_REG);
+ hsi_outl(pdata->ctx.gdd_gcr, base + HSI_GDD_GCR_REG);
+ for (port = 1; port <= pdata->num_ports; port++) {
+ p = &pdata->ctx.pctx[port - 1];
+ hsi_outl(p->sys_mpu_enable[0], base +
+ HSI_SYS_MPU_ENABLE_REG(port, 0));
+ hsi_outl(p->sys_mpu_enable[1], base +
+ HSI_SYS_MPU_ENABLE_REG(port, 1));
+ hsi_outl(p->hst.frame_size, base +
+ HSI_HST_FRAMESIZE_REG(port));
+ hsi_outl(p->hst.divisor, base + HSI_HST_DIVISOR_REG(port));
+ hsi_outl(p->hst.channels, base + HSI_HST_CHANNELS_REG(port));
+ hsi_outl(p->hst.arb_mode, base + HSI_HST_ARBMODE_REG(port));
+ hsi_outl(p->hsr.frame_size, base +
+ HSI_HSR_FRAMESIZE_REG(port));
+ hsi_outl(p->hsr.timeout, base + HSI_HSR_COUNTERS_REG(port));
+ hsi_outl(p->hsr.channels, base + HSI_HSR_CHANNELS_REG(port));
+ }
+}
+
+static void ssi_pdev_release(struct device *dev)
+{
+}
+
+/*
+ * NOTE: We abuse a little bit the struct port_ctx to use it also for
+ * initialization.
+ */
+static struct port_ctx ssi_port_ctx[] = {
+ [0] = {
+ .hst.mode = HSI_MODE_FRAME,
+ .hst.flow = 0, /* not supported on SSI */
+ .hst.frame_size = HSI_FRAMESIZE_DEFAULT,
+ .hst.divisor = 1,
+ .hst.channels = HSI_CHANNELS_DEFAULT,
+ .hst.arb_mode = HSI_ARBMODE_ROUNDROBIN,
+ .hsr.mode = HSI_MODE_FRAME,
+ .hsr.flow = 0, /* not supported on SSI */
+ .hsr.frame_size = HSI_FRAMESIZE_DEFAULT,
+ .hsr.channels = HSI_CHANNELS_DEFAULT,
+ .hsr.divisor = 0, /* not supported on SSI */
+ .hsr.timeout = HSI_TIMEOUT_DEFAULT,
+ },
+};
+
+static struct hsi_platform_data ssi_pdata = {
+ .num_ports = ARRAY_SIZE(ssi_port_ctx),
+ .ctx.pctx = ssi_port_ctx,
+ .clk_notifier_register = ssi_clk_notifier_register,
+ .clk_notifier_unregister = ssi_clk_notifier_unregister,
+};
+
+static struct resource ssi_resources[] = {
+ [0] = {
+ .start = 0x48058000,
+ .end = 0x4805bbff,
+ .name = "omap_ssi_iomem",
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = 67,
+ .end = 67,
+ .name = "ssi_p1_mpu_irq0",
+ .flags = IORESOURCE_IRQ,
+ },
+ [2] = {
+ .start = 69,
+ .end = 69,
+ .name = "ssi_p1_mpu_irq1",
+ .flags = IORESOURCE_IRQ,
+ },
+ [3] = {
+ .start = 68,
+ .end = 68,
+ .name = "ssi_p2_mpu_irq0",
+ .flags = IORESOURCE_IRQ,
+ },
+ [4] = {
+ .start = 70,
+ .end = 70,
+ .name = "ssi_p2_mpu_irq1",
+ .flags = IORESOURCE_IRQ,
+ },
+ [5] = {
+ .start = 71,
+ .end = 71,
+ .name = "ssi_gdd",
+ .flags = IORESOURCE_IRQ,
+ },
+ [6] = {
+ .start = 151,
+ .end = 0,
+ .name = "ssi_p1_cawake_gpio",
+ .flags = IORESOURCE_IRQ | IORESOURCE_UNSET,
+ },
+ [7] = {
+ .start = 0,
+ .end = 0,
+ .name = "ssi_p2_cawake_gpio",
+ .flags = IORESOURCE_IRQ | IORESOURCE_UNSET,
+ },
+ [8] = {
+ .start = 8, /* DMA channels available */
+ .end = 8,
+ .name = "hsi_gdd_chan_count",
+ .flags = IORESOURCE_DMA,
+ },
+};
+
+static struct platform_device ssi_pdev = {
+ .name = "omap_ssi",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(ssi_resources),
+ .resource = ssi_resources,
+ .dev = {
+ .release = ssi_pdev_release,
+ .platform_data = &ssi_pdata,
+ },
+};
+
+#if 0 /* TODO: check if following code can be removed when testing with PM */
+#define __HSI_CLK_FIX__
+#ifdef __HSI_CLK_FIX__
+/*
+ * FIXME: TO BE REMOVED.
+ * This hack allows us to ensure that clocks are stable before accessing
+ * SSI controller registers. To be removed when PM functionalty is in place.
+ */
+static int check_ssi_active(void)
+{
+ u32 reg;
+ unsigned long dl = jiffies + msecs_to_jiffies(500);
+ void __iomem *cm_idlest1 = OMAP2_IO_ADDRESS(0x48004a20);
+
+ reg = inl(cm_idlest1);
+ while ((!(reg & 0x01)) && (time_before(jiffies, dl)))
+ reg = inl(cm_idlest1);
+
+ if (!(reg & 0x01)) { /* ST_SSI */
+ pr_err("SSI is still in STANDBY ! (BUG !?)\n");
+ return -1;
+ }
+
+ return 0;
+}
+#endif /* __HSI_CLK_FIX__ */
+#endif
+
+static int ssi_clk_init(struct ssi_internal_clk *ssi_clk)
+{
+ const char *clk_names[] =
+ { "ssi_ssr_fck", "ssi_sst_fck", "ssi_l4_ick", "ssi_ick" };
+ int i;
+ int j;
+
+ ssi_clk->n_childs = ARRAY_SIZE(clk_names);
+ ssi_clk->childs = kzalloc(ssi_clk->n_childs * sizeof(*ssi_clk->childs),
+ GFP_KERNEL);
+ if (!ssi_clk->childs)
+ return -ENOMEM;
+
+ for (i = 0; i < ssi_clk->n_childs; i++) {
+ ssi_clk->childs[i] = clk_get(&ssi_clk->pdev->dev, clk_names[i]);
+ if (IS_ERR(ssi_clk->childs[i])) {
+ pr_err("Unable to get SSI clock: %s\n", clk_names[i]);
+ for (j = i - 1; j >= 0; j--)
+ clk_put(ssi_clk->childs[j]);
+ return -ENODEV;
+ }
+ }
+
+ return 0;
+}
+
+static int ssi_clk_enable(struct clk *clk)
+{
+ struct ssi_internal_clk *ssi_clk =
+ container_of(clk, struct ssi_internal_clk, clk);
+ int err;
+ int i;
+
+ for (i = 0; i < ssi_clk->n_childs; i++) {
+ err = omap2_clk_enable(ssi_clk->childs[i]);
+ if (unlikely(err < 0))
+ goto rollback;
+ }
+#ifdef __HSI_CLK_FIX__
+ /*
+ * FIXME: To be removed
+ * Wait until the SSI controller has the clocks stable
+ */
+ check_ssi_active();
+#endif
+ ssi_restore_ctx(ssi_clk->pdev);
+ if (!ssi_clk->rate_change)
+ ssi_restore_mode(ssi_clk->pdev);
+
+ return 0;
+rollback:
+ pr_err("Error on SSI clk child %d\n", i);
+ for (i = i - 1; i >= 0; i--)
+ omap2_clk_disable(ssi_clk->childs[i]);
+
+ return err;
+}
+
+static void ssi_clk_disable(struct clk *clk)
+{
+ struct ssi_internal_clk *ssi_clk =
+ container_of(clk, struct ssi_internal_clk, clk);
+ int i;
+
+ if (!ssi_clk->rate_change) {
+ ssi_save_mode(ssi_clk->pdev);
+ ssi_set_mode(ssi_clk->pdev, HSI_MODE_SLEEP);
+ }
+ /* Save ctx in all ports */
+ ssi_save_ctx(ssi_clk->pdev);
+
+ for (i = 0; i < ssi_clk->n_childs; i++)
+ omap2_clk_disable(ssi_clk->childs[i]);
+}
+
+int omap_ssi_config(struct omap_ssi_board_config *ssi_config)
+{
+ int port;
+ int cawake_gpio;
+
+ ssi_pdata.num_ports = ssi_config->num_ports;
+ for (port = 0; port < ssi_config->num_ports; port++) {
+ cawake_gpio = ssi_config->cawake_gpio[port];
+ if (cawake_gpio < 0)
+ continue; /* Nothing to do */
+
+ if (gpio_request(cawake_gpio, "CAWAKE") < 0) {
+ dev_err(&ssi_pdev.dev, "FAILED to request CAWAKE"
+ " GPIO %d\n", cawake_gpio);
+ return -EBUSY;
+ }
+
+ gpio_direction_input(cawake_gpio);
+ ssi_resources[6 + port].start = gpio_to_irq(cawake_gpio);
+ ssi_resources[6 + port].flags &= ~IORESOURCE_UNSET;
+ ssi_resources[6 + port].flags |= IORESOURCE_IRQ_HIGHEDGE |
+ IORESOURCE_IRQ_LOWEDGE;
+ }
+
+ pr_debug("GPIO for SSI is now configured\n");
+ return 0;
+}
+
+static const int omap34xx_ssi_pins[] = {
+ AA8_3430_SSI1_DAT_TX,
+ AA9_3430_SSI1_FLAG_TX,
+ W8_3430_SSI1_RDY_TX,
+ Y8_3430_GPIO_151,
+ AE1_3430_SSI1_DAT_RX,
+ AD1_3430_SSI1_FLAG_RX,
+ AD2_3430_SSI1_RDY_RX,
+ AC1_3430_SSI1_WAKE,
+};
+
+/* Mux settings for OMAP3430 */
+void omap_ssi_mux_setup(void)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(omap34xx_ssi_pins); i++)
+ omap_cfg_reg(omap34xx_ssi_pins[i]);
+ pr_debug("Pin muxing for SSI support (disables UART1 and McBsp4)\n");
+}
+
+static const struct clkops clkops_ssi = {
+ .enable = ssi_clk_enable,
+ .disable = ssi_clk_disable,
+};
+
+static struct ssi_internal_clk ssi_clock = {
+ .clk = {
+ .name = "hsi_clk",
+ .id = -1,
+ .clkdm_name = "core_l4_clkdm",
+ .ops = &clkops_ssi,
+ },
+ .nb = {
+ .notifier_call = ssi_clk_event,
+ .priority = INT_MAX,
+ },
+ .pdev = &ssi_pdev,
+};
+
+static struct clk_lookup hsi_lk = {
+ .dev_id = NULL,
+ .con_id = "hsi_clk",
+ .clk = &ssi_clock.clk,
+};
+
+static int __init omap_ssi_init(void)
+{
+ int err;
+ struct clk *hsi_clk = &ssi_clock.clk;
+
+ ssi_clk_init(&ssi_clock);
+ clk_preinit(hsi_clk);
+ clkdev_add(&hsi_lk);
+ clk_register(hsi_clk);
+ omap2_init_clk_clkdm(hsi_clk);
+
+ err = platform_device_register(&ssi_pdev);
+ if (err < 0) {
+ pr_err("Unable to register SSI platform device: %d\n", err);
+ return err;
+ }
+
+ omap_ssi_mux_setup();
+
+ pr_info("SSI: device registered\n");
+
+ return 0;
+}
+subsys_initcall(omap_ssi_init);
diff --git a/arch/arm/plat-omap/include/mach/mux.h b/arch/arm/plat-omap/include/mach/mux.h
index f3c1d8a..4d2d9e4 100644
--- a/arch/arm/plat-omap/include/mach/mux.h
+++ b/arch/arm/plat-omap/include/mach/mux.h
@@ -782,6 +782,16 @@ enum omap34xx_index {
AD2_3430_USB3FS_PHY_MM3_TXDAT,
AC1_3430_USB3FS_PHY_MM3_TXEN_N,
+ /* SSI support */
+ AA8_3430_SSI1_DAT_TX,
+ AA9_3430_SSI1_FLAG_TX,
+ W8_3430_SSI1_RDY_TX,
+ Y8_3430_GPIO_151,
+ AE1_3430_SSI1_DAT_RX,
+ AD1_3430_SSI1_FLAG_RX,
+ AD2_3430_SSI1_RDY_RX,
+ AC1_3430_SSI1_WAKE,
+
/* 34xx GPIO
* - normally these are bidirectional, no internal pullup/pulldown
* - "_UP" suffix (GPIO3_UP) if internal pullup is configured
--
1.6.0.4
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms
2009-10-30 9:59 [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Sebastien Jan
` (8 preceding siblings ...)
2009-10-30 10:00 ` [RFC PATCH 9/9] HSI: SSI device support and integration on 3430SDP platform Sebastien Jan
@ 2009-10-30 12:57 ` Felipe Balbi
2009-10-30 14:32 ` Sebastien Jan
9 siblings, 1 reply; 15+ messages in thread
From: Felipe Balbi @ 2009-10-30 12:57 UTC (permalink / raw)
To: ext Sebastien Jan; +Cc: linux-omap@vger.kernel.org
On Fri, Oct 30, 2009 at 10:59:59AM +0100, ext Sebastien Jan wrote:
> This is an RFC for a series of patch to add the support of MIPI HSI and SSI
> devices to OMAP platforms.
> The patch includes the HSI device driver and the device files for MIPI HSI and
> SSI.
>
> The driver is made of 2 distinct modules:
> - omap_hsi: is the Low-Level Driver (or hardware driver). It provides a
> kernel functional interface, to be used by other kernel modules and
> abstracts the HW.
> This part of the patch is based on Nokia SSI driver, already submitted as
> RFC by Carlos Chinea [1].
> - hsi-char: is a char device driver proposing a user-space interface for
> using the HSI and relies on omap_hsi.
> This part of the patch is based on a development from Andras Domokos.
> The intent is to propose a single HSI driver that can support MIPI HSI 1.1 as
> well as previous implementations (SSI) for OMAP platforms (OMAP3 / OMAP4).
>
> The driver core functionalities are there and ready for review. The following
> updates will come in the following weeks / months:
> - Clocks and Power Management support for OMAP4 platform (HSI device file)
> - HSI-char interface documentation
>
> Validation: The driver is validated on 3430 SDP (with a HW loopback on the SSI
> device), and is being validated on 4430 Virtio platform (MIPI HSI device).
>
> [1] http://lwn.net/Articles/301918/
pass all your drivers on checkpatch.pl --strict and to sparse as well.
--
balbi
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms
2009-10-30 12:57 ` [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms Felipe Balbi
@ 2009-10-30 14:32 ` Sebastien Jan
2009-10-30 17:58 ` Felipe Balbi
0 siblings, 1 reply; 15+ messages in thread
From: Sebastien Jan @ 2009-10-30 14:32 UTC (permalink / raw)
To: felipe.balbi@nokia.com; +Cc: linux-omap@vger.kernel.org
On Friday 30 October 2009 13:57:50 Felipe Balbi wrote:
> On Fri, Oct 30, 2009 at 10:59:59AM +0100, ext Sebastien Jan wrote:
> > This is an RFC for a series of patch to add the support of MIPI HSI and
> > SSI devices to OMAP platforms.
> > The patch includes the HSI device driver and the device files for MIPI
> > HSI and SSI.
> >
> > The driver is made of 2 distinct modules:
> > - omap_hsi: is the Low-Level Driver (or hardware driver). It provides a
> > kernel functional interface, to be used by other kernel modules and
> > abstracts the HW.
> > This part of the patch is based on Nokia SSI driver, already submitted
> > as RFC by Carlos Chinea [1].
> > - hsi-char: is a char device driver proposing a user-space interface for
> > using the HSI and relies on omap_hsi.
> > This part of the patch is based on a development from Andras Domokos.
> > The intent is to propose a single HSI driver that can support MIPI HSI
> > 1.1 as well as previous implementations (SSI) for OMAP platforms (OMAP3 /
> > OMAP4).
> >
> > The driver core functionalities are there and ready for review. The
> > following updates will come in the following weeks / months:
> > - Clocks and Power Management support for OMAP4 platform (HSI device
> > file) - HSI-char interface documentation
> >
> > Validation: The driver is validated on 3430 SDP (with a HW loopback on
> > the SSI device), and is being validated on 4430 Virtio platform (MIPI HSI
> > device).
> >
> > [1] http://lwn.net/Articles/301918/
>
> pass all your drivers on checkpatch.pl --strict and to sparse as well.
Thanks to point this out: I forgot to mention that I did run checkpatch.pl --
strict and sparse on these patches.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 0/9] MIPI HSI device support for OMAP platforms
2009-10-30 14:32 ` Sebastien Jan
@ 2009-10-30 17:58 ` Felipe Balbi
0 siblings, 0 replies; 15+ messages in thread
From: Felipe Balbi @ 2009-10-30 17:58 UTC (permalink / raw)
To: ext Sebastien Jan
Cc: Balbi Felipe (Nokia-D/Helsinki), linux-omap@vger.kernel.org
On Fri, Oct 30, 2009 at 03:32:06PM +0100, ext Sebastien Jan wrote:
> On Friday 30 October 2009 13:57:50 Felipe Balbi wrote:
> > On Fri, Oct 30, 2009 at 10:59:59AM +0100, ext Sebastien Jan wrote:
> > > This is an RFC for a series of patch to add the support of MIPI HSI and
> > > SSI devices to OMAP platforms.
> > > The patch includes the HSI device driver and the device files for MIPI
> > > HSI and SSI.
> > >
> > > The driver is made of 2 distinct modules:
> > > - omap_hsi: is the Low-Level Driver (or hardware driver). It provides a
> > > kernel functional interface, to be used by other kernel modules and
> > > abstracts the HW.
> > > This part of the patch is based on Nokia SSI driver, already submitted
> > > as RFC by Carlos Chinea [1].
> > > - hsi-char: is a char device driver proposing a user-space interface for
> > > using the HSI and relies on omap_hsi.
> > > This part of the patch is based on a development from Andras Domokos.
> > > The intent is to propose a single HSI driver that can support MIPI HSI
> > > 1.1 as well as previous implementations (SSI) for OMAP platforms (OMAP3 /
> > > OMAP4).
> > >
> > > The driver core functionalities are there and ready for review. The
> > > following updates will come in the following weeks / months:
> > > - Clocks and Power Management support for OMAP4 platform (HSI device
> > > file) - HSI-char interface documentation
> > >
> > > Validation: The driver is validated on 3430 SDP (with a HW loopback on
> > > the SSI device), and is being validated on 4430 Virtio platform (MIPI HSI
> > > device).
> > >
> > > [1] http://lwn.net/Articles/301918/
> >
> > pass all your drivers on checkpatch.pl --strict and to sparse as well.
>
> Thanks to point this out: I forgot to mention that I did run checkpatch.pl --
> strict and sparse on these patches.
Weird they passed. Just looking over the patches I saw some weird
comments here and there.
--
balbi
^ permalink raw reply [flat|nested] 15+ messages in thread