diff options
author | Chris Blair <chris.blair@stericsson.com> | 2011-11-04 11:03:27 +0000 |
---|---|---|
committer | Philippe Langlais <philippe.langlais@linaro.org> | 2012-03-19 09:02:43 +0100 |
commit | 1b629ae7ce5ce25321698463982b8cb18e6be75e (patch) | |
tree | aa21d77113ca5e6388479dd477c3794a468f8961 /drivers/modem | |
parent | 2bb287ee733f3943de1bca4b9729d44b1bc894d8 (diff) |
modem: Add M6718 IPC SPI driver protocol framework
Adds the M6718 modem IPC SPI driver procotol framework and types.
Placeholders remain where future parts of the protocol will be
called.
ST-Ericsson ID: 369397
ST-Ericsson FOSS-OUT ID: STETL-FOSS-OUT-12224
ST-Ericsson Linux next: NA
Change-Id: Id4cd5a7fa730c06838c9dd5b1a1cc0045135fdd4
Signed-off-by: Chris Blair <chris.blair@stericsson.com>
Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/36466
Reviewed-by: QATOOLS
Reviewed-by: QABUILD
Reviewed-by: Jonas ABERG <jonas.aberg@stericsson.com>
Diffstat (limited to 'drivers/modem')
-rw-r--r-- | drivers/modem/m6718_spi/Makefile | 2 | ||||
-rw-r--r-- | drivers/modem/m6718_spi/modem_driver.c | 26 | ||||
-rw-r--r-- | drivers/modem/m6718_spi/modem_private.h | 103 | ||||
-rw-r--r-- | drivers/modem/m6718_spi/modem_protocol.h | 22 | ||||
-rw-r--r-- | drivers/modem/m6718_spi/modem_state.c | 2 | ||||
-rw-r--r-- | drivers/modem/m6718_spi/modem_state.h | 36 | ||||
-rw-r--r-- | drivers/modem/m6718_spi/modem_statemachine.h | 74 | ||||
-rw-r--r-- | drivers/modem/m6718_spi/protocol.c | 287 |
8 files changed, 548 insertions, 4 deletions
diff --git a/drivers/modem/m6718_spi/Makefile b/drivers/modem/m6718_spi/Makefile index 492995bc753..66bad09c0dd 100644 --- a/drivers/modem/m6718_spi/Makefile +++ b/drivers/modem/m6718_spi/Makefile @@ -5,7 +5,7 @@ ifeq ($(CONFIG_MODEM_M6718_SPI_DEBUG),y) EXTRA_CFLAGS += -DDEBUG endif -m6718_modem_spi-objs := modem_driver.o +m6718_modem_spi-objs := modem_driver.o protocol.o ifeq ($(CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE),y) m6718_modem_spi-objs += modem_state.o diff --git a/drivers/modem/m6718_spi/modem_driver.c b/drivers/modem/m6718_spi/modem_driver.c index 7d0efa6e395..13d4a3e28b2 100644 --- a/drivers/modem/m6718_spi/modem_driver.c +++ b/drivers/modem/m6718_spi/modem_driver.c @@ -13,6 +13,7 @@ #include <linux/spi/spi.h> #include <linux/modem/modem_client.h> #include <linux/modem/m6718_spi/modem_driver.h> +#include "modem_protocol.h" static struct modem_spi_dev modem_driver_data = { .dev = NULL, @@ -53,6 +54,13 @@ static int spi_probe(struct spi_device *sdev) 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. @@ -65,13 +73,20 @@ static int spi_probe(struct spi_device *sdev) dev_err(&sdev->dev, "failed to retrieve modem description\n"); result = -ENODEV; + goto rollback_protocol_init; } } return result; + +rollback_protocol_init: + modem_protocol_exit(); +rollback: + return result; } static int __exit spi_remove(struct spi_device *sdev) { + modem_protocol_exit(); return 0; } @@ -86,8 +101,14 @@ static int __exit spi_remove(struct spi_device *sdev) */ static int spi_suspend(struct spi_device *sdev, pm_message_t mesg) { - dev_dbg(&sdev->dev, "suspend called\n"); - return 0; + bool busy; + + busy = modem_protocol_is_busy(sdev); + dev_dbg(&sdev->dev, "suspend called, protocol busy:%d\n", busy); + if (!busy) + return 0; + else + return -EBUSY; } /** @@ -118,6 +139,7 @@ static struct spi_driver spi_driver = { 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); diff --git a/drivers/modem/m6718_spi/modem_private.h b/drivers/modem/m6718_spi/modem_private.h new file mode 100644 index 00000000000..383699f1f3e --- /dev/null +++ b/drivers/modem/m6718_spi/modem_private.h @@ -0,0 +1,103 @@ +/* + * 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 (0x03) /* APE protocol version */ +#define IPC_DRIVER_MODEM_MIN_VER (0x03) /* 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 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 +}; + +#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..69511945cb5 --- /dev/null +++ b/drivers/modem/m6718_spi/modem_protocol.h @@ -0,0 +1,22 @@ +/* + * 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); + +#endif /* _MODEM_PROTOCOL_H_ */ diff --git a/drivers/modem/m6718_spi/modem_state.c b/drivers/modem/m6718_spi/modem_state.c index 266f75a7a71..47376934bcb 100644 --- a/drivers/modem/m6718_spi/modem_state.c +++ b/drivers/modem/m6718_spi/modem_state.c @@ -13,7 +13,6 @@ #include <linux/kernel.h> #include <linux/sched.h> #include <linux/wait.h> -#include <linux/modem/m6718_spi/modem_state.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/ioport.h> @@ -30,6 +29,7 @@ #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 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..bc737dbb7e8 --- /dev/null +++ b/drivers/modem/m6718_spi/modem_statemachine.h @@ -0,0 +1,74 @@ +/* + * 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 { + /* common link and shared states below */ + 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, + /* audio link states below */ + IPC_SM_INIT_AUD, + IPC_SM_HALT_AUD, + IPC_SM_RESET_AUD, + IPC_SM_IDL_AUD, + IPC_SM_SLW_TX_WR_DAT_AUD, + IPC_SM_ACT_TX_WR_DAT_AUD, + IPC_SM_SLW_RX_WR_DAT_AUD, + IPC_SM_ACT_RX_WR_DAT_AUD, + 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; +}; + +#endif /* _MODEM_STATEMACHINE_H_ */ diff --git a/drivers/modem/m6718_spi/protocol.c b/drivers/modem/m6718_spi/protocol.c new file mode 100644 index 00000000000..5f03167d024 --- /dev/null +++ b/drivers/modem/m6718_spi/protocol.c @@ -0,0 +1,287 @@ +/* + * 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" + +#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) +{ + /* TODO: statemachine will be kicked here */ +} + +void slave_stable_timeout(unsigned long data) +{ + /* TODO: statemachine will be kicked here */ +} + +/** + * 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); + l1_context.init_done = true; +#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) +{ + 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; + } + + /* TODO: statemachine will be kicked here */ + 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_IDL_AUD: + case IPC_SM_INIT: + case IPC_SM_INIT_AUD: + 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; +} + +#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE +static int modem_state_callback(unsigned long unused) +{ + int modem_state = modem_state_get_state(); + + pr_info("M6718 IPC protocol modemstate reports modem is %s\n", + modem_state_to_str(modem_state)); + + switch (modem_state) { + case MODEM_STATE_ON: + /* TODO: statemachine will be kicked here */ + break; + case MODEM_STATE_OFF: + case MODEM_STATE_RESET: + case MODEM_STATE_CRASH: + /* TODO: statemachine will be kicked here */ + 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; + atomic_set(&context->gpio_configured, 0); + spin_lock_init(&context->sm_lock); + 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; + +#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_VERIFY_FRAMES + context->last_frame = NULL; +#endif + + /* + * For link0 (the handshake link) we force a state transition now so + * that it prepares for boot sync. + */ + /* TODO: statemachine will be kicked here */ + + /* + * 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); + /* TODO: statemachine will be kicked here */ + } + return 0; +} + +void modem_protocol_exit(void) +{ + pr_info("M6718 IPC protocol exit\n"); +} |