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