summaryrefslogtreecommitdiff
path: root/drivers/modem
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/modem')
-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
30 files changed, 10086 insertions, 0 deletions
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;
+}