summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorPhilippe Langlais <philippe.langlais@stericsson.com>2012-06-04 19:45:37 +0800
committerPhilippe Langlais <philippe.langlais@stericsson.com>2012-06-04 19:45:37 +0800
commitd3aeada8de410edd0ea3cb1b73381ebae469c584 (patch)
tree8b22bec8a39e599c9250e50ab4078425ca749b35 /drivers
parent591e5e125f9de0513a732371378eeffe09ef4da5 (diff)
parent7338b0ab88d72b11989f40394069164d9326afbf (diff)
Merge topic branch 'modem' into integration-linux-ux500
Signed-off-by: Philippe Langlais <philippe.langlais@stericsson.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/Kconfig2
-rw-r--r--drivers/Makefile1
-rw-r--r--drivers/char/Makefile5
-rw-r--r--drivers/char/m6718_modem_char.c722
-rw-r--r--drivers/char/shrm_char.c897
-rw-r--r--drivers/misc/Kconfig31
-rw-r--r--drivers/misc/Makefile5
-rw-r--r--drivers/misc/db8500-modem-trace.c273
-rw-r--r--drivers/misc/dbx500-mloader.c288
-rw-r--r--drivers/misc/mbox.c867
-rw-r--r--drivers/misc/mbox_channels-db5500.c1273
-rw-r--r--drivers/misc/modem_audio/Kconfig6
-rw-r--r--drivers/misc/modem_audio/Makefile2
-rw-r--r--drivers/misc/modem_audio/mad.c506
-rw-r--r--drivers/misc/sim_detect.c306
-rw-r--r--drivers/modem/Kconfig44
-rw-r--r--drivers/modem/Makefile6
-rw-r--r--drivers/modem/m6718_spi/Kconfig83
-rw-r--r--drivers/modem/m6718_spi/Makefile15
-rw-r--r--drivers/modem/m6718_spi/debug.c482
-rw-r--r--drivers/modem/m6718_spi/modem_debug.h36
-rw-r--r--drivers/modem/m6718_spi/modem_driver.c292
-rw-r--r--drivers/modem/m6718_spi/modem_netlink.h20
-rw-r--r--drivers/modem/m6718_spi/modem_private.h106
-rw-r--r--drivers/modem/m6718_spi/modem_protocol.h24
-rw-r--r--drivers/modem/m6718_spi/modem_queue.h24
-rw-r--r--drivers/modem/m6718_spi/modem_state.c1300
-rw-r--r--drivers/modem/m6718_spi/modem_state.h36
-rw-r--r--drivers/modem/m6718_spi/modem_statemachine.h71
-rw-r--r--drivers/modem/m6718_spi/modem_util.h57
-rw-r--r--drivers/modem/m6718_spi/netlink.c182
-rw-r--r--drivers/modem/m6718_spi/protocol.c429
-rw-r--r--drivers/modem/m6718_spi/queue.c166
-rw-r--r--drivers/modem/m6718_spi/statemachine.c1089
-rw-r--r--drivers/modem/m6718_spi/util.c281
-rw-r--r--drivers/modem/mcdd.c190
-rw-r--r--drivers/modem/modem_access.c417
-rw-r--r--drivers/modem/modem_m6718.c95
-rw-r--r--drivers/modem/modem_u8500.c95
-rw-r--r--drivers/modem/shrm/Kconfig43
-rw-r--r--drivers/modem/shrm/Makefile11
-rw-r--r--drivers/modem/shrm/modem_shrm_driver.c670
-rw-r--r--drivers/modem/shrm/shrm_driver.c1439
-rw-r--r--drivers/modem/shrm/shrm_fifo.c837
-rw-r--r--drivers/modem/shrm/shrm_protocol.c1546
-rw-r--r--drivers/net/Makefile5
-rw-r--r--drivers/net/m6718_modem_net.c333
-rw-r--r--drivers/net/u8500_shrm.c312
-rw-r--r--drivers/staging/Kconfig10
-rw-r--r--drivers/staging/Makefile1
-rw-r--r--drivers/staging/ab5500_sim/Makefile1
-rw-r--r--drivers/staging/ab5500_sim/ab5500-sim.c306
-rw-r--r--drivers/staging/ab5500_sim/sysfs-sim83
53 files changed, 16321 insertions, 0 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 68e36aed05b..c66015a8379 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -142,5 +142,7 @@ source "drivers/virt/Kconfig"
source "drivers/devfreq/Kconfig"
+source "drivers/modem/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 4ca756c31be..ccd35da64cd 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -99,6 +99,7 @@ obj-$(CONFIG_CPU_FREQ) += cpufreq/
obj-$(CONFIG_CPU_IDLE) += cpuidle/
obj-y += mmc/
obj-$(CONFIG_MEMSTICK) += memstick/
+obj-$(CONFIG_MODEM) += modem/
obj-y += leds/
obj-$(CONFIG_INFINIBAND) += infiniband/
obj-$(CONFIG_SGI_SN) += sn/
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 0dc5d7ce486..a73e2c197cd 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -49,6 +49,11 @@ obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o
obj-$(CONFIG_GPIO_TB0219) += tb0219.o
obj-$(CONFIG_TELCLOCK) += tlclk.o
+ifdef CONFIG_PHONET
+obj-$(CONFIG_U8500_SHRM) += shrm_char.o
+obj-$(CONFIG_MODEM_M6718_SPI) += m6718_modem_char.o
+endif
+
obj-$(CONFIG_MWAVE) += mwave/
obj-$(CONFIG_AGP) += agp/
obj-$(CONFIG_PCMCIA) += pcmcia/
diff --git a/drivers/char/m6718_modem_char.c b/drivers/char/m6718_modem_char.c
new file mode 100644
index 00000000000..34bfe98aa57
--- /dev/null
+++ b/drivers/char/m6718_modem_char.c
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ * based on shrm_char.c
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * M6718 modem char device interface.
+ */
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/atomic.h>
+#include <linux/modem/m6718_spi/modem_char.h>
+#include <linux/modem/m6718_spi/modem_driver.h>
+
+#define NAME "IPC_ISA"
+
+#define MAX_PDU_SIZE (2000) /* largest frame we need to send */
+#define MAX_RX_FIFO_ENTRIES (10)
+#define SIZE_OF_RX_FIFO (MAX_PDU_SIZE * MAX_RX_FIFO_ENTRIES)
+#define SIZE_OF_TX_COPY_BUFFER (MAX_PDU_SIZE) /* only need 1 at a time */
+
+static u8 message_fifo[MODEM_M6718_SPI_MAX_CHANNELS][SIZE_OF_RX_FIFO];
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK
+static u8 wr_mlb_msg[SIZE_OF_TX_COPY_BUFFER];
+#endif
+static u8 wr_audio_msg[SIZE_OF_TX_COPY_BUFFER];
+
+struct map_device {
+ u8 l2_header;
+ u8 idx;
+ char *name;
+};
+
+static struct map_device map_dev[] = {
+ {MODEM_M6718_SPI_CHN_ISI, 0, "isi"},
+ {MODEM_M6718_SPI_CHN_AUDIO, 1, "modemaudio"},
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK
+ {MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0, 2, "master_loopback0"},
+ {MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0, 3, "slave_loopback0"},
+ {MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1, 4, "master_loopback1"},
+ {MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1, 5, "slave_loopback1"},
+#endif
+};
+
+/*
+ * major - variable exported as module_param to specify major node number
+ */
+static int major;
+module_param(major, int, 0);
+MODULE_PARM_DESC(major, "Major device number");
+
+/* global fops mutex */
+static DEFINE_MUTEX(isa_lock);
+
+/**
+ * modem_get_cdev_index() - return the index mapped to l2 header
+ * @l2_header: L2 header
+ *
+ * struct map_device maps the index(count) with the device L2 header.
+ * This function returns the index for the provided L2 header in case
+ * of success else -ve value.
+ */
+int modem_get_cdev_index(u8 l2_header)
+{
+ u8 cnt;
+ for (cnt = 0; cnt < ARRAY_SIZE(map_dev); cnt++) {
+ if (map_dev[cnt].l2_header == l2_header)
+ return map_dev[cnt].idx;
+ }
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(modem_get_cdev_index);
+
+/**
+ * modem_get_cdev_l2header() - return l2_header mapped to the index
+ * @idx: index
+ *
+ * struct map_device maps the index(count) with the device L2 header.
+ * This function returns the L2 header for the given index in case
+ * of success else -ve value.
+ */
+int modem_get_cdev_l2header(u8 idx)
+{
+ u8 cnt;
+ for (cnt = 0; cnt < ARRAY_SIZE(map_dev); cnt++) {
+ if (map_dev[cnt].idx == idx)
+ return map_dev[cnt].l2_header;
+ }
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(modem_get_cdev_l2header);
+
+/**
+ * modem_isa_reset() - reset device interfaces
+ * @modem_spi_dev: pointer to modem driver information structure
+ *
+ * Emptys the queue for each L2 mux channel.
+ */
+void modem_isa_reset(struct modem_spi_dev *modem_spi_dev)
+{
+ struct isa_device_context *isadev;
+ struct isa_driver_context *isa_context;
+ struct queue_element *cur_msg = NULL;
+ struct list_head *cur_msg_ptr = NULL;
+ struct list_head *msg_ptr;
+ struct message_queue *q;
+ int devidx;
+
+ dev_info(modem_spi_dev->dev, "resetting char device queues\n");
+
+ isa_context = modem_spi_dev->isa_context;
+ for (devidx = 0; devidx < ARRAY_SIZE(map_dev); devidx++) {
+ isadev = &isa_context->isadev[devidx];
+ q = &isadev->dl_queue;
+
+ spin_lock_bh(&q->update_lock);
+ /* empty out the msg queue */
+ list_for_each_safe(cur_msg_ptr, msg_ptr, &q->msg_list) {
+ cur_msg = list_entry(cur_msg_ptr,
+ struct queue_element, entry);
+ list_del(cur_msg_ptr);
+ kfree(cur_msg);
+ }
+
+ /* reset the msg queue pointers */
+ q->size = SIZE_OF_RX_FIFO;
+ q->readptr = 0;
+ q->writeptr = 0;
+ q->no = 0;
+
+ /* wake up the blocking read/select */
+ atomic_set(&q->q_rp, 1);
+ wake_up_interruptible(&q->wq_readable);
+ spin_unlock_bh(&q->update_lock);
+ }
+}
+EXPORT_SYMBOL_GPL(modem_isa_reset);
+
+static void create_queue(struct message_queue *q, u8 channel,
+ struct modem_spi_dev *modem_spi_dev)
+{
+ q->channel = channel;
+ q->fifo_base = (u8 *)&message_fifo[channel];
+ q->size = SIZE_OF_RX_FIFO;
+ q->free = q->size;
+ q->readptr = 0;
+ q->writeptr = 0;
+ q->no = 0;
+ spin_lock_init(&q->update_lock);
+ atomic_set(&q->q_rp, 0);
+ init_waitqueue_head(&q->wq_readable);
+ INIT_LIST_HEAD(&q->msg_list);
+ q->modem_spi_dev = modem_spi_dev;
+}
+
+static void delete_queue(struct message_queue *q)
+{
+ q->size = 0;
+ q->readptr = 0;
+ q->writeptr = 0;
+}
+
+/**
+ * modem_isa_queue_msg() - Add a message to a queue queue
+ * @q: message queue
+ * @size: size in bytes
+ *
+ * This function tries to allocate size bytes in FIFO q.
+ * It returns negative number when no memory can be allocated
+ * currently.
+ */
+int modem_isa_queue_msg(struct message_queue *q, u32 size)
+{
+ struct queue_element *new_msg = NULL;
+ struct modem_spi_dev *modem_spi_dev = q->modem_spi_dev;
+
+ new_msg = kmalloc(sizeof(struct queue_element), GFP_ATOMIC);
+ if (new_msg == NULL) {
+ dev_err(modem_spi_dev->dev,
+ "failed to allocate memory for queue item\n");
+ return -ENOMEM;
+ }
+ new_msg->offset = q->writeptr;
+ new_msg->size = size;
+ new_msg->no = q->no++;
+
+ /* check for overflow condition */
+ if (q->readptr <= q->writeptr) {
+ if (((q->writeptr - q->readptr) + size) >= q->size) {
+ dev_err(modem_spi_dev->dev, "rx q++ ch %d %d (%d)\n",
+ q->channel, size, q->free);
+ dev_err(modem_spi_dev->dev,
+ "ch%d buffer overflow, frame discarded\n",
+ q->channel);
+ return -ENOMEM;
+ }
+ } else {
+ if ((q->writeptr + size) >= q->readptr) {
+ dev_err(modem_spi_dev->dev, "rx q++ ch %d %d (%d)\n",
+ q->channel, size, q->free);
+ dev_err(modem_spi_dev->dev,
+ "ch%d buffer overflow, frame discarded\n",
+ q->channel);
+ return -ENOMEM;
+ }
+ }
+ q->free -= size;
+ q->writeptr = (q->writeptr + size) % q->size;
+ if (list_empty(&q->msg_list)) {
+ list_add_tail(&new_msg->entry, &q->msg_list);
+ /* There can be 2 blocking calls: read and another select */
+ atomic_set(&q->q_rp, 1);
+ wake_up_interruptible(&q->wq_readable);
+ } else {
+ list_add_tail(&new_msg->entry, &q->msg_list);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_isa_queue_msg);
+
+/**
+ * modem_isa_unqueue_msg() - remove a message from the msg queue
+ * @q: message queue
+ *
+ * Deletes a message from the message list associated with message
+ * queue q and also updates read ptr. If the message list is empty
+ * then a flag is set to block the select and read calls of the paricular queue.
+ *
+ * The message list is FIFO style and message is always added to tail and
+ * removed from head.
+ */
+int modem_isa_unqueue_msg(struct message_queue *q)
+{
+ struct queue_element *old_msg = NULL;
+ struct list_head *msg_ptr = NULL;
+ struct list_head *old_msg_ptr = NULL;
+
+ list_for_each_safe(old_msg_ptr, msg_ptr, &q->msg_list) {
+ old_msg = list_entry(old_msg_ptr, struct queue_element, entry);
+ if (old_msg == NULL)
+ return -EFAULT;
+ list_del(old_msg_ptr);
+ q->readptr = (q->readptr + old_msg->size) % q->size;
+ q->free += old_msg->size;
+ kfree(old_msg);
+ break;
+ }
+ if (list_empty(&q->msg_list))
+ atomic_set(&q->q_rp, 0);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_isa_unqueue_msg);
+
+/**
+ * modem_isa_msg_size() - retrieve the size of the most recent message
+ * @q: message queue
+ */
+int modem_isa_msg_size(struct message_queue *q)
+{
+ struct queue_element *new_msg = NULL;
+ struct list_head *msg_list;
+ unsigned long flags;
+ int size = 0;
+
+ spin_lock_irqsave(&q->update_lock, flags);
+ list_for_each(msg_list, &q->msg_list) {
+ new_msg = list_entry(msg_list, struct queue_element, entry);
+ if (new_msg == NULL) {
+ spin_unlock_irqrestore(&q->update_lock, flags);
+ return -EFAULT;
+ }
+ size = new_msg->size;
+ break;
+ }
+ spin_unlock_irqrestore(&q->update_lock, flags);
+ return size;
+}
+EXPORT_SYMBOL_GPL(modem_isa_msg_size);
+
+static u32 isa_select(struct file *filp, struct poll_table_struct *wait)
+{
+ struct isa_device_context *isadev = filp->private_data;
+ struct modem_spi_dev *modem_spi_dev = isadev->dl_queue.modem_spi_dev;
+ struct message_queue *q;
+ u32 m = iminor(filp->f_path.dentry->d_inode);
+ u8 idx = modem_get_cdev_index(m);
+
+ if (modem_spi_dev->msr_flag)
+ return -ENODEV;
+ if (isadev->device_id != idx)
+ return -1;
+
+ q = &isadev->dl_queue;
+ poll_wait(filp, &q->wq_readable, wait);
+ if (atomic_read(&q->q_rp) == 1)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+static ssize_t isa_read(struct file *filp, char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ u32 size = 0;
+ int ret;
+ char *psrc;
+ struct isa_device_context *isadev =
+ (struct isa_device_context *)filp->private_data;
+ struct message_queue *q = &isadev->dl_queue;
+ struct modem_spi_dev *modem_spi_dev = q->modem_spi_dev;
+ u32 msgsize;
+ unsigned long flags;
+
+ if (len <= 0)
+ return -EFAULT;
+
+ if (modem_spi_dev->msr_flag) {
+ atomic_set(&q->q_rp, 0);
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&q->update_lock, flags);
+ if (list_empty(&q->msg_list)) {
+ spin_unlock_irqrestore(&q->update_lock, flags);
+ dev_dbg(modem_spi_dev->dev, "waiting for data on device %d\n",
+ isadev->device_id);
+ if (wait_event_interruptible(q->wq_readable,
+ atomic_read(&q->q_rp) == 1))
+ return -ERESTARTSYS;
+ } else {
+ spin_unlock_irqrestore(&q->update_lock, flags);
+ }
+
+ if (modem_spi_dev->msr_flag) {
+ atomic_set(&q->q_rp, 0);
+ return -ENODEV;
+ }
+
+ msgsize = modem_isa_msg_size(q);
+ if (len < msgsize)
+ return -EINVAL;
+
+ if ((q->readptr + msgsize) >= q->size) {
+ psrc = (char *)buf;
+ size = (q->size - q->readptr);
+ /* copy first part of msg */
+ if (copy_to_user(psrc, (u8 *)(q->fifo_base + q->readptr),
+ size))
+ return -EFAULT;
+
+ psrc += size;
+ /* copy second part of msg at the top of fifo */
+ if (copy_to_user(psrc, (u8 *)(q->fifo_base),
+ (msgsize - size)))
+ return -EFAULT;
+ } else {
+ if (copy_to_user(buf, (u8 *)(q->fifo_base + q->readptr),
+ msgsize))
+ return -EFAULT;
+ }
+
+ spin_lock_irqsave(&q->update_lock, flags);
+ ret = modem_isa_unqueue_msg(q);
+ if (ret < 0)
+ msgsize = ret;
+ spin_unlock_irqrestore(&q->update_lock, flags);
+ return msgsize;
+}
+
+static ssize_t isa_write(struct file *filp, const char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ struct isa_device_context *isadev = filp->private_data;
+ struct message_queue *q = &isadev->dl_queue;
+ struct modem_spi_dev *modem_spi_dev = q->modem_spi_dev;
+ struct isa_driver_context *isa_context = modem_spi_dev->isa_context;
+ void *addr = 0;
+ int err;
+ int l2_header;
+ int ret = 0;
+ unsigned long flags;
+
+ if (len <= 0 || buf == NULL)
+ return -EFAULT;
+
+ if (len > SIZE_OF_TX_COPY_BUFFER) {
+ dev_err(modem_spi_dev->dev,
+ "invalid message size %d! max is %d bytes\n",
+ len, SIZE_OF_TX_COPY_BUFFER);
+ return -EFAULT;
+ }
+
+ l2_header = modem_get_cdev_l2header(isadev->device_id);
+ if (l2_header < 0) {
+ dev_err(modem_spi_dev->dev, "invalid L2 channel!\n");
+ return l2_header;
+ }
+
+ switch (l2_header) {
+ case MODEM_M6718_SPI_CHN_AUDIO:
+ addr = (void *)wr_audio_msg;
+ break;
+ case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0:
+ case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0:
+ case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1:
+ case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1:
+ addr = (void *)wr_mlb_msg;
+ break;
+ default:
+ dev_dbg(modem_spi_dev->dev, "invalid device!\n");
+ return -EFAULT;
+ }
+
+ if (copy_from_user(addr, buf, len))
+ return -EFAULT;
+
+ /*
+ * Special handling for audio channel:
+ * uses a mutext instead of a spinlock
+ */
+ if (l2_header == MODEM_M6718_SPI_CHN_AUDIO ||
+ l2_header == MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1) {
+ mutex_lock(&isa_context->audio_tx_mutex);
+ err = modem_m6718_spi_send(modem_spi_dev, l2_header, len, addr);
+ if (!err)
+ ret = len;
+ else
+ ret = err;
+ mutex_unlock(&modem_spi_dev->isa_context->audio_tx_mutex);
+ } else {
+ spin_lock_irqsave(&isa_context->common_tx_lock, flags);
+ err = modem_m6718_spi_send(modem_spi_dev, l2_header, len, addr);
+ if (!err)
+ ret = len;
+ else
+ ret = err;
+ spin_unlock_irqrestore(&isa_context->common_tx_lock, flags);
+ }
+ return ret;
+}
+
+static long isa_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ return -EINVAL;
+}
+
+static int isa_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ return -EINVAL;
+}
+
+static int isa_close(struct inode *inode, struct file *filp)
+{
+ struct isa_device_context *isadev = filp->private_data;
+ struct modem_spi_dev *modem_spi_dev = isadev->dl_queue.modem_spi_dev;
+ struct isa_driver_context *isa_context = modem_spi_dev->isa_context;
+ u8 m;
+ int idx;
+
+ mutex_lock(&isa_lock);
+ m = iminor(filp->f_path.dentry->d_inode);
+ idx = modem_get_cdev_index(m);
+ if (idx < 0) {
+ dev_err(modem_spi_dev->dev, "invalid L2 channel!\n");
+ return idx;
+ }
+
+ if (atomic_dec_and_test(&isa_context->is_open[idx])) {
+ atomic_inc(&isa_context->is_open[idx]);
+ dev_err(modem_spi_dev->dev, "device is not open yet!\n");
+ mutex_unlock(&isa_lock);
+ return -ENODEV;
+ }
+ atomic_set(&isa_context->is_open[idx], 1);
+
+ switch (m) {
+ case MODEM_M6718_SPI_CHN_AUDIO:
+ dev_dbg(modem_spi_dev->dev, "close channel AUDIO\n");
+ break;
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK
+ case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0:
+ dev_dbg(modem_spi_dev->dev, "close channel MASTER_LOOPBACK0\n");
+ break;
+ case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0:
+ dev_dbg(modem_spi_dev->dev, "close channel SLAVE_LOOPBACK0\n");
+ break;
+ case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1:
+ dev_dbg(modem_spi_dev->dev, "close channel MASTER_LOOPBACK1\n");
+ break;
+ case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1:
+ dev_dbg(modem_spi_dev->dev, "close channel SLAVE_LOOPBACK1\n");
+ break;
+#endif
+ default:
+ dev_dbg(modem_spi_dev->dev, "invalid device\n");
+ mutex_unlock(&isa_lock);
+ return -ENODEV;
+ }
+ mutex_unlock(&isa_lock);
+ return 0;
+}
+
+static int isa_open(struct inode *inode, struct file *filp)
+{
+ int err = 0;
+ u8 m;
+ int idx;
+ struct isa_device_context *isadev;
+ struct isa_driver_context *isa_context =
+ container_of(inode->i_cdev, struct isa_driver_context, cdev);
+ struct modem_spi_dev *modem_spi_dev =
+ isa_context->isadev->dl_queue.modem_spi_dev;
+
+ if (!modem_m6718_spi_is_boot_done()) {
+ dev_dbg(modem_spi_dev->dev,
+ "failed to open device, boot is not complete\n");
+ err = -EBUSY;
+ goto out;
+ }
+
+ mutex_lock(&isa_lock);
+ m = iminor(inode);
+ idx = modem_get_cdev_index(m);
+ if (idx < 0) {
+ dev_err(modem_spi_dev->dev, "invalid device\n");
+ err = -ENODEV;
+ goto cleanup;
+ }
+
+ if (!atomic_dec_and_test(&isa_context->is_open[idx])) {
+ atomic_inc(&isa_context->is_open[idx]);
+ dev_err(modem_spi_dev->dev, "device is already open\n");
+ err = -EBUSY;
+ goto cleanup;
+ }
+
+ isadev = &isa_context->isadev[idx];
+ if (filp != NULL)
+ filp->private_data = isadev;
+
+ switch (m) {
+ case MODEM_M6718_SPI_CHN_AUDIO:
+ dev_dbg(modem_spi_dev->dev, "open channel AUDIO\n");
+ break;
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK
+ case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0:
+ dev_dbg(modem_spi_dev->dev, "open channel MASTER_LOOPBACK0\n");
+ break;
+ case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0:
+ dev_dbg(modem_spi_dev->dev, "open channel SLAVE_LOOPBACK0\n");
+ break;
+ case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1:
+ dev_dbg(modem_spi_dev->dev, "open channel MASTER_LOOPBACK1\n");
+ break;
+ case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1:
+ dev_dbg(modem_spi_dev->dev, "open channel SLAVE_LOOPBACK1\n");
+ break;
+#endif
+ }
+
+cleanup:
+ mutex_unlock(&isa_lock);
+out:
+ return err;
+}
+
+const struct file_operations isa_fops = {
+ .owner = THIS_MODULE,
+ .open = isa_open,
+ .release = isa_close,
+ .unlocked_ioctl = isa_ioctl,
+ .mmap = isa_mmap,
+ .read = isa_read,
+ .write = isa_write,
+ .poll = isa_select,
+};
+
+/**
+ * modem_isa_init() - initialise the modem char device interfaces
+ * @modem_spi_dev: pointer to the modem driver information structure
+ *
+ * This function registers registers as a char device driver and creates the
+ * char device nodes supported by the modem.
+ */
+int modem_isa_init(struct modem_spi_dev *modem_spi_dev)
+{
+ dev_t dev_id;
+ int retval;
+ int devidx;
+ struct isa_device_context *isadev;
+ struct isa_driver_context *isa_context;
+
+ dev_dbg(modem_spi_dev->dev, "registering char device interfaces\n");
+
+ isa_context = kzalloc(sizeof(struct isa_driver_context), GFP_KERNEL);
+ if (isa_context == NULL) {
+ dev_err(modem_spi_dev->dev, "failed to allocate context\n");
+ retval = -ENOMEM;
+ goto rollback;
+ }
+
+ modem_spi_dev->isa_context = isa_context;
+ if (major) {
+ /* major node specified at module load */
+ dev_id = MKDEV(major, 0);
+ retval = register_chrdev_region(dev_id,
+ MODEM_M6718_SPI_MAX_CHANNELS, NAME);
+ } else {
+ retval = alloc_chrdev_region(&dev_id, 0,
+ MODEM_M6718_SPI_MAX_CHANNELS, NAME);
+ major = MAJOR(dev_id);
+ }
+
+ dev_dbg(modem_spi_dev->dev, "device major is %d\n", major);
+
+ cdev_init(&isa_context->cdev, &isa_fops);
+ isa_context->cdev.owner = THIS_MODULE;
+ retval = cdev_add(&isa_context->cdev, dev_id,
+ MODEM_M6718_SPI_MAX_CHANNELS);
+ if (retval) {
+ dev_err(modem_spi_dev->dev, "failed to add char device\n");
+ goto rollback_register;
+ }
+
+ isa_context->modem_class = class_create(THIS_MODULE, NAME);
+ if (IS_ERR(isa_context->modem_class)) {
+ dev_err(modem_spi_dev->dev, "failed to create modem class\n");
+ retval = PTR_ERR(isa_context->modem_class);
+ goto rollback_add_dev;
+ }
+ isa_context->isadev = kzalloc(sizeof(struct isa_device_context) *
+ MODEM_M6718_SPI_MAX_CHANNELS, GFP_KERNEL);
+ if (isa_context->isadev == NULL) {
+ dev_err(modem_spi_dev->dev,
+ "failed to allocate device context\n");
+ goto rollback_create_dev;
+ }
+
+ for (devidx = 0; devidx < ARRAY_SIZE(map_dev); devidx++) {
+ atomic_set(&isa_context->is_open[devidx], 1);
+ device_create(isa_context->modem_class,
+ NULL,
+ MKDEV(MAJOR(dev_id), map_dev[devidx].l2_header),
+ NULL,
+ map_dev[devidx].name);
+
+ isadev = &isa_context->isadev[devidx];
+ isadev->device_id = devidx;
+ create_queue(&isadev->dl_queue,
+ isadev->device_id, modem_spi_dev);
+
+ dev_dbg(modem_spi_dev->dev, "created device %d (%s) (%d.%d)\n",
+ devidx, map_dev[devidx].name, major,
+ map_dev[devidx].l2_header);
+ }
+
+ mutex_init(&isa_context->audio_tx_mutex);
+ spin_lock_init(&isa_context->common_tx_lock);
+
+ dev_dbg(modem_spi_dev->dev, "registered modem char devices\n");
+ return 0;
+
+rollback_create_dev:
+ for (devidx = 0; devidx < ARRAY_SIZE(map_dev); devidx++) {
+ device_destroy(isa_context->modem_class,
+ MKDEV(MAJOR(dev_id), map_dev[devidx].l2_header));
+ }
+ class_destroy(isa_context->modem_class);
+rollback_add_dev:
+ cdev_del(&isa_context->cdev);
+rollback_register:
+ unregister_chrdev_region(dev_id, MODEM_M6718_SPI_MAX_CHANNELS);
+ kfree(isa_context);
+ modem_spi_dev->isa_context = NULL;
+rollback:
+ return retval;
+}
+EXPORT_SYMBOL_GPL(modem_isa_init);
+
+/**
+ * modem_isa_exit() - remove the char device interfaces and clean up
+ * @modem_spi_dev: pointer to the modem driver information structure
+ */
+void modem_isa_exit(struct modem_spi_dev *modem_spi_dev)
+{
+ int devidx;
+ struct isa_device_context *isadev;
+ struct isa_driver_context *isa_context = modem_spi_dev->isa_context;
+ dev_t dev_id = MKDEV(major, 0);
+
+ if (!modem_spi_dev || !modem_spi_dev->isa_context)
+ return;
+
+ for (devidx = 0; devidx < ARRAY_SIZE(map_dev); devidx++)
+ device_destroy(isa_context->modem_class,
+ MKDEV(MAJOR(dev_id),
+ map_dev[devidx].l2_header));
+ for (devidx = 0; devidx < MODEM_M6718_SPI_MAX_CHANNELS; devidx++) {
+ isadev = &isa_context->isadev[devidx];
+ delete_queue(&isadev->dl_queue);
+ kfree(isadev);
+ }
+ class_destroy(isa_context->modem_class);
+ cdev_del(&isa_context->cdev);
+ unregister_chrdev_region(dev_id, MODEM_M6718_SPI_MAX_CHANNELS);
+ kfree(isa_context);
+ modem_spi_dev->isa_context = NULL;
+ dev_dbg(modem_spi_dev->dev, "removed modem char devices\n");
+}
+EXPORT_SYMBOL_GPL(modem_isa_exit);
+
+MODULE_AUTHOR("Chris Blair <chris.blair@stericsson.com>");
+MODULE_DESCRIPTION("M6718 modem IPC char device interface");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/char/shrm_char.c b/drivers/char/shrm_char.c
new file mode 100644
index 00000000000..e8f350e5da8
--- /dev/null
+++ b/drivers/char/shrm_char.c
@@ -0,0 +1,897 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson
+ * Author: Kumar Sanghavi <kumar.sanghvi@stericsson.com> for ST-Ericsson
+ * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/modem/shrm/shrm_driver.h>
+#include <linux/modem/shrm/shrm_private.h>
+#include <linux/modem/shrm/shrm_config.h>
+#include <linux/modem/shrm/shrm.h>
+#include <asm/atomic.h>
+
+#include <mach/isa_ioctl.h>
+
+
+#define NAME "IPC_ISA"
+/* L2 header for rtc_calibration device is 0xC8 and hence 0xC8 + 1 = 201 */
+#define MAX_L2_HEADERS 201
+
+#define SIZE_OF_FIFO (512*1024)
+
+static u8 message_fifo[ISA_DEVICES][SIZE_OF_FIFO];
+
+static u8 wr_rpc_msg[10*1024];
+static u8 wr_sec_msg[10*1024];
+static u8 wr_audio_msg[10*1024];
+static u8 wr_rtc_cal_msg[100];
+
+struct map_device {
+ u8 l2_header;
+ u8 idx;
+ char *name;
+};
+
+static struct map_device map_dev[] = {
+ {ISI_MESSAGING, 0, "isi"},
+ {RPC_MESSAGING, 1, "rpc"},
+ {AUDIO_MESSAGING, 2, "modemaudio"},
+ {SECURITY_MESSAGING, 3, "sec"},
+ {COMMON_LOOPBACK_MESSAGING, 4, "common_loopback"},
+ {AUDIO_LOOPBACK_MESSAGING, 5, "audio_loopback"},
+ {CIQ_MESSAGING, 6, "ciq"},
+ {RTC_CAL_MESSAGING, 7, "rtc_calibration"},
+};
+
+/*
+ * int major:This variable is exported to user as module_param to specify
+ * major number at load time
+ */
+static int major;
+module_param(major, int, 0);
+MODULE_PARM_DESC(major, "Major device number");
+/* global fops mutex */
+static DEFINE_MUTEX(isa_lock);
+
+/**
+ * shrm_get_cdev_index() - return the index mapped to l2 header
+ * @l2_header: L2 header
+ *
+ * struct map_device maps the index(count) with the device L2 header.
+ * This function returns the index for the provided L2 header in case
+ * of success else -ve value.
+ */
+int shrm_get_cdev_index(u8 l2_header)
+{
+ u8 cnt;
+ for (cnt = 0; cnt < ISA_DEVICES; cnt++) {
+ if (map_dev[cnt].l2_header == l2_header)
+ return map_dev[cnt].idx;
+ }
+ return -EINVAL;
+}
+
+/**
+ * shrm_get_cdev_l2header() - return l2_header mapped to the index
+ * @idx: index
+ *
+ * struct map_device maps the index(count) with the device L2 header.
+ * This function returns the L2 header for the given index in case
+ * of success else -ve value.
+ */
+int shrm_get_cdev_l2header(u8 idx)
+{
+ u8 cnt;
+ for (cnt = 0; cnt < ISA_DEVICES; cnt++) {
+ if (map_dev[cnt].idx == idx)
+ return map_dev[cnt].l2_header;
+ }
+ return -EINVAL;
+}
+
+void shrm_char_reset_queues(struct shrm_dev *shrm)
+{
+ struct isadev_context *isadev;
+ struct isa_driver_context *isa_context;
+ struct queue_element *cur_msg = NULL;
+ struct list_head *cur_msg_ptr = NULL;
+ struct list_head *msg_ptr;
+ struct message_queue *q;
+ int no_dev;
+
+ dev_info(shrm->dev, "%s: Resetting char device queues\n", __func__);
+ isa_context = shrm->isa_context;
+ for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) {
+ isadev = &isa_context->isadev[no_dev];
+ q = &isadev->dl_queue;
+
+ spin_lock_bh(&q->update_lock);
+ /* empty out the msg queue */
+ list_for_each_safe(cur_msg_ptr, msg_ptr, &q->msg_list) {
+ cur_msg = list_entry(cur_msg_ptr,
+ struct queue_element, entry);
+ list_del(cur_msg_ptr);
+ kfree(cur_msg);
+ }
+
+ /* reset the msg queue pointers */
+ q->size = SIZE_OF_FIFO;
+ q->readptr = 0;
+ q->writeptr = 0;
+ q->no = 0;
+
+ /* wake up the blocking read/select */
+ atomic_set(&q->q_rp, 1);
+ wake_up_interruptible(&q->wq_readable);
+
+ spin_unlock_bh(&q->update_lock);
+ }
+}
+
+/**
+ * create_queue() - To create FIFO for Tx and Rx message buffering.
+ * @q: message queue.
+ * @devicetype: device type 0-isi,1-rpc,2-audio,3-security,
+ * 4-common_loopback, 5-audio_loopback.
+ * @shrm: pointer to the shrm device information structure
+ *
+ * This function creates a FIFO buffer of n_bytes size using
+ * dma_alloc_coherent(). It also initializes all queue handling
+ * locks, queue management pointers. It also initializes message list
+ * which occupies this queue.
+ */
+static int create_queue(struct message_queue *q, u32 devicetype,
+ struct shrm_dev *shrm)
+{
+ q->fifo_base = (u8 *)&message_fifo[devicetype];
+ q->size = SIZE_OF_FIFO;
+ q->readptr = 0;
+ q->writeptr = 0;
+ q->no = 0;
+ q->shrm = shrm;
+ spin_lock_init(&q->update_lock);
+ INIT_LIST_HEAD(&q->msg_list);
+ init_waitqueue_head(&q->wq_readable);
+ atomic_set(&q->q_rp, 0);
+
+ return 0;
+}
+
+static void delete_queue(struct message_queue *q)
+{
+ q->size = 0;
+ q->readptr = 0;
+ q->writeptr = 0;
+}
+
+/**
+ * add_msg_to_queue() - Add a message inside queue
+ * @q: message queue
+ * @size: size in bytes
+ *
+ * This function tries to allocate n_bytes of size in FIFO q.
+ * It returns negative number when no memory can be allocated
+ * currently.
+ */
+int add_msg_to_queue(struct message_queue *q, u32 size)
+{
+ struct queue_element *new_msg = NULL;
+ struct shrm_dev *shrm = q->shrm;
+
+ dev_dbg(shrm->dev, "%s IN q->writeptr=%d\n", __func__, q->writeptr);
+ new_msg = kmalloc(sizeof(struct queue_element), GFP_ATOMIC);
+ if (new_msg == NULL) {
+ dev_err(shrm->dev, "unable to allocate memory\n");
+ return -ENOMEM;
+ }
+ new_msg->offset = q->writeptr;
+ new_msg->size = size;
+ new_msg->no = q->no++;
+
+ /* check for overflow condition */
+ if (q->readptr <= q->writeptr) {
+ if (((q->writeptr-q->readptr) + size) >= q->size) {
+ dev_err(shrm->dev, "Buffer overflow !!\n");
+ BUG_ON(((q->writeptr-q->readptr) + size) >= q->size);
+ }
+ } else {
+ if ((q->writeptr + size) >= q->readptr) {
+ dev_err(shrm->dev, "Buffer overflow !!\n");
+ BUG_ON((q->writeptr + size) >= q->readptr);
+ }
+ }
+ q->writeptr = (q->writeptr + size) % q->size;
+ if (list_empty(&q->msg_list)) {
+ list_add_tail(&new_msg->entry, &q->msg_list);
+ /* There can be 2 blocking calls read and another select */
+ atomic_set(&q->q_rp, 1);
+ wake_up_interruptible(&q->wq_readable);
+ } else
+ list_add_tail(&new_msg->entry, &q->msg_list);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return 0;
+}
+
+/**
+ * remove_msg_from_queue() - To remove a message from the msg queue.
+ * @q: message queue
+ *
+ * This function delets a message from the message list associated with message
+ * queue q and also updates read ptr.
+ * If the message list is empty, then, event is set to block the select and
+ * read calls of the paricular queue.
+ *
+ * The message list is FIFO style and message is always added to tail and
+ * removed from head.
+ */
+int remove_msg_from_queue(struct message_queue *q)
+{
+ struct queue_element *old_msg = NULL;
+ struct shrm_dev *shrm = q->shrm;
+ struct list_head *msg_ptr = NULL;
+ struct list_head *old_msg_ptr = NULL;
+
+ dev_dbg(shrm->dev, "%s IN q->readptr %d\n", __func__, q->readptr);
+
+ list_for_each_safe(old_msg_ptr, msg_ptr, &q->msg_list) {
+ old_msg = list_entry(old_msg_ptr, struct queue_element, entry);
+ if (old_msg == NULL) {
+ dev_err(shrm->dev, "no message found\n");
+ return -EFAULT;
+ }
+ list_del(old_msg_ptr);
+ q->readptr = (q->readptr + old_msg->size)%q->size;
+ kfree(old_msg);
+ break;
+ }
+ if (list_empty(&q->msg_list)) {
+ dev_dbg(shrm->dev, "List is empty setting RP= 0\n");
+ atomic_set(&q->q_rp, 0);
+ }
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return 0;
+}
+
+/**
+ * get_size_of_new_msg() - retrieve new message from message list
+ * @q: message queue
+ *
+ * This function will retrieve most recent message from the corresponding
+ * queue list. New message is always retrieved from head side.
+ * It returns new message no, offset if FIFO and size.
+ */
+int get_size_of_new_msg(struct message_queue *q)
+{
+ struct queue_element *new_msg = NULL;
+ struct list_head *msg_list;
+ struct shrm_dev *shrm = q->shrm;
+ int size = 0;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ spin_lock_bh(&q->update_lock);
+ list_for_each(msg_list, &q->msg_list) {
+ new_msg = list_entry(msg_list, struct queue_element, entry);
+ if (new_msg == NULL) {
+ spin_unlock_bh(&q->update_lock);
+ dev_err(shrm->dev, "no message found\n");
+ return -EFAULT;
+ }
+ size = new_msg->size;
+ break;
+ }
+ spin_unlock_bh(&q->update_lock);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return size;
+}
+
+/**
+ * isa_select() - shrm char interface driver select interface
+ * @filp: file descriptor pointer
+ * @wait: poll_table_struct pointer
+ *
+ * This function is used to perform non-blocking read operations. It allows
+ * a process to determine whether it can read from one or more open files
+ * without blocking. These calls can also block a process until any of a
+ * given set of file descriptors becomes available for reading.
+ * If a file is ready to read, POLLIN | POLLRDNORM bitmask is returned.
+ * The driver method is called whenever the user-space program performs a select
+ * system call involving a file descriptor associated with the driver.
+ */
+static u32 isa_select(struct file *filp,
+ struct poll_table_struct *wait)
+{
+ struct isadev_context *isadev = filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+ struct message_queue *q;
+ u32 mask = 0;
+ u32 m = iminor(filp->f_path.dentry->d_inode);
+ u8 idx = shrm_get_cdev_index(m);
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (shrm->msr_flag)
+ return -ENODEV;
+
+ if (isadev->device_id != idx)
+ return -1;
+
+ q = &isadev->dl_queue;
+ poll_wait(filp, &q->wq_readable, wait);
+ if (atomic_read(&q->q_rp) == 1)
+ mask = POLLIN | POLLRDNORM;
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return mask;
+}
+
+/**
+ * isa_read() - Read from device
+ * @filp: file descriptor
+ * @buf: user buffer pointer
+ * @len: size of requested data transfer
+ * @ppos: not used
+ *
+ * It reads a oldest message from queue and copies it into user buffer and
+ * returns its size.
+ * If there is no message present in queue, then it blocks until new data is
+ * available.
+ */
+ssize_t isa_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
+{
+ u32 size = 0;
+ int ret;
+ char *psrc;
+ struct isadev_context *isadev = (struct isadev_context *)
+ filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+ struct message_queue *q;
+ u32 msgsize;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (len <= 0)
+ return -EFAULT;
+
+ q = &isadev->dl_queue;
+
+ if (shrm->msr_flag) {
+ atomic_set(&q->q_rp, 0);
+ return -ENODEV;
+ }
+
+ spin_lock_bh(&q->update_lock);
+ if (list_empty(&q->msg_list)) {
+ spin_unlock_bh(&q->update_lock);
+ dev_dbg(shrm->dev, "Waiting for Data\n");
+ if (wait_event_interruptible(q->wq_readable,
+ atomic_read(&q->q_rp) == 1))
+ return -ERESTARTSYS;
+ } else
+ spin_unlock_bh(&q->update_lock);
+
+ if (shrm->msr_flag) {
+ atomic_set(&q->q_rp, 0);
+ return -ENODEV;
+ }
+
+ msgsize = get_size_of_new_msg(q);
+
+ if (len < msgsize)
+ return -EINVAL;
+
+ if ((q->readptr+msgsize) >= q->size) {
+ dev_dbg(shrm->dev, "Inside Loop Back\n");
+ psrc = (char *)buf;
+ size = (q->size-q->readptr);
+ /* Copy First Part of msg */
+ if (copy_to_user(psrc,
+ (u8 *)(q->fifo_base+q->readptr),
+ size)) {
+ dev_err(shrm->dev, "copy_to_user failed\n");
+ return -EFAULT;
+ }
+ psrc += size;
+ /* Copy Second Part of msg at the top of fifo */
+ if (copy_to_user(psrc,
+ (u8 *)(q->fifo_base),
+ (msgsize-size))) {
+ dev_err(shrm->dev, "copy_to_user failed\n");
+ return -EFAULT;
+ }
+ } else {
+ if (copy_to_user(buf,
+ (u8 *)(q->fifo_base + q->readptr),
+ msgsize)) {
+ dev_err(shrm->dev, "copy_to_user failed\n");
+ return -EFAULT;
+ }
+ }
+ spin_lock_bh(&q->update_lock);
+ ret = remove_msg_from_queue(q);
+ if (ret < 0) {
+ dev_err(shrm->dev,
+ "Remove msg from message queue failed\n");
+ msgsize = ret;
+ }
+ spin_unlock_bh(&q->update_lock);
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return msgsize;
+}
+
+/**
+ * isa_write() - Write to shrm char device
+ * @filp: file descriptor
+ * @buf: user buffer pointer
+ * @len: size of requested data transfer
+ * @ppos: not used
+ *
+ * It checks if there is space available in queue, and copies the message
+ * inside queue. If there is no space, it blocks until space becomes available.
+ * It also schedules transfer thread to transmit the newly added message.
+ */
+ssize_t isa_write(struct file *filp, const char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ struct isadev_context *isadev = filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+ struct message_queue *q;
+ void *addr = 0;
+ int err, l2_header;
+ int ret = 0;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (len <= 0 || buf == NULL)
+ return -EFAULT;
+ q = &isadev->dl_queue;
+ l2_header = shrm_get_cdev_l2header(isadev->device_id);
+ if (l2_header < 0) {
+ dev_err(shrm->dev, "failed to get L2 header\n");
+ return l2_header;
+ }
+
+ switch (l2_header) {
+ case RPC_MESSAGING:
+ dev_dbg(shrm->dev, "RPC\n");
+ addr = (void *)wr_rpc_msg;
+ break;
+ case AUDIO_MESSAGING:
+ dev_dbg(shrm->dev, "Audio\n");
+ addr = (void *)wr_audio_msg;
+ break;
+ case SECURITY_MESSAGING:
+ dev_dbg(shrm->dev, "Security\n");
+ addr = (void *)wr_sec_msg;
+ break;
+ case COMMON_LOOPBACK_MESSAGING:
+ dev_dbg(shrm->dev, "Common loopback\n");
+ addr = isadev->addr;
+ break;
+ case AUDIO_LOOPBACK_MESSAGING:
+ dev_dbg(shrm->dev, "Audio loopback\n");
+ addr = isadev->addr;
+ break;
+ case CIQ_MESSAGING:
+ dev_dbg(shrm->dev, "CIQ\n");
+ addr = isadev->addr;
+ break;
+ case RTC_CAL_MESSAGING:
+ dev_dbg(shrm->dev, "isa_write(): RTC Calibration\n");
+ addr = (void *)wr_rtc_cal_msg;
+ break;
+ default:
+ dev_dbg(shrm->dev, "Wrong device\n");
+ return -EFAULT;
+ }
+
+ if (copy_from_user(addr, buf, len)) {
+ dev_err(shrm->dev, "copy_from_user failed\n");
+ return -EFAULT;
+ }
+ /* Write msg to Fifo */
+ if ((l2_header == AUDIO_MESSAGING) ||
+ (l2_header == AUDIO_LOOPBACK_MESSAGING)) {
+ mutex_lock(&shrm->isa_context->tx_audio_mutex);
+ err = shm_write_msg(shrm, l2_header, addr, len);
+ if (!err)
+ ret = len;
+ else
+ ret = err;
+ mutex_unlock(&shrm->isa_context->tx_audio_mutex);
+ } else {
+ spin_lock_bh(&shrm->isa_context->common_tx);
+ err = shm_write_msg(shrm, l2_header, addr, len);
+ if (!err)
+ ret = len;
+ else
+ ret = err;
+ spin_unlock_bh(&shrm->isa_context->common_tx);
+ }
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return ret;
+}
+
+/**
+ * isa_ioctl() - To handle different ioctl commands supported by driver.
+ * @inode: structure is used by the kernel internally to represent files
+ * @filp: file descriptor pointer
+ * @cmd: ioctl command
+ * @arg: input param
+ *
+ * Following ioctls are supported by this driver.
+ * DLP_IOCTL_ALLOCATE_BUFFER - To allocate buffer for new uplink message.
+ * This ioctl is called with required message size. It returns offset for
+ * the allocates space in the queue. DLP_IOCTL_PUT_MESSAGE - To indicate
+ * new uplink message available in queuq for transmission. Message is copied
+ * from offset location returned by previous ioctl before calling this ioctl.
+ * DLP_IOCTL_GET_MESSAGE - To check if any downlink message is available in
+ * queue. It returns offset for new message inside queue.
+ * DLP_IOCTL_DEALLOCATE_BUFFER - To deallocate any buffer allocate for
+ * downlink message once the message is copied. Message is copied from offset
+ * location returned by previous ioctl before calling this ioctl.
+ */
+static long isa_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ long err = 0;
+ struct isadev_context *isadev = filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+ u32 m = iminor(filp->f_path.dentry->d_inode);
+
+ isadev = (struct isadev_context *)filp->private_data;
+
+ if (isadev->device_id != m)
+ return -EINVAL;
+
+ switch (cmd) {
+ case DLP_IOC_ALLOCATE_BUFFER:
+ dev_dbg(shrm->dev, "DLP_IOC_ALLOCATE_BUFFER\n");
+ break;
+ case DLP_IOC_PUT_MESSAGE:
+ dev_dbg(shrm->dev, "DLP_IOC_PUT_MESSAGE\n");
+ break;
+ case DLP_IOC_GET_MESSAGE:
+ dev_dbg(shrm->dev, "DLP_IOC_GET_MESSAGE\n");
+ break;
+ case DLP_IOC_DEALLOCATE_BUFFER:
+ dev_dbg(shrm->dev, "DLP_IOC_DEALLOCATE_BUFFER\n");
+ break;
+ default:
+ dev_dbg(shrm->dev, "Unknown IOCTL\n");
+ err = -EFAULT;
+ break;
+ }
+ return err;
+}
+/**
+ * isa_mmap() - Maps kernel queue memory to user space.
+ * @filp: file descriptor pointer
+ * @vma: virtual area memory structure.
+ *
+ * This function maps kernel FIFO into user space. This function
+ * shall be called twice to map both uplink and downlink buffers.
+ */
+static int isa_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct isadev_context *isadev = filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+
+ u32 m = iminor(filp->f_path.dentry->d_inode);
+ dev_dbg(shrm->dev, "%s %d\n", __func__, m);
+
+ return 0;
+}
+
+/**
+ * isa_close() - Close device file
+ * @inode: structure is used by the kernel internally to represent files
+ * @filp: device file descriptor
+ *
+ * This function deletes structues associated with this file, deletes
+ * queues, flushes and destroys workqueus and closes this file.
+ * It also unregisters itself from l2mux driver.
+ */
+static int isa_close(struct inode *inode, struct file *filp)
+{
+ struct isadev_context *isadev = filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+ struct isa_driver_context *isa_context = shrm->isa_context;
+ u8 m;
+ int idx;
+
+ mutex_lock(&isa_lock);
+ m = iminor(filp->f_path.dentry->d_inode);
+ idx = shrm_get_cdev_index(m);
+ if (idx < 0) {
+ dev_err(shrm->dev, "failed to get index\n");
+ mutex_unlock(&isa_lock);
+ return idx;
+ }
+ dev_dbg(shrm->dev, "isa_close %d", m);
+
+ if (atomic_dec_and_test(&isa_context->is_open[idx])) {
+ atomic_inc(&isa_context->is_open[idx]);
+ dev_err(shrm->dev, "Device not opened yet\n");
+ mutex_unlock(&isa_lock);
+ return -ENODEV;
+ }
+ atomic_set(&isa_context->is_open[idx], 1);
+
+ switch (m) {
+ case RPC_MESSAGING:
+ dev_info(shrm->dev, "Close RPC_MESSAGING Device\n");
+ break;
+ case AUDIO_MESSAGING:
+ dev_info(shrm->dev, "Close AUDIO_MESSAGING Device\n");
+ break;
+ case SECURITY_MESSAGING:
+ dev_info(shrm->dev, "CLose SECURITY_MESSAGING Device\n");
+ break;
+ case COMMON_LOOPBACK_MESSAGING:
+ kfree(isadev->addr);
+ dev_info(shrm->dev, "Close COMMON_LOOPBACK_MESSAGING Device\n");
+ break;
+ case AUDIO_LOOPBACK_MESSAGING:
+ kfree(isadev->addr);
+ dev_info(shrm->dev, "Close AUDIO_LOOPBACK_MESSAGING Device\n");
+ break;
+ case CIQ_MESSAGING:
+ kfree(isadev->addr);
+ dev_info(shrm->dev, "Close CIQ_MESSAGING Device\n");
+ break;
+ case RTC_CAL_MESSAGING:
+ dev_info(shrm->dev, "Close RTC_CAL_MESSAGING Device\n");
+ break;
+ default:
+ dev_info(shrm->dev, "No such device present\n");
+ mutex_unlock(&isa_lock);
+ return -ENODEV;
+ };
+ mutex_unlock(&isa_lock);
+ return 0;
+}
+/**
+ * isa_open() - Open device file
+ * @inode: structure is used by the kernel internally to represent files
+ * @filp: device file descriptor
+ *
+ * This function performs initialization tasks needed to open SHM channel.
+ * Following tasks are performed.
+ * -return if device is already opened
+ * -create uplink FIFO
+ * -create downlink FIFO
+ * -init delayed workqueue thread
+ * -register to l2mux driver
+ */
+static int isa_open(struct inode *inode, struct file *filp)
+{
+ int err = 0;
+ u8 m;
+ int idx;
+ struct isadev_context *isadev;
+ struct isa_driver_context *isa_context = container_of(
+ inode->i_cdev,
+ struct isa_driver_context,
+ cdev);
+ struct shrm_dev *shrm = isa_context->isadev->dl_queue.shrm;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (get_boot_state() != BOOT_DONE) {
+ dev_err(shrm->dev, "Boot is not done\n");
+ return -EBUSY;
+ }
+ mutex_lock(&isa_lock);
+ m = iminor(inode);
+
+ if ((m != RPC_MESSAGING) &&
+ (m != AUDIO_LOOPBACK_MESSAGING) &&
+ (m != COMMON_LOOPBACK_MESSAGING) &&
+ (m != AUDIO_MESSAGING) &&
+ (m != SECURITY_MESSAGING) &&
+ (m != CIQ_MESSAGING) &&
+ (m != RTC_CAL_MESSAGING)) {
+ dev_err(shrm->dev, "No such device present\n");
+ mutex_unlock(&isa_lock);
+ return -ENODEV;
+ }
+ idx = shrm_get_cdev_index(m);
+ if (idx < 0) {
+ dev_err(shrm->dev, "failed to get index\n");
+ mutex_unlock(&isa_lock);
+ return idx;
+ }
+ if (!atomic_dec_and_test(&isa_context->is_open[idx])) {
+ atomic_inc(&isa_context->is_open[idx]);
+ dev_err(shrm->dev, "Device already opened\n");
+ mutex_unlock(&isa_lock);
+ return -EBUSY;
+ }
+ isadev = &isa_context->isadev[idx];
+ if (filp != NULL)
+ filp->private_data = isadev;
+
+ switch (m) {
+ case RPC_MESSAGING:
+ dev_info(shrm->dev, "Open RPC_MESSAGING Device\n");
+ break;
+ case AUDIO_MESSAGING:
+ dev_info(shrm->dev, "Open AUDIO_MESSAGING Device\n");
+ break;
+ case SECURITY_MESSAGING:
+ dev_info(shrm->dev, "Open SECURITY_MESSAGING Device\n");
+ break;
+ case COMMON_LOOPBACK_MESSAGING:
+ isadev->addr = kzalloc(10 * 1024, GFP_KERNEL);
+ if (!isadev->addr) {
+ mutex_unlock(&isa_lock);
+ return -ENOMEM;
+ }
+ dev_info(shrm->dev, "Open COMMON_LOOPBACK_MESSAGING Device\n");
+ break;
+ case AUDIO_LOOPBACK_MESSAGING:
+ isadev->addr = kzalloc(10 * 1024, GFP_KERNEL);
+ if (!isadev->addr) {
+ mutex_unlock(&isa_lock);
+ return -ENOMEM;
+ }
+ dev_info(shrm->dev, "Open AUDIO_LOOPBACK_MESSAGING Device\n");
+ break;
+ case CIQ_MESSAGING:
+ isadev->addr = kzalloc(10 * 1024, GFP_KERNEL);
+ if (!isadev->addr) {
+ mutex_unlock(&isa_lock);
+ return -ENOMEM;
+ }
+ dev_info(shrm->dev, "Open CIQ_MESSAGING Device\n");
+ break;
+ case RTC_CAL_MESSAGING:
+ dev_info(shrm->dev, "Open RTC_CAL_MESSAGING Device\n");
+ break;
+ };
+
+ mutex_unlock(&isa_lock);
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return err;
+}
+
+const struct file_operations isa_fops = {
+ .owner = THIS_MODULE,
+ .open = isa_open,
+ .release = isa_close,
+ .unlocked_ioctl = isa_ioctl,
+ .mmap = isa_mmap,
+ .read = isa_read,
+ .write = isa_write,
+ .poll = isa_select,
+};
+
+/**
+ * isa_init() - module insertion function
+ * @shrm: pointer to the shrm device information structure
+ *
+ * This function registers module as a character driver using
+ * register_chrdev_region() or alloc_chrdev_region. It adds this
+ * driver to system using cdev_add() call. Major number is dynamically
+ * allocated using alloc_chrdev_region() by default or left to user to specify
+ * it during load time. For this variable major is used as module_param
+ * Nodes to be created using
+ * mknod /dev/isi c $major 0
+ * mknod /dev/rpc c $major 1
+ * mknod /dev/audio c $major 2
+ * mknod /dev/sec c $major 3
+ */
+int isa_init(struct shrm_dev *shrm)
+{
+ dev_t dev_id;
+ int retval, no_dev;
+ struct isadev_context *isadev;
+ struct isa_driver_context *isa_context;
+
+ isa_context = kzalloc(sizeof(struct isa_driver_context),
+ GFP_KERNEL);
+ if (isa_context == NULL) {
+ dev_err(shrm->dev, "Failed to alloc memory\n");
+ return -ENOMEM;
+ }
+ shrm->isa_context = isa_context;
+ if (major) {
+ dev_id = MKDEV(major, MAX_L2_HEADERS);
+ retval = register_chrdev_region(dev_id, ISA_DEVICES, NAME);
+ } else {
+ /*
+ * L2 header of loopback device is 192(0xc0). As per the shrm
+ * protocol the minor id of the deivce is mapped to the
+ * L2 header.
+ */
+ retval = alloc_chrdev_region(&dev_id, 0, MAX_L2_HEADERS, NAME);
+ major = MAJOR(dev_id);
+ }
+ dev_dbg(shrm->dev, " major %d\n", major);
+
+ cdev_init(&isa_context->cdev, &isa_fops);
+ isa_context->cdev.owner = THIS_MODULE;
+ retval = cdev_add(&isa_context->cdev, dev_id, MAX_L2_HEADERS);
+ if (retval) {
+ dev_err(shrm->dev, "Failed to add char device\n");
+ return retval;
+ }
+ /* create class and device */
+ isa_context->shm_class = class_create(THIS_MODULE, NAME);
+ if (IS_ERR(isa_context->shm_class)) {
+ dev_err(shrm->dev, "Error creating shrm class\n");
+ cdev_del(&isa_context->cdev);
+ retval = PTR_ERR(isa_context->shm_class);
+ kfree(isa_context);
+ return retval;
+ }
+
+ for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) {
+ atomic_set(&isa_context->is_open[no_dev], 1);
+ device_create(isa_context->shm_class, NULL,
+ MKDEV(MAJOR(dev_id),
+ map_dev[no_dev].l2_header), NULL,
+ map_dev[no_dev].name);
+ }
+
+ isa_context->isadev = kzalloc(sizeof
+ (struct isadev_context)*ISA_DEVICES,
+ GFP_KERNEL);
+ if (isa_context->isadev == NULL) {
+ dev_err(shrm->dev, "Failed to alloc memory\n");
+ return -ENOMEM;
+ }
+ for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) {
+ isadev = &isa_context->isadev[no_dev];
+ isadev->device_id = no_dev;
+ retval = create_queue(&isadev->dl_queue,
+ isadev->device_id, shrm);
+
+ if (retval < 0) {
+ dev_err(shrm->dev, "create dl_queue failed\n");
+ delete_queue(&isadev->dl_queue);
+ kfree(isadev);
+ return retval;
+ }
+ }
+ mutex_init(&isa_context->tx_audio_mutex);
+ spin_lock_init(&isa_context->common_tx);
+ dev_dbg(shrm->dev, " SHM Char Driver added\n");
+ return retval;
+}
+
+void isa_exit(struct shrm_dev *shrm)
+{
+ int no_dev;
+ struct isadev_context *isadev;
+ struct isa_driver_context *isa_context = shrm->isa_context;
+ dev_t dev_id = MKDEV(major, 0);
+
+ for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) {
+ device_destroy(isa_context->shm_class,
+ MKDEV(MAJOR(dev_id),
+ map_dev[no_dev].l2_header));
+ isadev = &isa_context->isadev[no_dev];
+ delete_queue(&isadev->dl_queue);
+ kfree(isadev);
+ }
+ class_destroy(isa_context->shm_class);
+ cdev_del(&isa_context->cdev);
+ unregister_chrdev_region(dev_id, ISA_DEVICES);
+ kfree(isa_context);
+ dev_dbg(shrm->dev, " SHM Char Driver removed\n");
+}
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index f92560ef750..4fbe2c3f0f9 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -451,6 +451,20 @@ config ARM_CHARLCD
line and the Linux version on the second line, but that's
still useful.
+config STE_TRACE_MODEM
+ tristate "DB8500 trace Modem"
+ depends on ARCH_U8500
+ default n
+ help
+ Select this option to enable modem tracing by APE
+
+config DBX500_MLOADER
+ tristate "Modem firmware loader for db8500"
+ default n
+ depends on UX500_SOC_DB8500 || UX500_SOC_DB5500
+ help
+ Provides a user interface to load modem firmware on dbx500 SOCs
+
config BMP085
tristate "BMP085 digital pressure sensor"
depends on I2C && SYSFS
@@ -523,6 +537,22 @@ config HWMEM
can be used by hardware. It also enables accessing hwmem allocated
memory buffers through a secure id which can be shared across processes.
+config U5500_MBOX
+ bool "Mailbox support"
+ depends on (UX500_SOC_DB5500 && U5500_MODEM_IRQ)
+ default y
+ help
+ Add support for U5500 mailbox communication with modem side
+
+config U8500_SIM_DETECT
+ bool "Sim hot swap detection support"
+ depends on (MODEM && UX500_SOC_DB8500)
+ default n
+ help
+ Add support for sim hot swap detection support in U8500.Driver
+ basically wakes up the modem if its sleeping when sim hot plug
+ in/out has happened.
+
config USB_SWITCH_FSA9480
tristate "FSA9480 USB Switch"
depends on I2C
@@ -549,4 +579,5 @@ source "drivers/misc/ti-st/Kconfig"
source "drivers/misc/lis3lv02d/Kconfig"
source "drivers/misc/carma/Kconfig"
source "drivers/misc/altera-stapl/Kconfig"
+source "drivers/misc/modem_audio/Kconfig"
endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 2741a01610f..fc02851cbe8 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -51,6 +51,11 @@ obj-$(CONFIG_HWMEM) += hwmem/
obj-$(CONFIG_DISPDEV) += dispdev/
obj-$(CONFIG_COMPDEV) += compdev/
obj-$(CONFIG_CLONEDEV) += clonedev/
+obj-$(CONFIG_STE_TRACE_MODEM) += db8500-modem-trace.o
+obj-$(CONFIG_DBX500_MLOADER) += dbx500-mloader.o
+obj-$(CONFIG_U5500_MBOX) += mbox.o mbox_channels-db5500.o
+obj-$(CONFIG_U8500_SIM_DETECT) += sim_detect.o
obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o
obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/
obj-$(CONFIG_MAX8997_MUIC) += max8997-muic.o
+obj-y += modem_audio/
diff --git a/drivers/misc/db8500-modem-trace.c b/drivers/misc/db8500-modem-trace.c
new file mode 100644
index 00000000000..b757b742121
--- /dev/null
+++ b/drivers/misc/db8500-modem-trace.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors: Michel JAOUEN <michel.jaouen@stericsson.com>
+ * Maxime COQUELIN <maxime.coquelin-nonst@stericsson.com>
+ * for ST-Ericsson
+ * License terms: GNU General Public License (GPL), version 2
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/uaccess.h>
+#include <linux/mman.h>
+#include <linux/db8500-modem-trace.h>
+
+#include <mach/hardware.h>
+
+#define DEVICE_NAME "db8500-modem-trace"
+
+/* activation of this flag triggers an initialization of 2 buffers
+ * 4kbytes , id 0xdeadbeef
+ * and 16Kbytes id 0xfadafada
+ * we assume that platform provides minimum 20Kbytes. */
+
+struct trace {
+ u32 start;
+ u32 end;
+ u32 mdm_base;
+ u32 ape_base;
+ void __iomem *area;
+ /* this spinlock to forbid concurrent access on the same trace buffer */
+ spinlock_t lock;
+ struct device *dev;
+ struct miscdevice misc_dev;
+};
+
+struct trace_modem {
+ u32 phys_addr;
+ u8 filler;
+};
+
+static struct trace *trace_priv;
+
+
+/* all this definition are linked to modem interface */
+#define MODEM_MARKER 0x88
+/* free marker is also written on filler */
+#define FREE_MARKER 0xa5
+#define FREE_MARKER_2 0xa5a5
+#define READ_MARKER 0x5a
+
+struct buffer_header {
+ u8 pattern;
+ u8 filler;
+ u16 head_size;
+};
+
+
+static int trace_read(unsigned long arg)
+{
+ struct modem_trace_req req;
+ struct buffer_header *pt;
+ char tmp_char;
+
+ if (copy_from_user(&req, (struct modem_trace_req *)arg,
+ sizeof(struct modem_trace_req)))
+ return -EFAULT;
+
+ /* compute Modem physical address to APE physical address range */
+ if (req.phys_addr < trace_priv->mdm_base) {
+ dev_err(trace_priv->dev, "MODEM ADDR uncorrect\n");
+ return -EINVAL;
+ }
+ req.phys_addr += trace_priv->ape_base - trace_priv->mdm_base;
+
+ /* check request is in the range and aligned */
+ if ((req.phys_addr % 4 != 0)
+ || (req.phys_addr < trace_priv->start)
+ || (req.phys_addr + req.size) >= trace_priv->end) {
+ dev_err(trace_priv->dev, "req out of range %x %x\n",
+ req.phys_addr, req.size);
+ return -EINVAL;
+ }
+
+ /* perform access to memory area */
+ pt = (struct buffer_header *)((u32)trace_priv->area +
+ req.phys_addr - trace_priv->start);
+
+ /* in case of several request coming on same trace buffer take a
+ * spinlock */
+ spin_lock(&trace_priv->lock);
+ if (pt->pattern != MODEM_MARKER) {
+ /* pattern and size not matching */
+ dev_err(trace_priv->dev, "req not matching filler %x/%x \
+ or/and pattern %x\n", req.filler, pt->filler,
+ pt->pattern);
+ spin_unlock(&trace_priv->lock);
+ return -EINVAL;
+ }
+ /* mark pattern as read and unlock spin */
+ pt->pattern = READ_MARKER;
+ spin_unlock(&trace_priv->lock);
+
+ req.size -= copy_to_user(req.buff, pt, req.size);
+
+ pt->pattern = FREE_MARKER;
+ pt->filler = FREE_MARKER;
+ tmp_char = MODEM_MARKER;
+
+ /* Update marker for trace tool */
+ if (copy_to_user(req.buff, &tmp_char, 1))
+ return -EFAULT;
+
+ /* Update effective written size */
+ if (copy_to_user((struct modem_trace_req *)arg, &req,
+ sizeof(struct modem_trace_req)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int trace_mmapdump(struct file *file, struct vm_area_struct *vma)
+{
+ unsigned long vma_start = vma->vm_start;
+
+ if (vma->vm_flags & VM_WRITE)
+ return -EPERM;
+
+ if ((vma->vm_end - vma->vm_start) <
+ (trace_priv->end - trace_priv->start))
+ return -EINVAL;
+ if (remap_pfn_range(vma,
+ vma_start,
+ trace_priv->start >> PAGE_SHIFT,
+ trace_priv->end - trace_priv->start,
+ vma->vm_page_prot))
+ return -EAGAIN;
+ return 0;
+}
+
+static long trace_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ long ret = 0;
+ void __user *argp = (void __user *)arg;
+ unsigned long size = trace_priv->end-trace_priv->start;
+
+ switch (cmd) {
+ case TM_GET_DUMPINFO:
+ ret = put_user(size, (unsigned long *)argp);
+ break;
+ case TM_TRACE_REQ:
+ ret = trace_read(arg);
+ break;
+
+ default:
+ ret = -EPERM;
+ break;
+ }
+ return ret;
+}
+
+static const struct file_operations trace_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = trace_ioctl,
+ .mmap = trace_mmapdump
+};
+
+static int trace_probe(struct platform_device *pdev)
+{
+ int rv = 0;
+ struct db8500_trace_platform_data *pdata = pdev->dev.platform_data;
+ /* retrieve area descriptor from platform device ressource */
+ struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ if ((mem->start == 0) && (mem->end == 0)) {
+ rv = -EINVAL;
+ goto out;
+ }
+
+ if ((pdata->ape_base == 0) || (pdata->modem_base == 0)) {
+ rv = -EINVAL;
+ goto out;
+ }
+
+ trace_priv = kzalloc(sizeof(*trace_priv), GFP_ATOMIC);
+ if (!trace_priv) {
+ rv = -ENOMEM;
+ goto out;
+ }
+
+ trace_priv->dev = &pdev->dev;
+ trace_priv->misc_dev.minor = MISC_DYNAMIC_MINOR;
+ trace_priv->misc_dev.name = DEVICE_NAME;
+ trace_priv->misc_dev.fops = &trace_fops;
+ trace_priv->area = (void __iomem *)ioremap_nocache(mem->start,
+ resource_size(mem));
+ if (!trace_priv->area) {
+ rv = -ENOMEM;
+ goto outfree;
+ }
+
+ trace_priv->start = mem->start;
+ trace_priv->end = mem->end;
+
+ trace_priv->mdm_base = pdata->modem_base;
+ trace_priv->ape_base = pdata->ape_base;
+
+ /* spin allowing smp access for reading/writing trace buffer header */
+ spin_lock_init(&trace_priv->lock);
+
+ rv = misc_register(&trace_priv->misc_dev);
+ if (rv) {
+ dev_err(&pdev->dev, "can't misc_register\n");
+ goto outunmap;
+ }
+
+ return rv;
+
+outunmap:
+ iounmap(trace_priv->area);
+outfree:
+ kfree(trace_priv);
+out:
+ return rv;
+
+}
+
+static int trace_remove(struct platform_device *pdev)
+{
+ int rv = 0;
+
+ if (trace_priv) {
+ rv = misc_deregister(&trace_priv->misc_dev);
+ iounmap(trace_priv->area);
+ kfree(trace_priv);
+ }
+
+ return rv;
+}
+
+static struct platform_driver trace_driver = {
+ .probe = trace_probe,
+ .remove = trace_remove,
+ .driver = {
+ .name = "db8500-modem-trace",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int trace_init(void)
+{
+ platform_driver_register(&trace_driver);
+ return 0;
+}
+static void trace_exit(void)
+{
+ platform_driver_unregister(&trace_driver);
+}
+module_init(trace_init);
+module_exit(trace_exit);
+
+MODULE_AUTHOR("ST-Ericsson");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/dbx500-mloader.c b/drivers/misc/dbx500-mloader.c
new file mode 100644
index 00000000000..408d1a1a29e
--- /dev/null
+++ b/drivers/misc/dbx500-mloader.c
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Author: Ludovic Barre <ludovic.barre@stericsson.com> for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/firmware.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/uaccess.h>
+#include <linux/mman.h>
+#include <linux/io.h>
+
+#include <mach/mloader-dbx500.h>
+#include <linux/mloader.h>
+#include <mach/hardware.h>
+
+#define DEVICE_NAME "dbx500_mloader_fw"
+
+struct mloader_priv {
+ struct platform_device *pdev;
+ struct dbx500_mloader_pdata *pdata;
+ struct miscdevice misc_dev;
+ u32 aeras_size;
+ void __iomem *uid_base;
+ u8 size;
+};
+
+static struct mloader_priv *mloader_priv;
+
+static int mloader_fw_send(struct dbx500_ml_fw *fw_info)
+{
+ const struct firmware *fw;
+ unsigned long size;
+ unsigned long phys_start;
+ void *fw_data;
+ void *vaddr;
+ void __iomem *ioaddr;
+ int ret;
+
+ ret = request_firmware(&fw, fw_info->name, &mloader_priv->pdev->dev);
+ if (ret) {
+ dev_err(&mloader_priv->pdev->dev, "request firmware failed\n");
+ goto out;
+ }
+
+ if (fw->size > (fw_info->area->size - fw_info->offset)) {
+ dev_err(&mloader_priv->pdev->dev,
+ "fw:%s is too big for:%s\n",
+ fw_info->name, fw_info->area->name);
+ ret = -EINVAL;
+ goto err_fw;
+ }
+
+ size = PAGE_ALIGN(fw->size);
+ phys_start = fw_info->area->start + fw_info->offset;
+ phys_start &= PAGE_MASK;
+ ioaddr = ioremap(phys_start, size);
+ if (!ioaddr) {
+ dev_err(&mloader_priv->pdev->dev,
+ "failed remap memory region.\n");
+ ret = -EINVAL;
+ goto err_fw;
+ }
+
+ vaddr = ioaddr + (fw_info->offset & ~PAGE_MASK);
+ fw_data = (void *)fw->data;
+ memcpy_toio(vaddr, fw_data, fw->size);
+ iounmap(ioaddr);
+
+err_fw:
+ release_firmware(fw);
+out:
+ return ret;
+}
+
+static int mloader_fw_upload(void)
+{
+ int i, ret;
+ struct dbx500_mloader_pdata *pdata = mloader_priv->pdata;
+
+ for (i = 0; i < pdata->nr_fws; i++) {
+ ret = mloader_fw_send(&pdata->fws[i]);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+err:
+ dev_err(&mloader_priv->pdev->dev,
+ "Failed to upload %s firmware", pdata->fws[i].name);
+ return ret;
+}
+
+static int mloader_fw_mmapdump(struct file *file, struct vm_area_struct *vma)
+{
+ int i;
+ unsigned long dump_size = 0;
+ unsigned long vma_start = vma->vm_start;
+
+ if (vma->vm_flags & VM_WRITE)
+ return -EPERM;
+
+ for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++)
+ dump_size += mloader_priv->pdata->areas[i].size;
+
+ if ((vma->vm_end - vma->vm_start) < dump_size)
+ return -EINVAL;
+
+ for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) {
+ if (remap_pfn_range(vma,
+ vma_start,
+ mloader_priv->pdata->areas[i].start >> PAGE_SHIFT,
+ mloader_priv->pdata->areas[i].size,
+ vma->vm_page_prot))
+ return -EAGAIN;
+ vma_start += mloader_priv->pdata->areas[i].size;
+ }
+ return 0;
+}
+
+static void mloader_fw_dumpinfo(struct dump_image *images)
+{
+ u32 offset = 0;
+ int i;
+
+ for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) {
+ strncpy(images[i].name,
+ mloader_priv->pdata->areas[i].name, MAX_NAME);
+ images[i].name[MAX_NAME-1] = 0;
+ images[i].offset = offset;
+ images[i].size = mloader_priv->pdata->areas[i].size;
+ offset += mloader_priv->pdata->areas[i].size;
+ }
+}
+
+static long mloader_fw_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ long ret = 0;
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+ case ML_UPLOAD:
+ ret = mloader_fw_upload();
+ break;
+ case ML_GET_NBIMAGES:
+ ret = put_user(mloader_priv->pdata->nr_areas,
+ (unsigned long __user *)argp);
+ break;
+ case ML_GET_DUMPINFO: {
+ struct dump_image *dump_images;
+ dump_images = kzalloc(mloader_priv->pdata->nr_areas
+ * sizeof(struct dump_image), GFP_ATOMIC);
+ mloader_fw_dumpinfo(dump_images);
+ ret = copy_to_user(argp, (void *) dump_images,
+ mloader_priv->pdata->nr_areas
+ * sizeof(struct dump_image)) ? -EFAULT : 0;
+ kfree(dump_images);
+ break;
+ }
+ case ML_GET_FUSEINFO: {
+ ret = copy_to_user(argp, (void *) mloader_priv->uid_base,
+ mloader_priv->size) ? -EFAULT : 0;
+ break;
+ }
+ default:
+ ret = -EPERM;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct file_operations modem_fw_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = mloader_fw_ioctl,
+ .mmap = mloader_fw_mmapdump,
+};
+
+static int __devinit mloader_fw_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ int i;
+ struct resource *res = NULL;
+
+ mloader_priv = kzalloc(sizeof(*mloader_priv), GFP_ATOMIC);
+ if (!mloader_priv) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ mloader_priv->pdev = pdev;
+ mloader_priv->pdata = pdev->dev.platform_data;
+
+ mloader_priv->misc_dev.minor = MISC_DYNAMIC_MINOR;
+ mloader_priv->misc_dev.name = DEVICE_NAME;
+ mloader_priv->misc_dev.fops = &modem_fw_fops;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mloader_priv->size = resource_size(res);
+ mloader_priv->uid_base = ioremap(res->start, mloader_priv->size);
+
+ if (!mloader_priv->uid_base) {
+ ret = -ENOMEM;
+ goto err_free_priv;
+ }
+
+ ret = misc_register(&mloader_priv->misc_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "can't misc_register\n");
+ goto err_free_priv;
+ }
+
+ dev_info(&mloader_priv->pdev->dev, "mloader device register\n");
+
+ for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) {
+ dev_dbg(&mloader_priv->pdev->dev,
+ "Area:%d (name:%s start:%x size:%x)\n",
+ i, mloader_priv->pdata->areas[i].name,
+ mloader_priv->pdata->areas[i].start,
+ mloader_priv->pdata->areas[i].size);
+ }
+
+ for (i = 0 ; i < mloader_priv->pdata->nr_fws ; i++) {
+ dev_dbg(&mloader_priv->pdev->dev,
+ "Firmware:%d (name:%s offset:%x "
+ "area_name:%s area_start:%x area_size:%x)\n",
+ i, mloader_priv->pdata->fws[i].name,
+ mloader_priv->pdata->fws[i].offset,
+ mloader_priv->pdata->fws[i].area->name,
+ mloader_priv->pdata->fws[i].area->start,
+ mloader_priv->pdata->fws[i].area->size);
+ }
+
+ return ret;
+
+err_free_priv:
+ kfree(mloader_priv);
+out:
+ return ret;
+}
+
+static int __devexit mloader_fw_remove(struct platform_device *pdev)
+{
+ int err;
+
+ err = misc_register(&mloader_priv->misc_dev);
+ if (err < 0)
+ dev_err(&pdev->dev, "can't misc_deregister, %d\n", err);
+
+ kfree(mloader_priv);
+
+ return err;
+}
+
+static struct platform_driver mloader_fw_driver = {
+ .driver.name = DEVICE_NAME,
+ .driver.owner = THIS_MODULE,
+ .probe = mloader_fw_probe,
+ .remove = __devexit_p(mloader_fw_remove),
+};
+
+static int __init mloader_fw_init(void)
+{
+ return platform_driver_register(&mloader_fw_driver);
+}
+
+static void __exit mloader_fw_exit(void)
+{
+ kfree(mloader_priv);
+ platform_driver_unregister(&mloader_fw_driver);
+}
+
+module_init(mloader_fw_init);
+module_exit(mloader_fw_exit);
+MODULE_DESCRIPTION("ST-Ericsson modem loader firmware");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Ludovic Barre <ludovic.barre@stericsson.com>");
diff --git a/drivers/misc/mbox.c b/drivers/misc/mbox.c
new file mode 100644
index 00000000000..d884496fa4c
--- /dev/null
+++ b/drivers/misc/mbox.c
@@ -0,0 +1,867 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Author: Stefan Nilsson <stefan.xk.nilsson@stericsson.com> for ST-Ericsson.
+ * Author: Martin Persson <martin.persson@stericsson.com> for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2.
+ */
+
+/*
+ * Mailbox nomenclature:
+ *
+ * APE MODEM
+ * mbox pairX
+ * ..........................
+ * . .
+ * . peer .
+ * . send ---- .
+ * . --> | | .
+ * . | | .
+ * . ---- .
+ * . .
+ * . local .
+ * . rec ---- .
+ * . | | <-- .
+ * . | | .
+ * . ---- .
+ * .........................
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/hrtimer.h>
+#include <linux/mfd/dbx500-prcmu.h>
+#include <linux/mfd/db5500-prcmu.h>
+#include <mach/mbox-db5500.h>
+#include <mach/reboot_reasons.h>
+
+#define MBOX_NAME "mbox"
+
+#define MBOX_FIFO_DATA 0x000
+#define MBOX_FIFO_ADD 0x004
+#define MBOX_FIFO_REMOVE 0x008
+#define MBOX_FIFO_THRES_FREE 0x00C
+#define MBOX_FIFO_THRES_OCCUP 0x010
+#define MBOX_FIFO_STATUS 0x014
+
+#define MBOX_DISABLE_IRQ 0x4
+#define MBOX_ENABLE_IRQ 0x0
+#define MBOX_LATCH 1
+
+struct mbox_device_info {
+ struct mbox *mbox;
+ struct workqueue_struct *mbox_modem_rel_wq;
+ struct work_struct mbox_modem_rel;
+ struct completion mod_req_ack_work;
+ atomic_t ape_state;
+ atomic_t mod_req;
+ atomic_t mod_reset;
+};
+
+/* Global list of all mailboxes */
+struct hrtimer ape_timer;
+struct hrtimer modem_timer;
+static DEFINE_MUTEX(modem_state_mutex);
+static struct list_head mboxs = LIST_HEAD_INIT(mboxs);
+static struct mbox_device_info *mb;
+
+static enum hrtimer_restart mbox_ape_callback(struct hrtimer *hrtimer)
+{
+ queue_work(mb->mbox_modem_rel_wq, &mb->mbox_modem_rel);
+
+ return HRTIMER_NORESTART;
+}
+
+static enum hrtimer_restart mbox_mod_callback(struct hrtimer *hrtimer)
+{
+ atomic_set(&mb->ape_state, 0);
+ return HRTIMER_NORESTART;
+}
+
+static void mbox_modem_rel_work(struct work_struct *work)
+{
+ mutex_lock(&modem_state_mutex);
+ prcmu_modem_rel();
+ atomic_set(&mb->mod_req, 0);
+ mutex_unlock(&modem_state_mutex);
+}
+
+static void mbox_modem_req(void)
+{
+ mutex_lock(&modem_state_mutex);
+ if (!db5500_prcmu_is_modem_requested()) {
+ prcmu_modem_req();
+ /* TODO: optimize this timeout */
+ if (!wait_for_completion_timeout(&mb->mod_req_ack_work,
+ msecs_to_jiffies(2000)))
+ printk(KERN_ERR "mbox:modem_req_ack timedout(2sec)\n");
+ }
+ atomic_set(&mb->mod_req, 1);
+ mutex_unlock(&modem_state_mutex);
+}
+
+static struct mbox *get_mbox_with_id(u8 id)
+{
+ u8 i;
+ struct list_head *pos = &mboxs;
+ for (i = 0; i <= id; i++)
+ pos = pos->next;
+
+ return (struct mbox *) list_entry(pos, struct mbox, list);
+}
+
+int mbox_send(struct mbox *mbox, u32 mbox_msg, bool block)
+{
+ int res = 0;
+ unsigned long flag;
+
+ if (atomic_read(&mb->mod_reset)) {
+ dev_err(&mbox->pdev->dev,
+ "mbox_send called after modem reset\n");
+ return -EINVAL;
+ }
+ dev_dbg(&(mbox->pdev->dev),
+ "About to buffer 0x%X to mailbox 0x%X."
+ " ri = %d, wi = %d\n",
+ mbox_msg, (u32)mbox, mbox->read_index,
+ mbox->write_index);
+
+ /* Request for modem */
+ if (!db5500_prcmu_is_modem_requested())
+ mbox_modem_req();
+
+ spin_lock_irqsave(&mbox->lock, flag);
+ /* Check if write buffer is full */
+ while (((mbox->write_index + 1) % MBOX_BUF_SIZE) == mbox->read_index) {
+ if (!block) {
+ dev_dbg(&(mbox->pdev->dev),
+ "Buffer full in non-blocking call! "
+ "Returning -ENOMEM!\n");
+ res = -ENOMEM;
+ goto exit;
+ }
+ spin_unlock_irqrestore(&mbox->lock, flag);
+ dev_dbg(&(mbox->pdev->dev),
+ "Buffer full in blocking call! Sleeping...\n");
+ mbox->client_blocked = 1;
+ wait_for_completion(&mbox->buffer_available);
+ dev_dbg(&(mbox->pdev->dev),
+ "Blocking send was woken up! Trying again...\n");
+ spin_lock_irqsave(&mbox->lock, flag);
+ }
+
+ mbox->buffer[mbox->write_index] = mbox_msg;
+ mbox->write_index = (mbox->write_index + 1) % MBOX_BUF_SIZE;
+
+ /*
+ * Indicate that we want an IRQ as soon as there is a slot
+ * in the FIFO
+ */
+ if (atomic_read(&mb->mod_reset)) {
+ dev_err(&mbox->pdev->dev,
+ "modem is in reset state, cannot proceed\n");
+ res = -EINVAL;
+ goto exit;
+ }
+ writel(MBOX_ENABLE_IRQ, mbox->virtbase_peer + MBOX_FIFO_THRES_FREE);
+
+exit:
+ spin_unlock_irqrestore(&mbox->lock, flag);
+ return res;
+}
+EXPORT_SYMBOL(mbox_send);
+
+#if defined(CONFIG_DEBUG_FS)
+/*
+ * Expected input: <value> <nbr sends>
+ * Example: "echo 0xdeadbeef 4 > mbox-node" sends 0xdeadbeef 4 times
+ */
+static ssize_t mbox_write_fifo(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ unsigned long mbox_mess;
+ unsigned long nbr_sends;
+ unsigned long i;
+ char int_buf[16];
+ char *token;
+ char *val;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mbox *mbox = platform_get_drvdata(pdev);
+
+ strncpy((char *) &int_buf, buf, sizeof(int_buf));
+ token = (char *) &int_buf;
+
+ /* Parse message */
+ val = strsep(&token, " ");
+ if ((val == NULL) || (strict_strtoul(val, 16, &mbox_mess) != 0))
+ mbox_mess = 0xDEADBEEF;
+
+ val = strsep(&token, " ");
+ if ((val == NULL) || (strict_strtoul(val, 10, &nbr_sends) != 0))
+ nbr_sends = 1;
+
+ dev_dbg(dev, "Will write 0x%lX %ld times using data struct at 0x%X\n",
+ mbox_mess, nbr_sends, (u32) mbox);
+
+ for (i = 0; i < nbr_sends; i++)
+ mbox_send(mbox, mbox_mess, true);
+
+ return count;
+}
+
+static ssize_t mbox_read_fifo(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int mbox_value;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mbox *mbox = platform_get_drvdata(pdev);
+
+ if (atomic_read(&mb->mod_reset)) {
+ dev_err(&mbox->pdev->dev, "modem crashed, returning\n");
+ return 0;
+ }
+ if ((readl(mbox->virtbase_local + MBOX_FIFO_STATUS) & 0x7) <= 0)
+ return sprintf(buf, "Mailbox is empty\n");
+
+ mbox_value = readl(mbox->virtbase_local + MBOX_FIFO_DATA);
+ writel(MBOX_LATCH, (mbox->virtbase_local + MBOX_FIFO_REMOVE));
+
+ return sprintf(buf, "0x%X\n", mbox_value);
+}
+
+static DEVICE_ATTR(fifo, S_IWUGO | S_IRUGO, mbox_read_fifo, mbox_write_fifo);
+
+static int mbox_show(struct seq_file *s, void *data)
+{
+ struct list_head *pos;
+ u8 mbox_index = 0;
+
+ list_for_each(pos, &mboxs) {
+ struct mbox *m =
+ (struct mbox *) list_entry(pos, struct mbox, list);
+ if (m == NULL) {
+ seq_printf(s,
+ "Unable to retrieve mailbox %d\n",
+ mbox_index);
+ continue;
+ }
+
+ spin_lock(&m->lock);
+ if ((m->virtbase_peer == NULL) || (m->virtbase_local == NULL)) {
+ seq_printf(s, "MAILBOX %d not setup or corrupt\n",
+ mbox_index);
+ spin_unlock(&m->lock);
+ continue;
+ }
+
+ if (atomic_read(&mb->mod_reset)) {
+ dev_err(&m->pdev->dev, "modem crashed, returning\n");
+ spin_unlock(&m->lock);
+ return 0;
+ }
+ seq_printf(s,
+ "===========================\n"
+ " MAILBOX %d\n"
+ " PEER MAILBOX DUMP\n"
+ "---------------------------\n"
+ "FIFO: 0x%X (%d)\n"
+ "Free Threshold: 0x%.2X (%d)\n"
+ "Occupied Threshold: 0x%.2X (%d)\n"
+ "Status: 0x%.2X (%d)\n"
+ " Free spaces (ot): %d (%d)\n"
+ " Occup spaces (ot): %d (%d)\n"
+ "===========================\n"
+ " LOCAL MAILBOX DUMP\n"
+ "---------------------------\n"
+ "FIFO: 0x%.X (%d)\n"
+ "Free Threshold: 0x%.2X (%d)\n"
+ "Occupied Threshold: 0x%.2X (%d)\n"
+ "Status: 0x%.2X (%d)\n"
+ " Free spaces (ot): %d (%d)\n"
+ " Occup spaces (ot): %d (%d)\n"
+ "===========================\n"
+ "write_index: %d\n"
+ "read_index : %d\n"
+ "===========================\n"
+ "\n",
+ mbox_index,
+ readl(m->virtbase_peer + MBOX_FIFO_DATA),
+ readl(m->virtbase_peer + MBOX_FIFO_DATA),
+ readl(m->virtbase_peer + MBOX_FIFO_THRES_FREE),
+ readl(m->virtbase_peer + MBOX_FIFO_THRES_FREE),
+ readl(m->virtbase_peer + MBOX_FIFO_THRES_OCCUP),
+ readl(m->virtbase_peer + MBOX_FIFO_THRES_OCCUP),
+ readl(m->virtbase_peer + MBOX_FIFO_STATUS),
+ readl(m->virtbase_peer + MBOX_FIFO_STATUS),
+ (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 4) & 0x7,
+ (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 7) & 0x1,
+ (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 0) & 0x7,
+ (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 3) & 0x1,
+ readl(m->virtbase_local + MBOX_FIFO_DATA),
+ readl(m->virtbase_local + MBOX_FIFO_DATA),
+ readl(m->virtbase_local + MBOX_FIFO_THRES_FREE),
+ readl(m->virtbase_local + MBOX_FIFO_THRES_FREE),
+ readl(m->virtbase_local + MBOX_FIFO_THRES_OCCUP),
+ readl(m->virtbase_local + MBOX_FIFO_THRES_OCCUP),
+ readl(m->virtbase_local + MBOX_FIFO_STATUS),
+ readl(m->virtbase_local + MBOX_FIFO_STATUS),
+ (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 4) & 0x7,
+ (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 7) & 0x1,
+ (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 0) & 0x7,
+ (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 3) & 0x1,
+ m->write_index, m->read_index);
+ mbox_index++;
+ spin_unlock(&m->lock);
+ }
+
+ return 0;
+}
+
+static int mbox_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mbox_show, NULL);
+}
+
+static const struct file_operations mbox_operations = {
+ .owner = THIS_MODULE,
+ .open = mbox_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+#endif
+
+static irqreturn_t mbox_irq(int irq, void *arg)
+{
+ u32 mbox_value;
+ int nbr_occup;
+ int nbr_free;
+ struct mbox *mbox = (struct mbox *) arg;
+
+ if (atomic_read(&mb->mod_reset)) {
+ dev_err(&mbox->pdev->dev, "modem in reset state\n");
+ return IRQ_HANDLED;
+ }
+ spin_lock(&mbox->lock);
+
+ dev_dbg(&(mbox->pdev->dev),
+ "mbox IRQ [%d] received. ri = %d, wi = %d\n",
+ irq, mbox->read_index, mbox->write_index);
+
+ /*
+ * Check if we have any outgoing messages, and if there is space for
+ * them in the FIFO.
+ */
+ if (mbox->read_index != mbox->write_index) {
+ /*
+ * Check by reading FREE for LOCAL since that indicates
+ * OCCUP for PEER
+ */
+ nbr_free = (readl(mbox->virtbase_local + MBOX_FIFO_STATUS)
+ >> 4) & 0x7;
+ dev_dbg(&(mbox->pdev->dev),
+ "Status indicates %d empty spaces in the FIFO!\n",
+ nbr_free);
+
+ while ((nbr_free > 0) &&
+ (mbox->read_index != mbox->write_index)) {
+ if (atomic_read(&mb->mod_reset)) {
+ dev_err(&mbox->pdev->dev,
+ "modem in reset state\n");
+ goto exit;
+ }
+ /* Write the message and latch it into the FIFO */
+ writel(mbox->buffer[mbox->read_index],
+ (mbox->virtbase_peer + MBOX_FIFO_DATA));
+ writel(MBOX_LATCH,
+ (mbox->virtbase_peer + MBOX_FIFO_ADD));
+ dev_dbg(&(mbox->pdev->dev),
+ "Wrote message 0x%X to addr 0x%X\n",
+ mbox->buffer[mbox->read_index],
+ (u32) (mbox->virtbase_peer + MBOX_FIFO_DATA));
+
+ nbr_free--;
+ mbox->read_index =
+ (mbox->read_index + 1) % MBOX_BUF_SIZE;
+ }
+
+ if (atomic_read(&mb->mod_reset)) {
+ dev_err(&mbox->pdev->dev, "modem in reset state\n");
+ goto exit;
+ }
+ /*
+ * Check if we still want IRQ:s when there is free
+ * space to send
+ */
+ if (mbox->read_index != mbox->write_index) {
+ dev_dbg(&(mbox->pdev->dev),
+ "Still have messages to send, but FIFO full. "
+ "Request IRQ again!\n");
+ writel(MBOX_ENABLE_IRQ,
+ mbox->virtbase_peer + MBOX_FIFO_THRES_FREE);
+ } else {
+ dev_dbg(&(mbox->pdev->dev),
+ "No more messages to send. "
+ "Do not request IRQ again!\n");
+ writel(MBOX_DISABLE_IRQ,
+ mbox->virtbase_peer + MBOX_FIFO_THRES_FREE);
+ }
+
+ /*
+ * Check if we can signal any blocked clients that it is OK to
+ * start buffering again
+ */
+ if (mbox->client_blocked &&
+ (((mbox->write_index + 1) % MBOX_BUF_SIZE)
+ != mbox->read_index)) {
+ dev_dbg(&(mbox->pdev->dev),
+ "Waking up blocked client\n");
+ complete(&mbox->buffer_available);
+ mbox->client_blocked = 0;
+ }
+ }
+
+ /* Start timer and on timer expiry call modem_rel */
+ hrtimer_start(&ape_timer, ktime_set(0, 10*NSEC_PER_MSEC),
+ HRTIMER_MODE_REL);
+
+ if (atomic_read(&mb->mod_reset)) {
+ dev_err(&mbox->pdev->dev, "modem in reset state\n");
+ goto exit;
+ }
+ /* Check if we have any incoming messages */
+ nbr_occup = readl(mbox->virtbase_local + MBOX_FIFO_STATUS) & 0x7;
+ if (nbr_occup == 0)
+ goto exit;
+
+redo:
+ if (mbox->cb == NULL) {
+ dev_dbg(&(mbox->pdev->dev), "No receive callback registered, "
+ "leaving %d incoming messages in fifo!\n", nbr_occup);
+ goto exit;
+ }
+ atomic_set(&mb->ape_state, 1);
+
+ if (atomic_read(&mb->mod_reset)) {
+ dev_err(&mbox->pdev->dev, "modem in reset state\n");
+ goto exit;
+ }
+ /* Read and acknowledge the message */
+ mbox_value = readl(mbox->virtbase_local + MBOX_FIFO_DATA);
+ writel(MBOX_LATCH, (mbox->virtbase_local + MBOX_FIFO_REMOVE));
+
+ /* Notify consumer of new mailbox message */
+ dev_dbg(&(mbox->pdev->dev), "Calling callback for message 0x%X!\n",
+ mbox_value);
+ mbox->cb(mbox_value, mbox->client_data);
+
+ nbr_occup = readl(mbox->virtbase_local + MBOX_FIFO_STATUS) & 0x7;
+
+ if (nbr_occup > 0)
+ goto redo;
+
+ /* Start a timer and timer expiry will be the criteria for sleep */
+ hrtimer_start(&modem_timer, ktime_set(0, 100*MSEC_PER_SEC),
+ HRTIMER_MODE_REL);
+exit:
+ dev_dbg(&(mbox->pdev->dev), "Exit mbox IRQ. ri = %d, wi = %d\n",
+ mbox->read_index, mbox->write_index);
+ spin_unlock(&mbox->lock);
+
+ return IRQ_HANDLED;
+}
+
+static void mbox_shutdown(struct mbox *mbox)
+{
+ if (!mbox->allocated)
+ return;
+#if defined(CONFIG_DEBUG_FS)
+ debugfs_remove(mbox->dentry);
+ device_remove_file(&mbox->pdev->dev, &dev_attr_fifo);
+#endif
+ /* TODO: Need to check if we can write after modem reset */
+ if (!atomic_read(&mb->mod_reset)) {
+ writel(MBOX_DISABLE_IRQ, mbox->virtbase_local +
+ MBOX_FIFO_THRES_OCCUP);
+ writel(MBOX_DISABLE_IRQ, mbox->virtbase_peer +
+ MBOX_FIFO_THRES_FREE);
+ }
+ free_irq(mbox->irq, (void *)mbox);
+ mbox->client_blocked = 0;
+ iounmap(mbox->virtbase_local);
+ iounmap(mbox->virtbase_peer);
+ mbox->cb = NULL;
+ mbox->client_data = NULL;
+ mbox->allocated = false;
+}
+
+/** mbox_state_reset - Reset the mailbox state machine
+ *
+ * This function is called on receiving modem reset interrupt. Reset all
+ * the mailbox state machine, disable irq, cancel timers, shutdown the
+ * mailboxs and re-enable irq's.
+ */
+void mbox_state_reset(void)
+{
+ struct mbox *mbox = mb->mbox;
+
+ /* Common for all mailbox */
+ atomic_set(&mb->mod_reset, 1);
+
+ /* Disable IRQ */
+ disable_irq_nosync(IRQ_DB5500_PRCMU_AC_WAKE_ACK);
+
+ /* Cancel sleep_req timers */
+ hrtimer_cancel(&modem_timer);
+ hrtimer_cancel(&ape_timer);
+
+ /* specific to each mailbox */
+ list_for_each_entry(mbox, &mboxs, list) {
+ mbox_shutdown(mbox);
+ }
+
+ /* Reset mailbox state machine */
+ atomic_set(&mb->mod_req, 0);
+ atomic_set(&mb->ape_state, 0);
+
+ /* Enable irq */
+ enable_irq(IRQ_DB5500_PRCMU_AC_WAKE_ACK);
+}
+
+
+/* Setup is executed once for each mbox pair */
+struct mbox *mbox_setup(u8 mbox_id, mbox_recv_cb_t *mbox_cb, void *priv)
+{
+ struct resource *resource;
+ int res;
+ struct mbox *mbox;
+
+ /*
+ * set mod_reset flag to '0', clients calling this APE should make sure
+ * that modem is rebooted after MSR. Mailbox doesnt have any means of
+ * knowing the boot status of modem.
+ */
+ atomic_set(&mb->mod_reset, 0);
+
+ mbox = get_mbox_with_id(mbox_id);
+ if (mbox == NULL) {
+ dev_err(&(mbox->pdev->dev), "Incorrect mailbox id: %d!\n",
+ mbox_id);
+ goto exit;
+ }
+
+ /*
+ * Check if mailbox has been allocated to someone else,
+ * otherwise allocate it
+ */
+ if (mbox->allocated) {
+ dev_err(&(mbox->pdev->dev), "Mailbox number %d is busy!\n",
+ mbox_id);
+ mbox = NULL;
+ goto exit;
+ }
+ mbox->allocated = true;
+
+ dev_dbg(&(mbox->pdev->dev), "Initiating mailbox number %d: 0x%X...\n",
+ mbox_id, (u32)mbox);
+
+ mbox->client_data = priv;
+ mbox->cb = mbox_cb;
+
+ /* Get addr for peer mailbox and ioremap it */
+ resource = platform_get_resource_byname(mbox->pdev,
+ IORESOURCE_MEM,
+ "mbox_peer");
+ if (resource == NULL) {
+ dev_err(&(mbox->pdev->dev),
+ "Unable to retrieve mbox peer resource\n");
+ mbox = NULL;
+ goto free_mbox;
+ }
+ dev_dbg(&(mbox->pdev->dev),
+ "Resource name: %s start: 0x%X, end: 0x%X\n",
+ resource->name, resource->start, resource->end);
+ mbox->virtbase_peer = ioremap(resource->start, resource_size(resource));
+ if (!mbox->virtbase_peer) {
+ dev_err(&(mbox->pdev->dev), "Unable to ioremap peer mbox\n");
+ mbox = NULL;
+ goto free_mbox;
+ }
+ dev_dbg(&(mbox->pdev->dev),
+ "ioremapped peer physical: (0x%X-0x%X) to virtual: 0x%X\n",
+ resource->start, resource->end, (u32) mbox->virtbase_peer);
+
+ /* Get addr for local mailbox and ioremap it */
+ resource = platform_get_resource_byname(mbox->pdev,
+ IORESOURCE_MEM,
+ "mbox_local");
+ if (resource == NULL) {
+ dev_err(&(mbox->pdev->dev),
+ "Unable to retrieve mbox local resource\n");
+ mbox = NULL;
+ goto free_map;
+ }
+ dev_dbg(&(mbox->pdev->dev),
+ "Resource name: %s start: 0x%X, end: 0x%X\n",
+ resource->name, resource->start, resource->end);
+ mbox->virtbase_local = ioremap(resource->start, resource_size(resource));
+ if (!mbox->virtbase_local) {
+ dev_err(&(mbox->pdev->dev), "Unable to ioremap local mbox\n");
+ mbox = NULL;
+ goto free_map;
+ }
+ dev_dbg(&(mbox->pdev->dev),
+ "ioremapped local physical: (0x%X-0x%X) to virtual: 0x%X\n",
+ resource->start, resource->end, (u32) mbox->virtbase_peer);
+
+ init_completion(&mbox->buffer_available);
+ mbox->client_blocked = 0;
+
+ /* Get IRQ for mailbox and allocate it */
+ mbox->irq = platform_get_irq_byname(mbox->pdev, "mbox_irq");
+ if (mbox->irq < 0) {
+ dev_err(&(mbox->pdev->dev),
+ "Unable to retrieve mbox irq resource\n");
+ mbox = NULL;
+ goto free_map1;
+ }
+
+ dev_dbg(&(mbox->pdev->dev), "Allocating irq %d...\n", mbox->irq);
+ res = request_threaded_irq(mbox->irq, NULL, mbox_irq,
+ IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ mbox->name, (void *) mbox);
+ if (res < 0) {
+ dev_err(&(mbox->pdev->dev),
+ "Unable to allocate mbox irq %d\n", mbox->irq);
+ mbox = NULL;
+ goto exit;
+ }
+
+ /* check if modem has reset */
+ if (atomic_read(&mb->mod_reset)) {
+ dev_err(&mbox->pdev->dev,
+ "modem is in reset state, cannot proceed\n");
+ mbox = NULL;
+ goto free_irq;
+ }
+ /* Set up mailbox to not launch IRQ on free space in mailbox */
+ writel(MBOX_DISABLE_IRQ, mbox->virtbase_peer + MBOX_FIFO_THRES_FREE);
+
+ /*
+ * Set up mailbox to launch IRQ on new message if we have
+ * a callback set. If not, do not raise IRQ, but keep message
+ * in FIFO for manual retrieval
+ */
+ if (mbox_cb != NULL)
+ writel(MBOX_ENABLE_IRQ,
+ mbox->virtbase_local + MBOX_FIFO_THRES_OCCUP);
+ else
+ writel(MBOX_DISABLE_IRQ,
+ mbox->virtbase_local + MBOX_FIFO_THRES_OCCUP);
+
+#if defined(CONFIG_DEBUG_FS)
+ res = device_create_file(&(mbox->pdev->dev), &dev_attr_fifo);
+ if (res != 0)
+ dev_warn(&(mbox->pdev->dev),
+ "Unable to create mbox sysfs entry");
+
+ mbox->dentry = debugfs_create_file("mbox", S_IFREG | S_IRUGO, NULL,
+ NULL, &mbox_operations);
+#endif
+ dev_info(&(mbox->pdev->dev),
+ "Mailbox driver with index %d initiated!\n", mbox_id);
+
+ return mbox;
+free_irq:
+ free_irq(mbox->irq, (void *)mbox);
+free_map1:
+ iounmap(mbox->virtbase_local);
+free_map:
+ iounmap(mbox->virtbase_peer);
+free_mbox:
+ mbox->client_data = NULL;
+ mbox->cb = NULL;
+exit:
+ return mbox;
+}
+EXPORT_SYMBOL(mbox_setup);
+
+static irqreturn_t mbox_prcmu_mod_req_ack_handler(int irq, void *data)
+{
+ complete(&mb->mod_req_ack_work);
+ return IRQ_HANDLED;
+}
+
+int __init mbox_probe(struct platform_device *pdev)
+{
+ struct mbox *mbox;
+ int res = 0;
+ dev_dbg(&(pdev->dev), "Probing mailbox (pdev = 0x%X)...\n", (u32) pdev);
+
+ mbox = kzalloc(sizeof(struct mbox), GFP_KERNEL);
+ if (mbox == NULL) {
+ dev_err(&pdev->dev,
+ "Could not allocate memory for struct mbox\n");
+ return -ENOMEM;
+ }
+
+ mbox->pdev = pdev;
+ mbox->write_index = 0;
+ mbox->read_index = 0;
+
+ INIT_LIST_HEAD(&(mbox->list));
+ list_add_tail(&(mbox->list), &mboxs);
+
+ sprintf(mbox->name, "%s", MBOX_NAME);
+ spin_lock_init(&mbox->lock);
+
+ platform_set_drvdata(pdev, mbox);
+ mb->mbox = mbox;
+ dev_info(&(pdev->dev), "Mailbox driver loaded\n");
+
+ return res;
+}
+
+static int __exit mbox_remove(struct platform_device *pdev)
+{
+ struct mbox *mbox = platform_get_drvdata(pdev);
+
+ hrtimer_cancel(&ape_timer);
+ hrtimer_cancel(&modem_timer);
+ mbox_shutdown(mbox);
+ list_del(&mbox->list);
+ kfree(mbox);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+int mbox_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mbox *mbox = platform_get_drvdata(pdev);
+
+ /*
+ * Nothing to be done for now, once APE-Modem power management is
+ * in place communication will have to be stopped.
+ */
+
+ list_for_each_entry(mbox, &mboxs, list) {
+ if (mbox->client_blocked)
+ return -EBUSY;
+ }
+ dev_dbg(dev, "APE_STATE = %d\n", atomic_read(&mb->ape_state));
+ dev_dbg(dev, "MODEM_STATE = %d\n", db5500_prcmu_is_modem_requested());
+ if (atomic_read(&mb->ape_state) || db5500_prcmu_is_modem_requested() ||
+ atomic_read(&mb->mod_req))
+ return -EBUSY;
+ return 0;
+}
+
+int mbox_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mbox *mbox = platform_get_drvdata(pdev);
+
+ /*
+ * Nothing to be done for now, once APE-Modem power management is
+ * in place communication will have to be resumed.
+ */
+
+ return 0;
+}
+
+static const struct dev_pm_ops mbox_dev_pm_ops = {
+ .suspend_noirq = mbox_suspend,
+ .resume_noirq = mbox_resume,
+};
+#endif
+
+static struct platform_driver mbox_driver = {
+ .remove = __exit_p(mbox_remove),
+ .driver = {
+ .name = MBOX_NAME,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &mbox_dev_pm_ops,
+#endif
+ },
+};
+
+static int __init mbox_init(void)
+{
+ struct mbox_device_info *mb_di;
+ int err;
+
+ mb_di = kzalloc(sizeof(struct mbox_device_info), GFP_KERNEL);
+ if (mb_di == NULL) {
+ printk(KERN_ERR
+ "mbox:Could not allocate memory for struct mbox_device_info\n");
+ return -ENOMEM;
+ }
+
+ mb_di->mbox_modem_rel_wq = create_singlethread_workqueue(
+ "mbox_modem_rel");
+ if (!mb_di->mbox_modem_rel_wq) {
+ printk(KERN_ERR "mbox:failed to create work queue\n");
+ err = -ENOMEM;
+ goto free_mem;
+ }
+
+ INIT_WORK(&mb_di->mbox_modem_rel, mbox_modem_rel_work);
+
+ hrtimer_init(&ape_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ ape_timer.function = mbox_ape_callback;
+ hrtimer_init(&modem_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ modem_timer.function = mbox_mod_callback;
+
+ atomic_set(&mb_di->ape_state, 0);
+ atomic_set(&mb_di->mod_req, 0);
+ atomic_set(&mb_di->mod_reset, 0);
+
+ err = request_irq(IRQ_DB5500_PRCMU_AC_WAKE_ACK,
+ mbox_prcmu_mod_req_ack_handler,
+ IRQF_NO_SUSPEND, "mod_req_ack", NULL);
+ if (err < 0) {
+ printk(KERN_ERR "mbox:Failed alloc IRQ_PRCMU_CA_SLEEP.\n");
+ goto free_irq;
+ }
+
+ init_completion(&mb_di->mod_req_ack_work);
+ mb = mb_di;
+ return platform_driver_probe(&mbox_driver, mbox_probe);
+free_irq:
+ destroy_workqueue(mb_di->mbox_modem_rel_wq);
+free_mem:
+ kfree(mb_di);
+ return err;
+}
+
+module_init(mbox_init);
+
+void __exit mbox_exit(void)
+{
+ free_irq(IRQ_DB5500_PRCMU_AC_WAKE_ACK, NULL);
+ destroy_workqueue(mb->mbox_modem_rel_wq);
+ platform_driver_unregister(&mbox_driver);
+ kfree(mb);
+}
+
+module_exit(mbox_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MBOX driver");
diff --git a/drivers/misc/mbox_channels-db5500.c b/drivers/misc/mbox_channels-db5500.c
new file mode 100644
index 00000000000..919be308ed4
--- /dev/null
+++ b/drivers/misc/mbox_channels-db5500.c
@@ -0,0 +1,1273 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Mailbox Logical Driver
+ *
+ * Author: Marcin Mielczarczyk <marcin.mielczarczyk@tieto.com> for ST-Ericsson.
+ * Bibek Basu ,bibek.basu@stericsson.com>
+ * License terms: GNU General Public License (GPL), version 2.
+ */
+
+#include <asm/mach-types.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <mach/mbox-db5500.h>
+#include <mach/mbox_channels-db5500.h>
+#include <linux/io.h>
+
+/* Defines start sequence number for given mailbox channel */
+#define CHANNEL_START_SEQUENCE_NUMBER 0x80
+
+/* Defines number of channels per mailbox unit */
+#define CHANNELS_PER_MBOX_UNIT 256
+
+/*
+ * This macro builds mbox channel PDU header with following format:
+ * ---------------------------------------------------------------------------
+ * | | | | |
+ * | Sequence nmbr | Type | Length | Destination logical channel number |
+ * | | | | |
+ * ---------------------------------------------------------------------------
+ * 31 24 20 16 0
+ *
+ */
+#define BUILD_HEADER(chan, len, type, seq_no) \
+ ((chan) | (((len) & 0xf) << 16) | \
+ (((type) & 0xf) << 20) | ((seq_no) << 24))
+
+/* Returns type from mbox message header */
+#define GET_TYPE(mbox_msg) (((mbox_msg) >> 20) & 0xf)
+
+/* Returns channel number from mbox message header */
+#define GET_CHANNEL(mbox_msg) ((mbox_msg) & 0xffff)
+
+/* Returns length of payload from mbox message header */
+#define GET_LENGTH(mbox_msg) (((mbox_msg) >> 16) & 0xf)
+
+/* Returns sequence number from mbox message header */
+#define GET_SEQ_NUMBER(mbox_msg) (((mbox_msg) >> 24)
+
+enum mbox_msg{
+ MBOX_CLOSE,
+ MBOX_OPEN,
+ MBOX_SEND,
+ MBOX_CAST,
+ MBOX_ACK,
+ MBOX_NAK,
+};
+
+enum mbox_dir {
+ MBOX_TX,
+ MBOX_RX,
+};
+
+struct mbox_channel_mapping {
+ u16 chan_base;
+ u8 mbox_id;
+ enum mbox_dir direction;
+};
+
+/* This table maps mbox logical channel to mbox id and direction */
+static struct mbox_channel_mapping channel_mappings[] = {
+ {0x500, 2, MBOX_RX}, /* channel 5 maps to mbox 0.1, dsp->app (unsec) */
+ {0x900, 2, MBOX_TX}, /* channel 9 maps to mbox 0.0, app->dsp (unsec) */
+};
+
+/* This table specifies mailbox ids which mbox channels module will use */
+static u8 mbox_ids[] = {
+ 2, /* app <-> dsp (unsec) */
+};
+
+/**
+ * struct mbox_unit_status - current status of mbox unit
+ * @mbox_id : holds mbox unit identification number
+ * @mbox : holds mbox pointer after mbox_register() call
+ * @tx_chans : holds list of open tx mbox channels
+ * @tx_lock: lock for tx channel
+ * @rx_chans : holds list of open rx mbox channels
+ * @rx_lock: lock for rx channel
+ */
+struct mbox_unit_status {
+ u8 mbox_id;
+ struct mbox *mbox;
+ struct list_head tx_chans;
+ spinlock_t tx_lock;
+ struct list_head rx_chans;
+ spinlock_t rx_lock;
+};
+
+static struct {
+ struct platform_device *pdev;
+ struct mbox_unit_status mbox_unit[ARRAY_SIZE(mbox_ids)];
+} channels;
+
+/* This structure describes pending element for mbox tx channel */
+struct pending_elem {
+ struct list_head list;
+ u32 *data;
+ u8 length;
+};
+
+struct rx_pending_elem {
+ u32 buffer[MAILBOX_NR_OF_DATAWORDS];
+ u8 length;
+ void *priv;
+};
+
+struct rx_pending_elem rx_pending[NUM_DSP_BUFFER];
+
+/* This structure holds list of pending elements for mbox tx channel */
+struct tx_channel {
+ struct list_head pending;
+};
+
+/* Specific status for mbox rx channel */
+struct rx_channel {
+ struct list_head pending;
+ spinlock_t lock;
+ u32 buffer[MAILBOX_NR_OF_DATAWORDS];
+ u8 index;
+ u8 length;
+};
+
+/**
+ * struct channel_status - status of mbox channel - common for tx and rx
+ * @list : holds list of channels registered
+ * @channel : holds channel number
+ * @state : holds state of channel
+ * @cb: holds callback function forr rx channel
+ * @with_ack : holds if ack is needed
+ * @rx: holds pointer to rx_channel
+ * @tx : holds pointer to tx_channel
+ * @receive_wq : holds pointer to receive workqueue_struct
+ * @cast_wq : holds pointer to cast workqueue_struct
+ * @open_msg: holds work_struct for open msg
+ * @receive_msg : holds work_struct for receive msg
+ * @cast_msg: holds work_struct for cast msg
+ * @lock: holds lock for channel
+ */
+struct channel_status {
+ atomic_t rcv_counter;
+ struct list_head list;
+ u16 channel;
+ int state;
+ mbox_channel_cb_t *cb;
+ void *priv;
+ u8 seq_number;
+ bool with_ack;
+ struct rx_channel rx;
+ struct tx_channel tx;
+ struct workqueue_struct *receive_wq;
+ struct workqueue_struct *cast_wq;
+ struct work_struct open_msg;
+ struct work_struct receive_msg;
+ struct work_struct cast_msg;
+ struct mutex lock;
+};
+
+/* Checks if provided channel number is valid */
+static bool check_channel(u16 channel, enum mbox_dir direction)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(channel_mappings); i++) {
+ if ((channel >= channel_mappings[i].chan_base) &&
+ (channel < channel_mappings[i].chan_base +
+ CHANNELS_PER_MBOX_UNIT)) {
+ /* Check if direction of given channel is correct*/
+ if (channel_mappings[i].direction == direction)
+ return true;
+ else
+ break;
+ }
+ }
+ return false;
+}
+
+/* get the tx channel corresponding to the given rx channel */
+static u16 get_tx_channel(u16 channel)
+{
+ int i;
+ int relative_chan = 0;
+ int mbox_id = 0xFF;
+ u16 tx_channel = 0xFF;
+
+ for (i = 0; i < ARRAY_SIZE(channel_mappings); i++) {
+ if ((channel >= channel_mappings[i].chan_base) &&
+ (channel < channel_mappings[i].chan_base +
+ CHANNELS_PER_MBOX_UNIT)) {
+ /* Check if direction of given channel is correct*/
+ relative_chan = channel - channel_mappings[i].chan_base;
+ mbox_id = channel_mappings[i].mbox_id;
+
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(channel_mappings); i++) {
+ if ((mbox_id == channel_mappings[i].mbox_id) &&
+ (channel_mappings[i].direction == MBOX_TX))
+ tx_channel = channel_mappings[i].chan_base +
+ relative_chan;
+ }
+ return tx_channel;
+}
+
+/* Returns mbox unit id for given mbox channel */
+static int get_mbox_id(u16 channel)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(channel_mappings); i++) {
+ if ((channel >= channel_mappings[i].chan_base) &&
+ (channel < channel_mappings[i].chan_base +
+ CHANNELS_PER_MBOX_UNIT)) {
+ return channel_mappings[i].mbox_id;
+ }
+ }
+ /* There is no mbox unit registered for given channel */
+ return -EINVAL;
+}
+
+/* Returns mbox structure saved after mbox_register() call */
+static struct mbox *get_mbox(u16 channel)
+{
+ int i;
+ int mbox_id = get_mbox_id(channel);
+
+ if (mbox_id < 0) {
+ dev_err(&channels.pdev->dev, "couldn't get mbox id\n");
+ return NULL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(channels.mbox_unit); i++) {
+ if (channels.mbox_unit[i].mbox_id == mbox_id)
+ return channels.mbox_unit[i].mbox;
+ }
+ return NULL;
+}
+
+/* Returns pointer to rx mbox channels list for given mbox unit */
+static struct list_head *get_rx_list(u8 mbox_id)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mbox_ids); i++) {
+ if (channels.mbox_unit[i].mbox_id == mbox_id)
+ return &channels.mbox_unit[i].rx_chans;
+ }
+ return NULL;
+}
+
+/* Returns pointer to tx mbox channels list for given mbox unit */
+static struct list_head *get_tx_list(u8 mbox_id)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mbox_ids); i++) {
+ if (channels.mbox_unit[i].mbox_id == mbox_id)
+ return &channels.mbox_unit[i].tx_chans;
+ }
+ return NULL;
+}
+
+static int send_pdu(struct channel_status *chan_status, int command,
+ u16 channel)
+{
+ struct mbox *mbox;
+ u32 header = 0;
+ int ret = 0;
+ /* SEND PDU is not supported */
+ if (command == MBOX_SEND) {
+ dev_err(&channels.pdev->dev, "SEND command not implemented\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+ mbox = get_mbox(chan_status->channel);
+ if (mbox == NULL) {
+ dev_err(&channels.pdev->dev, "couldn't get mailbox\n");
+ ret = -ENOSYS;
+ goto exit;
+ }
+ /* For CAST type send all pending messages */
+ if (command == MBOX_CAST) {
+ struct list_head *pos, *n;
+
+ /* Send all pending messages from TX channel */
+ list_for_each_safe(pos, n, &chan_status->tx.pending) {
+ struct pending_elem *pending =
+ list_entry(pos, struct pending_elem, list);
+ int i;
+
+ header = BUILD_HEADER(channel,
+ pending->length,
+ command,
+ chan_status->seq_number);
+
+ ret = mbox_send(mbox, header, true);
+ if (ret < 0) {
+ dev_err(&channels.pdev->dev,
+ "failed to send header, err=%d\n", ret);
+ goto exit;
+ }
+
+ for (i = 0; i < pending->length; i++) {
+ ret = mbox_send(mbox, pending->data[i], true);
+ if (ret < 0) {
+ dev_err(&channels.pdev->dev,
+ "failed to send header, err=%d\n", ret);
+ goto exit;
+ }
+ }
+
+ /* Call client's callback that data is already sent */
+ if (chan_status->cb)
+ chan_status->cb(pending->data, pending->length,
+ chan_status->priv);
+ else
+ dev_err(&channels.pdev->dev,
+ "%s no callback provided:header 0x%x\n",
+ __func__, header);
+
+ /* Increment sequence number */
+ chan_status->seq_number++;
+
+ /* Remove and free element from the list */
+ list_del(&pending->list);
+ kfree(pending);
+ }
+ } else {
+ header = BUILD_HEADER(channel, 0,
+ command, chan_status->seq_number);
+
+ ret = mbox_send(mbox, header, true);
+ if (ret < 0)
+ dev_err(&channels.pdev->dev, "failed to send header\n");
+ /* Increment sequence number */
+ chan_status->seq_number++;
+ }
+
+exit:
+ return ret;
+}
+
+void mbox_handle_receive_msg(struct work_struct *work)
+{
+ struct channel_status *rx_chan = container_of(work,
+ struct channel_status,
+ receive_msg);
+
+ if (!atomic_read(&rx_chan->rcv_counter))
+ return;
+rcv_msg:
+ /* Call client's callback and reset state */
+ if (rx_chan->cb) {
+ static int rx_pending_count;
+ rx_chan->cb(rx_pending[rx_pending_count].buffer,
+ rx_pending[rx_pending_count].length,
+ rx_pending[rx_pending_count].priv);
+ rx_pending_count++;
+ if (rx_pending_count == NUM_DSP_BUFFER)
+ rx_pending_count = 0;
+ } else {
+ dev_err(&channels.pdev->dev,
+ "%s no callback provided\n", __func__);
+ }
+ if (atomic_dec_return(&rx_chan->rcv_counter) > 0)
+ goto rcv_msg;
+
+}
+
+void mbox_handle_open_msg(struct work_struct *work)
+{
+ struct channel_status *tx_chan = container_of(work,
+ struct channel_status,
+ open_msg);
+ /* Change channel state to OPEN */
+ tx_chan->state = MBOX_OPEN;
+ /* If pending list not empty, start sending data */
+ mutex_lock(&tx_chan->lock);
+ if (!list_empty(&tx_chan->tx.pending))
+ send_pdu(tx_chan, MBOX_CAST, tx_chan->channel);
+ mutex_unlock(&tx_chan->lock);
+}
+
+void mbox_handle_cast_msg(struct work_struct *work)
+{
+ struct channel_status *rx_chan = container_of(work,
+ struct channel_status,
+ cast_msg);
+ /* Check if channel is opened */
+ if (rx_chan->state == MBOX_CLOSE) {
+ /* Peer sent message to closed channel */
+ dev_err(&channels.pdev->dev,
+ "channel in wrong state\n");
+ }
+}
+
+static bool handle_receive_msg(u32 mbox_msg, struct channel_status *rx_chan)
+{
+ int i;
+ static int rx_pending_count;
+
+ if (rx_chan) {
+ /* Store received data in RX channel buffer */
+ rx_chan->rx.buffer[rx_chan->rx.index++] = mbox_msg;
+
+ /* Check if it's last data of PDU */
+ if (rx_chan->rx.index == rx_chan->rx.length) {
+ for (i = 0; i < MAILBOX_NR_OF_DATAWORDS; i++) {
+ rx_pending[rx_pending_count].buffer[i] =
+ rx_chan->rx.buffer[i];
+ }
+
+ rx_pending[rx_pending_count].length =
+ rx_chan->rx.length;
+ rx_pending[rx_pending_count].priv = rx_chan->priv;
+ rx_chan->rx.index = 0;
+ rx_chan->rx.length = 0;
+ rx_chan->state = MBOX_OPEN;
+ rx_chan->seq_number++;
+ rx_pending_count++;
+ if (rx_pending_count == NUM_DSP_BUFFER)
+ rx_pending_count = 0;
+ atomic_inc(&rx_chan->rcv_counter);
+ queue_work(rx_chan->receive_wq,
+ &rx_chan->receive_msg);
+ }
+ dev_dbg(&channels.pdev->dev, "%s OK\n", __func__);
+
+ return true;
+ }
+ return false;
+}
+
+static void handle_open_msg(u16 channel, u8 mbox_id)
+{
+ struct list_head *tx_list, *pos;
+ struct channel_status *tmp;
+ struct channel_status *tx_chan = NULL;
+ struct mbox_unit_status *mbox_unit;
+ channel = get_tx_channel(channel);
+ dev_dbg(&channels.pdev->dev, "%s mbox_id %d\tchannel %x\n",
+ __func__, mbox_id, channel);
+ /* Get TX channel for given mbox unit */
+ tx_list = get_tx_list(mbox_id);
+ if (tx_list == NULL) {
+ dev_err(&channels.pdev->dev, "given mbox id is not valid %d\n",
+ mbox_id);
+ return;
+ }
+ mbox_unit = container_of(tx_list, struct mbox_unit_status, tx_chans);
+ /* Search for channel in tx list */
+ spin_lock(&mbox_unit->tx_lock);
+ list_for_each(pos, tx_list) {
+ tmp = list_entry(pos, struct channel_status, list);
+ dev_dbg(&channels.pdev->dev, "tmp->channel=%d\n",
+ tmp->channel);
+ if (tmp->channel == channel)
+ tx_chan = tmp;
+ }
+ spin_unlock(&mbox_unit->tx_lock);
+ if (tx_chan) {
+ schedule_work(&tx_chan->open_msg);
+ } else {
+ /* No tx channel found on the list, allocate new element */
+ tx_chan = kzalloc(sizeof(*tx_chan), GFP_ATOMIC);
+ if (tx_chan == NULL) {
+ dev_err(&channels.pdev->dev,
+ "failed to allocate memory\n");
+ return;
+ }
+
+ /* Fill initial data and add this element to tx list */
+ tx_chan->channel = get_tx_channel(channel);
+ tx_chan->state = MBOX_OPEN;
+ tx_chan->seq_number = CHANNEL_START_SEQUENCE_NUMBER;
+ INIT_LIST_HEAD(&tx_chan->tx.pending);
+ INIT_WORK(&tx_chan->open_msg, mbox_handle_open_msg);
+ INIT_WORK(&tx_chan->cast_msg, mbox_handle_cast_msg);
+ INIT_WORK(&tx_chan->receive_msg, mbox_handle_receive_msg);
+ mutex_init(&tx_chan->lock);
+ spin_lock(&mbox_unit->tx_lock);
+ list_add_tail(&tx_chan->list, tx_list);
+ spin_unlock(&mbox_unit->tx_lock);
+ }
+}
+
+static void handle_cast_msg(u16 channel, struct channel_status *rx_chan,
+ u32 mbox_msg, bool send)
+{
+ dev_dbg(&channels.pdev->dev, " %s\n", __func__);
+ if (rx_chan) {
+ rx_chan->rx.buffer[0] = mbox_msg;
+ rx_chan->with_ack = send;
+ rx_chan->rx.length = GET_LENGTH(rx_chan->rx.buffer[0]);
+ if (rx_chan->rx.length <= MAILBOX_NR_OF_DATAWORDS &&
+ rx_chan->rx.length > 0) {
+ rx_chan->rx.index = 0;
+ rx_chan->state = MBOX_CAST;
+ }
+ queue_work(rx_chan->cast_wq,
+ &rx_chan->cast_msg);
+ } else {
+ /* Channel not found, peer sent wrong message */
+ dev_err(&channels.pdev->dev, "channel %d doesn't exist\n",
+ channel);
+ }
+}
+
+/*
+ * This callback is called whenever mbox unit receives data.
+ * priv parameter holds mbox unit id.
+ */
+static void mbox_cb(u32 mbox_msg, void *priv)
+{
+ u8 mbox_id = *(u8 *)priv;
+ struct list_head *rx_list;
+ u8 type = GET_TYPE(mbox_msg);
+ u16 channel = GET_CHANNEL(mbox_msg);
+ struct mbox_unit_status *mbox_unit;
+ struct list_head *pos;
+ struct channel_status *tmp;
+ struct channel_status *rx_chan = NULL;
+ bool is_Payload = 0;
+
+ dev_dbg(&channels.pdev->dev, "%s type %d\t, mbox_msg %x\n",
+ __func__, type, mbox_msg);
+
+ /* Get RX channels list for given mbox unit */
+ rx_list = get_rx_list(mbox_id);
+ if (rx_list == NULL) {
+ dev_err(&channels.pdev->dev, "given mbox id is not valid %d\n",
+ mbox_id);
+ return;
+ }
+
+ mbox_unit = container_of(rx_list, struct mbox_unit_status, rx_chans);
+ /* Search for channel in rx list */
+ spin_lock(&mbox_unit->rx_lock);
+ list_for_each(pos, rx_list) {
+ tmp = list_entry(pos, struct channel_status, list);
+ if (tmp->state == MBOX_SEND ||
+ tmp->state == MBOX_CAST) {
+ /* Received message is payload */
+ is_Payload = 1;
+ rx_chan = tmp;
+ } else
+ if (tmp->channel == channel)
+ rx_chan = tmp;
+ }
+ spin_unlock(&mbox_unit->rx_lock);
+ /* if callback is present for that RX channel */
+ if (rx_chan && rx_chan->cb) {
+ /* If received message is payload this
+ * function will take care of it
+ */
+ if ((is_Payload) && (handle_receive_msg(mbox_msg, rx_chan)))
+ return;
+ } else
+ dev_err(&channels.pdev->dev, "callback not present:msg 0x%x "
+ "rx_chan 0x%x\n", mbox_msg, (u32)rx_chan);
+
+ /* Received message is header as no RX channel is in SEND/CAST state */
+ switch (type) {
+ case MBOX_CLOSE:
+ /* Not implemented */
+ break;
+ case MBOX_OPEN:
+ handle_open_msg(channel, mbox_id);
+ break;
+ case MBOX_SEND:
+ /* if callback is present for that RX channel */
+ if (rx_chan && rx_chan->cb)
+ handle_cast_msg(channel, rx_chan, mbox_msg, true);
+ break;
+ case MBOX_CAST:
+ /* if callback is present for that RX channel */
+ if (rx_chan && rx_chan->cb)
+ handle_cast_msg(channel, rx_chan, mbox_msg, false);
+ break;
+ case MBOX_ACK:
+ case MBOX_NAK:
+ /* Not implemented */
+ break;
+ }
+}
+
+/**
+ * mbox_channel_register() - Registers for a channel
+ * @channel: Channel Number.
+ * @cb: Pointer to function pointer mbox_channel_cb_t
+ * @priv: Pointer to private data
+ *
+ * This routine is used to register for a logical channel.
+ * It first does sanity check on the requested channel availability
+ * and parameters. Then it prepares internal entry for the channel.
+ * And send a OPEN request for that channel.
+ */
+int mbox_channel_register(u16 channel, mbox_channel_cb_t *cb, void *priv)
+{
+ struct channel_status *rx_chan;
+ struct list_head *pos, *rx_list;
+ int res = 0;
+ struct mbox_unit_status *mbox_unit;
+
+ dev_dbg(&channels.pdev->dev, " %s channel = %d\n", __func__, channel);
+ /* Check for callback fcn */
+ if (cb == NULL) {
+ dev_err(&channels.pdev->dev,
+ "channel callback missing:channel %d\n", channel);
+ res = -EINVAL;
+ goto exit;
+ }
+
+ /* Check if provided channel number is valid */
+ if (!check_channel(channel, MBOX_RX)) {
+ dev_err(&channels.pdev->dev, "wrong mbox channel number %d\n",
+ channel);
+ res = -EINVAL;
+ goto exit;
+ }
+
+ rx_list = get_rx_list(get_mbox_id(channel));
+ if (rx_list == NULL) {
+ dev_err(&channels.pdev->dev, "given mbox id is not valid\n");
+ res = -EINVAL;
+ goto exit;
+ }
+
+ mbox_unit = container_of(rx_list, struct mbox_unit_status, rx_chans);
+
+ /* Check if channel is already registered */
+ spin_lock(&mbox_unit->rx_lock);
+ list_for_each(pos, rx_list) {
+ rx_chan = list_entry(pos, struct channel_status, list);
+
+ if (rx_chan->channel == channel) {
+ dev_dbg(&channels.pdev->dev,
+ "channel already registered\n");
+ rx_chan->cb = cb;
+ rx_chan->priv = priv;
+ spin_unlock(&mbox_unit->rx_lock);
+ goto exit;
+ }
+ }
+ spin_unlock(&mbox_unit->rx_lock);
+
+ rx_chan = kzalloc(sizeof(*rx_chan), GFP_KERNEL);
+ if (rx_chan == NULL) {
+ dev_err(&channels.pdev->dev,
+ "couldn't allocate channel status\n");
+ res = -ENOMEM;
+ goto exit;
+ }
+
+ atomic_set(&rx_chan->rcv_counter, 0);
+ /* Fill out newly allocated element and add it to rx list */
+ rx_chan->channel = channel;
+ rx_chan->cb = cb;
+ rx_chan->priv = priv;
+ rx_chan->seq_number = CHANNEL_START_SEQUENCE_NUMBER;
+ mutex_init(&rx_chan->lock);
+ INIT_LIST_HEAD(&rx_chan->rx.pending);
+ rx_chan->cast_wq = create_singlethread_workqueue("mbox_cast_msg");
+ if (!rx_chan->cast_wq) {
+ dev_err(&channels.pdev->dev, "failed to create work queue\n");
+ res = -ENOMEM;
+ goto error_cast_wq;
+ }
+ rx_chan->receive_wq = create_singlethread_workqueue("mbox_receive_msg");
+ if (!rx_chan->receive_wq) {
+ dev_err(&channels.pdev->dev, "failed to create work queue\n");
+ res = -ENOMEM;
+ goto error_recv_wq;
+ }
+ INIT_WORK(&rx_chan->open_msg, mbox_handle_open_msg);
+ INIT_WORK(&rx_chan->cast_msg, mbox_handle_cast_msg);
+ INIT_WORK(&rx_chan->receive_msg, mbox_handle_receive_msg);
+ spin_lock(&mbox_unit->rx_lock);
+ list_add_tail(&rx_chan->list, rx_list);
+ spin_unlock(&mbox_unit->rx_lock);
+
+ mutex_lock(&rx_chan->lock);
+ res = send_pdu(rx_chan, MBOX_OPEN, get_tx_channel(rx_chan->channel));
+ if (res) {
+ dev_err(&channels.pdev->dev, "failed to send OPEN command\n");
+ spin_lock(&mbox_unit->rx_lock);
+ list_del(&rx_chan->list);
+ spin_unlock(&mbox_unit->rx_lock);
+ mutex_unlock(&rx_chan->lock);
+ goto error_send_pdu;
+ } else {
+ rx_chan->seq_number++;
+ rx_chan->state = MBOX_OPEN;
+ mutex_unlock(&rx_chan->lock);
+ return res;
+ }
+error_send_pdu:
+ flush_workqueue(rx_chan->receive_wq);
+error_recv_wq:
+ flush_workqueue(rx_chan->cast_wq);
+error_cast_wq:
+ kfree(rx_chan);
+exit:
+ return res;
+}
+EXPORT_SYMBOL(mbox_channel_register);
+
+/**
+ * mbox_channel_deregister() - DeRegisters for a channel
+ * @channel: Channel Number.
+ *
+ * This routine is used to deregister for a logical channel.
+ * It first does sanity check on the requested channel availability
+ * and parameters. Then it deletes the channel
+ */
+int mbox_channel_deregister(u16 channel)
+{
+ struct channel_status *rx_chan = NULL;
+ struct list_head *pos, *rx_list;
+ int res = 0;
+ struct mbox_unit_status *mbox_unit;
+
+ dev_dbg(&channels.pdev->dev, " %s channel = %d\n", __func__, channel);
+ /* Check if provided channel number is valid */
+ if (!check_channel(channel, MBOX_RX)) {
+ dev_err(&channels.pdev->dev, "wrong mbox channel number %d\n",
+ channel);
+ res = -EINVAL;
+ goto exit;
+ }
+
+ rx_list = get_rx_list(get_mbox_id(channel));
+ if (rx_list == NULL) {
+ dev_err(&channels.pdev->dev, "given mbox id is not valid\n");
+ res = -EINVAL;
+ goto exit;
+ }
+
+ mbox_unit = container_of(rx_list, struct mbox_unit_status, rx_chans);
+
+ /* Check if channel is already registered */
+ spin_lock(&mbox_unit->rx_lock);
+ list_for_each(pos, rx_list) {
+ rx_chan = list_entry(pos, struct channel_status, list);
+
+ if (rx_chan->channel == channel) {
+ dev_dbg(&channels.pdev->dev,
+ "channel found\n");
+ rx_chan->cb = NULL;
+ }
+ }
+ list_del(&rx_chan->list);
+ spin_unlock(&mbox_unit->rx_lock);
+ flush_workqueue(rx_chan->cast_wq);
+ flush_workqueue(rx_chan->receive_wq);
+ kfree(rx_chan);
+
+exit:
+ return res;
+}
+EXPORT_SYMBOL(mbox_channel_deregister);
+
+/**
+ * mbox_channel_send() - Send messages
+ * @msg: Pointer to mbox_channel_msg data structure.
+ *
+ * This routine is used to send messages over the registered logical
+ * TX channel. It first does sanity check on the message paramenters.
+ * It registered channel is not found then it just registers for that
+ * channel. If channel found, it puts the message to the pending list.
+ * If channel is OPEN, it then pushes the message to the mailbox in
+ * FIFO manner from the pending list.
+ */
+int mbox_channel_send(struct mbox_channel_msg *msg)
+{
+ struct list_head *pos, *tx_list;
+ struct channel_status *tmp = NULL;
+ struct channel_status *tx_chan = NULL;
+ struct pending_elem *pending;
+ struct mbox_unit_status *mbox_unit;
+ int res = 0;
+
+ if (msg->length > MAILBOX_NR_OF_DATAWORDS || msg->length == 0) {
+ dev_err(&channels.pdev->dev, "data length incorrect\n");
+ res = -EINVAL;
+ goto exit;
+ }
+
+ if (!check_channel(msg->channel, MBOX_TX)) {
+ dev_err(&channels.pdev->dev, "wrong channel number %d\n",
+ msg->channel);
+ res = -EINVAL;
+ goto exit;
+ }
+
+ tx_list = get_tx_list(get_mbox_id(msg->channel));
+ if (tx_list == NULL) {
+ dev_err(&channels.pdev->dev, "given mbox id is not valid\n");
+ res = -EINVAL;
+ goto exit;
+ }
+
+ mbox_unit = container_of(tx_list, struct mbox_unit_status, tx_chans);
+
+ spin_lock(&mbox_unit->tx_lock);
+ dev_dbg(&channels.pdev->dev, "send:tx_list=%x\tmbox_unit=%x\n",
+ (u32)tx_list, (u32)mbox_unit);
+ list_for_each(pos, tx_list) {
+ tmp = list_entry(pos, struct channel_status, list);
+ if (tmp->channel == msg->channel)
+ tx_chan = tmp;
+ }
+ spin_unlock(&mbox_unit->tx_lock);
+ /* Allocate pending element and add it to the list */
+ pending = kzalloc(sizeof(*pending), GFP_KERNEL);
+ if (pending == NULL) {
+ dev_err(&channels.pdev->dev,
+ "couldn't allocate memory for pending\n");
+ res = -ENOMEM;
+ goto exit;
+ }
+ pending->data = msg->data;
+ pending->length = msg->length;
+
+ if (tx_chan) {
+ mutex_lock(&tx_chan->lock);
+ list_add_tail(&pending->list, &tx_chan->tx.pending);
+ tx_chan->cb = msg->cb;
+ tx_chan->priv = msg->priv;
+ /* If channel is already opened start sending data */
+ if (tx_chan->state == MBOX_OPEN)
+ send_pdu(tx_chan, MBOX_CAST, tx_chan->channel);
+ /* Stop processing here */
+ mutex_unlock(&tx_chan->lock);
+ } else {
+ /* No channel found on the list, allocate new element */
+ tx_chan = kzalloc(sizeof(*tx_chan), GFP_KERNEL);
+ if (tx_chan == NULL) {
+ dev_err(&channels.pdev->dev,
+ "couldn't allocate memory for \
+ tx_chan\n");
+ res = -ENOMEM;
+ goto exit;
+ }
+ tx_chan->channel = msg->channel;
+ tx_chan->cb = msg->cb;
+ tx_chan->priv = msg->priv;
+ tx_chan->state = MBOX_CLOSE;
+ tx_chan->seq_number = CHANNEL_START_SEQUENCE_NUMBER;
+ INIT_LIST_HEAD(&tx_chan->tx.pending);
+ INIT_WORK(&tx_chan->open_msg, mbox_handle_open_msg);
+ INIT_WORK(&tx_chan->cast_msg, mbox_handle_cast_msg);
+ INIT_WORK(&tx_chan->receive_msg, mbox_handle_receive_msg);
+ mutex_init(&tx_chan->lock);
+ spin_lock(&mbox_unit->tx_lock);
+ list_add_tail(&tx_chan->list, tx_list);
+ spin_unlock(&mbox_unit->tx_lock);
+ mutex_lock(&tx_chan->lock);
+ list_add_tail(&pending->list, &tx_chan->tx.pending);
+ mutex_unlock(&tx_chan->lock);
+ }
+ return 0;
+
+exit:
+ return res;
+}
+EXPORT_SYMBOL(mbox_channel_send);
+
+static void revoke_pending_msgs(struct channel_status *tx_chan)
+{
+ struct list_head *pos, *n;
+ struct pending_elem *pending;
+
+ list_for_each_safe(pos, n, &tx_chan->tx.pending) {
+ pending = list_entry(pos, struct pending_elem, list);
+
+ if (tx_chan->cb)
+ tx_chan->cb(pending->data, pending->length,
+ tx_chan->priv);
+ else
+ dev_err(&channels.pdev->dev,
+ "%s no callback provided\n", __func__);
+ list_del(&pending->list);
+ kfree(pending);
+ }
+}
+
+/**
+ * mbox_channel_revoke_messages() - Revoke pending messages
+ * @channel: Channel on which action to be taken.
+ *
+ * This routine Clear all pending messages from TX channel
+ * It searches for the channel.Checks if there is pending
+ * messages.Calls if tehre is any registered function. And
+ * deletes the messages for the pending list.
+ */
+int mbox_channel_revoke_messages(u16 channel)
+{
+ struct list_head *pos, *tx_list;
+ struct channel_status *tmp;
+ struct channel_status *tx_chan = NULL;
+ struct mbox_unit_status *mbox_unit;
+ int res = 0;
+
+ if (!check_channel(channel, MBOX_TX)) {
+ dev_err(&channels.pdev->dev,
+ "wrong channel number %d\n", channel);
+ return -EINVAL;
+ }
+
+ tx_list = get_tx_list(get_mbox_id(channel));
+ if (tx_list == NULL) {
+ dev_err(&channels.pdev->dev, "given mbox id is not valid\n");
+ return -EINVAL;
+ }
+
+ mbox_unit = container_of(tx_list, struct mbox_unit_status, tx_chans);
+
+ spin_lock(&mbox_unit->tx_lock);
+ list_for_each(pos, tx_list) {
+ tmp = list_entry(pos, struct channel_status, list);
+ if (tmp->channel == channel)
+ tx_chan = tmp;
+ }
+ spin_unlock(&mbox_unit->tx_lock);
+
+ if (tx_chan) {
+ mutex_lock(&tx_chan->lock);
+ revoke_pending_msgs(tx_chan);
+ mutex_unlock(&tx_chan->lock);
+ dev_dbg(&channels.pdev->dev, "channel %d cleared\n",
+ channel);
+ } else {
+ dev_err(&channels.pdev->dev, "no channel found\n");
+ res = -EINVAL;
+ }
+
+ dev_dbg(&channels.pdev->dev, "%s exiting %d\n", __func__, res);
+ return res;
+}
+EXPORT_SYMBOL(mbox_channel_revoke_messages);
+
+#if defined(CONFIG_DEBUG_FS)
+#define MBOXTEST_DEBUG 1
+#ifdef MBOXTEST_DEBUG
+#define DBG_TEST(x) x
+#else
+#define DBG_TEST(x)
+#endif
+
+#define MBOX_TEST_MAX_WORDS 3
+#define MBOX_RX_CHAN 0x500
+#define MBOX_TX_RX_CHANNEL_DIFF 0x400
+#define MBOX_MAX_NUM_TRANSFER 30000
+static int registration_done;
+/**
+ * struct mboxtest_data - mbox test via debugfs information
+ * @rx_buff: Buffer for incomming data
+ * @rx_pointer: Ptr to actual RX data buff
+ * @tx_buff: Buffer for outgoing data
+ * @tx_pointer: Ptr to actual TX data buff
+ * @tx_done: TX Transfer done indicator
+ * @rx_done: RX Transfer done indicator
+ * @received: Received words
+ * @xfer_words: Num of bytes in actual trf
+ * @xfers: Number of transfers
+ * @words: Number of total words
+ * @channel: Channel test number
+ */
+struct mboxtest_data {
+ unsigned int *rx_buff;
+ unsigned int *rx_pointer;
+ unsigned int *tx_buff;
+ unsigned int *tx_pointer;
+ struct completion tx_done;
+ struct completion rx_done;
+ int received;
+ int xfer_words;
+ int xfers;
+ int words;
+ int channel;
+};
+
+static void mboxtest_receive_cb(u32 *data, u32 len, void *arg)
+{
+ struct mboxtest_data *mboxtest = (struct mboxtest_data *) arg;
+ int i;
+
+ printk(KERN_INFO "receive_cb.. data.= 0x%X, len = %d\n",
+ *data, len);
+ for (i = 0; i < len; i++)
+ *(mboxtest->rx_pointer++) = *(data++);
+
+ mboxtest->received += len;
+
+ printk(KERN_INFO "received = %d, words = %d\n",
+ mboxtest->received, mboxtest->words);
+ if (mboxtest->received >= mboxtest->words)
+ complete(&mboxtest->rx_done);
+ dev_dbg(&channels.pdev->dev, "%s exiting\n", __func__);
+}
+
+static void mboxtest_send_cb(u32 *data, u32 len, void *arg)
+{
+ struct mboxtest_data *mboxtest = (struct mboxtest_data *) arg;
+
+ printk(KERN_INFO "send_cb.. data.= 0x%X, len = %d\n",
+ *data, len);
+
+ complete(&mboxtest->tx_done);
+ dev_dbg(&channels.pdev->dev, "kernel:mboxtest_send_cb exiting\n");
+}
+
+static int mboxtest_transmit(struct mboxtest_data *mboxtest)
+{
+ int status = 0;
+ struct mbox_channel_msg msg;
+
+ dev_dbg(&channels.pdev->dev, "%s entering\n", __func__);
+ init_completion(&mboxtest->tx_done);
+
+ msg.channel = mboxtest->channel;
+ msg.data = mboxtest->tx_pointer;
+ msg.length = mboxtest->words;
+ msg.cb = mboxtest_send_cb;
+ msg.priv = mboxtest;
+
+ status = mbox_channel_send(&msg);
+ if (!status) {
+ mboxtest->tx_pointer += mboxtest->xfer_words;
+ wait_for_completion(&mboxtest->tx_done);
+ }
+
+ dev_dbg(&channels.pdev->dev, "%s exiting %d\n",
+ __func__, status);
+ return status;
+}
+
+static int transfer_test(struct mboxtest_data *mboxtest)
+{
+ int status = 0;
+ int len = 0;
+ int i;
+
+ len = mboxtest->words;
+
+ dev_dbg(&channels.pdev->dev, "%s enterring\n", __func__);
+ /* Allocate buffers */
+ mboxtest->rx_buff = kzalloc(sizeof(unsigned int) * len, GFP_KERNEL);
+ if (!mboxtest->rx_buff) {
+ DBG_TEST(printk(KERN_INFO
+ "Cannot allocate mbox rx memory\n"));
+ status = -ENOMEM;
+ goto err1;
+ }
+ memset(mboxtest->rx_buff, '\0', sizeof(unsigned int) * len);
+
+ mboxtest->tx_buff = kzalloc(sizeof(unsigned int) * len, GFP_KERNEL);
+ if (!mboxtest->tx_buff) {
+ DBG_TEST(printk(KERN_INFO
+ "Cannot allocate mbox tx memory\n"));
+ status = -ENOMEM;
+ goto err2;
+ }
+ memset(mboxtest->tx_buff, '\0', sizeof(unsigned int) * len);
+
+ /* Generate data */
+ get_random_bytes((unsigned char *)mboxtest->tx_buff,
+ sizeof(unsigned int) * len);
+ /* Set pointers */
+ mboxtest->tx_pointer = mboxtest->tx_buff;
+ mboxtest->rx_pointer = mboxtest->rx_buff;
+ mboxtest->received = 0;
+ init_completion(&mboxtest->rx_done);
+
+ /* Start tx transfer test transfer */
+ status = mboxtest_transmit(mboxtest);
+ DBG_TEST(printk(KERN_INFO "xfer_words=%d\n",
+ mboxtest->xfer_words));
+ if (!status)
+ wait_for_completion(&mboxtest->rx_done);
+ for (i = 0; i < len; i++)
+ DBG_TEST(printk(KERN_INFO "%d -> TX:0x%X, RX:0x%X\n", i,
+ mboxtest->tx_buff[i], mboxtest->rx_buff[i]));
+
+ dev_dbg(&channels.pdev->dev, "%s exiting %d\n", __func__, status);
+ return status;
+err2:
+ kfree(mboxtest->rx_buff);
+err1:
+ return status;
+}
+
+static int mboxtest_prepare(struct mboxtest_data *mboxtest)
+{
+ int err = 0;
+
+ mboxtest->xfers = MBOX_MAX_NUM_TRANSFER;
+ /* Calculate number of bytes in each transfer */
+ mboxtest->xfer_words = mboxtest->words / mboxtest->xfers;
+
+ /* Trim to maxiumum data words per transfer */
+ if (mboxtest->xfer_words > MBOX_TEST_MAX_WORDS) {
+ DBG_TEST(printk(KERN_INFO "Recalculating xfers ...\n"));
+ mboxtest->xfer_words = MBOX_TEST_MAX_WORDS;
+ if (mboxtest->words % mboxtest->xfer_words)
+ mboxtest->xfers = (mboxtest->words /
+ mboxtest->xfer_words) + 1;
+ else
+ mboxtest->xfers = (mboxtest->words /
+ mboxtest->xfer_words);
+ }
+
+ DBG_TEST(printk(KERN_INFO "Params: chan=0x%X words=%d, xfers=%d\n",
+ mboxtest->channel, mboxtest->words,
+ mboxtest->xfers));
+
+ if (mbox_channel_register(mboxtest->channel,
+ mboxtest_receive_cb, mboxtest)) {
+ DBG_TEST(printk(KERN_INFO "Cannot register mbox channel\n"));
+ err = -ENOMEM;
+ goto err;
+ }
+
+ registration_done = true;
+ return 0;
+err:
+ return err;
+}
+
+struct mboxtest_data mboxtest;
+/*
+ * Expected input: <nbr_channel> <nbr_word>
+ * Example: "echo 500 2"
+ */
+static ssize_t mbox_write_channel(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ unsigned long nbr_channel;
+ unsigned long nbr_word;
+ char int_buf[16];
+ char *token;
+ char *val;
+
+ strncpy((char *) &int_buf, buf, sizeof(int_buf));
+ token = (char *) &int_buf;
+
+ /* Parse message */
+ val = strsep(&token, " ");
+ if ((val == NULL) || (strict_strtoul(val, 16, &nbr_channel) != 0))
+ nbr_channel = MBOX_RX_CHAN;
+
+ val = strsep(&token, " ");
+ if ((val == NULL) || (strict_strtoul(val, 16, &nbr_word) != 0))
+ nbr_word = 2;
+
+ dev_dbg(dev, "Will setup logical channel %ld\n", nbr_channel);
+ mboxtest.channel = nbr_channel;
+ mboxtest.words = nbr_word;
+
+ if (!registration_done)
+ mboxtest_prepare(&mboxtest);
+ else
+ dev_dbg(&channels.pdev->dev, "already registration done\n");
+
+ return count;
+}
+
+static ssize_t mbox_read_channel(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+
+ unsigned long i;
+ static bool config_done;
+
+ if (!config_done) {
+ config_done = true;
+ mboxtest.channel += MBOX_TX_RX_CHANNEL_DIFF;
+ }
+ dev_dbg(dev, "Will transfer %d words %d times at channel 0x%x\n",
+ mboxtest.words, mboxtest.xfers, mboxtest.channel);
+ for (i = 0; i < mboxtest.xfers; i++)
+ transfer_test(&mboxtest);
+
+ return 1;
+}
+static DEVICE_ATTR(channel, S_IWUGO | S_IRUGO, mbox_read_channel,
+ mbox_write_channel);
+
+#endif
+
+static int __init mbox_channel_probe(struct platform_device *pdev)
+{
+ int i, ret = 0;
+ struct mbox *mbox;
+
+ dev_dbg(&(pdev->dev), "Probing mailbox (pdev = 0x%X)...\n", (u32)pdev);
+
+ /* Register to given mailbox units (ids) */
+ for (i = 0; i < ARRAY_SIZE(mbox_ids); i++) {
+ mbox = mbox_setup(mbox_ids[i], mbox_cb, &mbox_ids[i]);
+ if (mbox == NULL) {
+ dev_err(&(pdev->dev), "Unable to setup mailbox %d\n",
+ mbox_ids[i]);
+ ret = -EBUSY;
+ goto exit;
+ }
+ channels.mbox_unit[i].mbox_id = mbox_ids[i];
+ channels.mbox_unit[i].mbox = mbox;
+ INIT_LIST_HEAD(&channels.mbox_unit[i].rx_chans);
+ INIT_LIST_HEAD(&channels.mbox_unit[i].tx_chans);
+ spin_lock_init(&channels.mbox_unit[i].rx_lock);
+ spin_lock_init(&channels.mbox_unit[i].tx_lock);
+ }
+
+ channels.pdev = pdev;
+
+ dev_dbg(&(pdev->dev), "Mailbox channel driver loaded\n");
+#if defined(CONFIG_DEBUG_FS)
+ ret = device_create_file(&(pdev->dev), &dev_attr_channel);
+ if (ret != 0)
+ dev_warn(&(pdev->dev),
+ "Unable to create mbox_channel sysfs entry");
+
+
+#endif
+exit:
+ return ret;
+}
+
+static struct platform_driver mbox_channel_driver = {
+ .driver = {
+ .name = "mbox_channel",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mbox_channel_init(void)
+{
+ if (!machine_is_u5500())
+ return 0;
+
+ platform_device_register_simple("mbox_channel", 0, NULL, 0);
+
+ return platform_driver_probe(&mbox_channel_driver, mbox_channel_probe);
+}
+module_init(mbox_channel_init);
+
+static void __exit mbox_channel_exit(void)
+{
+ platform_driver_unregister(&mbox_channel_driver);
+}
+module_exit(mbox_channel_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MBOX channels driver");
diff --git a/drivers/misc/modem_audio/Kconfig b/drivers/misc/modem_audio/Kconfig
new file mode 100644
index 00000000000..5396868a9de
--- /dev/null
+++ b/drivers/misc/modem_audio/Kconfig
@@ -0,0 +1,6 @@
+config MODEM_AUDIO_DRIVER
+ bool "Modem Audio Driver"
+ depends on (U5500_MBOX && UX500_SOC_DB5500)
+ help
+ This module is used for read and write data between APE and
+ Access side in u5500 platform.
diff --git a/drivers/misc/modem_audio/Makefile b/drivers/misc/modem_audio/Makefile
new file mode 100644
index 00000000000..a5c1740ea48
--- /dev/null
+++ b/drivers/misc/modem_audio/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_MODEM_AUDIO_DRIVER) += mad.o
+
diff --git a/drivers/misc/modem_audio/mad.c b/drivers/misc/modem_audio/mad.c
new file mode 100644
index 00000000000..d31d78ba3f2
--- /dev/null
+++ b/drivers/misc/modem_audio/mad.c
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ *
+ * Modem Audio Driver
+ *
+ * Author:Rahul Venkatram <rahul.venkatram@stericsson.com> for ST-Ericsson
+ * Haridhar KALVALA<haridhar.kalvala@stericsson.com> for ST-Ericsson
+ * Amaresh Mulage<amaresh.mulage@stericsson.com> for ST-Ericsson.
+ *
+ * License terms:GNU General Public License (GPLv2)version 2
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/fcntl.h>
+#include <linux/spinlock.h>
+#include <mach/mbox_channels-db5500.h>
+
+MODULE_DESCRIPTION("Modem Audio Driver");
+MODULE_LICENSE("GPLv2");
+
+/**
+ * -----------------------------------------------------
+ * | | | |
+ * | Data[0] |Data[1] |Data[2] |===>Data word 32 bits
+ * -----------------------------------------------------
+ * | MESSAGE |Data | Index |
+ * | TYPE |length | number |===>READ/WRITE message
+ * -----------------------------------------------------
+ * -----------------------------------------------------
+ * | MESSAGE | DSP SHM addr | max_no_of_buffers |===> READ
+ * | TYPE | to write data | ||buffersize |WRITE SETUP message
+ * -----------------------------------------------------
+ */
+
+
+#define MAD_NAME "mad"
+/* Bit mask */
+#define MASK_UPPER_WORD 0xFFFF
+
+/* channel values for each direction */
+#define CHANNEL_NUM_RX 0x500
+#define CHANNEL_NUM_TX 0x900
+
+/*
+ * Maximum number of datawords which can be sent
+ * in the mailbox each word is 32 bits
+ */
+#define MAX_NR_OF_DATAWORDS MAILBOX_NR_OF_DATAWORDS
+#define MAX_NUM_RX_BUFF NUM_DSP_BUFFER
+#define NR_OF_DATAWORDS_REQD_FOR_ACK 1
+
+/**
+ * Message types, must be identical in DSP Side
+ * VCS_MBOX_MSG_WRITE_IF_SETUP : DSP -> ARM
+ * VCS_MBOX_MSG_WRITE_IF_SETUP_ACK : ARM -> DSP
+ * VCS_MBOX_MSG_READ_IF_SETUP : DSP -> ARM
+ * VCS_MBOX_MSG_READ_IF_SETUP_ACK : ARM -> DSP
+ * VCS_MBOX_MSG_IF_ENC_DATA : ARM -> DSP
+ * VCS_MBOX_MSG_IF_DEC_DATA : DSP -> ARM
+ */
+#define VCS_MBOX_MSG_WRITE_IF_SETUP 0x200
+#define VCS_MBOX_MSG_WRITE_IF_SETUP_ACK 0x201
+#define VCS_MBOX_MSG_READ_IF_SETUP 0x400
+#define VCS_MBOX_MSG_READ_IF_SETUP_ACK 0x401
+#define VCS_MBOX_MSG_IF_ENC_DATA 0x80
+#define VCS_MBOX_MSG_IF_DEC_DATA 0x100
+
+/**
+ * struct mad_data - This structure holds the state of the Modem Audio Driver.
+ *
+ * @dsp_shm_write_ptr : Ptr to the first TX buffer in DSP
+ * @dsp_shm_read_ptr : Ptr to the first RX buffer in DSP
+ * @max_tx_buffs : No. of DSP buffers available to write
+ * @max_rx_buffs : No. of DSP buffers available to read
+ * @write_offset : Size of each buffer in the DSP
+ * @read_offset : Size of each buffer in the DSP
+ * @rx_buff : Buffer for incoming data
+ * @tx_buff : Buffer for outgoing data
+ * @tx_buffer_num : Buffer counter for writing to DSP
+ * @rx_buffer_num : Buffer counter for reading to DSP
+ * @rx_buffer_read : Buffer counter for reading from userspace
+ * @data_written : RX data message arrival indicator
+ * @read_setup_msg : flag for opening read data
+ * @readq : read queue of data message
+ * @lock : lock for r/w message queue
+ */
+struct mad_data {
+ void __iomem *dsp_shm_write_ptr;
+ void __iomem *dsp_shm_read_ptr;
+ int max_tx_buffs;
+ int max_rx_buffs;
+ int write_offset;
+ int read_offset;
+ u32 *rx_buff;
+ u32 *tx_buff;
+ int tx_buffer_num;
+ int rx_buffer_num;
+ int rx_buffer_read;
+ u32 data_written;
+ bool read_setup_msg;
+ bool open_check;
+ wait_queue_head_t readq;
+ spinlock_t lock;
+};
+
+static struct mad_data *mad;
+
+static void mad_receive_cb(u32 *data, u32 length, void *priv);
+static int mad_read(struct file *filp, char __user *buff, size_t count,
+ loff_t *offp);
+static int mad_write(struct file *filp, const char __user *buff, size_t count,
+ loff_t *offp);
+static unsigned int mad_select(struct file *filp, poll_table *wait);
+static void mad_send_cb(u32 *data, u32 len, void *arg);
+static int mad_open(struct inode *ino, struct file *filp);
+static int mad_close(struct inode *ino, struct file *filp);
+
+static const struct file_operations mad_fops = {
+ .release = mad_close,
+ .open = mad_open,
+ .read = mad_read,
+ .write = mad_write,
+ .poll = mad_select,
+ .owner = THIS_MODULE,
+};
+
+static struct miscdevice mad_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = MAD_NAME,
+ .fops = &mad_fops
+};
+
+/**
+ * mad_send_cb - This function is default callback for send.
+ * @data -Pointer to the data buffer
+ * @len -Data buffer length
+ * @arg -Private data pointer associated with test
+ */
+static void mad_send_cb(u32 *data, u32 len, void *arg)
+{
+ dev_dbg(mad_dev.this_device, "%s", __func__);
+}
+
+/**
+ * mad_receive_cb - This callback function is for receiving data from mailbox
+ * @data -Pointer to the data buffer
+ * @len -length of the Mailbox
+ * @arg -Private data pointer associated with test
+ */
+static void mad_receive_cb(u32 *data, u32 length, void *priv)
+{
+ struct mad_data *mad = priv;
+ struct mbox_channel_msg msg;
+ u32 ack_to_dsp;
+ unsigned long flags;
+
+ /* setup message for write address */
+ if (*data == VCS_MBOX_MSG_WRITE_IF_SETUP) {
+
+ ack_to_dsp = VCS_MBOX_MSG_WRITE_IF_SETUP_ACK;
+
+ /* if setup message comes again.unmap */
+ if (mad->dsp_shm_write_ptr != NULL) {
+ iounmap(mad->dsp_shm_write_ptr);
+ mad->dsp_shm_write_ptr = NULL;
+ mad->write_offset = 0;
+ mad->max_tx_buffs = 0;
+ }
+
+ /* convert offset to uint size */
+ mad->write_offset = (data[2] & MASK_UPPER_WORD);
+ mad->max_tx_buffs = (data[2] >> 16);
+
+ mad->dsp_shm_write_ptr = ioremap(data[1],
+ mad->max_tx_buffs * mad->write_offset);
+ if (mad->dsp_shm_write_ptr == NULL)
+ dev_err(mad_dev.this_device, "incrt write address");
+
+ /* Initialize all buffer numbers */
+ mad->tx_buffer_num = 0;
+
+ /* Send ACK to the DSP */
+ msg.channel = CHANNEL_NUM_TX;
+ msg.data = &ack_to_dsp;
+ msg.length = NR_OF_DATAWORDS_REQD_FOR_ACK;
+ msg.cb = mad_send_cb;
+ msg.priv = mad;
+
+ if (mbox_channel_send(&msg))
+ dev_err(mad_dev.this_device, "%s: can't send data\n",
+ __func__);
+
+ } /* setup message for reading SHM */
+ else if (*data == VCS_MBOX_MSG_READ_IF_SETUP) {
+
+ ack_to_dsp = VCS_MBOX_MSG_READ_IF_SETUP_ACK;
+
+ /* if setup message comes again.unmap */
+ if (mad->dsp_shm_read_ptr != NULL) {
+ iounmap(mad->dsp_shm_read_ptr);
+ mad->dsp_shm_read_ptr = NULL;
+ mad->read_offset = 0;
+ mad->max_rx_buffs = 0;
+ }
+
+ /*convert offset to uint size*/
+ mad->read_offset = (data[2] & MASK_UPPER_WORD);
+ mad->max_rx_buffs = data[2] >> 16;
+
+ mad->dsp_shm_read_ptr = ioremap(data[1],
+ mad->max_rx_buffs * mad->read_offset);
+
+ /* Initialize all buffer numbers and flags */
+ mad->rx_buffer_num = 0;
+ mad->rx_buffer_read = 0;
+ mad->data_written = 0;
+
+ /* Send ACK to the DSP */
+ msg.channel = CHANNEL_NUM_TX;
+ msg.data = &ack_to_dsp;
+ msg.length = NR_OF_DATAWORDS_REQD_FOR_ACK;
+ msg.cb = mad_send_cb;
+ msg.priv = mad;
+
+ if (mbox_channel_send(&msg))
+ dev_err(mad_dev.this_device, "%s: can't send data\n",
+ __func__);
+
+ /* allow read */
+ spin_lock_irqsave(&mad->lock, flags);
+ mad->read_setup_msg = true;
+ spin_unlock_irqrestore(&mad->lock, flags);
+ /* blocked in select() */
+ wake_up_interruptible(&mad->readq);
+
+ } else if (*data == VCS_MBOX_MSG_IF_DEC_DATA) {
+ /*
+ * Check if you have valid message with proper length in message
+ * otherwise Dont care
+ */
+ if ((data[1] <= 0) || (mad->rx_buff == NULL)
+ || (mad->dsp_shm_read_ptr == NULL)) {
+ if (mad->rx_buff == NULL)
+ dev_warn(mad_dev.this_device, "%s :MAD closed",
+ __func__);
+ else
+ dev_warn(mad_dev.this_device, "%s :0-len msg",
+ __func__);
+ } else {
+ mad->rx_buff[mad->rx_buffer_num] = data[1];
+ mad->rx_buffer_num++;
+
+ /* store the offset */
+ mad->rx_buff[mad->rx_buffer_num] = data[2];
+
+ if (mad->rx_buffer_num < ((MAX_NUM_RX_BUFF * 2)-1))
+ mad->rx_buffer_num++;
+ else
+ mad->rx_buffer_num = 0;
+
+ spin_lock_irqsave(&mad->lock, flags);
+ mad->data_written++;
+
+ if (mad->data_written > MAX_NUM_RX_BUFF) {
+ dev_warn(mad_dev.this_device,
+ "%s :Read msg overflow = %u\n",
+ __func__ , mad->data_written);
+ /*
+ * Donot exceed MAX_NUM_RX_BUFF size of buffer
+ * TO DO overflow control
+ */
+ mad->data_written = MAX_NUM_RX_BUFF ;
+ }
+ spin_unlock_irqrestore(&mad->lock, flags);
+ wake_up_interruptible(&mad->readq);
+ }
+ } else {
+ /* received Invalid message */
+ dev_err(mad_dev.this_device, "%s : Invalid Msg", __func__);
+ }
+}
+
+static int mad_read(struct file *filp, char __user *buff, size_t count,
+ loff_t *offp)
+{
+ unsigned long flags;
+ unsigned int size = 0;
+ void __iomem *shm_ptr = NULL;
+
+ dev_dbg(mad_dev.this_device, "%s", __func__);
+
+ if (!(mad->data_written > 0)) {
+ if (wait_event_interruptible(mad->readq,
+ ((mad->data_written > 0) &&
+ (mad->dsp_shm_read_ptr != NULL))))
+ return -ERESTARTSYS;
+ }
+
+ if (mad->dsp_shm_read_ptr == NULL) {
+ dev_err(mad_dev.this_device, "%s :pointer err", __func__);
+ return -EINVAL ;
+ }
+
+ if (mad->rx_buff[mad->rx_buffer_read] > count) {
+ /*
+ * Size of message greater than buffer , this shouldnt happen
+ * It shouldnt come here : we ensured that message size
+ * smaller that buffer length
+ */
+ dev_err(mad_dev.this_device, "%s : Incrct length", __func__);
+ return -EFAULT;
+ }
+ size = mad->rx_buff[mad->rx_buffer_read];
+ mad->rx_buff[mad->rx_buffer_read] = 0;
+ mad->rx_buffer_read++;
+ shm_ptr = (u8 *)(mad->dsp_shm_read_ptr +
+ (mad->rx_buff[mad->rx_buffer_read] * mad->read_offset));
+ if (copy_to_user(buff, shm_ptr, size) < 0) {
+ dev_err(mad_dev.this_device, "%s :copy to user", __func__);
+ return -EFAULT;
+ }
+
+ if (mad->rx_buffer_read < ((MAX_NUM_RX_BUFF*2)-1))
+ mad->rx_buffer_read++;
+ else
+ mad->rx_buffer_read = 0;
+
+ spin_lock_irqsave(&mad->lock, flags);
+ mad->data_written--;
+ if (mad->data_written < 0) {
+ /* Means wrong read*/
+ mad->data_written = 0;
+ dev_err(mad_dev.this_device, "%s :data Rcev err", __func__);
+ }
+ spin_unlock_irqrestore(&mad->lock, flags);
+ return size;
+}
+
+static int mad_write(struct file *filp, const char __user *buff, size_t count,
+ loff_t *offp)
+{
+ int retval = 0;
+ void __iomem *dsp_write_address;
+ struct mbox_channel_msg msg;
+
+ dev_dbg(mad_dev.this_device, "%s", __func__);
+
+ /* check for valid write pointer else skip writing*/
+ if (mad->dsp_shm_write_ptr == NULL) {
+ dev_err(mad_dev.this_device, "%s :Illegal memory", __func__);
+ return -EFAULT;
+ }
+
+ dsp_write_address = (mad->dsp_shm_write_ptr +
+ (mad->tx_buffer_num * mad->write_offset));
+
+ if (copy_from_user(dsp_write_address, buff, count)) {
+ dev_err(mad_dev.this_device, "%s:copy_from_user\n", __func__);
+ return -EFAULT;
+ }
+
+ mad->tx_buff[0] = VCS_MBOX_MSG_IF_ENC_DATA;
+ mad->tx_buff[1] = count;
+ mad->tx_buff[2] = mad->tx_buffer_num;
+
+ if (mad->tx_buffer_num < (mad->max_tx_buffs-1))
+ mad->tx_buffer_num++;
+ else
+ mad->tx_buffer_num = 0;
+
+ msg.channel = CHANNEL_NUM_TX;
+ msg.data = mad->tx_buff;
+ msg.length = MAX_NR_OF_DATAWORDS;
+ msg.cb = mad_send_cb;
+ msg.priv = mad;
+
+ retval = mbox_channel_send(&msg);
+ if (retval) {
+ dev_err(mad_dev.this_device, "%s:can't send data", __func__);
+ return retval;
+ }
+ return count;
+}
+
+static unsigned int mad_select(struct file *filp, poll_table *wait)
+{
+ unsigned int mask = 0;
+ unsigned long flags;
+
+ dev_dbg(mad_dev.this_device, "%s", __func__);
+
+ poll_wait(filp, &mad->readq, wait);
+ spin_lock_irqsave(&mad->lock, flags);
+
+ if ((true == mad->read_setup_msg) && (mad->data_written > 0))
+ mask |= POLLIN | POLLRDNORM; /* allow readable */
+ spin_unlock_irqrestore(&mad->lock, flags);
+
+ return mask;
+}
+
+static int mad_open(struct inode *ino, struct file *filp)
+{
+ int err = 0;
+
+ dev_dbg(mad_dev.this_device, "%s", __func__);
+
+ if (mad->open_check == true) {
+ dev_err(mad_dev.this_device, "%s :Already opened", __func__);
+ return -EFAULT;
+ }
+
+ mad->rx_buff = kzalloc((MAX_NUM_RX_BUFF*2 *
+ sizeof(mad->rx_buff)), GFP_KERNEL);
+
+ if (mad->rx_buff == NULL) {
+ dev_err(mad_dev.this_device, "%s:RX memory\n", __func__);
+ err = -ENOMEM;
+ goto error;
+ }
+
+ mad->tx_buff = kzalloc(MAX_NR_OF_DATAWORDS, GFP_KERNEL);
+ if (mad->tx_buff == NULL) {
+ dev_err(mad_dev.this_device, "%s:TX memory\n", __func__);
+ err = -ENOMEM;
+ goto error;
+ }
+
+ /* Init spinlock for critical section access*/
+ spin_lock_init(&mad->lock);
+ init_waitqueue_head(&(mad->readq));
+
+ err = mbox_channel_register(CHANNEL_NUM_RX, mad_receive_cb, mad);
+ if (err) {
+ dev_err(mad_dev.this_device, "%s: register err", __func__);
+ err = -EFAULT;
+ goto error;
+ }
+ mad->open_check = true;
+
+ return 0;
+error:
+ kfree(mad->rx_buff);
+ kfree(mad->tx_buff);
+ return err;
+}
+
+static int mad_close(struct inode *ino, struct file *filp)
+{
+ dev_dbg(mad_dev.this_device, "%s", __func__);
+
+ if (mbox_channel_deregister(CHANNEL_NUM_RX)) {
+ dev_err(mad_dev.this_device, "%s:deregister err", __func__);
+ return -EFAULT;
+ }
+ kfree(mad->rx_buff);
+ kfree(mad->tx_buff);
+ mad->data_written = 0;
+ mad->rx_buffer_num = 0;
+ mad->rx_buffer_read = 0;
+ mad->open_check = false;
+
+ return 0;
+}
+
+static int __init mad_init(void)
+{
+ dev_dbg(mad_dev.this_device, "%s", __func__);
+
+ mad = kzalloc(sizeof(*mad), GFP_KERNEL);
+ if (mad == NULL) {
+ dev_err(mad_dev.this_device, "%s :MAD failed", __func__);
+ return -ENOMEM;
+ }
+
+ return misc_register(&mad_dev);
+}
+module_init(mad_init);
+
+static void __exit mad_exit(void)
+{
+ dev_dbg(mad_dev.this_device, "%s", __func__);
+
+ if (mad->dsp_shm_write_ptr != NULL) {
+ iounmap(mad->dsp_shm_write_ptr);
+ mad->dsp_shm_write_ptr = NULL;
+ }
+
+ if (mad->dsp_shm_read_ptr != NULL) {
+ iounmap(mad->dsp_shm_read_ptr);
+ mad->dsp_shm_read_ptr = NULL;
+ }
+
+ kfree(mad);
+ misc_deregister(&mad_dev);
+}
diff --git a/drivers/misc/sim_detect.c b/drivers/misc/sim_detect.c
new file mode 100644
index 00000000000..e67f4fad3db
--- /dev/null
+++ b/drivers/misc/sim_detect.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: BIBEK BASU <bibek.basu@stericsson.com>
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/hrtimer.h>
+#include <linux/workqueue.h>
+#include <linux/uaccess.h>
+#include <linux/modem/modem_client.h>
+#include <mach/sim_detect.h>
+#include <linux/regulator/consumer.h>
+
+/* time in millisec */
+#define TIMER_DELAY 10
+
+struct sim_detect{
+ struct work_struct timer_expired;
+ struct device *dev;
+ struct modem *modem;
+ struct hrtimer timer;
+ struct mutex lock;
+ int voltage;
+ struct regulator *vinvsim_regulator;
+ bool regulator_enabled;
+};
+
+static ssize_t show_voltage(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sim_detect *data = dev_get_drvdata(dev);
+ int ret, len;
+
+ ret = mutex_lock_interruptible(&data->lock);
+ if (ret < 0)
+ return ret;
+
+ len = sprintf(buf, "%i\n", data->voltage);
+
+ mutex_unlock(&data->lock);
+
+ return len;
+}
+
+static ssize_t write_voltage(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sim_detect *sim_detect = dev_get_drvdata(dev);
+ long val;
+ int ret;
+
+ /* check input */
+ if (strict_strtol(buf, 0, &val) != 0) {
+ dev_err(dev, "Invalid voltage class configured.\n");
+ return -EINVAL;
+ }
+
+ switch (val) {
+ case -1:
+ case 0:
+ case 1800000:
+ case 3000000:
+ break;
+ default:
+ dev_err(dev, "Invalid voltage class configured.\n");
+ return -EINVAL;
+ }
+
+ /* lock */
+ ret = mutex_lock_interruptible(&sim_detect->lock);
+ if (ret < 0)
+ return ret;
+
+ /* update state */
+ sim_detect->voltage = val;
+
+ /* call regulator */
+ switch (sim_detect->voltage) {
+ case 0:
+ /* SIM voltage is unknown, turn on regulator for 3 V SIM */
+ case 3000000:
+ /* Vinvsim supply is used only for 3 V SIM */
+ if (!sim_detect->regulator_enabled) {
+ ret = regulator_enable(sim_detect->vinvsim_regulator);
+ if (ret) {
+ dev_err(dev, "Failed to enable regulator.\n");
+ goto out_unlock;
+ }
+ sim_detect->regulator_enabled = true;
+ }
+ break;
+ case 1800000:
+ case -1:
+ /* Vbatvsim is used otherwise */
+ if (sim_detect->regulator_enabled) {
+ regulator_disable(sim_detect->vinvsim_regulator);
+ sim_detect->regulator_enabled = false;
+ }
+ }
+
+out_unlock:
+ /* unlock and return */
+ mutex_unlock(&sim_detect->lock);
+
+ return count;
+}
+
+static DEVICE_ATTR(voltage, S_IWUGO | S_IRUGO, show_voltage, write_voltage);
+
+static struct attribute *sim_attributes[] = {
+ &dev_attr_voltage.attr,
+ NULL
+};
+
+static const struct attribute_group sim_attr_group = {
+ .attrs = sim_attributes,
+};
+
+static void inform_modem_release(struct work_struct *work)
+{
+ struct sim_detect *sim_detect =
+ container_of(work, struct sim_detect, timer_expired);
+
+ /* call Modem Access Framework api to release modem */
+ modem_release(sim_detect->modem);
+}
+
+static enum hrtimer_restart timer_callback(struct hrtimer *timer)
+{
+ struct sim_detect *sim_detect =
+ container_of(timer, struct sim_detect, timer);
+
+ schedule_work(&sim_detect->timer_expired);
+ return HRTIMER_NORESTART;
+}
+
+static irqreturn_t sim_activity_irq(int irq, void *dev)
+{
+ struct sim_detect *sim_detect = dev;
+
+ /* call Modem Access Framework api to acquire modem */
+ modem_request(sim_detect->modem);
+ /* start the timer for 10ms */
+ hrtimer_start(&sim_detect->timer,
+ ktime_set(0, TIMER_DELAY*NSEC_PER_MSEC),
+ HRTIMER_MODE_REL);
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+/**
+ * sim_detect_suspend() - This routine puts the Sim detect in to sustend state.
+ * @dev: pointer to device structure.
+ *
+ * This routine checks the current ongoing communication with Modem by
+ * examining the modem_get_usage and work_pending state.
+ * accordingly prevents suspend if modem communication
+ * is on-going.
+ */
+int sim_detect_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct sim_detect *sim_detect = platform_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "%s called...\n", __func__);
+ /* if modem is accessed, event system suspend */
+ if (modem_get_usage(sim_detect->modem)
+ || work_pending(&sim_detect->timer_expired))
+ return -EBUSY;
+ else
+ return 0;
+}
+
+static const struct dev_pm_ops sim_detect_dev_pm_ops = {
+ .suspend = sim_detect_suspend,
+};
+#endif
+
+
+static int __devinit sim_detect_probe(struct platform_device *pdev)
+{
+ struct sim_detect_platform_data *plat = dev_get_platdata(&pdev->dev);
+ struct sim_detect *sim_detect;
+ int ret;
+
+ sim_detect = kzalloc(sizeof(struct sim_detect), GFP_KERNEL);
+ if (sim_detect == NULL) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ /* initialize data */
+ mutex_init(&sim_detect->lock);
+ sim_detect->voltage = 0;
+
+ sim_detect->dev = &pdev->dev;
+ INIT_WORK(&sim_detect->timer_expired, inform_modem_release);
+ hrtimer_init(&sim_detect->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ sim_detect->timer.function = timer_callback;
+
+ sim_detect->modem = modem_get(sim_detect->dev, "u8500-shrm-modem");
+ if (IS_ERR(sim_detect->modem)) {
+ ret = PTR_ERR(sim_detect->modem);
+ dev_err(sim_detect->dev, "Could not retrieve the modem\n");
+ goto out_free;
+ }
+
+ /* set drvdata */
+ platform_set_drvdata(pdev, sim_detect);
+
+ /* request irq */
+ ret = request_threaded_irq(plat->irq_num,
+ NULL, sim_activity_irq,
+ IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING |
+ IRQF_NO_SUSPEND,
+ "sim activity", sim_detect);
+ if (ret < 0)
+ goto out_put_modem;
+
+ /* get regulator */
+ sim_detect->regulator_enabled = false;
+ sim_detect->vinvsim_regulator = regulator_get(sim_detect->dev,
+ "vinvsim");
+ if (IS_ERR(sim_detect->vinvsim_regulator)) {
+ dev_err(&pdev->dev,
+ "Failed to get regulator. (dev_name %s).\n",
+ dev_name(sim_detect->dev));
+ ret = PTR_ERR(sim_detect->vinvsim_regulator);
+ goto out_free_irq;
+ }
+
+ /* register sysfs entry */
+ ret = sysfs_create_group(&pdev->dev.kobj, &sim_attr_group);
+ if (ret != 0) {
+ dev_err(&pdev->dev,
+ "Failed to create attribute group: %d\n", ret);
+ goto out_free_regulator;
+ }
+
+ return 0;
+
+out_free_regulator:
+ regulator_put(sim_detect->vinvsim_regulator);
+out_free_irq:
+ free_irq(plat->irq_num, sim_detect);
+out_put_modem:
+ modem_put(sim_detect->modem);
+ platform_set_drvdata(pdev, NULL);
+out_free:
+ kfree(sim_detect);
+ return ret;
+}
+
+static int __devexit sim_detect_remove(struct platform_device *pdev)
+{
+ struct sim_detect *sim_detect = platform_get_drvdata(pdev);
+
+ sysfs_remove_group(&pdev->dev.kobj, &sim_attr_group);
+ regulator_put(sim_detect->vinvsim_regulator);
+ modem_put(sim_detect->modem);
+ platform_set_drvdata(pdev, NULL);
+ kfree(sim_detect);
+ return 0;
+}
+
+static struct platform_driver sim_detect_driver = {
+ .driver = {
+ .name = "sim-detect",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &sim_detect_dev_pm_ops,
+#endif
+ },
+ .probe = sim_detect_probe,
+ .remove = __devexit_p(sim_detect_remove),
+};
+
+static int __init sim_detect_init(void)
+{
+ return platform_driver_register(&sim_detect_driver);
+}
+module_init(sim_detect_init);
+
+static void __exit sim_detect_exit(void)
+{
+ platform_driver_unregister(&sim_detect_driver);
+}
+module_exit(sim_detect_exit);
+
+MODULE_AUTHOR("BIBEK BASU <bibek.basu@stericsson.com>");
+MODULE_DESCRIPTION("Detects SIM Hot Swap and wakes modem");
+MODULE_ALIAS("platform:sim-detect");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/modem/Kconfig b/drivers/modem/Kconfig
new file mode 100644
index 00000000000..be8476ed0f9
--- /dev/null
+++ b/drivers/modem/Kconfig
@@ -0,0 +1,44 @@
+config MODEM
+ bool "Modem Access Framework"
+ default y
+ help
+ Add support for Modem Access Framework. It allows different
+ platform specific drivers to register modem access mechanisms
+ and allows transparent access to modem to the client drivers.
+
+ If unsure, say N.
+
+config MODEM_U5500_MCDD
+ tristate "Modem crash dump detection driver for STE U5500 platform"
+ depends on (UX500_SOC_DB5500 && U5500_MODEM_IRQ && MODEM)
+ default y
+ help
+ Add support for Modem crash detection
+ driver for STE U5500 platform.
+ And inform userspace.
+
+ If unsure, say N.
+
+config MODEM_U8500
+ bool "Modem Access driver for STE U8500 platform"
+ depends on MODEM
+ default n
+ help
+ Add support for Modem Access driver on STE U8500 platform which
+ uses Shared Memroy as IPC mechanism between Modem processor and
+ Application processor.
+
+ If unsure, say N.
+
+source "drivers/modem/shrm/Kconfig"
+
+config MODEM_M6718
+ tristate "Modem Access driver for STE M6718 modem"
+ depends on MODEM
+ default n
+ help
+ Add support for the modem access driver for the M6718 modem.
+
+ If unsure, say N.
+
+source "drivers/modem/m6718_spi/Kconfig"
diff --git a/drivers/modem/Makefile b/drivers/modem/Makefile
new file mode 100644
index 00000000000..82921988f27
--- /dev/null
+++ b/drivers/modem/Makefile
@@ -0,0 +1,6 @@
+obj-$(CONFIG_MODEM) := modem_access.o
+obj-$(CONFIG_MODEM_U8500) += modem_u8500.o
+obj-$(CONFIG_U8500_SHRM) += shrm/
+obj-$(CONFIG_MODEM_M6718) += modem_m6718.o
+obj-$(CONFIG_MODEM_M6718_SPI) += m6718_spi/
+obj-$(CONFIG_MODEM_U5500_MCDD) += mcdd.o
diff --git a/drivers/modem/m6718_spi/Kconfig b/drivers/modem/m6718_spi/Kconfig
new file mode 100644
index 00000000000..f945d24a094
--- /dev/null
+++ b/drivers/modem/m6718_spi/Kconfig
@@ -0,0 +1,83 @@
+#
+# M6718 modem SPI IPC driver kernel configuration
+#
+config MODEM_M6718_SPI
+ tristate "M6718 modem IPC SPI driver"
+ depends on MODEM_M6718
+ default y
+ ---help---
+ If you say Y here, you will enable the M6718 modem IPC SPI driver.
+
+ If unsure, say Y.
+
+config MODEM_M6718_SPI_DEBUG
+ boolean "Modem driver debug"
+ depends on MODEM_M6718_SPI
+ default N
+ ---help---
+ If you say Y here, you will enable full debug trace from the M6718
+ modem driver. This should not be enabled by default.
+
+ If unsure, say N.
+
+config MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
+ boolean "M6718 modem state driver integration"
+ depends on MODEM_M6718_SPI
+ default y
+ ---help---
+ Enables integration of the IPC driver with the modem state driver.
+ This allows the IPC driver to be notified of changes in modem state
+ (on, off, reset) and allows the IPC driver to cause modem state
+ changes if needed.
+
+ By default this should be enabled.
+
+config MODEM_M6718_SPI_ENABLE_FEATURE_FRAME_DUMP
+ boolean "IPC SPI L1 frame dump"
+ depends on MODEM_M6718_SPI
+ default n
+ ---help---
+ If you say Y here, you will enable dumping of the raw TX and RX frames
+ by the IPC driver L1.
+
+ If unsure, say N.
+
+config MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK
+ boolean "Modem IPC loopback support"
+ depends on MODEM_M6718_SPI
+ default y
+ ---help---
+ If you say Y here, you will enable the IPC loopback channels/devices.
+
+ If unsure, say Y.
+
+
+config MODEM_M6718_SPI_ENABLE_FEATURE_VERIFY_FRAMES
+ boolean "Verify loopback frames"
+ depends on MODEM_M6718_SPI
+ default n
+ ---help---
+ This will enabling checking of loopback frames to verify that the data
+ received is identical to the data sent.
+
+ If unsure, say N.
+
+config MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ boolean "Modem IPC throughput measurement"
+ depends on MODEM_M6718_SPI
+ default n
+ ---help---
+ If you say Y here, you will enable the IPC link throughput
+ measurement and reporting.
+
+ If unsure, say N.
+
+config MODEM_M6718_SPI_SET_THROUGHPUT_FREQUENCY
+ int "Sample rate for throughput measurements (seconds)"
+ default "5"
+ depends on MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ help
+ The sample frequency for taking IPC SPI link throughput measurements.
+ Increasing the rate (reducing the time) will increase the accuracy of
+ the measurements, but will also increase the impact on link and system
+ performance.
diff --git a/drivers/modem/m6718_spi/Makefile b/drivers/modem/m6718_spi/Makefile
new file mode 100644
index 00000000000..a0a82c30b07
--- /dev/null
+++ b/drivers/modem/m6718_spi/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for M6718 SPI driver
+#
+ifeq ($(CONFIG_MODEM_M6718_SPI_DEBUG),y)
+EXTRA_CFLAGS += -DDEBUG
+endif
+
+m6718_modem_spi-objs := modem_driver.o protocol.o util.o queue.o debug.o \
+ netlink.o statemachine.o
+
+ifeq ($(CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE),y)
+m6718_modem_spi-objs += modem_state.o
+endif
+
+obj-$(CONFIG_MODEM_M6718_SPI) += m6718_modem_spi.o
diff --git a/drivers/modem/m6718_spi/debug.c b/drivers/modem/m6718_spi/debug.c
new file mode 100644
index 00000000000..06ee4c34a5a
--- /dev/null
+++ b/drivers/modem/m6718_spi/debug.c
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010,2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * U9500 <-> M6718 IPC protocol implementation using SPI:
+ * debug functionality.
+ */
+#include <linux/gpio.h>
+#include <linux/modem/m6718_spi/modem_driver.h>
+#include "modem_debug.h"
+#include "modem_private.h"
+#include "modem_util.h"
+#include "modem_queue.h"
+
+/* name of each state - must match enum ipc_sm_state_id */
+static const char * const sm_state_id_str[] = {
+ "IPC_INIT",
+ "IPC_HALT",
+ "IPC_RESET",
+ "IPC_WAIT_SLAVE_STABLE",
+ "IPC_WAIT_HANDSHAKE_INACTIVE",
+ "IPC_SLW_TX_BOOTREQ",
+ "IPC_ACT_TX_BOOTREQ",
+ "IPC_SLW_RX_BOOTRESP",
+ "IPC_ACT_RX_BOOTRESP",
+ "IPC_IDL",
+ "IPC_SLW_TX_WR_CMD",
+ "IPC_ACT_TX_WR_CMD",
+ "IPC_SLW_TX_WR_DAT",
+ "IPC_ACT_TX_WR_DAT",
+ "IPC_SLW_TX_RD_CMD",
+ "IPC_ACT_TX_RD_CMD",
+ "IPC_SLW_RX_WR_CMD",
+ "IPC_ACT_RX_WR_CMD",
+ "IPC_ACT_RX_WR_DAT",
+};
+
+/* name of each state machine run cause */
+static const char * const sm_run_cause_str[] = {
+ [IPC_SM_RUN_NONE] = "IPC_SM_RUN_NONE",
+ [IPC_SM_RUN_SLAVE_IRQ] = "IPC_SM_RUN_SLAVE_IRQ",
+ [IPC_SM_RUN_TFR_COMPLETE] = "IPC_SM_RUN_TFR_COMPLETE",
+ [IPC_SM_RUN_TX_REQ] = "IPC_SM_RUN_TX_REQ",
+ [IPC_SM_RUN_INIT] = "IPC_SM_RUN_INIT",
+ [IPC_SM_RUN_ABORT] = "IPC_SM_RUN_ABORT",
+ [IPC_SM_RUN_COMMS_TMO] = "IPC_SM_RUN_COMMS_TMO",
+ [IPC_SM_RUN_STABLE_TMO] = "IPC_SM_RUN_STABLE_TMO",
+ [IPC_SM_RUN_RESET] = "IPC_SM_RUN_RESET"
+};
+
+
+#if defined DUMP_SPI_TFRS || \
+ defined CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_FRAME_DUMP
+static const char *format_buf(const void *buffer, int len)
+{
+ static char dumpbuf[6000];
+ char *wr = dumpbuf;
+ const char *rd = buffer;
+ int maxlen = min(len, (int)(sizeof(dumpbuf) / 3));
+ int i;
+
+ for (i = 0 ; i < maxlen ; i++) {
+ sprintf(wr, "%02x ", rd[i]);
+ wr += 3;
+ }
+ return dumpbuf;
+}
+#endif
+
+void ipc_dbg_dump_frame(struct device *dev, int linkid,
+ struct ipc_tx_queue *frame, bool tx)
+{
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_FRAME_DUMP
+ if (frame->actual_len == 0)
+ return;
+
+ /*
+ * Use printk(KERN_DEBUG... directly to ensure these are printed even
+ * when DEBUG is not defined for this device - we want to be able to
+ * dump the frames independently from the debug logging.
+ */
+ printk(KERN_DEBUG "IPC link%d %s %3d %4d bytes:%s\n",
+ linkid, (tx ? "TX" : "RX"), frame->counter, frame->len,
+ format_buf(frame->data, frame->len));
+#endif
+}
+
+void ipc_dbg_dump_spi_tfr(struct ipc_link_context *context)
+{
+#ifdef DUMP_SPI_TFRS
+ struct spi_transfer *tfr = &context->spi_transfer;
+ struct spi_message *msg = &context->spi_message;
+
+ if (tfr->tx_buf != NULL)
+ dev_info(&context->sdev->dev, "link%d TX %4d bytes:%s\n",
+ context->link->id, msg->actual_length,
+ format_buf(tfr->tx_buf, msg->actual_length));
+
+ if (tfr->rx_buf != NULL)
+ dev_info(&context->sdev->dev, "link%d RX %4d bytes:%s\n",
+ context->link->id, msg->actual_length,
+ format_buf(tfr->rx_buf, msg->actual_length));
+#endif
+}
+
+const char *ipc_dbg_state_id(const struct ipc_sm_state *state)
+{
+ if (state == NULL)
+ return "(unknown)";
+ else
+ return sm_state_id_str[state->id];
+}
+
+const char *ipc_dbg_event(u8 event)
+{
+ return sm_run_cause_str[event];
+}
+
+char *ipc_dbg_link_state_str(struct ipc_link_context *context)
+{
+ char *statestr;
+ int ss_pin;
+ int int_pin;
+ int min_free_pc;
+
+ if (context == NULL)
+ return NULL;
+
+ statestr = kmalloc(500, GFP_ATOMIC);
+ if (statestr == NULL)
+ return NULL;
+
+ ss_pin = gpio_get_value(context->link->gpio.ss_pin);
+ int_pin = gpio_get_value(context->link->gpio.int_pin);
+ min_free_pc = context->tx_q_min > 0 ?
+ (context->tx_q_min * 100) / IPC_TX_QUEUE_MAX_SIZE :
+ 0;
+
+ sprintf(statestr,
+ "state=%s (for %lus)\n"
+ "ss=%s(%d)\n"
+ "int=%s(%d)\n"
+ "lastevent=%s\n"
+ "lastignored=%s in %s (ignoredinthis=%d)\n"
+ "tx_q_min=%d(%d%%)\n"
+ "tx_q_count=%d\n"
+ "lastcmd=0x%08x (type %d count %d len %d)\n",
+ sm_state_id_str[context->state->id],
+ (jiffies - context->statesince) / HZ,
+ ss_pin == ipc_util_ss_level_active(context) ?
+ "ACTIVE" : "INACTIVE",
+ ss_pin,
+ int_pin == ipc_util_int_level_active(context) ?
+ "ACTIVE" : "INACTIVE",
+ int_pin,
+ sm_run_cause_str[context->lastevent],
+ sm_run_cause_str[context->lastignored],
+ sm_state_id_str[context->lastignored_in],
+ context->lastignored_inthis,
+ context->tx_q_min,
+ min_free_pc,
+ atomic_read(&context->tx_q_count),
+ context->cmd,
+ ipc_util_get_l1_cmd(context->cmd),
+ ipc_util_get_l1_counter(context->cmd),
+ ipc_util_get_l1_length(context->cmd));
+ return statestr;
+}
+
+void ipc_dbg_verify_rx_frame(struct ipc_link_context *context)
+{
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_VERIFY_FRAMES
+ int i;
+ u8 *last;
+ u8 *curr;
+ bool good = true;
+
+ if (context->last_frame == NULL)
+ return;
+
+ if (context->last_frame->actual_len != context->frame->actual_len) {
+ dev_err(&context->sdev->dev,
+ "link %d error: loopback frame length error, "
+ "TX %d RX %d\n",
+ context->link->id,
+ context->last_frame->actual_len,
+ context->frame->actual_len);
+ good = false;
+ goto out;
+ }
+
+ last = (u8 *)context->last_frame->data;
+ curr = (u8 *)context->frame->data;
+
+ /* skip any padding bytes */
+ for (i = 0; i < context->last_frame->actual_len; i++) {
+ if (last[i] != curr[i]) {
+ dev_err(&context->sdev->dev,
+ "link %d bad byte %05d: "
+ "TX %02x RX %02x\n",
+ context->link->id,
+ i,
+ last[i],
+ curr[i]);
+ good = false;
+ }
+ }
+
+out:
+ if (!good)
+ dev_info(&context->sdev->dev,
+ "link %d error: loopback frame verification failed!\n",
+ context->link->id);
+
+ ipc_queue_delete_frame(context->last_frame);
+ context->last_frame = NULL;
+#endif
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int debugfs_linkstate_open(struct inode *inode, struct file *file);
+static int debugfs_linkstate_show(struct seq_file *s, void *data);
+
+static int debugfs_msr_open(struct inode *inode, struct file *file);
+static int debugfs_msr_show(struct seq_file *s, void *data);
+static ssize_t debugfs_msr_write(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos);
+
+static const struct file_operations debugfs_fops = {
+ .open = debugfs_linkstate_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release
+};
+
+static const struct file_operations debugfs_msr_fops = {
+ .open = debugfs_msr_open,
+ .read = seq_read,
+ .write = debugfs_msr_write,
+ .llseek = seq_lseek,
+ .release = single_release
+};
+
+static int debugfs_linkstate_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, debugfs_linkstate_show, inode->i_private);
+}
+
+static int debugfs_linkstate_show(struct seq_file *s, void *data)
+{
+ struct ipc_link_context *context = s->private;
+ char *statestr;
+
+ if (context == NULL) {
+ seq_printf(s, "invalid context\n");
+ return 0;
+ }
+
+ statestr = ipc_dbg_link_state_str(context);
+ if (statestr == NULL) {
+ seq_printf(s, "unable to get link state string\n");
+ return 0;
+ }
+
+ seq_printf(s, "%s:\n%s", context->link->name, statestr);
+ kfree(statestr);
+ return 0;
+}
+
+static int debugfs_msr_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, debugfs_msr_show, inode->i_private);
+}
+
+static int debugfs_msr_show(struct seq_file *s, void *data)
+{
+ struct ipc_l1_context *context = s->private;
+
+ if (context == NULL) {
+ seq_printf(s, "invalid context\n");
+ return 0;
+ }
+
+ seq_printf(s, "msr %s\n",
+ context->msr_disable ? "disabled" : "enabled");
+ return 0;
+}
+
+static ssize_t debugfs_msr_write(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ char buf[128];
+ int buf_size;
+
+ /* get user space string and assure termination */
+ buf_size = min(count, (sizeof(buf) - 1));
+ if (copy_from_user(buf, user_buf, buf_size))
+ return -EFAULT;
+
+ buf[buf_size] = 0;
+
+ if (buf[0] == '0' || buf[0] == 'd') {
+ pr_info("disabling msr\n");
+ l1_context.msr_disable = true;
+ } else if (buf[0] == '1' || buf[0] == 'e') {
+ pr_info("enabling msr\n");
+ l1_context.msr_disable = false;
+ } else {
+ pr_info("unknown request\n");
+ }
+
+ return buf_size;
+}
+#endif /* CONFIG_DEBUG_FS */
+
+void ipc_dbg_debugfs_init(void)
+{
+#ifdef CONFIG_DEBUG_FS
+ /* create debugfs directory entry for ipc in debugfs root */
+ l1_context.debugfsdir = debugfs_create_dir("modemipc", NULL);
+ l1_context.debugfs_silentreset =
+ debugfs_create_file("msrenable", S_IRUSR | S_IWUSR,
+ l1_context.debugfsdir, &l1_context, &debugfs_msr_fops);
+ if (l1_context.debugfs_silentreset == NULL)
+ pr_err("failed to create debugfs MSR control file\n");
+#endif
+}
+
+void ipc_dbg_debugfs_link_init(struct ipc_link_context *context)
+{
+#ifdef CONFIG_DEBUG_FS
+ context->debugfsfile = NULL;
+ context->lastevent = IPC_SM_RUN_NONE;
+ context->lastignored = IPC_SM_RUN_NONE;
+ context->lastignored_in = IPC_SM_IDL;
+ context->lastignored_inthis = false;
+ context->tx_q_min = IPC_TX_QUEUE_MAX_SIZE;
+ context->statesince = 0;
+
+ if (l1_context.debugfsdir != NULL) {
+ context->debugfsfile =
+ debugfs_create_file(context->link->name, S_IRUGO,
+ l1_context.debugfsdir, context, &debugfs_fops);
+ if (context->debugfsfile == NULL)
+ dev_err(&context->sdev->dev,
+ "link %d: failed to create debugfs file %s\n",
+ context->link->id,
+ context->link->name);
+ }
+#endif
+}
+
+void ipc_dbg_ignoring_event(struct ipc_link_context *context, u8 event)
+{
+#ifdef CONFIG_DEBUG_FS
+ context->lastignored = event;
+ context->lastignored_in = context->state->id;
+ context->lastignored_inthis = true;
+#endif
+}
+
+void ipc_dbg_handling_event(struct ipc_link_context *context, u8 event)
+{
+#ifdef CONFIG_DEBUG_FS
+ context->lastevent = event;
+ context->lastignored_inthis = false;
+#endif
+}
+
+void ipc_dbg_entering_state(struct ipc_link_context *context)
+{
+#ifdef CONFIG_DEBUG_FS
+ context->statesince = jiffies;
+#endif
+}
+
+void ipc_dbg_enter_idle(struct ipc_link_context *context)
+{
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ context->idl_idle_enter = jiffies;
+#endif
+}
+
+void ipc_dbg_exit_idle(struct ipc_link_context *context)
+{
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ context->idl_idle_total += jiffies - context->idl_idle_enter;
+#endif
+}
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+static int measure_usage(struct ipc_link_context *context)
+{
+ unsigned long now = jiffies;
+ unsigned long idle;
+ unsigned long total;
+
+ if (ipc_util_link_is_idle(context))
+ ipc_dbg_exit_idle(context);
+
+ idle = context->idl_idle_total;
+ total = now - context->idl_measured_at;
+
+ context->idl_measured_at = now;
+ context->idl_idle_total = 0;
+ if (ipc_util_link_is_idle(context))
+ context->idl_idle_enter = now;
+
+ return 100 - ((idle * 100) / total);
+}
+#endif
+
+void ipc_dbg_measure_throughput(unsigned long unused)
+{
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ u32 tx_bps_0, tx_bps_1;
+ u32 rx_bps_0, rx_bps_1;
+ int pc0, pc1;
+
+ tx_bps_0 = tx_bps_1 = 0;
+ rx_bps_0 = rx_bps_1 = 0;
+
+ /* link0 */
+ tx_bps_0 = (l1_context.device_context[0].tx_bytes * 8) /
+ CONFIG_MODEM_M6718_SPI_SET_THROUGHPUT_FREQUENCY;
+ rx_bps_0 = (l1_context.device_context[0].rx_bytes * 8) /
+ CONFIG_MODEM_M6718_SPI_SET_THROUGHPUT_FREQUENCY;
+ l1_context.device_context[0].tx_bytes = 0;
+ l1_context.device_context[0].rx_bytes = 0;
+ pc0 = measure_usage(&l1_context.device_context[0]);
+#if IPC_NBR_SUPPORTED_SPI_LINKS > 0
+ /* link1 */
+ tx_bps_1 = (l1_context.device_context[1].tx_bytes * 8) /
+ CONFIG_MODEM_M6718_SPI_SET_THROUGHPUT_FREQUENCY;
+ rx_bps_1 = (l1_context.device_context[1].rx_bytes * 8) /
+ CONFIG_MODEM_M6718_SPI_SET_THROUGHPUT_FREQUENCY;
+ l1_context.device_context[1].tx_bytes = 0;
+ l1_context.device_context[1].rx_bytes = 0;
+ pc1 = measure_usage(&l1_context.device_context[1]);
+#endif
+
+ pr_info("IPC THROUGHPUT (bit/s): "
+ "link0 TX:%8d RX:%8d %3d%% "
+ "link1 TX:%8d RX:%8d %3d%%\n",
+ tx_bps_0, rx_bps_0, pc0,
+ tx_bps_1, rx_bps_1, pc1);
+
+ /* restart the measurement timer */
+ l1_context.tp_timer.expires = jiffies +
+ (CONFIG_MODEM_M6718_SPI_SET_THROUGHPUT_FREQUENCY * HZ);
+ add_timer(&l1_context.tp_timer);
+#endif
+}
+
+void ipc_dbg_throughput_init(void)
+{
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ pr_info("M6718 IPC throughput measurement interval: %d\n",
+ CONFIG_MODEM_M6718_SPI_SET_THROUGHPUT_FREQUENCY);
+ /* init the throughput measurement timer */
+ init_timer(&l1_context.tp_timer);
+ l1_context.tp_timer.function = ipc_dbg_measure_throughput;
+ l1_context.tp_timer.data = 0;
+#endif
+}
+
+void ipc_dbg_throughput_link_init(struct ipc_link_context *context)
+{
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ context->tx_bytes = 0;
+ context->rx_bytes = 0;
+ context->idl_measured_at = jiffies;
+ context->idl_idle_enter = 0;
+ context->idl_idle_total = 0;
+#endif
+}
+
diff --git a/drivers/modem/m6718_spi/modem_debug.h b/drivers/modem/m6718_spi/modem_debug.h
new file mode 100644
index 00000000000..9a2fa39acb4
--- /dev/null
+++ b/drivers/modem/m6718_spi/modem_debug.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * Modem IPC driver protocol interface header:
+ * debug functionality.
+ */
+#ifndef _MODEM_DEBUG_H_
+#define _MODEM_DEBUG_H_
+
+#include "modem_private.h"
+
+void ipc_dbg_dump_frame(struct device *dev, int linkid,
+ struct ipc_tx_queue *frame, bool tx);
+void ipc_dbg_dump_spi_tfr(struct ipc_link_context *context);
+const char *ipc_dbg_state_id(const struct ipc_sm_state *state);
+const char *ipc_dbg_event(u8 event);
+char *ipc_dbg_link_state_str(struct ipc_link_context *context);
+void ipc_dbg_verify_rx_frame(struct ipc_link_context *context);
+
+void ipc_dbg_debugfs_init(void);
+void ipc_dbg_debugfs_link_init(struct ipc_link_context *context);
+
+void ipc_dbg_ignoring_event(struct ipc_link_context *context, u8 event);
+void ipc_dbg_handling_event(struct ipc_link_context *context, u8 event);
+void ipc_dbg_entering_state(struct ipc_link_context *context);
+void ipc_dbg_enter_idle(struct ipc_link_context *context);
+void ipc_dbg_exit_idle(struct ipc_link_context *context);
+void ipc_dbg_measure_throughput(unsigned long unused);
+void ipc_dbg_throughput_init(void);
+void ipc_dbg_throughput_link_init(struct ipc_link_context *context);
+
+#endif /* _MODEM_DEBUG_H_ */
diff --git a/drivers/modem/m6718_spi/modem_driver.c b/drivers/modem/m6718_spi/modem_driver.c
new file mode 100644
index 00000000000..8086e97aa7c
--- /dev/null
+++ b/drivers/modem/m6718_spi/modem_driver.c
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ * based on modem_shrm_driver.c
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * SPI driver implementing the M6718 inter-processor communication protocol.
+ */
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/spi/spi.h>
+#include <linux/modem/modem_client.h>
+#include <linux/modem/m6718_spi/modem_driver.h>
+#include <linux/modem/m6718_spi/modem_net.h>
+#include <linux/modem/m6718_spi/modem_char.h>
+#include "modem_protocol.h"
+
+#ifdef CONFIG_PHONET
+static void phonet_rcv_tasklet_func(unsigned long);
+static struct tasklet_struct phonet_rcv_tasklet;
+#endif
+
+static struct modem_spi_dev modem_driver_data = {
+ .dev = NULL,
+ .ndev = NULL,
+ .modem = NULL,
+ .isa_context = NULL,
+ .netdev_flag_up = 0
+};
+
+/**
+ * modem_m6718_spi_receive() - Receive a frame from L1 physical layer
+ * @sdev: pointer to spi device structure
+ * @channel: L2 mux channel id
+ * @len: frame data length
+ * @data: pointer to frame data
+ *
+ * This function is called from the driver L1 physical transport layer. It
+ * copies the frame data to the receive queue for the channel on which the data
+ * was received.
+ *
+ * Special handling is given to slave-loopback channels where the data is simply
+ * sent back to the modem on the same channel.
+ *
+ * Special handling is given to the ISI channel when PHONET is enabled - the
+ * phonet tasklet is scheduled in order to pump the received data through the
+ * net device interface.
+ */
+int modem_m6718_spi_receive(struct spi_device *sdev, u8 channel,
+ u32 len, void *data)
+{
+ u32 size = 0;
+ int ret = 0;
+ int idx;
+ u8 *psrc;
+ u32 writeptr;
+ struct message_queue *q;
+ struct isa_device_context *isadev;
+
+ dev_dbg(&sdev->dev, "L2 received frame from L1: channel %d len %d\n",
+ channel, len);
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK
+ if (channel == MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0 ||
+ channel == MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1) {
+ /* data received on slave loopback channel - loop it back */
+ modem_m6718_spi_send(&modem_driver_data, channel, len, data);
+ return 0;
+ }
+#endif
+
+ /* find the isa device index for this L2 channel */
+ idx = modem_get_cdev_index(channel);
+ if (idx < 0) {
+ dev_err(&sdev->dev, "failed to get isa device index\n");
+ return idx;
+ }
+ isadev = &modem_driver_data.isa_context->isadev[idx];
+ q = &isadev->dl_queue;
+
+ spin_lock(&q->update_lock);
+
+ /* verify message can be contained in buffer */
+ writeptr = q->writeptr;
+ ret = modem_isa_queue_msg(q, len);
+ if (ret >= 0) {
+ /* memcopy RX data */
+ if ((writeptr + len) >= q->size) {
+ psrc = (u8 *)data;
+ size = q->size - writeptr;
+ /* copy first part of msg */
+ memcpy((q->fifo_base + writeptr), psrc, size);
+ psrc += size;
+ /* copy second part of msg at the top of fifo */
+ memcpy(q->fifo_base, psrc, (len - size));
+ } else {
+ memcpy((q->fifo_base + writeptr), data, len);
+ }
+ }
+ spin_unlock(&q->update_lock);
+
+ if (ret < 0) {
+ dev_err(&sdev->dev, "failed to queue frame!");
+ return ret;
+ }
+
+#ifdef CONFIG_PHONET
+ if (channel == MODEM_M6718_SPI_CHN_ISI &&
+ modem_driver_data.netdev_flag_up)
+ tasklet_schedule(&phonet_rcv_tasklet);
+#endif
+ return ret;
+}
+EXPORT_SYMBOL_GPL(modem_m6718_spi_receive);
+
+static void phonet_rcv_tasklet_func(unsigned long unused)
+{
+ ssize_t result;
+
+ dev_dbg(modem_driver_data.dev, "receiving frames for phonet\n");
+ /* continue receiving while there are frames in the queue */
+ for (;;) {
+ result = modem_net_receive(modem_driver_data.ndev);
+ if (result == 0) {
+ dev_dbg(modem_driver_data.dev,
+ "queue is empty, finished receiving\n");
+ break;
+ }
+ if (result < 0) {
+ dev_err(modem_driver_data.dev,
+ "failed to receive frame from queue!\n");
+ break;
+ }
+ }
+}
+
+static int spi_probe(struct spi_device *sdev)
+{
+ int result = 0;
+
+ spi_set_drvdata(sdev, &modem_driver_data);
+
+ if (modem_protocol_probe(sdev) != 0) {
+ dev_err(&sdev->dev,
+ "failed to initialise link protocol\n");
+ result = -ENODEV;
+ goto rollback;
+ }
+
+ /*
+ * Since we can have multiple spi links for the same modem, only
+ * initialise the modem data and char/net interfaces once.
+ */
+ if (modem_driver_data.dev == NULL) {
+ modem_driver_data.dev = &sdev->dev;
+ modem_driver_data.modem =
+ modem_get(modem_driver_data.dev, "m6718");
+ if (modem_driver_data.modem == NULL) {
+ dev_err(&sdev->dev,
+ "failed to retrieve modem description\n");
+ result = -ENODEV;
+ goto rollback_protocol_init;
+ }
+
+ result = modem_isa_init(&modem_driver_data);
+ if (result < 0) {
+ dev_err(&sdev->dev,
+ "failed to initialise char interface\n");
+ goto rollback_modem_get;
+ }
+
+ result = modem_net_init(&modem_driver_data);
+ if (result < 0) {
+ dev_err(&sdev->dev,
+ "failed to initialse net interface\n");
+ goto rollback_isa_init;
+ }
+
+#ifdef CONFIG_PHONET
+ tasklet_init(&phonet_rcv_tasklet, phonet_rcv_tasklet_func, 0);
+#endif
+ }
+ return result;
+
+rollback_isa_init:
+ modem_isa_exit(&modem_driver_data);
+rollback_modem_get:
+ modem_put(modem_driver_data.modem);
+rollback_protocol_init:
+ modem_protocol_exit();
+rollback:
+ return result;
+}
+
+static int __exit spi_remove(struct spi_device *sdev)
+{
+ modem_protocol_exit();
+ modem_net_exit(&modem_driver_data);
+ modem_isa_exit(&modem_driver_data);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/**
+ * spi_suspend() - This routine puts the IPC driver in to suspend state.
+ * @sdev: pointer to spi device structure.
+ * @mesg: pm operation
+ *
+ * This routine checks the current ongoing communication with modem
+ * and prevents suspend if modem communication is on-going.
+ */
+static int spi_suspend(struct spi_device *sdev, pm_message_t mesg)
+{
+ bool busy;
+ int ret = -EBUSY;
+
+ dev_dbg(&sdev->dev, "suspend called\n");
+ busy = modem_protocol_is_busy(sdev);
+ if (busy) {
+ dev_warn(&sdev->dev, "suspend failed (protocol busy)\n");
+ return -EBUSY;
+ }
+ ret = modem_protocol_suspend(sdev);
+ if (ret) {
+ dev_warn(&sdev->dev, "suspend failed, (protocol suspend))\n");
+ return ret;
+ }
+ ret = modem_net_suspend(modem_driver_data.ndev);
+ if (ret) {
+ dev_warn(&sdev->dev, "suspend failed, (netdev suspend)\n");
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * spi_resume() - This routine resumes the IPC driver from suspend state.
+ * @sdev: pointer to spi device structure
+ */
+static int spi_resume(struct spi_device *sdev)
+{
+ int ret;
+
+ dev_dbg(&sdev->dev, "resume called\n");
+ ret = modem_protocol_resume(sdev);
+ if (ret) {
+ dev_warn(&sdev->dev, "resume failed, (protocol resume))\n");
+ return ret;
+ }
+ ret = modem_net_resume(modem_driver_data.ndev);
+ if (ret) {
+ dev_warn(&sdev->dev, "resume failed, (netdev resume))\n");
+ return ret;
+ }
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static struct spi_driver spi_driver = {
+ .driver = {
+ .name = "spimodem",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE
+ },
+ .probe = spi_probe,
+ .remove = __exit_p(spi_remove),
+#ifdef CONFIG_PM
+ .suspend = spi_suspend,
+ .resume = spi_resume,
+#endif
+};
+
+static int __init m6718_spi_driver_init(void)
+{
+ pr_info("M6718 modem driver initialising\n");
+ modem_protocol_init();
+ return spi_register_driver(&spi_driver);
+}
+module_init(m6718_spi_driver_init);
+
+static void __exit m6718_spi_driver_exit(void)
+{
+ pr_debug("M6718 modem SPI IPC driver exit\n");
+ spi_unregister_driver(&spi_driver);
+}
+module_exit(m6718_spi_driver_exit);
+
+MODULE_AUTHOR("Chris Blair <chris.blair@stericsson.com>");
+MODULE_DESCRIPTION("M6718 modem IPC SPI driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/modem/m6718_spi/modem_netlink.h b/drivers/modem/m6718_spi/modem_netlink.h
new file mode 100644
index 00000000000..19e123d9b12
--- /dev/null
+++ b/drivers/modem/m6718_spi/modem_netlink.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * Modem IPC driver protocol interface header:
+ * netlink related functionality.
+ */
+#ifndef _MODEM_NETLINK_H_
+#define _MODEM_NETLINK_H_
+
+#include "modem_protocol.h"
+
+bool ipc_create_netlink_socket(struct ipc_link_context *context);
+void ipc_broadcast_modem_online(struct ipc_link_context *context);
+void ipc_broadcast_modem_reset(struct ipc_link_context *context);
+
+#endif /* _MODEM_NETLINK_H_ */
diff --git a/drivers/modem/m6718_spi/modem_private.h b/drivers/modem/m6718_spi/modem_private.h
new file mode 100644
index 00000000000..a4de4ba9e93
--- /dev/null
+++ b/drivers/modem/m6718_spi/modem_private.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ * based on shrm_driver.h
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * Modem IPC driver protocol interface header:
+ * private data
+ */
+#ifndef _MODEM_PRIVATE_H_
+#define _MODEM_PRIVATE_H_
+
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/atomic.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include "modem_protocol.h"
+#include "modem_statemachine.h"
+
+#define IPC_DRIVER_VERSION (0x02) /* APE protocol version */
+#define IPC_DRIVER_MODEM_MIN_VER (0x02) /* version required from modem */
+
+#define IPC_NBR_SUPPORTED_SPI_LINKS (2)
+#define IPC_LINK_COMMON (0)
+#define IPC_LINK_AUDIO (1)
+
+#define IPC_TX_QUEUE_MAX_SIZE (1024*1024)
+
+#define IPC_L1_HDR_SIZE (4)
+#define IPC_L2_HDR_SIZE (4)
+
+/* tx queue item (frame) */
+struct ipc_tx_queue {
+ struct list_head node;
+ int actual_len;
+ int len;
+ void *data;
+ int counter;
+};
+
+/* context structure for an spi link */
+struct ipc_link_context {
+ struct modem_m6718_spi_link_platform_data *link;
+ struct spi_device *sdev;
+ atomic_t suspended;
+ atomic_t gpio_configured;
+ atomic_t state_int;
+ spinlock_t sm_lock;
+ spinlock_t tx_q_update_lock;
+ atomic_t tx_q_count;
+ int tx_q_free;
+ struct list_head tx_q;
+ int tx_frame_counter;
+ const struct ipc_sm_state *state;
+ u32 cmd;
+ struct ipc_tx_queue *frame;
+ struct spi_message spi_message;
+ struct spi_transfer spi_transfer;
+ struct timer_list comms_timer;
+ struct timer_list slave_stable_timer;
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_VERIFY_FRAMES
+ struct ipc_tx_queue *last_frame;
+#endif
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ u32 tx_bytes;
+ u32 rx_bytes;
+ unsigned long idl_measured_at;
+ unsigned long idl_idle_enter;
+ unsigned long idl_idle_total;
+#endif
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfsfile;
+ u8 lastevent;
+ u8 lastignored;
+ enum ipc_sm_state_id lastignored_in;
+ bool lastignored_inthis;
+ int tx_q_min;
+ unsigned long statesince;
+#endif
+};
+
+/* context structure for the spi driver */
+struct ipc_l1_context {
+ bool init_done;
+ atomic_t boot_sync_done;
+ struct ipc_link_context device_context[IPC_NBR_SUPPORTED_SPI_LINKS];
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ struct timer_list tp_timer;
+#endif
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfsdir;
+ struct dentry *debugfs_silentreset;
+ bool msr_disable;
+#endif
+};
+
+extern struct ipc_l1_context l1_context;
+
+#endif /* _MODEM_PRIVATE_H_ */
diff --git a/drivers/modem/m6718_spi/modem_protocol.h b/drivers/modem/m6718_spi/modem_protocol.h
new file mode 100644
index 00000000000..751dcba1087
--- /dev/null
+++ b/drivers/modem/m6718_spi/modem_protocol.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ * based on shrm_driver.h
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * Modem IPC driver protocol interface header.
+ */
+#ifndef _MODEM_PROTOCOL_H_
+#define _MODEM_PROTOCOL_H_
+
+#include <linux/spi/spi.h>
+
+void modem_protocol_init(void);
+int modem_protocol_probe(struct spi_device *sdev);
+void modem_protocol_exit(void);
+bool modem_protocol_is_busy(struct spi_device *sdev);
+bool modem_protocol_channel_is_open(u8 channel);
+int modem_protocol_suspend(struct spi_device *sdev);
+int modem_protocol_resume(struct spi_device *sdev);
+
+#endif /* _MODEM_PROTOCOL_H_ */
diff --git a/drivers/modem/m6718_spi/modem_queue.h b/drivers/modem/m6718_spi/modem_queue.h
new file mode 100644
index 00000000000..62604129945
--- /dev/null
+++ b/drivers/modem/m6718_spi/modem_queue.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * Modem IPC driver protocol interface header:
+ * queue functionality.
+ */
+#ifndef _MODEM_QUEUE_H_
+#define _MODEM_QUEUE_H_
+
+void ipc_queue_init(struct ipc_link_context *context);
+void ipc_queue_delete_frame(struct ipc_tx_queue *frame);
+struct ipc_tx_queue *ipc_queue_new_frame(struct ipc_link_context *link_context,
+ u32 l2_length);
+bool ipc_queue_is_empty(struct ipc_link_context *context);
+int ipc_queue_push_frame(struct ipc_link_context *link_context, u8 l2_header,
+ u32 l2_length, void *l2_data);
+struct ipc_tx_queue *ipc_queue_get_frame(struct ipc_link_context *context);
+void ipc_queue_reset(struct ipc_link_context *context);
+
+#endif /* _MODEM_QUEUE_H_ */
diff --git a/drivers/modem/m6718_spi/modem_state.c b/drivers/modem/m6718_spi/modem_state.c
new file mode 100644
index 00000000000..47376934bcb
--- /dev/null
+++ b/drivers/modem/m6718_spi/modem_state.c
@@ -0,0 +1,1300 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Derek Morton <derek.morton@stericsson.com>
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * Power state driver for M6718 MODEM
+ */
+
+/* define DEBUG to enable debug logging */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/timer.h>
+#include <linux/gpio/nomadik.h>
+#include <plat/pincfg.h>
+#include <linux/workqueue.h>
+#include <linux/list.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include "modem_state.h"
+
+/*
+ * To enable this driver add a struct platform_device in the board
+ * configuration file (e.g. board-*.c) with name="modemstate"
+ * optionally specify dev.initname="m6718" to define the driver
+ * name as it will appear in the file system.
+ * e.g.
+ * static struct platform_device modem_state_device =
+ * {
+ * .name = "modemstate",
+ * .dev =
+ * {
+ * .init_name = "m6718" // Name that will appear in FS
+ * },
+ * .num_resources = ARRAY_SIZE(modem_state_resources),
+ * .resource = modem_state_resources
+ * };
+ *
+ * This driver uses gpio pins which should be specified as resources *
+ * e.g.
+ * static struct resource modem_state_resources[] = .......
+ * Output pins are specified as IORESOURCE_IO
+ * Currently supported Output pins are:
+ * onkey_pin
+ * reset_pin
+ * vbat_pin
+ * Input pins are specified as IORESOURCE_IRQ
+ * Currently supported input pins are:
+ * rsthc_pin
+ * rstext_pin
+ * crash_pin
+ * Currently only the start value is used as the gpio pin number but
+ * end should also be specified as the gpio pin number in case gpio ranges
+ * are used in the future.
+ * e.g. if gpio 161 is used as the onkey pin
+ * {
+ * .start = 161,
+ * .end = 161,
+ * .name = "onkey_pin",
+ * .flags = IORESOURCE_IO,
+ * },
+ */
+
+struct modem_state_dev {
+ int onkey_pin;
+ int rsthc_pin;
+ int rstext_pin;
+ int crash_pin;
+ int reset_pin;
+ int vbat_pin;
+ int power_state;
+ int irq_state;
+ int busy;
+ struct timer_list onkey_timer;
+ struct timer_list reset_timer;
+ struct timer_list onkey_debounce_timer;
+ struct timer_list vbat_off_timer;
+ struct timer_list busy_timer;
+ spinlock_t lock;
+ struct device *dev;
+ struct workqueue_struct *workqueue;
+ struct work_struct wq_rsthc;
+ struct work_struct wq_rstext;
+ struct work_struct wq_crash;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfsdir;
+ struct dentry *debugfs_debug;
+#endif
+};
+
+struct callback_list {
+ struct list_head node;
+ int (*callback) (unsigned long);
+ unsigned long data;
+};
+LIST_HEAD(callback_list);
+
+static char *modem_state_str[] = {
+ "off",
+ "reset",
+ "crash",
+ "on",
+ /*
+ * Add new states before error and update enum modem_states
+ * in modem_state.h
+ */
+ "error"
+};
+
+static struct modem_state_dev *modem_state;
+
+static void set_on_config(struct modem_state_dev *msdev)
+{
+ if (msdev->crash_pin)
+ nmk_config_pin(PIN_CFG(msdev->crash_pin, GPIO) |
+ PIN_INPUT_PULLDOWN, false);
+ if (msdev->rstext_pin)
+ nmk_config_pin(PIN_CFG(msdev->rstext_pin, GPIO) |
+ PIN_INPUT_PULLDOWN, false);
+ if (msdev->rsthc_pin)
+ nmk_config_pin(PIN_CFG(msdev->rsthc_pin, GPIO) |
+ PIN_INPUT_PULLDOWN, false);
+ if (msdev->reset_pin)
+ nmk_config_pin(PIN_CFG(msdev->reset_pin, GPIO) |
+ PIN_OUTPUT_HIGH, false);
+}
+
+static void set_off_config(struct modem_state_dev *msdev)
+{
+ if (msdev->crash_pin)
+ nmk_config_pin(PIN_CFG(msdev->crash_pin, GPIO) |
+ PIN_INPUT_PULLDOWN, false);
+ if (msdev->rstext_pin)
+ nmk_config_pin(PIN_CFG(msdev->rstext_pin, GPIO) |
+ PIN_OUTPUT_LOW, false);
+ if (msdev->rsthc_pin)
+ nmk_config_pin(PIN_CFG(msdev->rsthc_pin, GPIO) | PIN_OUTPUT_LOW,
+ false);
+ if (msdev->reset_pin)
+ nmk_config_pin(PIN_CFG(msdev->reset_pin, GPIO) |
+ PIN_OUTPUT_HIGH, false);
+}
+
+static void enable_irq_all(struct modem_state_dev *msdev)
+{
+ if (msdev->rsthc_pin) {
+ enable_irq(GPIO_TO_IRQ(msdev->rsthc_pin));
+ if ((0 > enable_irq_wake(GPIO_TO_IRQ(msdev->rsthc_pin))))
+ dev_err(msdev->dev,
+ "Request for wake on pin %d failed\n",
+ msdev->rsthc_pin);
+ }
+ if (msdev->rstext_pin) {
+ enable_irq(GPIO_TO_IRQ(msdev->rstext_pin));
+ if ((0 > enable_irq_wake(GPIO_TO_IRQ(msdev->rstext_pin))))
+ dev_err(msdev->dev,
+ "Request for wake on pin %d failed\n",
+ msdev->rstext_pin);
+ }
+ if (msdev->crash_pin) {
+ enable_irq(GPIO_TO_IRQ(msdev->crash_pin));
+ if ((0 > enable_irq_wake(GPIO_TO_IRQ(msdev->crash_pin))))
+ dev_err(msdev->dev,
+ "Request for wake on pin %d failed\n",
+ msdev->crash_pin);
+ }
+}
+
+static void disable_irq_all(struct modem_state_dev *msdev)
+{
+ if (msdev->rsthc_pin) {
+ disable_irq_wake(GPIO_TO_IRQ(msdev->rsthc_pin));
+ disable_irq(GPIO_TO_IRQ(msdev->rsthc_pin));
+ }
+ if (msdev->rstext_pin) {
+ disable_irq_wake(GPIO_TO_IRQ(msdev->rstext_pin));
+ disable_irq(GPIO_TO_IRQ(msdev->rstext_pin));
+ }
+ if (msdev->crash_pin) {
+ disable_irq_wake(GPIO_TO_IRQ(msdev->crash_pin));
+ disable_irq(GPIO_TO_IRQ(msdev->crash_pin));
+ }
+}
+
+/*
+ * These functions which access GPIO must only be called
+ * with spinlock enabled.
+ */
+
+/*
+ * Toggle ONKEY pin high then low to turn modem on or off. Modem expects
+ * ONKEY line to be pulled low then high. GPIO needs to be driven high then
+ * low as logic is inverted through a transistor.
+ */
+static void toggle_modem_power(struct modem_state_dev *msdev)
+{
+ dev_info(msdev->dev, "Modem power toggle\n");
+ msdev->busy = 1;
+ gpio_set_value(msdev->onkey_pin, 1);
+ msdev->onkey_timer.data = (unsigned long)msdev;
+ /* Timeout of at least 1 second */
+ mod_timer(&msdev->onkey_timer, jiffies + (1 * HZ) + 1);
+}
+
+/* Modem is forced into reset when its reset line is pulled low */
+/* Drive GPIO low then high to reset modem */
+static void modem_reset(struct modem_state_dev *msdev)
+{
+ dev_info(msdev->dev, "Modem reset\n");
+ msdev->busy = 1;
+ gpio_set_value(msdev->reset_pin, 0);
+ msdev->reset_timer.data = (unsigned long)msdev;
+ /* Wait a couple of Jiffies */
+ mod_timer(&msdev->reset_timer, jiffies + 2);
+}
+
+static void modem_vbat_set_value(struct modem_state_dev *msdev, int vbat_val)
+{
+ switch (vbat_val) {
+ case 0:
+ msdev->power_state = 0;
+ dev_info(msdev->dev, "Modem vbat off\n");
+ gpio_set_value(msdev->vbat_pin, vbat_val);
+ if (1 == msdev->irq_state) {
+ msdev->irq_state = 0;
+ disable_irq_all(msdev);
+ set_off_config(msdev);
+ }
+ break;
+ case 1:
+ dev_info(msdev->dev, "Modem vbat on\n");
+ if (0 == msdev->irq_state) {
+ msdev->irq_state = 1;
+ set_on_config(msdev);
+ enable_irq_all(msdev);
+ }
+ gpio_set_value(msdev->vbat_pin, vbat_val);
+ break;
+ default:
+ return;
+ break;
+ }
+}
+
+static void modem_power_on(struct modem_state_dev *msdev)
+{
+ int rsthc = gpio_get_value(msdev->rsthc_pin);
+ msdev->power_state = 1;
+ del_timer(&msdev->vbat_off_timer);
+ if (rsthc == 0) {
+ modem_vbat_set_value(msdev, 1);
+ toggle_modem_power(msdev);
+ }
+}
+
+static void modem_power_off(struct modem_state_dev *msdev)
+{
+ int rsthc = gpio_get_value(msdev->rsthc_pin);
+
+ msdev->power_state = 0;
+ if (rsthc == 1) {
+ toggle_modem_power(msdev);
+ /* Cut power to modem after 10 seconds */
+ msdev->vbat_off_timer.data = (unsigned long)msdev;
+ mod_timer(&msdev->vbat_off_timer, jiffies + (10 * HZ));
+ }
+}
+/* End of functions requiring spinlock */
+
+static void call_callbacks(void)
+{
+ struct callback_list *item;
+
+ list_for_each_entry(item, &callback_list, node)
+ item->callback(item->data);
+}
+
+static int get_modem_state(struct modem_state_dev *msdev)
+{
+ int state;
+ unsigned long flags;
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ if (0 == gpio_get_value(msdev->rsthc_pin))
+ state = MODEM_STATE_OFF;
+ else if (0 == gpio_get_value(msdev->rstext_pin))
+ state = MODEM_STATE_RESET;
+ else if (1 == gpio_get_value(msdev->crash_pin))
+ state = MODEM_STATE_CRASH;
+ else
+ state = MODEM_STATE_ON;
+ spin_unlock_irqrestore(&msdev->lock, flags);
+
+ return state;
+}
+
+/* modempower read handler */
+static ssize_t modem_state_power_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int rsthc;
+ int power_state;
+ unsigned long flags;
+ struct modem_state_dev *msdev =
+ platform_get_drvdata(to_platform_device(dev));
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ rsthc = gpio_get_value(msdev->rsthc_pin);
+ power_state = msdev->power_state;
+ spin_unlock_irqrestore(&msdev->lock, flags);
+
+ return sprintf(buf, "state=%d, expected=%d\n", rsthc, power_state);
+}
+
+/*
+ * modempower write handler
+ * Write '0' to /sys/devices/platform/modemstate/modempower to turn modem off
+ * Write '1' to /sys/devices/platform/modemstate/modempower to turn modem on
+ */
+static ssize_t modem_state_power_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long flags;
+ int ret = count;
+ struct modem_state_dev *msdev =
+ platform_get_drvdata(to_platform_device(dev));
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ if (msdev->busy) {
+ ret = -EAGAIN;
+ } else if (count > 0) {
+ switch (buf[0]) {
+ case '0':
+ modem_power_off(msdev);
+ break;
+ case '1':
+ modem_power_on(msdev);
+ break;
+ default:
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&msdev->lock, flags);
+ return ret;
+}
+
+/* reset read handler */
+static ssize_t modem_state_reset_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int rstext;
+ struct modem_state_dev *msdev =
+ platform_get_drvdata(to_platform_device(dev));
+
+ /* No need for spinlocks here as there is only 1 value */
+ rstext = gpio_get_value(msdev->rstext_pin);
+
+ return sprintf(buf, "state=%d\n", rstext);
+}
+
+/* reset write handler */
+/* Write '1' to /sys/devices/platform/modemstate/reset to reset modem */
+static ssize_t modem_state_reset_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long flags;
+ int ret = count;
+ struct modem_state_dev *msdev =
+ platform_get_drvdata(to_platform_device(dev));
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ if (msdev->busy) {
+ ret = -EAGAIN;
+ } else if (count > 0) {
+ if (buf[0] == '1')
+ modem_reset(msdev);
+ }
+ spin_unlock_irqrestore(&msdev->lock, flags);
+
+ return ret;
+}
+
+/* crash read handler */
+static ssize_t modem_state_crash_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int crash;
+ struct modem_state_dev *msdev =
+ platform_get_drvdata(to_platform_device(dev));
+
+ /* No need for spinlocks here as there is only 1 value */
+ crash = gpio_get_value(msdev->crash_pin);
+
+ return sprintf(buf, "state=%d\n", crash);
+}
+
+/* state read handler */
+static ssize_t modem_state_state_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int state;
+ struct modem_state_dev *msdev =
+ platform_get_drvdata(to_platform_device(dev));
+
+ state = get_modem_state(msdev);
+ if (state > MODEM_STATE_END_MARKER)
+ state = MODEM_STATE_END_MARKER;
+
+ return sprintf(buf, "%s\n", modem_state_str[state]);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int modem_state_debug_get(struct seq_file *s, void *data)
+{
+ int onkey;
+ int rsthc;
+ int rstext;
+ int reset;
+ int crash;
+ int vbat;
+ unsigned long flags;
+ struct modem_state_dev *msdev = s->private;
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ onkey = gpio_get_value(msdev->onkey_pin);
+ rsthc = gpio_get_value(msdev->rsthc_pin);
+ rstext = gpio_get_value(msdev->rstext_pin);
+ reset = gpio_get_value(msdev->reset_pin);
+ crash = gpio_get_value(msdev->crash_pin);
+ vbat = gpio_get_value(msdev->vbat_pin);
+ spin_unlock_irqrestore(&msdev->lock, flags);
+
+ seq_printf(s, "onkey=%d, rsthc=%d, rstext=%d, "
+ "reset=%d, crash=%d, vbat=%d\n",
+ onkey, rsthc, rstext, reset, crash, vbat);
+ return 0;
+}
+
+/*
+ * debug write handler
+ * Write o['0'|'1'] to /sys/devices/platform/modemstate/debug to set
+ * onkey line low or high.
+ * Write r['0'|'1'] to /sys/devices/platform/modemstate/debug to set
+ * reset line low or high.
+ * Write v['0'|'1'] to /sys/devices/platform/modemstate/debug to set
+ * vbat line low or high.
+ */
+static ssize_t modem_state_debug_set(struct file *file,
+ const char __user *user_buf,
+ size_t count,
+ loff_t *ppos)
+{
+ unsigned long flags;
+ int bufsize;
+ char buf[128];
+
+ bufsize = min(count, sizeof(buf) - 1);
+ if (copy_from_user(buf, user_buf, bufsize))
+ return -EFAULT;
+ buf[bufsize] = 0;
+
+ spin_lock_irqsave(&modem_state->lock, flags);
+ if (modem_state->busy) {
+ return -EAGAIN;
+ } else if (count > 1) {
+ switch (buf[1]) {
+ case '0': /* fallthrough */
+ case '1':
+ switch (buf[0]) {
+ case 'o':
+ gpio_set_value(modem_state->onkey_pin,
+ buf[1] - '0');
+ break;
+ case 'r':
+ gpio_set_value(modem_state->reset_pin,
+ buf[1] - '0');
+ break;
+ case 'v':
+ gpio_set_value(modem_state->vbat_pin,
+ buf[1] - '0');
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&modem_state->lock, flags);
+
+ return bufsize;
+}
+
+static int modem_state_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, modem_state_debug_get, inode->i_private);
+}
+#endif /* CONFIG_DEBUG_FS */
+
+static DEVICE_ATTR(modempower, S_IRUSR | S_IWUSR,
+ modem_state_power_get, modem_state_power_set);
+static DEVICE_ATTR(reset, S_IRUSR | S_IWUSR,
+ modem_state_reset_get, modem_state_reset_set);
+static DEVICE_ATTR(crash, S_IRUSR, modem_state_crash_get, NULL);
+static DEVICE_ATTR(state, S_IRUSR, modem_state_state_get, NULL);
+
+static struct attribute *modemstate_attributes[] = {
+ &dev_attr_modempower.attr,
+ &dev_attr_reset.attr,
+ &dev_attr_crash.attr,
+ &dev_attr_state.attr,
+ NULL
+};
+
+static struct attribute_group modemstate_attr_group = {
+ .attrs = modemstate_attributes,
+ .name = "modemstate"
+};
+
+#ifdef CONFIG_DEBUG_FS
+static const struct file_operations debugfs_debug_fops = {
+ .open = modem_state_debug_open,
+ .read = seq_read,
+ .write = modem_state_debug_set,
+ .llseek = seq_lseek,
+ .release = single_release
+};
+#endif
+
+static void sysfs_notify_rsthc(struct modem_state_dev *msdev)
+{
+ sysfs_notify(&msdev->dev->kobj, NULL, dev_attr_modempower.attr.name);
+ sysfs_notify(&msdev->dev->kobj, NULL, dev_attr_state.attr.name);
+}
+
+static void sysfs_notify_rstext(struct modem_state_dev *msdev)
+{
+ sysfs_notify(&msdev->dev->kobj, NULL, dev_attr_reset.attr.name);
+ sysfs_notify(&msdev->dev->kobj, NULL, dev_attr_state.attr.name);
+}
+
+static void sysfs_notify_crash(struct modem_state_dev *msdev)
+{
+ sysfs_notify(&msdev->dev->kobj, NULL, dev_attr_crash.attr.name);
+ sysfs_notify(&msdev->dev->kobj, NULL, dev_attr_state.attr.name);
+}
+
+static void wq_rsthc(struct work_struct *work)
+{
+ unsigned long flags;
+ int rsthc;
+ struct modem_state_dev *msdev =
+ container_of(work, struct modem_state_dev, wq_rsthc);
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ rsthc = gpio_get_value(msdev->rsthc_pin);
+ dev_dbg(msdev->dev, "RSTHC interrupt detected, rsthc=%d\n", rsthc);
+ if (msdev->power_state == rsthc) {
+ if (!rsthc) {
+ /* Modem has turned off, and we were expecting it to.
+ turn vbat to the modem off now */
+ del_timer(&msdev->vbat_off_timer);
+ modem_vbat_set_value(msdev, 0);
+ }
+ } else {
+ dev_dbg(msdev->dev,
+ "Modem power state is %d, expected %d\n", rsthc,
+ msdev->power_state);
+ dev_dbg(msdev->dev,
+ "Attempting to change modem power state "
+ "in 2 seconds\n");
+
+ msdev->onkey_debounce_timer.data = (unsigned long)msdev;
+ /* Wait > 2048ms due to debounce timer */
+ mod_timer(&msdev->onkey_debounce_timer,
+ jiffies + ((2050 * HZ) / 1000));
+ }
+ spin_unlock_irqrestore(&msdev->lock, flags);
+
+ call_callbacks();
+ sysfs_notify_rsthc(msdev);
+}
+
+static void wq_rstext(struct work_struct *work)
+{
+ struct modem_state_dev *msdev =
+ container_of(work, struct modem_state_dev, wq_rstext);
+
+ dev_dbg(msdev->dev, "RSTEXT interrupt detected, rstext=%d\n",
+ gpio_get_value(msdev->rstext_pin));
+
+ call_callbacks();
+ sysfs_notify_rstext(msdev);
+}
+
+static void wq_crash(struct work_struct *work)
+{
+ struct modem_state_dev *msdev =
+ container_of(work, struct modem_state_dev, wq_rstext);
+
+ dev_dbg(msdev->dev, "modem crash interrupt detected. crash=%d\n",
+ gpio_get_value(msdev->crash_pin));
+
+ call_callbacks();
+ sysfs_notify_crash(msdev);
+}
+
+/* Populate device structure used by the driver */
+static int modem_state_dev_init(struct platform_device *pdev,
+ struct modem_state_dev *msdev)
+{
+ int err = 0;
+ struct resource *r;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_IO, "onkey_pin");
+ if (r == NULL) {
+ err = -ENXIO;
+ dev_err(&pdev->dev,
+ "Could not get GPIO number for onkey pin\n");
+ goto err_resource;
+ }
+ msdev->onkey_pin = r->start;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_IO, "reset_pin");
+ if (r == NULL) {
+ err = -ENXIO;
+ dev_err(&pdev->dev,
+ "Could not get GPIO number for reset pin\n");
+ goto err_resource;
+ }
+ msdev->reset_pin = r->start;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_IO, "vbat_pin");
+ if (r == NULL) {
+ err = -ENXIO;
+ dev_err(&pdev->dev, "Could not get GPIO number for vbat pin\n");
+ goto err_resource;
+ }
+ msdev->vbat_pin = r->start;
+
+ msdev->rsthc_pin = platform_get_irq_byname(pdev, "rsthc_pin");
+ if (msdev->rsthc_pin < 0) {
+ err = msdev->rsthc_pin;
+ dev_err(&pdev->dev,
+ "Could not get GPIO number for rsthc pin\n");
+ goto err_resource;
+ }
+
+ msdev->rstext_pin = platform_get_irq_byname(pdev, "rstext_pin");
+ if (msdev->rstext_pin < 0) {
+ err = msdev->rstext_pin;
+ dev_err(&pdev->dev,
+ "Could not get GPIO number for retext pin\n");
+ goto err_resource;
+ }
+
+ msdev->crash_pin = platform_get_irq_byname(pdev, "crash_pin");
+ if (msdev->crash_pin < 0) {
+ err = msdev->crash_pin;
+ dev_err(&pdev->dev,
+ "Could not get GPIO number for crash pin\n");
+ goto err_resource;
+ }
+err_resource:
+ return err;
+}
+
+/* IRQ handlers */
+
+/* Handlers for rsthc (modem power off indication) IRQ */
+static irqreturn_t rsthc_irq(int irq, void *dev)
+{
+ struct modem_state_dev *msdev = (struct modem_state_dev *)dev;
+
+ /* check it's our interrupt */
+ if (irq != GPIO_TO_IRQ(msdev->rsthc_pin)) {
+ dev_err(msdev->dev, "Spurious RSTHC irq\n");
+ return IRQ_NONE;
+ }
+
+ queue_work(msdev->workqueue, &msdev->wq_rsthc);
+ return IRQ_HANDLED;
+}
+
+/* Handlers for rstext (modem reset indication) IRQ */
+static irqreturn_t rstext_irq(int irq, void *dev)
+{
+ struct modem_state_dev *msdev = (struct modem_state_dev *)dev;
+
+ /* check it's our interrupt */
+ if (irq != GPIO_TO_IRQ(msdev->rstext_pin)) {
+ dev_err(msdev->dev, "Spurious RSTEXT irq\n");
+ return IRQ_NONE;
+ }
+
+ queue_work(msdev->workqueue, &msdev->wq_rstext);
+ return IRQ_HANDLED;
+}
+
+/* Handlers for modem crash indication IRQ */
+static irqreturn_t crash_irq(int irq, void *dev)
+{
+ struct modem_state_dev *msdev = (struct modem_state_dev *)dev;
+
+ /* check it's our interrupt */
+ if (irq != GPIO_TO_IRQ(msdev->crash_pin)) {
+ dev_err(msdev->dev, "Spurious modem crash irq\n");
+ return IRQ_NONE;
+ }
+
+ queue_work(msdev->workqueue, &msdev->wq_crash);
+ return IRQ_HANDLED;
+}
+
+static int request_irq_pin(int pin, irq_handler_t handler, unsigned long flags,
+ struct modem_state_dev *msdev)
+{
+ int err = 0;
+ if (pin) {
+ err = request_irq(GPIO_TO_IRQ(pin), handler, flags,
+ dev_name(msdev->dev), msdev);
+ if (err == 0) {
+ err = enable_irq_wake(GPIO_TO_IRQ(pin));
+ if (err < 0) {
+ dev_err(msdev->dev,
+ "Request for wake on pin %d failed\n",
+ pin);
+ free_irq(GPIO_TO_IRQ(pin), NULL);
+ }
+ } else {
+ dev_err(msdev->dev,
+ "Request for irq on pin %d failed\n", pin);
+ }
+ }
+ return err;
+}
+
+static void free_irq_pin(int pin)
+{
+ disable_irq_wake(GPIO_TO_IRQ(pin));
+ free_irq(GPIO_TO_IRQ(pin), NULL);
+}
+
+static int request_irq_all(struct modem_state_dev *msdev)
+{
+ int err;
+
+ err = request_irq_pin(msdev->rsthc_pin, rsthc_irq,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_NO_SUSPEND, msdev);
+ if (err < 0)
+ goto err_rsthc_irq_req;
+
+ err = request_irq_pin(msdev->rstext_pin, rstext_irq,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_NO_SUSPEND, msdev);
+ if (err < 0)
+ goto err_rstext_irq_req;
+
+ err = request_irq_pin(msdev->crash_pin, crash_irq,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_NO_SUSPEND, msdev);
+ if (err < 0)
+ goto err_crash_irq_req;
+
+ return 0;
+
+err_crash_irq_req:
+ free_irq_pin(msdev->rstext_pin);
+err_rstext_irq_req:
+ free_irq_pin(msdev->rsthc_pin);
+err_rsthc_irq_req:
+ return err;
+}
+
+/* Configure GPIO used by the driver */
+static int modem_state_gpio_init(struct platform_device *pdev,
+ struct modem_state_dev *msdev)
+{
+ int err = 0;
+
+ /* Reserve gpio pins */
+ if (msdev->onkey_pin != 0) {
+ err = gpio_request(msdev->onkey_pin, dev_name(msdev->dev));
+ if (err < 0) {
+ dev_err(&pdev->dev, "Request for onkey pin failed\n");
+ goto err_onkey_req;
+ }
+ }
+ if (msdev->reset_pin != 0) {
+ err = gpio_request(msdev->reset_pin, dev_name(msdev->dev));
+ if (err < 0) {
+ dev_err(&pdev->dev, "Request for reset pin failed\n");
+ goto err_reset_req;
+ }
+ }
+ if (msdev->rsthc_pin != 0) {
+ err = gpio_request(msdev->rsthc_pin, dev_name(msdev->dev));
+ if (err < 0) {
+ dev_err(&pdev->dev, "Request for rsthc pin failed\n");
+ goto err_rsthc_req;
+ }
+ }
+ if (msdev->rstext_pin != 0) {
+ err = gpio_request(msdev->rstext_pin, dev_name(msdev->dev));
+ if (err < 0) {
+ dev_err(&pdev->dev, "Request for rstext pin failed\n");
+ goto err_rstext_req;
+ }
+ }
+ if (msdev->crash_pin != 0) {
+ err = gpio_request(msdev->crash_pin, dev_name(msdev->dev));
+ if (err < 0) {
+ dev_err(&pdev->dev, "Request for crash pin failed\n");
+ goto err_crash_req;
+ }
+ }
+ if (msdev->vbat_pin != 0) {
+ err = gpio_request(msdev->vbat_pin, dev_name(msdev->dev));
+ if (err < 0) {
+ dev_err(&pdev->dev, "Request for vbat pin failed\n");
+ goto err_vbat_req;
+ }
+ }
+
+ /* Set initial pin config */
+ set_on_config(msdev);
+ if (msdev->onkey_pin)
+ nmk_config_pin(PIN_CFG(msdev->onkey_pin, GPIO) |
+ PIN_OUTPUT_LOW, false);
+ if (msdev->vbat_pin)
+ nmk_config_pin(PIN_CFG(msdev->vbat_pin, GPIO) | PIN_OUTPUT_HIGH,
+ false);
+
+ /* Configure IRQs for GPIO pins */
+ err = request_irq_all(msdev);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Request for irqs failed, err = %d\n", err);
+ goto err_irq_req;
+ }
+ msdev->irq_state = 1;
+
+ /* Save current modem state */
+ msdev->power_state = gpio_get_value(msdev->rsthc_pin);
+
+ return 0;
+
+err_irq_req:
+ gpio_free(msdev->vbat_pin);
+err_vbat_req:
+ gpio_free(msdev->crash_pin);
+err_crash_req:
+ gpio_free(msdev->rstext_pin);
+err_rstext_req:
+ gpio_free(msdev->rsthc_pin);
+err_rsthc_req:
+ gpio_free(msdev->reset_pin);
+err_reset_req:
+ gpio_free(msdev->onkey_pin);
+err_onkey_req:
+ return err;
+}
+
+/* Timer handlers */
+
+static void modem_power_timeout(unsigned long data)
+{
+ unsigned long flags;
+ struct modem_state_dev *msdev = (struct modem_state_dev *)data;
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ if (msdev->busy)
+ msdev->busy = 0;
+ else
+ dev_err(msdev->dev,
+ "onkey timer expired and busy flag not set\n");
+
+ gpio_set_value(msdev->onkey_pin, 0);
+ spin_unlock_irqrestore(&msdev->lock, flags);
+}
+
+static void modem_reset_timeout(unsigned long data)
+{
+ unsigned long flags;
+ struct modem_state_dev *msdev = (struct modem_state_dev *)data;
+
+ spin_lock_irqsave(&modem_state->lock, flags);
+ if (msdev->busy)
+ msdev->busy = 0;
+ else
+ dev_err(msdev->dev,
+ "reset timer expired and busy flag not set\n");
+
+ gpio_set_value(msdev->reset_pin, 1);
+ spin_unlock_irqrestore(&modem_state->lock, flags);
+}
+
+static void modem_onkey_debounce_timeout(unsigned long data)
+{
+ unsigned long flags;
+ struct modem_state_dev *msdev = (struct modem_state_dev *)data;
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ if (msdev->busy) {
+ dev_info(msdev->dev,
+ "Delayed onkey change aborted. "
+ "Another action in progress\n");
+ } else {
+ if (gpio_get_value(msdev->rsthc_pin) != msdev->power_state) {
+ if (0 == msdev->power_state)
+ modem_power_off(msdev);
+ else
+ modem_power_on(msdev);
+ }
+ }
+ spin_unlock_irqrestore(&msdev->lock, flags);
+}
+
+static void modem_vbat_off_timeout(unsigned long data)
+{
+ struct modem_state_dev *msdev = (struct modem_state_dev *)data;
+ unsigned long flags;
+ spin_lock_irqsave(&msdev->lock, flags);
+ if (0 == msdev->power_state)
+ modem_vbat_set_value(msdev, 0);
+ spin_unlock_irqrestore(&msdev->lock, flags);
+}
+
+static void modem_busy_on_timeout(unsigned long data)
+{
+ unsigned long flags;
+ struct modem_state_dev *msdev = (struct modem_state_dev *)data;
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ if (msdev->busy) {
+ mod_timer(&msdev->busy_timer, jiffies + 1);
+ } else {
+ msdev->busy_timer.function = NULL;
+ modem_power_on(msdev);
+ }
+ spin_unlock_irqrestore(&msdev->lock, flags);
+}
+
+static void modem_busy_off_timeout(unsigned long data)
+{
+ unsigned long flags;
+ struct modem_state_dev *msdev = (struct modem_state_dev *)data;
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ if (msdev->busy) {
+ mod_timer(&msdev->busy_timer, jiffies + 1);
+ } else {
+ msdev->busy_timer.function = NULL;
+ modem_power_off(msdev);
+ }
+ spin_unlock_irqrestore(&msdev->lock, flags);
+}
+
+static void modem_busy_reset_timeout(unsigned long data)
+{
+ unsigned long flags;
+ struct modem_state_dev *msdev = (struct modem_state_dev *)data;
+
+ spin_lock_irqsave(&msdev->lock, flags);
+ if (msdev->busy) {
+ mod_timer(&msdev->busy_timer, jiffies + 1);
+ } else {
+ msdev->busy_timer.function = NULL;
+ modem_reset(msdev);
+ }
+ spin_unlock_irqrestore(&msdev->lock, flags);
+}
+
+#ifdef DEBUG
+static int callback_test(unsigned long data)
+{
+ struct modem_state_dev *msdev = (struct modem_state_dev *)data;
+ dev_info(msdev->dev, "Test callback. Modem state is %s\n",
+ modem_state_to_str(modem_state_get_state()));
+ return 0;
+}
+#endif
+
+/* Exported functions */
+
+void modem_state_power_on(void)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&modem_state->lock, flags);
+ if (modem_state->busy) {
+ /*
+ * Ignore on request if turning off is queued,
+ * cancel any queued reset request
+ */
+ if (modem_busy_reset_timeout ==
+ modem_state->busy_timer.function) {
+ del_timer_sync(&modem_state->busy_timer);
+ modem_state->busy_timer.function = NULL;
+ }
+ if (NULL == modem_state->busy_timer.function) {
+ modem_state->busy_timer.function =
+ modem_busy_on_timeout;
+ modem_state->busy_timer.data =
+ (unsigned long)modem_state;
+ mod_timer(&modem_state->busy_timer, jiffies + 1);
+ }
+ } else {
+ modem_power_on(modem_state);
+ }
+ spin_unlock_irqrestore(&modem_state->lock, flags);
+}
+
+void modem_state_power_off(void)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&modem_state->lock, flags);
+ if (modem_state->busy) {
+ /*
+ * Prioritize off request if others are queued.
+ * Must turn modem off if system is shutting down
+ */
+ if (NULL != modem_state->busy_timer.function)
+ del_timer_sync(&modem_state->busy_timer);
+
+ modem_state->busy_timer.function = modem_busy_off_timeout;
+ modem_state->busy_timer.data = (unsigned long)modem_state;
+ mod_timer(&modem_state->busy_timer, jiffies + 1);
+ } else {
+ modem_power_off(modem_state);
+ }
+ spin_unlock_irqrestore(&modem_state->lock, flags);
+}
+
+void modem_state_force_reset(void)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&modem_state->lock, flags);
+ if (modem_state->busy) {
+ /* Ignore reset request if turning on or off is queued */
+ if (NULL == modem_state->busy_timer.function) {
+ modem_state->busy_timer.function =
+ modem_busy_reset_timeout;
+ modem_state->busy_timer.data =
+ (unsigned long)modem_state;
+ mod_timer(&modem_state->busy_timer, jiffies + 1);
+ }
+ } else {
+ modem_reset(modem_state);
+ }
+ spin_unlock_irqrestore(&modem_state->lock, flags);
+}
+
+int modem_state_get_state(void)
+{
+ return get_modem_state(modem_state);
+}
+
+char *modem_state_to_str(int state)
+{
+ if (state > MODEM_STATE_END_MARKER)
+ state = MODEM_STATE_END_MARKER;
+
+ return modem_state_str[state];
+}
+
+int modem_state_register_callback(int (*callback) (unsigned long),
+ unsigned long data)
+{
+ struct callback_list *item;
+ unsigned long flags;
+
+ if (NULL == modem_state)
+ return -EAGAIN;
+
+ if (NULL == callback)
+ return -EINVAL;
+
+ item = kzalloc(sizeof(*item), GFP_KERNEL);
+ if (NULL == item) {
+ dev_err(modem_state->dev,
+ "Could not allocate memory for struct callback_list\n");
+ return -ENOMEM;
+ }
+ item->callback = callback;
+ item->data = data;
+
+ spin_lock_irqsave(&modem_state->lock, flags);
+ list_add_tail(&item->node, &callback_list);
+ spin_unlock_irqrestore(&modem_state->lock, flags);
+
+ return 0;
+}
+
+int modem_state_remove_callback(int (*callback) (unsigned long))
+{
+ struct callback_list *iterator;
+ struct callback_list *item;
+ unsigned long flags;
+ int ret = -ENXIO;
+
+ if (NULL == callback)
+ return -EINVAL;
+
+ spin_lock_irqsave(&modem_state->lock, flags);
+ list_for_each_entry_safe(iterator, item, &callback_list, node) {
+ if (callback == item->callback) {
+ list_del(&item->node);
+ kfree(item);
+ ret = 0;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&modem_state->lock, flags);
+
+ return ret;
+}
+
+#ifdef CONFIG_PM
+int modem_state_suspend(struct device *dev)
+{
+ struct modem_state_dev *msdev =
+ platform_get_drvdata(to_platform_device(dev));
+
+ if (msdev->busy) {
+ dev_info(dev, "Driver is busy\n");
+ return -EBUSY;
+ } else {
+ return 0;
+ }
+}
+
+int modem_state_resume(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+static int __devinit modem_state_probe(struct platform_device *pdev)
+{
+ int err = 0;
+
+ dev_info(&pdev->dev, "Starting probe\n");
+
+ modem_state = kzalloc(sizeof(struct modem_state_dev), GFP_KERNEL);
+ if (NULL == modem_state) {
+ dev_err(&pdev->dev,
+ "Could not allocate memory for modem_state_dev\n");
+ return -ENOMEM;
+ }
+ modem_state->dev = &pdev->dev;
+
+ spin_lock_init(&modem_state->lock);
+
+ INIT_WORK(&modem_state->wq_rsthc, wq_rsthc);
+ INIT_WORK(&modem_state->wq_rstext, wq_rstext);
+ INIT_WORK(&modem_state->wq_crash, wq_crash);
+ modem_state->workqueue =
+ create_singlethread_workqueue(dev_name(&pdev->dev));
+ if (modem_state->workqueue == NULL) {
+ dev_err(&pdev->dev, "Failed to create workqueue\n");
+ goto err_queue;
+ }
+
+ err = modem_state_dev_init(pdev, modem_state);
+ if (err != 0) {
+ dev_err(&pdev->dev, "Could not initialize device structure\n");
+ goto err_dev;
+ }
+
+ init_timer(&modem_state->onkey_timer);
+ init_timer(&modem_state->reset_timer);
+ init_timer(&modem_state->onkey_debounce_timer);
+ init_timer(&modem_state->vbat_off_timer);
+ init_timer(&modem_state->busy_timer);
+ modem_state->onkey_timer.function = modem_power_timeout;
+ modem_state->reset_timer.function = modem_reset_timeout;
+ modem_state->onkey_debounce_timer.function =
+ modem_onkey_debounce_timeout;
+ modem_state->vbat_off_timer.function = modem_vbat_off_timeout;
+ modem_state->busy_timer.function = NULL;
+
+ platform_set_drvdata(pdev, modem_state);
+
+ err = modem_state_gpio_init(pdev, modem_state);
+ if (err != 0) {
+ dev_err(&pdev->dev, "Could not initialize GPIO\n");
+ goto err_gpio;
+ }
+
+ if (sysfs_create_group(&pdev->dev.kobj, &modemstate_attr_group) < 0) {
+ dev_err(&pdev->dev, "failed to create sysfs nodes\n");
+ goto err_sysfs;
+ }
+
+#ifdef CONFIG_DEBUG_FS
+ modem_state->debugfsdir = debugfs_create_dir("modemstate", NULL);
+ modem_state->debugfs_debug = debugfs_create_file("debug",
+ S_IRUGO | S_IWUGO,
+ modem_state->debugfsdir,
+ modem_state,
+ &debugfs_debug_fops);
+#endif
+
+#ifdef DEBUG
+ modem_state_register_callback(callback_test,
+ (unsigned long)modem_state);
+#endif
+ return 0;
+
+err_sysfs:
+err_gpio:
+err_dev:
+ destroy_workqueue(modem_state->workqueue);
+err_queue:
+ kfree(modem_state);
+ return err;
+}
+
+static int __devexit modem_state_remove(struct platform_device *pdev)
+{
+ struct modem_state_dev *msdev = platform_get_drvdata(pdev);
+
+ sysfs_remove_group(&pdev->dev.kobj, &modemstate_attr_group);
+ destroy_workqueue(msdev->workqueue);
+ kfree(msdev);
+ return 0;
+}
+
+static void modem_state_shutdown(struct platform_device *pdev)
+{
+ /*
+ * Trigger software shutdown of the modem and then wait until
+ * modem-off state is detected. If the modem does not power off
+ * when requested power will be removed and we will detect the
+ * modem-off state that way.
+ */
+ modem_state_power_off();
+ if (MODEM_STATE_OFF != modem_state_get_state())
+ dev_alert(&pdev->dev, "Waiting for modem to power down\n");
+ while (MODEM_STATE_OFF != modem_state_get_state())
+ cond_resched();
+}
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops modem_state_dev_pm_ops = {
+ .suspend_noirq = modem_state_suspend,
+ .resume_noirq = modem_state_resume,
+};
+#endif
+
+static struct platform_driver modem_state_driver = {
+ .probe = modem_state_probe,
+ .remove = __devexit_p(modem_state_remove),
+ .shutdown = modem_state_shutdown,
+ .driver = {
+ .name = "modemstate",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &modem_state_dev_pm_ops,
+#endif
+ },
+};
+
+static int __init modem_state_init(void)
+{
+#ifdef DEBUG
+ printk(KERN_ALERT "Modem state driver init\n");
+#endif
+ return platform_driver_probe(&modem_state_driver, modem_state_probe);
+}
+
+static void __exit modem_state_exit(void)
+{
+ platform_driver_unregister(&modem_state_driver);
+}
+
+module_init(modem_state_init);
+module_exit(modem_state_exit);
+
+MODULE_AUTHOR("Derek Morton");
+MODULE_DESCRIPTION("M6718 modem power state driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/modem/m6718_spi/modem_state.h b/drivers/modem/m6718_spi/modem_state.h
new file mode 100644
index 00000000000..a2f1d9fbe3e
--- /dev/null
+++ b/drivers/modem/m6718_spi/modem_state.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Derek Morton <derek.morton@stericsson.com>
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * Power state driver for M6718 MODEM
+ */
+#ifndef MODEM_STATE_H
+#define MODEM_STATE_H
+
+enum modem_states {
+ MODEM_STATE_OFF,
+ MODEM_STATE_RESET,
+ MODEM_STATE_CRASH,
+ MODEM_STATE_ON,
+ /*
+ * Add new states before end marker and update modem_state_str[]
+ * in modem_state.c
+ */
+ MODEM_STATE_END_MARKER
+};
+
+void modem_state_power_on(void);
+void modem_state_power_off(void);
+void modem_state_force_reset(void);
+int modem_state_get_state(void);
+char *modem_state_to_str(int state);
+
+/* Callbacks will be running in tasklet context */
+int modem_state_register_callback(int (*callback) (unsigned long),
+ unsigned long data);
+int modem_state_remove_callback(int (*callback) (unsigned long));
+
+#endif
diff --git a/drivers/modem/m6718_spi/modem_statemachine.h b/drivers/modem/m6718_spi/modem_statemachine.h
new file mode 100644
index 00000000000..6a2a32cad3a
--- /dev/null
+++ b/drivers/modem/m6718_spi/modem_statemachine.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * Modem IPC driver protocol interface header:
+ * statemachine functionality.
+ */
+#ifndef _MODEM_STATEMACHINE_H_
+#define _MODEM_STATEMACHINE_H_
+
+#include <linux/kernel.h>
+
+/* valid states for the driver state machine */
+enum ipc_sm_state_id {
+ IPC_SM_INIT,
+ IPC_SM_HALT,
+ IPC_SM_RESET,
+ IPC_SM_WAIT_SLAVE_STABLE,
+ IPC_SM_WAIT_HANDSHAKE_INACTIVE,
+ IPC_SM_SLW_TX_BOOTREQ,
+ IPC_SM_ACT_TX_BOOTREQ,
+ IPC_SM_SLW_RX_BOOTRESP,
+ IPC_SM_ACT_RX_BOOTRESP,
+ IPC_SM_IDL,
+ IPC_SM_SLW_TX_WR_CMD,
+ IPC_SM_ACT_TX_WR_CMD,
+ IPC_SM_SLW_TX_WR_DAT,
+ IPC_SM_ACT_TX_WR_DAT,
+ IPC_SM_SLW_TX_RD_CMD,
+ IPC_SM_ACT_TX_RD_CMD,
+ IPC_SM_SLW_RX_WR_CMD,
+ IPC_SM_ACT_RX_WR_CMD,
+ IPC_SM_ACT_RX_WR_DAT,
+ IPC_SM_STATE_ID_NBR
+};
+
+/* state machine trigger causes events */
+#define IPC_SM_RUN_NONE (0x00)
+#define IPC_SM_RUN_SLAVE_IRQ (0x01)
+#define IPC_SM_RUN_TFR_COMPLETE (0x02)
+#define IPC_SM_RUN_TX_REQ (0x04)
+#define IPC_SM_RUN_INIT (0x08)
+#define IPC_SM_RUN_ABORT (0x10)
+#define IPC_SM_RUN_COMMS_TMO (0x20)
+#define IPC_SM_RUN_STABLE_TMO (0x40)
+#define IPC_SM_RUN_RESET (0x80)
+
+struct ipc_link_context; /* forward declaration */
+
+typedef u8 (*ipc_sm_enter_func)(u8 event, struct ipc_link_context *context);
+typedef const struct ipc_sm_state *(*ipc_sm_exit_func)(u8 event,
+ struct ipc_link_context *context);
+
+struct ipc_sm_state {
+ enum ipc_sm_state_id id;
+ ipc_sm_enter_func enter;
+ ipc_sm_exit_func exit;
+ u8 events;
+};
+
+const struct ipc_sm_state *ipc_sm_idle_state(struct ipc_link_context *context);
+const struct ipc_sm_state *ipc_sm_init_state(struct ipc_link_context *context);
+const struct ipc_sm_state *ipc_sm_state(u8 id);
+bool ipc_sm_valid_for_state(u8 event, const struct ipc_sm_state *state);
+
+void ipc_sm_kick(u8 event, struct ipc_link_context *context);
+
+#endif /* _MODEM_STATEMACHINE_H_ */
diff --git a/drivers/modem/m6718_spi/modem_util.h b/drivers/modem/m6718_spi/modem_util.h
new file mode 100644
index 00000000000..2d9e2e39abc
--- /dev/null
+++ b/drivers/modem/m6718_spi/modem_util.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * Modem IPC driver protocol interface header:
+ * utility functionality.
+ */
+#ifndef _MODEM_UTIL_H_
+#define _MODEM_UTIL_H_
+
+#include <linux/kernel.h>
+#include "modem_private.h"
+
+bool ipc_util_channel_is_loopback(u8 channel);
+
+u32 ipc_util_make_l2_header(u8 channel, u32 len);
+u8 ipc_util_get_l2_channel(u32 hdr);
+u32 ipc_util_get_l2_length(u32 hdr);
+u32 ipc_util_make_l1_header(u8 cmd, u8 counter, u32 len);
+u8 ipc_util_get_l1_cmd(u32 hdr);
+u8 ipc_util_get_l1_counter(u32 hdr);
+u32 ipc_util_get_l1_length(u32 hdr);
+u8 ipc_util_get_l1_bootresp_ver(u32 bootresp);
+
+int ipc_util_ss_level_active(struct ipc_link_context *context);
+int ipc_util_ss_level_inactive(struct ipc_link_context *context);
+int ipc_util_int_level_active(struct ipc_link_context *context);
+int ipc_util_int_level_inactive(struct ipc_link_context *context);
+
+void ipc_util_deactivate_ss(struct ipc_link_context *context);
+void ipc_util_activate_ss(struct ipc_link_context *context);
+void ipc_util_activate_ss_with_tmo(struct ipc_link_context *context);
+
+bool ipc_util_int_is_active(struct ipc_link_context *context);
+
+bool ipc_util_link_is_idle(struct ipc_link_context *context);
+
+void ipc_util_start_slave_stable_timer(struct ipc_link_context *context);
+
+void ipc_util_spi_message_prepare(struct ipc_link_context *link_context,
+ void *tx_buf, void *rx_buf, int len);
+void ipc_util_spi_message_init(struct ipc_link_context *link_context,
+ void (*complete)(void *));
+
+bool ipc_util_link_gpio_request(struct ipc_link_context *context,
+ irqreturn_t (*irqhnd)(int, void *));
+bool ipc_util_link_gpio_config(struct ipc_link_context *context);
+bool ipc_util_link_gpio_unconfig(struct ipc_link_context *context);
+
+bool ipc_util_link_is_suspended(struct ipc_link_context *context);
+void ipc_util_suspend_link(struct ipc_link_context *context);
+void ipc_util_resume_link(struct ipc_link_context *context);
+
+#endif /* _MODEM_UTIL_H_ */
diff --git a/drivers/modem/m6718_spi/netlink.c b/drivers/modem/m6718_spi/netlink.c
new file mode 100644
index 00000000000..253b19162b1
--- /dev/null
+++ b/drivers/modem/m6718_spi/netlink.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010,2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ * based on shrm_protocol.c
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * U9500 <-> M6718 IPC protocol implementation using SPI:
+ * netlink related functionality
+ */
+#include <linux/netlink.h>
+#include <linux/spi/spi.h>
+#include <linux/modem/m6718_spi/modem_net.h>
+#include <linux/modem/m6718_spi/modem_char.h>
+#include "modem_protocol.h"
+#include "modem_private.h"
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
+#include "modem_state.h"
+#endif
+
+static struct sock *netlink_sk;
+struct modem_spi_dev *modem_dev;
+
+#define MAX_PAYLOAD 1024
+
+/*
+ * Netlink broadcast message values: this must correspond to those values
+ * expected by userspace for the appropriate message.
+ */
+enum netlink_msg_id {
+ NETLINK_MODEM_RESET = 1,
+ NETLINK_MODEM_QUERY_STATE,
+ NETLINK_USER_REQUEST_MODEM_RESET,
+ NETLINK_MODEM_STATUS_ONLINE,
+ NETLINK_MODEM_STATUS_OFFLINE
+};
+
+static void netlink_multicast_tasklet(unsigned long data)
+{
+ struct sk_buff *skb;
+ struct nlmsghdr *nlh;
+ enum netlink_msg_id nlmsg = (enum netlink_msg_id)data;
+
+ if (netlink_sk == NULL) {
+ pr_err("could not send multicast, no socket\n");
+ return;
+ }
+
+ /* prepare netlink message */
+ skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_ATOMIC);
+ if (!skb) {
+ pr_err("failed to allocate socket buffer\n");
+ return;
+ }
+
+ if (nlmsg == NETLINK_MODEM_RESET)
+ modem_isa_reset(modem_dev);
+
+ nlh = (struct nlmsghdr *)skb->data;
+ nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
+ nlh->nlmsg_pid = 0; /* from kernel */
+ nlh->nlmsg_flags = 0;
+ *(int *)NLMSG_DATA(nlh) = nlmsg;
+ skb_put(skb, MAX_PAYLOAD);
+ /* sender is in group 1<<0 */
+ NETLINK_CB(skb).pid = 0; /* from kernel */
+ /* to mcast group 1<<0 */
+ NETLINK_CB(skb).dst_group = 1;
+
+ /* multicast the message to all listening processes */
+ pr_debug("sending netlink multicast message %d\n", nlmsg);
+ netlink_broadcast(netlink_sk, skb, 0, 1, GFP_ATOMIC);
+
+}
+
+static void send_unicast(int dst_pid)
+{
+ struct sk_buff *skb;
+ struct nlmsghdr *nlh;
+
+ if (netlink_sk == NULL) {
+ pr_err("could not send unicast, no socket\n");
+ return;
+ }
+
+ /* prepare the message for unicast */
+ skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_KERNEL);
+ if (!skb) {
+ pr_err("failed to allocate socket buffer\n");
+ return;
+ }
+
+ nlh = (struct nlmsghdr *)skb->data;
+ nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
+ nlh->nlmsg_pid = 0; /* from kernel */
+ nlh->nlmsg_flags = 0;
+
+ if (modem_m6718_spi_is_boot_done()) {
+ pr_debug("sending netlink unicast message %d\n",
+ NETLINK_MODEM_STATUS_ONLINE);
+ *(int *)NLMSG_DATA(nlh) = NETLINK_MODEM_STATUS_ONLINE;
+ } else {
+ pr_debug("sending netlink unicast message %d\n",
+ NETLINK_MODEM_STATUS_OFFLINE);
+ *(int *)NLMSG_DATA(nlh) = NETLINK_MODEM_STATUS_OFFLINE;
+ }
+
+ skb_put(skb, MAX_PAYLOAD);
+ /* sender is in group 1<<0 */
+ NETLINK_CB(skb).pid = 0; /* from kernel */
+ NETLINK_CB(skb).dst_group = 0;
+
+ /* unicast the message to the querying process */
+ netlink_unicast(netlink_sk, skb, dst_pid, MSG_DONTWAIT);
+}
+
+static void netlink_receive(struct sk_buff *skb)
+{
+ struct nlmsghdr *nlh = NULL;
+ int msg;
+
+ nlh = (struct nlmsghdr *)skb->data;
+ msg = *((int *)(NLMSG_DATA(nlh)));
+ switch (msg) {
+ case NETLINK_MODEM_QUERY_STATE:
+ send_unicast(nlh->nlmsg_pid);
+ break;
+ case NETLINK_USER_REQUEST_MODEM_RESET:
+ pr_info("user requested modem reset!\n");
+#ifdef CONFIG_DEBUG_FS
+ if (l1_context.msr_disable) {
+ pr_info("MSR is disabled, ignoring reset request\n");
+ break;
+ }
+#endif
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
+ modem_state_force_reset();
+#else
+ pr_err("modestate integration is not enabled in IPC, "
+ "unable to reset modem\n");
+#endif
+ break;
+ default:
+ pr_debug("ignoring invalid netlink message\n");
+ break;
+ }
+}
+
+bool ipc_create_netlink_socket(struct ipc_link_context *context)
+{
+ if (netlink_sk != NULL)
+ return true;
+
+ netlink_sk = netlink_kernel_create(NULL, NETLINK_MODEM, 1,
+ netlink_receive, NULL, THIS_MODULE);
+ if (netlink_sk == NULL) {
+ dev_err(&context->sdev->dev,
+ "failed to create netlink socket\n");
+ return false;
+ }
+ modem_dev = spi_get_drvdata(context->sdev);
+ return true;
+}
+
+DECLARE_TASKLET(modem_online_tasklet, netlink_multicast_tasklet,
+ NETLINK_MODEM_STATUS_ONLINE);
+DECLARE_TASKLET(modem_reset_tasklet, netlink_multicast_tasklet,
+ NETLINK_MODEM_RESET);
+
+void ipc_broadcast_modem_online(struct ipc_link_context *context)
+{
+ dev_info(&context->sdev->dev, "broadcast modem online event!\n");
+ tasklet_schedule(&modem_online_tasklet);
+}
+
+void ipc_broadcast_modem_reset(struct ipc_link_context *context)
+{
+ dev_info(&context->sdev->dev, "broadcast modem reset event!\n");
+ tasklet_schedule(&modem_reset_tasklet);
+}
+
diff --git a/drivers/modem/m6718_spi/protocol.c b/drivers/modem/m6718_spi/protocol.c
new file mode 100644
index 00000000000..38e9190e397
--- /dev/null
+++ b/drivers/modem/m6718_spi/protocol.c
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010,2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * U9500 <-> M6718 IPC protocol implementation using SPI.
+ */
+#include <linux/modem/m6718_spi/modem_driver.h>
+#include "modem_protocol.h"
+#include "modem_private.h"
+#include "modem_util.h"
+#include "modem_queue.h"
+#include "modem_debug.h"
+#include "modem_netlink.h"
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
+#include <linux/workqueue.h>
+#include "modem_state.h"
+
+#define MODEM_STATE_REGISTER_TMO_MS (500)
+#endif
+
+#ifdef WORKAROUND_DUPLICATED_IRQ
+#include <linux/amba/pl022.h>
+#endif
+
+struct l2mux_channel {
+ u8 open:1;
+ u8 link:7;
+};
+
+/* valid open L2 mux channels */
+static const struct l2mux_channel channels[255] = {
+ [MODEM_M6718_SPI_CHN_ISI] = {
+ .open = true,
+ .link = IPC_LINK_COMMON
+ },
+ [MODEM_M6718_SPI_CHN_AUDIO] = {
+ .open = true,
+ .link = IPC_LINK_AUDIO
+ },
+ [MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0] = {
+ .open = true,
+ .link = IPC_LINK_COMMON
+ },
+ [MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0] = {
+ .open = true,
+ .link = IPC_LINK_COMMON
+ },
+ [MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1] = {
+ .open = true,
+ .link = IPC_LINK_AUDIO
+ },
+ [MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1] = {
+ .open = true,
+ .link = IPC_LINK_AUDIO
+ }
+};
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
+static void modem_state_reg_wq(struct work_struct *work);
+static DECLARE_DELAYED_WORK(modem_state_reg_work, modem_state_reg_wq);
+#endif
+
+/* the spi driver context */
+struct ipc_l1_context l1_context = {
+#ifdef CONFIG_DEBUG_FS
+ .msr_disable = false,
+#endif
+ .init_done = false
+};
+
+bool modem_protocol_channel_is_open(u8 channel)
+{
+ return channels[channel].open;
+}
+
+void modem_comms_timeout(unsigned long data)
+{
+ ipc_sm_kick(IPC_SM_RUN_COMMS_TMO, (struct ipc_link_context *)data);
+}
+
+void slave_stable_timeout(unsigned long data)
+{
+ ipc_sm_kick(IPC_SM_RUN_STABLE_TMO, (struct ipc_link_context *)data);
+}
+
+/**
+ * modem_protocol_init() - initialise the IPC protocol
+ *
+ * Initialises the IPC protocol in preparation for use. After this is called
+ * the protocol is ready to be probed for each link to be supported.
+ */
+void modem_protocol_init(void)
+{
+ pr_info("M6718 IPC protocol initialising version %02x\n",
+ IPC_DRIVER_VERSION);
+
+ atomic_set(&l1_context.boot_sync_done, 0);
+ ipc_dbg_debugfs_init();
+ ipc_dbg_throughput_init();
+ l1_context.init_done = true;
+ ipc_dbg_measure_throughput(0);
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
+ schedule_delayed_work(&modem_state_reg_work, 0);
+#endif
+}
+
+/**
+ * modem_m6718_spi_send() - send a frame using the IPC protocol
+ * @modem_spi_dev: pointer to modem driver information structure
+ * @channel: L2 channel to send on
+ * @len: length of data to send
+ * @data: pointer to buffer containing data
+ *
+ * Check that the requested channel is supported and open, queue a frame
+ * containing the data on the appropriate link and ensure the state machine
+ * is running to start the transfer.
+ */
+int modem_m6718_spi_send(struct modem_spi_dev *modem_spi_dev, u8 channel,
+ u32 len, void *data)
+{
+ int err;
+ struct ipc_link_context *context;
+
+ if (!channels[channel].open) {
+ dev_err(modem_spi_dev->dev,
+ "error: invalid channel (%d), discarding frame\n",
+ channel);
+ return -EINVAL;
+ }
+
+ context = &l1_context.device_context[channels[channel].link];
+ if (context->state == NULL || context->state->id == IPC_SM_HALT) {
+ static unsigned long linkfail_warn_time;
+ if (printk_timed_ratelimit(&linkfail_warn_time, 60 * 1000))
+ dev_err(modem_spi_dev->dev,
+ "error: link %d for ch %d is not available, "
+ "discarding frames\n",
+ channels[channel].link, channel);
+ return -ENODEV;
+ }
+
+ err = ipc_queue_push_frame(context, channel, len, data);
+ if (err < 0)
+ return err;
+
+ if (ipc_util_link_is_idle(context)) {
+ dev_dbg(modem_spi_dev->dev,
+ "link %d is idle, kicking\n", channels[channel].link);
+ ipc_sm_kick(IPC_SM_RUN_TX_REQ, context);
+ } else {
+ dev_dbg(modem_spi_dev->dev,
+ "link %d is already running\n", channels[channel].link);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_m6718_spi_send);
+
+/**
+ * modem_m6718_spi_is_boot_done() - check if boot handshake with modem is done
+ */
+bool modem_m6718_spi_is_boot_done(void)
+{
+ return atomic_read(&l1_context.boot_sync_done);
+}
+EXPORT_SYMBOL_GPL(modem_m6718_spi_is_boot_done);
+
+/**
+ * modem_protocol_is_busy() - check if the protocol is currently active
+ * @sdev: pointer to spi_device for link to check
+ *
+ * Checks each of the IPC links to see if they are inactive: this means they
+ * can be in either IDLE or INIT states. If any of the links are not idle then
+ * true is returned to indicate that the protocol is busy.
+ */
+bool modem_protocol_is_busy(struct spi_device *sdev)
+{
+ int i;
+
+ for (i = 0; i < IPC_NBR_SUPPORTED_SPI_LINKS; i++)
+ switch (l1_context.device_context[i].state->id) {
+ case IPC_SM_IDL:
+ case IPC_SM_INIT:
+ case IPC_SM_WAIT_SLAVE_STABLE:
+ /* not busy; continue checking */
+ break;
+ default:
+ dev_info(&sdev->dev, "link %d is busy\n", i);
+ return true;
+ }
+ return false;
+}
+
+int modem_protocol_suspend(struct spi_device *sdev)
+{
+ struct modem_m6718_spi_link_platform_data *link =
+ sdev->dev.platform_data;
+ struct ipc_link_context *context;
+ int link_id;
+
+ if (link == NULL) {
+ /* platform data missing in board config? */
+ dev_err(&sdev->dev, "error: no platform data for link!\n");
+ return -ENODEV;
+ }
+
+ link_id = link->id;
+ context = &l1_context.device_context[link_id];
+
+ if (link_id >= IPC_NBR_SUPPORTED_SPI_LINKS) {
+ dev_err(&sdev->dev,
+ "link %d error: too many links! (max %d)\n",
+ link->id, IPC_NBR_SUPPORTED_SPI_LINKS);
+ return -ENODEV;
+ }
+
+ ipc_util_suspend_link(context);
+ return 0;
+}
+
+int modem_protocol_resume(struct spi_device *sdev)
+{
+ struct modem_m6718_spi_link_platform_data *link =
+ sdev->dev.platform_data;
+ struct ipc_link_context *context;
+ int link_id;
+
+ if (link == NULL) {
+ /* platform data missing in board config? */
+ dev_err(&sdev->dev, "error: no platform data for link!\n");
+ return -ENODEV;
+ }
+
+ link_id = link->id;
+ context = &l1_context.device_context[link_id];
+
+ if (link_id >= IPC_NBR_SUPPORTED_SPI_LINKS) {
+ dev_err(&sdev->dev,
+ "link %d error: too many links! (max %d)\n",
+ link->id, IPC_NBR_SUPPORTED_SPI_LINKS);
+ return -ENODEV;
+ }
+
+ ipc_util_resume_link(context);
+
+ /*
+ * If the resume event was an interrupt from the slave then the event
+ * is pending and we need to service it now.
+ */
+ if (ipc_util_int_is_active(context)) {
+ dev_dbg(&sdev->dev,
+ "link %d: slave-ready is pending after resume\n",
+ link_id);
+ ipc_sm_kick(IPC_SM_RUN_SLAVE_IRQ, context);
+ }
+ return 0;
+}
+
+static void spi_tfr_complete(void *context)
+{
+ ipc_sm_kick(IPC_SM_RUN_TFR_COMPLETE,
+ (struct ipc_link_context *)context);
+}
+
+static irqreturn_t slave_ready_irq(int irq, void *dev)
+{
+ struct ipc_link_context *context = (struct ipc_link_context *)dev;
+ struct modem_m6718_spi_link_platform_data *link = context->link;
+ struct spi_device *sdev = context->sdev;
+
+ if (irq != GPIO_TO_IRQ(link->gpio.int_pin)) {
+ dev_err(&sdev->dev,
+ "link %d error: spurious slave irq!", link->id);
+ return IRQ_NONE;
+ }
+
+#ifdef WORKAROUND_DUPLICATED_IRQ
+ if (pl022_tfr_in_progress(sdev)) {
+ dev_warn(&sdev->dev,
+ "link %d warning: slave irq while transfer "
+ "is active! discarding event\n", link->id);
+ return IRQ_HANDLED;
+ }
+#endif
+ ipc_sm_kick(IPC_SM_RUN_SLAVE_IRQ, context);
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
+static int modem_state_callback(unsigned long unused)
+{
+ int modem_state = modem_state_get_state();
+ struct ipc_link_context *contexts = l1_context.device_context;
+ u8 i;
+
+ pr_info("M6718 IPC protocol modemstate reports modem is %s\n",
+ modem_state_to_str(modem_state));
+
+ switch (modem_state) {
+ case MODEM_STATE_ON:
+ /*
+ * Modem is on, ensure each link is configured and trigger
+ * a state change on link0 to begin handshake.
+ */
+ for (i = 0; i < IPC_NBR_SUPPORTED_SPI_LINKS; i++)
+ ipc_util_link_gpio_config(&contexts[i]);
+ ipc_sm_kick(IPC_SM_RUN_INIT, &contexts[0]);
+ break;
+ case MODEM_STATE_OFF:
+ case MODEM_STATE_RESET:
+ case MODEM_STATE_CRASH:
+ /* force all links to reset */
+ for (i = 0; i < IPC_NBR_SUPPORTED_SPI_LINKS; i++)
+ ipc_sm_kick(IPC_SM_RUN_RESET, &contexts[i]);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static void modem_state_reg_wq(struct work_struct *work)
+{
+ if (modem_state_register_callback(modem_state_callback, 0) == -EAGAIN) {
+ pr_info("M6718 IPC protocol failed to register with "
+ "modemstate, will retry\n");
+ schedule_delayed_work(&modem_state_reg_work,
+ (MODEM_STATE_REGISTER_TMO_MS * HZ) / 1000);
+ } else {
+ pr_info("M6718 IPC protocol registered with modemstate\n");
+ }
+}
+#endif
+
+int modem_protocol_probe(struct spi_device *sdev)
+{
+ struct modem_m6718_spi_link_platform_data *link =
+ sdev->dev.platform_data;
+ struct ipc_link_context *context;
+ int link_id;
+
+ if (link == NULL) {
+ /* platform data missing in board config? */
+ dev_err(&sdev->dev, "error: no platform data for link!\n");
+ return -ENODEV;
+ }
+
+ link_id = link->id;
+ context = &l1_context.device_context[link_id];
+
+ if (link_id >= IPC_NBR_SUPPORTED_SPI_LINKS) {
+ dev_err(&sdev->dev,
+ "link %d error: too many links! (max %d)\n",
+ link->id, IPC_NBR_SUPPORTED_SPI_LINKS);
+ return -ENODEV;
+ }
+
+ dev_info(&sdev->dev,
+ "link %d: registering SPI link bus:%d cs:%d\n",
+ link->id, sdev->master->bus_num, sdev->chip_select);
+
+ /* update spi device with correct word size for our device */
+ sdev->bits_per_word = 16;
+ spi_setup(sdev);
+
+ /* init link context */
+ context->link = link;
+ context->sdev = sdev;
+ ipc_util_resume_link(context);
+ atomic_set(&context->gpio_configured, 0);
+ atomic_set(&context->state_int,
+ ipc_util_int_level_inactive(context));
+ spin_lock_init(&context->sm_lock);
+ context->state = ipc_sm_init_state(context);
+ ipc_util_spi_message_init(context, spi_tfr_complete);
+ init_timer(&context->comms_timer);
+ context->comms_timer.function = modem_comms_timeout;
+ context->comms_timer.data = (unsigned long)context;
+ init_timer(&context->slave_stable_timer);
+ context->slave_stable_timer.function = slave_stable_timeout;
+ context->slave_stable_timer.data = (unsigned long)context;
+
+ if (!ipc_util_link_gpio_request(context, slave_ready_irq))
+ return -ENODEV;
+ if (!ipc_util_link_gpio_config(context))
+ return -ENODEV;
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_VERIFY_FRAMES
+ context->last_frame = NULL;
+#endif
+
+ ipc_queue_init(context);
+ ipc_dbg_debugfs_link_init(context);
+ ipc_dbg_throughput_link_init(context);
+ ipc_create_netlink_socket(context);
+
+ /*
+ * For link0 (the handshake link) we force a state transition now so
+ * that it prepares for boot sync.
+ */
+ if (link->id == 0)
+ ipc_sm_kick(IPC_SM_RUN_INIT, context);
+
+ /*
+ * unlikely but possible: for links other than 0, check if handshake is
+ * already complete by the time this link is probed - if so we force a
+ * state transition since the one issued by the handshake exit actions
+ * will have been ignored.
+ */
+ if (link->id > 0 && atomic_read(&l1_context.boot_sync_done)) {
+ dev_dbg(&sdev->dev,
+ "link %d: boot sync is done, kicking state machine\n",
+ link->id);
+ ipc_sm_kick(IPC_SM_RUN_INIT, context);
+ }
+ return 0;
+}
+
+void modem_protocol_exit(void)
+{
+ int i;
+
+ pr_info("M6718 IPC protocol exit\n");
+ for (i = 0; i < IPC_NBR_SUPPORTED_SPI_LINKS; i++)
+ ipc_util_link_gpio_unconfig(&l1_context.device_context[i]);
+}
diff --git a/drivers/modem/m6718_spi/queue.c b/drivers/modem/m6718_spi/queue.c
new file mode 100644
index 00000000000..fe23ac36736
--- /dev/null
+++ b/drivers/modem/m6718_spi/queue.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010,2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * U9500 <-> M6718 IPC protocol implementation using SPI:
+ * TX queue functionality.
+ */
+#include <linux/modem/m6718_spi/modem_driver.h>
+#include "modem_util.h"
+
+#define FRAME_LENGTH_ALIGN (4)
+#define MAX_FRAME_COUNTER (256)
+
+void ipc_queue_init(struct ipc_link_context *context)
+{
+ spin_lock_init(&context->tx_q_update_lock);
+ atomic_set(&context->tx_q_count, 0);
+ context->tx_q_free = IPC_TX_QUEUE_MAX_SIZE;
+ INIT_LIST_HEAD(&context->tx_q);
+ context->tx_frame_counter = 0;
+}
+
+void ipc_queue_delete_frame(struct ipc_tx_queue *frame)
+{
+ kfree(frame);
+}
+
+struct ipc_tx_queue *ipc_queue_new_frame(struct ipc_link_context *link_context,
+ u32 l2_length)
+{
+ struct ipc_tx_queue *frame;
+ u32 padded_len = l2_length;
+
+ /* frame length padded to alignment boundary */
+ if (padded_len % FRAME_LENGTH_ALIGN)
+ padded_len += (FRAME_LENGTH_ALIGN -
+ (padded_len % FRAME_LENGTH_ALIGN));
+
+ dev_dbg(&link_context->sdev->dev,
+ "link %d: new frame: length %d, padded to %d\n",
+ link_context->link->id, l2_length, padded_len);
+
+ frame = kzalloc(sizeof(*frame) + padded_len, GFP_ATOMIC);
+ if (frame == NULL) {
+ dev_err(&link_context->sdev->dev,
+ "link %d error: failed to allocate frame\n",
+ link_context->link->id);
+ return NULL;
+ }
+
+ frame->actual_len = l2_length;
+ frame->len = padded_len;
+ frame->data = frame + 1;
+ return frame;
+}
+
+bool ipc_queue_is_empty(struct ipc_link_context *context)
+{
+ unsigned long flags;
+ bool empty;
+
+ spin_lock_irqsave(&context->tx_q_update_lock, flags);
+ empty = list_empty(&context->tx_q);
+ spin_unlock_irqrestore(&context->tx_q_update_lock, flags);
+
+ return empty;
+}
+
+int ipc_queue_push_frame(struct ipc_link_context *context, u8 channel,
+ u32 length, void *data)
+{
+ u32 l2_hdr;
+ unsigned long flags;
+ struct ipc_tx_queue *frame;
+ int *tx_frame_counter = &context->tx_frame_counter;
+ int qcount;
+
+ /*
+ * Max queue size is only approximate so we allow it to go a few bytes
+ * over the limit
+ */
+ if (context->tx_q_free < length) {
+ dev_dbg(&context->sdev->dev,
+ "link %d: tx queue full, wanted %d free %d\n",
+ context->link->id,
+ length,
+ context->tx_q_free);
+ return -EAGAIN;
+ }
+
+ frame = ipc_queue_new_frame(context, length + IPC_L2_HDR_SIZE);
+ if (frame == NULL)
+ return -ENOMEM;
+
+ /* create l2 header and copy to pdu buffer */
+ l2_hdr = ipc_util_make_l2_header(channel, length);
+ *(u32 *)frame->data = l2_hdr;
+
+ /* copy the l2 sdu into the pdu buffer after the header */
+ memcpy(frame->data + IPC_L2_HDR_SIZE, data, length);
+
+ spin_lock_irqsave(&context->tx_q_update_lock, flags);
+ frame->counter = *tx_frame_counter;
+ *tx_frame_counter = (*tx_frame_counter + 1) % MAX_FRAME_COUNTER;
+ list_add_tail(&frame->node, &context->tx_q);
+ qcount = atomic_add_return(1, &context->tx_q_count);
+ /* tx_q_free could go negative here */
+ context->tx_q_free -= frame->len;
+#ifdef CONFIG_DEBUG_FS
+ context->tx_q_min = min(context->tx_q_free, context->tx_q_min);
+#endif
+ spin_unlock_irqrestore(&context->tx_q_update_lock, flags);
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: push tx frame %d: %08x (ch %d len %d), "
+ "new count %d, new free %d\n",
+ context->link->id,
+ frame->counter,
+ l2_hdr,
+ ipc_util_get_l2_channel(l2_hdr),
+ ipc_util_get_l2_length(l2_hdr),
+ qcount,
+ context->tx_q_free);
+ return 0;
+}
+
+struct ipc_tx_queue *ipc_queue_get_frame(struct ipc_link_context *context)
+{
+ unsigned long flags;
+ struct ipc_tx_queue *frame;
+ int qcount;
+
+ spin_lock_irqsave(&context->tx_q_update_lock, flags);
+ frame = list_first_entry(&context->tx_q, struct ipc_tx_queue, node);
+ list_del(&frame->node);
+ qcount = atomic_sub_return(1, &context->tx_q_count);
+ context->tx_q_free += frame->len;
+ spin_unlock_irqrestore(&context->tx_q_update_lock, flags);
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: get tx frame %d, new count %d, "
+ "new free %d\n",
+ context->link->id, frame->counter, qcount, context->tx_q_free);
+ return frame;
+}
+
+void ipc_queue_reset(struct ipc_link_context *context)
+{
+ unsigned long flags;
+ struct ipc_tx_queue *frame;
+ int qcount;
+
+ spin_lock_irqsave(&context->tx_q_update_lock, flags);
+ qcount = atomic_read(&context->tx_q_count);
+ while (qcount != 0) {
+ frame = list_first_entry(&context->tx_q,
+ struct ipc_tx_queue, node);
+ list_del(&frame->node);
+ ipc_queue_delete_frame(frame);
+ qcount = atomic_sub_return(1, &context->tx_q_count);
+ }
+ spin_unlock_irqrestore(&context->tx_q_update_lock, flags);
+}
diff --git a/drivers/modem/m6718_spi/statemachine.c b/drivers/modem/m6718_spi/statemachine.c
new file mode 100644
index 00000000000..bb047fcee18
--- /dev/null
+++ b/drivers/modem/m6718_spi/statemachine.c
@@ -0,0 +1,1089 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010,2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * U9500 <-> M6718 IPC protocol implementation using SPI.
+ * state machine definition and functionality.
+ */
+#include <linux/modem/m6718_spi/modem_driver.h>
+#include "modem_statemachine.h"
+#include "modem_util.h"
+#include "modem_netlink.h"
+#include "modem_debug.h"
+#include "modem_queue.h"
+#include "modem_protocol.h"
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
+#include "modem_state.h"
+#endif
+
+#define CMD_BOOTREQ (1)
+#define CMD_BOOTRESP (2)
+#define CMD_WRITE (3)
+#define CMD_READ (4)
+
+static u8 sm_init_enter(u8 event, struct ipc_link_context *context)
+{
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
+ /* if modem is off un-configure the IPC GPIO pins for low-power */
+ if (modem_state_get_state() == MODEM_STATE_OFF) {
+ dev_info(&context->sdev->dev,
+ "link %d: modem is off, un-configuring GPIO\n",
+ context->link->id);
+ ipc_util_link_gpio_unconfig(context);
+ }
+#endif
+ /* nothing more to do until an event happens */
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_init_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ bool int_active = false;
+
+ /*
+ * For reset event just re-enter init in case the modem has
+ * powered off - we need to reconfigure our GPIO pins
+ */
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_INIT);
+
+ /* re-sample link INT pin */
+ int_active = ipc_util_int_is_active(context);
+ atomic_set(&context->state_int, int_active);
+
+ dev_info(&context->sdev->dev,
+ "link %d: link initialised; SS:INACTIVE(%d) INT:%s(%d)\n",
+ context->link->id,
+ ipc_util_ss_level_inactive(context),
+ int_active ? "ACTIVE" : "INACTIVE",
+ int_active ? ipc_util_int_level_active(context) :
+ ipc_util_int_level_inactive(context));
+
+ /* handshake is only on link 0 */
+ if (context->link->id == 0) {
+ if (!int_active) {
+ dev_info(&context->sdev->dev,
+ "link %d: slave INT signal is inactive\n",
+ context->link->id);
+ /* start boot handshake */
+ return ipc_sm_state(IPC_SM_SLW_TX_BOOTREQ);
+ } else {
+ /* wait for slave INT signal to stabilise inactive */
+ return ipc_sm_state(IPC_SM_WAIT_SLAVE_STABLE);
+ }
+ } else {
+ dev_info(&context->sdev->dev,
+ "link %d: boot sync not needed, going idle\n",
+ context->link->id);
+ return ipc_sm_state(IPC_SM_IDL);
+ }
+}
+
+static u8 sm_wait_slave_stable_enter(u8 event, struct ipc_link_context *context)
+{
+ static unsigned long printk_warn_time;
+ if (printk_timed_ratelimit(&printk_warn_time, 60 * 1000))
+ dev_info(&context->sdev->dev,
+ "link %d: waiting for stable inactive slave INT\n",
+ context->link->id);
+ ipc_util_start_slave_stable_timer(context);
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_wait_slave_stable_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ if (!ipc_util_int_is_active(context)) {
+ dev_info(&context->sdev->dev,
+ "link %d: slave INT signal is stable inactive\n",
+ context->link->id);
+ return ipc_sm_state(IPC_SM_SLW_TX_BOOTREQ);
+ } else {
+ return ipc_sm_state(IPC_SM_WAIT_SLAVE_STABLE);
+ }
+}
+
+static u8 sm_wait_handshake_inactive_enter(u8 event,
+ struct ipc_link_context *context)
+{
+ dev_info(&context->sdev->dev,
+ "link %d: waiting for stable inactive slave INT\n",
+ context->link->id);
+ ipc_util_start_slave_stable_timer(context);
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_wait_handshake_inactive_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ int i;
+
+ if (!ipc_util_int_is_active(context)) {
+ dev_info(&context->sdev->dev,
+ "link %d: slave INT signal is inactive, going idle\n",
+ context->link->id);
+
+ /* modem sync is done */
+ atomic_inc(&l1_context.boot_sync_done);
+ ipc_broadcast_modem_online(context);
+
+ /*
+ * Kick the state machine for any initialised links - skip link0
+ * since this link has just completed handshake
+ */
+ for (i = 1; i < IPC_NBR_SUPPORTED_SPI_LINKS; i++)
+ if (l1_context.device_context[i].state != NULL) {
+ dev_dbg(&context->sdev->dev,
+ "link %d has already been probed, "
+ "kicking state machine\n", i);
+ ipc_sm_kick(IPC_SM_RUN_INIT,
+ &l1_context.device_context[i]);
+ }
+ return ipc_sm_state(IPC_SM_IDL);
+ } else {
+ return ipc_sm_state(IPC_SM_WAIT_HANDSHAKE_INACTIVE);
+ }
+}
+
+static u8 sm_idl_enter(u8 event, struct ipc_link_context *context)
+{
+ ipc_util_deactivate_ss(context);
+ ipc_dbg_enter_idle(context);
+
+ /* check if tx queue contains items */
+ if (atomic_read(&context->tx_q_count) > 0) {
+ dev_dbg(&context->sdev->dev,
+ "link %d: tx queue contains items\n",
+ context->link->id);
+ return IPC_SM_RUN_TX_REQ;
+ }
+
+ /* check if modem has already requested transaction start */
+ if (atomic_read(&context->state_int)) {
+ dev_dbg(&context->sdev->dev,
+ "link %d: slave has already signalled ready\n",
+ context->link->id);
+ return IPC_SM_RUN_SLAVE_IRQ;
+ }
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: going idle\n", context->link->id);
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_idl_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ ipc_dbg_exit_idle(context);
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_RESET);
+ else if (event == IPC_SM_RUN_TX_REQ)
+ return ipc_sm_state(IPC_SM_SLW_TX_WR_CMD);
+ else if (event == IPC_SM_RUN_SLAVE_IRQ)
+ return ipc_sm_state(IPC_SM_SLW_TX_RD_CMD);
+ else
+ return ipc_sm_state(IPC_SM_HALT);
+}
+
+static u8 sm_slw_tx_wr_cmd_enter(u8 event, struct ipc_link_context *context)
+{
+ struct ipc_tx_queue *frame;
+
+ /* get the frame from the head of the tx queue */
+ if (ipc_queue_is_empty(context)) {
+ dev_err(&context->sdev->dev,
+ "link %d error: tx queue is empty!\n",
+ context->link->id);
+ return IPC_SM_RUN_ABORT;
+ }
+ frame = ipc_queue_get_frame(context);
+ ipc_dbg_dump_frame(&context->sdev->dev, context->link->id, frame, true);
+
+ context->cmd = ipc_util_make_l1_header(CMD_WRITE, frame->counter,
+ frame->len);
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: TX FRAME cmd %08x (type %d counter %d len %d)\n",
+ context->link->id,
+ context->cmd,
+ ipc_util_get_l1_cmd(context->cmd),
+ ipc_util_get_l1_counter(context->cmd),
+ ipc_util_get_l1_length(context->cmd));
+
+ ipc_util_spi_message_prepare(context, &context->cmd,
+ NULL, IPC_L1_HDR_SIZE);
+ context->frame = frame;
+
+ /* slave might already have signalled ready to transmit */
+ if (atomic_read(&context->state_int)) {
+ dev_dbg(&context->sdev->dev,
+ "link %d: slave has already signalled ready\n",
+ context->link->id);
+ ipc_util_activate_ss(context);
+ return IPC_SM_RUN_SLAVE_IRQ;
+ } else {
+ ipc_util_activate_ss_with_tmo(context);
+ return IPC_SM_RUN_NONE;
+ }
+}
+
+static const struct ipc_sm_state *sm_slw_tx_wr_cmd_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_RESET);
+ else if (event == IPC_SM_RUN_COMMS_TMO)
+ return ipc_sm_state(IPC_SM_HALT);
+ else
+ return ipc_sm_state(IPC_SM_ACT_TX_WR_CMD);
+}
+
+static u8 sm_act_tx_wr_cmd_enter(u8 event, struct ipc_link_context *context)
+{
+ int err;
+
+ /* slave is ready - start the spi transfer */
+ dev_dbg(&context->sdev->dev,
+ "link %d: starting spi tfr\n", context->link->id);
+ err = spi_async(context->sdev, &context->spi_message);
+ if (err < 0) {
+ dev_err(&context->sdev->dev,
+ "link %d error: spi tfr start failed, error %d\n",
+ context->link->id, err);
+ return IPC_SM_RUN_ABORT;
+ }
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_act_tx_wr_cmd_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_RESET);
+ else
+ return ipc_sm_state(IPC_SM_SLW_TX_WR_DAT);
+}
+
+static u8 sm_slw_tx_wr_dat_enter(u8 event, struct ipc_link_context *context)
+{
+ /* prepare to transfer the frame tx data */
+ ipc_util_spi_message_prepare(context, context->frame->data,
+ NULL, context->frame->len);
+
+ /* slave might already have signalled ready to transmit */
+ if (atomic_read(&context->state_int)) {
+ dev_dbg(&context->sdev->dev,
+ "link %d: slave has already signalled ready\n",
+ context->link->id);
+ ipc_util_activate_ss(context);
+ return IPC_SM_RUN_SLAVE_IRQ;
+ } else {
+ ipc_util_activate_ss_with_tmo(context);
+ return IPC_SM_RUN_NONE;
+ }
+}
+
+static const struct ipc_sm_state *sm_slw_tx_wr_dat_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_RESET);
+ else if (event == IPC_SM_RUN_COMMS_TMO)
+ return ipc_sm_state(IPC_SM_HALT);
+ else
+ return ipc_sm_state(IPC_SM_ACT_TX_WR_DAT);
+}
+
+static u8 sm_act_tx_wr_dat_enter(u8 event, struct ipc_link_context *context)
+{
+ int err;
+
+ /* slave is ready - start the spi transfer */
+ dev_dbg(&context->sdev->dev,
+ "link %d: starting spi tfr\n", context->link->id);
+ err = spi_async(context->sdev, &context->spi_message);
+ if (err < 0) {
+ dev_err(&context->sdev->dev,
+ "link %d error: spi tfr start failed, error %d\n",
+ context->link->id, err);
+ return IPC_SM_RUN_ABORT;
+ }
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_act_tx_wr_dat_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_RESET);
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ /* frame is sent, increment link tx counter */
+ context->tx_bytes += context->frame->actual_len;
+#endif
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_VERIFY_FRAMES
+ {
+ u8 channel;
+
+ channel = ipc_util_get_l2_channel(*(u32 *)context->frame->data);
+ if (ipc_util_channel_is_loopback(channel)) {
+ context->last_frame = context->frame;
+ } else {
+ ipc_queue_delete_frame(context->frame);
+ context->frame = NULL;
+ }
+ }
+#else
+ /* free the sent frame */
+ ipc_queue_delete_frame(context->frame);
+ context->frame = NULL;
+#endif
+ return ipc_sm_state(IPC_SM_SLW_TX_RD_CMD);
+}
+
+static u8 sm_slw_tx_rd_cmd_enter(u8 event, struct ipc_link_context *context)
+{
+ context->cmd = ipc_util_make_l1_header(CMD_READ, 0, 0);
+ dev_dbg(&context->sdev->dev,
+ "link %d: cmd %08x (type %d)\n",
+ context->link->id,
+ context->cmd,
+ ipc_util_get_l1_cmd(context->cmd));
+
+ /* prepare the spi message to transfer */
+ ipc_util_spi_message_prepare(context, &context->cmd,
+ NULL, IPC_L1_HDR_SIZE);
+
+ /* check if the slave requested this transaction */
+ if (event == IPC_SM_RUN_SLAVE_IRQ) {
+ dev_dbg(&context->sdev->dev,
+ "link %d: slave initiated transaction, continue\n",
+ context->link->id);
+ ipc_util_activate_ss(context);
+ return IPC_SM_RUN_SLAVE_IRQ;
+ } else {
+ /* slave might already have signalled ready to transmit */
+ if (atomic_read(&context->state_int)) {
+ dev_dbg(&context->sdev->dev,
+ "link %d: slave has already signalled ready\n",
+ context->link->id);
+ ipc_util_activate_ss(context);
+ return IPC_SM_RUN_SLAVE_IRQ;
+ } else {
+ ipc_util_activate_ss_with_tmo(context);
+ return IPC_SM_RUN_NONE;
+ }
+ }
+}
+
+static const struct ipc_sm_state *sm_slw_tx_rd_cmd_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_RESET);
+ else if (event == IPC_SM_RUN_COMMS_TMO)
+ return ipc_sm_state(IPC_SM_HALT);
+ else
+ return ipc_sm_state(IPC_SM_ACT_TX_RD_CMD);
+}
+
+static u8 sm_act_tx_rd_cmd_enter(u8 event, struct ipc_link_context *context)
+{
+ int err;
+
+ /* slave is ready - start the spi transfer */
+ dev_dbg(&context->sdev->dev,
+ "link %d: starting spi tfr\n", context->link->id);
+ err = spi_async(context->sdev, &context->spi_message);
+ if (err < 0) {
+ dev_err(&context->sdev->dev,
+ "link %d error: spi tfr start failed, error %d\n",
+ context->link->id, err);
+ return IPC_SM_RUN_ABORT;
+ }
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_act_tx_rd_cmd_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_RESET);
+ else
+ return ipc_sm_state(IPC_SM_SLW_RX_WR_CMD);
+}
+
+static u8 sm_slw_rx_wr_cmd_enter(u8 event, struct ipc_link_context *context)
+{
+ /* prepare to receive MESSAGE WRITE frame header */
+ ipc_util_spi_message_prepare(context, NULL,
+ &context->cmd, IPC_L1_HDR_SIZE);
+
+ /* slave might already have signalled ready to transmit */
+ if (atomic_read(&context->state_int)) {
+ dev_dbg(&context->sdev->dev,
+ "link %d: slave has already signalled ready\n",
+ context->link->id);
+ ipc_util_activate_ss(context);
+ return IPC_SM_RUN_SLAVE_IRQ;
+ } else {
+ ipc_util_activate_ss_with_tmo(context);
+ return IPC_SM_RUN_NONE;
+ }
+}
+
+static const struct ipc_sm_state *sm_slw_rx_wr_cmd_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_RESET);
+ else if (event == IPC_SM_RUN_COMMS_TMO)
+ return ipc_sm_state(IPC_SM_HALT);
+ else
+ return ipc_sm_state(IPC_SM_ACT_RX_WR_CMD);
+}
+
+static u8 sm_act_rx_wr_cmd_enter(u8 event, struct ipc_link_context *context)
+{
+ int err;
+
+ /* slave is ready - start the spi transfer */
+ dev_dbg(&context->sdev->dev,
+ "link %d: starting spi tfr\n", context->link->id);
+ err = spi_async(context->sdev, &context->spi_message);
+ if (err < 0) {
+ dev_err(&context->sdev->dev,
+ "link %d error: spi tfr start failed, error %d\n",
+ context->link->id, err);
+ return IPC_SM_RUN_ABORT;
+ }
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_act_rx_wr_cmd_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ u8 cmd_type = ipc_util_get_l1_cmd(context->cmd);
+ int counter = ipc_util_get_l1_counter(context->cmd);
+ int length = ipc_util_get_l1_length(context->cmd);
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: RX HEADER %08x (type %d counter %d length %d)\n",
+ context->link->id,
+ context->cmd,
+ cmd_type,
+ counter,
+ length);
+
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_RESET);
+
+ if (cmd_type == CMD_WRITE) {
+ /* slave has data to send - allocate a frame to hold it */
+ context->frame = ipc_queue_new_frame(context, length);
+ if (context->frame == NULL)
+ return ipc_sm_state(IPC_SM_IDL);
+
+ context->frame->counter = counter;
+ ipc_util_spi_message_prepare(context, NULL,
+ context->frame->data, context->frame->len);
+ return ipc_sm_state(IPC_SM_ACT_RX_WR_DAT);
+ } else {
+ if (cmd_type != 0)
+ dev_err(&context->sdev->dev,
+ "link %d error: received invalid frame type %x "
+ "(%08x)! assuming TRANSACTION_END...\n",
+ context->link->id,
+ cmd_type,
+ context->cmd);
+
+ /* slave has no data to send */
+ dev_dbg(&context->sdev->dev,
+ "link %d: slave has no data to send\n",
+ context->link->id);
+ return ipc_sm_state(IPC_SM_IDL);
+ }
+}
+
+static u8 sm_act_rx_wr_dat_enter(u8 event, struct ipc_link_context *context)
+{
+ int err;
+
+ /* assume slave is still ready - prepare and start the spi transfer */
+ ipc_util_spi_message_prepare(context, NULL,
+ context->frame->data, context->frame->len);
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: starting spi tfr\n", context->link->id);
+ err = spi_async(context->sdev, &context->spi_message);
+ if (err < 0) {
+ dev_err(&context->sdev->dev,
+ "link %d error: spi tfr start failed, error %d\n",
+ context->link->id, err);
+ return IPC_SM_RUN_ABORT;
+ }
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_act_rx_wr_dat_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ u32 frame_hdr;
+ unsigned char l2_header;
+ unsigned int l2_length;
+ u8 *l2_data;
+
+ if (event == IPC_SM_RUN_RESET)
+ return ipc_sm_state(IPC_SM_RESET);
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: RX PAYLOAD %d bytes\n",
+ context->link->id, context->frame->len);
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ /* frame is received, increment link rx counter */
+ context->rx_bytes += context->frame->len;
+#endif
+ /* decode L2 header */
+ frame_hdr = *(u32 *)context->frame->data;
+ l2_header = ipc_util_get_l2_channel(frame_hdr);
+ l2_length = ipc_util_get_l2_length(frame_hdr);
+ l2_data = (u8 *)context->frame->data + IPC_L2_HDR_SIZE;
+
+ context->frame->actual_len = l2_length + IPC_L2_HDR_SIZE;
+ ipc_dbg_dump_frame(&context->sdev->dev, context->link->id,
+ context->frame, false);
+
+ if (l2_length > (context->frame->len - 4)) {
+ dev_err(&context->sdev->dev,
+ "link %d: suspicious frame: L1 len %d L2 len %d\n",
+ context->link->id, context->frame->len, l2_length);
+ }
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: L2 PDU decode: header 0x%08x channel %d length %d "
+ "data[%02x%02x%02x...]\n",
+ context->link->id, frame_hdr, l2_header, l2_length,
+ l2_data[0], l2_data[1], l2_data[2]);
+
+ if (ipc_util_channel_is_loopback(l2_header))
+ ipc_dbg_verify_rx_frame(context);
+
+ /* pass received frame up to L2mux layer */
+ if (!modem_protocol_channel_is_open(l2_header)) {
+ dev_err(&context->sdev->dev,
+ "link %d error: received frame on invalid channel %d, "
+ "frame discarded\n",
+ context->link->id, l2_header);
+ } else {
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT
+ /*
+ * Discard loopback frames if we are taking throughput
+ * measurements - we'll be loading the links and so will likely
+ * overload the buffers.
+ */
+ if (!ipc_util_channel_is_loopback(l2_header))
+#endif
+ modem_m6718_spi_receive(context->sdev,
+ l2_header, l2_length, l2_data);
+ }
+
+ /* data is copied by L2mux so free the frame here */
+ ipc_queue_delete_frame(context->frame);
+ context->frame = NULL;
+
+ /* check tx queue for content */
+ if (!ipc_queue_is_empty(context)) {
+ dev_dbg(&context->sdev->dev,
+ "link %d: tx queue not empty\n", context->link->id);
+ return ipc_sm_state(IPC_SM_SLW_TX_WR_CMD);
+ } else {
+ dev_dbg(&context->sdev->dev,
+ "link %d: tx queue empty\n", context->link->id);
+ return ipc_sm_state(IPC_SM_SLW_TX_RD_CMD);
+ }
+}
+
+static u8 sm_halt_enter(u8 event, struct ipc_link_context *context)
+{
+ dev_err(&context->sdev->dev,
+ "link %d error: HALTED\n", context->link->id);
+
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
+ /*
+ * Force modem reset, this will cause a reset event from the modemstate
+ * driver which will reset the links. If debugfs is enabled then there
+ * is a userspace file which controls whether MSR is enabled or not.
+ */
+#ifdef CONFIG_DEBUG_FS
+ if (l1_context.msr_disable) {
+ dev_info(&context->sdev->dev,
+ "link %d: MSR is disabled by user, "
+ "not requesting modem reset\n", context->link->id);
+ return IPC_SM_RUN_RESET;
+ }
+#endif
+ modem_state_force_reset();
+#endif
+ return IPC_SM_RUN_RESET;
+}
+
+static const struct ipc_sm_state *sm_halt_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ return ipc_sm_state(IPC_SM_RESET);
+}
+
+static u8 sm_reset_enter(u8 event, struct ipc_link_context *context)
+{
+ dev_err(&context->sdev->dev,
+ "link %d resetting\n", context->link->id);
+
+ if (context->link->id == 0)
+ ipc_broadcast_modem_reset(context);
+
+ ipc_util_deactivate_ss(context);
+ ipc_queue_reset(context);
+ if (context->frame != NULL) {
+ ipc_queue_delete_frame(context->frame);
+ context->frame = NULL;
+ }
+#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_VERIFY_FRAMES
+ if (context->last_frame != NULL) {
+ ipc_queue_delete_frame(context->last_frame);
+ context->last_frame = NULL;
+ }
+#endif
+ dev_dbg(&context->sdev->dev,
+ "link %d reset completed\n", context->link->id);
+
+ return IPC_SM_RUN_RESET;
+}
+
+static const struct ipc_sm_state *sm_reset_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ return ipc_sm_state(IPC_SM_INIT);
+}
+
+static u8 sm_slw_tx_bootreq_enter(u8 event, struct ipc_link_context *context)
+{
+ dev_info(&context->sdev->dev,
+ "link %d: waiting for boot sync\n", context->link->id);
+
+ ipc_util_activate_ss(context);
+ context->cmd = ipc_util_make_l1_header(CMD_BOOTREQ, 0,
+ IPC_DRIVER_VERSION);
+ dev_dbg(&context->sdev->dev,
+ "link %d: TX HEADER cmd %08x (type %x)\n",
+ context->link->id,
+ context->cmd,
+ ipc_util_get_l1_cmd(context->cmd));
+ ipc_util_spi_message_prepare(context, &context->cmd,
+ NULL, IPC_L1_HDR_SIZE);
+
+ /* wait now for the slave to indicate ready... */
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_slw_tx_bootreq_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ return ipc_sm_state(IPC_SM_ACT_TX_BOOTREQ);
+}
+
+static u8 sm_act_tx_bootreq_enter(u8 event, struct ipc_link_context *context)
+{
+ int err;
+
+ /* slave is ready - start the spi transfer */
+ dev_dbg(&context->sdev->dev,
+ "link %d: starting spi tfr\n", context->link->id);
+ err = spi_async(context->sdev, &context->spi_message);
+ if (err < 0) {
+ dev_err(&context->sdev->dev,
+ "link %d error: spi tfr start failed, error %d\n",
+ context->link->id, err);
+ return IPC_SM_RUN_ABORT;
+ }
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_act_tx_bootreq_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ return ipc_sm_state(IPC_SM_SLW_RX_BOOTRESP);
+}
+
+static u8 sm_slw_rx_bootresp_enter(u8 event, struct ipc_link_context *context)
+{
+ /* prepare to receive BOOTRESP frame header */
+ ipc_util_spi_message_prepare(context, NULL,
+ &context->cmd, IPC_L1_HDR_SIZE);
+
+ /* slave might already have signalled ready to transmit */
+ if (atomic_read(&context->state_int)) {
+ dev_dbg(&context->sdev->dev,
+ "link %d: slave has already signalled ready\n",
+ context->link->id);
+ ipc_util_activate_ss(context);
+ return IPC_SM_RUN_SLAVE_IRQ;
+ } else {
+ ipc_util_activate_ss_with_tmo(context);
+ return IPC_SM_RUN_NONE;
+ }
+}
+
+static const struct ipc_sm_state *sm_slw_rx_bootresp_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ if (event == IPC_SM_RUN_COMMS_TMO) {
+ /*
+ * Modem timeout: was it really ready or just noise?
+ * Revert to waiting for handshake to start.
+ */
+ ipc_util_deactivate_ss(context);
+ return ipc_sm_state(IPC_SM_SLW_TX_BOOTREQ);
+ } else {
+ return ipc_sm_state(IPC_SM_ACT_RX_BOOTRESP);
+ }
+}
+
+static u8 sm_act_rx_bootresp_enter(u8 event, struct ipc_link_context *context)
+{
+ int err;
+
+ /* slave is ready - start the spi transfer */
+ dev_dbg(&context->sdev->dev,
+ "link %d: starting spi tfr\n", context->link->id);
+ err = spi_async(context->sdev, &context->spi_message);
+ if (err < 0) {
+ dev_err(&context->sdev->dev,
+ "link %d error: spi tfr start failed, error %d\n",
+ context->link->id, err);
+ return IPC_SM_RUN_ABORT;
+ }
+ return IPC_SM_RUN_NONE;
+}
+
+static const struct ipc_sm_state *sm_act_rx_bootresp_exit(u8 event,
+ struct ipc_link_context *context)
+{
+ u8 cmd_type = ipc_util_get_l1_cmd(context->cmd);
+ u8 modem_ver;
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: RX HEADER %08x (type %d)\n",
+ context->link->id, context->cmd, cmd_type);
+
+ if (cmd_type == CMD_BOOTRESP) {
+ modem_ver = ipc_util_get_l1_bootresp_ver(context->cmd);
+
+ dev_info(&context->sdev->dev,
+ "link %d: boot sync done; "
+ "APE version %02x, MODEM version %02x\n",
+ context->link->id, IPC_DRIVER_VERSION, modem_ver);
+
+ /* check for minimum required modem version */
+ if (modem_ver != IPC_DRIVER_MODEM_MIN_VER) {
+ dev_warn(&context->sdev->dev,
+ "link %d warning: modem version mismatch! "
+ "required version is %02x\n",
+ context->link->id,
+ IPC_DRIVER_MODEM_MIN_VER);
+ }
+
+ return ipc_sm_state(IPC_SM_WAIT_HANDSHAKE_INACTIVE);
+ } else {
+ /* invalid response... this is not our slave */
+ dev_err(&context->sdev->dev,
+ "link %d error: expected %x (BOOTRESP), received %x.\n",
+ context->link->id,
+ CMD_BOOTRESP,
+ cmd_type);
+ return ipc_sm_state(IPC_SM_HALT);
+ }
+}
+
+/* the driver protocol state machine */
+static const struct ipc_sm_state state_machine[IPC_SM_STATE_ID_NBR] = {
+ [IPC_SM_INIT] = {
+ .id = IPC_SM_INIT,
+ .enter = sm_init_enter,
+ .exit = sm_init_exit,
+ .events = IPC_SM_RUN_INIT | IPC_SM_RUN_RESET
+ },
+ [IPC_SM_HALT] = {
+ .id = IPC_SM_HALT,
+ .enter = sm_halt_enter,
+ .exit = sm_halt_exit,
+ .events = IPC_SM_RUN_RESET
+ },
+ [IPC_SM_RESET] = {
+ .id = IPC_SM_RESET,
+ .enter = sm_reset_enter,
+ .exit = sm_reset_exit,
+ .events = IPC_SM_RUN_RESET
+ },
+ [IPC_SM_WAIT_SLAVE_STABLE] = {
+ .id = IPC_SM_WAIT_SLAVE_STABLE,
+ .enter = sm_wait_slave_stable_enter,
+ .exit = sm_wait_slave_stable_exit,
+ .events = IPC_SM_RUN_STABLE_TMO
+ },
+ [IPC_SM_WAIT_HANDSHAKE_INACTIVE] = {
+ .id = IPC_SM_WAIT_HANDSHAKE_INACTIVE,
+ .enter = sm_wait_handshake_inactive_enter,
+ .exit = sm_wait_handshake_inactive_exit,
+ .events = IPC_SM_RUN_STABLE_TMO
+ },
+ [IPC_SM_SLW_TX_BOOTREQ] = {
+ .id = IPC_SM_SLW_TX_BOOTREQ,
+ .enter = sm_slw_tx_bootreq_enter,
+ .exit = sm_slw_tx_bootreq_exit,
+ .events = IPC_SM_RUN_SLAVE_IRQ
+ },
+ [IPC_SM_ACT_TX_BOOTREQ] = {
+ .id = IPC_SM_ACT_TX_BOOTREQ,
+ .enter = sm_act_tx_bootreq_enter,
+ .exit = sm_act_tx_bootreq_exit,
+ .events = IPC_SM_RUN_TFR_COMPLETE
+ },
+ [IPC_SM_SLW_RX_BOOTRESP] = {
+ .id = IPC_SM_SLW_RX_BOOTRESP,
+ .enter = sm_slw_rx_bootresp_enter,
+ .exit = sm_slw_rx_bootresp_exit,
+ .events = IPC_SM_RUN_SLAVE_IRQ | IPC_SM_RUN_COMMS_TMO
+ },
+ [IPC_SM_ACT_RX_BOOTRESP] = {
+ .id = IPC_SM_ACT_RX_BOOTRESP,
+ .enter = sm_act_rx_bootresp_enter,
+ .exit = sm_act_rx_bootresp_exit,
+ .events = IPC_SM_RUN_TFR_COMPLETE
+ },
+ [IPC_SM_IDL] = {
+ .id = IPC_SM_IDL,
+ .enter = sm_idl_enter,
+ .exit = sm_idl_exit,
+ .events = IPC_SM_RUN_SLAVE_IRQ | IPC_SM_RUN_TX_REQ |
+ IPC_SM_RUN_RESET
+ },
+ [IPC_SM_SLW_TX_WR_CMD] = {
+ .id = IPC_SM_SLW_TX_WR_CMD,
+ .enter = sm_slw_tx_wr_cmd_enter,
+ .exit = sm_slw_tx_wr_cmd_exit,
+ .events = IPC_SM_RUN_SLAVE_IRQ | IPC_SM_RUN_COMMS_TMO |
+ IPC_SM_RUN_RESET
+ },
+ [IPC_SM_ACT_TX_WR_CMD] = {
+ .id = IPC_SM_ACT_TX_WR_CMD,
+ .enter = sm_act_tx_wr_cmd_enter,
+ .exit = sm_act_tx_wr_cmd_exit,
+ .events = IPC_SM_RUN_TFR_COMPLETE | IPC_SM_RUN_RESET
+ },
+ [IPC_SM_SLW_TX_WR_DAT] = {
+ .id = IPC_SM_SLW_TX_WR_DAT,
+ .enter = sm_slw_tx_wr_dat_enter,
+ .exit = sm_slw_tx_wr_dat_exit,
+ .events = IPC_SM_RUN_SLAVE_IRQ | IPC_SM_RUN_COMMS_TMO |
+ IPC_SM_RUN_RESET
+ },
+ [IPC_SM_ACT_TX_WR_DAT] = {
+ .id = IPC_SM_ACT_TX_WR_DAT,
+ .enter = sm_act_tx_wr_dat_enter,
+ .exit = sm_act_tx_wr_dat_exit,
+ .events = IPC_SM_RUN_TFR_COMPLETE | IPC_SM_RUN_RESET
+ },
+ [IPC_SM_SLW_TX_RD_CMD] = {
+ .id = IPC_SM_SLW_TX_RD_CMD,
+ .enter = sm_slw_tx_rd_cmd_enter,
+ .exit = sm_slw_tx_rd_cmd_exit,
+ .events = IPC_SM_RUN_SLAVE_IRQ | IPC_SM_RUN_COMMS_TMO |
+ IPC_SM_RUN_RESET
+ },
+ [IPC_SM_ACT_TX_RD_CMD] = {
+ .id = IPC_SM_ACT_TX_RD_CMD,
+ .enter = sm_act_tx_rd_cmd_enter,
+ .exit = sm_act_tx_rd_cmd_exit,
+ .events = IPC_SM_RUN_TFR_COMPLETE | IPC_SM_RUN_RESET
+ },
+ [IPC_SM_SLW_RX_WR_CMD] = {
+ .id = IPC_SM_SLW_RX_WR_CMD,
+ .enter = sm_slw_rx_wr_cmd_enter,
+ .exit = sm_slw_rx_wr_cmd_exit,
+ .events = IPC_SM_RUN_SLAVE_IRQ | IPC_SM_RUN_COMMS_TMO |
+ IPC_SM_RUN_RESET
+ },
+ [IPC_SM_ACT_RX_WR_CMD] = {
+ .id = IPC_SM_ACT_RX_WR_CMD,
+ .enter = sm_act_rx_wr_cmd_enter,
+ .exit = sm_act_rx_wr_cmd_exit,
+ .events = IPC_SM_RUN_TFR_COMPLETE | IPC_SM_RUN_RESET
+ },
+ [IPC_SM_ACT_RX_WR_DAT] = {
+ .id = IPC_SM_ACT_RX_WR_DAT,
+ .enter = sm_act_rx_wr_dat_enter,
+ .exit = sm_act_rx_wr_dat_exit,
+ .events = IPC_SM_RUN_TFR_COMPLETE | IPC_SM_RUN_RESET
+ },
+};
+
+const struct ipc_sm_state *ipc_sm_idle_state(struct ipc_link_context *context)
+{
+ return ipc_sm_state(IPC_SM_IDL);
+}
+
+const struct ipc_sm_state *ipc_sm_init_state(struct ipc_link_context *context)
+{
+ return ipc_sm_state(IPC_SM_INIT);
+}
+
+const struct ipc_sm_state *ipc_sm_state(u8 id)
+{
+ BUG_ON(id >= IPC_SM_STATE_ID_NBR);
+ return &state_machine[id];
+}
+
+bool ipc_sm_valid_for_state(u8 event, const struct ipc_sm_state *state)
+{
+ return (state->events & event) == event;
+}
+
+static void state_machine_run(struct ipc_link_context *context, u8 event)
+{
+ struct modem_m6718_spi_link_platform_data *link = context->link;
+ struct spi_device *sdev = context->sdev;
+ const struct ipc_sm_state *cur_state = context->state;
+
+ /* some sanity checking */
+ if (context == NULL || link == NULL || cur_state == NULL) {
+ pr_err("M6718 IPC protocol error: "
+ "inconsistent driver state, ignoring event\n");
+ return;
+ }
+
+ dev_dbg(&sdev->dev, "link %d: RUNNING in %s (%s)\n", link->id,
+ ipc_dbg_state_id(cur_state), ipc_dbg_event(event));
+
+ /* valid trigger event for current state? */
+ if (!ipc_sm_valid_for_state(event, cur_state)) {
+ dev_dbg(&sdev->dev,
+ "link %d: ignoring invalid event\n", link->id);
+ ipc_dbg_ignoring_event(context, event);
+ return;
+ }
+ ipc_dbg_handling_event(context, event);
+
+ /* run machine while state entry functions trigger new changes */
+ do {
+ if (event == IPC_SM_RUN_SLAVE_IRQ &&
+ !ipc_util_int_is_active(context)) {
+ dev_err(&sdev->dev,
+ "link %d error: slave is not ready! (%s)",
+ link->id,
+ ipc_dbg_state_id(cur_state));
+ }
+
+ if (event == IPC_SM_RUN_ABORT) {
+ dev_err(&sdev->dev,
+ "link %d error: abort event\n", link->id);
+ /* reset state to idle */
+ context->state = ipc_sm_idle_state(context);
+ break;
+ } else {
+ /* exit current state */
+ dev_dbg(&sdev->dev, "link %d: exit %s (%s)\n",
+ link->id, ipc_dbg_state_id(cur_state),
+ ipc_dbg_event(event));
+ cur_state = cur_state->exit(event, context);
+ context->state = cur_state;
+ }
+
+ /* reset state of slave irq to prepare for next event */
+ if (event == IPC_SM_RUN_SLAVE_IRQ)
+ atomic_set(&context->state_int, 0);
+
+ /* enter new state */
+ dev_dbg(&sdev->dev, "link %d: enter %s (%s)\n", link->id,
+ ipc_dbg_state_id(cur_state), ipc_dbg_event(event));
+ event = context->state->enter(event, context);
+ ipc_dbg_entering_state(context);
+ } while (event != IPC_SM_RUN_NONE);
+
+ dev_dbg(&sdev->dev, "link %d: STOPPED in %s\n", link->id,
+ ipc_dbg_state_id(cur_state));
+}
+
+void ipc_sm_kick(u8 event, struct ipc_link_context *context)
+{
+ unsigned long flags;
+ struct modem_m6718_spi_link_platform_data *link = context->link;
+ struct spi_device *sdev = context->sdev;
+ struct spi_message *msg = &context->spi_message;
+ u8 i;
+
+ spin_lock_irqsave(&context->sm_lock, flags);
+ switch (event) {
+ case IPC_SM_RUN_SLAVE_IRQ:
+ dev_dbg(&sdev->dev,
+ "link %d EVENT: slave-ready irq\n", link->id);
+ del_timer(&context->comms_timer);
+ atomic_set(&context->state_int,
+ ipc_util_int_is_active(context));
+ break;
+
+ case IPC_SM_RUN_TFR_COMPLETE:
+ dev_dbg(&sdev->dev,
+ "link %d EVENT: spi tfr complete (status %d len %d)\n",
+ link->id, msg->status, msg->actual_length);
+ ipc_dbg_dump_spi_tfr(context);
+ break;
+
+ case IPC_SM_RUN_COMMS_TMO:
+ {
+ char *statestr;
+ struct ipc_link_context *contexts = l1_context.device_context;
+
+ statestr = ipc_dbg_link_state_str(context);
+ dev_err(&sdev->dev,
+ "link %d EVENT: modem comms timeout (%s)!\n",
+ link->id, ipc_dbg_state_id(context->state));
+ if (statestr != NULL) {
+ dev_err(&sdev->dev, "%s", statestr);
+ kfree(statestr);
+ }
+
+ /* cancel all link timeout timers except this one */
+ for (i = 0; i < IPC_NBR_SUPPORTED_SPI_LINKS; i++)
+ if (contexts[i].link->id != link->id)
+ del_timer(&contexts[i].comms_timer);
+ break;
+ }
+
+ case IPC_SM_RUN_STABLE_TMO:
+ dev_dbg(&sdev->dev,
+ "link %d EVENT: slave-stable timeout\n", link->id);
+ break;
+
+ case IPC_SM_RUN_RESET:
+ dev_dbg(&sdev->dev,
+ "link %d EVENT: reset\n", link->id);
+ del_timer(&context->comms_timer);
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ipc_util_link_is_suspended(context))
+ state_machine_run(context, event);
+ else
+ dev_dbg(&sdev->dev,
+ "link %d is suspended, waiting for resume\n", link->id);
+ spin_unlock_irqrestore(&context->sm_lock, flags);
+}
diff --git a/drivers/modem/m6718_spi/util.c b/drivers/modem/m6718_spi/util.c
new file mode 100644
index 00000000000..9026f4427dd
--- /dev/null
+++ b/drivers/modem/m6718_spi/util.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010,2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * U9500 <-> M6718 IPC protocol implementation using SPI:
+ * utility functions.
+ */
+#include <linux/gpio.h>
+#include <linux/modem/m6718_spi/modem_driver.h>
+#include "modem_util.h"
+
+#define MODEM_COMMS_TMO_MS (5000) /* 0 == no timeout */
+#define SLAVE_STABLE_TMO_MS (1000)
+
+#define DRIVER_NAME "ipcspi" /* name used when reserving gpio pins */
+
+
+bool ipc_util_channel_is_loopback(u8 channel)
+{
+ return channel == MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0 ||
+ channel == MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1;
+}
+
+u32 ipc_util_make_l2_header(u8 channel, u32 len)
+{
+ return ((channel & 0xf) << 28) | (len & 0x000fffff);
+}
+
+u8 ipc_util_get_l2_channel(u32 hdr)
+{
+ return hdr >> 28;
+}
+
+u32 ipc_util_get_l2_length(u32 hdr)
+{
+ return hdr & 0x000fffff;
+}
+
+u32 ipc_util_make_l1_header(u8 cmd, u8 counter, u32 len)
+{
+ return (cmd << 28) |
+ ((counter & 0x000000ff) << 20) |
+ (len & 0x000fffff);
+}
+
+u8 ipc_util_get_l1_cmd(u32 hdr)
+{
+ return hdr >> 28;
+}
+
+u8 ipc_util_get_l1_counter(u32 hdr)
+{
+ return (hdr >> 20) & 0x000000ff;
+}
+
+u32 ipc_util_get_l1_length(u32 hdr)
+{
+ return hdr & 0x000fffff;
+}
+
+u8 ipc_util_get_l1_bootresp_ver(u32 bootresp)
+{
+ return bootresp & 0x000000ff;
+}
+
+int ipc_util_ss_level_active(struct ipc_link_context *context)
+{
+ return context->link->gpio.ss_active == 0 ? 0 : 1;
+}
+
+int ipc_util_ss_level_inactive(struct ipc_link_context *context)
+{
+ return !ipc_util_ss_level_active(context);
+}
+
+int ipc_util_int_level_active(struct ipc_link_context *context)
+{
+ return context->link->gpio.int_active == 0 ? 0 : 1;
+}
+
+int ipc_util_int_level_inactive(struct ipc_link_context *context)
+{
+ return !ipc_util_int_level_active(context);
+}
+
+void ipc_util_deactivate_ss(struct ipc_link_context *context)
+{
+ gpio_set_value(context->link->gpio.ss_pin,
+ ipc_util_ss_level_inactive(context));
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: deactivated SS\n", context->link->id);
+}
+
+void ipc_util_activate_ss(struct ipc_link_context *context)
+{
+ gpio_set_value(context->link->gpio.ss_pin,
+ ipc_util_ss_level_active(context));
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: activated SS\n", context->link->id);
+}
+
+void ipc_util_activate_ss_with_tmo(struct ipc_link_context *context)
+{
+ gpio_set_value(context->link->gpio.ss_pin,
+ ipc_util_ss_level_active(context));
+
+#if MODEM_COMMS_TMO_MS == 0
+ dev_dbg(&context->sdev->dev,
+ "link %d: activated SS (timeout is disabled)\n",
+ context->link->id);
+#else
+ context->comms_timer.expires = jiffies +
+ ((MODEM_COMMS_TMO_MS * HZ) / 1000);
+ add_timer(&context->comms_timer);
+
+ dev_dbg(&context->sdev->dev,
+ "link %d: activated SS with timeout\n", context->link->id);
+#endif
+}
+
+bool ipc_util_int_is_active(struct ipc_link_context *context)
+{
+ return gpio_get_value(context->link->gpio.int_pin) ==
+ ipc_util_int_level_active(context);
+}
+
+bool ipc_util_link_is_idle(struct ipc_link_context *context)
+{
+ if (context->state == NULL)
+ return false;
+
+ switch (context->state->id) {
+ case IPC_SM_IDL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void ipc_util_start_slave_stable_timer(struct ipc_link_context *context)
+{
+ context->slave_stable_timer.expires =
+ jiffies + ((SLAVE_STABLE_TMO_MS * HZ) / 1000);
+ add_timer(&context->slave_stable_timer);
+}
+
+void ipc_util_spi_message_prepare(struct ipc_link_context *link_context,
+ void *tx_buf, void *rx_buf, int len)
+{
+ struct spi_transfer *tfr = &link_context->spi_transfer;
+ struct spi_message *msg = &link_context->spi_message;
+
+ tfr->tx_buf = tx_buf;
+ tfr->rx_buf = rx_buf;
+ tfr->len = len;
+ msg->context = link_context;
+}
+
+void ipc_util_spi_message_init(struct ipc_link_context *link_context,
+ void (*complete)(void *))
+{
+ struct spi_message *msg = &link_context->spi_message;
+ struct spi_transfer *tfr = &link_context->spi_transfer;
+
+ tfr->bits_per_word = 16;
+
+ /* common init of transfer - use default from board device */
+ tfr->cs_change = 0;
+ tfr->speed_hz = 0;
+ tfr->delay_usecs = 0;
+
+ /* common init of message */
+ spi_message_init(msg);
+ msg->spi = link_context->sdev;
+ msg->complete = complete;
+ spi_message_add_tail(tfr, msg);
+}
+
+bool ipc_util_link_gpio_request(struct ipc_link_context *context,
+ irqreturn_t (*irqhnd)(int, void*))
+{
+ struct spi_device *sdev = context->sdev;
+ struct modem_m6718_spi_link_platform_data *link = context->link;
+ unsigned long irqflags;
+
+ if (gpio_request(link->gpio.ss_pin, DRIVER_NAME) < 0) {
+ dev_err(&sdev->dev,
+ "link %d error: failed to get gpio %d for SS pin\n",
+ link->id,
+ link->gpio.ss_pin);
+ return false;
+ }
+ if (gpio_request(link->gpio.int_pin, DRIVER_NAME) < 0) {
+ dev_err(&sdev->dev,
+ "link %d error: failed to get gpio %d for INT pin\n",
+ link->id,
+ link->gpio.int_pin);
+ return false;
+ }
+
+ if (ipc_util_int_level_active(context) == 1)
+ irqflags = IRQF_TRIGGER_RISING;
+ else
+ irqflags = IRQF_TRIGGER_FALLING;
+
+ if (request_irq(GPIO_TO_IRQ(link->gpio.int_pin),
+ irqhnd,
+ irqflags,
+ DRIVER_NAME,
+ context) < 0) {
+ dev_err(&sdev->dev,
+ "link %d error: could not get irq %d\n",
+ link->id, GPIO_TO_IRQ(link->gpio.int_pin));
+ return false;
+ }
+ return true;
+}
+
+bool ipc_util_link_gpio_config(struct ipc_link_context *context)
+{
+ struct spi_device *sdev = context->sdev;
+ struct modem_m6718_spi_link_platform_data *link = context->link;
+
+ if (atomic_read(&context->gpio_configured) == 1)
+ return true;
+
+ dev_dbg(&sdev->dev, "link %d: configuring GPIO\n", link->id);
+
+ ipc_util_deactivate_ss(context);
+ gpio_direction_input(link->gpio.int_pin);
+ if (enable_irq_wake(GPIO_TO_IRQ(link->gpio.int_pin)) < 0) {
+ dev_err(&sdev->dev,
+ "link %d error: failed to enable wake on INT\n",
+ link->id);
+ return false;
+ }
+
+ atomic_set(&context->state_int, gpio_get_value(link->gpio.int_pin));
+ atomic_set(&context->gpio_configured, 1);
+ return true;
+}
+
+bool ipc_util_link_gpio_unconfig(struct ipc_link_context *context)
+{
+ struct spi_device *sdev = context->sdev;
+ struct modem_m6718_spi_link_platform_data *link = context->link;
+
+ if (atomic_read(&context->gpio_configured) == 0)
+ return true;
+
+ dev_dbg(&sdev->dev, "link %d: un-configuring GPIO\n", link->id);
+
+ /* SS: output anyway, just make sure it is low */
+ gpio_set_value(link->gpio.ss_pin, 0);
+
+ /* INT: disable system-wake, reconfigure as output-low */
+ disable_irq_wake(GPIO_TO_IRQ(link->gpio.int_pin));
+ gpio_direction_output(link->gpio.int_pin, 0);
+ atomic_set(&context->gpio_configured, 0);
+ return true;
+}
+
+bool ipc_util_link_is_suspended(struct ipc_link_context *context)
+{
+ return atomic_read(&context->suspended) == 1;
+}
+
+void ipc_util_suspend_link(struct ipc_link_context *context)
+{
+ atomic_set(&context->suspended, 1);
+}
+
+void ipc_util_resume_link(struct ipc_link_context *context)
+{
+ atomic_set(&context->suspended, 0);
+}
diff --git a/drivers/modem/mcdd.c b/drivers/modem/mcdd.c
new file mode 100644
index 00000000000..d291944e810
--- /dev/null
+++ b/drivers/modem/mcdd.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Modem Crash Detection Driver
+ *
+ * Author:Bibek Basu <bibek.basu@stericsson.com> for ST-Ericsson
+ *
+ * License terms:GNU General Public License (GPLv2)version 2
+ */
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+
+#define MCDD_INTERRUPT_CLEAR (1 << 13)
+#define MODEM_CRASH_EVT 1
+
+struct mcdd_data {
+ bool modem_event;
+ u32 event_type;
+ wait_queue_head_t readq;
+ spinlock_t lock;
+ void __iomem *remap_intcon;
+ struct device *dev;
+ struct miscdevice misc_dev;
+};
+
+static struct mcdd_data *mcdd;
+
+static irqreturn_t mcdd_interrupt_cb(int irq, void *dev)
+{
+ writel(MCDD_INTERRUPT_CLEAR, (u32 *)mcdd->remap_intcon);
+ spin_lock(&mcdd->lock);
+ mcdd->modem_event = true;
+ mcdd->event_type = MODEM_CRASH_EVT;
+ spin_unlock(&mcdd->lock);
+ wake_up_interruptible(&mcdd->readq);
+ return IRQ_HANDLED;
+}
+
+static unsigned int mcdd_select(struct file *filp, poll_table *wait)
+{
+ unsigned int mask = 0;
+ unsigned long flags;
+
+ poll_wait(filp, &mcdd->readq, wait);
+ spin_lock_irqsave(&mcdd->lock, flags);
+
+ if (mcdd->modem_event == true) {
+ mask |= POLLPRI;
+ mcdd->modem_event = false;
+ }
+ spin_unlock_irqrestore(&mcdd->lock, flags);
+
+ return mask;
+}
+
+static int mcdd_open(struct inode *ino, struct file *filp)
+{
+ /* Do nothing */
+ return 0;
+}
+
+ssize_t mcdd_read(struct file *filp, char __user *buff, size_t size, loff_t *t)
+{
+ if (copy_to_user(buff, &mcdd->event_type, size))
+ return -EFAULT;
+ return 0;
+};
+
+static const struct file_operations mcdd_fops = {
+ .open = mcdd_open,
+ .poll = mcdd_select,
+ .read = mcdd_read,
+ .owner = THIS_MODULE,
+};
+
+static int __devinit u5500_mcdd_probe(struct platform_device *pdev)
+{
+ struct resource *resource;
+ int ret = 0;
+ int irq;
+
+ mcdd = kzalloc(sizeof(*mcdd), GFP_KERNEL);
+ if (!mcdd) {
+ dev_err(&pdev->dev, "Memory Allocation Failed");
+ return -ENOMEM;
+ }
+ mcdd->dev = &pdev->dev;
+ mcdd->misc_dev.minor = MISC_DYNAMIC_MINOR;
+ mcdd->misc_dev.name = "mcdd";
+ mcdd->misc_dev.fops = &mcdd_fops;
+ spin_lock_init(&mcdd->lock);
+ init_waitqueue_head(&(mcdd->readq));
+
+ /* Get addr for mcdd crash interrupt reset register and ioremap it */
+ resource = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM,
+ "mcdd_intreset_addr");
+ if (resource == NULL) {
+ dev_err(&pdev->dev,
+ "Unable to retrieve mcdd_intreset_addr resource\n");
+ goto exit_free;
+ }
+ mcdd->remap_intcon = ioremap(resource->start, resource_size(resource));
+ if (!mcdd->remap_intcon) {
+ dev_err(&pdev->dev, "Unable to ioremap intcon mbox1\n");
+ ret = -EINVAL;
+ goto exit_free;
+ }
+
+ /* Get IRQ for mcdd mbox interrupt and allocate it */
+ irq = platform_get_irq_byname(pdev, "mcdd_mbox_irq");
+ if (irq < 0) {
+ dev_err(&pdev->dev,
+ "Unable to retrieve mcdd mbox irq resource\n");
+ goto exit_unmap;
+ }
+
+ ret = request_threaded_irq(irq, NULL,
+ mcdd_interrupt_cb, IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ "mcdd", &mcdd);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Could not allocate irq %d,error %d\n",
+ irq, ret);
+ goto exit_unmap;
+ }
+
+ ret = misc_register(&mcdd->misc_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "can't misc-register\n");
+ goto exit_unmap;
+ }
+ dev_info(&pdev->dev, "mcdd driver registration done\n");
+ return 0;
+
+exit_unmap:
+ iounmap(mcdd->remap_intcon);
+exit_free:
+ kfree(mcdd);
+ return ret;
+}
+
+static int u5500_mcdd_remove(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ if (mcdd) {
+ iounmap(mcdd->remap_intcon);
+ ret = misc_deregister(&mcdd->misc_dev);
+ kfree(mcdd);
+ }
+ return ret;
+}
+
+static struct platform_driver u5500_mcdd_driver = {
+ .driver = {
+ .name = "u5500-mcdd-modem",
+ .owner = THIS_MODULE,
+ },
+ .probe = u5500_mcdd_probe,
+ .remove = __devexit_p(u5500_mcdd_remove),
+};
+
+static int __init mcdd_init(void)
+{
+ return platform_driver_register(&u5500_mcdd_driver);
+}
+module_init(mcdd_init);
+
+static void __exit mcdd_exit(void)
+{
+ platform_driver_unregister(&u5500_mcdd_driver);
+}
+module_exit(mcdd_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("BIBEK BASU <bibek.basu@stericsson.com>");
+MODULE_DESCRIPTION("Modem Dump Detection Driver");
+MODULE_ALIAS("mcdd driver");
diff --git a/drivers/modem/modem_access.c b/drivers/modem/modem_access.c
new file mode 100644
index 00000000000..2bd32957ae2
--- /dev/null
+++ b/drivers/modem/modem_access.c
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * License Terms: GNU General Public License v2
+ * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com>
+ *
+ * Heavily adapted from Regulator framework.
+ * Provides mechanisms for registering platform specific access
+ * mechanisms for modem.
+ * Also, exposes APIs for gettng/releasing the access and even
+ * query the access status, and the modem usage status.
+ */
+#include <linux/module.h>
+#include <linux/modem/modem.h>
+#include <linux/modem/modem_client.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+
+static DEFINE_MUTEX(modem_list_mutex);
+static LIST_HEAD(modem_list);
+
+struct modem {
+ struct device *dev;
+ struct list_head list;
+ char *modem_name;
+ struct device_attribute dev_attr;
+ struct modem_dev *mdev;
+ atomic_t use;
+};
+
+static const char *mdev_get_name(struct modem_dev *mdev)
+{
+ if (mdev->desc->name)
+ return mdev->desc->name;
+ else
+ return "";
+}
+
+static int _modem_is_requested(struct modem_dev *mdev)
+{
+ /* If we don't know then assume that the modem is always on */
+ if (!mdev->desc->ops->is_requested)
+ return 0;
+
+ return mdev->desc->ops->is_requested(mdev);
+}
+
+/**
+ * modem_is_requested - check if modem access is requested
+ * @modem: modem device
+ *
+ * Checks whether modem is accessed or not by querying
+ * the underlying platform specific modem access
+ * implementation.
+ */
+int modem_is_requested(struct modem *modem)
+{
+ int ret;
+
+ mutex_lock(&modem->mdev->mutex);
+ ret = _modem_is_requested(modem->mdev);
+ mutex_unlock(&modem->mdev->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(modem_is_requested);
+
+static int _modem_request(struct modem_dev *mdev)
+{
+ int ret;
+
+ if (++mdev->use_count == 1) {
+ ret = _modem_is_requested(mdev);
+ if (ret == 0)
+ mdev->desc->ops->request(mdev);
+ }
+
+ return 0;
+}
+
+/**
+ * modem_request - Request access the modem
+ * @modem: modem device
+ *
+ * API to access the modem. It keeps a client
+ * specific check on whether the particular modem
+ * requested is accessed or not.
+ */
+void modem_request(struct modem *modem)
+{
+ struct modem_dev *mdev = modem->mdev;
+ int ret = 0;
+
+
+ mutex_lock(&mdev->mutex);
+ if (atomic_read(&modem->use) == 1) {
+ mutex_unlock(&mdev->mutex);
+ return;
+ }
+ ret = _modem_request(mdev);
+ if (ret == 0)
+ atomic_set(&modem->use, 1);
+ mutex_unlock(&mdev->mutex);
+}
+EXPORT_SYMBOL(modem_request);
+
+static int _modem_release(struct modem_dev *mdev)
+{
+ if (WARN(mdev->use_count <= 0,
+ "unbalanced releases for %s\n",
+ mdev_get_name(mdev)))
+ return -EIO;
+
+ if (--mdev->use_count == 0)
+ mdev->desc->ops->release(mdev);
+
+ return 0;
+}
+
+/**
+ * modem_release - Release access to modem
+ * @modem: modem device
+ *
+ * Releases accesss to the modem. It keeps a client
+ * specific check on whether a particular modem
+ * is released or not.
+ */
+void modem_release(struct modem *modem)
+{
+ struct modem_dev *mdev = modem->mdev;
+ int ret = 0;
+
+ mutex_lock(&mdev->mutex);
+ if (atomic_read(&modem->use) == 0) {
+ mutex_unlock(&mdev->mutex);
+ return;
+ }
+ ret = _modem_release(mdev);
+ if (ret == 0)
+ atomic_set(&modem->use, 0);
+ mutex_unlock(&mdev->mutex);
+}
+EXPORT_SYMBOL(modem_release);
+
+/**
+ * modem_get_usage - Check if particular client is using modem
+ * @modem: modem device
+ *
+ * Checks whether the particular client is using access to modem.
+ * This API could be used by client drivers in making their
+ * suspend decisions.
+ */
+int modem_get_usage(struct modem *modem)
+{
+ return atomic_read(&modem->use);
+}
+EXPORT_SYMBOL(modem_get_usage);
+
+static struct modem *create_modem(struct modem_dev *mdev,
+ struct device *dev,
+ const char *id)
+{
+ struct modem *modem;
+
+ modem = kzalloc(sizeof(*modem), GFP_KERNEL);
+ if (modem == NULL)
+ return NULL;
+
+ mutex_lock(&mdev->mutex);
+ modem->mdev = mdev;
+ modem->dev = dev;
+ list_add(&modem->list, &mdev->client_list);
+
+ mutex_unlock(&mdev->mutex);
+ return modem;
+
+}
+
+static struct modem *_modem_get(struct device *dev, const char *id,
+ int exclusive)
+{
+ struct modem_dev *mdev_ptr;
+ struct modem *modem = ERR_PTR(-ENODEV);
+ int ret;
+
+ if (id == NULL) {
+ pr_err("modem_get with no identifier\n");
+ return modem;
+ }
+
+ mutex_lock(&modem_list_mutex);
+ list_for_each_entry(mdev_ptr, &modem_list, modem_list) {
+ if (strcmp(mdev_get_name(mdev_ptr), id) == 0)
+ goto found;
+ }
+
+ goto out;
+
+found:
+ if (!try_module_get(mdev_ptr->owner))
+ goto out;
+
+ modem = create_modem(mdev_ptr, dev, id);
+ if (modem == NULL) {
+ modem = ERR_PTR(-ENOMEM);
+ module_put(mdev_ptr->owner);
+ }
+
+ mdev_ptr->open_count++;
+ ret = _modem_is_requested(mdev_ptr);
+ if (ret)
+ mdev_ptr->use_count = 1;
+ else
+ mdev_ptr->use_count = 0;
+
+out:
+ mutex_unlock(&modem_list_mutex);
+ return modem;
+
+}
+
+/**
+ * modem_get - Get reference to a particular platform specific modem
+ * @dev: device
+ * @id: modem device name
+ *
+ * Get reference to a particular modem device.
+ */
+struct modem *modem_get(struct device *dev, const char *id)
+{
+ return _modem_get(dev, id, 0);
+}
+EXPORT_SYMBOL(modem_get);
+
+/**
+ * modem_put - Release reference to a modem device
+ * @modem: modem device
+ *
+ * Release reference to a modem device.
+ */
+void modem_put(struct modem *modem)
+{
+ struct modem_dev *mdev;
+
+ if (modem == NULL || IS_ERR(modem))
+ return;
+
+ mutex_lock(&modem_list_mutex);
+ mdev = modem->mdev;
+
+ list_del(&modem->list);
+ kfree(modem);
+
+ mdev->open_count--;
+
+ module_put(mdev->owner);
+ mutex_unlock(&modem_list_mutex);
+}
+EXPORT_SYMBOL(modem_put);
+
+static ssize_t modem_print_state(char *buf, int state)
+{
+ if (state > 0)
+ return sprintf(buf, "accessed\n");
+ else if (state == 0)
+ return sprintf(buf, "released\n");
+ else
+ return sprintf(buf, "unknown\n");
+}
+
+static ssize_t modem_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct modem_dev *mdev = dev_get_drvdata(dev);
+ ssize_t ret;
+
+ mutex_lock(&mdev->mutex);
+ ret = modem_print_state(buf, _modem_is_requested(mdev));
+ mutex_unlock(&mdev->mutex);
+
+ return ret;
+}
+static DEVICE_ATTR(state, 0444, modem_state_show, NULL);
+
+static ssize_t modem_use_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct modem_dev *mdev = dev_get_drvdata(dev);
+ struct modem *mod;
+ size_t size = 0;
+
+ list_for_each_entry(mod, &mdev->client_list, list) {
+ if (mod->dev != NULL)
+ size += sprintf((buf + size), "%s (%d)\n",
+ dev_name(mod->dev), atomic_read(&mod->use));
+ else
+ size += sprintf((buf + size), "unknown (%d)\n",
+ atomic_read(&mod->use));
+ }
+ size += sprintf((buf + size), "\n");
+
+ return size;
+}
+static DEVICE_ATTR(use, 0444, modem_use_show, NULL);
+
+static ssize_t modem_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct modem_dev *mdev = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", mdev_get_name(mdev));
+}
+static DEVICE_ATTR(name, 0444, modem_name_show, NULL);
+
+static ssize_t modem_num_active_users_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct modem_dev *mdev = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", mdev->use_count);
+}
+static DEVICE_ATTR(num_active_users, 0444, modem_num_active_users_show, NULL);
+
+static int add_modem_attributes(struct modem_dev *mdev)
+{
+ struct device *dev = &mdev->dev;
+ struct modem_ops *ops = mdev->desc->ops;
+ int status = 0;
+
+ status = device_create_file(dev, &dev_attr_use);
+ if (status < 0)
+ return status;
+
+ status = device_create_file(dev, &dev_attr_name);
+ if (status < 0)
+ return status;
+
+ status = device_create_file(dev, &dev_attr_num_active_users);
+ if (status < 0)
+ return status;
+
+ if (ops->is_requested) {
+ status = device_create_file(dev, &dev_attr_state);
+ if (status < 0)
+ return status;
+ }
+
+ return 0;
+}
+
+/**
+ * modem_register - register a modem
+ * @modem_desc: - description for modem
+ * @dev: - device
+ * @driver_data:- driver specific data
+ *
+ * Register a modem with the modem access framework, so that
+ * it could be used by client drivers for accessing the
+ * modem.
+ */
+struct modem_dev *modem_register(struct modem_desc *modem_desc,
+ struct device *dev,
+ void *driver_data)
+{
+ static atomic_t modem_no = ATOMIC_INIT(0);
+ struct modem_dev *mdev;
+ int ret;
+
+ if (modem_desc == NULL)
+ return ERR_PTR(-EINVAL);
+
+ if (modem_desc->name == NULL || modem_desc->ops == NULL)
+ return ERR_PTR(-EINVAL);
+
+ mdev = kzalloc(sizeof(struct modem_dev), GFP_KERNEL);
+ if (mdev == NULL)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_lock(&modem_list_mutex);
+
+ mutex_init(&mdev->mutex);
+ mdev->modem_data = driver_data;
+ mdev->owner = modem_desc->owner;
+ mdev->desc = modem_desc;
+ INIT_LIST_HEAD(&mdev->client_list);
+ INIT_LIST_HEAD(&mdev->modem_list);
+ BLOCKING_INIT_NOTIFIER_HEAD(&mdev->notifier);
+
+ /* mdev->dev.class = &modem_class;*/
+ mdev->dev.parent = dev;
+ dev_set_name(&mdev->dev, "modem.%d", atomic_inc_return(&modem_no) - 1);
+ ret = device_register(&mdev->dev);
+ if (ret != 0)
+ goto clean;
+
+ dev_set_drvdata(&mdev->dev, mdev);
+
+ ret = add_modem_attributes(mdev);
+ if (ret < 0)
+ goto backoff;
+
+ list_add(&mdev->modem_list, &modem_list);
+
+out:
+ mutex_unlock(&modem_list_mutex);
+ return mdev;
+
+backoff:
+ device_unregister(&mdev->dev);
+ mdev = ERR_PTR(ret);
+ goto out;
+
+clean:
+ kfree(mdev);
+ mdev = ERR_PTR(ret);
+ goto out;
+}
+EXPORT_SYMBOL(modem_register);
diff --git a/drivers/modem/modem_m6718.c b/drivers/modem/modem_m6718.c
new file mode 100644
index 00000000000..5e457c16003
--- /dev/null
+++ b/drivers/modem/modem_m6718.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * License Terms: GNU General Public License v2
+ * Author: Chris Blair <chris.blair@stericsson.com>
+ * based on modem_u8500.c
+ *
+ * Platform driver implementing access mechanisms to the M6718 modem.
+ */
+#include <linux/modem/modem.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+
+static void modem_m6718_request(struct modem_dev *mdev)
+{
+ /* nothing to do - modem will wake when data is sent */
+}
+
+static void modem_m6718_release(struct modem_dev *mdev)
+{
+ /* nothing to do - modem does not need to be requested/released */
+}
+
+static int modem_m6718_is_requested(struct modem_dev *mdev)
+{
+ return 0;
+}
+
+static struct modem_ops modem_m6718_ops = {
+ .request = modem_m6718_request,
+ .release = modem_m6718_release,
+ .is_requested = modem_m6718_is_requested,
+};
+
+static struct modem_desc modem_m6718_desc = {
+ .name = "m6718",
+ .id = 0,
+ .ops = &modem_m6718_ops,
+ .owner = THIS_MODULE,
+};
+
+static int __devinit modem_m6718_probe(struct platform_device *pdev)
+{
+ struct modem_dev *mdev;
+ int err;
+
+ mdev = modem_register(&modem_m6718_desc, &pdev->dev,
+ NULL);
+ if (IS_ERR(mdev)) {
+ err = PTR_ERR(mdev);
+ dev_err(&pdev->dev, "failed to register %s: err %i\n",
+ modem_m6718_desc.name, err);
+ }
+
+ return 0;
+}
+
+static int __devexit modem_m6718_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver modem_m6718_driver = {
+ .driver = {
+ .name = "modem-m6718",
+ .owner = THIS_MODULE,
+ },
+ .probe = modem_m6718_probe,
+ .remove = __devexit_p(modem_m6718_remove),
+};
+
+static int __init modem_m6718_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&modem_m6718_driver);
+ if (ret < 0) {
+ printk(KERN_ERR "modem_m6718: platform driver reg failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit modem_m6718_exit(void)
+{
+ platform_driver_unregister(&modem_m6718_driver);
+}
+
+module_init(modem_m6718_init);
+module_exit(modem_m6718_exit);
+
+MODULE_AUTHOR("Chris Blair <chris.blair@stericsson.com>");
+MODULE_DESCRIPTION("M6718 modem access driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/modem/modem_u8500.c b/drivers/modem/modem_u8500.c
new file mode 100644
index 00000000000..39951995e8e
--- /dev/null
+++ b/drivers/modem/modem_u8500.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * License Terms: GNU General Public License v2
+ * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com>
+ *
+ * Platform driver implementing access mechanisms to modem
+ * on U8500 which uses Shared Memroy as IPC between Application
+ * Processor and Modem processor.
+ */
+#include <linux/module.h>
+#include <linux/modem/modem.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/mfd/dbx500-prcmu.h>
+
+static void u8500_modem_request(struct modem_dev *mdev)
+{
+ prcmu_ac_wake_req();
+}
+
+static void u8500_modem_release(struct modem_dev *mdev)
+{
+ prcmu_ac_sleep_req();
+}
+
+static int u8500_modem_is_requested(struct modem_dev *mdev)
+{
+ return prcmu_is_ac_wake_requested();
+}
+
+static struct modem_ops u8500_modem_ops = {
+ .request = u8500_modem_request,
+ .release = u8500_modem_release,
+ .is_requested = u8500_modem_is_requested,
+};
+
+static struct modem_desc u8500_modem_desc = {
+ .name = "u8500-shrm-modem",
+ .id = 0,
+ .ops = &u8500_modem_ops,
+ .owner = THIS_MODULE,
+};
+
+
+static int __devinit u8500_modem_probe(struct platform_device *pdev)
+{
+ struct modem_dev *mdev;
+ int err;
+
+ mdev = modem_register(&u8500_modem_desc, &pdev->dev,
+ NULL);
+ if (IS_ERR(mdev)) {
+ err = PTR_ERR(mdev);
+ pr_err("failed to register %s: err %i\n",
+ u8500_modem_desc.name, err);
+ }
+
+ return 0;
+}
+
+static int __devexit u8500_modem_remove(struct platform_device *pdev)
+{
+
+ return 0;
+}
+
+static struct platform_driver u8500_modem_driver = {
+ .driver = {
+ .name = "u8500-modem",
+ .owner = THIS_MODULE,
+ },
+ .probe = u8500_modem_probe,
+ .remove = __devexit_p(u8500_modem_remove),
+};
+
+static int __init u8500_modem_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&u8500_modem_driver);
+ if (ret < 0) {
+ printk(KERN_ERR "u8500_modem: platform driver reg failed\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void __exit u8500_modem_exit(void)
+{
+ platform_driver_unregister(&u8500_modem_driver);
+}
+
+arch_initcall(u8500_modem_init);
diff --git a/drivers/modem/shrm/Kconfig b/drivers/modem/shrm/Kconfig
new file mode 100644
index 00000000000..465c8bb10a1
--- /dev/null
+++ b/drivers/modem/shrm/Kconfig
@@ -0,0 +1,43 @@
+#
+# SHM HW kernel configuration
+#
+config U8500_SHRM
+ bool "U8500 SHRM hardware driver"
+ depends on ARCH_U8500 && PHONET && MODEM_U8500
+ default Y
+ ---help---
+ If you say Y here, you will enable the STN8500 SHM hardware driver.
+
+ If unsure, say N.
+choice
+ prompt "Modem Image Version"
+ depends on U8500_SHRM
+ default SHRM_V1_UPDATES_VERSION
+
+ config SHRM_V1_UPDATES_VERSION
+ depends on U8500_SHRM
+ bool "SHRM V1 UPDATES"
+ help
+ Modem Images with V1 Updates
+
+endchoice
+
+config U8500_SHRM_LOOP_BACK
+ bool "U8500 SHRM loopback"
+ depends on U8500_SHRM
+ default n
+ ---help---
+ If you say Y here, you will enable the shm loopback
+
+ If unsure, say N.
+
+config U8500_SHRM_MODEM_SILENT_RESET
+ bool "U8500 SHRM Modem Silent Reset"
+ depends on U8500_SHRM
+ default n
+ ---help---
+ If you say Y here, you will enable the modem silent reset feature
+
+ If unsure, say N.
+
+
diff --git a/drivers/modem/shrm/Makefile b/drivers/modem/shrm/Makefile
new file mode 100644
index 00000000000..8115c24920b
--- /dev/null
+++ b/drivers/modem/shrm/Makefile
@@ -0,0 +1,11 @@
+#
+# Makefile for SHRM drivers
+#
+
+ifdef CONFIG_PHONET
+u8500_shrm-objs := modem_shrm_driver.o shrm_fifo.o shrm_protocol.o
+else
+u8500_shrm-objs := shrm_driver.o shrm_fifo.o shrm_protocol.o
+endif
+
+obj-$(CONFIG_U8500_SHRM) += u8500_shrm.o
diff --git a/drivers/modem/shrm/modem_shrm_driver.c b/drivers/modem/shrm/modem_shrm_driver.c
new file mode 100644
index 00000000000..f46b86bd22e
--- /dev/null
+++ b/drivers/modem/shrm/modem_shrm_driver.c
@@ -0,0 +1,670 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson
+ * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> for ST-Ericsson
+ * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <asm/atomic.h>
+#include <linux/io.h>
+#include <linux/skbuff.h>
+#ifdef CONFIG_HIGH_RES_TIMERS
+#include <linux/hrtimer.h>
+static struct hrtimer timer;
+#endif
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <linux/phonet.h>
+#include <linux/modem/shrm/shrm_driver.h>
+#include <linux/modem/shrm/shrm_private.h>
+#include <linux/modem/shrm/shrm_config.h>
+#include <linux/modem/shrm/shrm_net.h>
+#include <linux/modem/shrm/shrm.h>
+
+#include <mach/isa_ioctl.h>
+/* debug functionality */
+#define ISA_DEBUG 0
+
+#define PHONET_TASKLET
+#define MAX_RCV_LEN 2048
+
+static void do_phonet_rcv_tasklet(unsigned long unused);
+struct tasklet_struct phonet_rcv_tasklet;
+
+/**
+ * audio_receive() - Receive audio channel completion callback
+ * @shrm: pointer to shrm device information structure
+ * @data: message pointer
+ * @n_bytes: message size
+ * @l2_header: L2 header/device ID 2->audio, 5->audio_loopback
+ *
+ * This fucntion is called from the audio receive handler. Copies the audio
+ * message from the FIFO to the AUDIO queue. The message is later copied from
+ * this queue to the user buffer through the char or net interface read
+ * operation.
+ */
+static int audio_receive(struct shrm_dev *shrm, void *data,
+ u32 n_bytes, u8 l2_header)
+{
+ u32 size = 0;
+ int ret = 0;
+ int idx;
+ u8 *psrc;
+ struct message_queue *q;
+ struct isadev_context *audiodev;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ idx = shrm_get_cdev_index(l2_header);
+ if (idx < 0) {
+ dev_err(shrm->dev, "failed to get index\n");
+ return idx;
+ }
+ audiodev = &shrm->isa_context->isadev[idx];
+ q = &audiodev->dl_queue;
+ spin_lock(&q->update_lock);
+ /* Memcopy RX data first */
+ if ((q->writeptr+n_bytes) >= q->size) {
+ psrc = (u8 *)data;
+ size = (q->size-q->writeptr);
+ /* Copy First Part of msg */
+ memcpy((q->fifo_base+q->writeptr), psrc, size);
+ psrc += size;
+ /* Copy Second Part of msg at the top of fifo */
+ memcpy(q->fifo_base, psrc, (n_bytes-size));
+ } else {
+ memcpy((q->fifo_base+q->writeptr), data, n_bytes);
+ }
+ ret = add_msg_to_queue(q, n_bytes);
+ spin_unlock(&q->update_lock);
+ if (ret < 0)
+ dev_err(shrm->dev, "Adding a msg to message queue failed");
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return ret;
+}
+
+/**
+ * common_receive() - Receive common channel completion callback
+ * @shrm: pointer to the shrm device information structure
+ * @data: message pointer
+ * @n_bytes: message size
+ * @l2_header: L2 header / device ID
+ *
+ * This function is called from the receive handler to copy the respective
+ * ISI, RPC, SECURITY message to its respective queue. The message is then
+ * copied from queue to the user buffer on char net interface read operation.
+ */
+static int common_receive(struct shrm_dev *shrm, void *data,
+ u32 n_bytes, u8 l2_header)
+{
+ u32 size = 0;
+ int ret = 0;
+ int idx;
+ u8 *psrc;
+ struct message_queue *q;
+ struct isadev_context *isa_dev;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ idx = shrm_get_cdev_index(l2_header);
+ if (idx < 0) {
+ dev_err(shrm->dev, "failed to get index\n");
+ return idx;
+ }
+ isa_dev = &shrm->isa_context->isadev[idx];
+ q = &isa_dev->dl_queue;
+ spin_lock(&q->update_lock);
+ /* Memcopy RX data first */
+ if ((q->writeptr+n_bytes) >= q->size) {
+ dev_dbg(shrm->dev, "Inside Loop Back\n");
+ psrc = (u8 *)data;
+ size = (q->size-q->writeptr);
+ /* Copy First Part of msg */
+ memcpy((q->fifo_base+q->writeptr), psrc, size);
+ psrc += size;
+ /* Copy Second Part of msg at the top of fifo */
+ memcpy(q->fifo_base, psrc, (n_bytes-size));
+ } else {
+ memcpy((q->fifo_base+q->writeptr), data, n_bytes);
+ }
+ ret = add_msg_to_queue(q, n_bytes);
+ spin_unlock(&q->update_lock);
+ if (ret < 0) {
+ dev_err(shrm->dev, "Adding a msg to message queue failed");
+ return ret;
+ }
+
+
+ if (l2_header == ISI_MESSAGING) {
+ if (shrm->netdev_flag_up) {
+ dev_dbg(shrm->dev,
+ "scheduling the phonet tasklet from %s!\n",
+ __func__);
+ tasklet_schedule(&phonet_rcv_tasklet);
+ }
+ dev_dbg(shrm->dev,
+ "Out of phonet tasklet %s!!!\n", __func__);
+ }
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return ret;
+}
+
+/**
+ * rx_common_l2msg_handler() - common channel receive handler
+ * @l2_header: L2 header
+ * @msg: pointer to the receive buffer
+ * @length: length of the msg to read
+ * @shrm: pointer to shrm device information structure
+ *
+ * This function is called to receive the message from CaMsgPendingNotification
+ * interrupt handler.
+ */
+static void rx_common_l2msg_handler(u8 l2_header,
+ void *msg, u32 length,
+ struct shrm_dev *shrm)
+{
+ int ret = 0;
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ ret = common_receive(shrm, msg, length, l2_header);
+ if (ret < 0)
+ dev_err(shrm->dev,
+ "common receive with l2 header %d failed\n", l2_header);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+/**
+ * rx_audio_l2msg_handler() - audio channel receive handler
+ * @l2_header: L2 header
+ * @msg: pointer to the receive buffer
+ * @length: length of the msg to read
+ * @shrm: pointer to shrm device information structure
+ *
+ * This function is called to receive the message from CaMsgPendingNotification
+ * interrupt handler.
+ */
+static void rx_audio_l2msg_handler(u8 l2_header,
+ void *msg, u32 length,
+ struct shrm_dev *shrm)
+{
+ int ret = 0;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ ret = audio_receive(shrm, msg, length, l2_header);
+ if (ret < 0)
+ dev_err(shrm->dev, "audio receive failed\n");
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+static int __init shm_initialise_irq(struct shrm_dev *shrm)
+{
+ int err = 0;
+
+ err = shrm_protocol_init(shrm,
+ rx_common_l2msg_handler, rx_audio_l2msg_handler);
+ if (err < 0) {
+ dev_err(shrm->dev, "SHM Protocol Init Failure\n");
+ return err;
+ }
+
+ err = request_irq(shrm->ca_wake_irq,
+ ca_wake_irq_handler, IRQF_TRIGGER_RISING,
+ "ca_wake-up", shrm);
+ if (err < 0) {
+ dev_err(shrm->dev,
+ "Unable to allocate shm tx interrupt line\n");
+ free_irq(shrm->ca_wake_irq, shrm);
+ return err;
+ }
+
+ err = request_irq(shrm->ac_read_notif_0_irq,
+ ac_read_notif_0_irq_handler, 0,
+ "ac_read_notif_0", shrm);
+
+ if (err < 0) {
+ dev_err(shrm->dev,
+ "error ac_read_notif_0_irq interrupt line\n");
+ goto irq_err1;
+ }
+
+ err = request_irq(shrm->ac_read_notif_1_irq,
+ ac_read_notif_1_irq_handler, 0,
+ "ac_read_notif_1", shrm);
+
+ if (err < 0) {
+ dev_err(shrm->dev,
+ "error ac_read_notif_1_irq interrupt line\n");
+ goto irq_err2;
+ }
+
+ err = request_irq(shrm->ca_msg_pending_notif_0_irq,
+ ca_msg_pending_notif_0_irq_handler, 0,
+ "ca_msg_pending_notif_0", shrm);
+
+ if (err < 0) {
+ dev_err(shrm->dev,
+ "error ca_msg_pending_notif_0_irq line\n");
+ goto irq_err3;
+ }
+
+ err = request_irq(shrm->ca_msg_pending_notif_1_irq,
+ ca_msg_pending_notif_1_irq_handler, 0,
+ "ca_msg_pending_notif_1", shrm);
+
+ if (err < 0) {
+ dev_err(shrm->dev,
+ "error ca_msg_pending_notif_1_irq interrupt line\n");
+ goto irq_err4;
+ }
+ return err;
+irq_err4:
+ free_irq(shrm->ca_msg_pending_notif_0_irq, shrm);
+irq_err3:
+ free_irq(shrm->ac_read_notif_1_irq, shrm);
+irq_err2:
+ free_irq(shrm->ac_read_notif_0_irq, shrm);
+irq_err1:
+ free_irq(shrm->ca_wake_irq, shrm);
+ return err;
+}
+
+static void free_shm_irq(struct shrm_dev *shrm)
+{
+ free_irq(shrm->ca_wake_irq, shrm);
+ free_irq(shrm->ac_read_notif_0_irq, shrm);
+ free_irq(shrm->ac_read_notif_1_irq, shrm);
+ free_irq(shrm->ca_msg_pending_notif_0_irq, shrm);
+ free_irq(shrm->ca_msg_pending_notif_1_irq, shrm);
+}
+
+
+
+#ifdef CONFIG_HIGH_RES_TIMERS
+static enum hrtimer_restart callback(struct hrtimer *timer)
+{
+ return HRTIMER_NORESTART;
+}
+#endif
+
+void do_phonet_rcv_tasklet(unsigned long unused)
+{
+ ssize_t ret;
+ struct shrm_dev *shrm = (struct shrm_dev *)unused;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ for (;;) {
+ ret = shrm_net_receive(shrm->ndev);
+ if (ret == 0) {
+ dev_dbg(shrm->dev, "len is zero, queue empty\n");
+ break;
+ }
+ if (ret < 0) {
+ dev_err(shrm->dev, "len < 0 !!! error!!!\n");
+ break;
+ }
+ }
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+static int shrm_probe(struct platform_device *pdev)
+{
+ int err = 0;
+ struct resource *res;
+ struct shrm_dev *shrm = NULL;
+
+ shrm = kzalloc(sizeof(struct shrm_dev), GFP_KERNEL);
+ if (shrm == NULL) {
+ dev_err(&pdev->dev,
+ "Could not allocate memory for struct shm_dev\n");
+ return -ENOMEM;
+ }
+
+ shrm->dev = &pdev->dev;
+ shrm->modem = modem_get(shrm->dev, "u8500-shrm-modem");
+ if (shrm->modem == NULL) {
+ dev_err(shrm->dev, " Could not retrieve the modem.\n");
+ err = -ENODEV;
+ goto rollback_intr;
+ }
+
+ /* initialise the SHM */
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(shrm->dev,
+ "Unable to map Ca Wake up interrupt\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ca_wake_irq = res->start;
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
+
+ if (!res) {
+ dev_err(shrm->dev,
+ "Unable to map APE_Read_notif_common IRQ base\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ac_read_notif_0_irq = res->start;
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 2);
+
+ if (!res) {
+ dev_err(shrm->dev,
+ "Unable to map APE_Read_notif_audio IRQ base\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ac_read_notif_1_irq = res->start;
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 3);
+
+ if (!res) {
+ dev_err(shrm->dev,
+ "Unable to map Cmt_msg_pending_notif_common IRQbase\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ca_msg_pending_notif_0_irq = res->start;
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 4);
+
+ if (!res) {
+ dev_err(shrm->dev,
+ "Unable to map Cmt_msg_pending_notif_audio IRQ base\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ca_msg_pending_notif_1_irq = res->start;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ if (!res) {
+ dev_err(shrm->dev,
+ "Could not get SHM IO memory information\n");
+ err = -ENODEV;
+ goto rollback_intr;
+ }
+ shrm->intr_base = (void __iomem *)ioremap_nocache(res->start,
+ res->end - res->start + 1);
+ if (!(shrm->intr_base)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ape_common_fifo_base_phy =
+ (u32 *)U8500_SHM_FIFO_APE_COMMON_BASE;
+ shrm->ape_common_fifo_base =
+ (void __iomem *)ioremap_nocache(
+ U8500_SHM_FIFO_APE_COMMON_BASE,
+ SHM_FIFO_0_SIZE);
+ shrm->ape_common_fifo_size = (SHM_FIFO_0_SIZE)/4;
+
+ if (!(shrm->ape_common_fifo_base)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_ape_common_fifo_base;
+ }
+ shrm->cmt_common_fifo_base_phy =
+ (u32 *)U8500_SHM_FIFO_CMT_COMMON_BASE;
+ shrm->cmt_common_fifo_base =
+ (void __iomem *)ioremap_nocache(
+ U8500_SHM_FIFO_CMT_COMMON_BASE, SHM_FIFO_0_SIZE);
+ shrm->cmt_common_fifo_size = (SHM_FIFO_0_SIZE)/4;
+
+ if (!(shrm->cmt_common_fifo_base)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_cmt_common_fifo_base;
+ }
+ shrm->ape_audio_fifo_base_phy =
+ (u32 *)U8500_SHM_FIFO_APE_AUDIO_BASE;
+ shrm->ape_audio_fifo_base =
+ (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_APE_AUDIO_BASE,
+ SHM_FIFO_1_SIZE);
+ shrm->ape_audio_fifo_size = (SHM_FIFO_1_SIZE)/4;
+
+ if (!(shrm->ape_audio_fifo_base)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_ape_audio_fifo_base;
+ }
+ shrm->cmt_audio_fifo_base_phy =
+ (u32 *)U8500_SHM_FIFO_CMT_AUDIO_BASE;
+ shrm->cmt_audio_fifo_base =
+ (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_CMT_AUDIO_BASE,
+ SHM_FIFO_1_SIZE);
+ shrm->cmt_audio_fifo_size = (SHM_FIFO_1_SIZE)/4;
+
+ if (!(shrm->cmt_audio_fifo_base)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_cmt_audio_fifo_base;
+ }
+ shrm->ac_common_shared_wptr =
+ (void __iomem *)ioremap(SHM_ACFIFO_0_WRITE_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ac_common_shared_wptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_ac_common_shared_wptr;
+ }
+ shrm->ac_common_shared_rptr =
+ (void __iomem *)ioremap(SHM_ACFIFO_0_READ_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ac_common_shared_rptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+ shrm->ca_common_shared_wptr =
+ (void __iomem *)ioremap(SHM_CAFIFO_0_WRITE_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ca_common_shared_wptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+ shrm->ca_common_shared_rptr =
+ (void __iomem *)ioremap(SHM_CAFIFO_0_READ_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ca_common_shared_rptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+ shrm->ac_audio_shared_wptr =
+ (void __iomem *)ioremap(SHM_ACFIFO_1_WRITE_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ac_audio_shared_wptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+ shrm->ac_audio_shared_rptr =
+ (void __iomem *)ioremap(SHM_ACFIFO_1_READ_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ac_audio_shared_rptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+ shrm->ca_audio_shared_wptr =
+ (void __iomem *)ioremap(SHM_CAFIFO_1_WRITE_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ca_audio_shared_wptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+ shrm->ca_audio_shared_rptr =
+ (void __iomem *)ioremap(SHM_CAFIFO_1_READ_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ca_audio_shared_rptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+
+ if (isa_init(shrm) != 0) {
+ dev_err(shrm->dev, "Driver Initialization Error\n");
+ err = -EBUSY;
+ }
+ /* install handlers and tasklets */
+ if (shm_initialise_irq(shrm)) {
+ dev_err(shrm->dev,
+ "shm error in interrupt registration\n");
+ goto rollback_irq;
+ }
+#ifdef CONFIG_HIGH_RES_TIMERS
+ hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ timer.function = callback;
+ hrtimer_start(&timer, ktime_set(0, 2*NSEC_PER_MSEC), HRTIMER_MODE_REL);
+#endif
+ err = shrm_register_netdev(shrm);
+ if (err < 0)
+ goto rollback_irq;
+
+ tasklet_init(&phonet_rcv_tasklet, do_phonet_rcv_tasklet, 0);
+ phonet_rcv_tasklet.data = (unsigned long)shrm;
+
+ platform_set_drvdata(pdev, shrm);
+
+ return err;
+rollback_irq:
+ free_shm_irq(shrm);
+rollback_map:
+ iounmap(shrm->ac_common_shared_wptr);
+ iounmap(shrm->ac_common_shared_rptr);
+ iounmap(shrm->ca_common_shared_wptr);
+ iounmap(shrm->ca_common_shared_rptr);
+ iounmap(shrm->ac_audio_shared_wptr);
+ iounmap(shrm->ac_audio_shared_rptr);
+ iounmap(shrm->ca_audio_shared_wptr);
+ iounmap(shrm->ca_audio_shared_rptr);
+rollback_ac_common_shared_wptr:
+ iounmap(shrm->cmt_audio_fifo_base);
+rollback_cmt_audio_fifo_base:
+ iounmap(shrm->ape_audio_fifo_base);
+rollback_ape_audio_fifo_base:
+ iounmap(shrm->cmt_common_fifo_base);
+rollback_cmt_common_fifo_base:
+ iounmap(shrm->ape_common_fifo_base);
+rollback_ape_common_fifo_base:
+ iounmap(shrm->intr_base);
+rollback_intr:
+ kfree(shrm);
+ return err;
+}
+
+static int __exit shrm_remove(struct platform_device *pdev)
+{
+ struct shrm_dev *shrm = platform_get_drvdata(pdev);
+
+ free_shm_irq(shrm);
+ iounmap(shrm->intr_base);
+ iounmap(shrm->ape_common_fifo_base);
+ iounmap(shrm->cmt_common_fifo_base);
+ iounmap(shrm->ape_audio_fifo_base);
+ iounmap(shrm->cmt_audio_fifo_base);
+ iounmap(shrm->ac_common_shared_wptr);
+ iounmap(shrm->ac_common_shared_rptr);
+ iounmap(shrm->ca_common_shared_wptr);
+ iounmap(shrm->ca_common_shared_rptr);
+ iounmap(shrm->ac_audio_shared_wptr);
+ iounmap(shrm->ac_audio_shared_rptr);
+ iounmap(shrm->ca_audio_shared_wptr);
+ iounmap(shrm->ca_audio_shared_rptr);
+ shrm_unregister_netdev(shrm);
+ isa_exit(shrm);
+ kfree(shrm);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/**
+ * u8500_shrm_suspend() - This routine puts the SHRM in to sustend state.
+ * @dev: pointer to device structure.
+ *
+ * This routine checks the current ongoing communication with Modem by
+ * examining the ca_wake state and prevents suspend if modem communication
+ * is on-going.
+ * If ca_wake = 1 (high), modem comm. is on-going; don't suspend
+ * If ca_wake = 0 (low), no comm. with modem on-going.Allow suspend
+ */
+int u8500_shrm_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct shrm_dev *shrm = platform_get_drvdata(pdev);
+ int err;
+
+ dev_dbg(&pdev->dev, "%s called...\n", __func__);
+ dev_dbg(&pdev->dev, "ca_wake_req_state = %x\n",
+ get_ca_wake_req_state());
+
+ /* if ca_wake_req is high, prevent system suspend */
+ if (!get_ca_wake_req_state()) {
+ err = shrm_suspend_netdev(shrm->ndev);
+ return err;
+ } else
+ return -EBUSY;
+}
+
+/**
+ * u8500_shrm_resume() - This routine resumes the SHRM from suspend state.
+ * @dev: pointer to device structure
+ *
+ * This routine restore back the current state of the SHRM
+ */
+int u8500_shrm_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct shrm_dev *shrm = platform_get_drvdata(pdev);
+ int err;
+
+ dev_dbg(&pdev->dev, "%s called...\n", __func__);
+ err = shrm_resume_netdev(shrm->ndev);
+
+ return err;
+}
+
+static const struct dev_pm_ops shrm_dev_pm_ops = {
+ .suspend_noirq = u8500_shrm_suspend,
+ .resume_noirq = u8500_shrm_resume,
+};
+#endif
+
+static struct platform_driver shrm_driver = {
+ .remove = __exit_p(shrm_remove),
+ .driver = {
+ .name = "u8500_shrm",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &shrm_dev_pm_ops,
+#endif
+ },
+};
+
+static int __init shrm_driver_init(void)
+{
+ return platform_driver_probe(&shrm_driver, shrm_probe);
+}
+
+static void __exit shrm_driver_exit(void)
+{
+ platform_driver_unregister(&shrm_driver);
+}
+
+module_init(shrm_driver_init);
+module_exit(shrm_driver_exit);
+
+MODULE_AUTHOR("Biju Das, Kumar Sanghvi, Arun Murthy");
+MODULE_DESCRIPTION("Shared Memory Modem Driver Interface");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/modem/shrm/shrm_driver.c b/drivers/modem/shrm/shrm_driver.c
new file mode 100644
index 00000000000..11540831f95
--- /dev/null
+++ b/drivers/modem/shrm/shrm_driver.c
@@ -0,0 +1,1439 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson
+ * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> for ST-Ericsson
+ * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#define DEBUG
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/smp_lock.h>
+#include <linux/poll.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <asm/atomic.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/modem/shrm/shrm_driver.h>
+#include <linux/modem/shrm/shrm_private.h>
+#include <linux/modem/shrm/shrm_config.h>
+#include <linux/modem/shrm/shrm.h>
+
+#include <mach/isa_ioctl.h>
+
+
+#ifdef CONFIG_HIGH_RES_TIMERS
+#include <linux/hrtimer.h>
+static struct hrtimer timer;
+#endif
+
+
+#define NAME "IPC_ISA"
+#define ISA_DEVICES 4
+/**debug functionality*/
+#define ISA_DEBUG 0
+
+#define ISI_MESSAGING (0)
+#define RPC_MESSAGING (1)
+#define AUDIO_MESSAGING (2)
+#define SECURITY_MESSAGING (3)
+
+#define SIZE_OF_FIFO (512*1024)
+
+static u8 message_fifo[4][SIZE_OF_FIFO];
+
+static u8 wr_isi_msg[10*1024];
+static u8 wr_rpc_msg[10*1024];
+static u8 wr_sec_msg[10*1024];
+static u8 wr_audio_msg[10*1024];
+
+/* global data */
+/*
+ * int major:This variable is exported to user as module_param to specify
+ * major number at load time
+ */
+static int major;
+module_param(major, int, 0);
+MODULE_PARM_DESC(major, "Major device number");
+/* global fops mutex */
+static DEFINE_MUTEX(isa_lock);
+rx_cb common_rx;
+rx_cb audio_rx;
+
+
+static int isi_receive(struct shrm_dev *shrm, void *data, u32 n_bytes);
+static int rpc_receive(struct shrm_dev *shrm, void *data, u32 n_bytes);
+static int audio_receive(struct shrm_dev *shrm, void *data, u32 n_bytes);
+static int security_receive(struct shrm_dev *shrm,
+ void *data, u32 n_bytes);
+
+static void rx_common_l2msg_handler(u8 l2_header,
+ void *msg, u32 length,
+ struct shrm_dev *shrm)
+{
+ int ret = 0;
+#ifdef CONFIG_U8500_SHRM_LOOP_BACK
+ u8 *pdata;
+#endif
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ switch (l2_header) {
+ case ISI_MESSAGING:
+ ret = isi_receive(shrm, msg, length);
+ if (ret < 0)
+ dev_err(shrm->dev, "isi receive failed\n");
+ break;
+ case RPC_MESSAGING:
+ ret = rpc_receive(shrm, msg, length);
+ if (ret < 0)
+ dev_err(shrm->dev, "rpc receive failed\n");
+ break;
+ case SECURITY_MESSAGING:
+ ret = security_receive(shrm, msg, length);
+ if (ret < 0)
+ dev_err(shrm->dev,
+ "security receive failed\n");
+ break;
+#ifdef CONFIG_U8500_SHRM_LOOP_BACK
+ case COMMMON_LOOPBACK_MESSAGING:
+ pdata = (u8 *)msg;
+ if ((*pdata == 0x50) || (*pdata == 0xAF)) {
+ ret = isi_receive(shrm, msg, length);
+ if (ret < 0)
+ dev_err(shrm->dev, "isi receive failed\n");
+ } else if ((*pdata == 0x0A) || (*pdata == 0xF5)) {
+ ret = rpc_receive(shrm, msg, length);
+ if (ret < 0)
+ dev_err(shrm->dev, "rpc receive failed\n");
+ } else if ((*pdata == 0xFF) || (*pdata == 0x00)) {
+ ret = security_receive(shrm, msg, length);
+ if (ret < 0)
+ dev_err(shrm->dev,
+ "security receive failed\n");
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+static void rx_audio_l2msg_handler(u8 l2_header,
+ void *msg, u32 length,
+ struct shrm_dev *shrm)
+{
+ int ret = 0;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ audio_receive(shrm, msg, length);
+ if (ret < 0)
+ dev_err(shrm->dev, "audio receive failed\n");
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+static int __init shm_initialise_irq(struct shrm_dev *shrm)
+{
+ int err = 0;
+
+ shrm_protocol_init(shrm,
+ rx_common_l2msg_handler, rx_audio_l2msg_handler);
+
+ err = request_irq(shrm->ca_wake_irq,
+ ca_wake_irq_handler, IRQF_TRIGGER_RISING,
+ "ca_wake-up", shrm);
+ if (err < 0) {
+ dev_err(shrm->dev,
+ "Unable to allocate shm tx interrupt line\n");
+ return err;
+ }
+
+ err = request_irq(shrm->ac_read_notif_0_irq,
+ ac_read_notif_0_irq_handler, 0,
+ "ac_read_notif_0", shrm);
+ if (err < 0) {
+ dev_err(shrm->dev,
+ "error ac_read_notif_0_irq interrupt line\n");
+ goto irq_err1;
+ }
+
+ err = request_irq(shrm->ac_read_notif_1_irq,
+ ac_read_notif_1_irq_handler, 0,
+ "ac_read_notif_1", shrm);
+ if (err < 0) {
+ dev_err(shrm->dev,
+ "error ac_read_notif_1_irq interrupt line\n");
+ goto irq_err2;
+ }
+
+ err = request_irq(shrm->ca_msg_pending_notif_0_irq,
+ ca_msg_pending_notif_0_irq_handler, 0,
+ "ca_msg_pending_notif_0", shrm);
+ if (err < 0) {
+ dev_err(shrm->dev,
+ "error ca_msg_pending_notif_0_irq line\n");
+ goto irq_err3;
+ }
+
+ err = request_irq(shrm->ca_msg_pending_notif_1_irq,
+ ca_msg_pending_notif_1_irq_handler, 0,
+ "ca_msg_pending_notif_1", shrm);
+ if (err < 0) {
+ dev_err(shrm->dev,
+ "error ca_msg_pending_notif_1_irq interrupt line\n");
+ goto irq_err4;
+ }
+
+ return err;
+
+irq_err4:
+ free_irq(shrm->ca_msg_pending_notif_0_irq, shrm);
+irq_err3:
+ free_irq(shrm->ac_read_notif_1_irq, shrm);
+irq_err2:
+ free_irq(shrm->ac_read_notif_0_irq, shrm);
+irq_err1:
+ free_irq(shrm->ca_wake_irq, shrm);
+ return err;
+}
+
+static void free_shm_irq(struct shrm_dev *shrm)
+{
+ free_irq(shrm->ca_wake_irq, shrm);
+ free_irq(shrm->ac_read_notif_0_irq, shrm);
+ free_irq(shrm->ac_read_notif_1_irq, shrm);
+ free_irq(shrm->ca_msg_pending_notif_0_irq, shrm);
+ free_irq(shrm->ca_msg_pending_notif_1_irq, shrm);
+}
+
+/**
+ * create_queue() - To create FIFO for Tx and Rx message buffering.
+ * @q: message queue.
+ * @devicetype: device type 0-isi,1-rpc,2-audio,3-security.
+ *
+ * This function creates a FIFO buffer of n_bytes size using
+ * dma_alloc_coherent(). It also initializes all queue handling
+ * locks, queue management pointers. It also initializes message list
+ * which occupies this queue.
+ *
+ * It return -ENOMEM in case of no memory.
+ */
+static int create_queue(struct message_queue *q, u32 devicetype,
+ struct shrm_dev *shrm)
+{
+ q->fifo_base = (u8 *)&message_fifo[devicetype];
+ q->size = SIZE_OF_FIFO;
+ q->readptr = 0;
+ q->writeptr = 0;
+ q->no = 0;
+ q->shrm = shrm;
+ spin_lock_init(&q->update_lock);
+ INIT_LIST_HEAD(&q->msg_list);
+ init_waitqueue_head(&q->wq_readable);
+ atomic_set(&q->q_rp, 0);
+
+ return 0;
+}
+/**
+ * delete_queue() - To delete FIFO and assiciated memory.
+ * @q: message queue
+ *
+ * This function deletes FIFO created using create_queue() function.
+ * It resets queue management pointers.
+ */
+static void delete_queue(struct message_queue *q)
+{
+ q->size = 0;
+ q->readptr = 0;
+ q->writeptr = 0;
+}
+
+/**
+ * add_msg_to_queue() - Add a message inside inside queue
+ *
+ * @q: message queue
+ * @size: size in bytes
+ *
+ * This function tries to allocate n_bytes of size in FIFO q.
+ * It returns negative number when no memory can be allocated
+ * currently.
+ */
+int add_msg_to_queue(struct message_queue *q, u32 size)
+{
+ struct queue_element *new_msg = NULL;
+ struct shrm_dev *shrm = q->shrm;
+
+ dev_dbg(shrm->dev, "%s IN q->writeptr=%d\n",
+ __func__, q->writeptr);
+ new_msg = kmalloc(sizeof(struct queue_element),
+ GFP_KERNEL|GFP_ATOMIC);
+
+ if (new_msg == NULL) {
+ dev_err(shrm->dev, "memory overflow inside while(1)\n");
+ return -ENOMEM;
+ }
+ new_msg->offset = q->writeptr;
+ new_msg->size = size;
+ new_msg->no = q->no++;
+
+ /* check for overflow condition */
+ if (q->readptr <= q->writeptr) {
+ if (((q->writeptr-q->readptr) + size) >= q->size) {
+ dev_err(shrm->dev, "Buffer overflow !!\n");
+ BUG_ON(((q->writeptr-q->readptr) + size) >= q->size);
+ }
+ } else {
+ if ((q->writeptr + size) >= q->readptr) {
+ dev_err(shrm->dev, "Buffer overflow !!\n");
+ BUG_ON((q->writeptr + size) >= q->readptr);
+ }
+ }
+ q->writeptr = (q->writeptr + size) % q->size;
+ if (list_empty(&q->msg_list)) {
+ list_add_tail(&new_msg->entry, &q->msg_list);
+ /* There can be 2 blocking calls read and another select */
+
+ atomic_set(&q->q_rp, 1);
+ wake_up_interruptible(&q->wq_readable);
+ } else
+ list_add_tail(&new_msg->entry, &q->msg_list);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return 0;
+}
+
+/**
+ * remove_msg_from_queue() - To remove a message from the msg queue.
+ *
+ * @q: message queue
+ *
+ * This function delets a message from the message list associated with message
+ * queue q and also updates read ptr.
+ * If the message list is empty, then, event is set to block the select and
+ * read calls of the paricular queue.
+ *
+ * The message list is FIFO style and message is always added to tail and
+ * removed from head.
+ */
+
+int remove_msg_from_queue(struct message_queue *q)
+{
+ struct queue_element *old_msg = NULL;
+ struct shrm_dev *shrm = q->shrm;
+ struct list_head *msg;
+
+ dev_dbg(shrm->dev, "%s IN q->readptr %d\n",
+ __func__, q->readptr);
+
+ list_for_each(msg, &q->msg_list) {
+ old_msg = list_entry(msg, struct queue_element, entry);
+ if (old_msg == NULL) {
+ dev_err(shrm->dev, ":no message found\n");
+ return -EFAULT;
+ }
+ break;
+ }
+ list_del(msg);
+ q->readptr = (q->readptr + old_msg->size) % q->size;
+ if (list_empty(&q->msg_list)) {
+ dev_dbg(shrm->dev, "List is empty setting RP= 0\n");
+ atomic_set(&q->q_rp, 0);
+ }
+ kfree(old_msg);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return 0;
+}
+
+/**
+ * get_size_of_new_msg() - retrieve new message from message list
+ *
+ * @q: message queue
+ *
+ * This function will retrieve most recent message from the corresponding
+ * queue list. New message is always retrieved from head side.
+ * It returns new message no, offset if FIFO and size.
+ */
+int get_size_of_new_msg(struct message_queue *q)
+{
+ struct queue_element *new_msg = NULL;
+ struct list_head *msg_list;
+ struct shrm_dev *shrm = q->shrm;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ spin_lock_bh(&q->update_lock);
+ list_for_each(msg_list, &q->msg_list) {
+ new_msg = list_entry(msg_list, struct queue_element, entry);
+ if (new_msg == NULL) {
+ spin_unlock_bh(&q->update_lock);
+ dev_err(shrm->dev, "no message found\n");
+ return -1;
+ }
+ break;
+ }
+ spin_unlock_bh(&q->update_lock);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return new_msg->size;
+}
+
+/**
+ * isi_receive() - Rx Completion callback
+ *
+ * @data:message pointer
+ * @n_bytes:message size
+ *
+ * This function is a callback to indicate ISI message reception is complete.
+ * It updates Writeptr of the Fifo
+ */
+static int isi_receive(struct shrm_dev *shrm,
+ void *data, u32 n_bytes)
+{
+ u32 size = 0;
+ int ret = 0;
+ u8 *psrc;
+ struct message_queue *q;
+ struct isadev_context *isidev = &shrm->isa_context->isadev[0];
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ q = &isidev->dl_queue;
+ spin_lock(&q->update_lock);
+ /* Memcopy RX data first */
+ if ((q->writeptr+n_bytes) >= q->size) {
+ dev_dbg(shrm->dev, "Inside Loop Back\n");
+ psrc = (u8 *)data;
+ size = (q->size-q->writeptr);
+ /* Copy First Part of msg */
+ memcpy((q->fifo_base+q->writeptr), psrc, size);
+ psrc += size;
+ /* Copy Second Part of msg at the top of fifo */
+ memcpy(q->fifo_base, psrc, (n_bytes-size));
+ } else {
+ memcpy((q->fifo_base+q->writeptr), data, n_bytes);
+ }
+ ret = add_msg_to_queue(q, n_bytes);
+ if (ret < 0)
+ dev_err(shrm->dev, "Adding msg to message queue failed\n");
+ spin_unlock(&q->update_lock);
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return ret;
+}
+
+/**
+ * rpc_receive() - Rx Completion callback
+ *
+ * @data:message pointer
+ * @n_bytes:message size
+ *
+ * This function is a callback to indicate RPC message reception is complete.
+ * It updates Writeptr of the Fifo
+ */
+static int rpc_receive(struct shrm_dev *shrm,
+ void *data, u32 n_bytes)
+{
+ u32 size = 0;
+ int ret = 0;
+ u8 *psrc;
+ struct message_queue *q;
+ struct isadev_context *rpcdev = &shrm->isa_context->isadev[1];
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ q = &rpcdev->dl_queue;
+ spin_lock(&q->update_lock);
+ /* Memcopy RX data first */
+ if ((q->writeptr+n_bytes) >= q->size) {
+ psrc = (u8 *)data;
+ size = (q->size-q->writeptr);
+ /* Copy First Part of msg */
+ memcpy((q->fifo_base+q->writeptr), psrc, size);
+ psrc += size;
+ /* Copy Second Part of msg at the top of fifo */
+ memcpy(q->fifo_base, psrc, (n_bytes-size));
+ } else {
+ memcpy((q->fifo_base+q->writeptr), data, n_bytes);
+ }
+
+ ret = add_msg_to_queue(q, n_bytes);
+ if (ret < 0)
+ dev_err(shrm->dev, "Adding msg to message queue failed\n");
+ spin_unlock(&q->update_lock);
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return ret;
+}
+
+/**
+ * audio_receive() - Rx Completion callback
+ *
+ * @data:message pointer
+ * @n_bytes:message size
+ *
+ * This function is a callback to indicate audio message reception is complete.
+ * It updates Writeptr of the Fifo
+ */
+static int audio_receive(struct shrm_dev *shrm,
+ void *data, u32 n_bytes)
+{
+ u32 size = 0;
+ int ret = 0;
+ u8 *psrc;
+ struct message_queue *q;
+ struct isadev_context *audiodev = &shrm->isa_context->isadev[2];
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ q = &audiodev->dl_queue;
+ spin_lock(&q->update_lock);
+ /* Memcopy RX data first */
+ if ((q->writeptr+n_bytes) >= q->size) {
+ psrc = (u8 *)data;
+ size = (q->size-q->writeptr);
+ /* Copy First Part of msg */
+ memcpy((q->fifo_base+q->writeptr), psrc, size);
+ psrc += size;
+ /* Copy Second Part of msg at the top of fifo */
+ memcpy(q->fifo_base, psrc, (n_bytes-size));
+ } else {
+ memcpy((q->fifo_base+q->writeptr), data, n_bytes);
+ }
+ ret = add_msg_to_queue(q, n_bytes);
+ if (ret < 0)
+ dev_err(shrm->dev, "Adding msg to message queue failed\n");
+ spin_unlock(&q->update_lock);
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return ret;
+}
+
+/**
+ * security_receive() - Rx Completion callback
+ *
+ * @data:message pointer
+ * @n_bytes: message size
+ *
+ * This function is a callback to indicate security message reception
+ * is complete.It updates Writeptr of the Fifo
+ */
+static int security_receive(struct shrm_dev *shrm,
+ void *data, u32 n_bytes)
+{
+ u32 size = 0;
+ int ret = 0;
+ u8 *psrc;
+ struct message_queue *q;
+ struct isadev_context *secdev = &shrm->isa_context->isadev[3];
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ q = &secdev->dl_queue;
+ spin_lock(&q->update_lock);
+ /* Memcopy RX data first */
+ if ((q->writeptr+n_bytes) >= q->size) {
+ psrc = (u8 *)data;
+ size = (q->size-q->writeptr);
+ /* Copy First Part of msg */
+ memcpy((q->fifo_base+q->writeptr), psrc, size);
+ psrc += size;
+ /* Copy Second Part of msg at the top of fifo */
+ memcpy(q->fifo_base, psrc, (n_bytes-size));
+ } else {
+ memcpy((q->fifo_base+q->writeptr), data, n_bytes);
+ }
+ ret = add_msg_to_queue(q, n_bytes);
+ if (ret < 0)
+ dev_err(shrm->dev, "Adding msg to message queue failed\n");
+ spin_unlock(&q->update_lock);
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return ret;
+}
+
+
+/**
+ * isa_select() - Select Interface
+ *
+ * @filp:file descriptor pointer
+ * @wait:poll_table_struct pointer
+ *
+ * This function is used to perform non-blocking read operations. It allows
+ * a process to determine whether it can read from one or more open files
+ * without blocking. These calls can also block a process until any of a
+ * given set of file descriptors becomes available for reading.
+ * If a file is ready to read, POLLIN | POLLRDNORM bitmask is returned.
+ * The driver method is called whenever the user-space program performs a select
+ * system call involving a file descriptor associated with the driver.
+ */
+static u32 isa_select(struct file *filp,
+ struct poll_table_struct *wait)
+{
+ struct isadev_context *isadev = filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+ struct message_queue *q;
+ u32 mask = 0;
+ u32 m = iminor(filp->f_path.dentry->d_inode);
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (isadev->device_id != m)
+ return -1;
+ q = &isadev->dl_queue;
+ poll_wait(filp, &q->wq_readable, wait);
+ if (atomic_read(&q->q_rp) == 1)
+ mask = POLLIN | POLLRDNORM;
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return mask;
+}
+
+/**
+ * isa_read() - Read from device
+ *
+ * @filp:file descriptor
+ * @buf:user buffer pointer
+ * @len:size of requested data transfer
+ * @ppos:not used
+ *
+ * This function is called whenever user calls read() system call.
+ * It reads a oldest message from queue and copies it into user buffer and
+ * returns its size.
+ * If there is no message present in queue, then it blocks until new data is
+ * available.
+ */
+ssize_t isa_read(struct file *filp, char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ struct isadev_context *isadev = (struct isadev_context *)
+ filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+ struct message_queue *q;
+ char *psrc;
+ u32 msgsize;
+ u32 size = 0;
+ int ret = 0;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (len <= 0)
+ return -EFAULT;
+ q = &isadev->dl_queue;
+
+ spin_lock_bh(&q->update_lock);
+ if (list_empty(&q->msg_list)) {
+ spin_unlock_bh(&q->update_lock);
+ if (wait_event_interruptible(q->wq_readable,
+ atomic_read(&q->q_rp) == 1)) {
+ return -ERESTARTSYS;
+ }
+ } else
+ spin_unlock_bh(&q->update_lock);
+
+ msgsize = get_size_of_new_msg(q);
+ if ((q->readptr+msgsize) >= q->size) {
+ dev_dbg(shrm->dev, "Inside Loop Back\n");
+ psrc = (char *)buf;
+ size = (q->size-q->readptr);
+ /* Copy First Part of msg */
+ if (copy_to_user(psrc,
+ (u8 *)(q->fifo_base+q->readptr),
+ size)) {
+ dev_err(shrm->dev, "copy_to_user failed\n");
+ return -EFAULT;
+ }
+ psrc += size;
+ /* Copy Second Part of msg at the top of fifo */
+ if (copy_to_user(psrc,
+ (u8 *)(q->fifo_base),
+ (msgsize-size))) {
+ dev_err(shrm->dev, "copy_to_user failed\n");
+ return -EFAULT;
+ }
+ } else {
+ if (copy_to_user(buf,
+ (u8 *)(q->fifo_base+q->readptr),
+ msgsize)) {
+ dev_err(shrm->dev, "copy_to_user failed\n");
+ return -EFAULT;
+ }
+ }
+
+ spin_lock_bh(&q->update_lock);
+ ret = remove_msg_from_queue(q);
+ if (ret < 0) {
+ dev_err(shrm->dev,
+ "Removing msg from message queue failed\n");
+ msgsize = ret;
+ }
+ spin_unlock_bh(&q->update_lock);
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return msgsize;
+}
+/**
+ * isa_write() - Write to device
+ *
+ * @filp:file descriptor
+ * @buf:user buffer pointer
+ * @len:size of requested data transfer
+ * @ppos:not used
+ *
+ * This function is called whenever user calls write() system call.
+ * It checks if there is space available in queue, and copies the message
+ * inside queue. If there is no space, it blocks until space becomes available.
+ * It also schedules transfer thread to transmit the newly added message.
+ */
+static ssize_t isa_write(struct file *filp, const char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ struct isadev_context *isadev = filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+ struct message_queue *q;
+ int err, ret;
+ void *addr = 0;
+ u8 l2_header = 0;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ if (len <= 0)
+ return -EFAULT;
+ q = &isadev->dl_queue;
+
+ switch (isadev->device_id) {
+ case ISI_MESSAGING:
+ dev_dbg(shrm->dev, "ISI\n");
+ addr = (void *)wr_isi_msg;
+#ifdef CONFIG_U8500_SHRM_LOOP_BACK
+ dev_dbg(shrm->dev, "Loopback\n");
+ l2_header = COMMON_LOOPBACK_MESSAGING;
+#else
+ l2_header = isadev->device_id;
+#endif
+ break;
+ case RPC_MESSAGING:
+ dev_dbg(shrm->dev, "RPC\n");
+ addr = (void *)wr_rpc_msg;
+#ifdef CONFIG_U8500_SHRM_LOOP_BACK
+ l2_header = COMMON_LOOPBACK_MESSAGING;
+#else
+ l2_header = isadev->device_id;
+#endif
+ break;
+ case AUDIO_MESSAGING:
+ dev_dbg(shrm->dev, "Audio\n");
+ addr = (void *)wr_audio_msg;
+#ifdef CONFIG_U8500_SHRM_LOOP_BACK
+ l2_header = AUDIO_LOOPBACK_MESSAGING;
+#else
+ l2_header = isadev->device_id;
+#endif
+
+ break;
+ case SECURITY_MESSAGING:
+ dev_dbg(shrm->dev, "Security\n");
+ addr = (void *)wr_sec_msg;
+#ifdef CONFIG_U8500_SHRM_LOOP_BACK
+ l2_header = COMMON_LOOPBACK_MESSAGING;
+#else
+ l2_header = isadev->device_id;
+#endif
+ break;
+ default:
+ dev_dbg(shrm->dev, "Wrong device\n");
+ return -EFAULT;
+ }
+
+ if (copy_from_user(addr, buf, len)) {
+ dev_err(shrm->dev, "copy_from_user failed\n");
+ return -EFAULT;
+ }
+
+ /* Write msg to Fifo */
+ if (isadev->device_id == 2) {
+ mutex_lock(&shrm->isa_context->tx_audio_mutex);
+ err = shm_write_msg(shrm, l2_header, addr, len);
+ if (!err)
+ ret = len;
+ else
+ ret = err;
+ mutex_unlock(&shrm->isa_context->tx_audio_mutex);
+ } else {
+ spin_lock_bh(&shrm->isa_context->common_tx);
+ err = shm_write_msg(shrm, l2_header, addr, len);
+ if (!err)
+ ret = len;
+ else
+ ret = err;
+ spin_unlock_bh(&shrm->isa_context->common_tx);
+ }
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return ret;
+}
+
+/**
+ * isa_ioctl() - To handle different ioctl commands supported by driver.
+ *
+ * @inode: structure is used by the kernel internally to represent files
+ * @filp:file descriptor pointer
+ * @cmd:ioctl command
+ * @arg:input param
+ *
+ * Following ioctls are supported by this driver.
+ * DLP_IOCTL_ALLOCATE_BUFFER - To allocate buffer for new uplink message.
+ * This ioctl is called with required message size. It returns offset for
+ * the allocates space in the queue. DLP_IOCTL_PUT_MESSAGE - To indicate
+ * new uplink message available in queuq for transmission. Message is copied
+ * from offset location returned by previous ioctl before calling this ioctl.
+ * DLP_IOCTL_GET_MESSAGE - To check if any downlink message is available in
+ * queue. It returns offset for new message inside queue.
+ * DLP_IOCTL_DEALLOCATE_BUFFER - To deallocate any buffer allocate for
+ * downlink message once the message is copied. Message is copied from offset
+ * location returned by previous ioctl before calling this ioctl.
+ */
+static int isa_ioctl(struct inode *inode, struct file *filp,
+ unsigned cmd, unsigned long arg)
+{
+ int err = 0;
+ struct isadev_context *isadev = filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+ u32 m = iminor(inode);
+
+ if (isadev->device_id != m)
+ return -1;
+
+ switch (cmd) {
+ case DLP_IOC_ALLOCATE_BUFFER:
+ dev_dbg(shrm->dev, "DLP_IOC_ALLOCATE_BUFFER\n");
+ break;
+ case DLP_IOC_PUT_MESSAGE:
+ dev_dbg(shrm->dev, "DLP_IOC_PUT_MESSAGE\n");
+ break;
+ case DLP_IOC_GET_MESSAGE:
+ dev_dbg(shrm->dev, "DLP_IOC_GET_MESSAGE\n");
+ break;
+ case DLP_IOC_DEALLOCATE_BUFFER:
+ dev_dbg(shrm->dev, "DLP_IOC_DEALLOCATE_BUFFER\n");
+ break;
+ default:
+ dev_dbg(shrm->dev, "Unknown IOCTL\n");
+ err = -1;
+ break;
+ }
+ return err;
+}
+/**
+ * isa_mmap() - Maps kernel queue memory to user space.
+ *
+ * @filp:file descriptor pointer
+ * @vma:virtual area memory structure.
+ *
+ * This function maps kernel FIFO into user space. This function
+ * shall be called twice to map both uplink and downlink buffers.
+ */
+static int isa_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct isadev_context *isadev = filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+
+ u32 m = iminor(filp->f_path.dentry->d_inode);
+ dev_dbg(shrm->dev, "%s %dIN\n", __func__, m);
+
+ isadev = (struct isadev_context *)filp->private_data;
+ return 0;
+}
+
+/**
+ * isa_close() - Close device file
+ *
+ * @inode:structure is used by the kernel internally to represent files
+ * @filp:device file descriptor
+ *
+ * This function deletes structues associated with this file, deletes
+ * queues, flushes and destroys workqueus and closes this file.
+ * It also unregisters itself from l2mux driver.
+ */
+static int isa_close(struct inode *inode, struct file *filp)
+{
+ struct isadev_context *isadev = filp->private_data;
+ struct shrm_dev *shrm = isadev->dl_queue.shrm;
+ struct isa_driver_context *isa_context = shrm->isa_context;
+ u8 m;
+
+ mutex_lock(&isa_lock);
+ m = iminor(filp->f_path.dentry->d_inode);
+ dev_dbg(shrm->dev, "%s IN %d", __func__, m);
+
+ if (atomic_dec_and_test(&isa_context->is_open[m])) {
+ atomic_inc(&isa_context->is_open[m]);
+ dev_err(shrm->dev, "Device not opened yet\n");
+ mutex_unlock(&isa_lock);
+ return -ENODEV;
+ }
+ atomic_set(&isa_context->is_open[m], 1);
+
+ dev_dbg(shrm->dev, "isadev->device_id %d", isadev->device_id);
+ dev_dbg(shrm->dev, "Closed %d device\n", m);
+
+ if (m == ISI_MESSAGING)
+ dev_dbg(shrm->dev, "Closed ISI_MESSAGING Device\n");
+ else if (m == RPC_MESSAGING)
+ dev_dbg(shrm->dev, "Closed RPC_MESSAGING Device\n");
+ else if (m == AUDIO_MESSAGING)
+ dev_dbg(shrm->dev, "Closed AUDIO_MESSAGING Device\n");
+ else if (m == SECURITY_MESSAGING)
+ dev_dbg(shrm->dev, "Closed SECURITY_MESSAGING Device\n");
+ else
+ dev_dbg(shrm->dev, NAME ":No such device present\n");
+
+ mutex_unlock(&isa_lock);
+ return 0;
+}
+/**
+ * isa_open() - Open device file
+ *
+ * @inode: structure is used by the kernel internally to represent files
+ * @filp: device file descriptor
+ *
+ * This function performs initialization tasks needed to open SHM channel.
+ * Following tasks are performed.
+ * -return if device is already opened
+ * -create uplink FIFO
+ * -create downlink FIFO
+ * -init delayed workqueue thread
+ * -register to l2mux driver
+ */
+static int isa_open(struct inode *inode, struct file *filp)
+{
+ int err = 0;
+ u8 m;
+ struct isadev_context *isadev;
+ struct isa_driver_context *isa_context = container_of(
+ inode->i_cdev,
+ struct isa_driver_context,
+ cdev);
+ struct shrm_dev *shrm = isa_context->isadev->dl_queue.shrm;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (get_boot_state() != BOOT_DONE) {
+ dev_err(shrm->dev, "Boot is not done\n");
+ return -EBUSY;
+ }
+ mutex_lock(&isa_lock);
+ m = iminor(inode);
+
+ if ((m != ISI_MESSAGING) && (m != RPC_MESSAGING) &&
+ (m != AUDIO_MESSAGING) && (m != SECURITY_MESSAGING)) {
+ dev_err(shrm->dev, "No such device present\n");
+ mutex_unlock(&isa_lock);
+ return -ENODEV;
+ }
+ if (!atomic_dec_and_test(&isa_context->is_open[m])) {
+ atomic_inc(&isa_context->is_open[m]);
+ dev_err(shrm->dev, "Device already opened\n");
+ mutex_unlock(&isa_lock);
+ return -EBUSY;
+ }
+
+ if (m == ISI_MESSAGING)
+ dev_dbg(shrm->dev, "Open ISI_MESSAGING Device\n");
+ else if (m == RPC_MESSAGING)
+ dev_dbg(shrm->dev, "Open RPC_MESSAGING Device\n");
+ else if (m == AUDIO_MESSAGING)
+ dev_dbg(shrm->dev, "Open AUDIO_MESSAGING Device\n");
+ else if (m == SECURITY_MESSAGING)
+ dev_dbg(shrm->dev, "Open SECURITY_MESSAGING Device\n");
+ else
+ dev_dbg(shrm->dev, ":No such device present\n");
+
+ isadev = &isa_context->isadev[m];
+ if (filp != NULL)
+ filp->private_data = isadev;
+
+ mutex_unlock(&isa_lock);
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return err;
+}
+
+const struct file_operations isa_fops = {
+ .owner = THIS_MODULE,
+ .open = isa_open,
+ .release = isa_close,
+ .ioctl = isa_ioctl,
+ .mmap = isa_mmap,
+ .read = isa_read,
+ .write = isa_write,
+ .poll = isa_select,
+};
+
+/**
+ * isa_init() - module insertion function
+ *
+ * This function registers module as a character driver using
+ * register_chrdev_region() or alloc_chrdev_region. It adds this
+ * driver to system using cdev_add() call. Major number is dynamically
+ * allocated using alloc_chrdev_region() by default or left to user to specify
+ * it during load time. For this variable major is used as module_param
+ * Nodes to be created using
+ * mknod /dev/isi c $major 0
+ * mknod /dev/rpc c $major 1
+ * mknod /dev/audio c $major 2
+ * mknod /dev/sec c $major 3
+ */
+int isa_init(struct shrm_dev *shrm)
+{
+ dev_t dev_id;
+ int retval, no_dev;
+ struct isadev_context *isadev;
+ struct isa_driver_context *isa_context;
+
+ isa_context = kzalloc(sizeof(struct isa_driver_context),
+ GFP_KERNEL);
+ shrm->isa_context = isa_context;
+ if (isa_context == NULL) {
+ dev_err(shrm->dev, "Failed to alloc memory\n");
+ return -ENOMEM;
+ }
+
+ if (major) {
+ dev_id = MKDEV(major, 0);
+ retval = register_chrdev_region(dev_id, ISA_DEVICES, NAME);
+ } else {
+ retval = alloc_chrdev_region(&dev_id, 0, ISA_DEVICES, NAME);
+ major = MAJOR(dev_id);
+ }
+
+ dev_dbg(shrm->dev, "major %d\n", major);
+
+ cdev_init(&isa_context->cdev, &isa_fops);
+ isa_context->cdev.owner = THIS_MODULE;
+ retval = cdev_add(&isa_context->cdev, dev_id, ISA_DEVICES);
+ if (retval) {
+ dev_err(shrm->dev, "Failed to add char device\n");
+ return retval;
+ }
+
+ for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++)
+ atomic_set(&isa_context->is_open[no_dev], 1);
+
+ isa_context->isadev = kzalloc(sizeof
+ (struct isadev_context)*ISA_DEVICES,
+ GFP_KERNEL);
+ if (isa_context->isadev == NULL) {
+ dev_err(shrm->dev, "Failed to alloc memory\n");
+ return -ENOMEM;
+ }
+ for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) {
+ isadev = &isa_context->isadev[no_dev];
+ isadev->device_id = no_dev;
+ retval = create_queue(&isadev->dl_queue,
+ isadev->device_id, shrm);
+ if (retval < 0) {
+ dev_err(shrm->dev, "create dl_queue failed\n");
+ delete_queue(&isadev->dl_queue);
+ kfree(isadev);
+ return retval;
+ }
+ }
+ mutex_init(&isa_context->tx_audio_mutex);
+ spin_lock_init(&isa_context->common_tx);
+
+ dev_err(shrm->dev, "SHRM char driver added\n");
+
+ return retval;
+}
+
+void isa_exit(struct shrm_dev *shrm)
+{
+ int no_dev;
+ struct isadev_context *isadev;
+ struct isa_driver_context *isa_context = shrm->isa_context;
+ dev_t dev_id = MKDEV(major, 0);
+
+ for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) {
+ isadev = &isa_context->isadev[no_dev];
+ delete_queue(&isadev->dl_queue);
+ kfree(isadev);
+ }
+
+ cdev_del(&isa_context->cdev);
+ unregister_chrdev_region(dev_id, ISA_DEVICES);
+ kfree(isa_context);
+
+ dev_err(shrm->dev, "SHRM char driver removed\n");
+}
+
+#ifdef CONFIG_HIGH_RES_TIMERS
+static enum hrtimer_restart callback(struct hrtimer *timer)
+{
+ return HRTIMER_NORESTART;
+}
+#endif
+
+
+static int __init shrm_probe(struct platform_device *pdev)
+{
+ int err = 0;
+ struct resource *res;
+ struct shrm_dev *shrm = NULL;
+
+ if (pdev == NULL) {
+ dev_err(shrm->dev,
+ "No device/platform_data found on shm device\n");
+ return -ENODEV;
+ }
+
+
+ shrm = kzalloc(sizeof(struct shrm_dev), GFP_KERNEL);
+ if (shrm == NULL) {
+ dev_err(shrm->dev,
+ "Could not allocate memory for struct shm_dev\n");
+ return -ENOMEM;
+ }
+ shrm->dev = &pdev->dev;
+
+ /* initialise the SHM */
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(shrm->dev, "Unable to map Ca Wake up interrupt\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ca_wake_irq = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
+ if (!res) {
+ dev_err(shrm->dev,
+ "Unable to map APE_Read_notif_common IRQ base\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ac_read_notif_0_irq = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 2);
+ if (!res) {
+ dev_err(shrm->dev,
+ "Unable to map APE_Read_notif_audio IRQ base\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ac_read_notif_1_irq = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 3);
+ if (!res) {
+ dev_err(shrm->dev,
+ "Unable to map Cmt_msg_pending_notif_common IRQbase\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ca_msg_pending_notif_0_irq = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 4);
+ if (!res) {
+ dev_err(shrm->dev,
+ "Unable to map Cmt_msg_pending_notif_audio IRQ base\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+ shrm->ca_msg_pending_notif_1_irq = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(shrm->dev,
+ "Could not get SHM IO memory information\n");
+ err = -ENODEV;
+ goto rollback_intr;
+ }
+
+ shrm->intr_base = (void __iomem *)ioremap_nocache(res->start,
+ res->end - res->start + 1);
+
+ if (!(shrm->intr_base)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_intr;
+ }
+
+ shrm->ape_common_fifo_base_phy =
+ (u32 *)U8500_SHM_FIFO_APE_COMMON_BASE;
+ shrm->ape_common_fifo_base =
+ (void __iomem *)ioremap_nocache(
+ U8500_SHM_FIFO_APE_COMMON_BASE,
+ SHM_FIFO_0_SIZE);
+ shrm->ape_common_fifo_size = (SHM_FIFO_0_SIZE)/4;
+
+ if (!(shrm->ape_common_fifo_base)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_ape_common_fifo_base;
+ }
+
+ shrm->cmt_common_fifo_base_phy =
+ (u32 *)U8500_SHM_FIFO_CMT_COMMON_BASE;
+
+ shrm->cmt_common_fifo_base =
+ (void __iomem *)ioremap_nocache(
+ U8500_SHM_FIFO_CMT_COMMON_BASE, SHM_FIFO_0_SIZE);
+ shrm->cmt_common_fifo_size = (SHM_FIFO_0_SIZE)/4;
+
+ if (!(shrm->cmt_common_fifo_base)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_cmt_common_fifo_base;
+ }
+
+ shrm->ape_audio_fifo_base_phy =
+ (u32 *)U8500_SHM_FIFO_APE_AUDIO_BASE;
+ shrm->ape_audio_fifo_base =
+ (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_APE_AUDIO_BASE,
+ SHM_FIFO_1_SIZE);
+ shrm->ape_audio_fifo_size = (SHM_FIFO_1_SIZE)/4;
+
+ if (!(shrm->ape_audio_fifo_base)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_ape_audio_fifo_base;
+ }
+
+ shrm->cmt_audio_fifo_base_phy =
+ (u32 *)U8500_SHM_FIFO_CMT_AUDIO_BASE;
+ shrm->cmt_audio_fifo_base =
+ (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_CMT_AUDIO_BASE,
+ SHM_FIFO_1_SIZE);
+ shrm->cmt_audio_fifo_size = (SHM_FIFO_1_SIZE)/4;
+
+ if (!(shrm->cmt_audio_fifo_base)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_cmt_audio_fifo_base;
+ }
+
+ shrm->ac_common_shared_wptr =
+ (void __iomem *)ioremap(SHM_ACFIFO_0_WRITE_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ac_common_shared_wptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_ac_common_shared_wptr;
+ }
+
+ shrm->ac_common_shared_rptr =
+ (void __iomem *)ioremap(SHM_ACFIFO_0_READ_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ac_common_shared_rptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+
+
+ shrm->ca_common_shared_wptr =
+ (void __iomem *)ioremap(SHM_CAFIFO_0_WRITE_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ca_common_shared_wptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+
+ shrm->ca_common_shared_rptr =
+ (void __iomem *)ioremap(SHM_CAFIFO_0_READ_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ca_common_shared_rptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+
+
+ shrm->ac_audio_shared_wptr =
+ (void __iomem *)ioremap(SHM_ACFIFO_1_WRITE_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ac_audio_shared_wptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+
+
+ shrm->ac_audio_shared_rptr =
+ (void __iomem *)ioremap(SHM_ACFIFO_1_READ_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ac_audio_shared_rptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+
+
+ shrm->ca_audio_shared_wptr =
+ (void __iomem *)ioremap(SHM_CAFIFO_1_WRITE_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ca_audio_shared_wptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+
+
+ shrm->ca_audio_shared_rptr =
+ (void __iomem *)ioremap(SHM_CAFIFO_1_READ_AMCU, SHM_PTR_SIZE);
+
+ if (!(shrm->ca_audio_shared_rptr)) {
+ dev_err(shrm->dev, "Unable to map register base\n");
+ err = -EBUSY;
+ goto rollback_map;
+ }
+
+
+ if (isa_init(shrm) != 0) {
+ dev_err(shrm->dev, "Driver Initialization Error\n");
+ err = -EBUSY;
+ }
+ /* install handlers and tasklets */
+ if (shm_initialise_irq(shrm)) {
+ dev_err(shrm->dev, "shm error in interrupt registration\n");
+ goto rollback_irq;
+ }
+
+#ifdef CONFIG_HIGH_RES_TIMERS
+ hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ timer.function = callback;
+
+ hrtimer_start(&timer, ktime_set(0, 2*NSEC_PER_MSEC), HRTIMER_MODE_REL);
+#endif
+
+ return err;
+
+rollback_irq:
+ free_shm_irq(shrm);
+rollback_map:
+ iounmap(shrm->ac_common_shared_wptr);
+ iounmap(shrm->ac_common_shared_rptr);
+ iounmap(shrm->ca_common_shared_wptr);
+ iounmap(shrm->ca_common_shared_rptr);
+ iounmap(shrm->ac_audio_shared_wptr);
+ iounmap(shrm->ac_audio_shared_rptr);
+ iounmap(shrm->ca_audio_shared_wptr);
+ iounmap(shrm->ca_audio_shared_rptr);
+rollback_ac_common_shared_wptr:
+ iounmap(shrm->cmt_audio_fifo_base);
+rollback_cmt_audio_fifo_base:
+ iounmap(shrm->ape_audio_fifo_base);
+rollback_ape_audio_fifo_base:
+ iounmap(shrm->cmt_common_fifo_base);
+rollback_cmt_common_fifo_base:
+ iounmap(shrm->ape_common_fifo_base);
+rollback_ape_common_fifo_base:
+ iounmap(shrm->intr_base);
+rollback_intr:
+ kfree(shrm);
+ return err;
+}
+
+static int __exit shrm_remove(struct platform_device *pdev)
+{
+ struct shrm_dev *shrm = platform_get_drvdata(pdev);
+
+ free_shm_irq(shrm);
+ iounmap(shrm->intr_base);
+ iounmap(shrm->ape_common_fifo_base);
+ iounmap(shrm->cmt_common_fifo_base);
+ iounmap(shrm->ape_audio_fifo_base);
+ iounmap(shrm->cmt_audio_fifo_base);
+ iounmap(shrm->ac_common_shared_wptr);
+ iounmap(shrm->ac_common_shared_rptr);
+ iounmap(shrm->ca_common_shared_wptr);
+ iounmap(shrm->ca_common_shared_rptr);
+ iounmap(shrm->ac_audio_shared_wptr);
+ iounmap(shrm->ac_audio_shared_rptr);
+ iounmap(shrm->ca_audio_shared_wptr);
+ iounmap(shrm->ca_audio_shared_rptr);
+ kfree(shrm);
+ isa_exit(shrm);
+
+ return 0;
+}
+#ifdef CONFIG_PM
+
+/**
+ * u8500_shrm_suspend() - This routine puts the SHRM in to sustend state.
+ * @pdev: platform device.
+ *
+ * This routine checks the current ongoing communication with Modem by
+ * examining the ca_wake state and prevents suspend if modem communication
+ * is on-going.
+ * If ca_wake = 1 (high), modem comm. is on-going; don't suspend
+ * If ca_wake = 0 (low), no comm. with modem on-going.Allow suspend
+ */
+int u8500_shrm_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct shrm_dev *shrm = platform_get_drvdata(pdev);
+
+ dev_dbg(shrm->dev, "%s called...\n", __func__);
+ dev_dbg(shrm->dev, "\n ca_wake_req_state = %x\n",
+ get_ca_wake_req_state());
+ /* if ca_wake_req is high, prevent system suspend */
+ if (get_ca_wake_req_state())
+ return -EBUSY;
+ else
+ return 0;
+}
+
+/**
+ * u8500_shrm_resume() - This routine resumes the SHRM from sustend state.
+ * @pdev: platform device.
+ *
+ * This routine restore back the current state of the SHRM
+ */
+int u8500_shrm_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct shrm_dev *shrm = platform_get_drvdata(pdev);
+
+ dev_dbg(shrm->dev, "%s called...\n", __func__);
+ /* TODO:
+ * As of now, no state save takes place in suspend.
+ * So, nothing to restore in resume.
+ * Simply return as of now.
+ * State saved in suspend should be restored here.
+ */
+
+ return 0;
+}
+
+static const struct dev_pm_ops shrm_dev_pm_ops = {
+ .suspend = u8500_shrm_suspend,
+ .resume = u8500_shrm_resume,
+};
+#endif
+
+static struct platform_driver shrm_driver = {
+ .remove = __exit_p(shrm_remove),
+ .driver = {
+ .name = "u8500_shrm",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &shrm_dev_pm_ops,
+#endif
+ },
+};
+
+static int __init shrm_driver_init(void)
+{
+ return platform_driver_probe(&shrm_driver, shrm_probe);
+}
+
+static void __exit shrm_driver_exit(void)
+{
+ platform_driver_unregister(&shrm_driver);
+}
+
+module_init(shrm_driver_init);
+module_exit(shrm_driver_exit);
+
+MODULE_AUTHOR("Biju Das");
+MODULE_DESCRIPTION("Shared Memory Modem Driver Interface");
+MODULE_LICENSE("GPL");
diff --git a/drivers/modem/shrm/shrm_fifo.c b/drivers/modem/shrm/shrm_fifo.c
new file mode 100644
index 00000000000..5fe9f3c5724
--- /dev/null
+++ b/drivers/modem/shrm/shrm_fifo.c
@@ -0,0 +1,837 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson
+ * Author: Kumar Sanghavi <kumar.sanghvi@stericsson.com> for ST-Ericsson
+ * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/modem/shrm/shrm.h>
+#include <linux/modem/shrm/shrm_driver.h>
+#include <linux/modem/shrm/shrm_private.h>
+#include <linux/modem/shrm/shrm_net.h>
+#include <linux/mfd/dbx500-prcmu.h>
+
+#define L1_BOOT_INFO_REQ 1
+#define L1_BOOT_INFO_RESP 2
+#define L1_NORMAL_MSG 3
+#define L1_HEADER_MASK 28
+#define L1_MAPID_MASK 0xF0000000
+#define CONFIG_OFFSET 8
+#define COUNTER_OFFSET 20
+#define L2_HEADER_SIZE 4
+#define L2_HEADER_OFFSET 24
+#define MASK_0_15_BIT 0xFF
+#define MASK_16_31_BIT 0xFF00
+#define MASK_16_27_BIT 0xFFF0000
+#define MASK_0_39_BIT 0xFFFFF
+#define MASK_40_55_BIT 0xFF00000
+#define MASK_8_16_BIT 0x0000FF00
+#define MSG_LEN_OFFSET 16
+#define SHRM_VER 2
+#define ca_ist_inactivity_timer 25 /*25ms */
+#define ca_csc_inactivity_timer 25 /*25ms */
+
+static u8 msg_audio_counter;
+static u8 msg_common_counter;
+
+struct fifo_write_params ape_shm_fifo_0;
+struct fifo_write_params ape_shm_fifo_1;
+struct fifo_read_params cmt_shm_fifo_0;
+struct fifo_read_params cmt_shm_fifo_1;
+
+
+static u8 cmt_read_notif_0_send;
+static u8 cmt_read_notif_1_send;
+
+void shm_fifo_init(struct shrm_dev *shrm)
+{
+ ape_shm_fifo_0.writer_local_wptr = 0;
+ ape_shm_fifo_0.writer_local_rptr = 0;
+ *((u32 *)shrm->ac_common_shared_wptr) = 0;
+ *((u32 *)shrm->ac_common_shared_rptr) = 0;
+ ape_shm_fifo_0.shared_wptr = 0;
+ ape_shm_fifo_0.shared_rptr = 0;
+ ape_shm_fifo_0.availablesize = shrm->ape_common_fifo_size;
+ ape_shm_fifo_0.end_addr_fifo = shrm->ape_common_fifo_size;
+ ape_shm_fifo_0.fifo_virtual_addr = shrm->ape_common_fifo_base;
+ spin_lock_init(&ape_shm_fifo_0.fifo_update_lock);
+
+
+ cmt_shm_fifo_0.reader_local_rptr = 0;
+ cmt_shm_fifo_0.reader_local_wptr = 0;
+ cmt_shm_fifo_0.shared_wptr =
+ *((u32 *)shrm->ca_common_shared_wptr);
+ cmt_shm_fifo_0.shared_rptr =
+ *((u32 *)shrm->ca_common_shared_rptr);
+ cmt_shm_fifo_0.availablesize = shrm->cmt_common_fifo_size;
+ cmt_shm_fifo_0.end_addr_fifo = shrm->cmt_common_fifo_size;
+ cmt_shm_fifo_0.fifo_virtual_addr = shrm->cmt_common_fifo_base;
+
+ ape_shm_fifo_1.writer_local_wptr = 0;
+ ape_shm_fifo_1.writer_local_rptr = 0;
+ ape_shm_fifo_1.shared_wptr = 0;
+ ape_shm_fifo_1.shared_rptr = 0;
+ *((u32 *)shrm->ac_audio_shared_wptr) = 0;
+ *((u32 *)shrm->ac_audio_shared_rptr) = 0;
+ ape_shm_fifo_1.availablesize = shrm->ape_audio_fifo_size;
+ ape_shm_fifo_1.end_addr_fifo = shrm->ape_audio_fifo_size;
+ ape_shm_fifo_1.fifo_virtual_addr = shrm->ape_audio_fifo_base;
+ spin_lock_init(&ape_shm_fifo_1.fifo_update_lock);
+
+ cmt_shm_fifo_1.reader_local_rptr = 0;
+ cmt_shm_fifo_1.reader_local_wptr = 0;
+ cmt_shm_fifo_1.shared_wptr =
+ *((u32 *)shrm->ca_audio_shared_wptr);
+ cmt_shm_fifo_1.shared_rptr =
+ *((u32 *)shrm->ca_audio_shared_rptr);
+ cmt_shm_fifo_1.availablesize = shrm->cmt_audio_fifo_size;
+ cmt_shm_fifo_1.end_addr_fifo = shrm->cmt_audio_fifo_size;
+ cmt_shm_fifo_1.fifo_virtual_addr = shrm->cmt_audio_fifo_base;
+ msg_audio_counter = 0;
+ msg_common_counter = 0;
+}
+
+u8 read_boot_info_req(struct shrm_dev *shrm,
+ u32 *config,
+ u32 *version)
+{
+ struct fifo_read_params *fifo = &cmt_shm_fifo_0;
+ u32 *msg;
+ u32 header = 0;
+ u8 msgtype;
+
+ /* Read L1 header read content of reader_local_rptr */
+ msg = (u32 *)
+ (fifo->reader_local_rptr + fifo->fifo_virtual_addr);
+ header = *msg;
+ msgtype = (header & L1_MAPID_MASK) >> L1_MSG_MAPID_OFFSET;
+ if (msgtype != L1_BOOT_INFO_REQ) {
+ dev_err(shrm->dev, "Read_Boot_Info_Req Fatal ERROR\n");
+ dev_err(shrm->dev, "Received msgtype is %d\n", msgtype);
+ dev_info(shrm->dev, "Initiating a modem reset\n");
+ queue_kthread_work(&shrm->shm_ac_wake_kw,
+ &shrm->shm_mod_reset_req);
+ return 0;
+ }
+ *config = (header >> CONFIG_OFFSET) & MASK_0_15_BIT;
+ *version = header & MASK_0_15_BIT;
+ fifo->reader_local_rptr += 1;
+
+ return 1;
+}
+
+void write_boot_info_resp(struct shrm_dev *shrm, u32 config,
+ u32 version)
+{
+ struct fifo_write_params *fifo = &ape_shm_fifo_0;
+ u32 *msg;
+ u8 msg_length;
+ version = SHRM_VER;
+
+ spin_lock_bh(&fifo->fifo_update_lock);
+ /* Read L1 header read content of reader_local_rptr */
+ msg = (u32 *)
+ (fifo->writer_local_wptr+fifo->fifo_virtual_addr);
+ if (version < 1) {
+ *msg = ((L1_BOOT_INFO_RESP << L1_MSG_MAPID_OFFSET) |
+ ((config << CONFIG_OFFSET) & MASK_16_31_BIT)
+ | (version & MASK_0_15_BIT));
+ msg_length = 1;
+ } else {
+ *msg = ((L1_BOOT_INFO_RESP << L1_MSG_MAPID_OFFSET) |
+ ((0x8 << MSG_LEN_OFFSET) & MASK_16_27_BIT) |
+ ((config << CONFIG_OFFSET) & MASK_8_16_BIT)|
+ version);
+ msg++;
+ *msg = ca_ist_inactivity_timer;
+ msg++;
+ *msg = ca_csc_inactivity_timer;
+ msg_length = L1_NORMAL_MSG;
+ }
+ fifo->writer_local_wptr += msg_length;
+ fifo->availablesize -= msg_length;
+ spin_unlock_bh(&fifo->fifo_update_lock);
+}
+
+/**
+ * shm_write_msg_to_fifo() - write message to FIFO
+ * @shrm: pointer to shrm device information structure
+ * @channel: audio or common channel
+ * @l2header: L2 header or device ID
+ * @addr: pointer to write buffer address
+ * @length: length of mst to write
+ *
+ * Function Which Writes the data into Fifo in IPC zone
+ * It is called from shm_write_msg. This function will copy the msg
+ * from the kernel buffer to FIFO. There are 4 kernel buffers from where
+ * the data is to copied to FIFO one for each of the messages ISI, RPC,
+ * AUDIO and SECURITY. ISI, RPC and SECURITY messages are pushed to FIFO
+ * in commmon channel and AUDIO message is pushed onto audio channel FIFO.
+ */
+int shm_write_msg_to_fifo(struct shrm_dev *shrm, u8 channel,
+ u8 l2header, void *addr, u32 length)
+{
+ struct fifo_write_params *fifo = NULL;
+ u32 l1_header = 0, l2_header = 0;
+ u32 requiredsize;
+ u32 size = 0;
+ u32 *msg;
+ u8 *src;
+
+ if (channel == COMMON_CHANNEL)
+ fifo = &ape_shm_fifo_0;
+ else if (channel == AUDIO_CHANNEL)
+ fifo = &ape_shm_fifo_1;
+ else {
+ dev_err(shrm->dev, "invalid channel\n");
+ return -EINVAL;
+ }
+
+ /* L2 size in 32b */
+ requiredsize = ((length + 3) / 4);
+ /* Add size of L1 & L2 header */
+ requiredsize += 2;
+
+ /* if availablesize = or < requiredsize then error */
+ if (fifo->availablesize <= requiredsize) {
+ /* Fatal ERROR - should never happens */
+ dev_dbg(shrm->dev, "wr_wptr= %x\n",
+ fifo->writer_local_wptr);
+ dev_dbg(shrm->dev, "wr_rptr= %x\n",
+ fifo->writer_local_rptr);
+ dev_dbg(shrm->dev, "shared_wptr= %x\n",
+ fifo->shared_wptr);
+ dev_dbg(shrm->dev, "shared_rptr= %x\n",
+ fifo->shared_rptr);
+ dev_dbg(shrm->dev, "availsize= %x\n",
+ fifo->availablesize);
+ dev_dbg(shrm->dev, "end__fifo= %x\n",
+ fifo->end_addr_fifo);
+ dev_warn(shrm->dev, "Modem is busy, please wait."
+ " c_cnt = %d; a_cnt = %d\n", msg_common_counter,
+ msg_audio_counter);
+ if (channel == COMMON_CHANNEL) {
+ dev_warn(shrm->dev,
+ "Modem is lagging behind in reading."
+ "Stopping n/w dev queue\n");
+ shrm_stop_netdev(shrm->ndev);
+ }
+
+ return -EAGAIN;
+ }
+
+ if (channel == COMMON_CHANNEL) {
+ /* build L1 header */
+ l1_header = ((L1_NORMAL_MSG << L1_MSG_MAPID_OFFSET) |
+ (((msg_common_counter++) << COUNTER_OFFSET)
+ & MASK_40_55_BIT) |
+ ((length + L2_HEADER_SIZE) & MASK_0_39_BIT));
+ } else if (channel == AUDIO_CHANNEL) {
+ /* build L1 header */
+ l1_header = ((L1_NORMAL_MSG << L1_MSG_MAPID_OFFSET) |
+ (((msg_audio_counter++) << COUNTER_OFFSET)
+ & MASK_40_55_BIT) |
+ ((length + L2_HEADER_SIZE) & MASK_0_39_BIT));
+ }
+
+ /*
+ * Need to take care race condition for fifo->availablesize
+ * & fifo->writer_local_rptr with Ac_Read_notification interrupt.
+ * One option could be use stack variable for LocalRptr and recompute
+ * fifo->availablesize,based on flag enabled in the
+ * Ac_read_notification
+ */
+ l2_header = ((l2header << L2_HEADER_OFFSET) |
+ ((length) & MASK_0_39_BIT));
+ spin_lock_bh(&fifo->fifo_update_lock);
+ /* Check Local Rptr is less than or equal to Local WPtr */
+ if (fifo->writer_local_rptr <= fifo->writer_local_wptr) {
+ msg = (u32 *)
+ (fifo->fifo_virtual_addr+fifo->writer_local_wptr);
+
+ /* check enough place bewteen writer_local_wptr & end of FIFO */
+ if ((fifo->end_addr_fifo-fifo->writer_local_wptr) >=
+ requiredsize) {
+ /* Add L1 header and L2 header */
+ *msg = l1_header;
+ msg++;
+ *msg = l2_header;
+ msg++;
+
+ /* copy the l2 message in 1 memcpy */
+ memcpy((void *)msg, addr, length);
+ /* UpdateWptr */
+ fifo->writer_local_wptr += requiredsize;
+ fifo->availablesize -= requiredsize;
+ fifo->writer_local_wptr %= fifo->end_addr_fifo;
+ } else {
+ /*
+ * message is split between and of FIFO and beg of FIFO
+ * copy first part from writer_local_wptr to end of FIFO
+ */
+ size = fifo->end_addr_fifo-fifo->writer_local_wptr;
+
+ if (size == 1) {
+ /* Add L1 header */
+ *msg = l1_header;
+ msg++;
+ /* UpdateWptr */
+ fifo->writer_local_wptr = 0;
+ fifo->availablesize -= size;
+ /*
+ * copy second part from beg of FIFO
+ * with remaining part of msg
+ */
+ msg = (u32 *)
+ fifo->fifo_virtual_addr;
+ *msg = l2_header;
+ msg++;
+
+ /* copy the l3 message in 1 memcpy */
+ memcpy((void *)msg, addr, length);
+ /* UpdateWptr */
+ fifo->writer_local_wptr +=
+ requiredsize-size;
+ fifo->availablesize -=
+ (requiredsize-size);
+ } else if (size == 2) {
+ /* Add L1 header and L2 header */
+ *msg = l1_header;
+ msg++;
+ *msg = l2_header;
+ msg++;
+
+ /* UpdateWptr */
+ fifo->writer_local_wptr = 0;
+ fifo->availablesize -= size;
+
+ /*
+ * copy second part from beg of FIFO
+ * with remaining part of msg
+ */
+ msg = (u32 *)
+ fifo->fifo_virtual_addr;
+ /* copy the l3 message in 1 memcpy */
+ memcpy((void *)msg, addr, length);
+
+ /* UpdateWptr */
+ fifo->writer_local_wptr +=
+ requiredsize-size;
+ fifo->availablesize -=
+ (requiredsize-size);
+ } else {
+ /* Add L1 header and L2 header */
+ *msg = l1_header;
+ msg++;
+ *msg = l2_header;
+ msg++;
+
+ /* copy the l2 message in 1 memcpy */
+ memcpy((void *)msg, addr, (size-2)*4);
+
+
+ /* UpdateWptr */
+ fifo->writer_local_wptr = 0;
+ fifo->availablesize -= size;
+
+ /*
+ * copy second part from beg of FIFO
+ * with remaining part of msg
+ */
+ msg = (u32 *)fifo->fifo_virtual_addr;
+ src = (u8 *)addr+((size - 2) * 4);
+ memcpy((void *)msg, src,
+ (length-((size - 2) * 4)));
+
+ /* UpdateWptr */
+ fifo->writer_local_wptr +=
+ requiredsize-size;
+ fifo->availablesize -=
+ (requiredsize-size);
+ }
+
+ }
+ } else {
+ /* writer_local_rptr > writer_local_wptr */
+ msg = (u32 *)
+ (fifo->fifo_virtual_addr+fifo->writer_local_wptr);
+ /* Add L1 header and L2 header */
+ *msg = l1_header;
+ msg++;
+ *msg = l2_header;
+ msg++;
+ /*
+ * copy message possbile between writer_local_wptr up
+ * to writer_local_rptr copy the l3 message in 1 memcpy
+ */
+ memcpy((void *)msg, addr, length);
+
+ /* UpdateWptr */
+ fifo->writer_local_wptr += requiredsize;
+ fifo->availablesize -= requiredsize;
+
+ }
+ spin_unlock_bh(&fifo->fifo_update_lock);
+ return length;
+}
+
+/**
+ * read_one_l2msg_common() - read message from common channel
+ * @shrm: pointer to shrm device information structure
+ * @l2_msg: pointer to the read L2 message buffer
+ * @len: message length
+ *
+ * This function read one message from the FIFO and returns l2 header type
+ */
+u8 read_one_l2msg_common(struct shrm_dev *shrm,
+ u8 *l2_msg, u32 *len)
+{
+ struct fifo_read_params *fifo = &cmt_shm_fifo_0;
+
+ u32 *msg;
+ u32 l1_header = 0;
+ u32 l2_header = 0;
+ u32 length;
+ u8 msgtype;
+ u32 msg_size;
+ u32 size = 0;
+
+ /* Read L1 header read content of reader_local_rptr */
+ msg = (u32 *)
+ (fifo->reader_local_rptr+fifo->fifo_virtual_addr);
+ l1_header = *msg++;
+ msgtype = (l1_header & 0xF0000000) >> L1_HEADER_MASK;
+
+ if (msgtype != L1_NORMAL_MSG) {
+ /* Fatal ERROR - should never happens */
+ dev_info(shrm->dev, "wr_wptr= %x\n",
+ fifo->reader_local_wptr);
+ dev_info(shrm->dev, "wr_rptr= %x\n",
+ fifo->reader_local_rptr);
+ dev_info(shrm->dev, "shared_wptr= %x\n",
+ fifo->shared_wptr);
+ dev_info(shrm->dev, "shared_rptr= %x\n",
+ fifo->shared_rptr);
+ dev_info(shrm->dev, "availsize= %x\n",
+ fifo->availablesize);
+ dev_info(shrm->dev, "end_fifo= %x\n",
+ fifo->end_addr_fifo);
+ /* Fatal ERROR - should never happens */
+ dev_crit(shrm->dev, "Fatal ERROR - should never happen\n");
+ dev_info(shrm->dev, "Initiating a modem reset\n");
+ queue_kthread_work(&shrm->shm_ac_wake_kw,
+ &shrm->shm_mod_reset_req);
+ }
+ if (fifo->reader_local_rptr == (fifo->end_addr_fifo-1)) {
+ l2_header = (*((u32 *)fifo->fifo_virtual_addr));
+ length = l2_header & MASK_0_39_BIT;
+ } else {
+ /* Read L2 header,Msg size & content of reader_local_rptr */
+ l2_header = *msg;
+ length = l2_header & MASK_0_39_BIT;
+ }
+
+ *len = length;
+ msg_size = ((length + 3) / 4);
+ msg_size += 2;
+
+ if (fifo->reader_local_rptr + msg_size <=
+ fifo->end_addr_fifo) {
+ /* Skip L2 header */
+ msg++;
+
+ /* read msg between reader_local_rptr and end of FIFO */
+ memcpy((void *)l2_msg, (void *)msg, length);
+ /* UpdateLocalRptr */
+ fifo->reader_local_rptr += msg_size;
+ fifo->reader_local_rptr %= fifo->end_addr_fifo;
+ } else {
+ /*
+ * msg split between end of FIFO and beg copy first
+ * part of msg read msg between reader_local_rptr
+ * and end of FIFO
+ */
+ size = fifo->end_addr_fifo-fifo->reader_local_rptr;
+ if (size == 1) {
+ msg = (u32 *)(fifo->fifo_virtual_addr);
+ /* Skip L2 header */
+ msg++;
+ memcpy((void *)l2_msg, (void *)(msg), length);
+ } else if (size == 2) {
+ /* Skip L2 header */
+ msg++;
+ msg = (u32 *)(fifo->fifo_virtual_addr);
+ memcpy((void *)l2_msg,
+ (void *)(msg), length);
+ } else {
+ /* Skip L2 header */
+ msg++;
+ memcpy((void *)l2_msg, (void *)msg, ((size - 2) * 4));
+ /* copy second part of msg */
+ l2_msg += ((size - 2) * 4);
+ msg = (u32 *)(fifo->fifo_virtual_addr);
+ memcpy((void *)l2_msg, (void *)(msg),
+ (length-((size - 2) * 4)));
+ }
+ fifo->reader_local_rptr =
+ (fifo->reader_local_rptr+msg_size) %
+ fifo->end_addr_fifo;
+ }
+ return (l2_header>>L2_HEADER_OFFSET) & MASK_0_15_BIT;
+ }
+
+u8 read_remaining_messages_common()
+{
+ struct fifo_read_params *fifo = &cmt_shm_fifo_0;
+ /*
+ * There won't be any Race condition reader_local_rptr &
+ * fifo->reader_local_wptr with CaMsgpending Notification Interrupt
+ */
+ return ((fifo->reader_local_rptr != fifo->reader_local_wptr) ? 1 : 0);
+}
+
+u8 read_one_l2msg_audio(struct shrm_dev *shrm,
+ u8 *l2_msg, u32 *len)
+{
+ struct fifo_read_params *fifo = &cmt_shm_fifo_1;
+
+ u32 *msg;
+ u32 l1_header = 0;
+ u32 l2_header = 0;
+ u32 length;
+ u8 msgtype;
+ u32 msg_size;
+ u32 size = 0;
+
+ /* Read L1 header read content of reader_local_rptr */
+ msg = (u32 *)
+ (fifo->reader_local_rptr+fifo->fifo_virtual_addr);
+ l1_header = *msg++;
+ msgtype = (l1_header & 0xF0000000) >> L1_HEADER_MASK;
+
+ if (msgtype != L1_NORMAL_MSG) {
+ /* Fatal ERROR - should never happens */
+ dev_info(shrm->dev, "wr_local_wptr= %x\n",
+ fifo->reader_local_wptr);
+ dev_info(shrm->dev, "wr_local_rptr= %x\n",
+ fifo->reader_local_rptr);
+ dev_info(shrm->dev, "shared_wptr= %x\n",
+ fifo->shared_wptr);
+ dev_info(shrm->dev, "shared_rptr= %x\n",
+ fifo->shared_rptr);
+ dev_info(shrm->dev, "availsize=%x\n",
+ fifo->availablesize);
+ dev_info(shrm->dev, "end_fifo= %x\n",
+ fifo->end_addr_fifo);
+ dev_info(shrm->dev, "Received msgtype is %d\n", msgtype);
+ /* Fatal ERROR - should never happens */
+ dev_crit(shrm->dev, "Fatal ERROR - should never happen\n");
+ dev_info(shrm->dev, "Initiating a modem reset\n");
+ queue_kthread_work(&shrm->shm_ac_wake_kw,
+ &shrm->shm_mod_reset_req);
+ }
+ if (fifo->reader_local_rptr == (fifo->end_addr_fifo-1)) {
+ l2_header = (*((u32 *)fifo->fifo_virtual_addr));
+ length = l2_header & MASK_0_39_BIT;
+ } else {
+ /* Read L2 header,Msg size & content of reader_local_rptr */
+ l2_header = *msg;
+ length = l2_header & MASK_0_39_BIT;
+ }
+
+ *len = length;
+ msg_size = ((length + 3) / 4);
+ msg_size += 2;
+
+ if (fifo->reader_local_rptr + msg_size <=
+ fifo->end_addr_fifo) {
+ /* Skip L2 header */
+ msg++;
+ /* read msg between reader_local_rptr and end of FIFO */
+ memcpy((void *)l2_msg, (void *)msg, length);
+ /* UpdateLocalRptr */
+ fifo->reader_local_rptr += msg_size;
+ fifo->reader_local_rptr %= fifo->end_addr_fifo;
+ } else {
+
+ /*
+ * msg split between end of FIFO and beg
+ * copy first part of msg
+ * read msg between reader_local_rptr and end of FIFO
+ */
+ size = fifo->end_addr_fifo-fifo->reader_local_rptr;
+ if (size == 1) {
+ msg = (u32 *)(fifo->fifo_virtual_addr);
+ /* Skip L2 header */
+ msg++;
+ memcpy((void *)l2_msg, (void *)(msg), length);
+ } else if (size == 2) {
+ /* Skip L2 header */
+ msg++;
+ msg = (u32 *)(fifo->fifo_virtual_addr);
+ memcpy((void *)l2_msg, (void *)(msg), length);
+ } else {
+ /* Skip L2 header */
+ msg++;
+ memcpy((void *)l2_msg, (void *)msg, ((size - 2) * 4));
+ /* copy second part of msg */
+ l2_msg += ((size - 2) * 4);
+ msg = (u32 *)(fifo->fifo_virtual_addr);
+ memcpy((void *)l2_msg, (void *)(msg),
+ (length-((size - 2) * 4)));
+ }
+ fifo->reader_local_rptr =
+ (fifo->reader_local_rptr+msg_size) %
+ fifo->end_addr_fifo;
+
+ }
+ return (l2_header>>L2_HEADER_OFFSET) & MASK_0_15_BIT;
+ }
+
+u8 read_remaining_messages_audio()
+{
+ struct fifo_read_params *fifo = &cmt_shm_fifo_1;
+
+ return ((fifo->reader_local_rptr != fifo->reader_local_wptr) ?
+ 1 : 0);
+}
+
+u8 is_the_only_one_unread_message(struct shrm_dev *shrm,
+ u8 channel, u32 length)
+{
+ struct fifo_write_params *fifo = NULL;
+ u32 messagesize = 0;
+ u8 is_only_one_unread_msg = 0;
+
+ if (channel == COMMON_CHANNEL)
+ fifo = &ape_shm_fifo_0;
+ else /* channel = AUDIO_CHANNEL */
+ fifo = &ape_shm_fifo_1;
+
+ /* L3 size in 32b */
+ messagesize = ((length + 3) / 4);
+ /* Add size of L1 & L2 header */
+ messagesize += 2;
+ /*
+ * possibility of race condition with Ac Read notification interrupt.
+ * need to check ?
+ */
+ if (fifo->writer_local_wptr > fifo->writer_local_rptr)
+ is_only_one_unread_msg =
+ ((fifo->writer_local_rptr + messagesize) ==
+ fifo->writer_local_wptr) ? 1 : 0;
+ else
+ /* Msg split between end of fifo and starting of Fifo */
+ is_only_one_unread_msg =
+ (((fifo->writer_local_rptr + messagesize) %
+ fifo->end_addr_fifo) == fifo->writer_local_wptr) ?
+ 1 : 0;
+
+ return is_only_one_unread_msg;
+}
+
+void update_ca_common_local_wptr(struct shrm_dev *shrm)
+{
+ /*
+ * update CA common reader local write pointer with the
+ * shared write pointer
+ */
+ struct fifo_read_params *fifo = &cmt_shm_fifo_0;
+
+ fifo->shared_wptr =
+ (*((u32 *)shrm->ca_common_shared_wptr));
+ fifo->reader_local_wptr = fifo->shared_wptr;
+}
+
+void update_ca_audio_local_wptr(struct shrm_dev *shrm)
+{
+ /*
+ * update CA audio reader local write pointer with the
+ * shared write pointer
+ */
+ struct fifo_read_params *fifo = &cmt_shm_fifo_1;
+
+ fifo->shared_wptr =
+ (*((u32 *)shrm->ca_audio_shared_wptr));
+ fifo->reader_local_wptr = fifo->shared_wptr;
+}
+
+void update_ac_common_local_rptr(struct shrm_dev *shrm)
+{
+ /*
+ * update AC common writer local read pointer with the
+ * shared read pointer
+ */
+ struct fifo_write_params *fifo;
+ u32 free_space = 0;
+
+ fifo = &ape_shm_fifo_0;
+
+ spin_lock_bh(&fifo->fifo_update_lock);
+ fifo->shared_rptr =
+ (*((u32 *)shrm->ac_common_shared_rptr));
+
+ if (fifo->shared_rptr >= fifo->writer_local_rptr)
+ free_space =
+ (fifo->shared_rptr-fifo->writer_local_rptr);
+ else {
+ free_space =
+ (fifo->end_addr_fifo-fifo->writer_local_rptr);
+ free_space += fifo->shared_rptr;
+ }
+
+ /* Chance of race condition of below variables with write_msg */
+ fifo->availablesize += free_space;
+ fifo->writer_local_rptr = fifo->shared_rptr;
+ spin_unlock_bh(&fifo->fifo_update_lock);
+}
+
+void update_ac_audio_local_rptr(struct shrm_dev *shrm)
+{
+ /*
+ * update AC audio writer local read pointer with the
+ * shared read pointer
+ */
+ struct fifo_write_params *fifo;
+ u32 free_space = 0;
+
+ fifo = &ape_shm_fifo_1;
+ spin_lock_bh(&fifo->fifo_update_lock);
+ fifo->shared_rptr =
+ (*((u32 *)shrm->ac_audio_shared_rptr));
+
+ if (fifo->shared_rptr >= fifo->writer_local_rptr)
+ free_space =
+ (fifo->shared_rptr-fifo->writer_local_rptr);
+ else {
+ free_space =
+ (fifo->end_addr_fifo-fifo->writer_local_rptr);
+ free_space += fifo->shared_rptr;
+ }
+
+ /* Chance of race condition of below variables with write_msg */
+ fifo->availablesize += free_space;
+ fifo->writer_local_rptr = fifo->shared_rptr;
+ spin_unlock_bh(&fifo->fifo_update_lock);
+}
+
+void update_ac_common_shared_wptr(struct shrm_dev *shrm)
+{
+ /*
+ * update AC common shared write pointer with the
+ * local write pointer
+ */
+ struct fifo_write_params *fifo;
+
+ fifo = &ape_shm_fifo_0;
+ spin_lock_bh(&fifo->fifo_update_lock);
+ /* Update shared pointer fifo offset of the IPC zone */
+ (*((u32 *)shrm->ac_common_shared_wptr)) =
+ fifo->writer_local_wptr;
+
+ fifo->shared_wptr = fifo->writer_local_wptr;
+ spin_unlock_bh(&fifo->fifo_update_lock);
+}
+
+void update_ac_audio_shared_wptr(struct shrm_dev *shrm)
+{
+ /*
+ * update AC audio shared write pointer with the
+ * local write pointer
+ */
+ struct fifo_write_params *fifo;
+
+ fifo = &ape_shm_fifo_1;
+ spin_lock_bh(&fifo->fifo_update_lock);
+ /* Update shared pointer fifo offset of the IPC zone */
+ (*((u32 *)shrm->ac_audio_shared_wptr)) =
+ fifo->writer_local_wptr;
+ fifo->shared_wptr = fifo->writer_local_wptr;
+ spin_unlock_bh(&fifo->fifo_update_lock);
+}
+
+void update_ca_common_shared_rptr(struct shrm_dev *shrm)
+{
+ /*
+ * update CA common shared read pointer with the
+ * local read pointer
+ */
+ struct fifo_read_params *fifo;
+
+ fifo = &cmt_shm_fifo_0;
+
+ /* Update shared pointer fifo offset of the IPC zone */
+ (*((u32 *)shrm->ca_common_shared_rptr)) =
+ fifo->reader_local_rptr;
+ fifo->shared_rptr = fifo->reader_local_rptr;
+}
+
+void update_ca_audio_shared_rptr(struct shrm_dev *shrm)
+{
+ /*
+ * update CA audio shared read pointer with the
+ * local read pointer
+ */
+ struct fifo_read_params *fifo;
+
+ fifo = &cmt_shm_fifo_1;
+
+ /* Update shared pointer fifo offset of the IPC zone */
+ (*((u32 *)shrm->ca_audio_shared_rptr)) =
+ fifo->reader_local_rptr;
+ fifo->shared_rptr = fifo->reader_local_rptr;
+}
+
+void get_reader_pointers(u8 channel_type, u32 *reader_local_rptr,
+ u32 *reader_local_wptr, u32 *shared_rptr)
+{
+ struct fifo_read_params *fifo = NULL;
+
+ if (channel_type == COMMON_CHANNEL)
+ fifo = &cmt_shm_fifo_0;
+ else /* channel_type = AUDIO_CHANNEL */
+ fifo = &cmt_shm_fifo_1;
+
+ *reader_local_rptr = fifo->reader_local_rptr;
+ *reader_local_wptr = fifo->reader_local_wptr;
+ *shared_rptr = fifo->shared_rptr;
+}
+
+void get_writer_pointers(u8 channel_type, u32 *writer_local_rptr,
+ u32 *writer_local_wptr, u32 *shared_wptr)
+{
+ struct fifo_write_params *fifo = NULL;
+
+ if (channel_type == COMMON_CHANNEL)
+ fifo = &ape_shm_fifo_0;
+ else /* channel_type = AUDIO_CHANNEL */
+ fifo = &ape_shm_fifo_1;
+
+ spin_lock_bh(&fifo->fifo_update_lock);
+ *writer_local_rptr = fifo->writer_local_rptr;
+ *writer_local_wptr = fifo->writer_local_wptr;
+ *shared_wptr = fifo->shared_wptr;
+ spin_unlock_bh(&fifo->fifo_update_lock);
+}
+
+void set_ca_msg_0_read_notif_send(u8 val)
+{
+ cmt_read_notif_0_send = val;
+}
+
+u8 get_ca_msg_0_read_notif_send(void)
+{
+ return cmt_read_notif_0_send;
+}
+
+void set_ca_msg_1_read_notif_send(u8 val)
+{
+ cmt_read_notif_1_send = val;
+}
+
+u8 get_ca_msg_1_read_notif_send(void)
+{
+ return cmt_read_notif_1_send;
+}
diff --git a/drivers/modem/shrm/shrm_protocol.c b/drivers/modem/shrm/shrm_protocol.c
new file mode 100644
index 00000000000..f029f4e0cfa
--- /dev/null
+++ b/drivers/modem/shrm/shrm_protocol.c
@@ -0,0 +1,1546 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson
+ * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> for ST-Ericsson
+ * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/hrtimer.h>
+#include <linux/delay.h>
+#include <linux/netlink.h>
+#include <linux/kthread.h>
+#include <linux/modem/shrm/shrm.h>
+#include <linux/modem/shrm/shrm_driver.h>
+#include <linux/modem/shrm/shrm_private.h>
+#include <linux/modem/shrm/shrm_net.h>
+#include <linux/modem/modem_client.h>
+#include <linux/mfd/dbx500-prcmu.h>
+#include <linux/mfd/abx500.h>
+#include <mach/reboot_reasons.h>
+#include <mach/suspend.h>
+#include <mach/prcmu-debug.h>
+
+#define L2_HEADER_ISI 0x0
+#define L2_HEADER_RPC 0x1
+#define L2_HEADER_AUDIO 0x2
+#define L2_HEADER_SECURITY 0x3
+#define L2_HEADER_COMMON_SIMPLE_LOOPBACK 0xC0
+#define L2_HEADER_COMMON_ADVANCED_LOOPBACK 0xC1
+#define L2_HEADER_AUDIO_SIMPLE_LOOPBACK 0x80
+#define L2_HEADER_AUDIO_ADVANCED_LOOPBACK 0x81
+#define L2_HEADER_CIQ 0xC3
+#define L2_HEADER_RTC_CALIBRATION 0xC8
+#define MAX_PAYLOAD 1024
+#define MOD_STUCK_TIMEOUT 6
+#define FIFO_FULL_TIMEOUT 1
+#define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_COREPD_AWAKE BIT(0)
+#define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_AAPD_AWAKE BIT(1)
+#define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_VMODEM_OFF_ISO BIT(2)
+#define PRCM_MOD_PURESET BIT(0)
+#define PRCM_MOD_SW_RESET BIT(1)
+
+#define PRCM_HOSTACCESS_REQ 0x334
+#define PRCM_MOD_AWAKE_STATUS 0x4A0
+#define PRCM_MOD_RESETN_VAL 0x204
+
+static u8 boot_state = BOOT_INIT;
+static u8 recieve_common_msg[8*1024];
+static u8 recieve_audio_msg[8*1024];
+static received_msg_handler rx_common_handler;
+static received_msg_handler rx_audio_handler;
+static struct hrtimer timer;
+static struct hrtimer mod_stuck_timer_0;
+static struct hrtimer mod_stuck_timer_1;
+static struct hrtimer fifo_full_timer;
+struct sock *shrm_nl_sk;
+
+static char shrm_common_tx_state = SHRM_SLEEP_STATE;
+static char shrm_common_rx_state = SHRM_SLEEP_STATE;
+static char shrm_audio_tx_state = SHRM_SLEEP_STATE;
+static char shrm_audio_rx_state = SHRM_SLEEP_STATE;
+
+static atomic_t ac_sleep_disable_count = ATOMIC_INIT(0);
+static atomic_t ac_msg_pend_1 = ATOMIC_INIT(0);
+static atomic_t mod_stuck = ATOMIC_INIT(0);
+static atomic_t fifo_full = ATOMIC_INIT(0);
+static struct shrm_dev *shm_dev;
+
+/* Spin lock and tasklet declaration */
+DECLARE_TASKLET(shm_ca_0_tasklet, shm_ca_msgpending_0_tasklet, 0);
+DECLARE_TASKLET(shm_ca_1_tasklet, shm_ca_msgpending_1_tasklet, 0);
+DECLARE_TASKLET(shm_ac_read_0_tasklet, shm_ac_read_notif_0_tasklet, 0);
+DECLARE_TASKLET(shm_ac_read_1_tasklet, shm_ac_read_notif_1_tasklet, 0);
+
+static DEFINE_MUTEX(ac_state_mutex);
+
+static DEFINE_SPINLOCK(ca_common_lock);
+static DEFINE_SPINLOCK(ca_audio_lock);
+static DEFINE_SPINLOCK(ca_wake_req_lock);
+static DEFINE_SPINLOCK(boot_lock);
+static DEFINE_SPINLOCK(mod_stuck_lock);
+static DEFINE_SPINLOCK(start_timer_lock);
+
+enum shrm_nl {
+ SHRM_NL_MOD_RESET = 1,
+ SHRM_NL_MOD_QUERY_STATE,
+ SHRM_NL_USER_MOD_RESET,
+ SHRM_NL_STATUS_MOD_ONLINE,
+ SHRM_NL_STATUS_MOD_OFFLINE,
+};
+
+static int check_modem_in_reset(void);
+
+void shm_print_dbg_info_work(struct kthread_work *work)
+{
+ abx500_dump_all_banks();
+ prcmu_debug_dump_regs();
+ prcmu_debug_dump_data_mem();
+}
+
+void shm_mod_reset_req_work(struct kthread_work *work)
+{
+ unsigned long flags;
+
+ /* update the boot_state */
+ spin_lock_irqsave(&boot_lock, flags);
+ if (boot_state != BOOT_DONE) {
+ dev_info(shm_dev->dev, "Modem in reset state\n");
+ spin_unlock_irqrestore(&boot_lock, flags);
+ return;
+ }
+ boot_state = BOOT_UNKNOWN;
+ wmb();
+ spin_unlock_irqrestore(&boot_lock, flags);
+ prcmu_modem_reset();
+}
+
+static void shm_ac_sleep_req_work(struct kthread_work *work)
+{
+ mutex_lock(&ac_state_mutex);
+ if (atomic_read(&ac_sleep_disable_count) == 0)
+ modem_release(shm_dev->modem);
+ mutex_unlock(&ac_state_mutex);
+}
+
+static void shm_ac_wake_req_work(struct kthread_work *work)
+{
+ mutex_lock(&ac_state_mutex);
+ modem_request(shm_dev->modem);
+ mutex_unlock(&ac_state_mutex);
+}
+
+static u32 get_host_accessport_val(void)
+{
+ u32 prcm_hostaccess;
+ u32 status;
+ u32 reset_stats;
+
+ status = (prcmu_read(PRCM_MOD_AWAKE_STATUS) & 0x03);
+ reset_stats = (prcmu_read(PRCM_MOD_RESETN_VAL) & 0x03);
+ prcm_hostaccess = prcmu_read(PRCM_HOSTACCESS_REQ);
+ wmb();
+ prcm_hostaccess = ((prcm_hostaccess & 0x01) &&
+ (status == (PRCM_MOD_AWAKE_STATUS_PRCM_MOD_AAPD_AWAKE |
+ PRCM_MOD_AWAKE_STATUS_PRCM_MOD_COREPD_AWAKE)) &&
+ (reset_stats == (PRCM_MOD_SW_RESET | PRCM_MOD_PURESET)));
+
+ return prcm_hostaccess;
+}
+
+static enum hrtimer_restart shm_fifo_full_timeout(struct hrtimer *timer)
+{
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_print_dbg_info);
+ return HRTIMER_NORESTART;
+}
+
+static enum hrtimer_restart shm_mod_stuck_timeout(struct hrtimer *timer)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mod_stuck_lock, flags);
+ /* Check MSR is already in progress */
+ if (shm_dev->msr_flag || boot_state == BOOT_UNKNOWN ||
+ atomic_read(&mod_stuck) || atomic_read(&fifo_full)) {
+ spin_unlock_irqrestore(&mod_stuck_lock, flags);
+ return HRTIMER_NORESTART;
+ }
+ atomic_set(&mod_stuck, 1);
+ spin_unlock_irqrestore(&mod_stuck_lock, flags);
+ dev_err(shm_dev->dev, "No response from modem, timeout %dsec\n",
+ MOD_STUCK_TIMEOUT);
+ dev_err(shm_dev->dev, "APE initiating MSR\n");
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_print_dbg_info);
+ return HRTIMER_NORESTART;
+}
+
+static enum hrtimer_restart callback(struct hrtimer *timer)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ca_wake_req_lock, flags);
+ if (((shrm_common_rx_state == SHRM_IDLE) ||
+ (shrm_common_rx_state == SHRM_SLEEP_STATE))
+ && ((shrm_common_tx_state == SHRM_IDLE) ||
+ (shrm_common_tx_state == SHRM_SLEEP_STATE))
+ && ((shrm_audio_rx_state == SHRM_IDLE) ||
+ (shrm_audio_rx_state == SHRM_SLEEP_STATE))
+ && ((shrm_audio_tx_state == SHRM_IDLE) ||
+ (shrm_audio_tx_state == SHRM_SLEEP_STATE))) {
+
+ shrm_common_rx_state = SHRM_SLEEP_STATE;
+ shrm_audio_rx_state = SHRM_SLEEP_STATE;
+ shrm_common_tx_state = SHRM_SLEEP_STATE;
+ shrm_audio_tx_state = SHRM_SLEEP_STATE;
+
+ queue_kthread_work(&shm_dev->shm_ac_sleep_kw,
+ &shm_dev->shm_ac_sleep_req);
+
+ }
+ spin_unlock_irqrestore(&ca_wake_req_lock, flags);
+
+ return HRTIMER_NORESTART;
+}
+
+int nl_send_multicast_message(int msg, gfp_t gfp_mask)
+{
+ struct sk_buff *skb = NULL;
+ struct nlmsghdr *nlh = NULL;
+ int err;
+
+ /* prepare netlink message */
+ skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), gfp_mask);
+ if (!skb) {
+ dev_err(shm_dev->dev, "%s:alloc_skb failed\n", __func__);
+ err = -ENOMEM;
+ goto out;
+ }
+
+ nlh = (struct nlmsghdr *)skb->data;
+ nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
+ dev_dbg(shm_dev->dev, "nlh->nlmsg_len = %d\n", nlh->nlmsg_len);
+
+ nlh->nlmsg_pid = 0; /* from kernel */
+ nlh->nlmsg_flags = 0;
+ *(int *)NLMSG_DATA(nlh) = msg;
+ skb_put(skb, MAX_PAYLOAD);
+ /* sender is in group 1<<0 */
+ NETLINK_CB(skb).pid = 0; /* from kernel */
+ /* to mcast group 1<<0 */
+ NETLINK_CB(skb).dst_group = 1;
+
+ /*multicast the message to all listening processes*/
+ err = netlink_broadcast(shrm_nl_sk, skb, 0, 1, gfp_mask);
+ dev_dbg(shm_dev->dev, "ret val from nl-multicast = %d\n", err);
+
+out:
+ return err;
+}
+
+static void nl_send_unicast_message(int dst_pid)
+{
+ struct sk_buff *skb = NULL;
+ struct nlmsghdr *nlh = NULL;
+ int err;
+ int bt_state;
+ unsigned long flags;
+
+ dev_info(shm_dev->dev, "Sending unicast message\n");
+
+ /* prepare the NL message for unicast */
+ skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_KERNEL);
+ if (!skb) {
+ dev_err(shm_dev->dev, "%s:alloc_skb failed\n", __func__);
+ return;
+ }
+
+ nlh = (struct nlmsghdr *)skb->data;
+ nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
+ dev_dbg(shm_dev->dev, "nlh->nlmsg_len = %d\n", nlh->nlmsg_len);
+
+ nlh->nlmsg_pid = 0; /* from kernel */
+ nlh->nlmsg_flags = 0;
+
+ spin_lock_irqsave(&boot_lock, flags);
+ bt_state = boot_state;
+ spin_unlock_irqrestore(&boot_lock, flags);
+
+ if (bt_state == BOOT_DONE)
+ *(int *)NLMSG_DATA(nlh) = SHRM_NL_STATUS_MOD_ONLINE;
+ else
+ *(int *)NLMSG_DATA(nlh) = SHRM_NL_STATUS_MOD_OFFLINE;
+
+ skb_put(skb, MAX_PAYLOAD);
+ /* sender is in group 1<<0 */
+ NETLINK_CB(skb).pid = 0; /* from kernel */
+ NETLINK_CB(skb).dst_group = 0;
+
+ /*unicast the message to the querying processes*/
+ err = netlink_unicast(shrm_nl_sk, skb, dst_pid, MSG_DONTWAIT);
+ dev_dbg(shm_dev->dev, "ret val from nl-unicast = %d\n", err);
+}
+
+
+static int check_modem_in_reset(void)
+{
+ u8 bt_state;
+ unsigned long flags;
+
+ spin_lock_irqsave(&boot_lock, flags);
+ bt_state = boot_state;
+ spin_unlock_irqrestore(&boot_lock, flags);
+
+#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET
+ if (bt_state != BOOT_UNKNOWN)
+ return 0;
+ else
+ return -ENODEV;
+#else
+ /*
+ * this check won't be applicable and won't work correctly
+ * if modem-silent-feature is not enabled
+ * so, simply return 0
+ */
+ return 0;
+#endif
+}
+
+void shm_ca_msgpending_0_tasklet(unsigned long tasklet_data)
+{
+ struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data;
+ u32 reader_local_rptr;
+ u32 reader_local_wptr;
+ u32 shared_rptr;
+ u32 config = 0, version = 0;
+ unsigned long flags;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ /* Interprocess locking */
+ spin_lock(&ca_common_lock);
+
+ /* Update_reader_local_wptr with shared_wptr */
+ update_ca_common_local_wptr(shrm);
+ get_reader_pointers(COMMON_CHANNEL, &reader_local_rptr,
+ &reader_local_wptr, &shared_rptr);
+
+ set_ca_msg_0_read_notif_send(0);
+
+ if (boot_state == BOOT_DONE) {
+ shrm_common_rx_state = SHRM_PTR_FREE;
+
+ if (reader_local_rptr != shared_rptr)
+ ca_msg_read_notification_0(shrm);
+ if (reader_local_rptr != reader_local_wptr)
+ receive_messages_common(shrm);
+ get_reader_pointers(COMMON_CHANNEL, &reader_local_rptr,
+ &reader_local_wptr, &shared_rptr);
+ if (reader_local_rptr == reader_local_wptr)
+ shrm_common_rx_state = SHRM_IDLE;
+ } else {
+ /* BOOT phase.only a BOOT_RESP should be in FIFO */
+ if (boot_state != BOOT_INFO_SYNC) {
+ if (!read_boot_info_req(shrm, &config, &version)) {
+ dev_err(shrm->dev,
+ "Unable to read boot state\n");
+ return;
+ }
+ /* SendReadNotification */
+ ca_msg_read_notification_0(shrm);
+ /*
+ * Check the version number before
+ * sending Boot info response
+ */
+
+ /* send MsgPending notification */
+ write_boot_info_resp(shrm, config, version);
+ spin_lock_irqsave(&boot_lock, flags);
+ boot_state = BOOT_INFO_SYNC;
+ spin_unlock_irqrestore(&boot_lock, flags);
+ dev_info(shrm->dev, "BOOT_INFO_SYNC\n");
+ queue_kthread_work(&shrm->shm_common_ch_wr_kw,
+ &shrm->send_ac_msg_pend_notify_0);
+ } else {
+ ca_msg_read_notification_0(shrm);
+ dev_info(shrm->dev,
+ "BOOT_INFO_SYNC\n");
+ }
+ }
+ /* Interprocess locking */
+ spin_unlock(&ca_common_lock);
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+void shm_ca_msgpending_1_tasklet(unsigned long tasklet_data)
+{
+ struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data;
+ u32 reader_local_rptr;
+ u32 reader_local_wptr;
+ u32 shared_rptr;
+
+ /*
+ * This function is called when CaMsgPendingNotification Trigerred
+ * by CMU. It means that CMU has wrote a message into Ca Audio FIFO
+ */
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown\n",
+ __func__);
+ return;
+ }
+
+ /* Interprocess locking */
+ spin_lock(&ca_audio_lock);
+
+ /* Update_reader_local_wptr(with shared_wptr) */
+ update_ca_audio_local_wptr(shrm);
+ get_reader_pointers(AUDIO_CHANNEL, &reader_local_rptr,
+ &reader_local_wptr, &shared_rptr);
+
+ set_ca_msg_1_read_notif_send(0);
+
+ if (boot_state != BOOT_DONE) {
+ dev_err(shrm->dev, "Boot Error\n");
+ return;
+ }
+ shrm_audio_rx_state = SHRM_PTR_FREE;
+ /* Check we already read the message */
+ if (reader_local_rptr != shared_rptr)
+ ca_msg_read_notification_1(shrm);
+ if (reader_local_rptr != reader_local_wptr)
+ receive_messages_audio(shrm);
+
+ get_reader_pointers(AUDIO_CHANNEL, &reader_local_rptr,
+ &reader_local_wptr, &shared_rptr);
+ if (reader_local_rptr == reader_local_wptr)
+ shrm_audio_rx_state = SHRM_IDLE;
+
+ /* Interprocess locking */
+ spin_unlock(&ca_audio_lock);
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+void shm_ac_read_notif_0_tasklet(unsigned long tasklet_data)
+{
+ struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data;
+ u32 writer_local_rptr;
+ u32 writer_local_wptr;
+ u32 shared_wptr;
+ unsigned long flags;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ /* Update writer_local_rptrwith shared_rptr */
+ update_ac_common_local_rptr(shrm);
+ get_writer_pointers(COMMON_CHANNEL, &writer_local_rptr,
+ &writer_local_wptr, &shared_wptr);
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown\n",
+ __func__);
+ return;
+ }
+
+ if (boot_state == BOOT_INFO_SYNC) {
+ /* BOOT_RESP sent by APE has been received by CMT */
+ spin_lock_irqsave(&boot_lock, flags);
+ boot_state = BOOT_DONE;
+ spin_unlock_irqrestore(&boot_lock, flags);
+ dev_info(shrm->dev, "IPC_ISA BOOT_DONE\n");
+
+ if (shrm->msr_flag) {
+ shrm_start_netdev(shrm->ndev);
+ shrm->msr_flag = 0;
+
+ /* multicast that modem is online */
+ nl_send_multicast_message(SHRM_NL_STATUS_MOD_ONLINE,
+ GFP_ATOMIC);
+ }
+
+ } else if (boot_state == BOOT_DONE) {
+ if (writer_local_rptr != writer_local_wptr) {
+ shrm_common_tx_state = SHRM_PTR_FREE;
+ queue_kthread_work(&shrm->shm_common_ch_wr_kw,
+ &shrm->send_ac_msg_pend_notify_0);
+ } else {
+ shrm_common_tx_state = SHRM_IDLE;
+ shrm_restart_netdev(shrm->ndev);
+ }
+ } else {
+ dev_err(shrm->dev, "Invalid boot state\n");
+ }
+ /* start timer here */
+ hrtimer_start(&timer, ktime_set(0, 25*NSEC_PER_MSEC),
+ HRTIMER_MODE_REL);
+ atomic_dec(&ac_sleep_disable_count);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+void shm_ac_read_notif_1_tasklet(unsigned long tasklet_data)
+{
+ struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data;
+ u32 writer_local_rptr;
+ u32 writer_local_wptr;
+ u32 shared_wptr;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown\n",
+ __func__);
+ return;
+ }
+
+ /* Update writer_local_rptr(with shared_rptr) */
+ update_ac_audio_local_rptr(shrm);
+ get_writer_pointers(AUDIO_CHANNEL, &writer_local_rptr,
+ &writer_local_wptr, &shared_wptr);
+ if (boot_state != BOOT_DONE) {
+ dev_err(shrm->dev, "Error Case in boot state\n");
+ return;
+ }
+ if (writer_local_rptr != writer_local_wptr) {
+ shrm_audio_tx_state = SHRM_PTR_FREE;
+ queue_kthread_work(&shrm->shm_audio_ch_wr_kw,
+ &shrm->send_ac_msg_pend_notify_1);
+ } else {
+ shrm_audio_tx_state = SHRM_IDLE;
+ }
+ /* start timer here */
+ hrtimer_start(&timer, ktime_set(0, 25*NSEC_PER_MSEC),
+ HRTIMER_MODE_REL);
+ atomic_dec(&ac_sleep_disable_count);
+ atomic_dec(&ac_msg_pend_1);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+void shm_ca_sleep_req_work(struct kthread_work *work)
+{
+ u8 bt_state;
+ unsigned long flags;
+
+ dev_dbg(shm_dev->dev, "%s:IRQ_PRCMU_CA_SLEEP\n", __func__);
+
+ spin_lock_irqsave(&boot_lock, flags);
+ bt_state = boot_state;
+ spin_unlock_irqrestore(&boot_lock, flags);
+
+ local_irq_save(flags);
+ preempt_disable();
+ if (bt_state != BOOT_DONE) {
+ dev_err(shm_dev->dev, "%s:Modem state reset or unknown\n",
+ __func__);
+ preempt_enable();
+ local_irq_restore(flags);
+ return;
+ }
+ shrm_common_rx_state = SHRM_IDLE;
+ shrm_audio_rx_state = SHRM_IDLE;
+
+ if (!get_host_accessport_val()) {
+ dev_err(shm_dev->dev, "%s: host_accessport is low\n", __func__);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ preempt_enable();
+ local_irq_restore(flags);
+ return;
+ }
+ writel((1<<GOP_CA_WAKE_ACK_BIT),
+ shm_dev->intr_base + GOP_SET_REGISTER_BASE);
+ preempt_enable();
+ local_irq_restore(flags);
+
+ hrtimer_start(&timer, ktime_set(0, 10*NSEC_PER_MSEC),
+ HRTIMER_MODE_REL);
+ suspend_unblock_sleep();
+ atomic_dec(&ac_sleep_disable_count);
+}
+
+void shm_ca_wake_req_work(struct kthread_work *work)
+{
+ unsigned long flags;
+ struct shrm_dev *shrm = container_of(work,
+ struct shrm_dev, shm_ca_wake_req);
+
+ /* initialize the FIFO Variables */
+ if (boot_state == BOOT_INIT)
+ shm_fifo_init(shrm);
+
+ mutex_lock(&ac_state_mutex);
+ modem_request(shrm->modem);
+ mutex_unlock(&ac_state_mutex);
+
+ local_irq_save(flags);
+ preempt_disable();
+ /* send ca_wake_ack_interrupt to CMU */
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown\n",
+ __func__);
+ preempt_enable();
+ local_irq_restore(flags);
+ return;
+ }
+
+ if (!get_host_accessport_val()) {
+ dev_err(shrm->dev, "%s: host_accessport is low\n", __func__);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ preempt_enable();
+ local_irq_restore(flags);
+ }
+
+ /* send ca_wake_ack_interrupt to CMU */
+ writel((1<<GOP_CA_WAKE_ACK_BIT),
+ shm_dev->intr_base + GOP_SET_REGISTER_BASE);
+ preempt_enable();
+ local_irq_restore(flags);
+}
+#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET
+static int shrm_modem_reset_sequence(void)
+{
+ int err;
+ unsigned long flags;
+
+ hrtimer_cancel(&timer);
+ hrtimer_cancel(&mod_stuck_timer_0);
+ hrtimer_cancel(&mod_stuck_timer_1);
+ hrtimer_cancel(&fifo_full_timer);
+ atomic_set(&mod_stuck, 0);
+ atomic_set(&fifo_full, 0);
+ tasklet_disable_nosync(&shm_ac_read_0_tasklet);
+ tasklet_disable_nosync(&shm_ac_read_1_tasklet);
+ tasklet_disable_nosync(&shm_ca_0_tasklet);
+ tasklet_disable_nosync(&shm_ca_1_tasklet);
+
+ /*
+ * keep the count to 0 so that we can bring down the line
+ * for normal ac-wake and ac-sleep logic
+ */
+ atomic_set(&ac_sleep_disable_count, 0);
+ atomic_set(&ac_msg_pend_1, 0);
+
+ /* workaround for MSR */
+ queue_kthread_work(&shm_dev->shm_ac_wake_kw,
+ &shm_dev->shm_ac_wake_req);
+
+ /* reset char device queues */
+ shrm_char_reset_queues(shm_dev);
+
+ /* reset protocol states */
+ shrm_common_tx_state = SHRM_SLEEP_STATE;
+ shrm_common_rx_state = SHRM_SLEEP_STATE;
+ shrm_audio_tx_state = SHRM_SLEEP_STATE;
+ shrm_audio_rx_state = SHRM_SLEEP_STATE;
+
+ /* set the msr flag */
+ shm_dev->msr_flag = 1;
+
+ /* multicast that modem is going to reset */
+ err = nl_send_multicast_message(SHRM_NL_MOD_RESET, GFP_ATOMIC);
+
+ /* reset the boot state */
+ spin_lock_irqsave(&boot_lock, flags);
+ boot_state = BOOT_INIT;
+ spin_unlock_irqrestore(&boot_lock, flags);
+
+ tasklet_enable(&shm_ac_read_0_tasklet);
+ tasklet_enable(&shm_ac_read_1_tasklet);
+ tasklet_enable(&shm_ca_0_tasklet);
+ tasklet_enable(&shm_ca_1_tasklet);
+ /* re-enable irqs */
+ enable_irq(shm_dev->ac_read_notif_0_irq);
+ enable_irq(shm_dev->ac_read_notif_1_irq);
+ enable_irq(shm_dev->ca_msg_pending_notif_0_irq);
+ enable_irq(shm_dev->ca_msg_pending_notif_1_irq);
+ enable_irq(IRQ_PRCMU_CA_WAKE);
+ enable_irq(IRQ_PRCMU_CA_SLEEP);
+
+ return err;
+}
+#endif
+
+static void shrm_modem_reset_callback(unsigned long irq)
+{
+ dev_err(shm_dev->dev, "Received mod_reset_req interrupt\n");
+
+#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET
+ {
+ int err;
+ dev_info(shm_dev->dev, "Initiating Modem silent reset\n");
+
+ err = shrm_modem_reset_sequence();
+ if (err)
+ dev_err(shm_dev->dev,
+ "Failed multicast of modem reset\n");
+ }
+#else
+ dev_info(shm_dev->dev, "Modem in reset loop, doing System reset\n");
+
+ /* Call the PRCMU reset API */
+ prcmu_system_reset(SW_RESET_NO_ARGUMENT);
+#endif
+}
+
+DECLARE_TASKLET(shrm_sw_reset_callback, shrm_modem_reset_callback,
+ IRQ_PRCMU_MODEM_SW_RESET_REQ);
+
+static irqreturn_t shrm_prcmu_irq_handler(int irq, void *data)
+{
+ struct shrm_dev *shrm = data;
+ unsigned long flags;
+
+ switch (irq) {
+ case IRQ_PRCMU_CA_WAKE:
+ suspend_block_sleep();
+ if (shrm->msr_flag)
+ atomic_set(&ac_sleep_disable_count, 0);
+ atomic_inc(&ac_sleep_disable_count);
+ queue_kthread_work(&shrm->shm_ca_wake_kw, &shrm->shm_ca_wake_req);
+ break;
+ case IRQ_PRCMU_CA_SLEEP:
+ queue_kthread_work(&shrm->shm_ca_wake_kw, &shrm->shm_ca_sleep_req);
+ break;
+ case IRQ_PRCMU_MODEM_SW_RESET_REQ:
+ /* update the boot_state */
+ spin_lock_irqsave(&boot_lock, flags);
+ boot_state = BOOT_UNKNOWN;
+
+ /*
+ * put a barrier over here to make sure boot_state is updated
+ * else, it is seen that some of already executing modem
+ * irqs or tasklets fail the protocol checks and will ultimately
+ * try to acces the modem causing system to hang.
+ * This is particularly seen with user-space initiated modem reset
+ */
+ wmb();
+ spin_unlock_irqrestore(&boot_lock, flags);
+
+ disable_irq_nosync(shrm->ac_read_notif_0_irq);
+ disable_irq_nosync(shrm->ac_read_notif_1_irq);
+ disable_irq_nosync(shrm->ca_msg_pending_notif_0_irq);
+ disable_irq_nosync(shrm->ca_msg_pending_notif_1_irq);
+ disable_irq_nosync(IRQ_PRCMU_CA_WAKE);
+ disable_irq_nosync(IRQ_PRCMU_CA_SLEEP);
+
+ /* stop network queue */
+ shrm_stop_netdev(shm_dev->ndev);
+
+ tasklet_schedule(&shrm_sw_reset_callback);
+ break;
+ default:
+ dev_err(shrm->dev, "%s: => IRQ %d\n", __func__, irq);
+ return IRQ_NONE;
+ }
+ return IRQ_HANDLED;
+}
+
+static void send_ac_msg_pend_notify_0_work(struct kthread_work *work)
+{
+ unsigned long flags;
+ struct shrm_dev *shrm = container_of(work, struct shrm_dev,
+ send_ac_msg_pend_notify_0);
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ update_ac_common_shared_wptr(shrm);
+
+ mutex_lock(&ac_state_mutex);
+ atomic_inc(&ac_sleep_disable_count);
+ modem_request(shrm->modem);
+ mutex_unlock(&ac_state_mutex);
+
+ spin_lock_irqsave(&start_timer_lock, flags);
+ if (!get_host_accessport_val()) {
+ dev_err(shrm->dev, "%s: host_accessport is low\n", __func__);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ spin_unlock_irqrestore(&start_timer_lock, flags);
+ return;
+ }
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ spin_unlock_irqrestore(&start_timer_lock, flags);
+ return;
+ }
+
+ /* Trigger AcMsgPendingNotification to CMU */
+ writel((1<<GOP_COMMON_AC_MSG_PENDING_NOTIFICATION_BIT),
+ shrm->intr_base + GOP_SET_REGISTER_BASE);
+
+ /* timer to detect modem stuck or hang */
+ hrtimer_start(&mod_stuck_timer_0, ktime_set(MOD_STUCK_TIMEOUT, 0),
+ HRTIMER_MODE_REL);
+ spin_unlock_irqrestore(&start_timer_lock, flags);
+ if (shrm_common_tx_state == SHRM_PTR_FREE)
+ shrm_common_tx_state = SHRM_PTR_BUSY;
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+static void send_ac_msg_pend_notify_1_work(struct kthread_work *work)
+{
+ unsigned long flags;
+ struct shrm_dev *shrm = container_of(work, struct shrm_dev,
+ send_ac_msg_pend_notify_1);
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ /* Update shared_wptr with writer_local_wptr) */
+ update_ac_audio_shared_wptr(shrm);
+
+ mutex_lock(&ac_state_mutex);
+ if (!atomic_read(&ac_msg_pend_1)) {
+ atomic_inc(&ac_sleep_disable_count);
+ atomic_inc(&ac_msg_pend_1);
+ }
+ modem_request(shrm->modem);
+ mutex_unlock(&ac_state_mutex);
+
+ spin_lock_irqsave(&start_timer_lock, flags);
+ if (!get_host_accessport_val()) {
+ dev_err(shrm->dev, "%s: host_accessport is low\n", __func__);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ spin_unlock_irqrestore(&start_timer_lock, flags);
+ return;
+ }
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ spin_unlock_irqrestore(&start_timer_lock, flags);
+ return;
+ }
+
+ /* Trigger AcMsgPendingNotification to CMU */
+ writel((1<<GOP_AUDIO_AC_MSG_PENDING_NOTIFICATION_BIT),
+ shrm->intr_base + GOP_SET_REGISTER_BASE);
+
+ /* timer to detect modem stuck or hang */
+ hrtimer_start(&mod_stuck_timer_1, ktime_set(MOD_STUCK_TIMEOUT, 0),
+ HRTIMER_MODE_REL);
+ spin_unlock_irqrestore(&start_timer_lock, flags);
+ if (shrm_audio_tx_state == SHRM_PTR_FREE)
+ shrm_audio_tx_state = SHRM_PTR_BUSY;
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+void shm_nl_receive(struct sk_buff *skb)
+{
+ struct nlmsghdr *nlh = NULL;
+ int msg;
+
+ dev_dbg(shm_dev->dev, "Received NL msg from user-space\n");
+
+ nlh = (struct nlmsghdr *)skb->data;
+ msg = *((int *)(NLMSG_DATA(nlh)));
+ switch (msg) {
+ case SHRM_NL_MOD_QUERY_STATE:
+ dev_info(shm_dev->dev, "mod-query-state from user-space\n");
+ nl_send_unicast_message(nlh->nlmsg_pid);
+ break;
+
+ case SHRM_NL_USER_MOD_RESET:
+ dev_info(shm_dev->dev, "user-space inited mod-reset-req\n");
+ dev_info(shm_dev->dev, "PCRMU resets modem\n");
+ if (atomic_read(&mod_stuck) || atomic_read(&fifo_full)) {
+ dev_info(shm_dev->dev,
+ "Modem reset already in progress\n");
+ break;
+ }
+ atomic_set(&mod_stuck, 1);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ break;
+
+ default:
+ dev_err(shm_dev->dev, "Invalid NL msg from user-space\n");
+ break;
+ };
+}
+
+int shrm_protocol_init(struct shrm_dev *shrm,
+ received_msg_handler common_rx_handler,
+ received_msg_handler audio_rx_handler)
+{
+ int err;
+ struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 };
+
+ shm_dev = shrm;
+ boot_state = BOOT_INIT;
+ dev_info(shrm->dev, "IPC_ISA BOOT_INIT\n");
+ rx_common_handler = common_rx_handler;
+ rx_audio_handler = audio_rx_handler;
+ atomic_set(&ac_sleep_disable_count, 0);
+
+ hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ timer.function = callback;
+ hrtimer_init(&mod_stuck_timer_0, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ mod_stuck_timer_0.function = shm_mod_stuck_timeout;
+ hrtimer_init(&mod_stuck_timer_1, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ mod_stuck_timer_1.function = shm_mod_stuck_timeout;
+ hrtimer_init(&fifo_full_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ fifo_full_timer.function = shm_fifo_full_timeout;
+
+ init_kthread_worker(&shrm->shm_common_ch_wr_kw);
+ shrm->shm_common_ch_wr_kw_task = kthread_run(kthread_worker_fn,
+ &shrm->shm_common_ch_wr_kw,
+ "shm_common_channel_irq");
+ if (IS_ERR(shrm->shm_common_ch_wr_kw_task)) {
+ dev_err(shrm->dev, "failed to create work task\n");
+ return -ENOMEM;
+ }
+
+ init_kthread_worker(&shrm->shm_audio_ch_wr_kw);
+ shrm->shm_audio_ch_wr_kw_task = kthread_run(kthread_worker_fn,
+ &shrm->shm_audio_ch_wr_kw,
+ "shm_audio_channel_irq");
+ if (IS_ERR(shrm->shm_audio_ch_wr_kw_task)) {
+ dev_err(shrm->dev, "failed to create work task\n");
+ err = -ENOMEM;
+ goto free_kw1;
+ }
+ /* must use the FIFO scheduler as it is realtime sensitive */
+ sched_setscheduler(shrm->shm_audio_ch_wr_kw_task, SCHED_FIFO, &param);
+
+ init_kthread_worker(&shrm->shm_ac_wake_kw);
+ shrm->shm_ac_wake_kw_task = kthread_run(kthread_worker_fn,
+ &shrm->shm_ac_wake_kw,
+ "shm_ac_wake_req");
+ if (IS_ERR(shrm->shm_ac_wake_kw_task)) {
+ dev_err(shrm->dev, "failed to create work task\n");
+ err = -ENOMEM;
+ goto free_kw2;
+ }
+ /* must use the FIFO scheduler as it is realtime sensitive */
+ sched_setscheduler(shrm->shm_ac_wake_kw_task, SCHED_FIFO, &param);
+
+ init_kthread_worker(&shrm->shm_ca_wake_kw);
+ shrm->shm_ca_wake_kw_task = kthread_run(kthread_worker_fn,
+ &shrm->shm_ca_wake_kw,
+ "shm_ca_wake_req");
+ if (IS_ERR(shrm->shm_ca_wake_kw_task)) {
+ dev_err(shrm->dev, "failed to create work task\n");
+ err = -ENOMEM;
+ goto free_kw3;
+ }
+ /* must use the FIFO scheduler as it is realtime sensitive */
+ sched_setscheduler(shrm->shm_ca_wake_kw_task, SCHED_FIFO, &param);
+
+ init_kthread_worker(&shrm->shm_ac_sleep_kw);
+ shrm->shm_ac_sleep_kw_task = kthread_run(kthread_worker_fn,
+ &shrm->shm_ac_sleep_kw,
+ "shm_ac_sleep_req");
+ if (IS_ERR(shrm->shm_ac_sleep_kw_task)) {
+ dev_err(shrm->dev, "failed to create work task\n");
+ err = -ENOMEM;
+ goto free_kw4;
+ }
+ init_kthread_worker(&shrm->shm_mod_stuck_kw);
+ shrm->shm_mod_stuck_kw_task = kthread_run(kthread_worker_fn,
+ &shrm->shm_mod_stuck_kw,
+ "shm_mod_reset_req");
+ if (IS_ERR(shrm->shm_mod_stuck_kw_task)) {
+ dev_err(shrm->dev, "failed to create work task\n");
+ err = -ENOMEM;
+ goto free_kw5;
+ }
+
+ init_kthread_work(&shrm->send_ac_msg_pend_notify_0,
+ send_ac_msg_pend_notify_0_work);
+ init_kthread_work(&shrm->send_ac_msg_pend_notify_1,
+ send_ac_msg_pend_notify_1_work);
+ init_kthread_work(&shrm->shm_ca_wake_req, shm_ca_wake_req_work);
+ init_kthread_work(&shrm->shm_ca_sleep_req, shm_ca_sleep_req_work);
+ init_kthread_work(&shrm->shm_ac_sleep_req, shm_ac_sleep_req_work);
+ init_kthread_work(&shrm->shm_ac_wake_req, shm_ac_wake_req_work);
+ init_kthread_work(&shrm->shm_mod_reset_req, shm_mod_reset_req_work);
+ init_kthread_work(&shrm->shm_print_dbg_info, shm_print_dbg_info_work);
+
+ /* set tasklet data */
+ shm_ca_0_tasklet.data = (unsigned long)shrm;
+ shm_ca_1_tasklet.data = (unsigned long)shrm;
+
+ err = request_irq(IRQ_PRCMU_CA_SLEEP, shrm_prcmu_irq_handler,
+ IRQF_NO_SUSPEND, "ca-sleep", shrm);
+ if (err < 0) {
+ dev_err(shm_dev->dev, "Failed alloc IRQ_PRCMU_CA_SLEEP.\n");
+ goto free_kw6;
+ }
+
+ err = request_irq(IRQ_PRCMU_CA_WAKE, shrm_prcmu_irq_handler,
+ IRQF_NO_SUSPEND, "ca-wake", shrm);
+ if (err < 0) {
+ dev_err(shm_dev->dev, "Failed alloc IRQ_PRCMU_CA_WAKE.\n");
+ goto drop2;
+ }
+
+ err = request_irq(IRQ_PRCMU_MODEM_SW_RESET_REQ, shrm_prcmu_irq_handler,
+ IRQF_NO_SUSPEND, "modem-sw-reset-req", shrm);
+ if (err < 0) {
+ dev_err(shm_dev->dev,
+ "Failed alloc IRQ_PRCMU_MODEM_SW_RESET_REQ.\n");
+ goto drop1;
+ }
+
+#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET
+ /* init netlink socket for user-space communication */
+ shrm_nl_sk = netlink_kernel_create(NULL, NETLINK_SHRM, 1,
+ shm_nl_receive, NULL, THIS_MODULE);
+
+ if (!shrm_nl_sk) {
+ dev_err(shm_dev->dev, "netlink socket creation failed\n");
+ goto drop;
+ }
+#endif
+ return 0;
+
+#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET
+drop:
+ free_irq(IRQ_PRCMU_MODEM_SW_RESET_REQ, NULL);
+#endif
+drop1:
+ free_irq(IRQ_PRCMU_CA_WAKE, NULL);
+drop2:
+ free_irq(IRQ_PRCMU_CA_SLEEP, NULL);
+free_kw6:
+ kthread_stop(shrm->shm_mod_stuck_kw_task);
+free_kw5:
+ kthread_stop(shrm->shm_ac_sleep_kw_task);
+free_kw4:
+ kthread_stop(shrm->shm_ca_wake_kw_task);
+free_kw3:
+ kthread_stop(shrm->shm_ac_wake_kw_task);
+free_kw2:
+ kthread_stop(shrm->shm_audio_ch_wr_kw_task);
+free_kw1:
+ kthread_stop(shrm->shm_common_ch_wr_kw_task);
+ return err;
+}
+
+void shrm_protocol_deinit(struct shrm_dev *shrm)
+{
+ free_irq(IRQ_PRCMU_CA_SLEEP, NULL);
+ free_irq(IRQ_PRCMU_CA_WAKE, NULL);
+ free_irq(IRQ_PRCMU_MODEM_SW_RESET_REQ, NULL);
+ flush_kthread_worker(&shrm->shm_common_ch_wr_kw);
+ flush_kthread_worker(&shrm->shm_audio_ch_wr_kw);
+ flush_kthread_worker(&shrm->shm_ac_wake_kw);
+ flush_kthread_worker(&shrm->shm_ca_wake_kw);
+ flush_kthread_worker(&shrm->shm_ac_sleep_kw);
+ flush_kthread_worker(&shrm->shm_mod_stuck_kw);
+ kthread_stop(shrm->shm_common_ch_wr_kw_task);
+ kthread_stop(shrm->shm_audio_ch_wr_kw_task);
+ kthread_stop(shrm->shm_ac_wake_kw_task);
+ kthread_stop(shrm->shm_ca_wake_kw_task);
+ kthread_stop(shrm->shm_ac_sleep_kw_task);
+ kthread_stop(shrm->shm_mod_stuck_kw_task);
+ modem_put(shrm->modem);
+}
+
+int get_ca_wake_req_state(void)
+{
+ return ((atomic_read(&ac_sleep_disable_count) > 0) ||
+ modem_get_usage(shm_dev->modem));
+}
+
+irqreturn_t ca_wake_irq_handler(int irq, void *ctrlr)
+{
+ struct shrm_dev *shrm = ctrlr;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ /* initialize the FIFO Variables */
+ if (boot_state == BOOT_INIT)
+ shm_fifo_init(shrm);
+
+ dev_dbg(shrm->dev, "Inside ca_wake_irq_handler\n");
+
+ /* Clear the interrupt */
+ writel((1 << GOP_CA_WAKE_REQ_BIT),
+ shrm->intr_base + GOP_CLEAR_REGISTER_BASE);
+
+ /* send ca_wake_ack_interrupt to CMU */
+ writel((1 << GOP_CA_WAKE_ACK_BIT),
+ shrm->intr_base + GOP_SET_REGISTER_BASE);
+
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return IRQ_HANDLED;
+}
+
+
+irqreturn_t ac_read_notif_0_irq_handler(int irq, void *ctrlr)
+{
+ unsigned long flags;
+ struct shrm_dev *shrm = ctrlr;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+ /* Cancel the modem stuck timer */
+ spin_lock_irqsave(&start_timer_lock, flags);
+ hrtimer_cancel(&mod_stuck_timer_0);
+ spin_unlock_irqrestore(&start_timer_lock, flags);
+ if (atomic_read(&fifo_full)) {
+ atomic_set(&fifo_full, 0);
+ hrtimer_cancel(&fifo_full_timer);
+ }
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ return IRQ_HANDLED;
+ }
+
+ shm_ac_read_0_tasklet.data = (unsigned long)shrm;
+ tasklet_schedule(&shm_ac_read_0_tasklet);
+
+ local_irq_save(flags);
+ preempt_disable();
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ preempt_enable();
+ local_irq_restore(flags);
+ return IRQ_HANDLED;
+ }
+
+ if (!get_host_accessport_val()) {
+ dev_err(shrm->dev, "%s: host_accessport is low\n", __func__);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ preempt_enable();
+ local_irq_restore(flags);
+ return IRQ_HANDLED;
+ }
+ /* Clear the interrupt */
+ writel((1 << GOP_COMMON_AC_READ_NOTIFICATION_BIT),
+ shrm->intr_base + GOP_CLEAR_REGISTER_BASE);
+ preempt_enable();
+ local_irq_restore(flags);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return IRQ_HANDLED;
+}
+
+irqreturn_t ac_read_notif_1_irq_handler(int irq, void *ctrlr)
+{
+ unsigned long flags;
+ struct shrm_dev *shrm = ctrlr;
+
+ dev_dbg(shrm->dev, "%s IN+\n", __func__);
+ /* Cancel the modem stuck timer */
+ spin_lock_irqsave(&start_timer_lock, flags);
+ hrtimer_cancel(&mod_stuck_timer_1);
+ spin_unlock_irqrestore(&start_timer_lock, flags);
+ if (atomic_read(&fifo_full)) {
+ atomic_set(&fifo_full, 0);
+ hrtimer_cancel(&fifo_full_timer);
+ }
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ return IRQ_HANDLED;
+ }
+
+ shm_ac_read_1_tasklet.data = (unsigned long)shrm;
+ tasklet_schedule(&shm_ac_read_1_tasklet);
+
+ local_irq_save(flags);
+ preempt_disable();
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ preempt_enable();
+ local_irq_restore(flags);
+ return IRQ_HANDLED;
+ }
+
+ if (!get_host_accessport_val()) {
+ dev_err(shrm->dev, "%s: host_accessport is low\n", __func__);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ preempt_enable();
+ local_irq_restore(flags);
+ return IRQ_HANDLED;
+ }
+ /* Clear the interrupt */
+ writel((1 << GOP_AUDIO_AC_READ_NOTIFICATION_BIT),
+ shrm->intr_base + GOP_CLEAR_REGISTER_BASE);
+ preempt_enable();
+ local_irq_restore(flags);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return IRQ_HANDLED;
+}
+
+irqreturn_t ca_msg_pending_notif_0_irq_handler(int irq, void *ctrlr)
+{
+ unsigned long flags;
+ struct shrm_dev *shrm = ctrlr;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ return IRQ_HANDLED;
+ }
+
+ tasklet_schedule(&shm_ca_0_tasklet);
+
+ local_irq_save(flags);
+ preempt_disable();
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ preempt_enable();
+ local_irq_restore(flags);
+ return IRQ_HANDLED;
+ }
+
+ if (!get_host_accessport_val()) {
+ dev_err(shrm->dev, "%s: host_accessport is low\n", __func__);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ preempt_enable();
+ local_irq_restore(flags);
+ return IRQ_HANDLED;
+ }
+ /* Clear the interrupt */
+ writel((1 << GOP_COMMON_CA_MSG_PENDING_NOTIFICATION_BIT),
+ shrm->intr_base + GOP_CLEAR_REGISTER_BASE);
+ preempt_enable();
+ local_irq_restore(flags);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return IRQ_HANDLED;
+}
+
+irqreturn_t ca_msg_pending_notif_1_irq_handler(int irq, void *ctrlr)
+{
+ unsigned long flags;
+ struct shrm_dev *shrm = ctrlr;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ return IRQ_HANDLED;
+ }
+
+ tasklet_schedule(&shm_ca_1_tasklet);
+
+ local_irq_save(flags);
+ preempt_disable();
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ preempt_enable();
+ local_irq_restore(flags);
+ return IRQ_HANDLED;
+ }
+
+ if (!get_host_accessport_val()) {
+ dev_err(shrm->dev, "%s: host_accessport is low\n", __func__);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ preempt_enable();
+ local_irq_restore(flags);
+ return IRQ_HANDLED;
+ }
+ /* Clear the interrupt */
+ writel((1<<GOP_AUDIO_CA_MSG_PENDING_NOTIFICATION_BIT),
+ shrm->intr_base+GOP_CLEAR_REGISTER_BASE);
+ preempt_enable();
+ local_irq_restore(flags);
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return IRQ_HANDLED;
+
+}
+
+/**
+ * shm_write_msg() - write message to shared memory
+ * @shrm: pointer to the shrm device information structure
+ * @l2_header: L2 header
+ * @addr: pointer to the message
+ * @length: length of the message to be written
+ *
+ * This function is called from net or char interface driver write operation.
+ * Prior to calling this function the message is copied from the user space
+ * buffer to the kernel buffer. This function based on the l2 header routes
+ * the message to the respective channel and FIFO. Then makes a call to the
+ * fifo write function where the message is written to the physical device.
+ */
+int shm_write_msg(struct shrm_dev *shrm, u8 l2_header,
+ void *addr, u32 length)
+{
+ u8 channel = 0;
+ int ret;
+
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (boot_state != BOOT_DONE) {
+ dev_err(shrm->dev,
+ "error:after boot done call this fn, L2Header = %d\n",
+ l2_header);
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if ((l2_header == L2_HEADER_ISI) ||
+ (l2_header == L2_HEADER_RPC) ||
+ (l2_header == L2_HEADER_SECURITY) ||
+ (l2_header == L2_HEADER_COMMON_SIMPLE_LOOPBACK) ||
+ (l2_header == L2_HEADER_COMMON_ADVANCED_LOOPBACK) ||
+ (l2_header == L2_HEADER_CIQ) ||
+ (l2_header == L2_HEADER_RTC_CALIBRATION)) {
+ channel = 0;
+ if (shrm_common_tx_state == SHRM_SLEEP_STATE)
+ shrm_common_tx_state = SHRM_PTR_FREE;
+ else if (shrm_common_tx_state == SHRM_IDLE)
+ shrm_common_tx_state = SHRM_PTR_FREE;
+
+ } else if ((l2_header == L2_HEADER_AUDIO) ||
+ (l2_header == L2_HEADER_AUDIO_SIMPLE_LOOPBACK) ||
+ (l2_header == L2_HEADER_AUDIO_ADVANCED_LOOPBACK)) {
+ if (shrm_audio_tx_state == SHRM_SLEEP_STATE)
+ shrm_audio_tx_state = SHRM_PTR_FREE;
+ else if (shrm_audio_tx_state == SHRM_IDLE)
+ shrm_audio_tx_state = SHRM_PTR_FREE;
+
+ channel = 1;
+ } else {
+ ret = -ENODEV;
+ goto out;
+ }
+ ret = shm_write_msg_to_fifo(shrm, channel, l2_header, addr, length);
+ if (ret < 0) {
+ dev_err(shrm->dev, "write message to fifo failed\n");
+ if (ret == -EAGAIN) {
+ if (!atomic_read(&fifo_full)) {
+ /* Start a timer so as to handle this gently */
+ atomic_set(&fifo_full, 1);
+ hrtimer_start(&fifo_full_timer, ktime_set(
+ FIFO_FULL_TIMEOUT, 0),
+ HRTIMER_MODE_REL);
+ }
+ }
+ return ret;
+ }
+ /*
+ * notify only if new msg copied is the only unread one
+ * otherwise it means that reading process is ongoing
+ */
+ if (is_the_only_one_unread_message(shrm, channel, length)) {
+
+ /* Send Message Pending Noitication to CMT */
+ if (channel == 0)
+ queue_kthread_work(&shrm->shm_common_ch_wr_kw,
+ &shrm->send_ac_msg_pend_notify_0);
+ else
+ queue_kthread_work(&shrm->shm_audio_ch_wr_kw,
+ &shrm->send_ac_msg_pend_notify_1);
+
+ }
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+ return 0;
+
+out:
+ return ret;
+}
+
+void ca_msg_read_notification_0(struct shrm_dev *shrm)
+{
+ unsigned long flags;
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (get_ca_msg_0_read_notif_send() == 0) {
+ update_ca_common_shared_rptr(shrm);
+
+ local_irq_save(flags);
+ preempt_disable();
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ preempt_enable();
+ local_irq_restore(flags);
+ return;
+ }
+
+ if (!get_host_accessport_val()) {
+ dev_err(shrm->dev, "%s: host_accessport is low\n",
+ __func__);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ preempt_enable();
+ local_irq_restore(flags);
+ return;
+ }
+ /* Trigger CaMsgReadNotification to CMU */
+ writel((1 << GOP_COMMON_CA_READ_NOTIFICATION_BIT),
+ shrm->intr_base + GOP_SET_REGISTER_BASE);
+ preempt_enable();
+ local_irq_restore(flags);
+ set_ca_msg_0_read_notif_send(1);
+ shrm_common_rx_state = SHRM_PTR_BUSY;
+ }
+
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+void ca_msg_read_notification_1(struct shrm_dev *shrm)
+{
+ unsigned long flags;
+ dev_dbg(shrm->dev, "%s IN\n", __func__);
+
+ if (get_ca_msg_1_read_notif_send() == 0) {
+ update_ca_audio_shared_rptr(shrm);
+
+ local_irq_save(flags);
+ preempt_disable();
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ preempt_enable();
+ local_irq_restore(flags);
+ return;
+ }
+
+ if (!get_host_accessport_val()) {
+ dev_err(shrm->dev, "%s: host_accessport is low\n",
+ __func__);
+ queue_kthread_work(&shm_dev->shm_mod_stuck_kw,
+ &shm_dev->shm_mod_reset_req);
+ preempt_enable();
+ local_irq_restore(flags);
+ return;
+ }
+ /* Trigger CaMsgReadNotification to CMU */
+ writel((1<<GOP_AUDIO_CA_READ_NOTIFICATION_BIT),
+ shrm->intr_base+GOP_SET_REGISTER_BASE);
+ preempt_enable();
+ local_irq_restore(flags);
+ set_ca_msg_1_read_notif_send(1);
+ shrm_audio_rx_state = SHRM_PTR_BUSY;
+ }
+ dev_dbg(shrm->dev, "%s OUT\n", __func__);
+}
+
+/**
+ * receive_messages_common - receive common channnel msg from
+ * CMT(Cellular Mobile Terminal)
+ * @shrm: pointer to shrm device information structure
+ *
+ * The messages sent from CMT to APE are written to the respective FIFO
+ * and an interrupt is triggered by the CMT. This ca message pending
+ * interrupt calls this function. This function sends a read notification
+ * acknowledgement to the CMT and calls the common channel receive handler
+ * where the messsage is copied to the respective(ISI, RPC, SECURIT) queue
+ * based on the message l2 header.
+ */
+void receive_messages_common(struct shrm_dev *shrm)
+{
+ u8 l2_header;
+ u32 len;
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ return;
+ }
+
+ l2_header = read_one_l2msg_common(shrm, recieve_common_msg, &len);
+ /* Send Recieve_Call_back to Upper Layer */
+ if (!rx_common_handler) {
+ dev_err(shrm->dev, "common_rx_handler is Null\n");
+ BUG();
+ }
+ (*rx_common_handler)(l2_header, &recieve_common_msg, len,
+ shrm);
+ /* SendReadNotification */
+ ca_msg_read_notification_0(shrm);
+
+ while (read_remaining_messages_common()) {
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ return;
+ }
+
+ l2_header = read_one_l2msg_common(shrm, recieve_common_msg,
+ &len);
+ /* Send Recieve_Call_back to Upper Layer */
+ (*rx_common_handler)(l2_header,
+ &recieve_common_msg, len,
+ shrm);
+ }
+}
+
+/**
+ * receive_messages_audio() - receive audio message from CMT
+ * @shrm: pointer to shrm device information structure
+ *
+ * The messages sent from CMT to APE are written to the respective FIFO
+ * and an interrupt is triggered by the CMT. This ca message pending
+ * interrupt calls this function. This function sends a read notification
+ * acknowledgement to the CMT and calls the common channel receive handler
+ * where the messsage is copied to the audio queue.
+ */
+void receive_messages_audio(struct shrm_dev *shrm)
+{
+ u8 l2_header;
+ u32 len;
+
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ return;
+ }
+
+ l2_header = read_one_l2msg_audio(shrm, recieve_audio_msg, &len);
+ /* Send Recieve_Call_back to Upper Layer */
+
+ if (!rx_audio_handler) {
+ dev_crit(shrm->dev, "audio_rx_handler is Null\n");
+ BUG();
+ }
+ (*rx_audio_handler)(l2_header, &recieve_audio_msg,
+ len, shrm);
+
+ /* SendReadNotification */
+ ca_msg_read_notification_1(shrm);
+ while (read_remaining_messages_audio()) {
+ if (check_modem_in_reset()) {
+ dev_err(shrm->dev, "%s:Modem state reset or unknown.\n",
+ __func__);
+ return;
+ }
+
+ l2_header = read_one_l2msg_audio(shrm,
+ recieve_audio_msg, &len);
+ /* Send Recieve_Call_back to Upper Layer */
+ (*rx_audio_handler)(l2_header,
+ &recieve_audio_msg, len,
+ shrm);
+ }
+}
+
+u8 get_boot_state()
+{
+ return boot_state;
+}
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index a6b8ce11a22..a0f2484368b 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -70,3 +70,8 @@ obj-$(CONFIG_USB_IPHETH) += usb/
obj-$(CONFIG_USB_CDC_PHONET) += usb/
obj-$(CONFIG_HYPERV_NET) += hyperv/
+
+ifdef CONFIG_PHONET
+obj-$(CONFIG_U8500_SHRM) += u8500_shrm.o
+obj-$(CONFIG_MODEM_M6718_SPI) += m6718_modem_net.o
+endif
diff --git a/drivers/net/m6718_modem_net.c b/drivers/net/m6718_modem_net.c
new file mode 100644
index 00000000000..f64a775560b
--- /dev/null
+++ b/drivers/net/m6718_modem_net.c
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
+ * based on u8500_shrm.c
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ *
+ * M6718 modem net device interface.
+ */
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <linux/phonet.h>
+#include <linux/if_phonet.h>
+#include <linux/if_arp.h>
+#include <net/sock.h>
+#include <net/phonet/phonet.h>
+#include <net/phonet/pep.h>
+#include <linux/modem/m6718_spi/modem_net.h>
+#include <linux/modem/m6718_spi/modem_driver.h>
+#include <linux/modem/m6718_spi/modem_char.h>
+#include <linux/ratelimit.h>
+
+
+/**
+ * modem_net_receive() - receive data and copy to user space buffer
+ * @dev: pointer to the network device structure
+ *
+ * Copy data from ISI queue to the user space buffer.
+ */
+int modem_net_receive(struct net_device *dev)
+{
+ struct sk_buff *skb;
+ struct isa_device_context *isadev;
+ struct message_queue *q;
+ u32 msgsize;
+ u32 size = 0;
+ struct modem_spi_net_dev *net_iface_priv =
+ (struct modem_spi_net_dev *)netdev_priv(dev);
+ struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev;
+
+ isadev = &modem_spi_dev->isa_context->isadev[MODEM_M6718_SPI_CHN_ISI];
+ q = &isadev->dl_queue;
+
+ spin_lock_bh(&q->update_lock);
+ if (list_empty(&q->msg_list)) {
+ spin_unlock_bh(&q->update_lock);
+ dev_dbg(modem_spi_dev->dev, "empty queue!\n");
+ return 0;
+ }
+ spin_unlock_bh(&q->update_lock);
+
+ msgsize = modem_isa_msg_size(q);
+ if (msgsize <= 0)
+ return msgsize;
+
+ /*
+ * The packet has been retrieved from the transmission
+ * medium. Build an skb around it, so upper layers can handle it
+ */
+ skb = dev_alloc_skb(msgsize);
+ if (!skb) {
+ pr_notice_ratelimited("isa rx: low on mem - packet dropped\n");
+ dev->stats.rx_dropped++;
+ return -ENOMEM;
+ }
+
+ if ((q->readptr + msgsize) >= q->size) {
+ size = (q->size - q->readptr);
+ /* copy first part of msg */
+ skb_copy_to_linear_data(skb,
+ (u8 *)(q->fifo_base + q->readptr), size);
+ skb_put(skb, size);
+
+ /* copy second part of msg at the top of fifo */
+ skb_copy_to_linear_data_offset(skb, size,
+ (u8 *)(q->fifo_base), (msgsize - size));
+ skb_put(skb, msgsize - size);
+
+ } else {
+ skb_copy_to_linear_data(skb,
+ (u8 *)(q->fifo_base + q->readptr), msgsize);
+ skb_put(skb, msgsize);
+ }
+
+ spin_lock_bh(&q->update_lock);
+ modem_isa_unqueue_msg(q);
+ spin_unlock_bh(&q->update_lock);
+
+ skb_reset_mac_header(skb);
+ __skb_pull(skb, dev->hard_header_len);
+ /* write metadata and then pass to the receive level */
+ skb->dev = dev;
+ skb->protocol = htons(ETH_P_PHONET);
+ skb->priority = 0;
+ skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
+ if (likely(netif_rx_ni(skb) == NET_RX_SUCCESS)) {
+ dev->stats.rx_packets++;
+ dev->stats.rx_bytes += msgsize;
+ } else {
+ dev->stats.rx_dropped++;
+ }
+
+ return msgsize;
+}
+EXPORT_SYMBOL_GPL(modem_net_receive);
+
+static int netdev_isa_open(struct net_device *dev)
+{
+ struct modem_spi_net_dev *net_iface_priv =
+ (struct modem_spi_net_dev *)netdev_priv(dev);
+ struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev;
+
+ modem_spi_dev->netdev_flag_up = 1;
+ if (!netif_carrier_ok(dev))
+ netif_carrier_on(dev);
+ netif_wake_queue(dev);
+ return 0;
+}
+
+static int netdev_isa_close(struct net_device *dev)
+{
+ struct modem_spi_net_dev *net_iface_priv =
+ (struct modem_spi_net_dev *)netdev_priv(dev);
+ struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev;
+
+ modem_spi_dev->netdev_flag_up = 0;
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+ return 0;
+}
+
+static int netdev_isa_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct if_phonet_req *req = (struct if_phonet_req *)ifr;
+
+ switch (cmd) {
+ case SIOCPNGAUTOCONF:
+ req->ifr_phonet_autoconf.device = PN_DEV_HOST;
+ return 0;
+ }
+ return -ENOIOCTLCMD;
+}
+
+static struct net_device_stats *netdev_isa_stats(struct net_device *dev)
+{
+ return &dev->stats;
+}
+
+/**
+ * netdev_isa_write() - write through the net interface
+ * @skb: pointer to the socket buffer
+ * @dev: pointer to the network device structure
+ *
+ * Copies data(ISI message) from the user buffer to the kernel buffer and
+ * schedule transfer thread to transmit the message to the modem via FIFO.
+ */
+static netdev_tx_t netdev_isa_write(struct sk_buff *skb, struct net_device *dev)
+{
+ int err;
+ int retval = 0;
+ struct modem_spi_net_dev *net_iface_priv =
+ (struct modem_spi_net_dev *)netdev_priv(dev);
+ struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev;
+
+ /*
+ * FIXME:
+ * U8500 modem requires that Pipe created/enabled Indication should
+ * be sent from the port corresponding to GPRS socket.
+ * Also, the U8500 modem does not implement Pipe controller
+ * which takes care of port manipulations for GPRS traffic.
+ *
+ * Now, APE has GPRS socket and the socket for sending
+ * Indication msgs bound to different ports.
+ * Phonet stack does not allow an indication msg to be sent
+ * from GPRS socket, since Phonet stack assumes the presence
+ * of Pipe controller in modem.
+ *
+ * So, due to lack of Pipe controller implementation in the
+ * U8500 modem, carry out the port manipulation related to
+ * GPRS traffic here.
+ * Ideally, it should be done either by Pipe controller in
+ * modem OR some implementation of Pipe controller on APE side
+ */
+ if (skb->data[RESOURCE_ID_INDEX] == PN_PIPE) {
+ if ((skb->data[MSG_ID_INDEX] == PNS_PIPE_CREATED_IND) ||
+ (skb->data[MSG_ID_INDEX] == PNS_PIPE_ENABLED_IND) ||
+ (skb->data[MSG_ID_INDEX] == PNS_PIPE_DISABLED_IND))
+ skb->data[SRC_OBJ_INDEX] = skb->data[PIPE_HDL_INDEX];
+ }
+
+ spin_lock_bh(&modem_spi_dev->isa_context->common_tx_lock);
+ err = modem_m6718_spi_send(modem_spi_dev, MODEM_M6718_SPI_CHN_ISI,
+ skb->len, skb->data);
+ if (!err) {
+ dev->stats.tx_packets++;
+ dev->stats.tx_bytes += skb->len;
+ retval = NETDEV_TX_OK;
+ dev_kfree_skb(skb);
+ } else {
+ dev->stats.tx_dropped++;
+ retval = NETDEV_TX_BUSY;
+ }
+ spin_unlock_bh(&modem_spi_dev->isa_context->common_tx_lock);
+
+ return retval;
+}
+
+static const struct net_device_ops modem_netdev_ops = {
+ .ndo_open = netdev_isa_open,
+ .ndo_stop = netdev_isa_close,
+ .ndo_do_ioctl = netdev_isa_ioctl,
+ .ndo_start_xmit = netdev_isa_write,
+ .ndo_get_stats = netdev_isa_stats,
+};
+
+static void net_device_init(struct net_device *dev)
+{
+ struct modem_spi_net_dev *net_iface_priv;
+
+ dev->netdev_ops = &modem_netdev_ops;
+ dev->header_ops = &phonet_header_ops;
+ dev->type = ARPHRD_PHONET;
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ dev->mtu = PHONET_MAX_MTU;
+ dev->hard_header_len = MODEM_HLEN;
+ dev->addr_len = PHONET_ALEN;
+ dev->tx_queue_len = PN_TX_QUEUE_LEN;
+ dev->destructor = free_netdev;
+ dev->dev_addr[0] = PN_LINK_ADDR;
+ net_iface_priv = netdev_priv(dev);
+ memset(net_iface_priv, 0 , sizeof(struct modem_spi_net_dev));
+}
+
+int modem_net_init(struct modem_spi_dev *modem_spi_dev)
+{
+ struct net_device *nw_device;
+ struct modem_spi_net_dev *net_iface_priv;
+ int err;
+ /*
+ * keep the same net device name as U8500 to allow userspace clients
+ * to remain unchanged and use the same interfaces
+ */
+ char *devname = "shrm%d";
+
+ /* allocate the net device */
+ nw_device = modem_spi_dev->ndev =
+ alloc_netdev(sizeof(struct modem_spi_net_dev),
+ devname, net_device_init);
+ if (nw_device == NULL) {
+ dev_err(modem_spi_dev->dev,
+ "failed to allocate modem net device\n");
+ return -ENOMEM;
+ }
+ err = register_netdev(modem_spi_dev->ndev);
+ if (err) {
+ dev_err(modem_spi_dev->dev,
+ "failed to register modem net device: error %d\n", err);
+ free_netdev(modem_spi_dev->ndev);
+ return -ENODEV;
+ }
+ dev_dbg(modem_spi_dev->dev, "registered modem net device\n");
+
+ net_iface_priv = (struct modem_spi_net_dev *)netdev_priv(nw_device);
+ net_iface_priv->modem_spi_dev = modem_spi_dev;
+ net_iface_priv->iface_num = 0;
+ return err;
+}
+EXPORT_SYMBOL_GPL(modem_net_init);
+
+int modem_net_stop(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ return 0;
+}
+
+int modem_net_restart(struct net_device *dev)
+{
+ if (netif_queue_stopped(dev))
+ netif_wake_queue(dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_net_restart);
+
+int modem_net_start(struct net_device *dev)
+{
+ struct modem_spi_net_dev *net_iface_priv =
+ (struct modem_spi_net_dev *)netdev_priv(dev);
+ struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev;
+
+ if (!netif_carrier_ok(dev))
+ netif_carrier_on(dev);
+ netif_start_queue(dev);
+ modem_spi_dev->netdev_flag_up = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_net_start);
+
+int modem_net_suspend(struct net_device *dev)
+{
+ if (netif_running(dev)) {
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+ }
+ netif_device_detach(dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_net_suspend);
+
+int modem_net_resume(struct net_device *dev)
+{
+ netif_device_attach(dev);
+ if (netif_running(dev)) {
+ netif_carrier_on(dev);
+ netif_wake_queue(dev);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(modem_net_resume);
+
+void modem_net_exit(struct modem_spi_dev *modem_spi_dev)
+{
+ if (modem_spi_dev && modem_spi_dev->ndev) {
+ unregister_netdev(modem_spi_dev->ndev);
+ modem_spi_dev->ndev = NULL;
+ dev_dbg(modem_spi_dev->dev, "removed modem net device\n");
+ }
+}
+EXPORT_SYMBOL_GPL(modem_net_exit);
+
+MODULE_AUTHOR("Chris Blair <chris.blair@stericsson.com>");
+MODULE_DESCRIPTION("M6718 modem IPC net device interface");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/u8500_shrm.c b/drivers/net/u8500_shrm.c
new file mode 100644
index 00000000000..08597623443
--- /dev/null
+++ b/drivers/net/u8500_shrm.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2009
+ *
+ * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson
+ * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> for ST-Ericsson
+ * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <linux/phonet.h>
+#include <linux/if_phonet.h>
+#include <linux/if_arp.h>
+#include <linux/modem/shrm/shrm_driver.h>
+#include <linux/modem/shrm/shrm_private.h>
+#include <linux/modem/shrm/shrm_config.h>
+#include <linux/modem/shrm/shrm_net.h>
+#include <linux/modem/shrm/shrm.h>
+#include <net/sock.h>
+#include <net/phonet/phonet.h>
+#include <net/phonet/pep.h>
+
+
+/**
+ * shrm_net_receive() - receive data and copy to user space buffer
+ * @dev: pointer to the network device structure
+ *
+ * Copy data from ISI queue to the user space buffer.
+ */
+int shrm_net_receive(struct net_device *dev)
+{
+ struct sk_buff *skb;
+ struct isadev_context *isadev;
+ struct message_queue *q;
+ u32 msgsize;
+ u32 size = 0;
+ struct shrm_net_iface_priv *net_iface_priv =
+ (struct shrm_net_iface_priv *)netdev_priv(dev);
+ struct shrm_dev *shrm = net_iface_priv->shrm_device;
+
+ isadev = &shrm->isa_context->isadev[ISI_MESSAGING];
+ q = &isadev->dl_queue;
+
+ spin_lock_bh(&q->update_lock);
+ if (list_empty(&q->msg_list)) {
+ spin_unlock_bh(&q->update_lock);
+ dev_dbg(shrm->dev, "Empty Shrm queue\n");
+ return 0;
+ }
+ spin_unlock_bh(&q->update_lock);
+
+ msgsize = get_size_of_new_msg(q);
+ if (msgsize <= 0)
+ return msgsize;
+
+ /*
+ * The packet has been retrieved from the transmission
+ * medium. Build an skb around it, so upper layers can handle it
+ */
+ skb = dev_alloc_skb(msgsize);
+ if (!skb) {
+ if (printk_ratelimit())
+ dev_notice(shrm->dev,
+ "isa rx: low on mem - packet dropped\n");
+ dev->stats.rx_dropped++;
+ goto out;
+ }
+
+ if ((q->readptr+msgsize) >= q->size) {
+ size = (q->size-q->readptr);
+ /*Copy First Part of msg*/
+ skb_copy_to_linear_data(skb,
+ (u8 *)(q->fifo_base + q->readptr), size);
+ skb_put(skb, size);
+
+ /*Copy Second Part of msg at the top of fifo*/
+ skb_copy_to_linear_data_offset(skb, size,
+ (u8 *)(q->fifo_base), (msgsize - size));
+ skb_put(skb, msgsize-size);
+
+ } else {
+ skb_copy_to_linear_data(skb,
+ (u8 *)(q->fifo_base+q->readptr), msgsize);
+ skb_put(skb, msgsize);
+ }
+
+ spin_lock_bh(&q->update_lock);
+ remove_msg_from_queue(q);
+ spin_unlock_bh(&q->update_lock);
+
+ skb_reset_mac_header(skb);
+ __skb_pull(skb, dev->hard_header_len);
+ /*Write metadata, and then pass to the receive level*/
+ skb->dev = dev;/*kmalloc(sizeof(struct net_device), GFP_ATOMIC);*/
+ skb->protocol = htons(ETH_P_PHONET);
+ skb->priority = 0;
+ skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
+ if (likely(netif_rx_ni(skb) == NET_RX_SUCCESS)) {
+ dev->stats.rx_packets++;
+ dev->stats.rx_bytes += msgsize;
+ } else
+ dev->stats.rx_dropped++;
+
+ return msgsize;
+out:
+ return -ENOMEM;
+}
+
+static int netdev_isa_open(struct net_device *dev)
+{
+ struct shrm_net_iface_priv *net_iface_priv =
+ (struct shrm_net_iface_priv *)netdev_priv(dev);
+ struct shrm_dev *shrm = net_iface_priv->shrm_device;
+
+ shrm->netdev_flag_up = 1;
+ if (!netif_carrier_ok(dev))
+ netif_carrier_on(dev);
+ netif_wake_queue(dev);
+ return 0;
+}
+
+static int netdev_isa_close(struct net_device *dev)
+{
+ struct shrm_net_iface_priv *net_iface_priv =
+ (struct shrm_net_iface_priv *)netdev_priv(dev);
+ struct shrm_dev *shrm = net_iface_priv->shrm_device;
+
+ shrm->netdev_flag_up = 0;
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+ return 0;
+}
+
+static int netdev_isa_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct if_phonet_req *req = (struct if_phonet_req *)ifr;
+
+ switch (cmd) {
+ case SIOCPNGAUTOCONF:
+ req->ifr_phonet_autoconf.device = PN_DEV_HOST;
+ return 0;
+ }
+ return -ENOIOCTLCMD;
+}
+
+static struct net_device_stats *netdev_isa_stats(struct net_device *dev)
+{
+ return &dev->stats;
+}
+
+/**
+ * netdev_isa_write() - write through the net interface
+ * @skb: pointer to the socket buffer
+ * @dev: pointer to the network device structure
+ *
+ * Copies data(ISI message) from the user buffer to the kernel buffer and
+ * schedule transfer thread to transmit the message to the modem via FIFO.
+ */
+static netdev_tx_t netdev_isa_write(struct sk_buff *skb, struct net_device *dev)
+{
+ int err;
+ int retval = 0;
+ struct shrm_net_iface_priv *net_iface_priv =
+ (struct shrm_net_iface_priv *)netdev_priv(dev);
+ struct shrm_dev *shrm = net_iface_priv->shrm_device;
+
+ /*
+ * FIXME:
+ * U8500 modem requires that Pipe created/enabled Indication should
+ * be sent from the port corresponding to GPRS socket.
+ * Also, the U8500 modem does not implement Pipe controller
+ * which takes care of port manipulations for GPRS traffic.
+ *
+ * Now, APE has GPRS socket and the socket for sending
+ * Indication msgs bound to different ports.
+ * Phonet stack does not allow an indication msg to be sent
+ * from GPRS socket, since Phonet stack assumes the presence
+ * of Pipe controller in modem.
+ *
+ * So, due to lack of Pipe controller implementation in the
+ * U8500 modem, carry out the port manipulation related to
+ * GPRS traffic here.
+ * Ideally, it should be done either by Pipe controller in
+ * modem OR some implementation of Pipe controller on APE side
+ */
+ if (skb->data[RESOURCE_ID_INDEX] == PN_PIPE) {
+ if ((skb->data[MSG_ID_INDEX] == PNS_PIPE_CREATED_IND) ||
+ (skb->data[MSG_ID_INDEX] == PNS_PIPE_ENABLED_IND) ||
+ (skb->data[MSG_ID_INDEX] == PNS_PIPE_DISABLED_IND))
+ skb->data[SRC_OBJ_INDEX] = skb->data[PIPE_HDL_INDEX];
+ }
+
+ spin_lock_bh(&shrm->isa_context->common_tx);
+ err = shm_write_msg(shrm, ISI_MESSAGING, skb->data,
+ skb->len);
+ if (!err) {
+ dev->stats.tx_packets++;
+ dev->stats.tx_bytes += skb->len;
+ retval = NETDEV_TX_OK;
+ dev_kfree_skb(skb);
+ } else {
+ dev->stats.tx_dropped++;
+ retval = NETDEV_TX_BUSY;
+ }
+ spin_unlock_bh(&shrm->isa_context->common_tx);
+
+ return retval;
+}
+
+static const struct net_device_ops shrm_netdev_ops = {
+ .ndo_open = netdev_isa_open,
+ .ndo_stop = netdev_isa_close,
+ .ndo_do_ioctl = netdev_isa_ioctl,
+ .ndo_start_xmit = netdev_isa_write,
+ .ndo_get_stats = netdev_isa_stats,
+};
+
+static void shm_net_init(struct net_device *dev)
+{
+ struct shrm_net_iface_priv *net_iface_priv;
+
+ dev->netdev_ops = &shrm_netdev_ops;
+ dev->header_ops = &phonet_header_ops;
+ dev->type = ARPHRD_PHONET;
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ dev->mtu = PHONET_MAX_MTU;
+ dev->hard_header_len = SHRM_HLEN;
+ dev->addr_len = PHONET_ALEN;
+ dev->tx_queue_len = PN_TX_QUEUE_LEN;
+ dev->destructor = free_netdev;
+ dev->dev_addr[0] = PN_LINK_ADDR;
+ net_iface_priv = netdev_priv(dev);
+ memset(net_iface_priv, 0 , sizeof(struct shrm_net_iface_priv));
+}
+
+int shrm_register_netdev(struct shrm_dev *shrm)
+{
+ struct net_device *nw_device;
+ struct shrm_net_iface_priv *net_iface_priv;
+ char *devname = "shrm%d";
+ int err;
+
+ /* allocate the net device */
+ nw_device = shrm->ndev = alloc_netdev(
+ sizeof(struct shrm_net_iface_priv),
+ devname, shm_net_init);
+ if (nw_device == NULL) {
+ dev_err(shrm->dev, "Failed to allocate SHRM Netdev\n");
+ return -ENOMEM;
+ }
+ err = register_netdev(shrm->ndev);
+ if (err) {
+ dev_err(shrm->dev, "Err %i in reg shrm-netdev\n", err);
+ free_netdev(shrm->ndev);
+ return -ENODEV;
+ }
+ dev_info(shrm->dev, "Registered shrm netdev\n");
+
+ net_iface_priv = (struct shrm_net_iface_priv *)netdev_priv(nw_device);
+ net_iface_priv->shrm_device = shrm;
+ net_iface_priv->iface_num = 0;
+
+ return err;
+}
+
+int shrm_stop_netdev(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ return 0;
+}
+
+int shrm_restart_netdev(struct net_device *dev)
+{
+ if (netif_queue_stopped(dev))
+ netif_wake_queue(dev);
+ return 0;
+}
+
+int shrm_start_netdev(struct net_device *dev)
+{
+ struct shrm_net_iface_priv *net_iface_priv =
+ (struct shrm_net_iface_priv *)netdev_priv(dev);
+ struct shrm_dev *shrm = net_iface_priv->shrm_device;
+
+ if (!netif_carrier_ok(dev))
+ netif_carrier_on(dev);
+ netif_start_queue(dev);
+ shrm->netdev_flag_up = 1;
+ return 0;
+}
+
+int shrm_suspend_netdev(struct net_device *dev)
+{
+ if (netif_running(dev))
+ netif_stop_queue(dev);
+ netif_device_detach(dev);
+ return 0;
+}
+
+int shrm_resume_netdev(struct net_device *dev)
+{
+ netif_device_attach(dev);
+ if (netif_running(dev))
+ netif_wake_queue(dev);
+ return 0;
+}
+
+void shrm_unregister_netdev(struct shrm_dev *shrm)
+{
+ unregister_netdev(shrm->ndev);
+}
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 78d03df1a59..f0f47306244 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -24,6 +24,16 @@ menuconfig STAGING
if STAGING
+config AB5500_SIM
+ bool "ST-Ericsson AB5500 SIM Interface driver"
+ depends on AB5500_CORE
+ help
+ SIM Interface driver provides interface to configure
+ various parameters of AB5550 SIM Level Shifter.Support provided are:
+ Configure Pull up on sim lines
+ Configure Operation Mode
+ Notify Sim Insert/Extract Interrupt
+
source "drivers/staging/serial/Kconfig"
source "drivers/staging/et131x/Kconfig"
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index e4b629258b1..1b023288d9b 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -62,3 +62,4 @@ obj-$(CONFIG_CW1200) += cw1200/
obj-$(CONFIG_U8500_MMIO) += mmio/
obj-$(CONFIG_U8500_FLASH) += camera_flash/
obj-$(CONFIG_U8500_CM) += nmf-cm/
+obj-$(CONFIG_AB5500_SIM) += ab5500_sim/
diff --git a/drivers/staging/ab5500_sim/Makefile b/drivers/staging/ab5500_sim/Makefile
new file mode 100644
index 00000000000..520717e4dd7
--- /dev/null
+++ b/drivers/staging/ab5500_sim/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_AB5500_SIM) += ab5500-sim.o
diff --git a/drivers/staging/ab5500_sim/ab5500-sim.c b/drivers/staging/ab5500_sim/ab5500-sim.c
new file mode 100644
index 00000000000..d222a22ed24
--- /dev/null
+++ b/drivers/staging/ab5500_sim/ab5500-sim.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) ST Ericsson SA 2010
+ *
+ * Sim Interface driver for AB5500
+ *
+ * License Terms: GNU General Public License v2
+ * Author: Bibek Basu <bibek.basu@stericsson.com>
+ */
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <linux/kobject.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab5500.h>
+#include <linux/io.h>
+#include <linux/err.h>
+
+#define USIM_SUP2_REG 0x13
+#define USIM_SUP_REG 0x14
+#define USIM_SIMCTRL_REG 0x17
+#define USIM_SIMCTRL2_REG 0x18
+#define USIM_USBUICC_REG 0x19
+#define USIM_USBUICC2_REG 0x20
+#define SIM_DAT_PULLUP_10K 0x0F
+#define SIM_LDO_1_8V 1875000
+#define SIM_LDO_2_8V 2800000
+#define SIM_LDO_2_9V 2900000
+
+enum shift {
+ SHIFT0,
+ SHIFT1,
+ SHIFT2,
+ SHIFT3,
+ SHIFT4,
+ SHIFT5,
+ SHIFT6,
+ SHIFT7,
+};
+
+enum mask {
+ MASK1 = 1,
+ MASK3 = 3,
+ MASK7 = 7,
+};
+
+enum sim_mode {
+ OFF_MODE,
+ LOW_PWR,
+ PWRCTRL,
+ FULL_PWR,
+};
+/**
+ * struct ab5500_sim - ab5500 Sim Interface device information
+ * @dev: pointer to the structure device
+ * @lock: mutex lock
+ * @sim_int_status: Sim presence status
+ * @irq_base: Base of the two irqs
+ */
+struct ab5500_sim {
+ struct device *dev;
+ struct mutex lock;
+ bool sim_int_status;
+ u8 irq_base;
+};
+
+/* Exposure to the sysfs interface */
+int ab5500_sim_weak_pulldforce(struct device *dev,
+ struct device_attribute *attr,
+ const char *user_buf, size_t count)
+{
+ unsigned long user_val;
+ int err;
+ bool enable;
+
+ err = strict_strtoul(user_buf, 0, &user_val);
+ if (err)
+ return -EINVAL;
+ enable = user_val ? true : false;
+ err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM,
+ USIM_USBUICC2_REG, MASK1 << SHIFT5, user_val << SHIFT5);
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+int ab5500_sim_load_sel(struct device *dev,
+ struct device_attribute *attr,
+ const char *user_buf, size_t count)
+{
+ unsigned long user_val;
+ int err;
+ bool enable;
+
+ err = strict_strtoul(user_buf, 0, &user_val);
+ if (err)
+ return -EINVAL;
+ enable = user_val ? true : false;
+ err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM,
+ USIM_USBUICC_REG, MASK1 << SHIFT1, user_val << SHIFT1);
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+int ab5500_sim_mode_sel(struct device *dev,
+ struct device_attribute *attr,
+ const char *user_buf, size_t count)
+{
+ unsigned long user_val;
+ int err;
+
+ err = strict_strtoul(user_buf, 0, &user_val);
+ if (err)
+ return -EINVAL;
+ err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM,
+ USIM_SIMCTRL2_REG, MASK3 << SHIFT4, user_val << SHIFT4);
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+int ab5500_sim_dat_pullup(struct device *dev,
+ struct device_attribute *attr,
+ const char *user_buf, size_t count)
+{
+ unsigned long user_val;
+ int err;
+
+ err = strict_strtoul(user_buf, 0, &user_val);
+ if (err)
+ return -EINVAL;
+ err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM,
+ USIM_SIMCTRL_REG, MASK7, user_val);
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+int ab5500_sim_enable_pullup(struct device *dev,
+ struct device_attribute *attr,
+ const char *user_buf, size_t count)
+{
+ unsigned long user_val;
+ int err;
+ bool enable;
+
+ err = strict_strtoul(user_buf, 0, &user_val);
+ if (err)
+ return -EINVAL;
+ enable = user_val ? true : false;
+ err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM,
+ USIM_SIMCTRL_REG, MASK1 << SHIFT3, enable << SHIFT3);
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t ab5500_simoff_int(struct device *dev,
+ struct device_attribute *devattr, char *user_buf)
+{
+ struct ab5500_sim *di = dev_get_drvdata(dev);
+ int len;
+
+ mutex_lock(&di->lock);
+ len = sprintf(user_buf, "%d\n", di->sim_int_status);
+ mutex_unlock(&di->lock);
+ return len;
+}
+
+static DEVICE_ATTR(enable_pullup, S_IWUSR, NULL, ab5500_sim_enable_pullup);
+static DEVICE_ATTR(dat_pullup, S_IWUSR, NULL, ab5500_sim_dat_pullup);
+static DEVICE_ATTR(mode_sel, S_IWUSR, NULL, ab5500_sim_mode_sel);
+static DEVICE_ATTR(load_sel, S_IWUSR, NULL, ab5500_sim_load_sel);
+static DEVICE_ATTR(weak_pulldforce, S_IWUSR, NULL, ab5500_sim_weak_pulldforce);
+static DEVICE_ATTR(simoff_int, S_IRUGO, ab5500_simoff_int, NULL);
+
+static struct attribute *ab5500_sim_attributes[] = {
+ &dev_attr_enable_pullup.attr,
+ &dev_attr_dat_pullup.attr,
+ &dev_attr_mode_sel.attr,
+ &dev_attr_load_sel.attr,
+ &dev_attr_weak_pulldforce.attr,
+ &dev_attr_simoff_int.attr,
+ NULL
+};
+
+static const struct attribute_group ab5500sim_attr_grp = {
+ .attrs = ab5500_sim_attributes,
+};
+
+static irqreturn_t ab5500_sim_irq_handler(int irq, void *irq_data)
+{
+ struct platform_device *pdev = irq_data;
+ struct ab5500_sim *data = platform_get_drvdata(pdev);
+
+ if (irq == data->irq_base)
+ data->sim_int_status = true;
+ else
+ data->sim_int_status = false;
+ sysfs_notify(&pdev->dev.kobj, NULL, "simoff_int");
+
+ return IRQ_HANDLED;
+}
+
+static int __devexit ab5500_sim_remove(struct platform_device *pdev)
+{
+ struct ab5500_sim *di = platform_get_drvdata(pdev);
+ int irq = platform_get_irq_byname(pdev, "SIMOFF");
+
+ if (irq >= 0) {
+ free_irq(irq, di);
+ irq++;
+ free_irq(irq, di);
+ }
+ sysfs_remove_group(&pdev->dev.kobj, &ab5500sim_attr_grp);
+ platform_set_drvdata(pdev, NULL);
+ kfree(di);
+
+ return 0;
+}
+
+static int __devinit ab5500_sim_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ int irq;
+ struct ab5500_sim *di =
+ kzalloc(sizeof(struct ab5500_sim), GFP_KERNEL);
+ if (!di) {
+ ret = -ENOMEM;
+ goto error_alloc;
+ }
+ dev_info(&pdev->dev, "ab5500_sim_driver PROBE\n");
+ irq = platform_get_irq_byname(pdev, "SIMOFF");
+ if (irq < 0) {
+ dev_err(&pdev->dev, "Get irq by name failed\n");
+ ret = irq;
+ goto exit;
+ }
+ di->irq_base = irq;
+ di->dev = &pdev->dev;
+ mutex_init(&di->lock);
+ platform_set_drvdata(pdev, di);
+ /* sysfs interface to configure sim reg from user space */
+ if (sysfs_create_group(&pdev->dev.kobj, &ab5500sim_attr_grp) < 0) {
+ dev_err(&pdev->dev, " Failed creating sysfs group\n");
+ ret = -ENOMEM;
+ goto error_sysfs;
+ }
+ ret = request_threaded_irq(irq, NULL, ab5500_sim_irq_handler,
+ IRQF_NO_SUSPEND , "ab5500-sim", pdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret);
+ goto error_irq;
+ }
+ /* this is the contiguous irq for sim removal,falling edge */
+ irq = irq + 1;
+ ret = request_threaded_irq(irq, NULL, ab5500_sim_irq_handler,
+ IRQF_NO_SUSPEND , "ab5500-sim", pdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret);
+ free_irq(--irq, di);
+ goto error_irq;
+ }
+ return ret;
+error_irq:
+ sysfs_remove_group(&pdev->dev.kobj, &ab5500sim_attr_grp);
+error_sysfs:
+ platform_set_drvdata(pdev, NULL);
+exit:
+ kfree(di);
+error_alloc:
+ return ret;
+}
+
+static struct platform_driver ab5500_sim_driver = {
+ .probe = ab5500_sim_probe,
+ .remove = __devexit_p(ab5500_sim_remove),
+ .driver = {
+ .name = "ab5500-sim",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ab5500_sim_init(void)
+{
+ return platform_driver_register(&ab5500_sim_driver);
+}
+
+static void __exit ab5500_sim_exit(void)
+{
+ platform_driver_unregister(&ab5500_sim_driver);
+}
+
+module_init(ab5500_sim_init);
+module_exit(ab5500_sim_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Bibek Basu");
+MODULE_ALIAS("platform:ab5500-sim");
+MODULE_DESCRIPTION("AB5500 sim interface driver");
diff --git a/drivers/staging/ab5500_sim/sysfs-sim b/drivers/staging/ab5500_sim/sysfs-sim
new file mode 100644
index 00000000000..b809b21e39e
--- /dev/null
+++ b/drivers/staging/ab5500_sim/sysfs-sim
@@ -0,0 +1,83 @@
+What: /sys/devices/platform/ab5500-core.0/ab5500-sim.4/
+Date: June 2011
+KernelVersion: 2.6.35
+Contact: Bibek Basu <bibek.basu@stericsson.com>
+Description:
+ The /sys/devices/.../ab5500-sim.4 directory contains attributes
+ allowing the user space to check and configure ab5500 sim level
+ shifter interface caracteristics for communication to SIM card
+
+What: /sys/devices/.../enable_pullup
+Date: June 2011
+KernelVersion: 2.6.35
+Contact: Bibek Basu <bibek.basu@stericsson.com>
+Description:
+ The /sys/devices/.../ab5500-sim.4/enable_pullup attribute allows
+ the user space to configure if internal pull up in SIMIO lines
+ has to be enabled or disabled. For enabling write 1 to the file
+ and 0 for disabling
+
+
+What: /sys/devices/.../dat_pullup
+Date: June 2011
+KernelVersion: 2.6.35
+Contact: Bibek Basu <bibek.basu@stericsson.com>
+Description:
+ The /sys/devices/.../ab5500-sim.4/dat_pullup attribute allows
+ the user space to configure the resistance value for internal
+ pull up in SIMIO lines. Following value can be written on the file
+ 0 SIM_DAT pull-up disabled
+ 1 SIM_DAT pull-up 4kOhm
+ 2 SIM_DAT pull-up 5kOhm
+ 3 SIM_DAT pull-up 6kOhm
+ 4 SIM_DAT pull-up 7kOhm
+ 5 SIM_DAT pull-up 8kOhm
+ 6 SIM_DAT pull-up 9kOhm
+ 7 SIM_DAT pull-up 10kOhm
+
+What: /sys/devices/.../mode_sel
+Date: June 2011
+KernelVersion: 2.6.35
+Contact: Bibek Basu <bibek.basu@stericsson.com>
+Description:
+ The /sys/devices/.../ab5500-sim.4/mode_sel attribute allows
+ the user space to configure the mode at which the level shifter
+ will work. Following value can be written on the file
+ 0 TG mode and LI mode off
+ 1 TG mode on
+ 2 LI mode on
+ 3 TG mode and LI mode off
+
+What: /sys/devices/.../load_sel
+Date: June 2011
+KernelVersion: 2.6.35
+Contact: Bibek Basu <bibek.basu@stericsson.com>
+Description:
+ The /sys/devices/.../ab5500-sim.4/load_sel attribute allows
+ the user space to configure the load on the USBUICC lines.
+ Following value can be written on the file.
+ 0 Data line load < 21pF
+ 1 Data line load 21-30pF
+
+What: /sys/devices/.../weak_pulldforce
+Date: June 2011
+KernelVersion: 2.6.35
+Contact: Bibek Basu <bibek.basu@stericsson.com>
+Description:
+ The /sys/devices/.../ab5500-sim.4/weak_pulldforce attribute allows
+ the user space to configure the weak pull down on the USBUICC lines.
+ Following value can be written on the file.
+ 0 USB-UICC data lines weak pull down active
+ 1 USB-UICC data lines weak pull down not active
+
+What: /sys/devices/.../simoff_int
+Date: June 2011
+KernelVersion: 2.6.35
+Contact: Bibek Basu <bibek.basu@stericsson.com>
+Description:
+ The /sys/devices/.../ab5500-sim.4/simoff_int attribute allows
+ the user space to poll this file and get notified in case a sim
+ hot swap has happened. a zero means sim extracetd and a one means
+ inserted.
+
+