diff options
Diffstat (limited to 'drivers')
53 files changed, 16377 insertions, 0 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index cecb6b3c2cc..be367428c1d 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -138,5 +138,7 @@ source "drivers/virt/Kconfig" source "drivers/devfreq/Kconfig" +source "drivers/modem/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index cbc7229016e..42edc9ef222 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_CPU_IDLE) += cpuidle/ obj-y += mmc/ obj-$(CONFIG_MEMSTICK) += memstick/ +obj-$(CONFIG_MODEM) += modem/ obj-y += leds/ obj-$(CONFIG_INFINIBAND) += infiniband/ obj-$(CONFIG_SGI_SN) += sn/ diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 32762ba769c..3b82024f70e 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -50,6 +50,11 @@ obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o obj-$(CONFIG_GPIO_TB0219) += tb0219.o obj-$(CONFIG_TELCLOCK) += tlclk.o +ifdef CONFIG_PHONET +obj-$(CONFIG_U8500_SHRM) += shrm_char.o +obj-$(CONFIG_MODEM_M6718_SPI) += m6718_modem_char.o +endif + obj-$(CONFIG_MWAVE) += mwave/ obj-$(CONFIG_AGP) += agp/ obj-$(CONFIG_PCMCIA) += pcmcia/ diff --git a/drivers/char/m6718_modem_char.c b/drivers/char/m6718_modem_char.c new file mode 100644 index 00000000000..34bfe98aa57 --- /dev/null +++ b/drivers/char/m6718_modem_char.c @@ -0,0 +1,722 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson + * based on shrm_char.c + * + * License terms: GNU General Public License (GPL) version 2 + * + * M6718 modem char device interface. + */ +#include <linux/err.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/sched.h> +#include <linux/atomic.h> +#include <linux/modem/m6718_spi/modem_char.h> +#include <linux/modem/m6718_spi/modem_driver.h> + +#define NAME "IPC_ISA" + +#define MAX_PDU_SIZE (2000) /* largest frame we need to send */ +#define MAX_RX_FIFO_ENTRIES (10) +#define SIZE_OF_RX_FIFO (MAX_PDU_SIZE * MAX_RX_FIFO_ENTRIES) +#define SIZE_OF_TX_COPY_BUFFER (MAX_PDU_SIZE) /* only need 1 at a time */ + +static u8 message_fifo[MODEM_M6718_SPI_MAX_CHANNELS][SIZE_OF_RX_FIFO]; + +#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK +static u8 wr_mlb_msg[SIZE_OF_TX_COPY_BUFFER]; +#endif +static u8 wr_audio_msg[SIZE_OF_TX_COPY_BUFFER]; + +struct map_device { + u8 l2_header; + u8 idx; + char *name; +}; + +static struct map_device map_dev[] = { + {MODEM_M6718_SPI_CHN_ISI, 0, "isi"}, + {MODEM_M6718_SPI_CHN_AUDIO, 1, "modemaudio"}, +#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK + {MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0, 2, "master_loopback0"}, + {MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0, 3, "slave_loopback0"}, + {MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1, 4, "master_loopback1"}, + {MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1, 5, "slave_loopback1"}, +#endif +}; + +/* + * major - variable exported as module_param to specify major node number + */ +static int major; +module_param(major, int, 0); +MODULE_PARM_DESC(major, "Major device number"); + +/* global fops mutex */ +static DEFINE_MUTEX(isa_lock); + +/** + * modem_get_cdev_index() - return the index mapped to l2 header + * @l2_header: L2 header + * + * struct map_device maps the index(count) with the device L2 header. + * This function returns the index for the provided L2 header in case + * of success else -ve value. + */ +int modem_get_cdev_index(u8 l2_header) +{ + u8 cnt; + for (cnt = 0; cnt < ARRAY_SIZE(map_dev); cnt++) { + if (map_dev[cnt].l2_header == l2_header) + return map_dev[cnt].idx; + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(modem_get_cdev_index); + +/** + * modem_get_cdev_l2header() - return l2_header mapped to the index + * @idx: index + * + * struct map_device maps the index(count) with the device L2 header. + * This function returns the L2 header for the given index in case + * of success else -ve value. + */ +int modem_get_cdev_l2header(u8 idx) +{ + u8 cnt; + for (cnt = 0; cnt < ARRAY_SIZE(map_dev); cnt++) { + if (map_dev[cnt].idx == idx) + return map_dev[cnt].l2_header; + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(modem_get_cdev_l2header); + +/** + * modem_isa_reset() - reset device interfaces + * @modem_spi_dev: pointer to modem driver information structure + * + * Emptys the queue for each L2 mux channel. + */ +void modem_isa_reset(struct modem_spi_dev *modem_spi_dev) +{ + struct isa_device_context *isadev; + struct isa_driver_context *isa_context; + struct queue_element *cur_msg = NULL; + struct list_head *cur_msg_ptr = NULL; + struct list_head *msg_ptr; + struct message_queue *q; + int devidx; + + dev_info(modem_spi_dev->dev, "resetting char device queues\n"); + + isa_context = modem_spi_dev->isa_context; + for (devidx = 0; devidx < ARRAY_SIZE(map_dev); devidx++) { + isadev = &isa_context->isadev[devidx]; + q = &isadev->dl_queue; + + spin_lock_bh(&q->update_lock); + /* empty out the msg queue */ + list_for_each_safe(cur_msg_ptr, msg_ptr, &q->msg_list) { + cur_msg = list_entry(cur_msg_ptr, + struct queue_element, entry); + list_del(cur_msg_ptr); + kfree(cur_msg); + } + + /* reset the msg queue pointers */ + q->size = SIZE_OF_RX_FIFO; + q->readptr = 0; + q->writeptr = 0; + q->no = 0; + + /* wake up the blocking read/select */ + atomic_set(&q->q_rp, 1); + wake_up_interruptible(&q->wq_readable); + spin_unlock_bh(&q->update_lock); + } +} +EXPORT_SYMBOL_GPL(modem_isa_reset); + +static void create_queue(struct message_queue *q, u8 channel, + struct modem_spi_dev *modem_spi_dev) +{ + q->channel = channel; + q->fifo_base = (u8 *)&message_fifo[channel]; + q->size = SIZE_OF_RX_FIFO; + q->free = q->size; + q->readptr = 0; + q->writeptr = 0; + q->no = 0; + spin_lock_init(&q->update_lock); + atomic_set(&q->q_rp, 0); + init_waitqueue_head(&q->wq_readable); + INIT_LIST_HEAD(&q->msg_list); + q->modem_spi_dev = modem_spi_dev; +} + +static void delete_queue(struct message_queue *q) +{ + q->size = 0; + q->readptr = 0; + q->writeptr = 0; +} + +/** + * modem_isa_queue_msg() - Add a message to a queue queue + * @q: message queue + * @size: size in bytes + * + * This function tries to allocate size bytes in FIFO q. + * It returns negative number when no memory can be allocated + * currently. + */ +int modem_isa_queue_msg(struct message_queue *q, u32 size) +{ + struct queue_element *new_msg = NULL; + struct modem_spi_dev *modem_spi_dev = q->modem_spi_dev; + + new_msg = kmalloc(sizeof(struct queue_element), GFP_ATOMIC); + if (new_msg == NULL) { + dev_err(modem_spi_dev->dev, + "failed to allocate memory for queue item\n"); + return -ENOMEM; + } + new_msg->offset = q->writeptr; + new_msg->size = size; + new_msg->no = q->no++; + + /* check for overflow condition */ + if (q->readptr <= q->writeptr) { + if (((q->writeptr - q->readptr) + size) >= q->size) { + dev_err(modem_spi_dev->dev, "rx q++ ch %d %d (%d)\n", + q->channel, size, q->free); + dev_err(modem_spi_dev->dev, + "ch%d buffer overflow, frame discarded\n", + q->channel); + return -ENOMEM; + } + } else { + if ((q->writeptr + size) >= q->readptr) { + dev_err(modem_spi_dev->dev, "rx q++ ch %d %d (%d)\n", + q->channel, size, q->free); + dev_err(modem_spi_dev->dev, + "ch%d buffer overflow, frame discarded\n", + q->channel); + return -ENOMEM; + } + } + q->free -= size; + q->writeptr = (q->writeptr + size) % q->size; + if (list_empty(&q->msg_list)) { + list_add_tail(&new_msg->entry, &q->msg_list); + /* There can be 2 blocking calls: read and another select */ + atomic_set(&q->q_rp, 1); + wake_up_interruptible(&q->wq_readable); + } else { + list_add_tail(&new_msg->entry, &q->msg_list); + } + return 0; +} +EXPORT_SYMBOL_GPL(modem_isa_queue_msg); + +/** + * modem_isa_unqueue_msg() - remove a message from the msg queue + * @q: message queue + * + * Deletes a message from the message list associated with message + * queue q and also updates read ptr. If the message list is empty + * then a flag is set to block the select and read calls of the paricular queue. + * + * The message list is FIFO style and message is always added to tail and + * removed from head. + */ +int modem_isa_unqueue_msg(struct message_queue *q) +{ + struct queue_element *old_msg = NULL; + struct list_head *msg_ptr = NULL; + struct list_head *old_msg_ptr = NULL; + + list_for_each_safe(old_msg_ptr, msg_ptr, &q->msg_list) { + old_msg = list_entry(old_msg_ptr, struct queue_element, entry); + if (old_msg == NULL) + return -EFAULT; + list_del(old_msg_ptr); + q->readptr = (q->readptr + old_msg->size) % q->size; + q->free += old_msg->size; + kfree(old_msg); + break; + } + if (list_empty(&q->msg_list)) + atomic_set(&q->q_rp, 0); + return 0; +} +EXPORT_SYMBOL_GPL(modem_isa_unqueue_msg); + +/** + * modem_isa_msg_size() - retrieve the size of the most recent message + * @q: message queue + */ +int modem_isa_msg_size(struct message_queue *q) +{ + struct queue_element *new_msg = NULL; + struct list_head *msg_list; + unsigned long flags; + int size = 0; + + spin_lock_irqsave(&q->update_lock, flags); + list_for_each(msg_list, &q->msg_list) { + new_msg = list_entry(msg_list, struct queue_element, entry); + if (new_msg == NULL) { + spin_unlock_irqrestore(&q->update_lock, flags); + return -EFAULT; + } + size = new_msg->size; + break; + } + spin_unlock_irqrestore(&q->update_lock, flags); + return size; +} +EXPORT_SYMBOL_GPL(modem_isa_msg_size); + +static u32 isa_select(struct file *filp, struct poll_table_struct *wait) +{ + struct isa_device_context *isadev = filp->private_data; + struct modem_spi_dev *modem_spi_dev = isadev->dl_queue.modem_spi_dev; + struct message_queue *q; + u32 m = iminor(filp->f_path.dentry->d_inode); + u8 idx = modem_get_cdev_index(m); + + if (modem_spi_dev->msr_flag) + return -ENODEV; + if (isadev->device_id != idx) + return -1; + + q = &isadev->dl_queue; + poll_wait(filp, &q->wq_readable, wait); + if (atomic_read(&q->q_rp) == 1) + return POLLIN | POLLRDNORM; + return 0; +} + +static ssize_t isa_read(struct file *filp, char __user *buf, + size_t len, loff_t *ppos) +{ + u32 size = 0; + int ret; + char *psrc; + struct isa_device_context *isadev = + (struct isa_device_context *)filp->private_data; + struct message_queue *q = &isadev->dl_queue; + struct modem_spi_dev *modem_spi_dev = q->modem_spi_dev; + u32 msgsize; + unsigned long flags; + + if (len <= 0) + return -EFAULT; + + if (modem_spi_dev->msr_flag) { + atomic_set(&q->q_rp, 0); + return -ENODEV; + } + + spin_lock_irqsave(&q->update_lock, flags); + if (list_empty(&q->msg_list)) { + spin_unlock_irqrestore(&q->update_lock, flags); + dev_dbg(modem_spi_dev->dev, "waiting for data on device %d\n", + isadev->device_id); + if (wait_event_interruptible(q->wq_readable, + atomic_read(&q->q_rp) == 1)) + return -ERESTARTSYS; + } else { + spin_unlock_irqrestore(&q->update_lock, flags); + } + + if (modem_spi_dev->msr_flag) { + atomic_set(&q->q_rp, 0); + return -ENODEV; + } + + msgsize = modem_isa_msg_size(q); + if (len < msgsize) + return -EINVAL; + + if ((q->readptr + msgsize) >= q->size) { + psrc = (char *)buf; + size = (q->size - q->readptr); + /* copy first part of msg */ + if (copy_to_user(psrc, (u8 *)(q->fifo_base + q->readptr), + size)) + return -EFAULT; + + psrc += size; + /* copy second part of msg at the top of fifo */ + if (copy_to_user(psrc, (u8 *)(q->fifo_base), + (msgsize - size))) + return -EFAULT; + } else { + if (copy_to_user(buf, (u8 *)(q->fifo_base + q->readptr), + msgsize)) + return -EFAULT; + } + + spin_lock_irqsave(&q->update_lock, flags); + ret = modem_isa_unqueue_msg(q); + if (ret < 0) + msgsize = ret; + spin_unlock_irqrestore(&q->update_lock, flags); + return msgsize; +} + +static ssize_t isa_write(struct file *filp, const char __user *buf, + size_t len, loff_t *ppos) +{ + struct isa_device_context *isadev = filp->private_data; + struct message_queue *q = &isadev->dl_queue; + struct modem_spi_dev *modem_spi_dev = q->modem_spi_dev; + struct isa_driver_context *isa_context = modem_spi_dev->isa_context; + void *addr = 0; + int err; + int l2_header; + int ret = 0; + unsigned long flags; + + if (len <= 0 || buf == NULL) + return -EFAULT; + + if (len > SIZE_OF_TX_COPY_BUFFER) { + dev_err(modem_spi_dev->dev, + "invalid message size %d! max is %d bytes\n", + len, SIZE_OF_TX_COPY_BUFFER); + return -EFAULT; + } + + l2_header = modem_get_cdev_l2header(isadev->device_id); + if (l2_header < 0) { + dev_err(modem_spi_dev->dev, "invalid L2 channel!\n"); + return l2_header; + } + + switch (l2_header) { + case MODEM_M6718_SPI_CHN_AUDIO: + addr = (void *)wr_audio_msg; + break; + case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0: + case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0: + case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1: + case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1: + addr = (void *)wr_mlb_msg; + break; + default: + dev_dbg(modem_spi_dev->dev, "invalid device!\n"); + return -EFAULT; + } + + if (copy_from_user(addr, buf, len)) + return -EFAULT; + + /* + * Special handling for audio channel: + * uses a mutext instead of a spinlock + */ + if (l2_header == MODEM_M6718_SPI_CHN_AUDIO || + l2_header == MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1) { + mutex_lock(&isa_context->audio_tx_mutex); + err = modem_m6718_spi_send(modem_spi_dev, l2_header, len, addr); + if (!err) + ret = len; + else + ret = err; + mutex_unlock(&modem_spi_dev->isa_context->audio_tx_mutex); + } else { + spin_lock_irqsave(&isa_context->common_tx_lock, flags); + err = modem_m6718_spi_send(modem_spi_dev, l2_header, len, addr); + if (!err) + ret = len; + else + ret = err; + spin_unlock_irqrestore(&isa_context->common_tx_lock, flags); + } + return ret; +} + +static long isa_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + return -EINVAL; +} + +static int isa_mmap(struct file *filp, struct vm_area_struct *vma) +{ + return -EINVAL; +} + +static int isa_close(struct inode *inode, struct file *filp) +{ + struct isa_device_context *isadev = filp->private_data; + struct modem_spi_dev *modem_spi_dev = isadev->dl_queue.modem_spi_dev; + struct isa_driver_context *isa_context = modem_spi_dev->isa_context; + u8 m; + int idx; + + mutex_lock(&isa_lock); + m = iminor(filp->f_path.dentry->d_inode); + idx = modem_get_cdev_index(m); + if (idx < 0) { + dev_err(modem_spi_dev->dev, "invalid L2 channel!\n"); + return idx; + } + + if (atomic_dec_and_test(&isa_context->is_open[idx])) { + atomic_inc(&isa_context->is_open[idx]); + dev_err(modem_spi_dev->dev, "device is not open yet!\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + } + atomic_set(&isa_context->is_open[idx], 1); + + switch (m) { + case MODEM_M6718_SPI_CHN_AUDIO: + dev_dbg(modem_spi_dev->dev, "close channel AUDIO\n"); + break; +#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK + case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0: + dev_dbg(modem_spi_dev->dev, "close channel MASTER_LOOPBACK0\n"); + break; + case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0: + dev_dbg(modem_spi_dev->dev, "close channel SLAVE_LOOPBACK0\n"); + break; + case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1: + dev_dbg(modem_spi_dev->dev, "close channel MASTER_LOOPBACK1\n"); + break; + case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1: + dev_dbg(modem_spi_dev->dev, "close channel SLAVE_LOOPBACK1\n"); + break; +#endif + default: + dev_dbg(modem_spi_dev->dev, "invalid device\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + } + mutex_unlock(&isa_lock); + return 0; +} + +static int isa_open(struct inode *inode, struct file *filp) +{ + int err = 0; + u8 m; + int idx; + struct isa_device_context *isadev; + struct isa_driver_context *isa_context = + container_of(inode->i_cdev, struct isa_driver_context, cdev); + struct modem_spi_dev *modem_spi_dev = + isa_context->isadev->dl_queue.modem_spi_dev; + + if (!modem_m6718_spi_is_boot_done()) { + dev_dbg(modem_spi_dev->dev, + "failed to open device, boot is not complete\n"); + err = -EBUSY; + goto out; + } + + mutex_lock(&isa_lock); + m = iminor(inode); + idx = modem_get_cdev_index(m); + if (idx < 0) { + dev_err(modem_spi_dev->dev, "invalid device\n"); + err = -ENODEV; + goto cleanup; + } + + if (!atomic_dec_and_test(&isa_context->is_open[idx])) { + atomic_inc(&isa_context->is_open[idx]); + dev_err(modem_spi_dev->dev, "device is already open\n"); + err = -EBUSY; + goto cleanup; + } + + isadev = &isa_context->isadev[idx]; + if (filp != NULL) + filp->private_data = isadev; + + switch (m) { + case MODEM_M6718_SPI_CHN_AUDIO: + dev_dbg(modem_spi_dev->dev, "open channel AUDIO\n"); + break; +#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK + case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0: + dev_dbg(modem_spi_dev->dev, "open channel MASTER_LOOPBACK0\n"); + break; + case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0: + dev_dbg(modem_spi_dev->dev, "open channel SLAVE_LOOPBACK0\n"); + break; + case MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1: + dev_dbg(modem_spi_dev->dev, "open channel MASTER_LOOPBACK1\n"); + break; + case MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1: + dev_dbg(modem_spi_dev->dev, "open channel SLAVE_LOOPBACK1\n"); + break; +#endif + } + +cleanup: + mutex_unlock(&isa_lock); +out: + return err; +} + +const struct file_operations isa_fops = { + .owner = THIS_MODULE, + .open = isa_open, + .release = isa_close, + .unlocked_ioctl = isa_ioctl, + .mmap = isa_mmap, + .read = isa_read, + .write = isa_write, + .poll = isa_select, +}; + +/** + * modem_isa_init() - initialise the modem char device interfaces + * @modem_spi_dev: pointer to the modem driver information structure + * + * This function registers registers as a char device driver and creates the + * char device nodes supported by the modem. + */ +int modem_isa_init(struct modem_spi_dev *modem_spi_dev) +{ + dev_t dev_id; + int retval; + int devidx; + struct isa_device_context *isadev; + struct isa_driver_context *isa_context; + + dev_dbg(modem_spi_dev->dev, "registering char device interfaces\n"); + + isa_context = kzalloc(sizeof(struct isa_driver_context), GFP_KERNEL); + if (isa_context == NULL) { + dev_err(modem_spi_dev->dev, "failed to allocate context\n"); + retval = -ENOMEM; + goto rollback; + } + + modem_spi_dev->isa_context = isa_context; + if (major) { + /* major node specified at module load */ + dev_id = MKDEV(major, 0); + retval = register_chrdev_region(dev_id, + MODEM_M6718_SPI_MAX_CHANNELS, NAME); + } else { + retval = alloc_chrdev_region(&dev_id, 0, + MODEM_M6718_SPI_MAX_CHANNELS, NAME); + major = MAJOR(dev_id); + } + + dev_dbg(modem_spi_dev->dev, "device major is %d\n", major); + + cdev_init(&isa_context->cdev, &isa_fops); + isa_context->cdev.owner = THIS_MODULE; + retval = cdev_add(&isa_context->cdev, dev_id, + MODEM_M6718_SPI_MAX_CHANNELS); + if (retval) { + dev_err(modem_spi_dev->dev, "failed to add char device\n"); + goto rollback_register; + } + + isa_context->modem_class = class_create(THIS_MODULE, NAME); + if (IS_ERR(isa_context->modem_class)) { + dev_err(modem_spi_dev->dev, "failed to create modem class\n"); + retval = PTR_ERR(isa_context->modem_class); + goto rollback_add_dev; + } + isa_context->isadev = kzalloc(sizeof(struct isa_device_context) * + MODEM_M6718_SPI_MAX_CHANNELS, GFP_KERNEL); + if (isa_context->isadev == NULL) { + dev_err(modem_spi_dev->dev, + "failed to allocate device context\n"); + goto rollback_create_dev; + } + + for (devidx = 0; devidx < ARRAY_SIZE(map_dev); devidx++) { + atomic_set(&isa_context->is_open[devidx], 1); + device_create(isa_context->modem_class, + NULL, + MKDEV(MAJOR(dev_id), map_dev[devidx].l2_header), + NULL, + map_dev[devidx].name); + + isadev = &isa_context->isadev[devidx]; + isadev->device_id = devidx; + create_queue(&isadev->dl_queue, + isadev->device_id, modem_spi_dev); + + dev_dbg(modem_spi_dev->dev, "created device %d (%s) (%d.%d)\n", + devidx, map_dev[devidx].name, major, + map_dev[devidx].l2_header); + } + + mutex_init(&isa_context->audio_tx_mutex); + spin_lock_init(&isa_context->common_tx_lock); + + dev_dbg(modem_spi_dev->dev, "registered modem char devices\n"); + return 0; + +rollback_create_dev: + for (devidx = 0; devidx < ARRAY_SIZE(map_dev); devidx++) { + device_destroy(isa_context->modem_class, + MKDEV(MAJOR(dev_id), map_dev[devidx].l2_header)); + } + class_destroy(isa_context->modem_class); +rollback_add_dev: + cdev_del(&isa_context->cdev); +rollback_register: + unregister_chrdev_region(dev_id, MODEM_M6718_SPI_MAX_CHANNELS); + kfree(isa_context); + modem_spi_dev->isa_context = NULL; +rollback: + return retval; +} +EXPORT_SYMBOL_GPL(modem_isa_init); + +/** + * modem_isa_exit() - remove the char device interfaces and clean up + * @modem_spi_dev: pointer to the modem driver information structure + */ +void modem_isa_exit(struct modem_spi_dev *modem_spi_dev) +{ + int devidx; + struct isa_device_context *isadev; + struct isa_driver_context *isa_context = modem_spi_dev->isa_context; + dev_t dev_id = MKDEV(major, 0); + + if (!modem_spi_dev || !modem_spi_dev->isa_context) + return; + + for (devidx = 0; devidx < ARRAY_SIZE(map_dev); devidx++) + device_destroy(isa_context->modem_class, + MKDEV(MAJOR(dev_id), + map_dev[devidx].l2_header)); + for (devidx = 0; devidx < MODEM_M6718_SPI_MAX_CHANNELS; devidx++) { + isadev = &isa_context->isadev[devidx]; + delete_queue(&isadev->dl_queue); + kfree(isadev); + } + class_destroy(isa_context->modem_class); + cdev_del(&isa_context->cdev); + unregister_chrdev_region(dev_id, MODEM_M6718_SPI_MAX_CHANNELS); + kfree(isa_context); + modem_spi_dev->isa_context = NULL; + dev_dbg(modem_spi_dev->dev, "removed modem char devices\n"); +} +EXPORT_SYMBOL_GPL(modem_isa_exit); + +MODULE_AUTHOR("Chris Blair <chris.blair@stericsson.com>"); +MODULE_DESCRIPTION("M6718 modem IPC char device interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/char/shrm_char.c b/drivers/char/shrm_char.c new file mode 100644 index 00000000000..e8f350e5da8 --- /dev/null +++ b/drivers/char/shrm_char.c @@ -0,0 +1,897 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson + * Author: Kumar Sanghavi <kumar.sanghvi@stericsson.com> for ST-Ericsson + * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/sched.h> +#include <linux/modem/shrm/shrm_driver.h> +#include <linux/modem/shrm/shrm_private.h> +#include <linux/modem/shrm/shrm_config.h> +#include <linux/modem/shrm/shrm.h> +#include <asm/atomic.h> + +#include <mach/isa_ioctl.h> + + +#define NAME "IPC_ISA" +/* L2 header for rtc_calibration device is 0xC8 and hence 0xC8 + 1 = 201 */ +#define MAX_L2_HEADERS 201 + +#define SIZE_OF_FIFO (512*1024) + +static u8 message_fifo[ISA_DEVICES][SIZE_OF_FIFO]; + +static u8 wr_rpc_msg[10*1024]; +static u8 wr_sec_msg[10*1024]; +static u8 wr_audio_msg[10*1024]; +static u8 wr_rtc_cal_msg[100]; + +struct map_device { + u8 l2_header; + u8 idx; + char *name; +}; + +static struct map_device map_dev[] = { + {ISI_MESSAGING, 0, "isi"}, + {RPC_MESSAGING, 1, "rpc"}, + {AUDIO_MESSAGING, 2, "modemaudio"}, + {SECURITY_MESSAGING, 3, "sec"}, + {COMMON_LOOPBACK_MESSAGING, 4, "common_loopback"}, + {AUDIO_LOOPBACK_MESSAGING, 5, "audio_loopback"}, + {CIQ_MESSAGING, 6, "ciq"}, + {RTC_CAL_MESSAGING, 7, "rtc_calibration"}, +}; + +/* + * int major:This variable is exported to user as module_param to specify + * major number at load time + */ +static int major; +module_param(major, int, 0); +MODULE_PARM_DESC(major, "Major device number"); +/* global fops mutex */ +static DEFINE_MUTEX(isa_lock); + +/** + * shrm_get_cdev_index() - return the index mapped to l2 header + * @l2_header: L2 header + * + * struct map_device maps the index(count) with the device L2 header. + * This function returns the index for the provided L2 header in case + * of success else -ve value. + */ +int shrm_get_cdev_index(u8 l2_header) +{ + u8 cnt; + for (cnt = 0; cnt < ISA_DEVICES; cnt++) { + if (map_dev[cnt].l2_header == l2_header) + return map_dev[cnt].idx; + } + return -EINVAL; +} + +/** + * shrm_get_cdev_l2header() - return l2_header mapped to the index + * @idx: index + * + * struct map_device maps the index(count) with the device L2 header. + * This function returns the L2 header for the given index in case + * of success else -ve value. + */ +int shrm_get_cdev_l2header(u8 idx) +{ + u8 cnt; + for (cnt = 0; cnt < ISA_DEVICES; cnt++) { + if (map_dev[cnt].idx == idx) + return map_dev[cnt].l2_header; + } + return -EINVAL; +} + +void shrm_char_reset_queues(struct shrm_dev *shrm) +{ + struct isadev_context *isadev; + struct isa_driver_context *isa_context; + struct queue_element *cur_msg = NULL; + struct list_head *cur_msg_ptr = NULL; + struct list_head *msg_ptr; + struct message_queue *q; + int no_dev; + + dev_info(shrm->dev, "%s: Resetting char device queues\n", __func__); + isa_context = shrm->isa_context; + for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) { + isadev = &isa_context->isadev[no_dev]; + q = &isadev->dl_queue; + + spin_lock_bh(&q->update_lock); + /* empty out the msg queue */ + list_for_each_safe(cur_msg_ptr, msg_ptr, &q->msg_list) { + cur_msg = list_entry(cur_msg_ptr, + struct queue_element, entry); + list_del(cur_msg_ptr); + kfree(cur_msg); + } + + /* reset the msg queue pointers */ + q->size = SIZE_OF_FIFO; + q->readptr = 0; + q->writeptr = 0; + q->no = 0; + + /* wake up the blocking read/select */ + atomic_set(&q->q_rp, 1); + wake_up_interruptible(&q->wq_readable); + + spin_unlock_bh(&q->update_lock); + } +} + +/** + * create_queue() - To create FIFO for Tx and Rx message buffering. + * @q: message queue. + * @devicetype: device type 0-isi,1-rpc,2-audio,3-security, + * 4-common_loopback, 5-audio_loopback. + * @shrm: pointer to the shrm device information structure + * + * This function creates a FIFO buffer of n_bytes size using + * dma_alloc_coherent(). It also initializes all queue handling + * locks, queue management pointers. It also initializes message list + * which occupies this queue. + */ +static int create_queue(struct message_queue *q, u32 devicetype, + struct shrm_dev *shrm) +{ + q->fifo_base = (u8 *)&message_fifo[devicetype]; + q->size = SIZE_OF_FIFO; + q->readptr = 0; + q->writeptr = 0; + q->no = 0; + q->shrm = shrm; + spin_lock_init(&q->update_lock); + INIT_LIST_HEAD(&q->msg_list); + init_waitqueue_head(&q->wq_readable); + atomic_set(&q->q_rp, 0); + + return 0; +} + +static void delete_queue(struct message_queue *q) +{ + q->size = 0; + q->readptr = 0; + q->writeptr = 0; +} + +/** + * add_msg_to_queue() - Add a message inside queue + * @q: message queue + * @size: size in bytes + * + * This function tries to allocate n_bytes of size in FIFO q. + * It returns negative number when no memory can be allocated + * currently. + */ +int add_msg_to_queue(struct message_queue *q, u32 size) +{ + struct queue_element *new_msg = NULL; + struct shrm_dev *shrm = q->shrm; + + dev_dbg(shrm->dev, "%s IN q->writeptr=%d\n", __func__, q->writeptr); + new_msg = kmalloc(sizeof(struct queue_element), GFP_ATOMIC); + if (new_msg == NULL) { + dev_err(shrm->dev, "unable to allocate memory\n"); + return -ENOMEM; + } + new_msg->offset = q->writeptr; + new_msg->size = size; + new_msg->no = q->no++; + + /* check for overflow condition */ + if (q->readptr <= q->writeptr) { + if (((q->writeptr-q->readptr) + size) >= q->size) { + dev_err(shrm->dev, "Buffer overflow !!\n"); + BUG_ON(((q->writeptr-q->readptr) + size) >= q->size); + } + } else { + if ((q->writeptr + size) >= q->readptr) { + dev_err(shrm->dev, "Buffer overflow !!\n"); + BUG_ON((q->writeptr + size) >= q->readptr); + } + } + q->writeptr = (q->writeptr + size) % q->size; + if (list_empty(&q->msg_list)) { + list_add_tail(&new_msg->entry, &q->msg_list); + /* There can be 2 blocking calls read and another select */ + atomic_set(&q->q_rp, 1); + wake_up_interruptible(&q->wq_readable); + } else + list_add_tail(&new_msg->entry, &q->msg_list); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return 0; +} + +/** + * remove_msg_from_queue() - To remove a message from the msg queue. + * @q: message queue + * + * This function delets a message from the message list associated with message + * queue q and also updates read ptr. + * If the message list is empty, then, event is set to block the select and + * read calls of the paricular queue. + * + * The message list is FIFO style and message is always added to tail and + * removed from head. + */ +int remove_msg_from_queue(struct message_queue *q) +{ + struct queue_element *old_msg = NULL; + struct shrm_dev *shrm = q->shrm; + struct list_head *msg_ptr = NULL; + struct list_head *old_msg_ptr = NULL; + + dev_dbg(shrm->dev, "%s IN q->readptr %d\n", __func__, q->readptr); + + list_for_each_safe(old_msg_ptr, msg_ptr, &q->msg_list) { + old_msg = list_entry(old_msg_ptr, struct queue_element, entry); + if (old_msg == NULL) { + dev_err(shrm->dev, "no message found\n"); + return -EFAULT; + } + list_del(old_msg_ptr); + q->readptr = (q->readptr + old_msg->size)%q->size; + kfree(old_msg); + break; + } + if (list_empty(&q->msg_list)) { + dev_dbg(shrm->dev, "List is empty setting RP= 0\n"); + atomic_set(&q->q_rp, 0); + } + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return 0; +} + +/** + * get_size_of_new_msg() - retrieve new message from message list + * @q: message queue + * + * This function will retrieve most recent message from the corresponding + * queue list. New message is always retrieved from head side. + * It returns new message no, offset if FIFO and size. + */ +int get_size_of_new_msg(struct message_queue *q) +{ + struct queue_element *new_msg = NULL; + struct list_head *msg_list; + struct shrm_dev *shrm = q->shrm; + int size = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + spin_lock_bh(&q->update_lock); + list_for_each(msg_list, &q->msg_list) { + new_msg = list_entry(msg_list, struct queue_element, entry); + if (new_msg == NULL) { + spin_unlock_bh(&q->update_lock); + dev_err(shrm->dev, "no message found\n"); + return -EFAULT; + } + size = new_msg->size; + break; + } + spin_unlock_bh(&q->update_lock); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return size; +} + +/** + * isa_select() - shrm char interface driver select interface + * @filp: file descriptor pointer + * @wait: poll_table_struct pointer + * + * This function is used to perform non-blocking read operations. It allows + * a process to determine whether it can read from one or more open files + * without blocking. These calls can also block a process until any of a + * given set of file descriptors becomes available for reading. + * If a file is ready to read, POLLIN | POLLRDNORM bitmask is returned. + * The driver method is called whenever the user-space program performs a select + * system call involving a file descriptor associated with the driver. + */ +static u32 isa_select(struct file *filp, + struct poll_table_struct *wait) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + u32 mask = 0; + u32 m = iminor(filp->f_path.dentry->d_inode); + u8 idx = shrm_get_cdev_index(m); + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (shrm->msr_flag) + return -ENODEV; + + if (isadev->device_id != idx) + return -1; + + q = &isadev->dl_queue; + poll_wait(filp, &q->wq_readable, wait); + if (atomic_read(&q->q_rp) == 1) + mask = POLLIN | POLLRDNORM; + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return mask; +} + +/** + * isa_read() - Read from device + * @filp: file descriptor + * @buf: user buffer pointer + * @len: size of requested data transfer + * @ppos: not used + * + * It reads a oldest message from queue and copies it into user buffer and + * returns its size. + * If there is no message present in queue, then it blocks until new data is + * available. + */ +ssize_t isa_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos) +{ + u32 size = 0; + int ret; + char *psrc; + struct isadev_context *isadev = (struct isadev_context *) + filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + u32 msgsize; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (len <= 0) + return -EFAULT; + + q = &isadev->dl_queue; + + if (shrm->msr_flag) { + atomic_set(&q->q_rp, 0); + return -ENODEV; + } + + spin_lock_bh(&q->update_lock); + if (list_empty(&q->msg_list)) { + spin_unlock_bh(&q->update_lock); + dev_dbg(shrm->dev, "Waiting for Data\n"); + if (wait_event_interruptible(q->wq_readable, + atomic_read(&q->q_rp) == 1)) + return -ERESTARTSYS; + } else + spin_unlock_bh(&q->update_lock); + + if (shrm->msr_flag) { + atomic_set(&q->q_rp, 0); + return -ENODEV; + } + + msgsize = get_size_of_new_msg(q); + + if (len < msgsize) + return -EINVAL; + + if ((q->readptr+msgsize) >= q->size) { + dev_dbg(shrm->dev, "Inside Loop Back\n"); + psrc = (char *)buf; + size = (q->size-q->readptr); + /* Copy First Part of msg */ + if (copy_to_user(psrc, + (u8 *)(q->fifo_base+q->readptr), + size)) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + if (copy_to_user(psrc, + (u8 *)(q->fifo_base), + (msgsize-size))) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + } else { + if (copy_to_user(buf, + (u8 *)(q->fifo_base + q->readptr), + msgsize)) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + } + spin_lock_bh(&q->update_lock); + ret = remove_msg_from_queue(q); + if (ret < 0) { + dev_err(shrm->dev, + "Remove msg from message queue failed\n"); + msgsize = ret; + } + spin_unlock_bh(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return msgsize; +} + +/** + * isa_write() - Write to shrm char device + * @filp: file descriptor + * @buf: user buffer pointer + * @len: size of requested data transfer + * @ppos: not used + * + * It checks if there is space available in queue, and copies the message + * inside queue. If there is no space, it blocks until space becomes available. + * It also schedules transfer thread to transmit the newly added message. + */ +ssize_t isa_write(struct file *filp, const char __user *buf, + size_t len, loff_t *ppos) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + void *addr = 0; + int err, l2_header; + int ret = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (len <= 0 || buf == NULL) + return -EFAULT; + q = &isadev->dl_queue; + l2_header = shrm_get_cdev_l2header(isadev->device_id); + if (l2_header < 0) { + dev_err(shrm->dev, "failed to get L2 header\n"); + return l2_header; + } + + switch (l2_header) { + case RPC_MESSAGING: + dev_dbg(shrm->dev, "RPC\n"); + addr = (void *)wr_rpc_msg; + break; + case AUDIO_MESSAGING: + dev_dbg(shrm->dev, "Audio\n"); + addr = (void *)wr_audio_msg; + break; + case SECURITY_MESSAGING: + dev_dbg(shrm->dev, "Security\n"); + addr = (void *)wr_sec_msg; + break; + case COMMON_LOOPBACK_MESSAGING: + dev_dbg(shrm->dev, "Common loopback\n"); + addr = isadev->addr; + break; + case AUDIO_LOOPBACK_MESSAGING: + dev_dbg(shrm->dev, "Audio loopback\n"); + addr = isadev->addr; + break; + case CIQ_MESSAGING: + dev_dbg(shrm->dev, "CIQ\n"); + addr = isadev->addr; + break; + case RTC_CAL_MESSAGING: + dev_dbg(shrm->dev, "isa_write(): RTC Calibration\n"); + addr = (void *)wr_rtc_cal_msg; + break; + default: + dev_dbg(shrm->dev, "Wrong device\n"); + return -EFAULT; + } + + if (copy_from_user(addr, buf, len)) { + dev_err(shrm->dev, "copy_from_user failed\n"); + return -EFAULT; + } + /* Write msg to Fifo */ + if ((l2_header == AUDIO_MESSAGING) || + (l2_header == AUDIO_LOOPBACK_MESSAGING)) { + mutex_lock(&shrm->isa_context->tx_audio_mutex); + err = shm_write_msg(shrm, l2_header, addr, len); + if (!err) + ret = len; + else + ret = err; + mutex_unlock(&shrm->isa_context->tx_audio_mutex); + } else { + spin_lock_bh(&shrm->isa_context->common_tx); + err = shm_write_msg(shrm, l2_header, addr, len); + if (!err) + ret = len; + else + ret = err; + spin_unlock_bh(&shrm->isa_context->common_tx); + } + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * isa_ioctl() - To handle different ioctl commands supported by driver. + * @inode: structure is used by the kernel internally to represent files + * @filp: file descriptor pointer + * @cmd: ioctl command + * @arg: input param + * + * Following ioctls are supported by this driver. + * DLP_IOCTL_ALLOCATE_BUFFER - To allocate buffer for new uplink message. + * This ioctl is called with required message size. It returns offset for + * the allocates space in the queue. DLP_IOCTL_PUT_MESSAGE - To indicate + * new uplink message available in queuq for transmission. Message is copied + * from offset location returned by previous ioctl before calling this ioctl. + * DLP_IOCTL_GET_MESSAGE - To check if any downlink message is available in + * queue. It returns offset for new message inside queue. + * DLP_IOCTL_DEALLOCATE_BUFFER - To deallocate any buffer allocate for + * downlink message once the message is copied. Message is copied from offset + * location returned by previous ioctl before calling this ioctl. + */ +static long isa_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + long err = 0; + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + u32 m = iminor(filp->f_path.dentry->d_inode); + + isadev = (struct isadev_context *)filp->private_data; + + if (isadev->device_id != m) + return -EINVAL; + + switch (cmd) { + case DLP_IOC_ALLOCATE_BUFFER: + dev_dbg(shrm->dev, "DLP_IOC_ALLOCATE_BUFFER\n"); + break; + case DLP_IOC_PUT_MESSAGE: + dev_dbg(shrm->dev, "DLP_IOC_PUT_MESSAGE\n"); + break; + case DLP_IOC_GET_MESSAGE: + dev_dbg(shrm->dev, "DLP_IOC_GET_MESSAGE\n"); + break; + case DLP_IOC_DEALLOCATE_BUFFER: + dev_dbg(shrm->dev, "DLP_IOC_DEALLOCATE_BUFFER\n"); + break; + default: + dev_dbg(shrm->dev, "Unknown IOCTL\n"); + err = -EFAULT; + break; + } + return err; +} +/** + * isa_mmap() - Maps kernel queue memory to user space. + * @filp: file descriptor pointer + * @vma: virtual area memory structure. + * + * This function maps kernel FIFO into user space. This function + * shall be called twice to map both uplink and downlink buffers. + */ +static int isa_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + + u32 m = iminor(filp->f_path.dentry->d_inode); + dev_dbg(shrm->dev, "%s %d\n", __func__, m); + + return 0; +} + +/** + * isa_close() - Close device file + * @inode: structure is used by the kernel internally to represent files + * @filp: device file descriptor + * + * This function deletes structues associated with this file, deletes + * queues, flushes and destroys workqueus and closes this file. + * It also unregisters itself from l2mux driver. + */ +static int isa_close(struct inode *inode, struct file *filp) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct isa_driver_context *isa_context = shrm->isa_context; + u8 m; + int idx; + + mutex_lock(&isa_lock); + m = iminor(filp->f_path.dentry->d_inode); + idx = shrm_get_cdev_index(m); + if (idx < 0) { + dev_err(shrm->dev, "failed to get index\n"); + mutex_unlock(&isa_lock); + return idx; + } + dev_dbg(shrm->dev, "isa_close %d", m); + + if (atomic_dec_and_test(&isa_context->is_open[idx])) { + atomic_inc(&isa_context->is_open[idx]); + dev_err(shrm->dev, "Device not opened yet\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + } + atomic_set(&isa_context->is_open[idx], 1); + + switch (m) { + case RPC_MESSAGING: + dev_info(shrm->dev, "Close RPC_MESSAGING Device\n"); + break; + case AUDIO_MESSAGING: + dev_info(shrm->dev, "Close AUDIO_MESSAGING Device\n"); + break; + case SECURITY_MESSAGING: + dev_info(shrm->dev, "CLose SECURITY_MESSAGING Device\n"); + break; + case COMMON_LOOPBACK_MESSAGING: + kfree(isadev->addr); + dev_info(shrm->dev, "Close COMMON_LOOPBACK_MESSAGING Device\n"); + break; + case AUDIO_LOOPBACK_MESSAGING: + kfree(isadev->addr); + dev_info(shrm->dev, "Close AUDIO_LOOPBACK_MESSAGING Device\n"); + break; + case CIQ_MESSAGING: + kfree(isadev->addr); + dev_info(shrm->dev, "Close CIQ_MESSAGING Device\n"); + break; + case RTC_CAL_MESSAGING: + dev_info(shrm->dev, "Close RTC_CAL_MESSAGING Device\n"); + break; + default: + dev_info(shrm->dev, "No such device present\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + }; + mutex_unlock(&isa_lock); + return 0; +} +/** + * isa_open() - Open device file + * @inode: structure is used by the kernel internally to represent files + * @filp: device file descriptor + * + * This function performs initialization tasks needed to open SHM channel. + * Following tasks are performed. + * -return if device is already opened + * -create uplink FIFO + * -create downlink FIFO + * -init delayed workqueue thread + * -register to l2mux driver + */ +static int isa_open(struct inode *inode, struct file *filp) +{ + int err = 0; + u8 m; + int idx; + struct isadev_context *isadev; + struct isa_driver_context *isa_context = container_of( + inode->i_cdev, + struct isa_driver_context, + cdev); + struct shrm_dev *shrm = isa_context->isadev->dl_queue.shrm; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (get_boot_state() != BOOT_DONE) { + dev_err(shrm->dev, "Boot is not done\n"); + return -EBUSY; + } + mutex_lock(&isa_lock); + m = iminor(inode); + + if ((m != RPC_MESSAGING) && + (m != AUDIO_LOOPBACK_MESSAGING) && + (m != COMMON_LOOPBACK_MESSAGING) && + (m != AUDIO_MESSAGING) && + (m != SECURITY_MESSAGING) && + (m != CIQ_MESSAGING) && + (m != RTC_CAL_MESSAGING)) { + dev_err(shrm->dev, "No such device present\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + } + idx = shrm_get_cdev_index(m); + if (idx < 0) { + dev_err(shrm->dev, "failed to get index\n"); + mutex_unlock(&isa_lock); + return idx; + } + if (!atomic_dec_and_test(&isa_context->is_open[idx])) { + atomic_inc(&isa_context->is_open[idx]); + dev_err(shrm->dev, "Device already opened\n"); + mutex_unlock(&isa_lock); + return -EBUSY; + } + isadev = &isa_context->isadev[idx]; + if (filp != NULL) + filp->private_data = isadev; + + switch (m) { + case RPC_MESSAGING: + dev_info(shrm->dev, "Open RPC_MESSAGING Device\n"); + break; + case AUDIO_MESSAGING: + dev_info(shrm->dev, "Open AUDIO_MESSAGING Device\n"); + break; + case SECURITY_MESSAGING: + dev_info(shrm->dev, "Open SECURITY_MESSAGING Device\n"); + break; + case COMMON_LOOPBACK_MESSAGING: + isadev->addr = kzalloc(10 * 1024, GFP_KERNEL); + if (!isadev->addr) { + mutex_unlock(&isa_lock); + return -ENOMEM; + } + dev_info(shrm->dev, "Open COMMON_LOOPBACK_MESSAGING Device\n"); + break; + case AUDIO_LOOPBACK_MESSAGING: + isadev->addr = kzalloc(10 * 1024, GFP_KERNEL); + if (!isadev->addr) { + mutex_unlock(&isa_lock); + return -ENOMEM; + } + dev_info(shrm->dev, "Open AUDIO_LOOPBACK_MESSAGING Device\n"); + break; + case CIQ_MESSAGING: + isadev->addr = kzalloc(10 * 1024, GFP_KERNEL); + if (!isadev->addr) { + mutex_unlock(&isa_lock); + return -ENOMEM; + } + dev_info(shrm->dev, "Open CIQ_MESSAGING Device\n"); + break; + case RTC_CAL_MESSAGING: + dev_info(shrm->dev, "Open RTC_CAL_MESSAGING Device\n"); + break; + }; + + mutex_unlock(&isa_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return err; +} + +const struct file_operations isa_fops = { + .owner = THIS_MODULE, + .open = isa_open, + .release = isa_close, + .unlocked_ioctl = isa_ioctl, + .mmap = isa_mmap, + .read = isa_read, + .write = isa_write, + .poll = isa_select, +}; + +/** + * isa_init() - module insertion function + * @shrm: pointer to the shrm device information structure + * + * This function registers module as a character driver using + * register_chrdev_region() or alloc_chrdev_region. It adds this + * driver to system using cdev_add() call. Major number is dynamically + * allocated using alloc_chrdev_region() by default or left to user to specify + * it during load time. For this variable major is used as module_param + * Nodes to be created using + * mknod /dev/isi c $major 0 + * mknod /dev/rpc c $major 1 + * mknod /dev/audio c $major 2 + * mknod /dev/sec c $major 3 + */ +int isa_init(struct shrm_dev *shrm) +{ + dev_t dev_id; + int retval, no_dev; + struct isadev_context *isadev; + struct isa_driver_context *isa_context; + + isa_context = kzalloc(sizeof(struct isa_driver_context), + GFP_KERNEL); + if (isa_context == NULL) { + dev_err(shrm->dev, "Failed to alloc memory\n"); + return -ENOMEM; + } + shrm->isa_context = isa_context; + if (major) { + dev_id = MKDEV(major, MAX_L2_HEADERS); + retval = register_chrdev_region(dev_id, ISA_DEVICES, NAME); + } else { + /* + * L2 header of loopback device is 192(0xc0). As per the shrm + * protocol the minor id of the deivce is mapped to the + * L2 header. + */ + retval = alloc_chrdev_region(&dev_id, 0, MAX_L2_HEADERS, NAME); + major = MAJOR(dev_id); + } + dev_dbg(shrm->dev, " major %d\n", major); + + cdev_init(&isa_context->cdev, &isa_fops); + isa_context->cdev.owner = THIS_MODULE; + retval = cdev_add(&isa_context->cdev, dev_id, MAX_L2_HEADERS); + if (retval) { + dev_err(shrm->dev, "Failed to add char device\n"); + return retval; + } + /* create class and device */ + isa_context->shm_class = class_create(THIS_MODULE, NAME); + if (IS_ERR(isa_context->shm_class)) { + dev_err(shrm->dev, "Error creating shrm class\n"); + cdev_del(&isa_context->cdev); + retval = PTR_ERR(isa_context->shm_class); + kfree(isa_context); + return retval; + } + + for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) { + atomic_set(&isa_context->is_open[no_dev], 1); + device_create(isa_context->shm_class, NULL, + MKDEV(MAJOR(dev_id), + map_dev[no_dev].l2_header), NULL, + map_dev[no_dev].name); + } + + isa_context->isadev = kzalloc(sizeof + (struct isadev_context)*ISA_DEVICES, + GFP_KERNEL); + if (isa_context->isadev == NULL) { + dev_err(shrm->dev, "Failed to alloc memory\n"); + return -ENOMEM; + } + for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) { + isadev = &isa_context->isadev[no_dev]; + isadev->device_id = no_dev; + retval = create_queue(&isadev->dl_queue, + isadev->device_id, shrm); + + if (retval < 0) { + dev_err(shrm->dev, "create dl_queue failed\n"); + delete_queue(&isadev->dl_queue); + kfree(isadev); + return retval; + } + } + mutex_init(&isa_context->tx_audio_mutex); + spin_lock_init(&isa_context->common_tx); + dev_dbg(shrm->dev, " SHM Char Driver added\n"); + return retval; +} + +void isa_exit(struct shrm_dev *shrm) +{ + int no_dev; + struct isadev_context *isadev; + struct isa_driver_context *isa_context = shrm->isa_context; + dev_t dev_id = MKDEV(major, 0); + + for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) { + device_destroy(isa_context->shm_class, + MKDEV(MAJOR(dev_id), + map_dev[no_dev].l2_header)); + isadev = &isa_context->isadev[no_dev]; + delete_queue(&isadev->dl_queue); + kfree(isadev); + } + class_destroy(isa_context->shm_class); + cdev_del(&isa_context->cdev); + unregister_chrdev_region(dev_id, ISA_DEVICES); + kfree(isa_context); + dev_dbg(shrm->dev, " SHM Char Driver removed\n"); +} diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c7f39c78e7d..6c97ebfb5b9 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -451,6 +451,20 @@ config ARM_CHARLCD line and the Linux version on the second line, but that's still useful. +config STE_TRACE_MODEM + tristate "DB8500 trace Modem" + depends on ARCH_U8500 + default n + help + Select this option to enable modem tracing by APE + +config DBX500_MLOADER + tristate "Modem firmware loader for db8500" + default n + depends on UX500_SOC_DB8500 || UX500_SOC_DB5500 + help + Provides a user interface to load modem firmware on dbx500 SOCs + config BMP085 tristate "BMP085 digital pressure sensor" depends on I2C && SYSFS @@ -507,6 +521,22 @@ config HWMEM can be used by hardware. It also enables accessing hwmem allocated memory buffers through a secure id which can be shared across processes. +config U5500_MBOX + bool "Mailbox support" + depends on (UX500_SOC_DB5500 && U5500_MODEM_IRQ) + default y + help + Add support for U5500 mailbox communication with modem side + +config U8500_SIM_DETECT + bool "Sim hot swap detection support" + depends on (MODEM && UX500_SOC_DB8500) + default n + help + Add support for sim hot swap detection support in U8500.Driver + basically wakes up the modem if its sleeping when sim hot plug + in/out has happened. + config USB_SWITCH_FSA9480 tristate "FSA9480 USB Switch" depends on I2C @@ -533,4 +563,5 @@ source "drivers/misc/ti-st/Kconfig" source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" source "drivers/misc/altera-stapl/Kconfig" +source "drivers/misc/modem_audio/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 25c8efc695d..17c50c1b1d2 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -50,6 +50,11 @@ obj-$(CONFIG_STM_TRACE) += stm.o obj-$(CONFIG_HWMEM) += hwmem/ obj-$(CONFIG_DISPDEV) += dispdev/ obj-$(CONFIG_COMPDEV) += compdev/ +obj-$(CONFIG_STE_TRACE_MODEM) += db8500-modem-trace.o +obj-$(CONFIG_DBX500_MLOADER) += dbx500-mloader.o +obj-$(CONFIG_U5500_MBOX) += mbox.o mbox_channels-db5500.o +obj-$(CONFIG_U8500_SIM_DETECT) += sim_detect.o obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ obj-$(CONFIG_MAX8997_MUIC) += max8997-muic.o +obj-y += modem_audio/ diff --git a/drivers/misc/db8500-modem-trace.c b/drivers/misc/db8500-modem-trace.c new file mode 100644 index 00000000000..b757b742121 --- /dev/null +++ b/drivers/misc/db8500-modem-trace.c @@ -0,0 +1,273 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: Michel JAOUEN <michel.jaouen@stericsson.com> + * Maxime COQUELIN <maxime.coquelin-nonst@stericsson.com> + * for ST-Ericsson + * License terms: GNU General Public License (GPL), version 2 + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/cdev.h> +#include <linux/poll.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/uaccess.h> +#include <linux/mman.h> +#include <linux/db8500-modem-trace.h> + +#include <mach/hardware.h> + +#define DEVICE_NAME "db8500-modem-trace" + +/* activation of this flag triggers an initialization of 2 buffers + * 4kbytes , id 0xdeadbeef + * and 16Kbytes id 0xfadafada + * we assume that platform provides minimum 20Kbytes. */ + +struct trace { + u32 start; + u32 end; + u32 mdm_base; + u32 ape_base; + void __iomem *area; + /* this spinlock to forbid concurrent access on the same trace buffer */ + spinlock_t lock; + struct device *dev; + struct miscdevice misc_dev; +}; + +struct trace_modem { + u32 phys_addr; + u8 filler; +}; + +static struct trace *trace_priv; + + +/* all this definition are linked to modem interface */ +#define MODEM_MARKER 0x88 +/* free marker is also written on filler */ +#define FREE_MARKER 0xa5 +#define FREE_MARKER_2 0xa5a5 +#define READ_MARKER 0x5a + +struct buffer_header { + u8 pattern; + u8 filler; + u16 head_size; +}; + + +static int trace_read(unsigned long arg) +{ + struct modem_trace_req req; + struct buffer_header *pt; + char tmp_char; + + if (copy_from_user(&req, (struct modem_trace_req *)arg, + sizeof(struct modem_trace_req))) + return -EFAULT; + + /* compute Modem physical address to APE physical address range */ + if (req.phys_addr < trace_priv->mdm_base) { + dev_err(trace_priv->dev, "MODEM ADDR uncorrect\n"); + return -EINVAL; + } + req.phys_addr += trace_priv->ape_base - trace_priv->mdm_base; + + /* check request is in the range and aligned */ + if ((req.phys_addr % 4 != 0) + || (req.phys_addr < trace_priv->start) + || (req.phys_addr + req.size) >= trace_priv->end) { + dev_err(trace_priv->dev, "req out of range %x %x\n", + req.phys_addr, req.size); + return -EINVAL; + } + + /* perform access to memory area */ + pt = (struct buffer_header *)((u32)trace_priv->area + + req.phys_addr - trace_priv->start); + + /* in case of several request coming on same trace buffer take a + * spinlock */ + spin_lock(&trace_priv->lock); + if (pt->pattern != MODEM_MARKER) { + /* pattern and size not matching */ + dev_err(trace_priv->dev, "req not matching filler %x/%x \ + or/and pattern %x\n", req.filler, pt->filler, + pt->pattern); + spin_unlock(&trace_priv->lock); + return -EINVAL; + } + /* mark pattern as read and unlock spin */ + pt->pattern = READ_MARKER; + spin_unlock(&trace_priv->lock); + + req.size -= copy_to_user(req.buff, pt, req.size); + + pt->pattern = FREE_MARKER; + pt->filler = FREE_MARKER; + tmp_char = MODEM_MARKER; + + /* Update marker for trace tool */ + if (copy_to_user(req.buff, &tmp_char, 1)) + return -EFAULT; + + /* Update effective written size */ + if (copy_to_user((struct modem_trace_req *)arg, &req, + sizeof(struct modem_trace_req))) + return -EFAULT; + + return 0; +} + +static int trace_mmapdump(struct file *file, struct vm_area_struct *vma) +{ + unsigned long vma_start = vma->vm_start; + + if (vma->vm_flags & VM_WRITE) + return -EPERM; + + if ((vma->vm_end - vma->vm_start) < + (trace_priv->end - trace_priv->start)) + return -EINVAL; + if (remap_pfn_range(vma, + vma_start, + trace_priv->start >> PAGE_SHIFT, + trace_priv->end - trace_priv->start, + vma->vm_page_prot)) + return -EAGAIN; + return 0; +} + +static long trace_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + long ret = 0; + void __user *argp = (void __user *)arg; + unsigned long size = trace_priv->end-trace_priv->start; + + switch (cmd) { + case TM_GET_DUMPINFO: + ret = put_user(size, (unsigned long *)argp); + break; + case TM_TRACE_REQ: + ret = trace_read(arg); + break; + + default: + ret = -EPERM; + break; + } + return ret; +} + +static const struct file_operations trace_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = trace_ioctl, + .mmap = trace_mmapdump +}; + +static int trace_probe(struct platform_device *pdev) +{ + int rv = 0; + struct db8500_trace_platform_data *pdata = pdev->dev.platform_data; + /* retrieve area descriptor from platform device ressource */ + struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if ((mem->start == 0) && (mem->end == 0)) { + rv = -EINVAL; + goto out; + } + + if ((pdata->ape_base == 0) || (pdata->modem_base == 0)) { + rv = -EINVAL; + goto out; + } + + trace_priv = kzalloc(sizeof(*trace_priv), GFP_ATOMIC); + if (!trace_priv) { + rv = -ENOMEM; + goto out; + } + + trace_priv->dev = &pdev->dev; + trace_priv->misc_dev.minor = MISC_DYNAMIC_MINOR; + trace_priv->misc_dev.name = DEVICE_NAME; + trace_priv->misc_dev.fops = &trace_fops; + trace_priv->area = (void __iomem *)ioremap_nocache(mem->start, + resource_size(mem)); + if (!trace_priv->area) { + rv = -ENOMEM; + goto outfree; + } + + trace_priv->start = mem->start; + trace_priv->end = mem->end; + + trace_priv->mdm_base = pdata->modem_base; + trace_priv->ape_base = pdata->ape_base; + + /* spin allowing smp access for reading/writing trace buffer header */ + spin_lock_init(&trace_priv->lock); + + rv = misc_register(&trace_priv->misc_dev); + if (rv) { + dev_err(&pdev->dev, "can't misc_register\n"); + goto outunmap; + } + + return rv; + +outunmap: + iounmap(trace_priv->area); +outfree: + kfree(trace_priv); +out: + return rv; + +} + +static int trace_remove(struct platform_device *pdev) +{ + int rv = 0; + + if (trace_priv) { + rv = misc_deregister(&trace_priv->misc_dev); + iounmap(trace_priv->area); + kfree(trace_priv); + } + + return rv; +} + +static struct platform_driver trace_driver = { + .probe = trace_probe, + .remove = trace_remove, + .driver = { + .name = "db8500-modem-trace", + .owner = THIS_MODULE, + }, +}; + +static int trace_init(void) +{ + platform_driver_register(&trace_driver); + return 0; +} +static void trace_exit(void) +{ + platform_driver_unregister(&trace_driver); +} +module_init(trace_init); +module_exit(trace_exit); + +MODULE_AUTHOR("ST-Ericsson"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/dbx500-mloader.c b/drivers/misc/dbx500-mloader.c new file mode 100644 index 00000000000..c3ec8b67983 --- /dev/null +++ b/drivers/misc/dbx500-mloader.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Ludovic Barre <ludovic.barre@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/miscdevice.h> +#include <linux/firmware.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/uaccess.h> +#include <linux/mman.h> +#include <linux/io.h> + +#include <mach/mloader-dbx500.h> +#include <linux/mloader.h> + +#define DEVICE_NAME "dbx500_mloader_fw" + +struct mloader_priv { + struct platform_device *pdev; + struct dbx500_mloader_pdata *pdata; + struct miscdevice misc_dev; + u32 aeras_size; +}; + +static struct mloader_priv *mloader_priv; + +static int mloader_fw_send(struct dbx500_ml_fw *fw_info) +{ + const struct firmware *fw; + unsigned long size; + unsigned long phys_start; + void *fw_data; + void *vaddr; + void __iomem *ioaddr; + int ret; + + ret = request_firmware(&fw, fw_info->name, &mloader_priv->pdev->dev); + if (ret) { + dev_err(&mloader_priv->pdev->dev, "request firmware failed\n"); + goto out; + } + + if (fw->size > (fw_info->area->size - fw_info->offset)) { + dev_err(&mloader_priv->pdev->dev, + "fw:%s is too big for:%s\n", + fw_info->name, fw_info->area->name); + ret = -EINVAL; + goto err_fw; + } + + size = PAGE_ALIGN(fw->size); + phys_start = fw_info->area->start + fw_info->offset; + phys_start &= PAGE_MASK; + ioaddr = ioremap(phys_start, size); + if (!ioaddr) { + dev_err(&mloader_priv->pdev->dev, + "failed remap memory region.\n"); + ret = -EINVAL; + goto err_fw; + } + + vaddr = ioaddr + (fw_info->offset & ~PAGE_MASK); + fw_data = (void *)fw->data; + memcpy_toio(vaddr, fw_data, fw->size); + iounmap(ioaddr); + +err_fw: + release_firmware(fw); +out: + return ret; +} + +static int mloader_fw_upload(void) +{ + int i, ret; + struct dbx500_mloader_pdata *pdata = mloader_priv->pdata; + + for (i = 0; i < pdata->nr_fws; i++) { + ret = mloader_fw_send(&pdata->fws[i]); + if (ret) + goto err; + } + + return 0; +err: + dev_err(&mloader_priv->pdev->dev, + "Failed to upload %s firmware", pdata->fws[i].name); + return ret; +} + +static int mloader_fw_mmapdump(struct file *file, struct vm_area_struct *vma) +{ + int i; + unsigned long dump_size = 0; + unsigned long vma_start = vma->vm_start; + + if (vma->vm_flags & VM_WRITE) + return -EPERM; + + for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) + dump_size += mloader_priv->pdata->areas[i].size; + + if ((vma->vm_end - vma->vm_start) < dump_size) + return -EINVAL; + + for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) { + if (remap_pfn_range(vma, + vma_start, + mloader_priv->pdata->areas[i].start >> PAGE_SHIFT, + mloader_priv->pdata->areas[i].size, + vma->vm_page_prot)) + return -EAGAIN; + vma_start += mloader_priv->pdata->areas[i].size; + } + return 0; +} + +static void mloader_fw_dumpinfo(struct dump_image *images) +{ + u32 offset = 0; + int i; + + for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) { + strncpy(images[i].name, + mloader_priv->pdata->areas[i].name, MAX_NAME); + images[i].name[MAX_NAME-1] = 0; + images[i].offset = offset; + images[i].size = mloader_priv->pdata->areas[i].size; + offset += mloader_priv->pdata->areas[i].size; + } +} + +static long mloader_fw_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + long ret = 0; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case ML_UPLOAD: + ret = mloader_fw_upload(); + break; + case ML_GET_NBIMAGES: + ret = put_user(mloader_priv->pdata->nr_areas, + (unsigned long __user *)argp); + break; + case ML_GET_DUMPINFO: { + struct dump_image *dump_images; + dump_images = kzalloc(mloader_priv->pdata->nr_areas + * sizeof(struct dump_image), GFP_ATOMIC); + mloader_fw_dumpinfo(dump_images); + ret = copy_to_user(argp, (void *) dump_images, + mloader_priv->pdata->nr_areas + * sizeof(struct dump_image)) ? -EFAULT : 0; + kfree(dump_images); + break; + } + default: + ret = -EPERM; + break; + } + + return ret; +} + +static const struct file_operations modem_fw_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = mloader_fw_ioctl, + .mmap = mloader_fw_mmapdump, +}; + +static int __devinit mloader_fw_probe(struct platform_device *pdev) +{ + int ret = 0; + int i; + + mloader_priv = kzalloc(sizeof(*mloader_priv), GFP_ATOMIC); + if (!mloader_priv) { + ret = -ENOMEM; + goto out; + } + + mloader_priv->pdev = pdev; + mloader_priv->pdata = pdev->dev.platform_data; + + mloader_priv->misc_dev.minor = MISC_DYNAMIC_MINOR; + mloader_priv->misc_dev.name = DEVICE_NAME; + mloader_priv->misc_dev.fops = &modem_fw_fops; + ret = misc_register(&mloader_priv->misc_dev); + if (ret < 0) { + dev_err(&pdev->dev, "can't misc_register\n"); + goto err_free_priv; + } + + dev_info(&mloader_priv->pdev->dev, "mloader device register\n"); + + for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) { + dev_dbg(&mloader_priv->pdev->dev, + "Area:%d (name:%s start:%x size:%x)\n", + i, mloader_priv->pdata->areas[i].name, + mloader_priv->pdata->areas[i].start, + mloader_priv->pdata->areas[i].size); + } + + for (i = 0 ; i < mloader_priv->pdata->nr_fws ; i++) { + dev_dbg(&mloader_priv->pdev->dev, + "Firmware:%d (name:%s offset:%x " + "area_name:%s area_start:%x area_size:%x)\n", + i, mloader_priv->pdata->fws[i].name, + mloader_priv->pdata->fws[i].offset, + mloader_priv->pdata->fws[i].area->name, + mloader_priv->pdata->fws[i].area->start, + mloader_priv->pdata->fws[i].area->size); + } + + return ret; + +err_free_priv: + kfree(mloader_priv); +out: + return ret; +} + +static int __devexit mloader_fw_remove(struct platform_device *pdev) +{ + int err; + + err = misc_register(&mloader_priv->misc_dev); + if (err < 0) + dev_err(&pdev->dev, "can't misc_deregister, %d\n", err); + + kfree(mloader_priv); + + return err; +} + +static struct platform_driver mloader_fw_driver = { + .driver.name = DEVICE_NAME, + .driver.owner = THIS_MODULE, + .probe = mloader_fw_probe, + .remove = __devexit_p(mloader_fw_remove), +}; + +static int __init mloader_fw_init(void) +{ + return platform_driver_register(&mloader_fw_driver); +} + +static void __exit mloader_fw_exit(void) +{ + kfree(mloader_priv); + platform_driver_unregister(&mloader_fw_driver); +} + +module_init(mloader_fw_init); +module_exit(mloader_fw_exit); +MODULE_DESCRIPTION("ST-Ericsson modem loader firmware"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ludovic Barre <ludovic.barre@stericsson.com>"); diff --git a/drivers/misc/mbox.c b/drivers/misc/mbox.c new file mode 100644 index 00000000000..d884496fa4c --- /dev/null +++ b/drivers/misc/mbox.c @@ -0,0 +1,867 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Stefan Nilsson <stefan.xk.nilsson@stericsson.com> for ST-Ericsson. + * Author: Martin Persson <martin.persson@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +/* + * Mailbox nomenclature: + * + * APE MODEM + * mbox pairX + * .......................... + * . . + * . peer . + * . send ---- . + * . --> | | . + * . | | . + * . ---- . + * . . + * . local . + * . rec ---- . + * . | | <-- . + * . | | . + * . ---- . + * ......................... + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/hrtimer.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/mfd/db5500-prcmu.h> +#include <mach/mbox-db5500.h> +#include <mach/reboot_reasons.h> + +#define MBOX_NAME "mbox" + +#define MBOX_FIFO_DATA 0x000 +#define MBOX_FIFO_ADD 0x004 +#define MBOX_FIFO_REMOVE 0x008 +#define MBOX_FIFO_THRES_FREE 0x00C +#define MBOX_FIFO_THRES_OCCUP 0x010 +#define MBOX_FIFO_STATUS 0x014 + +#define MBOX_DISABLE_IRQ 0x4 +#define MBOX_ENABLE_IRQ 0x0 +#define MBOX_LATCH 1 + +struct mbox_device_info { + struct mbox *mbox; + struct workqueue_struct *mbox_modem_rel_wq; + struct work_struct mbox_modem_rel; + struct completion mod_req_ack_work; + atomic_t ape_state; + atomic_t mod_req; + atomic_t mod_reset; +}; + +/* Global list of all mailboxes */ +struct hrtimer ape_timer; +struct hrtimer modem_timer; +static DEFINE_MUTEX(modem_state_mutex); +static struct list_head mboxs = LIST_HEAD_INIT(mboxs); +static struct mbox_device_info *mb; + +static enum hrtimer_restart mbox_ape_callback(struct hrtimer *hrtimer) +{ + queue_work(mb->mbox_modem_rel_wq, &mb->mbox_modem_rel); + + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart mbox_mod_callback(struct hrtimer *hrtimer) +{ + atomic_set(&mb->ape_state, 0); + return HRTIMER_NORESTART; +} + +static void mbox_modem_rel_work(struct work_struct *work) +{ + mutex_lock(&modem_state_mutex); + prcmu_modem_rel(); + atomic_set(&mb->mod_req, 0); + mutex_unlock(&modem_state_mutex); +} + +static void mbox_modem_req(void) +{ + mutex_lock(&modem_state_mutex); + if (!db5500_prcmu_is_modem_requested()) { + prcmu_modem_req(); + /* TODO: optimize this timeout */ + if (!wait_for_completion_timeout(&mb->mod_req_ack_work, + msecs_to_jiffies(2000))) + printk(KERN_ERR "mbox:modem_req_ack timedout(2sec)\n"); + } + atomic_set(&mb->mod_req, 1); + mutex_unlock(&modem_state_mutex); +} + +static struct mbox *get_mbox_with_id(u8 id) +{ + u8 i; + struct list_head *pos = &mboxs; + for (i = 0; i <= id; i++) + pos = pos->next; + + return (struct mbox *) list_entry(pos, struct mbox, list); +} + +int mbox_send(struct mbox *mbox, u32 mbox_msg, bool block) +{ + int res = 0; + unsigned long flag; + + if (atomic_read(&mb->mod_reset)) { + dev_err(&mbox->pdev->dev, + "mbox_send called after modem reset\n"); + return -EINVAL; + } + dev_dbg(&(mbox->pdev->dev), + "About to buffer 0x%X to mailbox 0x%X." + " ri = %d, wi = %d\n", + mbox_msg, (u32)mbox, mbox->read_index, + mbox->write_index); + + /* Request for modem */ + if (!db5500_prcmu_is_modem_requested()) + mbox_modem_req(); + + spin_lock_irqsave(&mbox->lock, flag); + /* Check if write buffer is full */ + while (((mbox->write_index + 1) % MBOX_BUF_SIZE) == mbox->read_index) { + if (!block) { + dev_dbg(&(mbox->pdev->dev), + "Buffer full in non-blocking call! " + "Returning -ENOMEM!\n"); + res = -ENOMEM; + goto exit; + } + spin_unlock_irqrestore(&mbox->lock, flag); + dev_dbg(&(mbox->pdev->dev), + "Buffer full in blocking call! Sleeping...\n"); + mbox->client_blocked = 1; + wait_for_completion(&mbox->buffer_available); + dev_dbg(&(mbox->pdev->dev), + "Blocking send was woken up! Trying again...\n"); + spin_lock_irqsave(&mbox->lock, flag); + } + + mbox->buffer[mbox->write_index] = mbox_msg; + mbox->write_index = (mbox->write_index + 1) % MBOX_BUF_SIZE; + + /* + * Indicate that we want an IRQ as soon as there is a slot + * in the FIFO + */ + if (atomic_read(&mb->mod_reset)) { + dev_err(&mbox->pdev->dev, + "modem is in reset state, cannot proceed\n"); + res = -EINVAL; + goto exit; + } + writel(MBOX_ENABLE_IRQ, mbox->virtbase_peer + MBOX_FIFO_THRES_FREE); + +exit: + spin_unlock_irqrestore(&mbox->lock, flag); + return res; +} +EXPORT_SYMBOL(mbox_send); + +#if defined(CONFIG_DEBUG_FS) +/* + * Expected input: <value> <nbr sends> + * Example: "echo 0xdeadbeef 4 > mbox-node" sends 0xdeadbeef 4 times + */ +static ssize_t mbox_write_fifo(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + unsigned long mbox_mess; + unsigned long nbr_sends; + unsigned long i; + char int_buf[16]; + char *token; + char *val; + + struct platform_device *pdev = to_platform_device(dev); + struct mbox *mbox = platform_get_drvdata(pdev); + + strncpy((char *) &int_buf, buf, sizeof(int_buf)); + token = (char *) &int_buf; + + /* Parse message */ + val = strsep(&token, " "); + if ((val == NULL) || (strict_strtoul(val, 16, &mbox_mess) != 0)) + mbox_mess = 0xDEADBEEF; + + val = strsep(&token, " "); + if ((val == NULL) || (strict_strtoul(val, 10, &nbr_sends) != 0)) + nbr_sends = 1; + + dev_dbg(dev, "Will write 0x%lX %ld times using data struct at 0x%X\n", + mbox_mess, nbr_sends, (u32) mbox); + + for (i = 0; i < nbr_sends; i++) + mbox_send(mbox, mbox_mess, true); + + return count; +} + +static ssize_t mbox_read_fifo(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int mbox_value; + struct platform_device *pdev = to_platform_device(dev); + struct mbox *mbox = platform_get_drvdata(pdev); + + if (atomic_read(&mb->mod_reset)) { + dev_err(&mbox->pdev->dev, "modem crashed, returning\n"); + return 0; + } + if ((readl(mbox->virtbase_local + MBOX_FIFO_STATUS) & 0x7) <= 0) + return sprintf(buf, "Mailbox is empty\n"); + + mbox_value = readl(mbox->virtbase_local + MBOX_FIFO_DATA); + writel(MBOX_LATCH, (mbox->virtbase_local + MBOX_FIFO_REMOVE)); + + return sprintf(buf, "0x%X\n", mbox_value); +} + +static DEVICE_ATTR(fifo, S_IWUGO | S_IRUGO, mbox_read_fifo, mbox_write_fifo); + +static int mbox_show(struct seq_file *s, void *data) +{ + struct list_head *pos; + u8 mbox_index = 0; + + list_for_each(pos, &mboxs) { + struct mbox *m = + (struct mbox *) list_entry(pos, struct mbox, list); + if (m == NULL) { + seq_printf(s, + "Unable to retrieve mailbox %d\n", + mbox_index); + continue; + } + + spin_lock(&m->lock); + if ((m->virtbase_peer == NULL) || (m->virtbase_local == NULL)) { + seq_printf(s, "MAILBOX %d not setup or corrupt\n", + mbox_index); + spin_unlock(&m->lock); + continue; + } + + if (atomic_read(&mb->mod_reset)) { + dev_err(&m->pdev->dev, "modem crashed, returning\n"); + spin_unlock(&m->lock); + return 0; + } + seq_printf(s, + "===========================\n" + " MAILBOX %d\n" + " PEER MAILBOX DUMP\n" + "---------------------------\n" + "FIFO: 0x%X (%d)\n" + "Free Threshold: 0x%.2X (%d)\n" + "Occupied Threshold: 0x%.2X (%d)\n" + "Status: 0x%.2X (%d)\n" + " Free spaces (ot): %d (%d)\n" + " Occup spaces (ot): %d (%d)\n" + "===========================\n" + " LOCAL MAILBOX DUMP\n" + "---------------------------\n" + "FIFO: 0x%.X (%d)\n" + "Free Threshold: 0x%.2X (%d)\n" + "Occupied Threshold: 0x%.2X (%d)\n" + "Status: 0x%.2X (%d)\n" + " Free spaces (ot): %d (%d)\n" + " Occup spaces (ot): %d (%d)\n" + "===========================\n" + "write_index: %d\n" + "read_index : %d\n" + "===========================\n" + "\n", + mbox_index, + readl(m->virtbase_peer + MBOX_FIFO_DATA), + readl(m->virtbase_peer + MBOX_FIFO_DATA), + readl(m->virtbase_peer + MBOX_FIFO_THRES_FREE), + readl(m->virtbase_peer + MBOX_FIFO_THRES_FREE), + readl(m->virtbase_peer + MBOX_FIFO_THRES_OCCUP), + readl(m->virtbase_peer + MBOX_FIFO_THRES_OCCUP), + readl(m->virtbase_peer + MBOX_FIFO_STATUS), + readl(m->virtbase_peer + MBOX_FIFO_STATUS), + (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 4) & 0x7, + (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 7) & 0x1, + (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 0) & 0x7, + (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 3) & 0x1, + readl(m->virtbase_local + MBOX_FIFO_DATA), + readl(m->virtbase_local + MBOX_FIFO_DATA), + readl(m->virtbase_local + MBOX_FIFO_THRES_FREE), + readl(m->virtbase_local + MBOX_FIFO_THRES_FREE), + readl(m->virtbase_local + MBOX_FIFO_THRES_OCCUP), + readl(m->virtbase_local + MBOX_FIFO_THRES_OCCUP), + readl(m->virtbase_local + MBOX_FIFO_STATUS), + readl(m->virtbase_local + MBOX_FIFO_STATUS), + (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 4) & 0x7, + (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 7) & 0x1, + (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 0) & 0x7, + (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 3) & 0x1, + m->write_index, m->read_index); + mbox_index++; + spin_unlock(&m->lock); + } + + return 0; +} + +static int mbox_open(struct inode *inode, struct file *file) +{ + return single_open(file, mbox_show, NULL); +} + +static const struct file_operations mbox_operations = { + .owner = THIS_MODULE, + .open = mbox_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +static irqreturn_t mbox_irq(int irq, void *arg) +{ + u32 mbox_value; + int nbr_occup; + int nbr_free; + struct mbox *mbox = (struct mbox *) arg; + + if (atomic_read(&mb->mod_reset)) { + dev_err(&mbox->pdev->dev, "modem in reset state\n"); + return IRQ_HANDLED; + } + spin_lock(&mbox->lock); + + dev_dbg(&(mbox->pdev->dev), + "mbox IRQ [%d] received. ri = %d, wi = %d\n", + irq, mbox->read_index, mbox->write_index); + + /* + * Check if we have any outgoing messages, and if there is space for + * them in the FIFO. + */ + if (mbox->read_index != mbox->write_index) { + /* + * Check by reading FREE for LOCAL since that indicates + * OCCUP for PEER + */ + nbr_free = (readl(mbox->virtbase_local + MBOX_FIFO_STATUS) + >> 4) & 0x7; + dev_dbg(&(mbox->pdev->dev), + "Status indicates %d empty spaces in the FIFO!\n", + nbr_free); + + while ((nbr_free > 0) && + (mbox->read_index != mbox->write_index)) { + if (atomic_read(&mb->mod_reset)) { + dev_err(&mbox->pdev->dev, + "modem in reset state\n"); + goto exit; + } + /* Write the message and latch it into the FIFO */ + writel(mbox->buffer[mbox->read_index], + (mbox->virtbase_peer + MBOX_FIFO_DATA)); + writel(MBOX_LATCH, + (mbox->virtbase_peer + MBOX_FIFO_ADD)); + dev_dbg(&(mbox->pdev->dev), + "Wrote message 0x%X to addr 0x%X\n", + mbox->buffer[mbox->read_index], + (u32) (mbox->virtbase_peer + MBOX_FIFO_DATA)); + + nbr_free--; + mbox->read_index = + (mbox->read_index + 1) % MBOX_BUF_SIZE; + } + + if (atomic_read(&mb->mod_reset)) { + dev_err(&mbox->pdev->dev, "modem in reset state\n"); + goto exit; + } + /* + * Check if we still want IRQ:s when there is free + * space to send + */ + if (mbox->read_index != mbox->write_index) { + dev_dbg(&(mbox->pdev->dev), + "Still have messages to send, but FIFO full. " + "Request IRQ again!\n"); + writel(MBOX_ENABLE_IRQ, + mbox->virtbase_peer + MBOX_FIFO_THRES_FREE); + } else { + dev_dbg(&(mbox->pdev->dev), + "No more messages to send. " + "Do not request IRQ again!\n"); + writel(MBOX_DISABLE_IRQ, + mbox->virtbase_peer + MBOX_FIFO_THRES_FREE); + } + + /* + * Check if we can signal any blocked clients that it is OK to + * start buffering again + */ + if (mbox->client_blocked && + (((mbox->write_index + 1) % MBOX_BUF_SIZE) + != mbox->read_index)) { + dev_dbg(&(mbox->pdev->dev), + "Waking up blocked client\n"); + complete(&mbox->buffer_available); + mbox->client_blocked = 0; + } + } + + /* Start timer and on timer expiry call modem_rel */ + hrtimer_start(&ape_timer, ktime_set(0, 10*NSEC_PER_MSEC), + HRTIMER_MODE_REL); + + if (atomic_read(&mb->mod_reset)) { + dev_err(&mbox->pdev->dev, "modem in reset state\n"); + goto exit; + } + /* Check if we have any incoming messages */ + nbr_occup = readl(mbox->virtbase_local + MBOX_FIFO_STATUS) & 0x7; + if (nbr_occup == 0) + goto exit; + +redo: + if (mbox->cb == NULL) { + dev_dbg(&(mbox->pdev->dev), "No receive callback registered, " + "leaving %d incoming messages in fifo!\n", nbr_occup); + goto exit; + } + atomic_set(&mb->ape_state, 1); + + if (atomic_read(&mb->mod_reset)) { + dev_err(&mbox->pdev->dev, "modem in reset state\n"); + goto exit; + } + /* Read and acknowledge the message */ + mbox_value = readl(mbox->virtbase_local + MBOX_FIFO_DATA); + writel(MBOX_LATCH, (mbox->virtbase_local + MBOX_FIFO_REMOVE)); + + /* Notify consumer of new mailbox message */ + dev_dbg(&(mbox->pdev->dev), "Calling callback for message 0x%X!\n", + mbox_value); + mbox->cb(mbox_value, mbox->client_data); + + nbr_occup = readl(mbox->virtbase_local + MBOX_FIFO_STATUS) & 0x7; + + if (nbr_occup > 0) + goto redo; + + /* Start a timer and timer expiry will be the criteria for sleep */ + hrtimer_start(&modem_timer, ktime_set(0, 100*MSEC_PER_SEC), + HRTIMER_MODE_REL); +exit: + dev_dbg(&(mbox->pdev->dev), "Exit mbox IRQ. ri = %d, wi = %d\n", + mbox->read_index, mbox->write_index); + spin_unlock(&mbox->lock); + + return IRQ_HANDLED; +} + +static void mbox_shutdown(struct mbox *mbox) +{ + if (!mbox->allocated) + return; +#if defined(CONFIG_DEBUG_FS) + debugfs_remove(mbox->dentry); + device_remove_file(&mbox->pdev->dev, &dev_attr_fifo); +#endif + /* TODO: Need to check if we can write after modem reset */ + if (!atomic_read(&mb->mod_reset)) { + writel(MBOX_DISABLE_IRQ, mbox->virtbase_local + + MBOX_FIFO_THRES_OCCUP); + writel(MBOX_DISABLE_IRQ, mbox->virtbase_peer + + MBOX_FIFO_THRES_FREE); + } + free_irq(mbox->irq, (void *)mbox); + mbox->client_blocked = 0; + iounmap(mbox->virtbase_local); + iounmap(mbox->virtbase_peer); + mbox->cb = NULL; + mbox->client_data = NULL; + mbox->allocated = false; +} + +/** mbox_state_reset - Reset the mailbox state machine + * + * This function is called on receiving modem reset interrupt. Reset all + * the mailbox state machine, disable irq, cancel timers, shutdown the + * mailboxs and re-enable irq's. + */ +void mbox_state_reset(void) +{ + struct mbox *mbox = mb->mbox; + + /* Common for all mailbox */ + atomic_set(&mb->mod_reset, 1); + + /* Disable IRQ */ + disable_irq_nosync(IRQ_DB5500_PRCMU_AC_WAKE_ACK); + + /* Cancel sleep_req timers */ + hrtimer_cancel(&modem_timer); + hrtimer_cancel(&ape_timer); + + /* specific to each mailbox */ + list_for_each_entry(mbox, &mboxs, list) { + mbox_shutdown(mbox); + } + + /* Reset mailbox state machine */ + atomic_set(&mb->mod_req, 0); + atomic_set(&mb->ape_state, 0); + + /* Enable irq */ + enable_irq(IRQ_DB5500_PRCMU_AC_WAKE_ACK); +} + + +/* Setup is executed once for each mbox pair */ +struct mbox *mbox_setup(u8 mbox_id, mbox_recv_cb_t *mbox_cb, void *priv) +{ + struct resource *resource; + int res; + struct mbox *mbox; + + /* + * set mod_reset flag to '0', clients calling this APE should make sure + * that modem is rebooted after MSR. Mailbox doesnt have any means of + * knowing the boot status of modem. + */ + atomic_set(&mb->mod_reset, 0); + + mbox = get_mbox_with_id(mbox_id); + if (mbox == NULL) { + dev_err(&(mbox->pdev->dev), "Incorrect mailbox id: %d!\n", + mbox_id); + goto exit; + } + + /* + * Check if mailbox has been allocated to someone else, + * otherwise allocate it + */ + if (mbox->allocated) { + dev_err(&(mbox->pdev->dev), "Mailbox number %d is busy!\n", + mbox_id); + mbox = NULL; + goto exit; + } + mbox->allocated = true; + + dev_dbg(&(mbox->pdev->dev), "Initiating mailbox number %d: 0x%X...\n", + mbox_id, (u32)mbox); + + mbox->client_data = priv; + mbox->cb = mbox_cb; + + /* Get addr for peer mailbox and ioremap it */ + resource = platform_get_resource_byname(mbox->pdev, + IORESOURCE_MEM, + "mbox_peer"); + if (resource == NULL) { + dev_err(&(mbox->pdev->dev), + "Unable to retrieve mbox peer resource\n"); + mbox = NULL; + goto free_mbox; + } + dev_dbg(&(mbox->pdev->dev), + "Resource name: %s start: 0x%X, end: 0x%X\n", + resource->name, resource->start, resource->end); + mbox->virtbase_peer = ioremap(resource->start, resource_size(resource)); + if (!mbox->virtbase_peer) { + dev_err(&(mbox->pdev->dev), "Unable to ioremap peer mbox\n"); + mbox = NULL; + goto free_mbox; + } + dev_dbg(&(mbox->pdev->dev), + "ioremapped peer physical: (0x%X-0x%X) to virtual: 0x%X\n", + resource->start, resource->end, (u32) mbox->virtbase_peer); + + /* Get addr for local mailbox and ioremap it */ + resource = platform_get_resource_byname(mbox->pdev, + IORESOURCE_MEM, + "mbox_local"); + if (resource == NULL) { + dev_err(&(mbox->pdev->dev), + "Unable to retrieve mbox local resource\n"); + mbox = NULL; + goto free_map; + } + dev_dbg(&(mbox->pdev->dev), + "Resource name: %s start: 0x%X, end: 0x%X\n", + resource->name, resource->start, resource->end); + mbox->virtbase_local = ioremap(resource->start, resource_size(resource)); + if (!mbox->virtbase_local) { + dev_err(&(mbox->pdev->dev), "Unable to ioremap local mbox\n"); + mbox = NULL; + goto free_map; + } + dev_dbg(&(mbox->pdev->dev), + "ioremapped local physical: (0x%X-0x%X) to virtual: 0x%X\n", + resource->start, resource->end, (u32) mbox->virtbase_peer); + + init_completion(&mbox->buffer_available); + mbox->client_blocked = 0; + + /* Get IRQ for mailbox and allocate it */ + mbox->irq = platform_get_irq_byname(mbox->pdev, "mbox_irq"); + if (mbox->irq < 0) { + dev_err(&(mbox->pdev->dev), + "Unable to retrieve mbox irq resource\n"); + mbox = NULL; + goto free_map1; + } + + dev_dbg(&(mbox->pdev->dev), "Allocating irq %d...\n", mbox->irq); + res = request_threaded_irq(mbox->irq, NULL, mbox_irq, + IRQF_NO_SUSPEND | IRQF_ONESHOT, + mbox->name, (void *) mbox); + if (res < 0) { + dev_err(&(mbox->pdev->dev), + "Unable to allocate mbox irq %d\n", mbox->irq); + mbox = NULL; + goto exit; + } + + /* check if modem has reset */ + if (atomic_read(&mb->mod_reset)) { + dev_err(&mbox->pdev->dev, + "modem is in reset state, cannot proceed\n"); + mbox = NULL; + goto free_irq; + } + /* Set up mailbox to not launch IRQ on free space in mailbox */ + writel(MBOX_DISABLE_IRQ, mbox->virtbase_peer + MBOX_FIFO_THRES_FREE); + + /* + * Set up mailbox to launch IRQ on new message if we have + * a callback set. If not, do not raise IRQ, but keep message + * in FIFO for manual retrieval + */ + if (mbox_cb != NULL) + writel(MBOX_ENABLE_IRQ, + mbox->virtbase_local + MBOX_FIFO_THRES_OCCUP); + else + writel(MBOX_DISABLE_IRQ, + mbox->virtbase_local + MBOX_FIFO_THRES_OCCUP); + +#if defined(CONFIG_DEBUG_FS) + res = device_create_file(&(mbox->pdev->dev), &dev_attr_fifo); + if (res != 0) + dev_warn(&(mbox->pdev->dev), + "Unable to create mbox sysfs entry"); + + mbox->dentry = debugfs_create_file("mbox", S_IFREG | S_IRUGO, NULL, + NULL, &mbox_operations); +#endif + dev_info(&(mbox->pdev->dev), + "Mailbox driver with index %d initiated!\n", mbox_id); + + return mbox; +free_irq: + free_irq(mbox->irq, (void *)mbox); +free_map1: + iounmap(mbox->virtbase_local); +free_map: + iounmap(mbox->virtbase_peer); +free_mbox: + mbox->client_data = NULL; + mbox->cb = NULL; +exit: + return mbox; +} +EXPORT_SYMBOL(mbox_setup); + +static irqreturn_t mbox_prcmu_mod_req_ack_handler(int irq, void *data) +{ + complete(&mb->mod_req_ack_work); + return IRQ_HANDLED; +} + +int __init mbox_probe(struct platform_device *pdev) +{ + struct mbox *mbox; + int res = 0; + dev_dbg(&(pdev->dev), "Probing mailbox (pdev = 0x%X)...\n", (u32) pdev); + + mbox = kzalloc(sizeof(struct mbox), GFP_KERNEL); + if (mbox == NULL) { + dev_err(&pdev->dev, + "Could not allocate memory for struct mbox\n"); + return -ENOMEM; + } + + mbox->pdev = pdev; + mbox->write_index = 0; + mbox->read_index = 0; + + INIT_LIST_HEAD(&(mbox->list)); + list_add_tail(&(mbox->list), &mboxs); + + sprintf(mbox->name, "%s", MBOX_NAME); + spin_lock_init(&mbox->lock); + + platform_set_drvdata(pdev, mbox); + mb->mbox = mbox; + dev_info(&(pdev->dev), "Mailbox driver loaded\n"); + + return res; +} + +static int __exit mbox_remove(struct platform_device *pdev) +{ + struct mbox *mbox = platform_get_drvdata(pdev); + + hrtimer_cancel(&ape_timer); + hrtimer_cancel(&modem_timer); + mbox_shutdown(mbox); + list_del(&mbox->list); + kfree(mbox); + return 0; +} + +#ifdef CONFIG_PM +int mbox_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mbox *mbox = platform_get_drvdata(pdev); + + /* + * Nothing to be done for now, once APE-Modem power management is + * in place communication will have to be stopped. + */ + + list_for_each_entry(mbox, &mboxs, list) { + if (mbox->client_blocked) + return -EBUSY; + } + dev_dbg(dev, "APE_STATE = %d\n", atomic_read(&mb->ape_state)); + dev_dbg(dev, "MODEM_STATE = %d\n", db5500_prcmu_is_modem_requested()); + if (atomic_read(&mb->ape_state) || db5500_prcmu_is_modem_requested() || + atomic_read(&mb->mod_req)) + return -EBUSY; + return 0; +} + +int mbox_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mbox *mbox = platform_get_drvdata(pdev); + + /* + * Nothing to be done for now, once APE-Modem power management is + * in place communication will have to be resumed. + */ + + return 0; +} + +static const struct dev_pm_ops mbox_dev_pm_ops = { + .suspend_noirq = mbox_suspend, + .resume_noirq = mbox_resume, +}; +#endif + +static struct platform_driver mbox_driver = { + .remove = __exit_p(mbox_remove), + .driver = { + .name = MBOX_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &mbox_dev_pm_ops, +#endif + }, +}; + +static int __init mbox_init(void) +{ + struct mbox_device_info *mb_di; + int err; + + mb_di = kzalloc(sizeof(struct mbox_device_info), GFP_KERNEL); + if (mb_di == NULL) { + printk(KERN_ERR + "mbox:Could not allocate memory for struct mbox_device_info\n"); + return -ENOMEM; + } + + mb_di->mbox_modem_rel_wq = create_singlethread_workqueue( + "mbox_modem_rel"); + if (!mb_di->mbox_modem_rel_wq) { + printk(KERN_ERR "mbox:failed to create work queue\n"); + err = -ENOMEM; + goto free_mem; + } + + INIT_WORK(&mb_di->mbox_modem_rel, mbox_modem_rel_work); + + hrtimer_init(&ape_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ape_timer.function = mbox_ape_callback; + hrtimer_init(&modem_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + modem_timer.function = mbox_mod_callback; + + atomic_set(&mb_di->ape_state, 0); + atomic_set(&mb_di->mod_req, 0); + atomic_set(&mb_di->mod_reset, 0); + + err = request_irq(IRQ_DB5500_PRCMU_AC_WAKE_ACK, + mbox_prcmu_mod_req_ack_handler, + IRQF_NO_SUSPEND, "mod_req_ack", NULL); + if (err < 0) { + printk(KERN_ERR "mbox:Failed alloc IRQ_PRCMU_CA_SLEEP.\n"); + goto free_irq; + } + + init_completion(&mb_di->mod_req_ack_work); + mb = mb_di; + return platform_driver_probe(&mbox_driver, mbox_probe); +free_irq: + destroy_workqueue(mb_di->mbox_modem_rel_wq); +free_mem: + kfree(mb_di); + return err; +} + +module_init(mbox_init); + +void __exit mbox_exit(void) +{ + free_irq(IRQ_DB5500_PRCMU_AC_WAKE_ACK, NULL); + destroy_workqueue(mb->mbox_modem_rel_wq); + platform_driver_unregister(&mbox_driver); + kfree(mb); +} + +module_exit(mbox_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MBOX driver"); diff --git a/drivers/misc/mbox_channels-db5500.c b/drivers/misc/mbox_channels-db5500.c new file mode 100644 index 00000000000..919be308ed4 --- /dev/null +++ b/drivers/misc/mbox_channels-db5500.c @@ -0,0 +1,1273 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Mailbox Logical Driver + * + * Author: Marcin Mielczarczyk <marcin.mielczarczyk@tieto.com> for ST-Ericsson. + * Bibek Basu ,bibek.basu@stericsson.com> + * License terms: GNU General Public License (GPL), version 2. + */ + +#include <asm/mach-types.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <mach/mbox-db5500.h> +#include <mach/mbox_channels-db5500.h> +#include <linux/io.h> + +/* Defines start sequence number for given mailbox channel */ +#define CHANNEL_START_SEQUENCE_NUMBER 0x80 + +/* Defines number of channels per mailbox unit */ +#define CHANNELS_PER_MBOX_UNIT 256 + +/* + * This macro builds mbox channel PDU header with following format: + * --------------------------------------------------------------------------- + * | | | | | + * | Sequence nmbr | Type | Length | Destination logical channel number | + * | | | | | + * --------------------------------------------------------------------------- + * 31 24 20 16 0 + * + */ +#define BUILD_HEADER(chan, len, type, seq_no) \ + ((chan) | (((len) & 0xf) << 16) | \ + (((type) & 0xf) << 20) | ((seq_no) << 24)) + +/* Returns type from mbox message header */ +#define GET_TYPE(mbox_msg) (((mbox_msg) >> 20) & 0xf) + +/* Returns channel number from mbox message header */ +#define GET_CHANNEL(mbox_msg) ((mbox_msg) & 0xffff) + +/* Returns length of payload from mbox message header */ +#define GET_LENGTH(mbox_msg) (((mbox_msg) >> 16) & 0xf) + +/* Returns sequence number from mbox message header */ +#define GET_SEQ_NUMBER(mbox_msg) (((mbox_msg) >> 24) + +enum mbox_msg{ + MBOX_CLOSE, + MBOX_OPEN, + MBOX_SEND, + MBOX_CAST, + MBOX_ACK, + MBOX_NAK, +}; + +enum mbox_dir { + MBOX_TX, + MBOX_RX, +}; + +struct mbox_channel_mapping { + u16 chan_base; + u8 mbox_id; + enum mbox_dir direction; +}; + +/* This table maps mbox logical channel to mbox id and direction */ +static struct mbox_channel_mapping channel_mappings[] = { + {0x500, 2, MBOX_RX}, /* channel 5 maps to mbox 0.1, dsp->app (unsec) */ + {0x900, 2, MBOX_TX}, /* channel 9 maps to mbox 0.0, app->dsp (unsec) */ +}; + +/* This table specifies mailbox ids which mbox channels module will use */ +static u8 mbox_ids[] = { + 2, /* app <-> dsp (unsec) */ +}; + +/** + * struct mbox_unit_status - current status of mbox unit + * @mbox_id : holds mbox unit identification number + * @mbox : holds mbox pointer after mbox_register() call + * @tx_chans : holds list of open tx mbox channels + * @tx_lock: lock for tx channel + * @rx_chans : holds list of open rx mbox channels + * @rx_lock: lock for rx channel + */ +struct mbox_unit_status { + u8 mbox_id; + struct mbox *mbox; + struct list_head tx_chans; + spinlock_t tx_lock; + struct list_head rx_chans; + spinlock_t rx_lock; +}; + +static struct { + struct platform_device *pdev; + struct mbox_unit_status mbox_unit[ARRAY_SIZE(mbox_ids)]; +} channels; + +/* This structure describes pending element for mbox tx channel */ +struct pending_elem { + struct list_head list; + u32 *data; + u8 length; +}; + +struct rx_pending_elem { + u32 buffer[MAILBOX_NR_OF_DATAWORDS]; + u8 length; + void *priv; +}; + +struct rx_pending_elem rx_pending[NUM_DSP_BUFFER]; + +/* This structure holds list of pending elements for mbox tx channel */ +struct tx_channel { + struct list_head pending; +}; + +/* Specific status for mbox rx channel */ +struct rx_channel { + struct list_head pending; + spinlock_t lock; + u32 buffer[MAILBOX_NR_OF_DATAWORDS]; + u8 index; + u8 length; +}; + +/** + * struct channel_status - status of mbox channel - common for tx and rx + * @list : holds list of channels registered + * @channel : holds channel number + * @state : holds state of channel + * @cb: holds callback function forr rx channel + * @with_ack : holds if ack is needed + * @rx: holds pointer to rx_channel + * @tx : holds pointer to tx_channel + * @receive_wq : holds pointer to receive workqueue_struct + * @cast_wq : holds pointer to cast workqueue_struct + * @open_msg: holds work_struct for open msg + * @receive_msg : holds work_struct for receive msg + * @cast_msg: holds work_struct for cast msg + * @lock: holds lock for channel + */ +struct channel_status { + atomic_t rcv_counter; + struct list_head list; + u16 channel; + int state; + mbox_channel_cb_t *cb; + void *priv; + u8 seq_number; + bool with_ack; + struct rx_channel rx; + struct tx_channel tx; + struct workqueue_struct *receive_wq; + struct workqueue_struct *cast_wq; + struct work_struct open_msg; + struct work_struct receive_msg; + struct work_struct cast_msg; + struct mutex lock; +}; + +/* Checks if provided channel number is valid */ +static bool check_channel(u16 channel, enum mbox_dir direction) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(channel_mappings); i++) { + if ((channel >= channel_mappings[i].chan_base) && + (channel < channel_mappings[i].chan_base + + CHANNELS_PER_MBOX_UNIT)) { + /* Check if direction of given channel is correct*/ + if (channel_mappings[i].direction == direction) + return true; + else + break; + } + } + return false; +} + +/* get the tx channel corresponding to the given rx channel */ +static u16 get_tx_channel(u16 channel) +{ + int i; + int relative_chan = 0; + int mbox_id = 0xFF; + u16 tx_channel = 0xFF; + + for (i = 0; i < ARRAY_SIZE(channel_mappings); i++) { + if ((channel >= channel_mappings[i].chan_base) && + (channel < channel_mappings[i].chan_base + + CHANNELS_PER_MBOX_UNIT)) { + /* Check if direction of given channel is correct*/ + relative_chan = channel - channel_mappings[i].chan_base; + mbox_id = channel_mappings[i].mbox_id; + + } + } + + for (i = 0; i < ARRAY_SIZE(channel_mappings); i++) { + if ((mbox_id == channel_mappings[i].mbox_id) && + (channel_mappings[i].direction == MBOX_TX)) + tx_channel = channel_mappings[i].chan_base + + relative_chan; + } + return tx_channel; +} + +/* Returns mbox unit id for given mbox channel */ +static int get_mbox_id(u16 channel) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(channel_mappings); i++) { + if ((channel >= channel_mappings[i].chan_base) && + (channel < channel_mappings[i].chan_base + + CHANNELS_PER_MBOX_UNIT)) { + return channel_mappings[i].mbox_id; + } + } + /* There is no mbox unit registered for given channel */ + return -EINVAL; +} + +/* Returns mbox structure saved after mbox_register() call */ +static struct mbox *get_mbox(u16 channel) +{ + int i; + int mbox_id = get_mbox_id(channel); + + if (mbox_id < 0) { + dev_err(&channels.pdev->dev, "couldn't get mbox id\n"); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(channels.mbox_unit); i++) { + if (channels.mbox_unit[i].mbox_id == mbox_id) + return channels.mbox_unit[i].mbox; + } + return NULL; +} + +/* Returns pointer to rx mbox channels list for given mbox unit */ +static struct list_head *get_rx_list(u8 mbox_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mbox_ids); i++) { + if (channels.mbox_unit[i].mbox_id == mbox_id) + return &channels.mbox_unit[i].rx_chans; + } + return NULL; +} + +/* Returns pointer to tx mbox channels list for given mbox unit */ +static struct list_head *get_tx_list(u8 mbox_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mbox_ids); i++) { + if (channels.mbox_unit[i].mbox_id == mbox_id) + return &channels.mbox_unit[i].tx_chans; + } + return NULL; +} + +static int send_pdu(struct channel_status *chan_status, int command, + u16 channel) +{ + struct mbox *mbox; + u32 header = 0; + int ret = 0; + /* SEND PDU is not supported */ + if (command == MBOX_SEND) { + dev_err(&channels.pdev->dev, "SEND command not implemented\n"); + ret = -EINVAL; + goto exit; + } + mbox = get_mbox(chan_status->channel); + if (mbox == NULL) { + dev_err(&channels.pdev->dev, "couldn't get mailbox\n"); + ret = -ENOSYS; + goto exit; + } + /* For CAST type send all pending messages */ + if (command == MBOX_CAST) { + struct list_head *pos, *n; + + /* Send all pending messages from TX channel */ + list_for_each_safe(pos, n, &chan_status->tx.pending) { + struct pending_elem *pending = + list_entry(pos, struct pending_elem, list); + int i; + + header = BUILD_HEADER(channel, + pending->length, + command, + chan_status->seq_number); + + ret = mbox_send(mbox, header, true); + if (ret < 0) { + dev_err(&channels.pdev->dev, + "failed to send header, err=%d\n", ret); + goto exit; + } + + for (i = 0; i < pending->length; i++) { + ret = mbox_send(mbox, pending->data[i], true); + if (ret < 0) { + dev_err(&channels.pdev->dev, + "failed to send header, err=%d\n", ret); + goto exit; + } + } + + /* Call client's callback that data is already sent */ + if (chan_status->cb) + chan_status->cb(pending->data, pending->length, + chan_status->priv); + else + dev_err(&channels.pdev->dev, + "%s no callback provided:header 0x%x\n", + __func__, header); + + /* Increment sequence number */ + chan_status->seq_number++; + + /* Remove and free element from the list */ + list_del(&pending->list); + kfree(pending); + } + } else { + header = BUILD_HEADER(channel, 0, + command, chan_status->seq_number); + + ret = mbox_send(mbox, header, true); + if (ret < 0) + dev_err(&channels.pdev->dev, "failed to send header\n"); + /* Increment sequence number */ + chan_status->seq_number++; + } + +exit: + return ret; +} + +void mbox_handle_receive_msg(struct work_struct *work) +{ + struct channel_status *rx_chan = container_of(work, + struct channel_status, + receive_msg); + + if (!atomic_read(&rx_chan->rcv_counter)) + return; +rcv_msg: + /* Call client's callback and reset state */ + if (rx_chan->cb) { + static int rx_pending_count; + rx_chan->cb(rx_pending[rx_pending_count].buffer, + rx_pending[rx_pending_count].length, + rx_pending[rx_pending_count].priv); + rx_pending_count++; + if (rx_pending_count == NUM_DSP_BUFFER) + rx_pending_count = 0; + } else { + dev_err(&channels.pdev->dev, + "%s no callback provided\n", __func__); + } + if (atomic_dec_return(&rx_chan->rcv_counter) > 0) + goto rcv_msg; + +} + +void mbox_handle_open_msg(struct work_struct *work) +{ + struct channel_status *tx_chan = container_of(work, + struct channel_status, + open_msg); + /* Change channel state to OPEN */ + tx_chan->state = MBOX_OPEN; + /* If pending list not empty, start sending data */ + mutex_lock(&tx_chan->lock); + if (!list_empty(&tx_chan->tx.pending)) + send_pdu(tx_chan, MBOX_CAST, tx_chan->channel); + mutex_unlock(&tx_chan->lock); +} + +void mbox_handle_cast_msg(struct work_struct *work) +{ + struct channel_status *rx_chan = container_of(work, + struct channel_status, + cast_msg); + /* Check if channel is opened */ + if (rx_chan->state == MBOX_CLOSE) { + /* Peer sent message to closed channel */ + dev_err(&channels.pdev->dev, + "channel in wrong state\n"); + } +} + +static bool handle_receive_msg(u32 mbox_msg, struct channel_status *rx_chan) +{ + int i; + static int rx_pending_count; + + if (rx_chan) { + /* Store received data in RX channel buffer */ + rx_chan->rx.buffer[rx_chan->rx.index++] = mbox_msg; + + /* Check if it's last data of PDU */ + if (rx_chan->rx.index == rx_chan->rx.length) { + for (i = 0; i < MAILBOX_NR_OF_DATAWORDS; i++) { + rx_pending[rx_pending_count].buffer[i] = + rx_chan->rx.buffer[i]; + } + + rx_pending[rx_pending_count].length = + rx_chan->rx.length; + rx_pending[rx_pending_count].priv = rx_chan->priv; + rx_chan->rx.index = 0; + rx_chan->rx.length = 0; + rx_chan->state = MBOX_OPEN; + rx_chan->seq_number++; + rx_pending_count++; + if (rx_pending_count == NUM_DSP_BUFFER) + rx_pending_count = 0; + atomic_inc(&rx_chan->rcv_counter); + queue_work(rx_chan->receive_wq, + &rx_chan->receive_msg); + } + dev_dbg(&channels.pdev->dev, "%s OK\n", __func__); + + return true; + } + return false; +} + +static void handle_open_msg(u16 channel, u8 mbox_id) +{ + struct list_head *tx_list, *pos; + struct channel_status *tmp; + struct channel_status *tx_chan = NULL; + struct mbox_unit_status *mbox_unit; + channel = get_tx_channel(channel); + dev_dbg(&channels.pdev->dev, "%s mbox_id %d\tchannel %x\n", + __func__, mbox_id, channel); + /* Get TX channel for given mbox unit */ + tx_list = get_tx_list(mbox_id); + if (tx_list == NULL) { + dev_err(&channels.pdev->dev, "given mbox id is not valid %d\n", + mbox_id); + return; + } + mbox_unit = container_of(tx_list, struct mbox_unit_status, tx_chans); + /* Search for channel in tx list */ + spin_lock(&mbox_unit->tx_lock); + list_for_each(pos, tx_list) { + tmp = list_entry(pos, struct channel_status, list); + dev_dbg(&channels.pdev->dev, "tmp->channel=%d\n", + tmp->channel); + if (tmp->channel == channel) + tx_chan = tmp; + } + spin_unlock(&mbox_unit->tx_lock); + if (tx_chan) { + schedule_work(&tx_chan->open_msg); + } else { + /* No tx channel found on the list, allocate new element */ + tx_chan = kzalloc(sizeof(*tx_chan), GFP_ATOMIC); + if (tx_chan == NULL) { + dev_err(&channels.pdev->dev, + "failed to allocate memory\n"); + return; + } + + /* Fill initial data and add this element to tx list */ + tx_chan->channel = get_tx_channel(channel); + tx_chan->state = MBOX_OPEN; + tx_chan->seq_number = CHANNEL_START_SEQUENCE_NUMBER; + INIT_LIST_HEAD(&tx_chan->tx.pending); + INIT_WORK(&tx_chan->open_msg, mbox_handle_open_msg); + INIT_WORK(&tx_chan->cast_msg, mbox_handle_cast_msg); + INIT_WORK(&tx_chan->receive_msg, mbox_handle_receive_msg); + mutex_init(&tx_chan->lock); + spin_lock(&mbox_unit->tx_lock); + list_add_tail(&tx_chan->list, tx_list); + spin_unlock(&mbox_unit->tx_lock); + } +} + +static void handle_cast_msg(u16 channel, struct channel_status *rx_chan, + u32 mbox_msg, bool send) +{ + dev_dbg(&channels.pdev->dev, " %s\n", __func__); + if (rx_chan) { + rx_chan->rx.buffer[0] = mbox_msg; + rx_chan->with_ack = send; + rx_chan->rx.length = GET_LENGTH(rx_chan->rx.buffer[0]); + if (rx_chan->rx.length <= MAILBOX_NR_OF_DATAWORDS && + rx_chan->rx.length > 0) { + rx_chan->rx.index = 0; + rx_chan->state = MBOX_CAST; + } + queue_work(rx_chan->cast_wq, + &rx_chan->cast_msg); + } else { + /* Channel not found, peer sent wrong message */ + dev_err(&channels.pdev->dev, "channel %d doesn't exist\n", + channel); + } +} + +/* + * This callback is called whenever mbox unit receives data. + * priv parameter holds mbox unit id. + */ +static void mbox_cb(u32 mbox_msg, void *priv) +{ + u8 mbox_id = *(u8 *)priv; + struct list_head *rx_list; + u8 type = GET_TYPE(mbox_msg); + u16 channel = GET_CHANNEL(mbox_msg); + struct mbox_unit_status *mbox_unit; + struct list_head *pos; + struct channel_status *tmp; + struct channel_status *rx_chan = NULL; + bool is_Payload = 0; + + dev_dbg(&channels.pdev->dev, "%s type %d\t, mbox_msg %x\n", + __func__, type, mbox_msg); + + /* Get RX channels list for given mbox unit */ + rx_list = get_rx_list(mbox_id); + if (rx_list == NULL) { + dev_err(&channels.pdev->dev, "given mbox id is not valid %d\n", + mbox_id); + return; + } + + mbox_unit = container_of(rx_list, struct mbox_unit_status, rx_chans); + /* Search for channel in rx list */ + spin_lock(&mbox_unit->rx_lock); + list_for_each(pos, rx_list) { + tmp = list_entry(pos, struct channel_status, list); + if (tmp->state == MBOX_SEND || + tmp->state == MBOX_CAST) { + /* Received message is payload */ + is_Payload = 1; + rx_chan = tmp; + } else + if (tmp->channel == channel) + rx_chan = tmp; + } + spin_unlock(&mbox_unit->rx_lock); + /* if callback is present for that RX channel */ + if (rx_chan && rx_chan->cb) { + /* If received message is payload this + * function will take care of it + */ + if ((is_Payload) && (handle_receive_msg(mbox_msg, rx_chan))) + return; + } else + dev_err(&channels.pdev->dev, "callback not present:msg 0x%x " + "rx_chan 0x%x\n", mbox_msg, (u32)rx_chan); + + /* Received message is header as no RX channel is in SEND/CAST state */ + switch (type) { + case MBOX_CLOSE: + /* Not implemented */ + break; + case MBOX_OPEN: + handle_open_msg(channel, mbox_id); + break; + case MBOX_SEND: + /* if callback is present for that RX channel */ + if (rx_chan && rx_chan->cb) + handle_cast_msg(channel, rx_chan, mbox_msg, true); + break; + case MBOX_CAST: + /* if callback is present for that RX channel */ + if (rx_chan && rx_chan->cb) + handle_cast_msg(channel, rx_chan, mbox_msg, false); + break; + case MBOX_ACK: + case MBOX_NAK: + /* Not implemented */ + break; + } +} + +/** + * mbox_channel_register() - Registers for a channel + * @channel: Channel Number. + * @cb: Pointer to function pointer mbox_channel_cb_t + * @priv: Pointer to private data + * + * This routine is used to register for a logical channel. + * It first does sanity check on the requested channel availability + * and parameters. Then it prepares internal entry for the channel. + * And send a OPEN request for that channel. + */ +int mbox_channel_register(u16 channel, mbox_channel_cb_t *cb, void *priv) +{ + struct channel_status *rx_chan; + struct list_head *pos, *rx_list; + int res = 0; + struct mbox_unit_status *mbox_unit; + + dev_dbg(&channels.pdev->dev, " %s channel = %d\n", __func__, channel); + /* Check for callback fcn */ + if (cb == NULL) { + dev_err(&channels.pdev->dev, + "channel callback missing:channel %d\n", channel); + res = -EINVAL; + goto exit; + } + + /* Check if provided channel number is valid */ + if (!check_channel(channel, MBOX_RX)) { + dev_err(&channels.pdev->dev, "wrong mbox channel number %d\n", + channel); + res = -EINVAL; + goto exit; + } + + rx_list = get_rx_list(get_mbox_id(channel)); + if (rx_list == NULL) { + dev_err(&channels.pdev->dev, "given mbox id is not valid\n"); + res = -EINVAL; + goto exit; + } + + mbox_unit = container_of(rx_list, struct mbox_unit_status, rx_chans); + + /* Check if channel is already registered */ + spin_lock(&mbox_unit->rx_lock); + list_for_each(pos, rx_list) { + rx_chan = list_entry(pos, struct channel_status, list); + + if (rx_chan->channel == channel) { + dev_dbg(&channels.pdev->dev, + "channel already registered\n"); + rx_chan->cb = cb; + rx_chan->priv = priv; + spin_unlock(&mbox_unit->rx_lock); + goto exit; + } + } + spin_unlock(&mbox_unit->rx_lock); + + rx_chan = kzalloc(sizeof(*rx_chan), GFP_KERNEL); + if (rx_chan == NULL) { + dev_err(&channels.pdev->dev, + "couldn't allocate channel status\n"); + res = -ENOMEM; + goto exit; + } + + atomic_set(&rx_chan->rcv_counter, 0); + /* Fill out newly allocated element and add it to rx list */ + rx_chan->channel = channel; + rx_chan->cb = cb; + rx_chan->priv = priv; + rx_chan->seq_number = CHANNEL_START_SEQUENCE_NUMBER; + mutex_init(&rx_chan->lock); + INIT_LIST_HEAD(&rx_chan->rx.pending); + rx_chan->cast_wq = create_singlethread_workqueue("mbox_cast_msg"); + if (!rx_chan->cast_wq) { + dev_err(&channels.pdev->dev, "failed to create work queue\n"); + res = -ENOMEM; + goto error_cast_wq; + } + rx_chan->receive_wq = create_singlethread_workqueue("mbox_receive_msg"); + if (!rx_chan->receive_wq) { + dev_err(&channels.pdev->dev, "failed to create work queue\n"); + res = -ENOMEM; + goto error_recv_wq; + } + INIT_WORK(&rx_chan->open_msg, mbox_handle_open_msg); + INIT_WORK(&rx_chan->cast_msg, mbox_handle_cast_msg); + INIT_WORK(&rx_chan->receive_msg, mbox_handle_receive_msg); + spin_lock(&mbox_unit->rx_lock); + list_add_tail(&rx_chan->list, rx_list); + spin_unlock(&mbox_unit->rx_lock); + + mutex_lock(&rx_chan->lock); + res = send_pdu(rx_chan, MBOX_OPEN, get_tx_channel(rx_chan->channel)); + if (res) { + dev_err(&channels.pdev->dev, "failed to send OPEN command\n"); + spin_lock(&mbox_unit->rx_lock); + list_del(&rx_chan->list); + spin_unlock(&mbox_unit->rx_lock); + mutex_unlock(&rx_chan->lock); + goto error_send_pdu; + } else { + rx_chan->seq_number++; + rx_chan->state = MBOX_OPEN; + mutex_unlock(&rx_chan->lock); + return res; + } +error_send_pdu: + flush_workqueue(rx_chan->receive_wq); +error_recv_wq: + flush_workqueue(rx_chan->cast_wq); +error_cast_wq: + kfree(rx_chan); +exit: + return res; +} +EXPORT_SYMBOL(mbox_channel_register); + +/** + * mbox_channel_deregister() - DeRegisters for a channel + * @channel: Channel Number. + * + * This routine is used to deregister for a logical channel. + * It first does sanity check on the requested channel availability + * and parameters. Then it deletes the channel + */ +int mbox_channel_deregister(u16 channel) +{ + struct channel_status *rx_chan = NULL; + struct list_head *pos, *rx_list; + int res = 0; + struct mbox_unit_status *mbox_unit; + + dev_dbg(&channels.pdev->dev, " %s channel = %d\n", __func__, channel); + /* Check if provided channel number is valid */ + if (!check_channel(channel, MBOX_RX)) { + dev_err(&channels.pdev->dev, "wrong mbox channel number %d\n", + channel); + res = -EINVAL; + goto exit; + } + + rx_list = get_rx_list(get_mbox_id(channel)); + if (rx_list == NULL) { + dev_err(&channels.pdev->dev, "given mbox id is not valid\n"); + res = -EINVAL; + goto exit; + } + + mbox_unit = container_of(rx_list, struct mbox_unit_status, rx_chans); + + /* Check if channel is already registered */ + spin_lock(&mbox_unit->rx_lock); + list_for_each(pos, rx_list) { + rx_chan = list_entry(pos, struct channel_status, list); + + if (rx_chan->channel == channel) { + dev_dbg(&channels.pdev->dev, + "channel found\n"); + rx_chan->cb = NULL; + } + } + list_del(&rx_chan->list); + spin_unlock(&mbox_unit->rx_lock); + flush_workqueue(rx_chan->cast_wq); + flush_workqueue(rx_chan->receive_wq); + kfree(rx_chan); + +exit: + return res; +} +EXPORT_SYMBOL(mbox_channel_deregister); + +/** + * mbox_channel_send() - Send messages + * @msg: Pointer to mbox_channel_msg data structure. + * + * This routine is used to send messages over the registered logical + * TX channel. It first does sanity check on the message paramenters. + * It registered channel is not found then it just registers for that + * channel. If channel found, it puts the message to the pending list. + * If channel is OPEN, it then pushes the message to the mailbox in + * FIFO manner from the pending list. + */ +int mbox_channel_send(struct mbox_channel_msg *msg) +{ + struct list_head *pos, *tx_list; + struct channel_status *tmp = NULL; + struct channel_status *tx_chan = NULL; + struct pending_elem *pending; + struct mbox_unit_status *mbox_unit; + int res = 0; + + if (msg->length > MAILBOX_NR_OF_DATAWORDS || msg->length == 0) { + dev_err(&channels.pdev->dev, "data length incorrect\n"); + res = -EINVAL; + goto exit; + } + + if (!check_channel(msg->channel, MBOX_TX)) { + dev_err(&channels.pdev->dev, "wrong channel number %d\n", + msg->channel); + res = -EINVAL; + goto exit; + } + + tx_list = get_tx_list(get_mbox_id(msg->channel)); + if (tx_list == NULL) { + dev_err(&channels.pdev->dev, "given mbox id is not valid\n"); + res = -EINVAL; + goto exit; + } + + mbox_unit = container_of(tx_list, struct mbox_unit_status, tx_chans); + + spin_lock(&mbox_unit->tx_lock); + dev_dbg(&channels.pdev->dev, "send:tx_list=%x\tmbox_unit=%x\n", + (u32)tx_list, (u32)mbox_unit); + list_for_each(pos, tx_list) { + tmp = list_entry(pos, struct channel_status, list); + if (tmp->channel == msg->channel) + tx_chan = tmp; + } + spin_unlock(&mbox_unit->tx_lock); + /* Allocate pending element and add it to the list */ + pending = kzalloc(sizeof(*pending), GFP_KERNEL); + if (pending == NULL) { + dev_err(&channels.pdev->dev, + "couldn't allocate memory for pending\n"); + res = -ENOMEM; + goto exit; + } + pending->data = msg->data; + pending->length = msg->length; + + if (tx_chan) { + mutex_lock(&tx_chan->lock); + list_add_tail(&pending->list, &tx_chan->tx.pending); + tx_chan->cb = msg->cb; + tx_chan->priv = msg->priv; + /* If channel is already opened start sending data */ + if (tx_chan->state == MBOX_OPEN) + send_pdu(tx_chan, MBOX_CAST, tx_chan->channel); + /* Stop processing here */ + mutex_unlock(&tx_chan->lock); + } else { + /* No channel found on the list, allocate new element */ + tx_chan = kzalloc(sizeof(*tx_chan), GFP_KERNEL); + if (tx_chan == NULL) { + dev_err(&channels.pdev->dev, + "couldn't allocate memory for \ + tx_chan\n"); + res = -ENOMEM; + goto exit; + } + tx_chan->channel = msg->channel; + tx_chan->cb = msg->cb; + tx_chan->priv = msg->priv; + tx_chan->state = MBOX_CLOSE; + tx_chan->seq_number = CHANNEL_START_SEQUENCE_NUMBER; + INIT_LIST_HEAD(&tx_chan->tx.pending); + INIT_WORK(&tx_chan->open_msg, mbox_handle_open_msg); + INIT_WORK(&tx_chan->cast_msg, mbox_handle_cast_msg); + INIT_WORK(&tx_chan->receive_msg, mbox_handle_receive_msg); + mutex_init(&tx_chan->lock); + spin_lock(&mbox_unit->tx_lock); + list_add_tail(&tx_chan->list, tx_list); + spin_unlock(&mbox_unit->tx_lock); + mutex_lock(&tx_chan->lock); + list_add_tail(&pending->list, &tx_chan->tx.pending); + mutex_unlock(&tx_chan->lock); + } + return 0; + +exit: + return res; +} +EXPORT_SYMBOL(mbox_channel_send); + +static void revoke_pending_msgs(struct channel_status *tx_chan) +{ + struct list_head *pos, *n; + struct pending_elem *pending; + + list_for_each_safe(pos, n, &tx_chan->tx.pending) { + pending = list_entry(pos, struct pending_elem, list); + + if (tx_chan->cb) + tx_chan->cb(pending->data, pending->length, + tx_chan->priv); + else + dev_err(&channels.pdev->dev, + "%s no callback provided\n", __func__); + list_del(&pending->list); + kfree(pending); + } +} + +/** + * mbox_channel_revoke_messages() - Revoke pending messages + * @channel: Channel on which action to be taken. + * + * This routine Clear all pending messages from TX channel + * It searches for the channel.Checks if there is pending + * messages.Calls if tehre is any registered function. And + * deletes the messages for the pending list. + */ +int mbox_channel_revoke_messages(u16 channel) +{ + struct list_head *pos, *tx_list; + struct channel_status *tmp; + struct channel_status *tx_chan = NULL; + struct mbox_unit_status *mbox_unit; + int res = 0; + + if (!check_channel(channel, MBOX_TX)) { + dev_err(&channels.pdev->dev, + "wrong channel number %d\n", channel); + return -EINVAL; + } + + tx_list = get_tx_list(get_mbox_id(channel)); + if (tx_list == NULL) { + dev_err(&channels.pdev->dev, "given mbox id is not valid\n"); + return -EINVAL; + } + + mbox_unit = container_of(tx_list, struct mbox_unit_status, tx_chans); + + spin_lock(&mbox_unit->tx_lock); + list_for_each(pos, tx_list) { + tmp = list_entry(pos, struct channel_status, list); + if (tmp->channel == channel) + tx_chan = tmp; + } + spin_unlock(&mbox_unit->tx_lock); + + if (tx_chan) { + mutex_lock(&tx_chan->lock); + revoke_pending_msgs(tx_chan); + mutex_unlock(&tx_chan->lock); + dev_dbg(&channels.pdev->dev, "channel %d cleared\n", + channel); + } else { + dev_err(&channels.pdev->dev, "no channel found\n"); + res = -EINVAL; + } + + dev_dbg(&channels.pdev->dev, "%s exiting %d\n", __func__, res); + return res; +} +EXPORT_SYMBOL(mbox_channel_revoke_messages); + +#if defined(CONFIG_DEBUG_FS) +#define MBOXTEST_DEBUG 1 +#ifdef MBOXTEST_DEBUG +#define DBG_TEST(x) x +#else +#define DBG_TEST(x) +#endif + +#define MBOX_TEST_MAX_WORDS 3 +#define MBOX_RX_CHAN 0x500 +#define MBOX_TX_RX_CHANNEL_DIFF 0x400 +#define MBOX_MAX_NUM_TRANSFER 30000 +static int registration_done; +/** + * struct mboxtest_data - mbox test via debugfs information + * @rx_buff: Buffer for incomming data + * @rx_pointer: Ptr to actual RX data buff + * @tx_buff: Buffer for outgoing data + * @tx_pointer: Ptr to actual TX data buff + * @tx_done: TX Transfer done indicator + * @rx_done: RX Transfer done indicator + * @received: Received words + * @xfer_words: Num of bytes in actual trf + * @xfers: Number of transfers + * @words: Number of total words + * @channel: Channel test number + */ +struct mboxtest_data { + unsigned int *rx_buff; + unsigned int *rx_pointer; + unsigned int *tx_buff; + unsigned int *tx_pointer; + struct completion tx_done; + struct completion rx_done; + int received; + int xfer_words; + int xfers; + int words; + int channel; +}; + +static void mboxtest_receive_cb(u32 *data, u32 len, void *arg) +{ + struct mboxtest_data *mboxtest = (struct mboxtest_data *) arg; + int i; + + printk(KERN_INFO "receive_cb.. data.= 0x%X, len = %d\n", + *data, len); + for (i = 0; i < len; i++) + *(mboxtest->rx_pointer++) = *(data++); + + mboxtest->received += len; + + printk(KERN_INFO "received = %d, words = %d\n", + mboxtest->received, mboxtest->words); + if (mboxtest->received >= mboxtest->words) + complete(&mboxtest->rx_done); + dev_dbg(&channels.pdev->dev, "%s exiting\n", __func__); +} + +static void mboxtest_send_cb(u32 *data, u32 len, void *arg) +{ + struct mboxtest_data *mboxtest = (struct mboxtest_data *) arg; + + printk(KERN_INFO "send_cb.. data.= 0x%X, len = %d\n", + *data, len); + + complete(&mboxtest->tx_done); + dev_dbg(&channels.pdev->dev, "kernel:mboxtest_send_cb exiting\n"); +} + +static int mboxtest_transmit(struct mboxtest_data *mboxtest) +{ + int status = 0; + struct mbox_channel_msg msg; + + dev_dbg(&channels.pdev->dev, "%s entering\n", __func__); + init_completion(&mboxtest->tx_done); + + msg.channel = mboxtest->channel; + msg.data = mboxtest->tx_pointer; + msg.length = mboxtest->words; + msg.cb = mboxtest_send_cb; + msg.priv = mboxtest; + + status = mbox_channel_send(&msg); + if (!status) { + mboxtest->tx_pointer += mboxtest->xfer_words; + wait_for_completion(&mboxtest->tx_done); + } + + dev_dbg(&channels.pdev->dev, "%s exiting %d\n", + __func__, status); + return status; +} + +static int transfer_test(struct mboxtest_data *mboxtest) +{ + int status = 0; + int len = 0; + int i; + + len = mboxtest->words; + + dev_dbg(&channels.pdev->dev, "%s enterring\n", __func__); + /* Allocate buffers */ + mboxtest->rx_buff = kzalloc(sizeof(unsigned int) * len, GFP_KERNEL); + if (!mboxtest->rx_buff) { + DBG_TEST(printk(KERN_INFO + "Cannot allocate mbox rx memory\n")); + status = -ENOMEM; + goto err1; + } + memset(mboxtest->rx_buff, '\0', sizeof(unsigned int) * len); + + mboxtest->tx_buff = kzalloc(sizeof(unsigned int) * len, GFP_KERNEL); + if (!mboxtest->tx_buff) { + DBG_TEST(printk(KERN_INFO + "Cannot allocate mbox tx memory\n")); + status = -ENOMEM; + goto err2; + } + memset(mboxtest->tx_buff, '\0', sizeof(unsigned int) * len); + + /* Generate data */ + get_random_bytes((unsigned char *)mboxtest->tx_buff, + sizeof(unsigned int) * len); + /* Set pointers */ + mboxtest->tx_pointer = mboxtest->tx_buff; + mboxtest->rx_pointer = mboxtest->rx_buff; + mboxtest->received = 0; + init_completion(&mboxtest->rx_done); + + /* Start tx transfer test transfer */ + status = mboxtest_transmit(mboxtest); + DBG_TEST(printk(KERN_INFO "xfer_words=%d\n", + mboxtest->xfer_words)); + if (!status) + wait_for_completion(&mboxtest->rx_done); + for (i = 0; i < len; i++) + DBG_TEST(printk(KERN_INFO "%d -> TX:0x%X, RX:0x%X\n", i, + mboxtest->tx_buff[i], mboxtest->rx_buff[i])); + + dev_dbg(&channels.pdev->dev, "%s exiting %d\n", __func__, status); + return status; +err2: + kfree(mboxtest->rx_buff); +err1: + return status; +} + +static int mboxtest_prepare(struct mboxtest_data *mboxtest) +{ + int err = 0; + + mboxtest->xfers = MBOX_MAX_NUM_TRANSFER; + /* Calculate number of bytes in each transfer */ + mboxtest->xfer_words = mboxtest->words / mboxtest->xfers; + + /* Trim to maxiumum data words per transfer */ + if (mboxtest->xfer_words > MBOX_TEST_MAX_WORDS) { + DBG_TEST(printk(KERN_INFO "Recalculating xfers ...\n")); + mboxtest->xfer_words = MBOX_TEST_MAX_WORDS; + if (mboxtest->words % mboxtest->xfer_words) + mboxtest->xfers = (mboxtest->words / + mboxtest->xfer_words) + 1; + else + mboxtest->xfers = (mboxtest->words / + mboxtest->xfer_words); + } + + DBG_TEST(printk(KERN_INFO "Params: chan=0x%X words=%d, xfers=%d\n", + mboxtest->channel, mboxtest->words, + mboxtest->xfers)); + + if (mbox_channel_register(mboxtest->channel, + mboxtest_receive_cb, mboxtest)) { + DBG_TEST(printk(KERN_INFO "Cannot register mbox channel\n")); + err = -ENOMEM; + goto err; + } + + registration_done = true; + return 0; +err: + return err; +} + +struct mboxtest_data mboxtest; +/* + * Expected input: <nbr_channel> <nbr_word> + * Example: "echo 500 2" + */ +static ssize_t mbox_write_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + unsigned long nbr_channel; + unsigned long nbr_word; + char int_buf[16]; + char *token; + char *val; + + strncpy((char *) &int_buf, buf, sizeof(int_buf)); + token = (char *) &int_buf; + + /* Parse message */ + val = strsep(&token, " "); + if ((val == NULL) || (strict_strtoul(val, 16, &nbr_channel) != 0)) + nbr_channel = MBOX_RX_CHAN; + + val = strsep(&token, " "); + if ((val == NULL) || (strict_strtoul(val, 16, &nbr_word) != 0)) + nbr_word = 2; + + dev_dbg(dev, "Will setup logical channel %ld\n", nbr_channel); + mboxtest.channel = nbr_channel; + mboxtest.words = nbr_word; + + if (!registration_done) + mboxtest_prepare(&mboxtest); + else + dev_dbg(&channels.pdev->dev, "already registration done\n"); + + return count; +} + +static ssize_t mbox_read_channel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + + unsigned long i; + static bool config_done; + + if (!config_done) { + config_done = true; + mboxtest.channel += MBOX_TX_RX_CHANNEL_DIFF; + } + dev_dbg(dev, "Will transfer %d words %d times at channel 0x%x\n", + mboxtest.words, mboxtest.xfers, mboxtest.channel); + for (i = 0; i < mboxtest.xfers; i++) + transfer_test(&mboxtest); + + return 1; +} +static DEVICE_ATTR(channel, S_IWUGO | S_IRUGO, mbox_read_channel, + mbox_write_channel); + +#endif + +static int __init mbox_channel_probe(struct platform_device *pdev) +{ + int i, ret = 0; + struct mbox *mbox; + + dev_dbg(&(pdev->dev), "Probing mailbox (pdev = 0x%X)...\n", (u32)pdev); + + /* Register to given mailbox units (ids) */ + for (i = 0; i < ARRAY_SIZE(mbox_ids); i++) { + mbox = mbox_setup(mbox_ids[i], mbox_cb, &mbox_ids[i]); + if (mbox == NULL) { + dev_err(&(pdev->dev), "Unable to setup mailbox %d\n", + mbox_ids[i]); + ret = -EBUSY; + goto exit; + } + channels.mbox_unit[i].mbox_id = mbox_ids[i]; + channels.mbox_unit[i].mbox = mbox; + INIT_LIST_HEAD(&channels.mbox_unit[i].rx_chans); + INIT_LIST_HEAD(&channels.mbox_unit[i].tx_chans); + spin_lock_init(&channels.mbox_unit[i].rx_lock); + spin_lock_init(&channels.mbox_unit[i].tx_lock); + } + + channels.pdev = pdev; + + dev_dbg(&(pdev->dev), "Mailbox channel driver loaded\n"); +#if defined(CONFIG_DEBUG_FS) + ret = device_create_file(&(pdev->dev), &dev_attr_channel); + if (ret != 0) + dev_warn(&(pdev->dev), + "Unable to create mbox_channel sysfs entry"); + + +#endif +exit: + return ret; +} + +static struct platform_driver mbox_channel_driver = { + .driver = { + .name = "mbox_channel", + .owner = THIS_MODULE, + }, +}; + +static int __init mbox_channel_init(void) +{ + if (!machine_is_u5500()) + return 0; + + platform_device_register_simple("mbox_channel", 0, NULL, 0); + + return platform_driver_probe(&mbox_channel_driver, mbox_channel_probe); +} +module_init(mbox_channel_init); + +static void __exit mbox_channel_exit(void) +{ + platform_driver_unregister(&mbox_channel_driver); +} +module_exit(mbox_channel_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MBOX channels driver"); diff --git a/drivers/misc/modem_audio/Kconfig b/drivers/misc/modem_audio/Kconfig new file mode 100644 index 00000000000..5396868a9de --- /dev/null +++ b/drivers/misc/modem_audio/Kconfig @@ -0,0 +1,6 @@ +config MODEM_AUDIO_DRIVER + bool "Modem Audio Driver" + depends on (U5500_MBOX && UX500_SOC_DB5500) + help + This module is used for read and write data between APE and + Access side in u5500 platform. diff --git a/drivers/misc/modem_audio/Makefile b/drivers/misc/modem_audio/Makefile new file mode 100644 index 00000000000..a5c1740ea48 --- /dev/null +++ b/drivers/misc/modem_audio/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_MODEM_AUDIO_DRIVER) += mad.o + diff --git a/drivers/misc/modem_audio/mad.c b/drivers/misc/modem_audio/mad.c new file mode 100644 index 00000000000..d31d78ba3f2 --- /dev/null +++ b/drivers/misc/modem_audio/mad.c @@ -0,0 +1,506 @@ +/* + * Copyright (C) ST-Ericsson AB 2011 + * + * Modem Audio Driver + * + * Author:Rahul Venkatram <rahul.venkatram@stericsson.com> for ST-Ericsson + * Haridhar KALVALA<haridhar.kalvala@stericsson.com> for ST-Ericsson + * Amaresh Mulage<amaresh.mulage@stericsson.com> for ST-Ericsson. + * + * License terms:GNU General Public License (GPLv2)version 2 + */ + +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/sched.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/fcntl.h> +#include <linux/spinlock.h> +#include <mach/mbox_channels-db5500.h> + +MODULE_DESCRIPTION("Modem Audio Driver"); +MODULE_LICENSE("GPLv2"); + +/** + * ----------------------------------------------------- + * | | | | + * | Data[0] |Data[1] |Data[2] |===>Data word 32 bits + * ----------------------------------------------------- + * | MESSAGE |Data | Index | + * | TYPE |length | number |===>READ/WRITE message + * ----------------------------------------------------- + * ----------------------------------------------------- + * | MESSAGE | DSP SHM addr | max_no_of_buffers |===> READ + * | TYPE | to write data | ||buffersize |WRITE SETUP message + * ----------------------------------------------------- + */ + + +#define MAD_NAME "mad" +/* Bit mask */ +#define MASK_UPPER_WORD 0xFFFF + +/* channel values for each direction */ +#define CHANNEL_NUM_RX 0x500 +#define CHANNEL_NUM_TX 0x900 + +/* + * Maximum number of datawords which can be sent + * in the mailbox each word is 32 bits + */ +#define MAX_NR_OF_DATAWORDS MAILBOX_NR_OF_DATAWORDS +#define MAX_NUM_RX_BUFF NUM_DSP_BUFFER +#define NR_OF_DATAWORDS_REQD_FOR_ACK 1 + +/** + * Message types, must be identical in DSP Side + * VCS_MBOX_MSG_WRITE_IF_SETUP : DSP -> ARM + * VCS_MBOX_MSG_WRITE_IF_SETUP_ACK : ARM -> DSP + * VCS_MBOX_MSG_READ_IF_SETUP : DSP -> ARM + * VCS_MBOX_MSG_READ_IF_SETUP_ACK : ARM -> DSP + * VCS_MBOX_MSG_IF_ENC_DATA : ARM -> DSP + * VCS_MBOX_MSG_IF_DEC_DATA : DSP -> ARM + */ +#define VCS_MBOX_MSG_WRITE_IF_SETUP 0x200 +#define VCS_MBOX_MSG_WRITE_IF_SETUP_ACK 0x201 +#define VCS_MBOX_MSG_READ_IF_SETUP 0x400 +#define VCS_MBOX_MSG_READ_IF_SETUP_ACK 0x401 +#define VCS_MBOX_MSG_IF_ENC_DATA 0x80 +#define VCS_MBOX_MSG_IF_DEC_DATA 0x100 + +/** + * struct mad_data - This structure holds the state of the Modem Audio Driver. + * + * @dsp_shm_write_ptr : Ptr to the first TX buffer in DSP + * @dsp_shm_read_ptr : Ptr to the first RX buffer in DSP + * @max_tx_buffs : No. of DSP buffers available to write + * @max_rx_buffs : No. of DSP buffers available to read + * @write_offset : Size of each buffer in the DSP + * @read_offset : Size of each buffer in the DSP + * @rx_buff : Buffer for incoming data + * @tx_buff : Buffer for outgoing data + * @tx_buffer_num : Buffer counter for writing to DSP + * @rx_buffer_num : Buffer counter for reading to DSP + * @rx_buffer_read : Buffer counter for reading from userspace + * @data_written : RX data message arrival indicator + * @read_setup_msg : flag for opening read data + * @readq : read queue of data message + * @lock : lock for r/w message queue + */ +struct mad_data { + void __iomem *dsp_shm_write_ptr; + void __iomem *dsp_shm_read_ptr; + int max_tx_buffs; + int max_rx_buffs; + int write_offset; + int read_offset; + u32 *rx_buff; + u32 *tx_buff; + int tx_buffer_num; + int rx_buffer_num; + int rx_buffer_read; + u32 data_written; + bool read_setup_msg; + bool open_check; + wait_queue_head_t readq; + spinlock_t lock; +}; + +static struct mad_data *mad; + +static void mad_receive_cb(u32 *data, u32 length, void *priv); +static int mad_read(struct file *filp, char __user *buff, size_t count, + loff_t *offp); +static int mad_write(struct file *filp, const char __user *buff, size_t count, + loff_t *offp); +static unsigned int mad_select(struct file *filp, poll_table *wait); +static void mad_send_cb(u32 *data, u32 len, void *arg); +static int mad_open(struct inode *ino, struct file *filp); +static int mad_close(struct inode *ino, struct file *filp); + +static const struct file_operations mad_fops = { + .release = mad_close, + .open = mad_open, + .read = mad_read, + .write = mad_write, + .poll = mad_select, + .owner = THIS_MODULE, +}; + +static struct miscdevice mad_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = MAD_NAME, + .fops = &mad_fops +}; + +/** + * mad_send_cb - This function is default callback for send. + * @data -Pointer to the data buffer + * @len -Data buffer length + * @arg -Private data pointer associated with test + */ +static void mad_send_cb(u32 *data, u32 len, void *arg) +{ + dev_dbg(mad_dev.this_device, "%s", __func__); +} + +/** + * mad_receive_cb - This callback function is for receiving data from mailbox + * @data -Pointer to the data buffer + * @len -length of the Mailbox + * @arg -Private data pointer associated with test + */ +static void mad_receive_cb(u32 *data, u32 length, void *priv) +{ + struct mad_data *mad = priv; + struct mbox_channel_msg msg; + u32 ack_to_dsp; + unsigned long flags; + + /* setup message for write address */ + if (*data == VCS_MBOX_MSG_WRITE_IF_SETUP) { + + ack_to_dsp = VCS_MBOX_MSG_WRITE_IF_SETUP_ACK; + + /* if setup message comes again.unmap */ + if (mad->dsp_shm_write_ptr != NULL) { + iounmap(mad->dsp_shm_write_ptr); + mad->dsp_shm_write_ptr = NULL; + mad->write_offset = 0; + mad->max_tx_buffs = 0; + } + + /* convert offset to uint size */ + mad->write_offset = (data[2] & MASK_UPPER_WORD); + mad->max_tx_buffs = (data[2] >> 16); + + mad->dsp_shm_write_ptr = ioremap(data[1], + mad->max_tx_buffs * mad->write_offset); + if (mad->dsp_shm_write_ptr == NULL) + dev_err(mad_dev.this_device, "incrt write address"); + + /* Initialize all buffer numbers */ + mad->tx_buffer_num = 0; + + /* Send ACK to the DSP */ + msg.channel = CHANNEL_NUM_TX; + msg.data = &ack_to_dsp; + msg.length = NR_OF_DATAWORDS_REQD_FOR_ACK; + msg.cb = mad_send_cb; + msg.priv = mad; + + if (mbox_channel_send(&msg)) + dev_err(mad_dev.this_device, "%s: can't send data\n", + __func__); + + } /* setup message for reading SHM */ + else if (*data == VCS_MBOX_MSG_READ_IF_SETUP) { + + ack_to_dsp = VCS_MBOX_MSG_READ_IF_SETUP_ACK; + + /* if setup message comes again.unmap */ + if (mad->dsp_shm_read_ptr != NULL) { + iounmap(mad->dsp_shm_read_ptr); + mad->dsp_shm_read_ptr = NULL; + mad->read_offset = 0; + mad->max_rx_buffs = 0; + } + + /*convert offset to uint size*/ + mad->read_offset = (data[2] & MASK_UPPER_WORD); + mad->max_rx_buffs = data[2] >> 16; + + mad->dsp_shm_read_ptr = ioremap(data[1], + mad->max_rx_buffs * mad->read_offset); + + /* Initialize all buffer numbers and flags */ + mad->rx_buffer_num = 0; + mad->rx_buffer_read = 0; + mad->data_written = 0; + + /* Send ACK to the DSP */ + msg.channel = CHANNEL_NUM_TX; + msg.data = &ack_to_dsp; + msg.length = NR_OF_DATAWORDS_REQD_FOR_ACK; + msg.cb = mad_send_cb; + msg.priv = mad; + + if (mbox_channel_send(&msg)) + dev_err(mad_dev.this_device, "%s: can't send data\n", + __func__); + + /* allow read */ + spin_lock_irqsave(&mad->lock, flags); + mad->read_setup_msg = true; + spin_unlock_irqrestore(&mad->lock, flags); + /* blocked in select() */ + wake_up_interruptible(&mad->readq); + + } else if (*data == VCS_MBOX_MSG_IF_DEC_DATA) { + /* + * Check if you have valid message with proper length in message + * otherwise Dont care + */ + if ((data[1] <= 0) || (mad->rx_buff == NULL) + || (mad->dsp_shm_read_ptr == NULL)) { + if (mad->rx_buff == NULL) + dev_warn(mad_dev.this_device, "%s :MAD closed", + __func__); + else + dev_warn(mad_dev.this_device, "%s :0-len msg", + __func__); + } else { + mad->rx_buff[mad->rx_buffer_num] = data[1]; + mad->rx_buffer_num++; + + /* store the offset */ + mad->rx_buff[mad->rx_buffer_num] = data[2]; + + if (mad->rx_buffer_num < ((MAX_NUM_RX_BUFF * 2)-1)) + mad->rx_buffer_num++; + else + mad->rx_buffer_num = 0; + + spin_lock_irqsave(&mad->lock, flags); + mad->data_written++; + + if (mad->data_written > MAX_NUM_RX_BUFF) { + dev_warn(mad_dev.this_device, + "%s :Read msg overflow = %u\n", + __func__ , mad->data_written); + /* + * Donot exceed MAX_NUM_RX_BUFF size of buffer + * TO DO overflow control + */ + mad->data_written = MAX_NUM_RX_BUFF ; + } + spin_unlock_irqrestore(&mad->lock, flags); + wake_up_interruptible(&mad->readq); + } + } else { + /* received Invalid message */ + dev_err(mad_dev.this_device, "%s : Invalid Msg", __func__); + } +} + +static int mad_read(struct file *filp, char __user *buff, size_t count, + loff_t *offp) +{ + unsigned long flags; + unsigned int size = 0; + void __iomem *shm_ptr = NULL; + + dev_dbg(mad_dev.this_device, "%s", __func__); + + if (!(mad->data_written > 0)) { + if (wait_event_interruptible(mad->readq, + ((mad->data_written > 0) && + (mad->dsp_shm_read_ptr != NULL)))) + return -ERESTARTSYS; + } + + if (mad->dsp_shm_read_ptr == NULL) { + dev_err(mad_dev.this_device, "%s :pointer err", __func__); + return -EINVAL ; + } + + if (mad->rx_buff[mad->rx_buffer_read] > count) { + /* + * Size of message greater than buffer , this shouldnt happen + * It shouldnt come here : we ensured that message size + * smaller that buffer length + */ + dev_err(mad_dev.this_device, "%s : Incrct length", __func__); + return -EFAULT; + } + size = mad->rx_buff[mad->rx_buffer_read]; + mad->rx_buff[mad->rx_buffer_read] = 0; + mad->rx_buffer_read++; + shm_ptr = (u8 *)(mad->dsp_shm_read_ptr + + (mad->rx_buff[mad->rx_buffer_read] * mad->read_offset)); + if (copy_to_user(buff, shm_ptr, size) < 0) { + dev_err(mad_dev.this_device, "%s :copy to user", __func__); + return -EFAULT; + } + + if (mad->rx_buffer_read < ((MAX_NUM_RX_BUFF*2)-1)) + mad->rx_buffer_read++; + else + mad->rx_buffer_read = 0; + + spin_lock_irqsave(&mad->lock, flags); + mad->data_written--; + if (mad->data_written < 0) { + /* Means wrong read*/ + mad->data_written = 0; + dev_err(mad_dev.this_device, "%s :data Rcev err", __func__); + } + spin_unlock_irqrestore(&mad->lock, flags); + return size; +} + +static int mad_write(struct file *filp, const char __user *buff, size_t count, + loff_t *offp) +{ + int retval = 0; + void __iomem *dsp_write_address; + struct mbox_channel_msg msg; + + dev_dbg(mad_dev.this_device, "%s", __func__); + + /* check for valid write pointer else skip writing*/ + if (mad->dsp_shm_write_ptr == NULL) { + dev_err(mad_dev.this_device, "%s :Illegal memory", __func__); + return -EFAULT; + } + + dsp_write_address = (mad->dsp_shm_write_ptr + + (mad->tx_buffer_num * mad->write_offset)); + + if (copy_from_user(dsp_write_address, buff, count)) { + dev_err(mad_dev.this_device, "%s:copy_from_user\n", __func__); + return -EFAULT; + } + + mad->tx_buff[0] = VCS_MBOX_MSG_IF_ENC_DATA; + mad->tx_buff[1] = count; + mad->tx_buff[2] = mad->tx_buffer_num; + + if (mad->tx_buffer_num < (mad->max_tx_buffs-1)) + mad->tx_buffer_num++; + else + mad->tx_buffer_num = 0; + + msg.channel = CHANNEL_NUM_TX; + msg.data = mad->tx_buff; + msg.length = MAX_NR_OF_DATAWORDS; + msg.cb = mad_send_cb; + msg.priv = mad; + + retval = mbox_channel_send(&msg); + if (retval) { + dev_err(mad_dev.this_device, "%s:can't send data", __func__); + return retval; + } + return count; +} + +static unsigned int mad_select(struct file *filp, poll_table *wait) +{ + unsigned int mask = 0; + unsigned long flags; + + dev_dbg(mad_dev.this_device, "%s", __func__); + + poll_wait(filp, &mad->readq, wait); + spin_lock_irqsave(&mad->lock, flags); + + if ((true == mad->read_setup_msg) && (mad->data_written > 0)) + mask |= POLLIN | POLLRDNORM; /* allow readable */ + spin_unlock_irqrestore(&mad->lock, flags); + + return mask; +} + +static int mad_open(struct inode *ino, struct file *filp) +{ + int err = 0; + + dev_dbg(mad_dev.this_device, "%s", __func__); + + if (mad->open_check == true) { + dev_err(mad_dev.this_device, "%s :Already opened", __func__); + return -EFAULT; + } + + mad->rx_buff = kzalloc((MAX_NUM_RX_BUFF*2 * + sizeof(mad->rx_buff)), GFP_KERNEL); + + if (mad->rx_buff == NULL) { + dev_err(mad_dev.this_device, "%s:RX memory\n", __func__); + err = -ENOMEM; + goto error; + } + + mad->tx_buff = kzalloc(MAX_NR_OF_DATAWORDS, GFP_KERNEL); + if (mad->tx_buff == NULL) { + dev_err(mad_dev.this_device, "%s:TX memory\n", __func__); + err = -ENOMEM; + goto error; + } + + /* Init spinlock for critical section access*/ + spin_lock_init(&mad->lock); + init_waitqueue_head(&(mad->readq)); + + err = mbox_channel_register(CHANNEL_NUM_RX, mad_receive_cb, mad); + if (err) { + dev_err(mad_dev.this_device, "%s: register err", __func__); + err = -EFAULT; + goto error; + } + mad->open_check = true; + + return 0; +error: + kfree(mad->rx_buff); + kfree(mad->tx_buff); + return err; +} + +static int mad_close(struct inode *ino, struct file *filp) +{ + dev_dbg(mad_dev.this_device, "%s", __func__); + + if (mbox_channel_deregister(CHANNEL_NUM_RX)) { + dev_err(mad_dev.this_device, "%s:deregister err", __func__); + return -EFAULT; + } + kfree(mad->rx_buff); + kfree(mad->tx_buff); + mad->data_written = 0; + mad->rx_buffer_num = 0; + mad->rx_buffer_read = 0; + mad->open_check = false; + + return 0; +} + +static int __init mad_init(void) +{ + dev_dbg(mad_dev.this_device, "%s", __func__); + + mad = kzalloc(sizeof(*mad), GFP_KERNEL); + if (mad == NULL) { + dev_err(mad_dev.this_device, "%s :MAD failed", __func__); + return -ENOMEM; + } + + return misc_register(&mad_dev); +} +module_init(mad_init); + +static void __exit mad_exit(void) +{ + dev_dbg(mad_dev.this_device, "%s", __func__); + + if (mad->dsp_shm_write_ptr != NULL) { + iounmap(mad->dsp_shm_write_ptr); + mad->dsp_shm_write_ptr = NULL; + } + + if (mad->dsp_shm_read_ptr != NULL) { + iounmap(mad->dsp_shm_read_ptr); + mad->dsp_shm_read_ptr = NULL; + } + + kfree(mad); + misc_deregister(&mad_dev); +} diff --git a/drivers/misc/sim_detect.c b/drivers/misc/sim_detect.c new file mode 100644 index 00000000000..6e6446d0fc5 --- /dev/null +++ b/drivers/misc/sim_detect.c @@ -0,0 +1,304 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: BIBEK BASU <bibek.basu@stericsson.com> + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/hrtimer.h> +#include <linux/workqueue.h> +#include <linux/uaccess.h> +#include <linux/modem/modem_client.h> +#include <mach/sim_detect.h> +#include <linux/regulator/consumer.h> + +/* time in millisec */ +#define TIMER_DELAY 10 + +struct sim_detect{ + struct work_struct timer_expired; + struct device *dev; + struct modem *modem; + struct hrtimer timer; + struct mutex lock; + int voltage; + struct regulator *vinvsim_regulator; + bool regulator_enabled; +}; + +static ssize_t show_voltage(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sim_detect *data = dev_get_drvdata(dev); + int ret, len; + + ret = mutex_lock_interruptible(&data->lock); + if (ret < 0) + return ret; + + len = sprintf(buf, "%i\n", data->voltage); + + mutex_unlock(&data->lock); + + return len; +} + +static ssize_t write_voltage(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sim_detect *sim_detect = dev_get_drvdata(dev); + long val; + int ret; + + /* check input */ + if (strict_strtol(buf, 0, &val) != 0) { + dev_err(dev, "Invalid voltage class configured.\n"); + return count; + } + + switch (val) { + case -1: + case 0: + case 1800000: + case 3000000: + break; + default: + dev_err(dev, "Invalid voltage class configured.\n"); + return count; + } + + /* lock */ + ret = mutex_lock_interruptible(&sim_detect->lock); + if (ret < 0) + return ret; + + /* update state */ + sim_detect->voltage = val; + + /* call regulator */ + switch (sim_detect->voltage) { + case 0: + /* SIM voltage is unknown, turn on regulator for 3 V SIM */ + case 3000000: + /* Vinvsim supply is used only for 3 V SIM */ + if (!sim_detect->regulator_enabled) { + ret = regulator_enable(sim_detect->vinvsim_regulator); + if (ret) { + dev_err(dev, "Failed to enable regulator.\n"); + goto out_unlock; + } + sim_detect->regulator_enabled = true; + } + break; + case 1800000: + case -1: + /* Vbatvsim is used otherwise */ + if (sim_detect->regulator_enabled) { + regulator_disable(sim_detect->vinvsim_regulator); + sim_detect->regulator_enabled = false; + } + } + +out_unlock: + /* unlock and return */ + mutex_unlock(&sim_detect->lock); + + return count; +} + +static DEVICE_ATTR(voltage, S_IWUSR | S_IRUGO, show_voltage, write_voltage); + +static struct attribute *sim_attributes[] = { + &dev_attr_voltage.attr, + NULL +}; + +static const struct attribute_group sim_attr_group = { + .attrs = sim_attributes, +}; + +static void inform_modem_release(struct work_struct *work) +{ + struct sim_detect *sim_detect = + container_of(work, struct sim_detect, timer_expired); + + /* call Modem Access Framework api to release modem */ + modem_release(sim_detect->modem); +} + +static enum hrtimer_restart timer_callback(struct hrtimer *timer) +{ + struct sim_detect *sim_detect = + container_of(timer, struct sim_detect, timer); + + schedule_work(&sim_detect->timer_expired); + return HRTIMER_NORESTART; +} + +static irqreturn_t sim_activity_irq(int irq, void *dev) +{ + struct sim_detect *sim_detect = dev; + + /* call Modem Access Framework api to acquire modem */ + modem_request(sim_detect->modem); + /* start the timer for 10ms */ + hrtimer_start(&sim_detect->timer, + ktime_set(0, TIMER_DELAY*NSEC_PER_MSEC), + HRTIMER_MODE_REL); + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +/** + * sim_detect_suspend() - This routine puts the Sim detect in to sustend state. + * @dev: pointer to device structure. + * + * This routine checks the current ongoing communication with Modem by + * examining the modem_get_usage and work_pending state. + * accordingly prevents suspend if modem communication + * is on-going. + */ +int sim_detect_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sim_detect *sim_detect = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "%s called...\n", __func__); + /* if modem is accessed, event system suspend */ + if (modem_get_usage(sim_detect->modem) + || work_pending(&sim_detect->timer_expired)) + return -EBUSY; + else + return 0; +} + +static const struct dev_pm_ops sim_detect_dev_pm_ops = { + .suspend = sim_detect_suspend, +}; +#endif + + +static int __devinit sim_detect_probe(struct platform_device *pdev) +{ + struct sim_detect_platform_data *plat = dev_get_platdata(&pdev->dev); + struct sim_detect *sim_detect; + int ret; + + sim_detect = kzalloc(sizeof(struct sim_detect), GFP_KERNEL); + if (sim_detect == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + /* initialize data */ + mutex_init(&sim_detect->lock); + sim_detect->voltage = 0; + + sim_detect->dev = &pdev->dev; + INIT_WORK(&sim_detect->timer_expired, inform_modem_release); + hrtimer_init(&sim_detect->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + sim_detect->timer.function = timer_callback; + + sim_detect->modem = modem_get(sim_detect->dev, "u8500-shrm-modem"); + if (IS_ERR(sim_detect->modem)) { + ret = PTR_ERR(sim_detect->modem); + dev_err(sim_detect->dev, "Could not retrieve the modem\n"); + goto out_free; + } + + /* set drvdata */ + platform_set_drvdata(pdev, sim_detect); + + /* request irq */ + ret = request_threaded_irq(plat->irq_num, + NULL, sim_activity_irq, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "sim activity", sim_detect); + if (ret < 0) + goto out_put_modem; + + /* get regulator */ + sim_detect->regulator_enabled = false; + sim_detect->vinvsim_regulator = regulator_get(sim_detect->dev, + "vinvsim"); + if (IS_ERR(sim_detect->vinvsim_regulator)) { + dev_err(&pdev->dev, + "Failed to get regulator. (dev_name %s).\n", + dev_name(sim_detect->dev)); + ret = PTR_ERR(sim_detect->vinvsim_regulator); + goto out_free_irq; + } + + /* register sysfs entry */ + ret = sysfs_create_group(&pdev->dev.kobj, &sim_attr_group); + if (ret != 0) { + dev_err(&pdev->dev, + "Failed to create attribute group: %d\n", ret); + goto out_free_regulator; + } + + return 0; + +out_free_regulator: + regulator_put(sim_detect->vinvsim_regulator); +out_free_irq: + free_irq(plat->irq_num, sim_detect); +out_put_modem: + modem_put(sim_detect->modem); + platform_set_drvdata(pdev, NULL); +out_free: + kfree(sim_detect); + return ret; +} + +static int __devexit sim_detect_remove(struct platform_device *pdev) +{ + struct sim_detect *sim_detect = platform_get_drvdata(pdev); + + sysfs_remove_group(&pdev->dev.kobj, &sim_attr_group); + regulator_put(sim_detect->vinvsim_regulator); + modem_put(sim_detect->modem); + platform_set_drvdata(pdev, NULL); + kfree(sim_detect); + return 0; +} + +static struct platform_driver sim_detect_driver = { + .driver = { + .name = "sim-detect", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &sim_detect_dev_pm_ops, +#endif + }, + .probe = sim_detect_probe, + .remove = __devexit_p(sim_detect_remove), +}; + +static int __init sim_detect_init(void) +{ + return platform_driver_register(&sim_detect_driver); +} +module_init(sim_detect_init); + +static void __exit sim_detect_exit(void) +{ + platform_driver_unregister(&sim_detect_driver); +} +module_exit(sim_detect_exit); + +MODULE_AUTHOR("BIBEK BASU <bibek.basu@stericsson.com>"); +MODULE_DESCRIPTION("Detects SIM Hot Swap and wakes modem"); +MODULE_ALIAS("platform:sim-detect"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/modem/Kconfig b/drivers/modem/Kconfig new file mode 100644 index 00000000000..be8476ed0f9 --- /dev/null +++ b/drivers/modem/Kconfig @@ -0,0 +1,44 @@ +config MODEM + bool "Modem Access Framework" + default y + help + Add support for Modem Access Framework. It allows different + platform specific drivers to register modem access mechanisms + and allows transparent access to modem to the client drivers. + + If unsure, say N. + +config MODEM_U5500_MCDD + tristate "Modem crash dump detection driver for STE U5500 platform" + depends on (UX500_SOC_DB5500 && U5500_MODEM_IRQ && MODEM) + default y + help + Add support for Modem crash detection + driver for STE U5500 platform. + And inform userspace. + + If unsure, say N. + +config MODEM_U8500 + bool "Modem Access driver for STE U8500 platform" + depends on MODEM + default n + help + Add support for Modem Access driver on STE U8500 platform which + uses Shared Memroy as IPC mechanism between Modem processor and + Application processor. + + If unsure, say N. + +source "drivers/modem/shrm/Kconfig" + +config MODEM_M6718 + tristate "Modem Access driver for STE M6718 modem" + depends on MODEM + default n + help + Add support for the modem access driver for the M6718 modem. + + If unsure, say N. + +source "drivers/modem/m6718_spi/Kconfig" diff --git a/drivers/modem/Makefile b/drivers/modem/Makefile new file mode 100644 index 00000000000..82921988f27 --- /dev/null +++ b/drivers/modem/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_MODEM) := modem_access.o +obj-$(CONFIG_MODEM_U8500) += modem_u8500.o +obj-$(CONFIG_U8500_SHRM) += shrm/ +obj-$(CONFIG_MODEM_M6718) += modem_m6718.o +obj-$(CONFIG_MODEM_M6718_SPI) += m6718_spi/ +obj-$(CONFIG_MODEM_U5500_MCDD) += mcdd.o diff --git a/drivers/modem/m6718_spi/Kconfig b/drivers/modem/m6718_spi/Kconfig new file mode 100644 index 00000000000..f945d24a094 --- /dev/null +++ b/drivers/modem/m6718_spi/Kconfig @@ -0,0 +1,83 @@ +# +# M6718 modem SPI IPC driver kernel configuration +# +config MODEM_M6718_SPI + tristate "M6718 modem IPC SPI driver" + depends on MODEM_M6718 + default y + ---help--- + If you say Y here, you will enable the M6718 modem IPC SPI driver. + + If unsure, say Y. + +config MODEM_M6718_SPI_DEBUG + boolean "Modem driver debug" + depends on MODEM_M6718_SPI + default N + ---help--- + If you say Y here, you will enable full debug trace from the M6718 + modem driver. This should not be enabled by default. + + If unsure, say N. + +config MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE + boolean "M6718 modem state driver integration" + depends on MODEM_M6718_SPI + default y + ---help--- + Enables integration of the IPC driver with the modem state driver. + This allows the IPC driver to be notified of changes in modem state + (on, off, reset) and allows the IPC driver to cause modem state + changes if needed. + + By default this should be enabled. + +config MODEM_M6718_SPI_ENABLE_FEATURE_FRAME_DUMP + boolean "IPC SPI L1 frame dump" + depends on MODEM_M6718_SPI + default n + ---help--- + If you say Y here, you will enable dumping of the raw TX and RX frames + by the IPC driver L1. + + If unsure, say N. + +config MODEM_M6718_SPI_ENABLE_FEATURE_LOOPBACK + boolean "Modem IPC loopback support" + depends on MODEM_M6718_SPI + default y + ---help--- + If you say Y here, you will enable the IPC loopback channels/devices. + + If unsure, say Y. + + +config MODEM_M6718_SPI_ENABLE_FEATURE_VERIFY_FRAMES + boolean "Verify loopback frames" + depends on MODEM_M6718_SPI + default n + ---help--- + This will enabling checking of loopback frames to verify that the data + received is identical to the data sent. + + If unsure, say N. + +config MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT + boolean "Modem IPC throughput measurement" + depends on MODEM_M6718_SPI + default n + ---help--- + If you say Y here, you will enable the IPC link throughput + measurement and reporting. + + If unsure, say N. + +config MODEM_M6718_SPI_SET_THROUGHPUT_FREQUENCY + int "Sample rate for throughput measurements (seconds)" + default "5" + depends on MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT + help + The sample frequency for taking IPC SPI link throughput measurements. + Increasing the rate (reducing the time) will increase the accuracy of + the measurements, but will also increase the impact on link and system + performance. diff --git a/drivers/modem/m6718_spi/Makefile b/drivers/modem/m6718_spi/Makefile new file mode 100644 index 00000000000..a0a82c30b07 --- /dev/null +++ b/drivers/modem/m6718_spi/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for M6718 SPI driver +# +ifeq ($(CONFIG_MODEM_M6718_SPI_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif + +m6718_modem_spi-objs := modem_driver.o protocol.o util.o queue.o debug.o \ + netlink.o statemachine.o + +ifeq ($(CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE),y) +m6718_modem_spi-objs += modem_state.o +endif + +obj-$(CONFIG_MODEM_M6718_SPI) += m6718_modem_spi.o diff --git a/drivers/modem/m6718_spi/debug.c b/drivers/modem/m6718_spi/debug.c new file mode 100644 index 00000000000..522a37163c1 --- /dev/null +++ b/drivers/modem/m6718_spi/debug.c @@ -0,0 +1,490 @@ +/* + * 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", + "IPC_INIT_AUD", + "IPC_HALT_AUD", + "IPC_RESET_AUD", + "IPC_IDL_AUD", + "IPC_SLW_TX_WR_DAT_AUD", + "IPC_ACT_TX_WR_DAT_AUD", + "IPC_SLW_RX_WR_DAT_AUD", + "IPC_ACT_RX_WR_DAT_AUD", +}; + +/* 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..10e651c01ea --- /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 (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 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..55e4a520d3d --- /dev/null +++ b/drivers/modem/m6718_spi/modem_statemachine.h @@ -0,0 +1,81 @@ +/* + * 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; +}; + +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..fa6b2528dd4 --- /dev/null +++ b/drivers/modem/m6718_spi/protocol.c @@ -0,0 +1,431 @@ +/* + * 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_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; +} + +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 (link->id != IPC_LINK_AUDIO && 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..911d538ee82 --- /dev/null +++ b/drivers/modem/m6718_spi/queue.c @@ -0,0 +1,183 @@ +/* + * 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) + +/* fixed L1 frame size for audio link: 4 byte L2 header + 664 byte L2 payload */ +#define FRAME_SIZE_AUDIO (668) + +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; + + /* audio link frames are always a fixed size */ + if (link_context->link->id == IPC_LINK_AUDIO) { + if (l2_length > FRAME_SIZE_AUDIO) { + dev_err(&link_context->sdev->dev, + "link %d error: invalid frame size %d " + "requested, max is %d\n", + link_context->link->id, + l2_length, + FRAME_SIZE_AUDIO); + return NULL; + } + padded_len = FRAME_SIZE_AUDIO; + } else { + /* 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..a956661c3bf --- /dev/null +++ b/drivers/modem/m6718_spi/statemachine.c @@ -0,0 +1,1406 @@ +/* + * 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 const struct ipc_sm_state *sm_init_aud_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_AUD); + + /* 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)); + dev_info(&context->sdev->dev, + "link %d: boot sync not needed, going idle\n", + context->link->id); + return ipc_sm_state(IPC_SM_IDL_AUD); +} + +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 const struct ipc_sm_state *sm_idl_aud_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_AUD); + + /* always transmit data first */ + return ipc_sm_state(IPC_SM_SLW_TX_WR_DAT_AUD); +} + +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 u8 sm_slw_tx_wr_dat_aud_enter(u8 event, struct ipc_link_context *context) +{ + struct ipc_tx_queue *frame = NULL; + + /* check if there is a frame to be sent */ + if (!ipc_queue_is_empty(context)) { + frame = ipc_queue_get_frame(context); + } else { + /* no frame to send, create an empty one */ + dev_dbg(&context->sdev->dev, + "link %d: no frame to send, allocating dummy\n", + context->link->id); + frame = ipc_queue_new_frame(context, 0); + if (frame == NULL) + return IPC_SM_RUN_ABORT; + } + + ipc_dbg_dump_frame(&context->sdev->dev, context->link->id, frame, true); + + /* prepare to transfer the frame tx data */ + context->frame = frame; + ipc_util_spi_message_prepare(context, context->frame->data, + NULL, context->frame->len); + + /* slave might already have signalled ready to transmit */ + if (event == IPC_SM_RUN_SLAVE_IRQ || 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 const struct ipc_sm_state *sm_slw_tx_wr_dat_aud_exit(u8 event, + struct ipc_link_context *context) +{ + if (event == IPC_SM_RUN_RESET) + return ipc_sm_state(IPC_SM_RESET_AUD); + else if (event == IPC_SM_RUN_COMMS_TMO) + return ipc_sm_state(IPC_SM_HALT_AUD); + else + return ipc_sm_state(IPC_SM_ACT_TX_WR_DAT_AUD); +} + +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 const struct ipc_sm_state *sm_act_tx_wr_dat_aud_exit(u8 event, + struct ipc_link_context *context) +{ + if (event == IPC_SM_RUN_RESET) + return ipc_sm_state(IPC_SM_RESET_AUD); + +#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)) { + /* create a copy of the frame */ + context->last_frame = ipc_queue_new_frame(context, + context->frame->actual_len); + memcpy(context->last_frame->data, + context->frame->data, + context->frame->actual_len); + } + } +#endif + return ipc_sm_state(IPC_SM_SLW_RX_WR_DAT_AUD); +} + +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_slw_rx_wr_dat_aud_enter(u8 event, struct ipc_link_context *context) +{ + /* + * We're using the same frame buffer we just sent, so no need for a + * new allocation here, just prepare the spi message + */ + ipc_util_spi_message_prepare(context, NULL, + context->frame->data, 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_rx_wr_dat_aud_exit(u8 event, + struct ipc_link_context *context) +{ + if (event == IPC_SM_RUN_RESET) + return ipc_sm_state(IPC_SM_RESET_AUD); + else if (event == IPC_SM_RUN_COMMS_TMO) + return ipc_sm_state(IPC_SM_HALT_AUD); + else + return ipc_sm_state(IPC_SM_ACT_RX_WR_DAT_AUD); +} + +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 u8 sm_act_rx_wr_dat_aud_enter(u8 event, struct ipc_link_context *context) +{ + int err; + + 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 const struct ipc_sm_state *sm_act_rx_wr_dat_aud_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_AUD); + + dev_dbg(&context->sdev->dev, + "link %d: RX PAYLOAD %d bytes\n", + context->link->id, context->frame->len); + + /* 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; + +#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_THROUGHPUT_MEASUREMENT + /* frame is received, increment link rx counter */ + context->rx_bytes += l2_length; +#endif + if (frame_hdr != 0) + context->frame->actual_len = l2_length + IPC_L2_HDR_SIZE; + else + context->frame->actual_len = 0; + 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); + + /* did the slave actually have anything to send? */ + if (frame_hdr != 0) { + /* 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); + } + } else { + dev_dbg(&context->sdev->dev, + "link %d: received dummy frame, discarding\n", + context->link->id); + } + + /* data is copied by L2mux so free the frame here */ + ipc_queue_delete_frame(context->frame); + context->frame = NULL; + + /* audio link goes idle ready for next transaction */ + return ipc_sm_state(IPC_SM_IDL_AUD); +} + +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 const struct ipc_sm_state *sm_halt_aud_exit(u8 event, + struct ipc_link_context *context) +{ + return ipc_sm_state(IPC_SM_RESET_AUD); +} + +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 const struct ipc_sm_state *sm_reset_aud_exit(u8 event, + struct ipc_link_context *context) +{ + return ipc_sm_state(IPC_SM_INIT_AUD); +} + +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! " + "minimum 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 + }, + /* audio link states below */ + [IPC_SM_INIT_AUD] = { + .id = IPC_SM_INIT_AUD, + .enter = sm_init_enter, + .exit = sm_init_aud_exit, + .events = IPC_SM_RUN_INIT | IPC_SM_RUN_RESET + }, + [IPC_SM_HALT_AUD] = { + .id = IPC_SM_HALT_AUD, + .enter = sm_halt_enter, + .exit = sm_halt_aud_exit, + .events = IPC_SM_RUN_RESET + }, + [IPC_SM_RESET_AUD] = { + .id = IPC_SM_RESET_AUD, + .enter = sm_reset_enter, + .exit = sm_reset_aud_exit, + .events = IPC_SM_RUN_RESET + }, + [IPC_SM_IDL_AUD] = { + .id = IPC_SM_IDL_AUD, + .enter = sm_idl_enter, + .exit = sm_idl_aud_exit, + .events = IPC_SM_RUN_SLAVE_IRQ | IPC_SM_RUN_TX_REQ | + IPC_SM_RUN_RESET + }, + [IPC_SM_SLW_TX_WR_DAT_AUD] = { + .id = IPC_SM_SLW_TX_WR_DAT_AUD, + .enter = sm_slw_tx_wr_dat_aud_enter, + .exit = sm_slw_tx_wr_dat_aud_exit, + .events = IPC_SM_RUN_SLAVE_IRQ | IPC_SM_RUN_COMMS_TMO | + IPC_SM_RUN_RESET + }, + [IPC_SM_ACT_TX_WR_DAT_AUD] = { + .id = IPC_SM_ACT_TX_WR_DAT_AUD, + .enter = sm_act_tx_wr_dat_enter, + .exit = sm_act_tx_wr_dat_aud_exit, + .events = IPC_SM_RUN_TFR_COMPLETE | IPC_SM_RUN_RESET + }, + [IPC_SM_SLW_RX_WR_DAT_AUD] = { + .id = IPC_SM_SLW_RX_WR_DAT_AUD, + .enter = sm_slw_rx_wr_dat_aud_enter, + .exit = sm_slw_rx_wr_dat_aud_exit, + .events = IPC_SM_RUN_SLAVE_IRQ | IPC_SM_RUN_COMMS_TMO | + IPC_SM_RUN_RESET + }, + [IPC_SM_ACT_RX_WR_DAT_AUD] = { + .id = IPC_SM_ACT_RX_WR_DAT_AUD, + .enter = sm_act_rx_wr_dat_aud_enter, + .exit = sm_act_rx_wr_dat_aud_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) +{ + if (context->link->id == IPC_LINK_AUDIO) + return ipc_sm_state(IPC_SM_IDL_AUD); + else + return ipc_sm_state(IPC_SM_IDL); +} + +const struct ipc_sm_state *ipc_sm_init_state(struct ipc_link_context *context) +{ + if (context->link->id == IPC_LINK_AUDIO) + return ipc_sm_state(IPC_SM_INIT_AUD); + else + 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..9c89eb9b34a --- /dev/null +++ b/drivers/modem/m6718_spi/util.c @@ -0,0 +1,282 @@ +/* + * 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: + case IPC_SM_IDL_AUD: + return true; + default: + return false; + } +} + +void ipc_util_start_slave_stable_timer(struct ipc_link_context *context) +{ + context->slave_stable_timer.expires = + jiffies + ((SLAVE_STABLE_TMO_MS * HZ) / 1000); + add_timer(&context->slave_stable_timer); +} + +void ipc_util_spi_message_prepare(struct ipc_link_context *link_context, + void *tx_buf, void *rx_buf, int len) +{ + struct spi_transfer *tfr = &link_context->spi_transfer; + struct spi_message *msg = &link_context->spi_message; + + tfr->tx_buf = tx_buf; + tfr->rx_buf = rx_buf; + tfr->len = len; + msg->context = link_context; +} + +void ipc_util_spi_message_init(struct ipc_link_context *link_context, + void (*complete)(void *)) +{ + struct spi_message *msg = &link_context->spi_message; + struct spi_transfer *tfr = &link_context->spi_transfer; + + tfr->bits_per_word = 16; + + /* common init of transfer - use default from board device */ + tfr->cs_change = 0; + tfr->speed_hz = 0; + tfr->delay_usecs = 0; + + /* common init of message */ + spi_message_init(msg); + msg->spi = link_context->sdev; + msg->complete = complete; + spi_message_add_tail(tfr, msg); +} + +bool ipc_util_link_gpio_request(struct ipc_link_context *context, + irqreturn_t (*irqhnd)(int, void*)) +{ + struct spi_device *sdev = context->sdev; + struct modem_m6718_spi_link_platform_data *link = context->link; + unsigned long irqflags; + + if (gpio_request(link->gpio.ss_pin, DRIVER_NAME) < 0) { + dev_err(&sdev->dev, + "link %d error: failed to get gpio %d for SS pin\n", + link->id, + link->gpio.ss_pin); + return false; + } + if (gpio_request(link->gpio.int_pin, DRIVER_NAME) < 0) { + dev_err(&sdev->dev, + "link %d error: failed to get gpio %d for INT pin\n", + link->id, + link->gpio.int_pin); + return false; + } + + if (ipc_util_int_level_active(context) == 1) + irqflags = IRQF_TRIGGER_RISING; + else + irqflags = IRQF_TRIGGER_FALLING; + + if (request_irq(GPIO_TO_IRQ(link->gpio.int_pin), + irqhnd, + irqflags, + DRIVER_NAME, + context) < 0) { + dev_err(&sdev->dev, + "link %d error: could not get irq %d\n", + link->id, GPIO_TO_IRQ(link->gpio.int_pin)); + return false; + } + return true; +} + +bool ipc_util_link_gpio_config(struct ipc_link_context *context) +{ + struct spi_device *sdev = context->sdev; + struct modem_m6718_spi_link_platform_data *link = context->link; + + if (atomic_read(&context->gpio_configured) == 1) + return true; + + dev_dbg(&sdev->dev, "link %d: configuring GPIO\n", link->id); + + ipc_util_deactivate_ss(context); + gpio_direction_input(link->gpio.int_pin); + if (enable_irq_wake(GPIO_TO_IRQ(link->gpio.int_pin)) < 0) { + dev_err(&sdev->dev, + "link %d error: failed to enable wake on INT\n", + link->id); + return false; + } + + atomic_set(&context->state_int, gpio_get_value(link->gpio.int_pin)); + atomic_set(&context->gpio_configured, 1); + return true; +} + +bool ipc_util_link_gpio_unconfig(struct ipc_link_context *context) +{ + struct spi_device *sdev = context->sdev; + struct modem_m6718_spi_link_platform_data *link = context->link; + + if (atomic_read(&context->gpio_configured) == 0) + return true; + + dev_dbg(&sdev->dev, "link %d: un-configuring GPIO\n", link->id); + + /* SS: output anyway, just make sure it is low */ + gpio_set_value(link->gpio.ss_pin, 0); + + /* INT: disable system-wake, reconfigure as output-low */ + disable_irq_wake(GPIO_TO_IRQ(link->gpio.int_pin)); + gpio_direction_output(link->gpio.int_pin, 0); + atomic_set(&context->gpio_configured, 0); + return true; +} + +bool ipc_util_link_is_suspended(struct ipc_link_context *context) +{ + return atomic_read(&context->suspended) == 1; +} + +void ipc_util_suspend_link(struct ipc_link_context *context) +{ + atomic_set(&context->suspended, 1); +} + +void ipc_util_resume_link(struct ipc_link_context *context) +{ + atomic_set(&context->suspended, 0); +} diff --git a/drivers/modem/mcdd.c b/drivers/modem/mcdd.c new file mode 100644 index 00000000000..d291944e810 --- /dev/null +++ b/drivers/modem/mcdd.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Modem Crash Detection Driver + * + * Author:Bibek Basu <bibek.basu@stericsson.com> for ST-Ericsson + * + * License terms:GNU General Public License (GPLv2)version 2 + */ + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/sched.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> + +#define MCDD_INTERRUPT_CLEAR (1 << 13) +#define MODEM_CRASH_EVT 1 + +struct mcdd_data { + bool modem_event; + u32 event_type; + wait_queue_head_t readq; + spinlock_t lock; + void __iomem *remap_intcon; + struct device *dev; + struct miscdevice misc_dev; +}; + +static struct mcdd_data *mcdd; + +static irqreturn_t mcdd_interrupt_cb(int irq, void *dev) +{ + writel(MCDD_INTERRUPT_CLEAR, (u32 *)mcdd->remap_intcon); + spin_lock(&mcdd->lock); + mcdd->modem_event = true; + mcdd->event_type = MODEM_CRASH_EVT; + spin_unlock(&mcdd->lock); + wake_up_interruptible(&mcdd->readq); + return IRQ_HANDLED; +} + +static unsigned int mcdd_select(struct file *filp, poll_table *wait) +{ + unsigned int mask = 0; + unsigned long flags; + + poll_wait(filp, &mcdd->readq, wait); + spin_lock_irqsave(&mcdd->lock, flags); + + if (mcdd->modem_event == true) { + mask |= POLLPRI; + mcdd->modem_event = false; + } + spin_unlock_irqrestore(&mcdd->lock, flags); + + return mask; +} + +static int mcdd_open(struct inode *ino, struct file *filp) +{ + /* Do nothing */ + return 0; +} + +ssize_t mcdd_read(struct file *filp, char __user *buff, size_t size, loff_t *t) +{ + if (copy_to_user(buff, &mcdd->event_type, size)) + return -EFAULT; + return 0; +}; + +static const struct file_operations mcdd_fops = { + .open = mcdd_open, + .poll = mcdd_select, + .read = mcdd_read, + .owner = THIS_MODULE, +}; + +static int __devinit u5500_mcdd_probe(struct platform_device *pdev) +{ + struct resource *resource; + int ret = 0; + int irq; + + mcdd = kzalloc(sizeof(*mcdd), GFP_KERNEL); + if (!mcdd) { + dev_err(&pdev->dev, "Memory Allocation Failed"); + return -ENOMEM; + } + mcdd->dev = &pdev->dev; + mcdd->misc_dev.minor = MISC_DYNAMIC_MINOR; + mcdd->misc_dev.name = "mcdd"; + mcdd->misc_dev.fops = &mcdd_fops; + spin_lock_init(&mcdd->lock); + init_waitqueue_head(&(mcdd->readq)); + + /* Get addr for mcdd crash interrupt reset register and ioremap it */ + resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "mcdd_intreset_addr"); + if (resource == NULL) { + dev_err(&pdev->dev, + "Unable to retrieve mcdd_intreset_addr resource\n"); + goto exit_free; + } + mcdd->remap_intcon = ioremap(resource->start, resource_size(resource)); + if (!mcdd->remap_intcon) { + dev_err(&pdev->dev, "Unable to ioremap intcon mbox1\n"); + ret = -EINVAL; + goto exit_free; + } + + /* Get IRQ for mcdd mbox interrupt and allocate it */ + irq = platform_get_irq_byname(pdev, "mcdd_mbox_irq"); + if (irq < 0) { + dev_err(&pdev->dev, + "Unable to retrieve mcdd mbox irq resource\n"); + goto exit_unmap; + } + + ret = request_threaded_irq(irq, NULL, + mcdd_interrupt_cb, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "mcdd", &mcdd); + if (ret < 0) { + dev_err(&pdev->dev, + "Could not allocate irq %d,error %d\n", + irq, ret); + goto exit_unmap; + } + + ret = misc_register(&mcdd->misc_dev); + if (ret) { + dev_err(&pdev->dev, "can't misc-register\n"); + goto exit_unmap; + } + dev_info(&pdev->dev, "mcdd driver registration done\n"); + return 0; + +exit_unmap: + iounmap(mcdd->remap_intcon); +exit_free: + kfree(mcdd); + return ret; +} + +static int u5500_mcdd_remove(struct platform_device *pdev) +{ + int ret = 0; + + if (mcdd) { + iounmap(mcdd->remap_intcon); + ret = misc_deregister(&mcdd->misc_dev); + kfree(mcdd); + } + return ret; +} + +static struct platform_driver u5500_mcdd_driver = { + .driver = { + .name = "u5500-mcdd-modem", + .owner = THIS_MODULE, + }, + .probe = u5500_mcdd_probe, + .remove = __devexit_p(u5500_mcdd_remove), +}; + +static int __init mcdd_init(void) +{ + return platform_driver_register(&u5500_mcdd_driver); +} +module_init(mcdd_init); + +static void __exit mcdd_exit(void) +{ + platform_driver_unregister(&u5500_mcdd_driver); +} +module_exit(mcdd_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("BIBEK BASU <bibek.basu@stericsson.com>"); +MODULE_DESCRIPTION("Modem Dump Detection Driver"); +MODULE_ALIAS("mcdd driver"); diff --git a/drivers/modem/modem_access.c b/drivers/modem/modem_access.c new file mode 100644 index 00000000000..2bd32957ae2 --- /dev/null +++ b/drivers/modem/modem_access.c @@ -0,0 +1,417 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> + * + * Heavily adapted from Regulator framework. + * Provides mechanisms for registering platform specific access + * mechanisms for modem. + * Also, exposes APIs for gettng/releasing the access and even + * query the access status, and the modem usage status. + */ +#include <linux/module.h> +#include <linux/modem/modem.h> +#include <linux/modem/modem_client.h> +#include <linux/slab.h> +#include <linux/err.h> + +static DEFINE_MUTEX(modem_list_mutex); +static LIST_HEAD(modem_list); + +struct modem { + struct device *dev; + struct list_head list; + char *modem_name; + struct device_attribute dev_attr; + struct modem_dev *mdev; + atomic_t use; +}; + +static const char *mdev_get_name(struct modem_dev *mdev) +{ + if (mdev->desc->name) + return mdev->desc->name; + else + return ""; +} + +static int _modem_is_requested(struct modem_dev *mdev) +{ + /* If we don't know then assume that the modem is always on */ + if (!mdev->desc->ops->is_requested) + return 0; + + return mdev->desc->ops->is_requested(mdev); +} + +/** + * modem_is_requested - check if modem access is requested + * @modem: modem device + * + * Checks whether modem is accessed or not by querying + * the underlying platform specific modem access + * implementation. + */ +int modem_is_requested(struct modem *modem) +{ + int ret; + + mutex_lock(&modem->mdev->mutex); + ret = _modem_is_requested(modem->mdev); + mutex_unlock(&modem->mdev->mutex); + + return ret; +} +EXPORT_SYMBOL(modem_is_requested); + +static int _modem_request(struct modem_dev *mdev) +{ + int ret; + + if (++mdev->use_count == 1) { + ret = _modem_is_requested(mdev); + if (ret == 0) + mdev->desc->ops->request(mdev); + } + + return 0; +} + +/** + * modem_request - Request access the modem + * @modem: modem device + * + * API to access the modem. It keeps a client + * specific check on whether the particular modem + * requested is accessed or not. + */ +void modem_request(struct modem *modem) +{ + struct modem_dev *mdev = modem->mdev; + int ret = 0; + + + mutex_lock(&mdev->mutex); + if (atomic_read(&modem->use) == 1) { + mutex_unlock(&mdev->mutex); + return; + } + ret = _modem_request(mdev); + if (ret == 0) + atomic_set(&modem->use, 1); + mutex_unlock(&mdev->mutex); +} +EXPORT_SYMBOL(modem_request); + +static int _modem_release(struct modem_dev *mdev) +{ + if (WARN(mdev->use_count <= 0, + "unbalanced releases for %s\n", + mdev_get_name(mdev))) + return -EIO; + + if (--mdev->use_count == 0) + mdev->desc->ops->release(mdev); + + return 0; +} + +/** + * modem_release - Release access to modem + * @modem: modem device + * + * Releases accesss to the modem. It keeps a client + * specific check on whether a particular modem + * is released or not. + */ +void modem_release(struct modem *modem) +{ + struct modem_dev *mdev = modem->mdev; + int ret = 0; + + mutex_lock(&mdev->mutex); + if (atomic_read(&modem->use) == 0) { + mutex_unlock(&mdev->mutex); + return; + } + ret = _modem_release(mdev); + if (ret == 0) + atomic_set(&modem->use, 0); + mutex_unlock(&mdev->mutex); +} +EXPORT_SYMBOL(modem_release); + +/** + * modem_get_usage - Check if particular client is using modem + * @modem: modem device + * + * Checks whether the particular client is using access to modem. + * This API could be used by client drivers in making their + * suspend decisions. + */ +int modem_get_usage(struct modem *modem) +{ + return atomic_read(&modem->use); +} +EXPORT_SYMBOL(modem_get_usage); + +static struct modem *create_modem(struct modem_dev *mdev, + struct device *dev, + const char *id) +{ + struct modem *modem; + + modem = kzalloc(sizeof(*modem), GFP_KERNEL); + if (modem == NULL) + return NULL; + + mutex_lock(&mdev->mutex); + modem->mdev = mdev; + modem->dev = dev; + list_add(&modem->list, &mdev->client_list); + + mutex_unlock(&mdev->mutex); + return modem; + +} + +static struct modem *_modem_get(struct device *dev, const char *id, + int exclusive) +{ + struct modem_dev *mdev_ptr; + struct modem *modem = ERR_PTR(-ENODEV); + int ret; + + if (id == NULL) { + pr_err("modem_get with no identifier\n"); + return modem; + } + + mutex_lock(&modem_list_mutex); + list_for_each_entry(mdev_ptr, &modem_list, modem_list) { + if (strcmp(mdev_get_name(mdev_ptr), id) == 0) + goto found; + } + + goto out; + +found: + if (!try_module_get(mdev_ptr->owner)) + goto out; + + modem = create_modem(mdev_ptr, dev, id); + if (modem == NULL) { + modem = ERR_PTR(-ENOMEM); + module_put(mdev_ptr->owner); + } + + mdev_ptr->open_count++; + ret = _modem_is_requested(mdev_ptr); + if (ret) + mdev_ptr->use_count = 1; + else + mdev_ptr->use_count = 0; + +out: + mutex_unlock(&modem_list_mutex); + return modem; + +} + +/** + * modem_get - Get reference to a particular platform specific modem + * @dev: device + * @id: modem device name + * + * Get reference to a particular modem device. + */ +struct modem *modem_get(struct device *dev, const char *id) +{ + return _modem_get(dev, id, 0); +} +EXPORT_SYMBOL(modem_get); + +/** + * modem_put - Release reference to a modem device + * @modem: modem device + * + * Release reference to a modem device. + */ +void modem_put(struct modem *modem) +{ + struct modem_dev *mdev; + + if (modem == NULL || IS_ERR(modem)) + return; + + mutex_lock(&modem_list_mutex); + mdev = modem->mdev; + + list_del(&modem->list); + kfree(modem); + + mdev->open_count--; + + module_put(mdev->owner); + mutex_unlock(&modem_list_mutex); +} +EXPORT_SYMBOL(modem_put); + +static ssize_t modem_print_state(char *buf, int state) +{ + if (state > 0) + return sprintf(buf, "accessed\n"); + else if (state == 0) + return sprintf(buf, "released\n"); + else + return sprintf(buf, "unknown\n"); +} + +static ssize_t modem_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct modem_dev *mdev = dev_get_drvdata(dev); + ssize_t ret; + + mutex_lock(&mdev->mutex); + ret = modem_print_state(buf, _modem_is_requested(mdev)); + mutex_unlock(&mdev->mutex); + + return ret; +} +static DEVICE_ATTR(state, 0444, modem_state_show, NULL); + +static ssize_t modem_use_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct modem_dev *mdev = dev_get_drvdata(dev); + struct modem *mod; + size_t size = 0; + + list_for_each_entry(mod, &mdev->client_list, list) { + if (mod->dev != NULL) + size += sprintf((buf + size), "%s (%d)\n", + dev_name(mod->dev), atomic_read(&mod->use)); + else + size += sprintf((buf + size), "unknown (%d)\n", + atomic_read(&mod->use)); + } + size += sprintf((buf + size), "\n"); + + return size; +} +static DEVICE_ATTR(use, 0444, modem_use_show, NULL); + +static ssize_t modem_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct modem_dev *mdev = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", mdev_get_name(mdev)); +} +static DEVICE_ATTR(name, 0444, modem_name_show, NULL); + +static ssize_t modem_num_active_users_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct modem_dev *mdev = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", mdev->use_count); +} +static DEVICE_ATTR(num_active_users, 0444, modem_num_active_users_show, NULL); + +static int add_modem_attributes(struct modem_dev *mdev) +{ + struct device *dev = &mdev->dev; + struct modem_ops *ops = mdev->desc->ops; + int status = 0; + + status = device_create_file(dev, &dev_attr_use); + if (status < 0) + return status; + + status = device_create_file(dev, &dev_attr_name); + if (status < 0) + return status; + + status = device_create_file(dev, &dev_attr_num_active_users); + if (status < 0) + return status; + + if (ops->is_requested) { + status = device_create_file(dev, &dev_attr_state); + if (status < 0) + return status; + } + + return 0; +} + +/** + * modem_register - register a modem + * @modem_desc: - description for modem + * @dev: - device + * @driver_data:- driver specific data + * + * Register a modem with the modem access framework, so that + * it could be used by client drivers for accessing the + * modem. + */ +struct modem_dev *modem_register(struct modem_desc *modem_desc, + struct device *dev, + void *driver_data) +{ + static atomic_t modem_no = ATOMIC_INIT(0); + struct modem_dev *mdev; + int ret; + + if (modem_desc == NULL) + return ERR_PTR(-EINVAL); + + if (modem_desc->name == NULL || modem_desc->ops == NULL) + return ERR_PTR(-EINVAL); + + mdev = kzalloc(sizeof(struct modem_dev), GFP_KERNEL); + if (mdev == NULL) + return ERR_PTR(-ENOMEM); + + mutex_lock(&modem_list_mutex); + + mutex_init(&mdev->mutex); + mdev->modem_data = driver_data; + mdev->owner = modem_desc->owner; + mdev->desc = modem_desc; + INIT_LIST_HEAD(&mdev->client_list); + INIT_LIST_HEAD(&mdev->modem_list); + BLOCKING_INIT_NOTIFIER_HEAD(&mdev->notifier); + + /* mdev->dev.class = &modem_class;*/ + mdev->dev.parent = dev; + dev_set_name(&mdev->dev, "modem.%d", atomic_inc_return(&modem_no) - 1); + ret = device_register(&mdev->dev); + if (ret != 0) + goto clean; + + dev_set_drvdata(&mdev->dev, mdev); + + ret = add_modem_attributes(mdev); + if (ret < 0) + goto backoff; + + list_add(&mdev->modem_list, &modem_list); + +out: + mutex_unlock(&modem_list_mutex); + return mdev; + +backoff: + device_unregister(&mdev->dev); + mdev = ERR_PTR(ret); + goto out; + +clean: + kfree(mdev); + mdev = ERR_PTR(ret); + goto out; +} +EXPORT_SYMBOL(modem_register); diff --git a/drivers/modem/modem_m6718.c b/drivers/modem/modem_m6718.c new file mode 100644 index 00000000000..5e457c16003 --- /dev/null +++ b/drivers/modem/modem_m6718.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Chris Blair <chris.blair@stericsson.com> + * based on modem_u8500.c + * + * Platform driver implementing access mechanisms to the M6718 modem. + */ +#include <linux/modem/modem.h> +#include <linux/platform_device.h> +#include <linux/err.h> + +static void modem_m6718_request(struct modem_dev *mdev) +{ + /* nothing to do - modem will wake when data is sent */ +} + +static void modem_m6718_release(struct modem_dev *mdev) +{ + /* nothing to do - modem does not need to be requested/released */ +} + +static int modem_m6718_is_requested(struct modem_dev *mdev) +{ + return 0; +} + +static struct modem_ops modem_m6718_ops = { + .request = modem_m6718_request, + .release = modem_m6718_release, + .is_requested = modem_m6718_is_requested, +}; + +static struct modem_desc modem_m6718_desc = { + .name = "m6718", + .id = 0, + .ops = &modem_m6718_ops, + .owner = THIS_MODULE, +}; + +static int __devinit modem_m6718_probe(struct platform_device *pdev) +{ + struct modem_dev *mdev; + int err; + + mdev = modem_register(&modem_m6718_desc, &pdev->dev, + NULL); + if (IS_ERR(mdev)) { + err = PTR_ERR(mdev); + dev_err(&pdev->dev, "failed to register %s: err %i\n", + modem_m6718_desc.name, err); + } + + return 0; +} + +static int __devexit modem_m6718_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver modem_m6718_driver = { + .driver = { + .name = "modem-m6718", + .owner = THIS_MODULE, + }, + .probe = modem_m6718_probe, + .remove = __devexit_p(modem_m6718_remove), +}; + +static int __init modem_m6718_init(void) +{ + int ret; + + ret = platform_driver_register(&modem_m6718_driver); + if (ret < 0) { + printk(KERN_ERR "modem_m6718: platform driver reg failed\n"); + return ret; + } + + return 0; +} + +static void __exit modem_m6718_exit(void) +{ + platform_driver_unregister(&modem_m6718_driver); +} + +module_init(modem_m6718_init); +module_exit(modem_m6718_exit); + +MODULE_AUTHOR("Chris Blair <chris.blair@stericsson.com>"); +MODULE_DESCRIPTION("M6718 modem access driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/modem/modem_u8500.c b/drivers/modem/modem_u8500.c new file mode 100644 index 00000000000..39951995e8e --- /dev/null +++ b/drivers/modem/modem_u8500.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> + * + * Platform driver implementing access mechanisms to modem + * on U8500 which uses Shared Memroy as IPC between Application + * Processor and Modem processor. + */ +#include <linux/module.h> +#include <linux/modem/modem.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/mfd/dbx500-prcmu.h> + +static void u8500_modem_request(struct modem_dev *mdev) +{ + prcmu_ac_wake_req(); +} + +static void u8500_modem_release(struct modem_dev *mdev) +{ + prcmu_ac_sleep_req(); +} + +static int u8500_modem_is_requested(struct modem_dev *mdev) +{ + return prcmu_is_ac_wake_requested(); +} + +static struct modem_ops u8500_modem_ops = { + .request = u8500_modem_request, + .release = u8500_modem_release, + .is_requested = u8500_modem_is_requested, +}; + +static struct modem_desc u8500_modem_desc = { + .name = "u8500-shrm-modem", + .id = 0, + .ops = &u8500_modem_ops, + .owner = THIS_MODULE, +}; + + +static int __devinit u8500_modem_probe(struct platform_device *pdev) +{ + struct modem_dev *mdev; + int err; + + mdev = modem_register(&u8500_modem_desc, &pdev->dev, + NULL); + if (IS_ERR(mdev)) { + err = PTR_ERR(mdev); + pr_err("failed to register %s: err %i\n", + u8500_modem_desc.name, err); + } + + return 0; +} + +static int __devexit u8500_modem_remove(struct platform_device *pdev) +{ + + return 0; +} + +static struct platform_driver u8500_modem_driver = { + .driver = { + .name = "u8500-modem", + .owner = THIS_MODULE, + }, + .probe = u8500_modem_probe, + .remove = __devexit_p(u8500_modem_remove), +}; + +static int __init u8500_modem_init(void) +{ + int ret; + + ret = platform_driver_register(&u8500_modem_driver); + if (ret < 0) { + printk(KERN_ERR "u8500_modem: platform driver reg failed\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit u8500_modem_exit(void) +{ + platform_driver_unregister(&u8500_modem_driver); +} + +arch_initcall(u8500_modem_init); diff --git a/drivers/modem/shrm/Kconfig b/drivers/modem/shrm/Kconfig new file mode 100644 index 00000000000..465c8bb10a1 --- /dev/null +++ b/drivers/modem/shrm/Kconfig @@ -0,0 +1,43 @@ +# +# SHM HW kernel configuration +# +config U8500_SHRM + bool "U8500 SHRM hardware driver" + depends on ARCH_U8500 && PHONET && MODEM_U8500 + default Y + ---help--- + If you say Y here, you will enable the STN8500 SHM hardware driver. + + If unsure, say N. +choice + prompt "Modem Image Version" + depends on U8500_SHRM + default SHRM_V1_UPDATES_VERSION + + config SHRM_V1_UPDATES_VERSION + depends on U8500_SHRM + bool "SHRM V1 UPDATES" + help + Modem Images with V1 Updates + +endchoice + +config U8500_SHRM_LOOP_BACK + bool "U8500 SHRM loopback" + depends on U8500_SHRM + default n + ---help--- + If you say Y here, you will enable the shm loopback + + If unsure, say N. + +config U8500_SHRM_MODEM_SILENT_RESET + bool "U8500 SHRM Modem Silent Reset" + depends on U8500_SHRM + default n + ---help--- + If you say Y here, you will enable the modem silent reset feature + + If unsure, say N. + + diff --git a/drivers/modem/shrm/Makefile b/drivers/modem/shrm/Makefile new file mode 100644 index 00000000000..8115c24920b --- /dev/null +++ b/drivers/modem/shrm/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for SHRM drivers +# + +ifdef CONFIG_PHONET +u8500_shrm-objs := modem_shrm_driver.o shrm_fifo.o shrm_protocol.o +else +u8500_shrm-objs := shrm_driver.o shrm_fifo.o shrm_protocol.o +endif + +obj-$(CONFIG_U8500_SHRM) += u8500_shrm.o diff --git a/drivers/modem/shrm/modem_shrm_driver.c b/drivers/modem/shrm/modem_shrm_driver.c new file mode 100644 index 00000000000..f46b86bd22e --- /dev/null +++ b/drivers/modem/shrm/modem_shrm_driver.c @@ -0,0 +1,670 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson + * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> for ST-Ericsson + * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <asm/atomic.h> +#include <linux/io.h> +#include <linux/skbuff.h> +#ifdef CONFIG_HIGH_RES_TIMERS +#include <linux/hrtimer.h> +static struct hrtimer timer; +#endif +#include <linux/if_ether.h> +#include <linux/netdevice.h> +#include <linux/phonet.h> +#include <linux/modem/shrm/shrm_driver.h> +#include <linux/modem/shrm/shrm_private.h> +#include <linux/modem/shrm/shrm_config.h> +#include <linux/modem/shrm/shrm_net.h> +#include <linux/modem/shrm/shrm.h> + +#include <mach/isa_ioctl.h> +/* debug functionality */ +#define ISA_DEBUG 0 + +#define PHONET_TASKLET +#define MAX_RCV_LEN 2048 + +static void do_phonet_rcv_tasklet(unsigned long unused); +struct tasklet_struct phonet_rcv_tasklet; + +/** + * audio_receive() - Receive audio channel completion callback + * @shrm: pointer to shrm device information structure + * @data: message pointer + * @n_bytes: message size + * @l2_header: L2 header/device ID 2->audio, 5->audio_loopback + * + * This fucntion is called from the audio receive handler. Copies the audio + * message from the FIFO to the AUDIO queue. The message is later copied from + * this queue to the user buffer through the char or net interface read + * operation. + */ +static int audio_receive(struct shrm_dev *shrm, void *data, + u32 n_bytes, u8 l2_header) +{ + u32 size = 0; + int ret = 0; + int idx; + u8 *psrc; + struct message_queue *q; + struct isadev_context *audiodev; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + idx = shrm_get_cdev_index(l2_header); + if (idx < 0) { + dev_err(shrm->dev, "failed to get index\n"); + return idx; + } + audiodev = &shrm->isa_context->isadev[idx]; + q = &audiodev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + ret = add_msg_to_queue(q, n_bytes); + spin_unlock(&q->update_lock); + if (ret < 0) + dev_err(shrm->dev, "Adding a msg to message queue failed"); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * common_receive() - Receive common channel completion callback + * @shrm: pointer to the shrm device information structure + * @data: message pointer + * @n_bytes: message size + * @l2_header: L2 header / device ID + * + * This function is called from the receive handler to copy the respective + * ISI, RPC, SECURITY message to its respective queue. The message is then + * copied from queue to the user buffer on char net interface read operation. + */ +static int common_receive(struct shrm_dev *shrm, void *data, + u32 n_bytes, u8 l2_header) +{ + u32 size = 0; + int ret = 0; + int idx; + u8 *psrc; + struct message_queue *q; + struct isadev_context *isa_dev; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + idx = shrm_get_cdev_index(l2_header); + if (idx < 0) { + dev_err(shrm->dev, "failed to get index\n"); + return idx; + } + isa_dev = &shrm->isa_context->isadev[idx]; + q = &isa_dev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + dev_dbg(shrm->dev, "Inside Loop Back\n"); + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + ret = add_msg_to_queue(q, n_bytes); + spin_unlock(&q->update_lock); + if (ret < 0) { + dev_err(shrm->dev, "Adding a msg to message queue failed"); + return ret; + } + + + if (l2_header == ISI_MESSAGING) { + if (shrm->netdev_flag_up) { + dev_dbg(shrm->dev, + "scheduling the phonet tasklet from %s!\n", + __func__); + tasklet_schedule(&phonet_rcv_tasklet); + } + dev_dbg(shrm->dev, + "Out of phonet tasklet %s!!!\n", __func__); + } + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * rx_common_l2msg_handler() - common channel receive handler + * @l2_header: L2 header + * @msg: pointer to the receive buffer + * @length: length of the msg to read + * @shrm: pointer to shrm device information structure + * + * This function is called to receive the message from CaMsgPendingNotification + * interrupt handler. + */ +static void rx_common_l2msg_handler(u8 l2_header, + void *msg, u32 length, + struct shrm_dev *shrm) +{ + int ret = 0; + dev_dbg(shrm->dev, "%s IN\n", __func__); + + ret = common_receive(shrm, msg, length, l2_header); + if (ret < 0) + dev_err(shrm->dev, + "common receive with l2 header %d failed\n", l2_header); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +/** + * rx_audio_l2msg_handler() - audio channel receive handler + * @l2_header: L2 header + * @msg: pointer to the receive buffer + * @length: length of the msg to read + * @shrm: pointer to shrm device information structure + * + * This function is called to receive the message from CaMsgPendingNotification + * interrupt handler. + */ +static void rx_audio_l2msg_handler(u8 l2_header, + void *msg, u32 length, + struct shrm_dev *shrm) +{ + int ret = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + ret = audio_receive(shrm, msg, length, l2_header); + if (ret < 0) + dev_err(shrm->dev, "audio receive failed\n"); + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +static int __init shm_initialise_irq(struct shrm_dev *shrm) +{ + int err = 0; + + err = shrm_protocol_init(shrm, + rx_common_l2msg_handler, rx_audio_l2msg_handler); + if (err < 0) { + dev_err(shrm->dev, "SHM Protocol Init Failure\n"); + return err; + } + + err = request_irq(shrm->ca_wake_irq, + ca_wake_irq_handler, IRQF_TRIGGER_RISING, + "ca_wake-up", shrm); + if (err < 0) { + dev_err(shrm->dev, + "Unable to allocate shm tx interrupt line\n"); + free_irq(shrm->ca_wake_irq, shrm); + return err; + } + + err = request_irq(shrm->ac_read_notif_0_irq, + ac_read_notif_0_irq_handler, 0, + "ac_read_notif_0", shrm); + + if (err < 0) { + dev_err(shrm->dev, + "error ac_read_notif_0_irq interrupt line\n"); + goto irq_err1; + } + + err = request_irq(shrm->ac_read_notif_1_irq, + ac_read_notif_1_irq_handler, 0, + "ac_read_notif_1", shrm); + + if (err < 0) { + dev_err(shrm->dev, + "error ac_read_notif_1_irq interrupt line\n"); + goto irq_err2; + } + + err = request_irq(shrm->ca_msg_pending_notif_0_irq, + ca_msg_pending_notif_0_irq_handler, 0, + "ca_msg_pending_notif_0", shrm); + + if (err < 0) { + dev_err(shrm->dev, + "error ca_msg_pending_notif_0_irq line\n"); + goto irq_err3; + } + + err = request_irq(shrm->ca_msg_pending_notif_1_irq, + ca_msg_pending_notif_1_irq_handler, 0, + "ca_msg_pending_notif_1", shrm); + + if (err < 0) { + dev_err(shrm->dev, + "error ca_msg_pending_notif_1_irq interrupt line\n"); + goto irq_err4; + } + return err; +irq_err4: + free_irq(shrm->ca_msg_pending_notif_0_irq, shrm); +irq_err3: + free_irq(shrm->ac_read_notif_1_irq, shrm); +irq_err2: + free_irq(shrm->ac_read_notif_0_irq, shrm); +irq_err1: + free_irq(shrm->ca_wake_irq, shrm); + return err; +} + +static void free_shm_irq(struct shrm_dev *shrm) +{ + free_irq(shrm->ca_wake_irq, shrm); + free_irq(shrm->ac_read_notif_0_irq, shrm); + free_irq(shrm->ac_read_notif_1_irq, shrm); + free_irq(shrm->ca_msg_pending_notif_0_irq, shrm); + free_irq(shrm->ca_msg_pending_notif_1_irq, shrm); +} + + + +#ifdef CONFIG_HIGH_RES_TIMERS +static enum hrtimer_restart callback(struct hrtimer *timer) +{ + return HRTIMER_NORESTART; +} +#endif + +void do_phonet_rcv_tasklet(unsigned long unused) +{ + ssize_t ret; + struct shrm_dev *shrm = (struct shrm_dev *)unused; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + for (;;) { + ret = shrm_net_receive(shrm->ndev); + if (ret == 0) { + dev_dbg(shrm->dev, "len is zero, queue empty\n"); + break; + } + if (ret < 0) { + dev_err(shrm->dev, "len < 0 !!! error!!!\n"); + break; + } + } + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +static int shrm_probe(struct platform_device *pdev) +{ + int err = 0; + struct resource *res; + struct shrm_dev *shrm = NULL; + + shrm = kzalloc(sizeof(struct shrm_dev), GFP_KERNEL); + if (shrm == NULL) { + dev_err(&pdev->dev, + "Could not allocate memory for struct shm_dev\n"); + return -ENOMEM; + } + + shrm->dev = &pdev->dev; + shrm->modem = modem_get(shrm->dev, "u8500-shrm-modem"); + if (shrm->modem == NULL) { + dev_err(shrm->dev, " Could not retrieve the modem.\n"); + err = -ENODEV; + goto rollback_intr; + } + + /* initialise the SHM */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(shrm->dev, + "Unable to map Ca Wake up interrupt\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_wake_irq = res->start; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + + if (!res) { + dev_err(shrm->dev, + "Unable to map APE_Read_notif_common IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ac_read_notif_0_irq = res->start; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 2); + + if (!res) { + dev_err(shrm->dev, + "Unable to map APE_Read_notif_audio IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ac_read_notif_1_irq = res->start; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 3); + + if (!res) { + dev_err(shrm->dev, + "Unable to map Cmt_msg_pending_notif_common IRQbase\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_msg_pending_notif_0_irq = res->start; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 4); + + if (!res) { + dev_err(shrm->dev, + "Unable to map Cmt_msg_pending_notif_audio IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_msg_pending_notif_1_irq = res->start; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) { + dev_err(shrm->dev, + "Could not get SHM IO memory information\n"); + err = -ENODEV; + goto rollback_intr; + } + shrm->intr_base = (void __iomem *)ioremap_nocache(res->start, + res->end - res->start + 1); + if (!(shrm->intr_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ape_common_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_APE_COMMON_BASE; + shrm->ape_common_fifo_base = + (void __iomem *)ioremap_nocache( + U8500_SHM_FIFO_APE_COMMON_BASE, + SHM_FIFO_0_SIZE); + shrm->ape_common_fifo_size = (SHM_FIFO_0_SIZE)/4; + + if (!(shrm->ape_common_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ape_common_fifo_base; + } + shrm->cmt_common_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_CMT_COMMON_BASE; + shrm->cmt_common_fifo_base = + (void __iomem *)ioremap_nocache( + U8500_SHM_FIFO_CMT_COMMON_BASE, SHM_FIFO_0_SIZE); + shrm->cmt_common_fifo_size = (SHM_FIFO_0_SIZE)/4; + + if (!(shrm->cmt_common_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_cmt_common_fifo_base; + } + shrm->ape_audio_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_APE_AUDIO_BASE; + shrm->ape_audio_fifo_base = + (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_APE_AUDIO_BASE, + SHM_FIFO_1_SIZE); + shrm->ape_audio_fifo_size = (SHM_FIFO_1_SIZE)/4; + + if (!(shrm->ape_audio_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ape_audio_fifo_base; + } + shrm->cmt_audio_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_CMT_AUDIO_BASE; + shrm->cmt_audio_fifo_base = + (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_CMT_AUDIO_BASE, + SHM_FIFO_1_SIZE); + shrm->cmt_audio_fifo_size = (SHM_FIFO_1_SIZE)/4; + + if (!(shrm->cmt_audio_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_cmt_audio_fifo_base; + } + shrm->ac_common_shared_wptr = + (void __iomem *)ioremap(SHM_ACFIFO_0_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_common_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ac_common_shared_wptr; + } + shrm->ac_common_shared_rptr = + (void __iomem *)ioremap(SHM_ACFIFO_0_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_common_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ca_common_shared_wptr = + (void __iomem *)ioremap(SHM_CAFIFO_0_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_common_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ca_common_shared_rptr = + (void __iomem *)ioremap(SHM_CAFIFO_0_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_common_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ac_audio_shared_wptr = + (void __iomem *)ioremap(SHM_ACFIFO_1_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_audio_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ac_audio_shared_rptr = + (void __iomem *)ioremap(SHM_ACFIFO_1_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_audio_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ca_audio_shared_wptr = + (void __iomem *)ioremap(SHM_CAFIFO_1_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_audio_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ca_audio_shared_rptr = + (void __iomem *)ioremap(SHM_CAFIFO_1_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_audio_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + if (isa_init(shrm) != 0) { + dev_err(shrm->dev, "Driver Initialization Error\n"); + err = -EBUSY; + } + /* install handlers and tasklets */ + if (shm_initialise_irq(shrm)) { + dev_err(shrm->dev, + "shm error in interrupt registration\n"); + goto rollback_irq; + } +#ifdef CONFIG_HIGH_RES_TIMERS + hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + timer.function = callback; + hrtimer_start(&timer, ktime_set(0, 2*NSEC_PER_MSEC), HRTIMER_MODE_REL); +#endif + err = shrm_register_netdev(shrm); + if (err < 0) + goto rollback_irq; + + tasklet_init(&phonet_rcv_tasklet, do_phonet_rcv_tasklet, 0); + phonet_rcv_tasklet.data = (unsigned long)shrm; + + platform_set_drvdata(pdev, shrm); + + return err; +rollback_irq: + free_shm_irq(shrm); +rollback_map: + iounmap(shrm->ac_common_shared_wptr); + iounmap(shrm->ac_common_shared_rptr); + iounmap(shrm->ca_common_shared_wptr); + iounmap(shrm->ca_common_shared_rptr); + iounmap(shrm->ac_audio_shared_wptr); + iounmap(shrm->ac_audio_shared_rptr); + iounmap(shrm->ca_audio_shared_wptr); + iounmap(shrm->ca_audio_shared_rptr); +rollback_ac_common_shared_wptr: + iounmap(shrm->cmt_audio_fifo_base); +rollback_cmt_audio_fifo_base: + iounmap(shrm->ape_audio_fifo_base); +rollback_ape_audio_fifo_base: + iounmap(shrm->cmt_common_fifo_base); +rollback_cmt_common_fifo_base: + iounmap(shrm->ape_common_fifo_base); +rollback_ape_common_fifo_base: + iounmap(shrm->intr_base); +rollback_intr: + kfree(shrm); + return err; +} + +static int __exit shrm_remove(struct platform_device *pdev) +{ + struct shrm_dev *shrm = platform_get_drvdata(pdev); + + free_shm_irq(shrm); + iounmap(shrm->intr_base); + iounmap(shrm->ape_common_fifo_base); + iounmap(shrm->cmt_common_fifo_base); + iounmap(shrm->ape_audio_fifo_base); + iounmap(shrm->cmt_audio_fifo_base); + iounmap(shrm->ac_common_shared_wptr); + iounmap(shrm->ac_common_shared_rptr); + iounmap(shrm->ca_common_shared_wptr); + iounmap(shrm->ca_common_shared_rptr); + iounmap(shrm->ac_audio_shared_wptr); + iounmap(shrm->ac_audio_shared_rptr); + iounmap(shrm->ca_audio_shared_wptr); + iounmap(shrm->ca_audio_shared_rptr); + shrm_unregister_netdev(shrm); + isa_exit(shrm); + kfree(shrm); + + return 0; +} + +#ifdef CONFIG_PM +/** + * u8500_shrm_suspend() - This routine puts the SHRM in to sustend state. + * @dev: pointer to device structure. + * + * This routine checks the current ongoing communication with Modem by + * examining the ca_wake state and prevents suspend if modem communication + * is on-going. + * If ca_wake = 1 (high), modem comm. is on-going; don't suspend + * If ca_wake = 0 (low), no comm. with modem on-going.Allow suspend + */ +int u8500_shrm_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct shrm_dev *shrm = platform_get_drvdata(pdev); + int err; + + dev_dbg(&pdev->dev, "%s called...\n", __func__); + dev_dbg(&pdev->dev, "ca_wake_req_state = %x\n", + get_ca_wake_req_state()); + + /* if ca_wake_req is high, prevent system suspend */ + if (!get_ca_wake_req_state()) { + err = shrm_suspend_netdev(shrm->ndev); + return err; + } else + return -EBUSY; +} + +/** + * u8500_shrm_resume() - This routine resumes the SHRM from suspend state. + * @dev: pointer to device structure + * + * This routine restore back the current state of the SHRM + */ +int u8500_shrm_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct shrm_dev *shrm = platform_get_drvdata(pdev); + int err; + + dev_dbg(&pdev->dev, "%s called...\n", __func__); + err = shrm_resume_netdev(shrm->ndev); + + return err; +} + +static const struct dev_pm_ops shrm_dev_pm_ops = { + .suspend_noirq = u8500_shrm_suspend, + .resume_noirq = u8500_shrm_resume, +}; +#endif + +static struct platform_driver shrm_driver = { + .remove = __exit_p(shrm_remove), + .driver = { + .name = "u8500_shrm", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &shrm_dev_pm_ops, +#endif + }, +}; + +static int __init shrm_driver_init(void) +{ + return platform_driver_probe(&shrm_driver, shrm_probe); +} + +static void __exit shrm_driver_exit(void) +{ + platform_driver_unregister(&shrm_driver); +} + +module_init(shrm_driver_init); +module_exit(shrm_driver_exit); + +MODULE_AUTHOR("Biju Das, Kumar Sanghvi, Arun Murthy"); +MODULE_DESCRIPTION("Shared Memory Modem Driver Interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/modem/shrm/shrm_driver.c b/drivers/modem/shrm/shrm_driver.c new file mode 100644 index 00000000000..11540831f95 --- /dev/null +++ b/drivers/modem/shrm/shrm_driver.c @@ -0,0 +1,1439 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson + * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> for ST-Ericsson + * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#define DEBUG + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/smp_lock.h> +#include <linux/poll.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <asm/atomic.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/modem/shrm/shrm_driver.h> +#include <linux/modem/shrm/shrm_private.h> +#include <linux/modem/shrm/shrm_config.h> +#include <linux/modem/shrm/shrm.h> + +#include <mach/isa_ioctl.h> + + +#ifdef CONFIG_HIGH_RES_TIMERS +#include <linux/hrtimer.h> +static struct hrtimer timer; +#endif + + +#define NAME "IPC_ISA" +#define ISA_DEVICES 4 +/**debug functionality*/ +#define ISA_DEBUG 0 + +#define ISI_MESSAGING (0) +#define RPC_MESSAGING (1) +#define AUDIO_MESSAGING (2) +#define SECURITY_MESSAGING (3) + +#define SIZE_OF_FIFO (512*1024) + +static u8 message_fifo[4][SIZE_OF_FIFO]; + +static u8 wr_isi_msg[10*1024]; +static u8 wr_rpc_msg[10*1024]; +static u8 wr_sec_msg[10*1024]; +static u8 wr_audio_msg[10*1024]; + +/* global data */ +/* + * int major:This variable is exported to user as module_param to specify + * major number at load time + */ +static int major; +module_param(major, int, 0); +MODULE_PARM_DESC(major, "Major device number"); +/* global fops mutex */ +static DEFINE_MUTEX(isa_lock); +rx_cb common_rx; +rx_cb audio_rx; + + +static int isi_receive(struct shrm_dev *shrm, void *data, u32 n_bytes); +static int rpc_receive(struct shrm_dev *shrm, void *data, u32 n_bytes); +static int audio_receive(struct shrm_dev *shrm, void *data, u32 n_bytes); +static int security_receive(struct shrm_dev *shrm, + void *data, u32 n_bytes); + +static void rx_common_l2msg_handler(u8 l2_header, + void *msg, u32 length, + struct shrm_dev *shrm) +{ + int ret = 0; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + u8 *pdata; +#endif + dev_dbg(shrm->dev, "%s IN\n", __func__); + + switch (l2_header) { + case ISI_MESSAGING: + ret = isi_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, "isi receive failed\n"); + break; + case RPC_MESSAGING: + ret = rpc_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, "rpc receive failed\n"); + break; + case SECURITY_MESSAGING: + ret = security_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, + "security receive failed\n"); + break; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + case COMMMON_LOOPBACK_MESSAGING: + pdata = (u8 *)msg; + if ((*pdata == 0x50) || (*pdata == 0xAF)) { + ret = isi_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, "isi receive failed\n"); + } else if ((*pdata == 0x0A) || (*pdata == 0xF5)) { + ret = rpc_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, "rpc receive failed\n"); + } else if ((*pdata == 0xFF) || (*pdata == 0x00)) { + ret = security_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, + "security receive failed\n"); + } + break; +#endif + default: + break; + } + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +static void rx_audio_l2msg_handler(u8 l2_header, + void *msg, u32 length, + struct shrm_dev *shrm) +{ + int ret = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + audio_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, "audio receive failed\n"); + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +static int __init shm_initialise_irq(struct shrm_dev *shrm) +{ + int err = 0; + + shrm_protocol_init(shrm, + rx_common_l2msg_handler, rx_audio_l2msg_handler); + + err = request_irq(shrm->ca_wake_irq, + ca_wake_irq_handler, IRQF_TRIGGER_RISING, + "ca_wake-up", shrm); + if (err < 0) { + dev_err(shrm->dev, + "Unable to allocate shm tx interrupt line\n"); + return err; + } + + err = request_irq(shrm->ac_read_notif_0_irq, + ac_read_notif_0_irq_handler, 0, + "ac_read_notif_0", shrm); + if (err < 0) { + dev_err(shrm->dev, + "error ac_read_notif_0_irq interrupt line\n"); + goto irq_err1; + } + + err = request_irq(shrm->ac_read_notif_1_irq, + ac_read_notif_1_irq_handler, 0, + "ac_read_notif_1", shrm); + if (err < 0) { + dev_err(shrm->dev, + "error ac_read_notif_1_irq interrupt line\n"); + goto irq_err2; + } + + err = request_irq(shrm->ca_msg_pending_notif_0_irq, + ca_msg_pending_notif_0_irq_handler, 0, + "ca_msg_pending_notif_0", shrm); + if (err < 0) { + dev_err(shrm->dev, + "error ca_msg_pending_notif_0_irq line\n"); + goto irq_err3; + } + + err = request_irq(shrm->ca_msg_pending_notif_1_irq, + ca_msg_pending_notif_1_irq_handler, 0, + "ca_msg_pending_notif_1", shrm); + if (err < 0) { + dev_err(shrm->dev, + "error ca_msg_pending_notif_1_irq interrupt line\n"); + goto irq_err4; + } + + return err; + +irq_err4: + free_irq(shrm->ca_msg_pending_notif_0_irq, shrm); +irq_err3: + free_irq(shrm->ac_read_notif_1_irq, shrm); +irq_err2: + free_irq(shrm->ac_read_notif_0_irq, shrm); +irq_err1: + free_irq(shrm->ca_wake_irq, shrm); + return err; +} + +static void free_shm_irq(struct shrm_dev *shrm) +{ + free_irq(shrm->ca_wake_irq, shrm); + free_irq(shrm->ac_read_notif_0_irq, shrm); + free_irq(shrm->ac_read_notif_1_irq, shrm); + free_irq(shrm->ca_msg_pending_notif_0_irq, shrm); + free_irq(shrm->ca_msg_pending_notif_1_irq, shrm); +} + +/** + * create_queue() - To create FIFO for Tx and Rx message buffering. + * @q: message queue. + * @devicetype: device type 0-isi,1-rpc,2-audio,3-security. + * + * This function creates a FIFO buffer of n_bytes size using + * dma_alloc_coherent(). It also initializes all queue handling + * locks, queue management pointers. It also initializes message list + * which occupies this queue. + * + * It return -ENOMEM in case of no memory. + */ +static int create_queue(struct message_queue *q, u32 devicetype, + struct shrm_dev *shrm) +{ + q->fifo_base = (u8 *)&message_fifo[devicetype]; + q->size = SIZE_OF_FIFO; + q->readptr = 0; + q->writeptr = 0; + q->no = 0; + q->shrm = shrm; + spin_lock_init(&q->update_lock); + INIT_LIST_HEAD(&q->msg_list); + init_waitqueue_head(&q->wq_readable); + atomic_set(&q->q_rp, 0); + + return 0; +} +/** + * delete_queue() - To delete FIFO and assiciated memory. + * @q: message queue + * + * This function deletes FIFO created using create_queue() function. + * It resets queue management pointers. + */ +static void delete_queue(struct message_queue *q) +{ + q->size = 0; + q->readptr = 0; + q->writeptr = 0; +} + +/** + * add_msg_to_queue() - Add a message inside inside queue + * + * @q: message queue + * @size: size in bytes + * + * This function tries to allocate n_bytes of size in FIFO q. + * It returns negative number when no memory can be allocated + * currently. + */ +int add_msg_to_queue(struct message_queue *q, u32 size) +{ + struct queue_element *new_msg = NULL; + struct shrm_dev *shrm = q->shrm; + + dev_dbg(shrm->dev, "%s IN q->writeptr=%d\n", + __func__, q->writeptr); + new_msg = kmalloc(sizeof(struct queue_element), + GFP_KERNEL|GFP_ATOMIC); + + if (new_msg == NULL) { + dev_err(shrm->dev, "memory overflow inside while(1)\n"); + return -ENOMEM; + } + new_msg->offset = q->writeptr; + new_msg->size = size; + new_msg->no = q->no++; + + /* check for overflow condition */ + if (q->readptr <= q->writeptr) { + if (((q->writeptr-q->readptr) + size) >= q->size) { + dev_err(shrm->dev, "Buffer overflow !!\n"); + BUG_ON(((q->writeptr-q->readptr) + size) >= q->size); + } + } else { + if ((q->writeptr + size) >= q->readptr) { + dev_err(shrm->dev, "Buffer overflow !!\n"); + BUG_ON((q->writeptr + size) >= q->readptr); + } + } + q->writeptr = (q->writeptr + size) % q->size; + if (list_empty(&q->msg_list)) { + list_add_tail(&new_msg->entry, &q->msg_list); + /* There can be 2 blocking calls read and another select */ + + atomic_set(&q->q_rp, 1); + wake_up_interruptible(&q->wq_readable); + } else + list_add_tail(&new_msg->entry, &q->msg_list); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return 0; +} + +/** + * remove_msg_from_queue() - To remove a message from the msg queue. + * + * @q: message queue + * + * This function delets a message from the message list associated with message + * queue q and also updates read ptr. + * If the message list is empty, then, event is set to block the select and + * read calls of the paricular queue. + * + * The message list is FIFO style and message is always added to tail and + * removed from head. + */ + +int remove_msg_from_queue(struct message_queue *q) +{ + struct queue_element *old_msg = NULL; + struct shrm_dev *shrm = q->shrm; + struct list_head *msg; + + dev_dbg(shrm->dev, "%s IN q->readptr %d\n", + __func__, q->readptr); + + list_for_each(msg, &q->msg_list) { + old_msg = list_entry(msg, struct queue_element, entry); + if (old_msg == NULL) { + dev_err(shrm->dev, ":no message found\n"); + return -EFAULT; + } + break; + } + list_del(msg); + q->readptr = (q->readptr + old_msg->size) % q->size; + if (list_empty(&q->msg_list)) { + dev_dbg(shrm->dev, "List is empty setting RP= 0\n"); + atomic_set(&q->q_rp, 0); + } + kfree(old_msg); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return 0; +} + +/** + * get_size_of_new_msg() - retrieve new message from message list + * + * @q: message queue + * + * This function will retrieve most recent message from the corresponding + * queue list. New message is always retrieved from head side. + * It returns new message no, offset if FIFO and size. + */ +int get_size_of_new_msg(struct message_queue *q) +{ + struct queue_element *new_msg = NULL; + struct list_head *msg_list; + struct shrm_dev *shrm = q->shrm; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + spin_lock_bh(&q->update_lock); + list_for_each(msg_list, &q->msg_list) { + new_msg = list_entry(msg_list, struct queue_element, entry); + if (new_msg == NULL) { + spin_unlock_bh(&q->update_lock); + dev_err(shrm->dev, "no message found\n"); + return -1; + } + break; + } + spin_unlock_bh(&q->update_lock); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return new_msg->size; +} + +/** + * isi_receive() - Rx Completion callback + * + * @data:message pointer + * @n_bytes:message size + * + * This function is a callback to indicate ISI message reception is complete. + * It updates Writeptr of the Fifo + */ +static int isi_receive(struct shrm_dev *shrm, + void *data, u32 n_bytes) +{ + u32 size = 0; + int ret = 0; + u8 *psrc; + struct message_queue *q; + struct isadev_context *isidev = &shrm->isa_context->isadev[0]; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + q = &isidev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + dev_dbg(shrm->dev, "Inside Loop Back\n"); + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + ret = add_msg_to_queue(q, n_bytes); + if (ret < 0) + dev_err(shrm->dev, "Adding msg to message queue failed\n"); + spin_unlock(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * rpc_receive() - Rx Completion callback + * + * @data:message pointer + * @n_bytes:message size + * + * This function is a callback to indicate RPC message reception is complete. + * It updates Writeptr of the Fifo + */ +static int rpc_receive(struct shrm_dev *shrm, + void *data, u32 n_bytes) +{ + u32 size = 0; + int ret = 0; + u8 *psrc; + struct message_queue *q; + struct isadev_context *rpcdev = &shrm->isa_context->isadev[1]; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + q = &rpcdev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + + ret = add_msg_to_queue(q, n_bytes); + if (ret < 0) + dev_err(shrm->dev, "Adding msg to message queue failed\n"); + spin_unlock(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * audio_receive() - Rx Completion callback + * + * @data:message pointer + * @n_bytes:message size + * + * This function is a callback to indicate audio message reception is complete. + * It updates Writeptr of the Fifo + */ +static int audio_receive(struct shrm_dev *shrm, + void *data, u32 n_bytes) +{ + u32 size = 0; + int ret = 0; + u8 *psrc; + struct message_queue *q; + struct isadev_context *audiodev = &shrm->isa_context->isadev[2]; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + q = &audiodev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + ret = add_msg_to_queue(q, n_bytes); + if (ret < 0) + dev_err(shrm->dev, "Adding msg to message queue failed\n"); + spin_unlock(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * security_receive() - Rx Completion callback + * + * @data:message pointer + * @n_bytes: message size + * + * This function is a callback to indicate security message reception + * is complete.It updates Writeptr of the Fifo + */ +static int security_receive(struct shrm_dev *shrm, + void *data, u32 n_bytes) +{ + u32 size = 0; + int ret = 0; + u8 *psrc; + struct message_queue *q; + struct isadev_context *secdev = &shrm->isa_context->isadev[3]; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + q = &secdev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + ret = add_msg_to_queue(q, n_bytes); + if (ret < 0) + dev_err(shrm->dev, "Adding msg to message queue failed\n"); + spin_unlock(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + + +/** + * isa_select() - Select Interface + * + * @filp:file descriptor pointer + * @wait:poll_table_struct pointer + * + * This function is used to perform non-blocking read operations. It allows + * a process to determine whether it can read from one or more open files + * without blocking. These calls can also block a process until any of a + * given set of file descriptors becomes available for reading. + * If a file is ready to read, POLLIN | POLLRDNORM bitmask is returned. + * The driver method is called whenever the user-space program performs a select + * system call involving a file descriptor associated with the driver. + */ +static u32 isa_select(struct file *filp, + struct poll_table_struct *wait) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + u32 mask = 0; + u32 m = iminor(filp->f_path.dentry->d_inode); + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (isadev->device_id != m) + return -1; + q = &isadev->dl_queue; + poll_wait(filp, &q->wq_readable, wait); + if (atomic_read(&q->q_rp) == 1) + mask = POLLIN | POLLRDNORM; + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return mask; +} + +/** + * isa_read() - Read from device + * + * @filp:file descriptor + * @buf:user buffer pointer + * @len:size of requested data transfer + * @ppos:not used + * + * This function is called whenever user calls read() system call. + * It reads a oldest message from queue and copies it into user buffer and + * returns its size. + * If there is no message present in queue, then it blocks until new data is + * available. + */ +ssize_t isa_read(struct file *filp, char __user *buf, + size_t len, loff_t *ppos) +{ + struct isadev_context *isadev = (struct isadev_context *) + filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + char *psrc; + u32 msgsize; + u32 size = 0; + int ret = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (len <= 0) + return -EFAULT; + q = &isadev->dl_queue; + + spin_lock_bh(&q->update_lock); + if (list_empty(&q->msg_list)) { + spin_unlock_bh(&q->update_lock); + if (wait_event_interruptible(q->wq_readable, + atomic_read(&q->q_rp) == 1)) { + return -ERESTARTSYS; + } + } else + spin_unlock_bh(&q->update_lock); + + msgsize = get_size_of_new_msg(q); + if ((q->readptr+msgsize) >= q->size) { + dev_dbg(shrm->dev, "Inside Loop Back\n"); + psrc = (char *)buf; + size = (q->size-q->readptr); + /* Copy First Part of msg */ + if (copy_to_user(psrc, + (u8 *)(q->fifo_base+q->readptr), + size)) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + if (copy_to_user(psrc, + (u8 *)(q->fifo_base), + (msgsize-size))) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + } else { + if (copy_to_user(buf, + (u8 *)(q->fifo_base+q->readptr), + msgsize)) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + } + + spin_lock_bh(&q->update_lock); + ret = remove_msg_from_queue(q); + if (ret < 0) { + dev_err(shrm->dev, + "Removing msg from message queue failed\n"); + msgsize = ret; + } + spin_unlock_bh(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return msgsize; +} +/** + * isa_write() - Write to device + * + * @filp:file descriptor + * @buf:user buffer pointer + * @len:size of requested data transfer + * @ppos:not used + * + * This function is called whenever user calls write() system call. + * It checks if there is space available in queue, and copies the message + * inside queue. If there is no space, it blocks until space becomes available. + * It also schedules transfer thread to transmit the newly added message. + */ +static ssize_t isa_write(struct file *filp, const char __user *buf, + size_t len, loff_t *ppos) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + int err, ret; + void *addr = 0; + u8 l2_header = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + if (len <= 0) + return -EFAULT; + q = &isadev->dl_queue; + + switch (isadev->device_id) { + case ISI_MESSAGING: + dev_dbg(shrm->dev, "ISI\n"); + addr = (void *)wr_isi_msg; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + dev_dbg(shrm->dev, "Loopback\n"); + l2_header = COMMON_LOOPBACK_MESSAGING; +#else + l2_header = isadev->device_id; +#endif + break; + case RPC_MESSAGING: + dev_dbg(shrm->dev, "RPC\n"); + addr = (void *)wr_rpc_msg; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + l2_header = COMMON_LOOPBACK_MESSAGING; +#else + l2_header = isadev->device_id; +#endif + break; + case AUDIO_MESSAGING: + dev_dbg(shrm->dev, "Audio\n"); + addr = (void *)wr_audio_msg; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + l2_header = AUDIO_LOOPBACK_MESSAGING; +#else + l2_header = isadev->device_id; +#endif + + break; + case SECURITY_MESSAGING: + dev_dbg(shrm->dev, "Security\n"); + addr = (void *)wr_sec_msg; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + l2_header = COMMON_LOOPBACK_MESSAGING; +#else + l2_header = isadev->device_id; +#endif + break; + default: + dev_dbg(shrm->dev, "Wrong device\n"); + return -EFAULT; + } + + if (copy_from_user(addr, buf, len)) { + dev_err(shrm->dev, "copy_from_user failed\n"); + return -EFAULT; + } + + /* Write msg to Fifo */ + if (isadev->device_id == 2) { + mutex_lock(&shrm->isa_context->tx_audio_mutex); + err = shm_write_msg(shrm, l2_header, addr, len); + if (!err) + ret = len; + else + ret = err; + mutex_unlock(&shrm->isa_context->tx_audio_mutex); + } else { + spin_lock_bh(&shrm->isa_context->common_tx); + err = shm_write_msg(shrm, l2_header, addr, len); + if (!err) + ret = len; + else + ret = err; + spin_unlock_bh(&shrm->isa_context->common_tx); + } + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * isa_ioctl() - To handle different ioctl commands supported by driver. + * + * @inode: structure is used by the kernel internally to represent files + * @filp:file descriptor pointer + * @cmd:ioctl command + * @arg:input param + * + * Following ioctls are supported by this driver. + * DLP_IOCTL_ALLOCATE_BUFFER - To allocate buffer for new uplink message. + * This ioctl is called with required message size. It returns offset for + * the allocates space in the queue. DLP_IOCTL_PUT_MESSAGE - To indicate + * new uplink message available in queuq for transmission. Message is copied + * from offset location returned by previous ioctl before calling this ioctl. + * DLP_IOCTL_GET_MESSAGE - To check if any downlink message is available in + * queue. It returns offset for new message inside queue. + * DLP_IOCTL_DEALLOCATE_BUFFER - To deallocate any buffer allocate for + * downlink message once the message is copied. Message is copied from offset + * location returned by previous ioctl before calling this ioctl. + */ +static int isa_ioctl(struct inode *inode, struct file *filp, + unsigned cmd, unsigned long arg) +{ + int err = 0; + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + u32 m = iminor(inode); + + if (isadev->device_id != m) + return -1; + + switch (cmd) { + case DLP_IOC_ALLOCATE_BUFFER: + dev_dbg(shrm->dev, "DLP_IOC_ALLOCATE_BUFFER\n"); + break; + case DLP_IOC_PUT_MESSAGE: + dev_dbg(shrm->dev, "DLP_IOC_PUT_MESSAGE\n"); + break; + case DLP_IOC_GET_MESSAGE: + dev_dbg(shrm->dev, "DLP_IOC_GET_MESSAGE\n"); + break; + case DLP_IOC_DEALLOCATE_BUFFER: + dev_dbg(shrm->dev, "DLP_IOC_DEALLOCATE_BUFFER\n"); + break; + default: + dev_dbg(shrm->dev, "Unknown IOCTL\n"); + err = -1; + break; + } + return err; +} +/** + * isa_mmap() - Maps kernel queue memory to user space. + * + * @filp:file descriptor pointer + * @vma:virtual area memory structure. + * + * This function maps kernel FIFO into user space. This function + * shall be called twice to map both uplink and downlink buffers. + */ +static int isa_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + + u32 m = iminor(filp->f_path.dentry->d_inode); + dev_dbg(shrm->dev, "%s %dIN\n", __func__, m); + + isadev = (struct isadev_context *)filp->private_data; + return 0; +} + +/** + * isa_close() - Close device file + * + * @inode:structure is used by the kernel internally to represent files + * @filp:device file descriptor + * + * This function deletes structues associated with this file, deletes + * queues, flushes and destroys workqueus and closes this file. + * It also unregisters itself from l2mux driver. + */ +static int isa_close(struct inode *inode, struct file *filp) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct isa_driver_context *isa_context = shrm->isa_context; + u8 m; + + mutex_lock(&isa_lock); + m = iminor(filp->f_path.dentry->d_inode); + dev_dbg(shrm->dev, "%s IN %d", __func__, m); + + if (atomic_dec_and_test(&isa_context->is_open[m])) { + atomic_inc(&isa_context->is_open[m]); + dev_err(shrm->dev, "Device not opened yet\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + } + atomic_set(&isa_context->is_open[m], 1); + + dev_dbg(shrm->dev, "isadev->device_id %d", isadev->device_id); + dev_dbg(shrm->dev, "Closed %d device\n", m); + + if (m == ISI_MESSAGING) + dev_dbg(shrm->dev, "Closed ISI_MESSAGING Device\n"); + else if (m == RPC_MESSAGING) + dev_dbg(shrm->dev, "Closed RPC_MESSAGING Device\n"); + else if (m == AUDIO_MESSAGING) + dev_dbg(shrm->dev, "Closed AUDIO_MESSAGING Device\n"); + else if (m == SECURITY_MESSAGING) + dev_dbg(shrm->dev, "Closed SECURITY_MESSAGING Device\n"); + else + dev_dbg(shrm->dev, NAME ":No such device present\n"); + + mutex_unlock(&isa_lock); + return 0; +} +/** + * isa_open() - Open device file + * + * @inode: structure is used by the kernel internally to represent files + * @filp: device file descriptor + * + * This function performs initialization tasks needed to open SHM channel. + * Following tasks are performed. + * -return if device is already opened + * -create uplink FIFO + * -create downlink FIFO + * -init delayed workqueue thread + * -register to l2mux driver + */ +static int isa_open(struct inode *inode, struct file *filp) +{ + int err = 0; + u8 m; + struct isadev_context *isadev; + struct isa_driver_context *isa_context = container_of( + inode->i_cdev, + struct isa_driver_context, + cdev); + struct shrm_dev *shrm = isa_context->isadev->dl_queue.shrm; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (get_boot_state() != BOOT_DONE) { + dev_err(shrm->dev, "Boot is not done\n"); + return -EBUSY; + } + mutex_lock(&isa_lock); + m = iminor(inode); + + if ((m != ISI_MESSAGING) && (m != RPC_MESSAGING) && + (m != AUDIO_MESSAGING) && (m != SECURITY_MESSAGING)) { + dev_err(shrm->dev, "No such device present\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + } + if (!atomic_dec_and_test(&isa_context->is_open[m])) { + atomic_inc(&isa_context->is_open[m]); + dev_err(shrm->dev, "Device already opened\n"); + mutex_unlock(&isa_lock); + return -EBUSY; + } + + if (m == ISI_MESSAGING) + dev_dbg(shrm->dev, "Open ISI_MESSAGING Device\n"); + else if (m == RPC_MESSAGING) + dev_dbg(shrm->dev, "Open RPC_MESSAGING Device\n"); + else if (m == AUDIO_MESSAGING) + dev_dbg(shrm->dev, "Open AUDIO_MESSAGING Device\n"); + else if (m == SECURITY_MESSAGING) + dev_dbg(shrm->dev, "Open SECURITY_MESSAGING Device\n"); + else + dev_dbg(shrm->dev, ":No such device present\n"); + + isadev = &isa_context->isadev[m]; + if (filp != NULL) + filp->private_data = isadev; + + mutex_unlock(&isa_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return err; +} + +const struct file_operations isa_fops = { + .owner = THIS_MODULE, + .open = isa_open, + .release = isa_close, + .ioctl = isa_ioctl, + .mmap = isa_mmap, + .read = isa_read, + .write = isa_write, + .poll = isa_select, +}; + +/** + * isa_init() - module insertion function + * + * This function registers module as a character driver using + * register_chrdev_region() or alloc_chrdev_region. It adds this + * driver to system using cdev_add() call. Major number is dynamically + * allocated using alloc_chrdev_region() by default or left to user to specify + * it during load time. For this variable major is used as module_param + * Nodes to be created using + * mknod /dev/isi c $major 0 + * mknod /dev/rpc c $major 1 + * mknod /dev/audio c $major 2 + * mknod /dev/sec c $major 3 + */ +int isa_init(struct shrm_dev *shrm) +{ + dev_t dev_id; + int retval, no_dev; + struct isadev_context *isadev; + struct isa_driver_context *isa_context; + + isa_context = kzalloc(sizeof(struct isa_driver_context), + GFP_KERNEL); + shrm->isa_context = isa_context; + if (isa_context == NULL) { + dev_err(shrm->dev, "Failed to alloc memory\n"); + return -ENOMEM; + } + + if (major) { + dev_id = MKDEV(major, 0); + retval = register_chrdev_region(dev_id, ISA_DEVICES, NAME); + } else { + retval = alloc_chrdev_region(&dev_id, 0, ISA_DEVICES, NAME); + major = MAJOR(dev_id); + } + + dev_dbg(shrm->dev, "major %d\n", major); + + cdev_init(&isa_context->cdev, &isa_fops); + isa_context->cdev.owner = THIS_MODULE; + retval = cdev_add(&isa_context->cdev, dev_id, ISA_DEVICES); + if (retval) { + dev_err(shrm->dev, "Failed to add char device\n"); + return retval; + } + + for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) + atomic_set(&isa_context->is_open[no_dev], 1); + + isa_context->isadev = kzalloc(sizeof + (struct isadev_context)*ISA_DEVICES, + GFP_KERNEL); + if (isa_context->isadev == NULL) { + dev_err(shrm->dev, "Failed to alloc memory\n"); + return -ENOMEM; + } + for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) { + isadev = &isa_context->isadev[no_dev]; + isadev->device_id = no_dev; + retval = create_queue(&isadev->dl_queue, + isadev->device_id, shrm); + if (retval < 0) { + dev_err(shrm->dev, "create dl_queue failed\n"); + delete_queue(&isadev->dl_queue); + kfree(isadev); + return retval; + } + } + mutex_init(&isa_context->tx_audio_mutex); + spin_lock_init(&isa_context->common_tx); + + dev_err(shrm->dev, "SHRM char driver added\n"); + + return retval; +} + +void isa_exit(struct shrm_dev *shrm) +{ + int no_dev; + struct isadev_context *isadev; + struct isa_driver_context *isa_context = shrm->isa_context; + dev_t dev_id = MKDEV(major, 0); + + for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) { + isadev = &isa_context->isadev[no_dev]; + delete_queue(&isadev->dl_queue); + kfree(isadev); + } + + cdev_del(&isa_context->cdev); + unregister_chrdev_region(dev_id, ISA_DEVICES); + kfree(isa_context); + + dev_err(shrm->dev, "SHRM char driver removed\n"); +} + +#ifdef CONFIG_HIGH_RES_TIMERS +static enum hrtimer_restart callback(struct hrtimer *timer) +{ + return HRTIMER_NORESTART; +} +#endif + + +static int __init shrm_probe(struct platform_device *pdev) +{ + int err = 0; + struct resource *res; + struct shrm_dev *shrm = NULL; + + if (pdev == NULL) { + dev_err(shrm->dev, + "No device/platform_data found on shm device\n"); + return -ENODEV; + } + + + shrm = kzalloc(sizeof(struct shrm_dev), GFP_KERNEL); + if (shrm == NULL) { + dev_err(shrm->dev, + "Could not allocate memory for struct shm_dev\n"); + return -ENOMEM; + } + shrm->dev = &pdev->dev; + + /* initialise the SHM */ + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(shrm->dev, "Unable to map Ca Wake up interrupt\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_wake_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + if (!res) { + dev_err(shrm->dev, + "Unable to map APE_Read_notif_common IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ac_read_notif_0_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 2); + if (!res) { + dev_err(shrm->dev, + "Unable to map APE_Read_notif_audio IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ac_read_notif_1_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 3); + if (!res) { + dev_err(shrm->dev, + "Unable to map Cmt_msg_pending_notif_common IRQbase\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_msg_pending_notif_0_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 4); + if (!res) { + dev_err(shrm->dev, + "Unable to map Cmt_msg_pending_notif_audio IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_msg_pending_notif_1_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(shrm->dev, + "Could not get SHM IO memory information\n"); + err = -ENODEV; + goto rollback_intr; + } + + shrm->intr_base = (void __iomem *)ioremap_nocache(res->start, + res->end - res->start + 1); + + if (!(shrm->intr_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_intr; + } + + shrm->ape_common_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_APE_COMMON_BASE; + shrm->ape_common_fifo_base = + (void __iomem *)ioremap_nocache( + U8500_SHM_FIFO_APE_COMMON_BASE, + SHM_FIFO_0_SIZE); + shrm->ape_common_fifo_size = (SHM_FIFO_0_SIZE)/4; + + if (!(shrm->ape_common_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ape_common_fifo_base; + } + + shrm->cmt_common_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_CMT_COMMON_BASE; + + shrm->cmt_common_fifo_base = + (void __iomem *)ioremap_nocache( + U8500_SHM_FIFO_CMT_COMMON_BASE, SHM_FIFO_0_SIZE); + shrm->cmt_common_fifo_size = (SHM_FIFO_0_SIZE)/4; + + if (!(shrm->cmt_common_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_cmt_common_fifo_base; + } + + shrm->ape_audio_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_APE_AUDIO_BASE; + shrm->ape_audio_fifo_base = + (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_APE_AUDIO_BASE, + SHM_FIFO_1_SIZE); + shrm->ape_audio_fifo_size = (SHM_FIFO_1_SIZE)/4; + + if (!(shrm->ape_audio_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ape_audio_fifo_base; + } + + shrm->cmt_audio_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_CMT_AUDIO_BASE; + shrm->cmt_audio_fifo_base = + (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_CMT_AUDIO_BASE, + SHM_FIFO_1_SIZE); + shrm->cmt_audio_fifo_size = (SHM_FIFO_1_SIZE)/4; + + if (!(shrm->cmt_audio_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_cmt_audio_fifo_base; + } + + shrm->ac_common_shared_wptr = + (void __iomem *)ioremap(SHM_ACFIFO_0_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_common_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ac_common_shared_wptr; + } + + shrm->ac_common_shared_rptr = + (void __iomem *)ioremap(SHM_ACFIFO_0_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_common_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + shrm->ca_common_shared_wptr = + (void __iomem *)ioremap(SHM_CAFIFO_0_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_common_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + shrm->ca_common_shared_rptr = + (void __iomem *)ioremap(SHM_CAFIFO_0_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_common_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + shrm->ac_audio_shared_wptr = + (void __iomem *)ioremap(SHM_ACFIFO_1_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_audio_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + shrm->ac_audio_shared_rptr = + (void __iomem *)ioremap(SHM_ACFIFO_1_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_audio_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + shrm->ca_audio_shared_wptr = + (void __iomem *)ioremap(SHM_CAFIFO_1_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_audio_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + shrm->ca_audio_shared_rptr = + (void __iomem *)ioremap(SHM_CAFIFO_1_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_audio_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + if (isa_init(shrm) != 0) { + dev_err(shrm->dev, "Driver Initialization Error\n"); + err = -EBUSY; + } + /* install handlers and tasklets */ + if (shm_initialise_irq(shrm)) { + dev_err(shrm->dev, "shm error in interrupt registration\n"); + goto rollback_irq; + } + +#ifdef CONFIG_HIGH_RES_TIMERS + hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + timer.function = callback; + + hrtimer_start(&timer, ktime_set(0, 2*NSEC_PER_MSEC), HRTIMER_MODE_REL); +#endif + + return err; + +rollback_irq: + free_shm_irq(shrm); +rollback_map: + iounmap(shrm->ac_common_shared_wptr); + iounmap(shrm->ac_common_shared_rptr); + iounmap(shrm->ca_common_shared_wptr); + iounmap(shrm->ca_common_shared_rptr); + iounmap(shrm->ac_audio_shared_wptr); + iounmap(shrm->ac_audio_shared_rptr); + iounmap(shrm->ca_audio_shared_wptr); + iounmap(shrm->ca_audio_shared_rptr); +rollback_ac_common_shared_wptr: + iounmap(shrm->cmt_audio_fifo_base); +rollback_cmt_audio_fifo_base: + iounmap(shrm->ape_audio_fifo_base); +rollback_ape_audio_fifo_base: + iounmap(shrm->cmt_common_fifo_base); +rollback_cmt_common_fifo_base: + iounmap(shrm->ape_common_fifo_base); +rollback_ape_common_fifo_base: + iounmap(shrm->intr_base); +rollback_intr: + kfree(shrm); + return err; +} + +static int __exit shrm_remove(struct platform_device *pdev) +{ + struct shrm_dev *shrm = platform_get_drvdata(pdev); + + free_shm_irq(shrm); + iounmap(shrm->intr_base); + iounmap(shrm->ape_common_fifo_base); + iounmap(shrm->cmt_common_fifo_base); + iounmap(shrm->ape_audio_fifo_base); + iounmap(shrm->cmt_audio_fifo_base); + iounmap(shrm->ac_common_shared_wptr); + iounmap(shrm->ac_common_shared_rptr); + iounmap(shrm->ca_common_shared_wptr); + iounmap(shrm->ca_common_shared_rptr); + iounmap(shrm->ac_audio_shared_wptr); + iounmap(shrm->ac_audio_shared_rptr); + iounmap(shrm->ca_audio_shared_wptr); + iounmap(shrm->ca_audio_shared_rptr); + kfree(shrm); + isa_exit(shrm); + + return 0; +} +#ifdef CONFIG_PM + +/** + * u8500_shrm_suspend() - This routine puts the SHRM in to sustend state. + * @pdev: platform device. + * + * This routine checks the current ongoing communication with Modem by + * examining the ca_wake state and prevents suspend if modem communication + * is on-going. + * If ca_wake = 1 (high), modem comm. is on-going; don't suspend + * If ca_wake = 0 (low), no comm. with modem on-going.Allow suspend + */ +int u8500_shrm_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct shrm_dev *shrm = platform_get_drvdata(pdev); + + dev_dbg(shrm->dev, "%s called...\n", __func__); + dev_dbg(shrm->dev, "\n ca_wake_req_state = %x\n", + get_ca_wake_req_state()); + /* if ca_wake_req is high, prevent system suspend */ + if (get_ca_wake_req_state()) + return -EBUSY; + else + return 0; +} + +/** + * u8500_shrm_resume() - This routine resumes the SHRM from sustend state. + * @pdev: platform device. + * + * This routine restore back the current state of the SHRM + */ +int u8500_shrm_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct shrm_dev *shrm = platform_get_drvdata(pdev); + + dev_dbg(shrm->dev, "%s called...\n", __func__); + /* TODO: + * As of now, no state save takes place in suspend. + * So, nothing to restore in resume. + * Simply return as of now. + * State saved in suspend should be restored here. + */ + + return 0; +} + +static const struct dev_pm_ops shrm_dev_pm_ops = { + .suspend = u8500_shrm_suspend, + .resume = u8500_shrm_resume, +}; +#endif + +static struct platform_driver shrm_driver = { + .remove = __exit_p(shrm_remove), + .driver = { + .name = "u8500_shrm", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &shrm_dev_pm_ops, +#endif + }, +}; + +static int __init shrm_driver_init(void) +{ + return platform_driver_probe(&shrm_driver, shrm_probe); +} + +static void __exit shrm_driver_exit(void) +{ + platform_driver_unregister(&shrm_driver); +} + +module_init(shrm_driver_init); +module_exit(shrm_driver_exit); + +MODULE_AUTHOR("Biju Das"); +MODULE_DESCRIPTION("Shared Memory Modem Driver Interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/modem/shrm/shrm_fifo.c b/drivers/modem/shrm/shrm_fifo.c new file mode 100644 index 00000000000..1804c1be69e --- /dev/null +++ b/drivers/modem/shrm/shrm_fifo.c @@ -0,0 +1,837 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson + * Author: Kumar Sanghavi <kumar.sanghvi@stericsson.com> for ST-Ericsson + * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/modem/shrm/shrm.h> +#include <linux/modem/shrm/shrm_driver.h> +#include <linux/modem/shrm/shrm_private.h> +#include <linux/modem/shrm/shrm_net.h> +#include <linux/mfd/dbx500-prcmu.h> + +#define L1_BOOT_INFO_REQ 1 +#define L1_BOOT_INFO_RESP 2 +#define L1_NORMAL_MSG 3 +#define L1_HEADER_MASK 28 +#define L1_MAPID_MASK 0xF0000000 +#define CONFIG_OFFSET 8 +#define COUNTER_OFFSET 20 +#define L2_HEADER_SIZE 4 +#define L2_HEADER_OFFSET 24 +#define MASK_0_15_BIT 0xFF +#define MASK_16_31_BIT 0xFF00 +#define MASK_16_27_BIT 0xFFF0000 +#define MASK_0_39_BIT 0xFFFFF +#define MASK_40_55_BIT 0xFF00000 +#define MASK_8_16_BIT 0x0000FF00 +#define MSG_LEN_OFFSET 16 +#define SHRM_VER 2 +#define ca_ist_inactivity_timer 100 /*100ms */ +#define ca_csc_inactivity_timer 100 /*100ms */ + +static u8 msg_audio_counter; +static u8 msg_common_counter; + +struct fifo_write_params ape_shm_fifo_0; +struct fifo_write_params ape_shm_fifo_1; +struct fifo_read_params cmt_shm_fifo_0; +struct fifo_read_params cmt_shm_fifo_1; + + +static u8 cmt_read_notif_0_send; +static u8 cmt_read_notif_1_send; + +void shm_fifo_init(struct shrm_dev *shrm) +{ + ape_shm_fifo_0.writer_local_wptr = 0; + ape_shm_fifo_0.writer_local_rptr = 0; + *((u32 *)shrm->ac_common_shared_wptr) = 0; + *((u32 *)shrm->ac_common_shared_rptr) = 0; + ape_shm_fifo_0.shared_wptr = 0; + ape_shm_fifo_0.shared_rptr = 0; + ape_shm_fifo_0.availablesize = shrm->ape_common_fifo_size; + ape_shm_fifo_0.end_addr_fifo = shrm->ape_common_fifo_size; + ape_shm_fifo_0.fifo_virtual_addr = shrm->ape_common_fifo_base; + spin_lock_init(&ape_shm_fifo_0.fifo_update_lock); + + + cmt_shm_fifo_0.reader_local_rptr = 0; + cmt_shm_fifo_0.reader_local_wptr = 0; + cmt_shm_fifo_0.shared_wptr = + *((u32 *)shrm->ca_common_shared_wptr); + cmt_shm_fifo_0.shared_rptr = + *((u32 *)shrm->ca_common_shared_rptr); + cmt_shm_fifo_0.availablesize = shrm->cmt_common_fifo_size; + cmt_shm_fifo_0.end_addr_fifo = shrm->cmt_common_fifo_size; + cmt_shm_fifo_0.fifo_virtual_addr = shrm->cmt_common_fifo_base; + + ape_shm_fifo_1.writer_local_wptr = 0; + ape_shm_fifo_1.writer_local_rptr = 0; + ape_shm_fifo_1.shared_wptr = 0; + ape_shm_fifo_1.shared_rptr = 0; + *((u32 *)shrm->ac_audio_shared_wptr) = 0; + *((u32 *)shrm->ac_audio_shared_rptr) = 0; + ape_shm_fifo_1.availablesize = shrm->ape_audio_fifo_size; + ape_shm_fifo_1.end_addr_fifo = shrm->ape_audio_fifo_size; + ape_shm_fifo_1.fifo_virtual_addr = shrm->ape_audio_fifo_base; + spin_lock_init(&ape_shm_fifo_1.fifo_update_lock); + + cmt_shm_fifo_1.reader_local_rptr = 0; + cmt_shm_fifo_1.reader_local_wptr = 0; + cmt_shm_fifo_1.shared_wptr = + *((u32 *)shrm->ca_audio_shared_wptr); + cmt_shm_fifo_1.shared_rptr = + *((u32 *)shrm->ca_audio_shared_rptr); + cmt_shm_fifo_1.availablesize = shrm->cmt_audio_fifo_size; + cmt_shm_fifo_1.end_addr_fifo = shrm->cmt_audio_fifo_size; + cmt_shm_fifo_1.fifo_virtual_addr = shrm->cmt_audio_fifo_base; + msg_audio_counter = 0; + msg_common_counter = 0; +} + +u8 read_boot_info_req(struct shrm_dev *shrm, + u32 *config, + u32 *version) +{ + struct fifo_read_params *fifo = &cmt_shm_fifo_0; + u32 *msg; + u32 header = 0; + u8 msgtype; + + /* Read L1 header read content of reader_local_rptr */ + msg = (u32 *) + (fifo->reader_local_rptr + fifo->fifo_virtual_addr); + header = *msg; + msgtype = (header & L1_MAPID_MASK) >> L1_MSG_MAPID_OFFSET; + if (msgtype != L1_BOOT_INFO_REQ) { + dev_err(shrm->dev, "Read_Boot_Info_Req Fatal ERROR\n"); + dev_err(shrm->dev, "Received msgtype is %d\n", msgtype); + dev_info(shrm->dev, "Initiating a modem reset\n"); + queue_kthread_work(&shrm->shm_ac_wake_kw, + &shrm->shm_mod_reset_req); + return 0; + } + *config = (header >> CONFIG_OFFSET) & MASK_0_15_BIT; + *version = header & MASK_0_15_BIT; + fifo->reader_local_rptr += 1; + + return 1; +} + +void write_boot_info_resp(struct shrm_dev *shrm, u32 config, + u32 version) +{ + struct fifo_write_params *fifo = &ape_shm_fifo_0; + u32 *msg; + u8 msg_length; + version = SHRM_VER; + + spin_lock_bh(&fifo->fifo_update_lock); + /* Read L1 header read content of reader_local_rptr */ + msg = (u32 *) + (fifo->writer_local_wptr+fifo->fifo_virtual_addr); + if (version < 1) { + *msg = ((L1_BOOT_INFO_RESP << L1_MSG_MAPID_OFFSET) | + ((config << CONFIG_OFFSET) & MASK_16_31_BIT) + | (version & MASK_0_15_BIT)); + msg_length = 1; + } else { + *msg = ((L1_BOOT_INFO_RESP << L1_MSG_MAPID_OFFSET) | + ((0x8 << MSG_LEN_OFFSET) & MASK_16_27_BIT) | + ((config << CONFIG_OFFSET) & MASK_8_16_BIT)| + version); + msg++; + *msg = ca_ist_inactivity_timer; + msg++; + *msg = ca_csc_inactivity_timer; + msg_length = L1_NORMAL_MSG; + } + fifo->writer_local_wptr += msg_length; + fifo->availablesize -= msg_length; + spin_unlock_bh(&fifo->fifo_update_lock); +} + +/** + * shm_write_msg_to_fifo() - write message to FIFO + * @shrm: pointer to shrm device information structure + * @channel: audio or common channel + * @l2header: L2 header or device ID + * @addr: pointer to write buffer address + * @length: length of mst to write + * + * Function Which Writes the data into Fifo in IPC zone + * It is called from shm_write_msg. This function will copy the msg + * from the kernel buffer to FIFO. There are 4 kernel buffers from where + * the data is to copied to FIFO one for each of the messages ISI, RPC, + * AUDIO and SECURITY. ISI, RPC and SECURITY messages are pushed to FIFO + * in commmon channel and AUDIO message is pushed onto audio channel FIFO. + */ +int shm_write_msg_to_fifo(struct shrm_dev *shrm, u8 channel, + u8 l2header, void *addr, u32 length) +{ + struct fifo_write_params *fifo = NULL; + u32 l1_header = 0, l2_header = 0; + u32 requiredsize; + u32 size = 0; + u32 *msg; + u8 *src; + + if (channel == COMMON_CHANNEL) + fifo = &ape_shm_fifo_0; + else if (channel == AUDIO_CHANNEL) + fifo = &ape_shm_fifo_1; + else { + dev_err(shrm->dev, "invalid channel\n"); + return -EINVAL; + } + + /* L2 size in 32b */ + requiredsize = ((length + 3) / 4); + /* Add size of L1 & L2 header */ + requiredsize += 2; + + /* if availablesize = or < requiredsize then error */ + if (fifo->availablesize <= requiredsize) { + /* Fatal ERROR - should never happens */ + dev_dbg(shrm->dev, "wr_wptr= %x\n", + fifo->writer_local_wptr); + dev_dbg(shrm->dev, "wr_rptr= %x\n", + fifo->writer_local_rptr); + dev_dbg(shrm->dev, "shared_wptr= %x\n", + fifo->shared_wptr); + dev_dbg(shrm->dev, "shared_rptr= %x\n", + fifo->shared_rptr); + dev_dbg(shrm->dev, "availsize= %x\n", + fifo->availablesize); + dev_dbg(shrm->dev, "end__fifo= %x\n", + fifo->end_addr_fifo); + dev_warn(shrm->dev, "Modem is busy, please wait." + " c_cnt = %d; a_cnt = %d\n", msg_common_counter, + msg_audio_counter); + if (channel == COMMON_CHANNEL) { + dev_warn(shrm->dev, + "Modem is lagging behind in reading." + "Stopping n/w dev queue\n"); + shrm_stop_netdev(shrm->ndev); + } + + return -EAGAIN; + } + + if (channel == COMMON_CHANNEL) { + /* build L1 header */ + l1_header = ((L1_NORMAL_MSG << L1_MSG_MAPID_OFFSET) | + (((msg_common_counter++) << COUNTER_OFFSET) + & MASK_40_55_BIT) | + ((length + L2_HEADER_SIZE) & MASK_0_39_BIT)); + } else if (channel == AUDIO_CHANNEL) { + /* build L1 header */ + l1_header = ((L1_NORMAL_MSG << L1_MSG_MAPID_OFFSET) | + (((msg_audio_counter++) << COUNTER_OFFSET) + & MASK_40_55_BIT) | + ((length + L2_HEADER_SIZE) & MASK_0_39_BIT)); + } + + /* + * Need to take care race condition for fifo->availablesize + * & fifo->writer_local_rptr with Ac_Read_notification interrupt. + * One option could be use stack variable for LocalRptr and recompute + * fifo->availablesize,based on flag enabled in the + * Ac_read_notification + */ + l2_header = ((l2header << L2_HEADER_OFFSET) | + ((length) & MASK_0_39_BIT)); + spin_lock_bh(&fifo->fifo_update_lock); + /* Check Local Rptr is less than or equal to Local WPtr */ + if (fifo->writer_local_rptr <= fifo->writer_local_wptr) { + msg = (u32 *) + (fifo->fifo_virtual_addr+fifo->writer_local_wptr); + + /* check enough place bewteen writer_local_wptr & end of FIFO */ + if ((fifo->end_addr_fifo-fifo->writer_local_wptr) >= + requiredsize) { + /* Add L1 header and L2 header */ + *msg = l1_header; + msg++; + *msg = l2_header; + msg++; + + /* copy the l2 message in 1 memcpy */ + memcpy((void *)msg, addr, length); + /* UpdateWptr */ + fifo->writer_local_wptr += requiredsize; + fifo->availablesize -= requiredsize; + fifo->writer_local_wptr %= fifo->end_addr_fifo; + } else { + /* + * message is split between and of FIFO and beg of FIFO + * copy first part from writer_local_wptr to end of FIFO + */ + size = fifo->end_addr_fifo-fifo->writer_local_wptr; + + if (size == 1) { + /* Add L1 header */ + *msg = l1_header; + msg++; + /* UpdateWptr */ + fifo->writer_local_wptr = 0; + fifo->availablesize -= size; + /* + * copy second part from beg of FIFO + * with remaining part of msg + */ + msg = (u32 *) + fifo->fifo_virtual_addr; + *msg = l2_header; + msg++; + + /* copy the l3 message in 1 memcpy */ + memcpy((void *)msg, addr, length); + /* UpdateWptr */ + fifo->writer_local_wptr += + requiredsize-size; + fifo->availablesize -= + (requiredsize-size); + } else if (size == 2) { + /* Add L1 header and L2 header */ + *msg = l1_header; + msg++; + *msg = l2_header; + msg++; + + /* UpdateWptr */ + fifo->writer_local_wptr = 0; + fifo->availablesize -= size; + + /* + * copy second part from beg of FIFO + * with remaining part of msg + */ + msg = (u32 *) + fifo->fifo_virtual_addr; + /* copy the l3 message in 1 memcpy */ + memcpy((void *)msg, addr, length); + + /* UpdateWptr */ + fifo->writer_local_wptr += + requiredsize-size; + fifo->availablesize -= + (requiredsize-size); + } else { + /* Add L1 header and L2 header */ + *msg = l1_header; + msg++; + *msg = l2_header; + msg++; + + /* copy the l2 message in 1 memcpy */ + memcpy((void *)msg, addr, (size-2)*4); + + + /* UpdateWptr */ + fifo->writer_local_wptr = 0; + fifo->availablesize -= size; + + /* + * copy second part from beg of FIFO + * with remaining part of msg + */ + msg = (u32 *)fifo->fifo_virtual_addr; + src = (u8 *)addr+((size - 2) * 4); + memcpy((void *)msg, src, + (length-((size - 2) * 4))); + + /* UpdateWptr */ + fifo->writer_local_wptr += + requiredsize-size; + fifo->availablesize -= + (requiredsize-size); + } + + } + } else { + /* writer_local_rptr > writer_local_wptr */ + msg = (u32 *) + (fifo->fifo_virtual_addr+fifo->writer_local_wptr); + /* Add L1 header and L2 header */ + *msg = l1_header; + msg++; + *msg = l2_header; + msg++; + /* + * copy message possbile between writer_local_wptr up + * to writer_local_rptr copy the l3 message in 1 memcpy + */ + memcpy((void *)msg, addr, length); + + /* UpdateWptr */ + fifo->writer_local_wptr += requiredsize; + fifo->availablesize -= requiredsize; + + } + spin_unlock_bh(&fifo->fifo_update_lock); + return length; +} + +/** + * read_one_l2msg_common() - read message from common channel + * @shrm: pointer to shrm device information structure + * @l2_msg: pointer to the read L2 message buffer + * @len: message length + * + * This function read one message from the FIFO and returns l2 header type + */ +u8 read_one_l2msg_common(struct shrm_dev *shrm, + u8 *l2_msg, u32 *len) +{ + struct fifo_read_params *fifo = &cmt_shm_fifo_0; + + u32 *msg; + u32 l1_header = 0; + u32 l2_header = 0; + u32 length; + u8 msgtype; + u32 msg_size; + u32 size = 0; + + /* Read L1 header read content of reader_local_rptr */ + msg = (u32 *) + (fifo->reader_local_rptr+fifo->fifo_virtual_addr); + l1_header = *msg++; + msgtype = (l1_header & 0xF0000000) >> L1_HEADER_MASK; + + if (msgtype != L1_NORMAL_MSG) { + /* Fatal ERROR - should never happens */ + dev_info(shrm->dev, "wr_wptr= %x\n", + fifo->reader_local_wptr); + dev_info(shrm->dev, "wr_rptr= %x\n", + fifo->reader_local_rptr); + dev_info(shrm->dev, "shared_wptr= %x\n", + fifo->shared_wptr); + dev_info(shrm->dev, "shared_rptr= %x\n", + fifo->shared_rptr); + dev_info(shrm->dev, "availsize= %x\n", + fifo->availablesize); + dev_info(shrm->dev, "end_fifo= %x\n", + fifo->end_addr_fifo); + /* Fatal ERROR - should never happens */ + dev_crit(shrm->dev, "Fatal ERROR - should never happen\n"); + dev_info(shrm->dev, "Initiating a modem reset\n"); + queue_kthread_work(&shrm->shm_ac_wake_kw, + &shrm->shm_mod_reset_req); + } + if (fifo->reader_local_rptr == (fifo->end_addr_fifo-1)) { + l2_header = (*((u32 *)fifo->fifo_virtual_addr)); + length = l2_header & MASK_0_39_BIT; + } else { + /* Read L2 header,Msg size & content of reader_local_rptr */ + l2_header = *msg; + length = l2_header & MASK_0_39_BIT; + } + + *len = length; + msg_size = ((length + 3) / 4); + msg_size += 2; + + if (fifo->reader_local_rptr + msg_size <= + fifo->end_addr_fifo) { + /* Skip L2 header */ + msg++; + + /* read msg between reader_local_rptr and end of FIFO */ + memcpy((void *)l2_msg, (void *)msg, length); + /* UpdateLocalRptr */ + fifo->reader_local_rptr += msg_size; + fifo->reader_local_rptr %= fifo->end_addr_fifo; + } else { + /* + * msg split between end of FIFO and beg copy first + * part of msg read msg between reader_local_rptr + * and end of FIFO + */ + size = fifo->end_addr_fifo-fifo->reader_local_rptr; + if (size == 1) { + msg = (u32 *)(fifo->fifo_virtual_addr); + /* Skip L2 header */ + msg++; + memcpy((void *)l2_msg, (void *)(msg), length); + } else if (size == 2) { + /* Skip L2 header */ + msg++; + msg = (u32 *)(fifo->fifo_virtual_addr); + memcpy((void *)l2_msg, + (void *)(msg), length); + } else { + /* Skip L2 header */ + msg++; + memcpy((void *)l2_msg, (void *)msg, ((size - 2) * 4)); + /* copy second part of msg */ + l2_msg += ((size - 2) * 4); + msg = (u32 *)(fifo->fifo_virtual_addr); + memcpy((void *)l2_msg, (void *)(msg), + (length-((size - 2) * 4))); + } + fifo->reader_local_rptr = + (fifo->reader_local_rptr+msg_size) % + fifo->end_addr_fifo; + } + return (l2_header>>L2_HEADER_OFFSET) & MASK_0_15_BIT; + } + +u8 read_remaining_messages_common() +{ + struct fifo_read_params *fifo = &cmt_shm_fifo_0; + /* + * There won't be any Race condition reader_local_rptr & + * fifo->reader_local_wptr with CaMsgpending Notification Interrupt + */ + return ((fifo->reader_local_rptr != fifo->reader_local_wptr) ? 1 : 0); +} + +u8 read_one_l2msg_audio(struct shrm_dev *shrm, + u8 *l2_msg, u32 *len) +{ + struct fifo_read_params *fifo = &cmt_shm_fifo_1; + + u32 *msg; + u32 l1_header = 0; + u32 l2_header = 0; + u32 length; + u8 msgtype; + u32 msg_size; + u32 size = 0; + + /* Read L1 header read content of reader_local_rptr */ + msg = (u32 *) + (fifo->reader_local_rptr+fifo->fifo_virtual_addr); + l1_header = *msg++; + msgtype = (l1_header & 0xF0000000) >> L1_HEADER_MASK; + + if (msgtype != L1_NORMAL_MSG) { + /* Fatal ERROR - should never happens */ + dev_info(shrm->dev, "wr_local_wptr= %x\n", + fifo->reader_local_wptr); + dev_info(shrm->dev, "wr_local_rptr= %x\n", + fifo->reader_local_rptr); + dev_info(shrm->dev, "shared_wptr= %x\n", + fifo->shared_wptr); + dev_info(shrm->dev, "shared_rptr= %x\n", + fifo->shared_rptr); + dev_info(shrm->dev, "availsize=%x\n", + fifo->availablesize); + dev_info(shrm->dev, "end_fifo= %x\n", + fifo->end_addr_fifo); + dev_info(shrm->dev, "Received msgtype is %d\n", msgtype); + /* Fatal ERROR - should never happens */ + dev_crit(shrm->dev, "Fatal ERROR - should never happen\n"); + dev_info(shrm->dev, "Initiating a modem reset\n"); + queue_kthread_work(&shrm->shm_ac_wake_kw, + &shrm->shm_mod_reset_req); + } + if (fifo->reader_local_rptr == (fifo->end_addr_fifo-1)) { + l2_header = (*((u32 *)fifo->fifo_virtual_addr)); + length = l2_header & MASK_0_39_BIT; + } else { + /* Read L2 header,Msg size & content of reader_local_rptr */ + l2_header = *msg; + length = l2_header & MASK_0_39_BIT; + } + + *len = length; + msg_size = ((length + 3) / 4); + msg_size += 2; + + if (fifo->reader_local_rptr + msg_size <= + fifo->end_addr_fifo) { + /* Skip L2 header */ + msg++; + /* read msg between reader_local_rptr and end of FIFO */ + memcpy((void *)l2_msg, (void *)msg, length); + /* UpdateLocalRptr */ + fifo->reader_local_rptr += msg_size; + fifo->reader_local_rptr %= fifo->end_addr_fifo; + } else { + + /* + * msg split between end of FIFO and beg + * copy first part of msg + * read msg between reader_local_rptr and end of FIFO + */ + size = fifo->end_addr_fifo-fifo->reader_local_rptr; + if (size == 1) { + msg = (u32 *)(fifo->fifo_virtual_addr); + /* Skip L2 header */ + msg++; + memcpy((void *)l2_msg, (void *)(msg), length); + } else if (size == 2) { + /* Skip L2 header */ + msg++; + msg = (u32 *)(fifo->fifo_virtual_addr); + memcpy((void *)l2_msg, (void *)(msg), length); + } else { + /* Skip L2 header */ + msg++; + memcpy((void *)l2_msg, (void *)msg, ((size - 2) * 4)); + /* copy second part of msg */ + l2_msg += ((size - 2) * 4); + msg = (u32 *)(fifo->fifo_virtual_addr); + memcpy((void *)l2_msg, (void *)(msg), + (length-((size - 2) * 4))); + } + fifo->reader_local_rptr = + (fifo->reader_local_rptr+msg_size) % + fifo->end_addr_fifo; + + } + return (l2_header>>L2_HEADER_OFFSET) & MASK_0_15_BIT; + } + +u8 read_remaining_messages_audio() +{ + struct fifo_read_params *fifo = &cmt_shm_fifo_1; + + return ((fifo->reader_local_rptr != fifo->reader_local_wptr) ? + 1 : 0); +} + +u8 is_the_only_one_unread_message(struct shrm_dev *shrm, + u8 channel, u32 length) +{ + struct fifo_write_params *fifo = NULL; + u32 messagesize = 0; + u8 is_only_one_unread_msg = 0; + + if (channel == COMMON_CHANNEL) + fifo = &ape_shm_fifo_0; + else /* channel = AUDIO_CHANNEL */ + fifo = &ape_shm_fifo_1; + + /* L3 size in 32b */ + messagesize = ((length + 3) / 4); + /* Add size of L1 & L2 header */ + messagesize += 2; + /* + * possibility of race condition with Ac Read notification interrupt. + * need to check ? + */ + if (fifo->writer_local_wptr > fifo->writer_local_rptr) + is_only_one_unread_msg = + ((fifo->writer_local_rptr + messagesize) == + fifo->writer_local_wptr) ? 1 : 0; + else + /* Msg split between end of fifo and starting of Fifo */ + is_only_one_unread_msg = + (((fifo->writer_local_rptr + messagesize) % + fifo->end_addr_fifo) == fifo->writer_local_wptr) ? + 1 : 0; + + return is_only_one_unread_msg; +} + +void update_ca_common_local_wptr(struct shrm_dev *shrm) +{ + /* + * update CA common reader local write pointer with the + * shared write pointer + */ + struct fifo_read_params *fifo = &cmt_shm_fifo_0; + + fifo->shared_wptr = + (*((u32 *)shrm->ca_common_shared_wptr)); + fifo->reader_local_wptr = fifo->shared_wptr; +} + +void update_ca_audio_local_wptr(struct shrm_dev *shrm) +{ + /* + * update CA audio reader local write pointer with the + * shared write pointer + */ + struct fifo_read_params *fifo = &cmt_shm_fifo_1; + + fifo->shared_wptr = + (*((u32 *)shrm->ca_audio_shared_wptr)); + fifo->reader_local_wptr = fifo->shared_wptr; +} + +void update_ac_common_local_rptr(struct shrm_dev *shrm) +{ + /* + * update AC common writer local read pointer with the + * shared read pointer + */ + struct fifo_write_params *fifo; + u32 free_space = 0; + + fifo = &ape_shm_fifo_0; + + spin_lock_bh(&fifo->fifo_update_lock); + fifo->shared_rptr = + (*((u32 *)shrm->ac_common_shared_rptr)); + + if (fifo->shared_rptr >= fifo->writer_local_rptr) + free_space = + (fifo->shared_rptr-fifo->writer_local_rptr); + else { + free_space = + (fifo->end_addr_fifo-fifo->writer_local_rptr); + free_space += fifo->shared_rptr; + } + + /* Chance of race condition of below variables with write_msg */ + fifo->availablesize += free_space; + fifo->writer_local_rptr = fifo->shared_rptr; + spin_unlock_bh(&fifo->fifo_update_lock); +} + +void update_ac_audio_local_rptr(struct shrm_dev *shrm) +{ + /* + * update AC audio writer local read pointer with the + * shared read pointer + */ + struct fifo_write_params *fifo; + u32 free_space = 0; + + fifo = &ape_shm_fifo_1; + spin_lock_bh(&fifo->fifo_update_lock); + fifo->shared_rptr = + (*((u32 *)shrm->ac_audio_shared_rptr)); + + if (fifo->shared_rptr >= fifo->writer_local_rptr) + free_space = + (fifo->shared_rptr-fifo->writer_local_rptr); + else { + free_space = + (fifo->end_addr_fifo-fifo->writer_local_rptr); + free_space += fifo->shared_rptr; + } + + /* Chance of race condition of below variables with write_msg */ + fifo->availablesize += free_space; + fifo->writer_local_rptr = fifo->shared_rptr; + spin_unlock_bh(&fifo->fifo_update_lock); +} + +void update_ac_common_shared_wptr(struct shrm_dev *shrm) +{ + /* + * update AC common shared write pointer with the + * local write pointer + */ + struct fifo_write_params *fifo; + + fifo = &ape_shm_fifo_0; + spin_lock_bh(&fifo->fifo_update_lock); + /* Update shared pointer fifo offset of the IPC zone */ + (*((u32 *)shrm->ac_common_shared_wptr)) = + fifo->writer_local_wptr; + + fifo->shared_wptr = fifo->writer_local_wptr; + spin_unlock_bh(&fifo->fifo_update_lock); +} + +void update_ac_audio_shared_wptr(struct shrm_dev *shrm) +{ + /* + * update AC audio shared write pointer with the + * local write pointer + */ + struct fifo_write_params *fifo; + + fifo = &ape_shm_fifo_1; + spin_lock_bh(&fifo->fifo_update_lock); + /* Update shared pointer fifo offset of the IPC zone */ + (*((u32 *)shrm->ac_audio_shared_wptr)) = + fifo->writer_local_wptr; + fifo->shared_wptr = fifo->writer_local_wptr; + spin_unlock_bh(&fifo->fifo_update_lock); +} + +void update_ca_common_shared_rptr(struct shrm_dev *shrm) +{ + /* + * update CA common shared read pointer with the + * local read pointer + */ + struct fifo_read_params *fifo; + + fifo = &cmt_shm_fifo_0; + + /* Update shared pointer fifo offset of the IPC zone */ + (*((u32 *)shrm->ca_common_shared_rptr)) = + fifo->reader_local_rptr; + fifo->shared_rptr = fifo->reader_local_rptr; +} + +void update_ca_audio_shared_rptr(struct shrm_dev *shrm) +{ + /* + * update CA audio shared read pointer with the + * local read pointer + */ + struct fifo_read_params *fifo; + + fifo = &cmt_shm_fifo_1; + + /* Update shared pointer fifo offset of the IPC zone */ + (*((u32 *)shrm->ca_audio_shared_rptr)) = + fifo->reader_local_rptr; + fifo->shared_rptr = fifo->reader_local_rptr; +} + +void get_reader_pointers(u8 channel_type, u32 *reader_local_rptr, + u32 *reader_local_wptr, u32 *shared_rptr) +{ + struct fifo_read_params *fifo = NULL; + + if (channel_type == COMMON_CHANNEL) + fifo = &cmt_shm_fifo_0; + else /* channel_type = AUDIO_CHANNEL */ + fifo = &cmt_shm_fifo_1; + + *reader_local_rptr = fifo->reader_local_rptr; + *reader_local_wptr = fifo->reader_local_wptr; + *shared_rptr = fifo->shared_rptr; +} + +void get_writer_pointers(u8 channel_type, u32 *writer_local_rptr, + u32 *writer_local_wptr, u32 *shared_wptr) +{ + struct fifo_write_params *fifo = NULL; + + if (channel_type == COMMON_CHANNEL) + fifo = &ape_shm_fifo_0; + else /* channel_type = AUDIO_CHANNEL */ + fifo = &ape_shm_fifo_1; + + spin_lock_bh(&fifo->fifo_update_lock); + *writer_local_rptr = fifo->writer_local_rptr; + *writer_local_wptr = fifo->writer_local_wptr; + *shared_wptr = fifo->shared_wptr; + spin_unlock_bh(&fifo->fifo_update_lock); +} + +void set_ca_msg_0_read_notif_send(u8 val) +{ + cmt_read_notif_0_send = val; +} + +u8 get_ca_msg_0_read_notif_send(void) +{ + return cmt_read_notif_0_send; +} + +void set_ca_msg_1_read_notif_send(u8 val) +{ + cmt_read_notif_1_send = val; +} + +u8 get_ca_msg_1_read_notif_send(void) +{ + return cmt_read_notif_1_send; +} diff --git a/drivers/modem/shrm/shrm_protocol.c b/drivers/modem/shrm/shrm_protocol.c new file mode 100644 index 00000000000..cbb3a820317 --- /dev/null +++ b/drivers/modem/shrm/shrm_protocol.c @@ -0,0 +1,1262 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson + * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> for ST-Ericsson + * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/hrtimer.h> +#include <linux/delay.h> +#include <linux/netlink.h> +#include <linux/kthread.h> +#include <linux/modem/shrm/shrm.h> +#include <linux/modem/shrm/shrm_driver.h> +#include <linux/modem/shrm/shrm_private.h> +#include <linux/modem/shrm/shrm_net.h> +#include <linux/modem/modem_client.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <mach/reboot_reasons.h> +#include <mach/suspend.h> + +#define L2_HEADER_ISI 0x0 +#define L2_HEADER_RPC 0x1 +#define L2_HEADER_AUDIO 0x2 +#define L2_HEADER_SECURITY 0x3 +#define L2_HEADER_COMMON_SIMPLE_LOOPBACK 0xC0 +#define L2_HEADER_COMMON_ADVANCED_LOOPBACK 0xC1 +#define L2_HEADER_AUDIO_SIMPLE_LOOPBACK 0x80 +#define L2_HEADER_AUDIO_ADVANCED_LOOPBACK 0x81 +#define L2_HEADER_CIQ 0xC3 +#define L2_HEADER_RTC_CALIBRATION 0xC8 +#define MAX_PAYLOAD 1024 + +#define PRCM_HOSTACCESS_REQ 0x334 + +static u8 boot_state = BOOT_INIT; +static u8 recieve_common_msg[8*1024]; +static u8 recieve_audio_msg[8*1024]; +static received_msg_handler rx_common_handler; +static received_msg_handler rx_audio_handler; +static struct hrtimer timer; +struct sock *shrm_nl_sk; + +static char shrm_common_tx_state = SHRM_SLEEP_STATE; +static char shrm_common_rx_state = SHRM_SLEEP_STATE; +static char shrm_audio_tx_state = SHRM_SLEEP_STATE; +static char shrm_audio_rx_state = SHRM_SLEEP_STATE; + +static atomic_t ac_sleep_disable_count = ATOMIC_INIT(0); +static atomic_t ac_msg_pend_1 = ATOMIC_INIT(0); +static struct shrm_dev *shm_dev; + +/* Spin lock and tasklet declaration */ +DECLARE_TASKLET(shm_ca_0_tasklet, shm_ca_msgpending_0_tasklet, 0); +DECLARE_TASKLET(shm_ca_1_tasklet, shm_ca_msgpending_1_tasklet, 0); +DECLARE_TASKLET(shm_ac_read_0_tasklet, shm_ac_read_notif_0_tasklet, 0); +DECLARE_TASKLET(shm_ac_read_1_tasklet, shm_ac_read_notif_1_tasklet, 0); + +static DEFINE_MUTEX(ac_state_mutex); + +static DEFINE_SPINLOCK(ca_common_lock); +static DEFINE_SPINLOCK(ca_audio_lock); +static DEFINE_SPINLOCK(ca_wake_req_lock); +static DEFINE_SPINLOCK(boot_lock); + +enum shrm_nl { + SHRM_NL_MOD_RESET = 1, + SHRM_NL_MOD_QUERY_STATE, + SHRM_NL_USER_MOD_RESET, + SHRM_NL_STATUS_MOD_ONLINE, + SHRM_NL_STATUS_MOD_OFFLINE, +}; + +void shm_mod_reset_req_work(struct kthread_work *work) +{ + prcmu_modem_reset(); +} + +static void shm_ac_sleep_req_work(struct kthread_work *work) +{ + mutex_lock(&ac_state_mutex); + if (atomic_read(&ac_sleep_disable_count) == 0) + modem_release(shm_dev->modem); + mutex_unlock(&ac_state_mutex); +} + +static void shm_ac_wake_req_work(struct kthread_work *work) +{ + mutex_lock(&ac_state_mutex); + modem_request(shm_dev->modem); + mutex_unlock(&ac_state_mutex); +} + +static u32 get_host_accessport_val(void) +{ + u32 prcm_hostaccess; + + prcm_hostaccess = prcmu_read(PRCM_HOSTACCESS_REQ); + wmb(); + prcm_hostaccess = prcm_hostaccess & 0x01; + + return prcm_hostaccess; +} +static enum hrtimer_restart callback(struct hrtimer *timer) +{ + unsigned long flags; + + spin_lock_irqsave(&ca_wake_req_lock, flags); + if (((shrm_common_rx_state == SHRM_IDLE) || + (shrm_common_rx_state == SHRM_SLEEP_STATE)) + && ((shrm_common_tx_state == SHRM_IDLE) || + (shrm_common_tx_state == SHRM_SLEEP_STATE)) + && ((shrm_audio_rx_state == SHRM_IDLE) || + (shrm_audio_rx_state == SHRM_SLEEP_STATE)) + && ((shrm_audio_tx_state == SHRM_IDLE) || + (shrm_audio_tx_state == SHRM_SLEEP_STATE))) { + + shrm_common_rx_state = SHRM_SLEEP_STATE; + shrm_audio_rx_state = SHRM_SLEEP_STATE; + shrm_common_tx_state = SHRM_SLEEP_STATE; + shrm_audio_tx_state = SHRM_SLEEP_STATE; + + queue_kthread_work(&shm_dev->shm_ac_sleep_kw, + &shm_dev->shm_ac_sleep_req); + + } + spin_unlock_irqrestore(&ca_wake_req_lock, flags); + + return HRTIMER_NORESTART; +} + +int nl_send_multicast_message(int msg, gfp_t gfp_mask) +{ + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh = NULL; + int err; + + /* prepare netlink message */ + skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), gfp_mask); + if (!skb) { + dev_err(shm_dev->dev, "%s:alloc_skb failed\n", __func__); + err = -ENOMEM; + goto out; + } + + nlh = (struct nlmsghdr *)skb->data; + nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); + dev_dbg(shm_dev->dev, "nlh->nlmsg_len = %d\n", nlh->nlmsg_len); + + nlh->nlmsg_pid = 0; /* from kernel */ + nlh->nlmsg_flags = 0; + *(int *)NLMSG_DATA(nlh) = msg; + skb_put(skb, MAX_PAYLOAD); + /* sender is in group 1<<0 */ + NETLINK_CB(skb).pid = 0; /* from kernel */ + /* to mcast group 1<<0 */ + NETLINK_CB(skb).dst_group = 1; + + /*multicast the message to all listening processes*/ + err = netlink_broadcast(shrm_nl_sk, skb, 0, 1, gfp_mask); + dev_dbg(shm_dev->dev, "ret val from nl-multicast = %d\n", err); + +out: + return err; +} + +static void nl_send_unicast_message(int dst_pid) +{ + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh = NULL; + int err; + int bt_state; + unsigned long flags; + + dev_info(shm_dev->dev, "Sending unicast message\n"); + + /* prepare the NL message for unicast */ + skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_KERNEL); + if (!skb) { + dev_err(shm_dev->dev, "%s:alloc_skb failed\n", __func__); + return; + } + + nlh = (struct nlmsghdr *)skb->data; + nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); + dev_dbg(shm_dev->dev, "nlh->nlmsg_len = %d\n", nlh->nlmsg_len); + + nlh->nlmsg_pid = 0; /* from kernel */ + nlh->nlmsg_flags = 0; + + spin_lock_irqsave(&boot_lock, flags); + bt_state = boot_state; + spin_unlock_irqrestore(&boot_lock, flags); + + if (bt_state == BOOT_DONE) + *(int *)NLMSG_DATA(nlh) = SHRM_NL_STATUS_MOD_ONLINE; + else + *(int *)NLMSG_DATA(nlh) = SHRM_NL_STATUS_MOD_OFFLINE; + + skb_put(skb, MAX_PAYLOAD); + /* sender is in group 1<<0 */ + NETLINK_CB(skb).pid = 0; /* from kernel */ + NETLINK_CB(skb).dst_group = 0; + + /*unicast the message to the querying processes*/ + err = netlink_unicast(shrm_nl_sk, skb, dst_pid, MSG_DONTWAIT); + dev_dbg(shm_dev->dev, "ret val from nl-unicast = %d\n", err); +} + + +static int check_modem_in_reset(void) +{ + u8 bt_state; + unsigned long flags; + + spin_lock_irqsave(&boot_lock, flags); + bt_state = boot_state; + spin_unlock_irqrestore(&boot_lock, flags); + +#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET + if (bt_state != BOOT_UNKNOWN) + return 0; + else + return -ENODEV; +#else + /* + * this check won't be applicable and won't work correctly + * if modem-silent-feature is not enabled + * so, simply return 0 + */ + return 0; +#endif +} + +void shm_ca_msgpending_0_tasklet(unsigned long tasklet_data) +{ + struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data; + u32 reader_local_rptr; + u32 reader_local_wptr; + u32 shared_rptr; + u32 config = 0, version = 0; + unsigned long flags; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + /* Interprocess locking */ + spin_lock(&ca_common_lock); + + /* Update_reader_local_wptr with shared_wptr */ + update_ca_common_local_wptr(shrm); + get_reader_pointers(COMMON_CHANNEL, &reader_local_rptr, + &reader_local_wptr, &shared_rptr); + + set_ca_msg_0_read_notif_send(0); + + if (boot_state == BOOT_DONE) { + shrm_common_rx_state = SHRM_PTR_FREE; + + if (reader_local_rptr != shared_rptr) + ca_msg_read_notification_0(shrm); + if (reader_local_rptr != reader_local_wptr) + receive_messages_common(shrm); + get_reader_pointers(COMMON_CHANNEL, &reader_local_rptr, + &reader_local_wptr, &shared_rptr); + if (reader_local_rptr == reader_local_wptr) + shrm_common_rx_state = SHRM_IDLE; + } else { + /* BOOT phase.only a BOOT_RESP should be in FIFO */ + if (boot_state != BOOT_INFO_SYNC) { + if (!read_boot_info_req(shrm, &config, &version)) { + dev_err(shrm->dev, + "Unable to read boot state\n"); + return; + } + /* SendReadNotification */ + ca_msg_read_notification_0(shrm); + /* + * Check the version number before + * sending Boot info response + */ + + /* send MsgPending notification */ + write_boot_info_resp(shrm, config, version); + spin_lock_irqsave(&boot_lock, flags); + boot_state = BOOT_INFO_SYNC; + spin_unlock_irqrestore(&boot_lock, flags); + dev_info(shrm->dev, "BOOT_INFO_SYNC\n"); + queue_kthread_work(&shrm->shm_common_ch_wr_kw, + &shrm->send_ac_msg_pend_notify_0); + } else { + ca_msg_read_notification_0(shrm); + dev_info(shrm->dev, + "BOOT_INFO_SYNC\n"); + } + } + /* Interprocess locking */ + spin_unlock(&ca_common_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void shm_ca_msgpending_1_tasklet(unsigned long tasklet_data) +{ + struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data; + u32 reader_local_rptr; + u32 reader_local_wptr; + u32 shared_rptr; + + /* + * This function is called when CaMsgPendingNotification Trigerred + * by CMU. It means that CMU has wrote a message into Ca Audio FIFO + */ + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown\n", + __func__); + return; + } + + /* Interprocess locking */ + spin_lock(&ca_audio_lock); + + /* Update_reader_local_wptr(with shared_wptr) */ + update_ca_audio_local_wptr(shrm); + get_reader_pointers(AUDIO_CHANNEL, &reader_local_rptr, + &reader_local_wptr, &shared_rptr); + + set_ca_msg_1_read_notif_send(0); + + if (boot_state != BOOT_DONE) { + dev_err(shrm->dev, "Boot Error\n"); + return; + } + shrm_audio_rx_state = SHRM_PTR_FREE; + /* Check we already read the message */ + if (reader_local_rptr != shared_rptr) + ca_msg_read_notification_1(shrm); + if (reader_local_rptr != reader_local_wptr) + receive_messages_audio(shrm); + + get_reader_pointers(AUDIO_CHANNEL, &reader_local_rptr, + &reader_local_wptr, &shared_rptr); + if (reader_local_rptr == reader_local_wptr) + shrm_audio_rx_state = SHRM_IDLE; + + /* Interprocess locking */ + spin_unlock(&ca_audio_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void shm_ac_read_notif_0_tasklet(unsigned long tasklet_data) +{ + struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data; + u32 writer_local_rptr; + u32 writer_local_wptr; + u32 shared_wptr; + unsigned long flags; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + /* Update writer_local_rptrwith shared_rptr */ + update_ac_common_local_rptr(shrm); + get_writer_pointers(COMMON_CHANNEL, &writer_local_rptr, + &writer_local_wptr, &shared_wptr); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown\n", + __func__); + return; + } + + if (boot_state == BOOT_INFO_SYNC) { + /* BOOT_RESP sent by APE has been received by CMT */ + spin_lock_irqsave(&boot_lock, flags); + boot_state = BOOT_DONE; + spin_unlock_irqrestore(&boot_lock, flags); + dev_info(shrm->dev, "IPC_ISA BOOT_DONE\n"); + + if (shrm->msr_flag) { + shrm_start_netdev(shrm->ndev); + shrm->msr_flag = 0; + + /* multicast that modem is online */ + nl_send_multicast_message(SHRM_NL_STATUS_MOD_ONLINE, + GFP_ATOMIC); + } + + } else if (boot_state == BOOT_DONE) { + if (writer_local_rptr != writer_local_wptr) { + shrm_common_tx_state = SHRM_PTR_FREE; + queue_kthread_work(&shrm->shm_common_ch_wr_kw, + &shrm->send_ac_msg_pend_notify_0); + } else { + shrm_common_tx_state = SHRM_IDLE; + shrm_restart_netdev(shrm->ndev); + } + } else { + dev_err(shrm->dev, "Invalid boot state\n"); + } + /* start timer here */ + hrtimer_start(&timer, ktime_set(0, 10*NSEC_PER_MSEC), + HRTIMER_MODE_REL); + atomic_dec(&ac_sleep_disable_count); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void shm_ac_read_notif_1_tasklet(unsigned long tasklet_data) +{ + struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data; + u32 writer_local_rptr; + u32 writer_local_wptr; + u32 shared_wptr; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown\n", + __func__); + return; + } + + /* Update writer_local_rptr(with shared_rptr) */ + update_ac_audio_local_rptr(shrm); + get_writer_pointers(AUDIO_CHANNEL, &writer_local_rptr, + &writer_local_wptr, &shared_wptr); + if (boot_state != BOOT_DONE) { + dev_err(shrm->dev, "Error Case in boot state\n"); + return; + } + if (writer_local_rptr != writer_local_wptr) { + shrm_audio_tx_state = SHRM_PTR_FREE; + queue_kthread_work(&shrm->shm_audio_ch_wr_kw, + &shrm->send_ac_msg_pend_notify_1); + } else { + shrm_audio_tx_state = SHRM_IDLE; + } + /* start timer here */ + hrtimer_start(&timer, ktime_set(0, 10*NSEC_PER_MSEC), + HRTIMER_MODE_REL); + atomic_dec(&ac_sleep_disable_count); + atomic_dec(&ac_msg_pend_1); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void shm_ca_sleep_req_work(struct kthread_work *work) +{ + dev_dbg(shm_dev->dev, "%s:IRQ_PRCMU_CA_SLEEP\n", __func__); + + shrm_common_rx_state = SHRM_IDLE; + shrm_audio_rx_state = SHRM_IDLE; + + if (check_modem_in_reset()) { + dev_err(shm_dev->dev, "%s:Modem state reset or unknown\n", + __func__); + return; + } + + writel((1<<GOP_CA_WAKE_ACK_BIT), + shm_dev->intr_base + GOP_SET_REGISTER_BASE); + + hrtimer_start(&timer, ktime_set(0, 10*NSEC_PER_MSEC), + HRTIMER_MODE_REL); + suspend_unblock_sleep(); + atomic_dec(&ac_sleep_disable_count); +} + +void shm_ca_wake_req_work(struct kthread_work *work) +{ + struct shrm_dev *shrm = container_of(work, + struct shrm_dev, shm_ca_wake_req); + + /* initialize the FIFO Variables */ + if (boot_state == BOOT_INIT) + shm_fifo_init(shrm); + + mutex_lock(&ac_state_mutex); + modem_request(shrm->modem); + mutex_unlock(&ac_state_mutex); + + /* send ca_wake_ack_interrupt to CMU */ + if (!get_host_accessport_val()) { + dev_crit(shrm->dev, "get_host_accessport failed\n"); + dev_info(shrm->dev, "Initiating a modem reset\n"); + prcmu_modem_reset(); + } + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown\n", + __func__); + return; + } + + writel((1<<GOP_CA_WAKE_ACK_BIT), + shm_dev->intr_base + GOP_SET_REGISTER_BASE); +} +#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET +static int shrm_modem_reset_sequence(void) +{ + int err; + unsigned long flags; + + hrtimer_cancel(&timer); + + /* + * keep the count to 0 so that we can bring down the line + * for normal ac-wake and ac-sleep logic + */ + atomic_set(&ac_sleep_disable_count, 0); + atomic_set(&ac_msg_pend_1, 0); + + /* workaround for MSR */ + queue_kthread_work(&shm_dev->shm_ac_wake_kw, + &shm_dev->shm_ac_wake_req); + + /* reset char device queues */ + shrm_char_reset_queues(shm_dev); + + /* reset protocol states */ + shrm_common_tx_state = SHRM_SLEEP_STATE; + shrm_common_rx_state = SHRM_SLEEP_STATE; + shrm_audio_tx_state = SHRM_SLEEP_STATE; + shrm_audio_rx_state = SHRM_SLEEP_STATE; + + /* set the msr flag */ + shm_dev->msr_flag = 1; + + /* multicast that modem is going to reset */ + err = nl_send_multicast_message(SHRM_NL_MOD_RESET, GFP_ATOMIC); + + /* reset the boot state */ + spin_lock_irqsave(&boot_lock, flags); + boot_state = BOOT_INIT; + spin_unlock_irqrestore(&boot_lock, flags); + + /* re-enable irqs */ + enable_irq(shm_dev->ac_read_notif_0_irq); + enable_irq(shm_dev->ac_read_notif_1_irq); + enable_irq(shm_dev->ca_msg_pending_notif_0_irq); + enable_irq(shm_dev->ca_msg_pending_notif_1_irq); + enable_irq(IRQ_PRCMU_CA_WAKE); + enable_irq(IRQ_PRCMU_CA_SLEEP); + + return err; +} +#endif + +static void shrm_modem_reset_callback(unsigned long irq) +{ + dev_err(shm_dev->dev, "Received mod_reset_req interrupt\n"); + +#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET + { + int err; + dev_info(shm_dev->dev, "Initiating Modem silent reset\n"); + + err = shrm_modem_reset_sequence(); + if (err) + dev_err(shm_dev->dev, + "Failed multicast of modem reset\n"); + } +#else + dev_info(shm_dev->dev, "Modem in reset loop, doing System reset\n"); + + /* Call the PRCMU reset API */ + prcmu_system_reset(SW_RESET_NO_ARGUMENT); +#endif +} + +DECLARE_TASKLET(shrm_sw_reset_callback, shrm_modem_reset_callback, + IRQ_PRCMU_MODEM_SW_RESET_REQ); + +static irqreturn_t shrm_prcmu_irq_handler(int irq, void *data) +{ + struct shrm_dev *shrm = data; + unsigned long flags; + + switch (irq) { + case IRQ_PRCMU_CA_WAKE: + suspend_block_sleep(); + if (shrm->msr_flag) + atomic_set(&ac_sleep_disable_count, 0); + atomic_inc(&ac_sleep_disable_count); + queue_kthread_work(&shrm->shm_ca_wake_kw, &shrm->shm_ca_wake_req); + break; + case IRQ_PRCMU_CA_SLEEP: + queue_kthread_work(&shrm->shm_ca_wake_kw, &shrm->shm_ca_sleep_req); + break; + case IRQ_PRCMU_MODEM_SW_RESET_REQ: + /* update the boot_state */ + spin_lock_irqsave(&boot_lock, flags); + boot_state = BOOT_UNKNOWN; + + /* + * put a barrier over here to make sure boot_state is updated + * else, it is seen that some of already executing modem + * irqs or tasklets fail the protocol checks and will ultimately + * try to acces the modem causing system to hang. + * This is particularly seen with user-space initiated modem reset + */ + wmb(); + spin_unlock_irqrestore(&boot_lock, flags); + + disable_irq_nosync(shrm->ac_read_notif_0_irq); + disable_irq_nosync(shrm->ac_read_notif_1_irq); + disable_irq_nosync(shrm->ca_msg_pending_notif_0_irq); + disable_irq_nosync(shrm->ca_msg_pending_notif_1_irq); + disable_irq_nosync(IRQ_PRCMU_CA_WAKE); + disable_irq_nosync(IRQ_PRCMU_CA_SLEEP); + + /* stop network queue */ + shrm_stop_netdev(shm_dev->ndev); + + tasklet_schedule(&shrm_sw_reset_callback); + break; + default: + dev_err(shrm->dev, "%s: => IRQ %d\n", __func__, irq); + return IRQ_NONE; + } + return IRQ_HANDLED; +} + +static void send_ac_msg_pend_notify_0_work(struct kthread_work *work) +{ + struct shrm_dev *shrm = container_of(work, struct shrm_dev, + send_ac_msg_pend_notify_0); + + dev_dbg(shrm->dev, "%s IN\n", __func__); + update_ac_common_shared_wptr(shrm); + + mutex_lock(&ac_state_mutex); + atomic_inc(&ac_sleep_disable_count); + modem_request(shrm->modem); + mutex_unlock(&ac_state_mutex); + + if (!get_host_accessport_val()) { + dev_crit(shrm->dev, "get_host_accessport failed\n"); + dev_info(shrm->dev, "Initiating a modem reset\n"); + prcmu_modem_reset(); + } + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + /* Trigger AcMsgPendingNotification to CMU */ + writel((1<<GOP_COMMON_AC_MSG_PENDING_NOTIFICATION_BIT), + shrm->intr_base + GOP_SET_REGISTER_BASE); + + if (shrm_common_tx_state == SHRM_PTR_FREE) + shrm_common_tx_state = SHRM_PTR_BUSY; + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +static void send_ac_msg_pend_notify_1_work(struct kthread_work *work) +{ + struct shrm_dev *shrm = container_of(work, struct shrm_dev, + send_ac_msg_pend_notify_1); + + dev_dbg(shrm->dev, "%s IN\n", __func__); + /* Update shared_wptr with writer_local_wptr) */ + update_ac_audio_shared_wptr(shrm); + + mutex_lock(&ac_state_mutex); + if (!atomic_read(&ac_msg_pend_1)) { + atomic_inc(&ac_sleep_disable_count); + atomic_inc(&ac_msg_pend_1); + } + modem_request(shrm->modem); + mutex_unlock(&ac_state_mutex); + + if (!get_host_accessport_val()) { + dev_crit(shrm->dev, "get_host_accessport failed\n"); + dev_info(shrm->dev, "Initiating a modem reset\n"); + prcmu_modem_reset(); + } + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + /* Trigger AcMsgPendingNotification to CMU */ + writel((1<<GOP_AUDIO_AC_MSG_PENDING_NOTIFICATION_BIT), + shrm->intr_base + GOP_SET_REGISTER_BASE); + + if (shrm_audio_tx_state == SHRM_PTR_FREE) + shrm_audio_tx_state = SHRM_PTR_BUSY; + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void shm_nl_receive(struct sk_buff *skb) +{ + struct nlmsghdr *nlh = NULL; + int msg; + + dev_dbg(shm_dev->dev, "Received NL msg from user-space\n"); + + nlh = (struct nlmsghdr *)skb->data; + msg = *((int *)(NLMSG_DATA(nlh))); + switch (msg) { + case SHRM_NL_MOD_QUERY_STATE: + dev_info(shm_dev->dev, "mod-query-state from user-space\n"); + nl_send_unicast_message(nlh->nlmsg_pid); + break; + + case SHRM_NL_USER_MOD_RESET: + dev_info(shm_dev->dev, "user-space inited mod-reset-req\n"); + dev_info(shm_dev->dev, "PCRMU resets modem\n"); + prcmu_modem_reset(); + break; + + default: + dev_err(shm_dev->dev, "Invalid NL msg from user-space\n"); + break; + }; +} + +int shrm_protocol_init(struct shrm_dev *shrm, + received_msg_handler common_rx_handler, + received_msg_handler audio_rx_handler) +{ + int err; + struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 }; + + shm_dev = shrm; + boot_state = BOOT_INIT; + dev_info(shrm->dev, "IPC_ISA BOOT_INIT\n"); + rx_common_handler = common_rx_handler; + rx_audio_handler = audio_rx_handler; + atomic_set(&ac_sleep_disable_count, 0); + + hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + timer.function = callback; + + init_kthread_worker(&shrm->shm_common_ch_wr_kw); + shrm->shm_common_ch_wr_kw_task = kthread_run(kthread_worker_fn, + &shrm->shm_common_ch_wr_kw, + "shm_common_channel_irq"); + if (IS_ERR(shrm->shm_common_ch_wr_kw_task)) { + dev_err(shrm->dev, "failed to create work task\n"); + return -ENOMEM; + } + + init_kthread_worker(&shrm->shm_audio_ch_wr_kw); + shrm->shm_audio_ch_wr_kw_task = kthread_run(kthread_worker_fn, + &shrm->shm_audio_ch_wr_kw, + "shm_audio_channel_irq"); + if (IS_ERR(shrm->shm_audio_ch_wr_kw_task)) { + dev_err(shrm->dev, "failed to create work task\n"); + err = -ENOMEM; + goto free_kw1; + } + /* must use the FIFO scheduler as it is realtime sensitive */ + sched_setscheduler(shrm->shm_audio_ch_wr_kw_task, SCHED_FIFO, ¶m); + + init_kthread_worker(&shrm->shm_ac_wake_kw); + shrm->shm_ac_wake_kw_task = kthread_run(kthread_worker_fn, + &shrm->shm_ac_wake_kw, + "shm_ac_wake_req"); + if (IS_ERR(shrm->shm_ac_wake_kw_task)) { + dev_err(shrm->dev, "failed to create work task\n"); + err = -ENOMEM; + goto free_kw2; + } + /* must use the FIFO scheduler as it is realtime sensitive */ + sched_setscheduler(shrm->shm_ac_wake_kw_task, SCHED_FIFO, ¶m); + + init_kthread_worker(&shrm->shm_ca_wake_kw); + shrm->shm_ca_wake_kw_task = kthread_run(kthread_worker_fn, + &shrm->shm_ca_wake_kw, + "shm_ca_wake_req"); + if (IS_ERR(shrm->shm_ca_wake_kw_task)) { + dev_err(shrm->dev, "failed to create work task\n"); + err = -ENOMEM; + goto free_kw3; + } + /* must use the FIFO scheduler as it is realtime sensitive */ + sched_setscheduler(shrm->shm_ca_wake_kw_task, SCHED_FIFO, ¶m); + + init_kthread_worker(&shrm->shm_ac_sleep_kw); + shrm->shm_ac_sleep_kw_task = kthread_run(kthread_worker_fn, + &shrm->shm_ac_sleep_kw, + "shm_ac_sleep_req"); + if (IS_ERR(shrm->shm_ac_sleep_kw_task)) { + dev_err(shrm->dev, "failed to create work task\n"); + err = -ENOMEM; + goto free_kw4; + } + + init_kthread_work(&shrm->send_ac_msg_pend_notify_0, + send_ac_msg_pend_notify_0_work); + init_kthread_work(&shrm->send_ac_msg_pend_notify_1, + send_ac_msg_pend_notify_1_work); + init_kthread_work(&shrm->shm_ca_wake_req, shm_ca_wake_req_work); + init_kthread_work(&shrm->shm_ca_sleep_req, shm_ca_sleep_req_work); + init_kthread_work(&shrm->shm_ac_sleep_req, shm_ac_sleep_req_work); + init_kthread_work(&shrm->shm_ac_wake_req, shm_ac_wake_req_work); + init_kthread_work(&shrm->shm_mod_reset_req, shm_mod_reset_req_work); + + /* set tasklet data */ + shm_ca_0_tasklet.data = (unsigned long)shrm; + shm_ca_1_tasklet.data = (unsigned long)shrm; + + err = request_irq(IRQ_PRCMU_CA_SLEEP, shrm_prcmu_irq_handler, + IRQF_NO_SUSPEND, "ca-sleep", shrm); + if (err < 0) { + dev_err(shm_dev->dev, "Failed alloc IRQ_PRCMU_CA_SLEEP.\n"); + goto free_kw5; + } + + err = request_irq(IRQ_PRCMU_CA_WAKE, shrm_prcmu_irq_handler, + IRQF_NO_SUSPEND, "ca-wake", shrm); + if (err < 0) { + dev_err(shm_dev->dev, "Failed alloc IRQ_PRCMU_CA_WAKE.\n"); + goto drop2; + } + + err = request_irq(IRQ_PRCMU_MODEM_SW_RESET_REQ, shrm_prcmu_irq_handler, + IRQF_NO_SUSPEND, "modem-sw-reset-req", shrm); + if (err < 0) { + dev_err(shm_dev->dev, + "Failed alloc IRQ_PRCMU_MODEM_SW_RESET_REQ.\n"); + goto drop1; + } + +#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET + /* init netlink socket for user-space communication */ + shrm_nl_sk = netlink_kernel_create(NULL, NETLINK_SHRM, 1, + shm_nl_receive, NULL, THIS_MODULE); + + if (!shrm_nl_sk) { + dev_err(shm_dev->dev, "netlink socket creation failed\n"); + goto drop; + } +#endif + return 0; + +#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET +drop: + free_irq(IRQ_PRCMU_MODEM_SW_RESET_REQ, NULL); +#endif +drop1: + free_irq(IRQ_PRCMU_CA_WAKE, NULL); +drop2: + free_irq(IRQ_PRCMU_CA_SLEEP, NULL); +free_kw5: + kthread_stop(shrm->shm_ac_sleep_kw_task); +free_kw4: + kthread_stop(shrm->shm_ca_wake_kw_task); +free_kw3: + kthread_stop(shrm->shm_ac_wake_kw_task); +free_kw2: + kthread_stop(shrm->shm_audio_ch_wr_kw_task); +free_kw1: + kthread_stop(shrm->shm_common_ch_wr_kw_task); + return err; +} + +void shrm_protocol_deinit(struct shrm_dev *shrm) +{ + free_irq(IRQ_PRCMU_CA_SLEEP, NULL); + free_irq(IRQ_PRCMU_CA_WAKE, NULL); + free_irq(IRQ_PRCMU_MODEM_SW_RESET_REQ, NULL); + flush_kthread_worker(&shrm->shm_common_ch_wr_kw); + flush_kthread_worker(&shrm->shm_audio_ch_wr_kw); + flush_kthread_worker(&shrm->shm_ac_wake_kw); + flush_kthread_worker(&shrm->shm_ca_wake_kw); + flush_kthread_worker(&shrm->shm_ac_sleep_kw); + kthread_stop(shrm->shm_common_ch_wr_kw_task); + kthread_stop(shrm->shm_audio_ch_wr_kw_task); + kthread_stop(shrm->shm_ac_wake_kw_task); + kthread_stop(shrm->shm_ca_wake_kw_task); + kthread_stop(shrm->shm_ac_sleep_kw_task); + modem_put(shrm->modem); +} + +int get_ca_wake_req_state(void) +{ + return ((atomic_read(&ac_sleep_disable_count) > 0) || + modem_get_usage(shm_dev->modem)); +} + +irqreturn_t ca_wake_irq_handler(int irq, void *ctrlr) +{ + struct shrm_dev *shrm = ctrlr; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + /* initialize the FIFO Variables */ + if (boot_state == BOOT_INIT) + shm_fifo_init(shrm); + + dev_dbg(shrm->dev, "Inside ca_wake_irq_handler\n"); + + /* Clear the interrupt */ + writel((1 << GOP_CA_WAKE_REQ_BIT), + shrm->intr_base + GOP_CLEAR_REGISTER_BASE); + + /* send ca_wake_ack_interrupt to CMU */ + writel((1 << GOP_CA_WAKE_ACK_BIT), + shrm->intr_base + GOP_SET_REGISTER_BASE); + + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return IRQ_HANDLED; +} + + +irqreturn_t ac_read_notif_0_irq_handler(int irq, void *ctrlr) +{ + struct shrm_dev *shrm = ctrlr; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + shm_ac_read_0_tasklet.data = (unsigned long)shrm; + tasklet_schedule(&shm_ac_read_0_tasklet); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + /* Clear the interrupt */ + writel((1 << GOP_COMMON_AC_READ_NOTIFICATION_BIT), + shrm->intr_base + GOP_CLEAR_REGISTER_BASE); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return IRQ_HANDLED; +} + +irqreturn_t ac_read_notif_1_irq_handler(int irq, void *ctrlr) +{ + struct shrm_dev *shrm = ctrlr; + + dev_dbg(shrm->dev, "%s IN+\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + shm_ac_read_1_tasklet.data = (unsigned long)shrm; + tasklet_schedule(&shm_ac_read_1_tasklet); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + /* Clear the interrupt */ + writel((1 << GOP_AUDIO_AC_READ_NOTIFICATION_BIT), + shrm->intr_base + GOP_CLEAR_REGISTER_BASE); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return IRQ_HANDLED; +} + +irqreturn_t ca_msg_pending_notif_0_irq_handler(int irq, void *ctrlr) +{ + struct shrm_dev *shrm = ctrlr; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + tasklet_schedule(&shm_ca_0_tasklet); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + /* Clear the interrupt */ + writel((1 << GOP_COMMON_CA_MSG_PENDING_NOTIFICATION_BIT), + shrm->intr_base + GOP_CLEAR_REGISTER_BASE); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return IRQ_HANDLED; +} + +irqreturn_t ca_msg_pending_notif_1_irq_handler(int irq, void *ctrlr) +{ + struct shrm_dev *shrm = ctrlr; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + tasklet_schedule(&shm_ca_1_tasklet); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + /* Clear the interrupt */ + writel((1<<GOP_AUDIO_CA_MSG_PENDING_NOTIFICATION_BIT), + shrm->intr_base+GOP_CLEAR_REGISTER_BASE); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return IRQ_HANDLED; + +} + +/** + * shm_write_msg() - write message to shared memory + * @shrm: pointer to the shrm device information structure + * @l2_header: L2 header + * @addr: pointer to the message + * @length: length of the message to be written + * + * This function is called from net or char interface driver write operation. + * Prior to calling this function the message is copied from the user space + * buffer to the kernel buffer. This function based on the l2 header routes + * the message to the respective channel and FIFO. Then makes a call to the + * fifo write function where the message is written to the physical device. + */ +int shm_write_msg(struct shrm_dev *shrm, u8 l2_header, + void *addr, u32 length) +{ + u8 channel = 0; + int ret; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (boot_state != BOOT_DONE) { + dev_err(shrm->dev, + "error:after boot done call this fn, L2Header = %d\n", + l2_header); + ret = -ENODEV; + goto out; + } + + if ((l2_header == L2_HEADER_ISI) || + (l2_header == L2_HEADER_RPC) || + (l2_header == L2_HEADER_SECURITY) || + (l2_header == L2_HEADER_COMMON_SIMPLE_LOOPBACK) || + (l2_header == L2_HEADER_COMMON_ADVANCED_LOOPBACK) || + (l2_header == L2_HEADER_CIQ) || + (l2_header == L2_HEADER_RTC_CALIBRATION)) { + channel = 0; + if (shrm_common_tx_state == SHRM_SLEEP_STATE) + shrm_common_tx_state = SHRM_PTR_FREE; + else if (shrm_common_tx_state == SHRM_IDLE) + shrm_common_tx_state = SHRM_PTR_FREE; + + } else if ((l2_header == L2_HEADER_AUDIO) || + (l2_header == L2_HEADER_AUDIO_SIMPLE_LOOPBACK) || + (l2_header == L2_HEADER_AUDIO_ADVANCED_LOOPBACK)) { + if (shrm_audio_tx_state == SHRM_SLEEP_STATE) + shrm_audio_tx_state = SHRM_PTR_FREE; + else if (shrm_audio_tx_state == SHRM_IDLE) + shrm_audio_tx_state = SHRM_PTR_FREE; + + channel = 1; + } else { + ret = -ENODEV; + goto out; + } + ret = shm_write_msg_to_fifo(shrm, channel, l2_header, addr, length); + if (ret < 0) { + dev_err(shrm->dev, "write message to fifo failed\n"); + return ret; + } + /* + * notify only if new msg copied is the only unread one + * otherwise it means that reading process is ongoing + */ + if (is_the_only_one_unread_message(shrm, channel, length)) { + + /* Send Message Pending Noitication to CMT */ + if (channel == 0) + queue_kthread_work(&shrm->shm_common_ch_wr_kw, + &shrm->send_ac_msg_pend_notify_0); + else + queue_kthread_work(&shrm->shm_audio_ch_wr_kw, + &shrm->send_ac_msg_pend_notify_1); + + } + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return 0; + +out: + return ret; +} + +void ca_msg_read_notification_0(struct shrm_dev *shrm) +{ + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (get_ca_msg_0_read_notif_send() == 0) { + update_ca_common_shared_rptr(shrm); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + /* Trigger CaMsgReadNotification to CMU */ + writel((1 << GOP_COMMON_CA_READ_NOTIFICATION_BIT), + shrm->intr_base + GOP_SET_REGISTER_BASE); + set_ca_msg_0_read_notif_send(1); + shrm_common_rx_state = SHRM_PTR_BUSY; + } + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void ca_msg_read_notification_1(struct shrm_dev *shrm) +{ + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (get_ca_msg_1_read_notif_send() == 0) { + update_ca_audio_shared_rptr(shrm); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + /* Trigger CaMsgReadNotification to CMU */ + writel((1<<GOP_AUDIO_CA_READ_NOTIFICATION_BIT), + shrm->intr_base+GOP_SET_REGISTER_BASE); + set_ca_msg_1_read_notif_send(1); + shrm_audio_rx_state = SHRM_PTR_BUSY; + } + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +/** + * receive_messages_common - receive common channnel msg from + * CMT(Cellular Mobile Terminal) + * @shrm: pointer to shrm device information structure + * + * The messages sent from CMT to APE are written to the respective FIFO + * and an interrupt is triggered by the CMT. This ca message pending + * interrupt calls this function. This function sends a read notification + * acknowledgement to the CMT and calls the common channel receive handler + * where the messsage is copied to the respective(ISI, RPC, SECURIT) queue + * based on the message l2 header. + */ +void receive_messages_common(struct shrm_dev *shrm) +{ + u8 l2_header; + u32 len; + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + l2_header = read_one_l2msg_common(shrm, recieve_common_msg, &len); + /* Send Recieve_Call_back to Upper Layer */ + if (!rx_common_handler) { + dev_err(shrm->dev, "common_rx_handler is Null\n"); + BUG(); + } + (*rx_common_handler)(l2_header, &recieve_common_msg, len, + shrm); + /* SendReadNotification */ + ca_msg_read_notification_0(shrm); + + while (read_remaining_messages_common()) { + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + l2_header = read_one_l2msg_common(shrm, recieve_common_msg, + &len); + /* Send Recieve_Call_back to Upper Layer */ + (*rx_common_handler)(l2_header, + &recieve_common_msg, len, + shrm); + } +} + +/** + * receive_messages_audio() - receive audio message from CMT + * @shrm: pointer to shrm device information structure + * + * The messages sent from CMT to APE are written to the respective FIFO + * and an interrupt is triggered by the CMT. This ca message pending + * interrupt calls this function. This function sends a read notification + * acknowledgement to the CMT and calls the common channel receive handler + * where the messsage is copied to the audio queue. + */ +void receive_messages_audio(struct shrm_dev *shrm) +{ + u8 l2_header; + u32 len; + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + l2_header = read_one_l2msg_audio(shrm, recieve_audio_msg, &len); + /* Send Recieve_Call_back to Upper Layer */ + + if (!rx_audio_handler) { + dev_crit(shrm->dev, "audio_rx_handler is Null\n"); + BUG(); + } + (*rx_audio_handler)(l2_header, &recieve_audio_msg, + len, shrm); + + /* SendReadNotification */ + ca_msg_read_notification_1(shrm); + while (read_remaining_messages_audio()) { + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + l2_header = read_one_l2msg_audio(shrm, + recieve_audio_msg, &len); + /* Send Recieve_Call_back to Upper Layer */ + (*rx_audio_handler)(l2_header, + &recieve_audio_msg, len, + shrm); + } +} + +u8 get_boot_state() +{ + return boot_state; +} diff --git a/drivers/net/Makefile b/drivers/net/Makefile index a6b8ce11a22..a0f2484368b 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -70,3 +70,8 @@ obj-$(CONFIG_USB_IPHETH) += usb/ obj-$(CONFIG_USB_CDC_PHONET) += usb/ obj-$(CONFIG_HYPERV_NET) += hyperv/ + +ifdef CONFIG_PHONET +obj-$(CONFIG_U8500_SHRM) += u8500_shrm.o +obj-$(CONFIG_MODEM_M6718_SPI) += m6718_modem_net.o +endif diff --git a/drivers/net/m6718_modem_net.c b/drivers/net/m6718_modem_net.c new file mode 100644 index 00000000000..f64a775560b --- /dev/null +++ b/drivers/net/m6718_modem_net.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson + * based on u8500_shrm.c + * + * License terms: GNU General Public License (GPL) version 2 + * + * M6718 modem net device interface. + */ +#include <linux/if_ether.h> +#include <linux/netdevice.h> +#include <linux/phonet.h> +#include <linux/if_phonet.h> +#include <linux/if_arp.h> +#include <net/sock.h> +#include <net/phonet/phonet.h> +#include <net/phonet/pep.h> +#include <linux/modem/m6718_spi/modem_net.h> +#include <linux/modem/m6718_spi/modem_driver.h> +#include <linux/modem/m6718_spi/modem_char.h> +#include <linux/ratelimit.h> + + +/** + * modem_net_receive() - receive data and copy to user space buffer + * @dev: pointer to the network device structure + * + * Copy data from ISI queue to the user space buffer. + */ +int modem_net_receive(struct net_device *dev) +{ + struct sk_buff *skb; + struct isa_device_context *isadev; + struct message_queue *q; + u32 msgsize; + u32 size = 0; + struct modem_spi_net_dev *net_iface_priv = + (struct modem_spi_net_dev *)netdev_priv(dev); + struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev; + + isadev = &modem_spi_dev->isa_context->isadev[MODEM_M6718_SPI_CHN_ISI]; + q = &isadev->dl_queue; + + spin_lock_bh(&q->update_lock); + if (list_empty(&q->msg_list)) { + spin_unlock_bh(&q->update_lock); + dev_dbg(modem_spi_dev->dev, "empty queue!\n"); + return 0; + } + spin_unlock_bh(&q->update_lock); + + msgsize = modem_isa_msg_size(q); + if (msgsize <= 0) + return msgsize; + + /* + * The packet has been retrieved from the transmission + * medium. Build an skb around it, so upper layers can handle it + */ + skb = dev_alloc_skb(msgsize); + if (!skb) { + pr_notice_ratelimited("isa rx: low on mem - packet dropped\n"); + dev->stats.rx_dropped++; + return -ENOMEM; + } + + if ((q->readptr + msgsize) >= q->size) { + size = (q->size - q->readptr); + /* copy first part of msg */ + skb_copy_to_linear_data(skb, + (u8 *)(q->fifo_base + q->readptr), size); + skb_put(skb, size); + + /* copy second part of msg at the top of fifo */ + skb_copy_to_linear_data_offset(skb, size, + (u8 *)(q->fifo_base), (msgsize - size)); + skb_put(skb, msgsize - size); + + } else { + skb_copy_to_linear_data(skb, + (u8 *)(q->fifo_base + q->readptr), msgsize); + skb_put(skb, msgsize); + } + + spin_lock_bh(&q->update_lock); + modem_isa_unqueue_msg(q); + spin_unlock_bh(&q->update_lock); + + skb_reset_mac_header(skb); + __skb_pull(skb, dev->hard_header_len); + /* write metadata and then pass to the receive level */ + skb->dev = dev; + skb->protocol = htons(ETH_P_PHONET); + skb->priority = 0; + skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ + if (likely(netif_rx_ni(skb) == NET_RX_SUCCESS)) { + dev->stats.rx_packets++; + dev->stats.rx_bytes += msgsize; + } else { + dev->stats.rx_dropped++; + } + + return msgsize; +} +EXPORT_SYMBOL_GPL(modem_net_receive); + +static int netdev_isa_open(struct net_device *dev) +{ + struct modem_spi_net_dev *net_iface_priv = + (struct modem_spi_net_dev *)netdev_priv(dev); + struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev; + + modem_spi_dev->netdev_flag_up = 1; + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + netif_wake_queue(dev); + return 0; +} + +static int netdev_isa_close(struct net_device *dev) +{ + struct modem_spi_net_dev *net_iface_priv = + (struct modem_spi_net_dev *)netdev_priv(dev); + struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev; + + modem_spi_dev->netdev_flag_up = 0; + netif_stop_queue(dev); + netif_carrier_off(dev); + return 0; +} + +static int netdev_isa_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct if_phonet_req *req = (struct if_phonet_req *)ifr; + + switch (cmd) { + case SIOCPNGAUTOCONF: + req->ifr_phonet_autoconf.device = PN_DEV_HOST; + return 0; + } + return -ENOIOCTLCMD; +} + +static struct net_device_stats *netdev_isa_stats(struct net_device *dev) +{ + return &dev->stats; +} + +/** + * netdev_isa_write() - write through the net interface + * @skb: pointer to the socket buffer + * @dev: pointer to the network device structure + * + * Copies data(ISI message) from the user buffer to the kernel buffer and + * schedule transfer thread to transmit the message to the modem via FIFO. + */ +static netdev_tx_t netdev_isa_write(struct sk_buff *skb, struct net_device *dev) +{ + int err; + int retval = 0; + struct modem_spi_net_dev *net_iface_priv = + (struct modem_spi_net_dev *)netdev_priv(dev); + struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev; + + /* + * FIXME: + * U8500 modem requires that Pipe created/enabled Indication should + * be sent from the port corresponding to GPRS socket. + * Also, the U8500 modem does not implement Pipe controller + * which takes care of port manipulations for GPRS traffic. + * + * Now, APE has GPRS socket and the socket for sending + * Indication msgs bound to different ports. + * Phonet stack does not allow an indication msg to be sent + * from GPRS socket, since Phonet stack assumes the presence + * of Pipe controller in modem. + * + * So, due to lack of Pipe controller implementation in the + * U8500 modem, carry out the port manipulation related to + * GPRS traffic here. + * Ideally, it should be done either by Pipe controller in + * modem OR some implementation of Pipe controller on APE side + */ + if (skb->data[RESOURCE_ID_INDEX] == PN_PIPE) { + if ((skb->data[MSG_ID_INDEX] == PNS_PIPE_CREATED_IND) || + (skb->data[MSG_ID_INDEX] == PNS_PIPE_ENABLED_IND) || + (skb->data[MSG_ID_INDEX] == PNS_PIPE_DISABLED_IND)) + skb->data[SRC_OBJ_INDEX] = skb->data[PIPE_HDL_INDEX]; + } + + spin_lock_bh(&modem_spi_dev->isa_context->common_tx_lock); + err = modem_m6718_spi_send(modem_spi_dev, MODEM_M6718_SPI_CHN_ISI, + skb->len, skb->data); + if (!err) { + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + retval = NETDEV_TX_OK; + dev_kfree_skb(skb); + } else { + dev->stats.tx_dropped++; + retval = NETDEV_TX_BUSY; + } + spin_unlock_bh(&modem_spi_dev->isa_context->common_tx_lock); + + return retval; +} + +static const struct net_device_ops modem_netdev_ops = { + .ndo_open = netdev_isa_open, + .ndo_stop = netdev_isa_close, + .ndo_do_ioctl = netdev_isa_ioctl, + .ndo_start_xmit = netdev_isa_write, + .ndo_get_stats = netdev_isa_stats, +}; + +static void net_device_init(struct net_device *dev) +{ + struct modem_spi_net_dev *net_iface_priv; + + dev->netdev_ops = &modem_netdev_ops; + dev->header_ops = &phonet_header_ops; + dev->type = ARPHRD_PHONET; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; + dev->mtu = PHONET_MAX_MTU; + dev->hard_header_len = MODEM_HLEN; + dev->addr_len = PHONET_ALEN; + dev->tx_queue_len = PN_TX_QUEUE_LEN; + dev->destructor = free_netdev; + dev->dev_addr[0] = PN_LINK_ADDR; + net_iface_priv = netdev_priv(dev); + memset(net_iface_priv, 0 , sizeof(struct modem_spi_net_dev)); +} + +int modem_net_init(struct modem_spi_dev *modem_spi_dev) +{ + struct net_device *nw_device; + struct modem_spi_net_dev *net_iface_priv; + int err; + /* + * keep the same net device name as U8500 to allow userspace clients + * to remain unchanged and use the same interfaces + */ + char *devname = "shrm%d"; + + /* allocate the net device */ + nw_device = modem_spi_dev->ndev = + alloc_netdev(sizeof(struct modem_spi_net_dev), + devname, net_device_init); + if (nw_device == NULL) { + dev_err(modem_spi_dev->dev, + "failed to allocate modem net device\n"); + return -ENOMEM; + } + err = register_netdev(modem_spi_dev->ndev); + if (err) { + dev_err(modem_spi_dev->dev, + "failed to register modem net device: error %d\n", err); + free_netdev(modem_spi_dev->ndev); + return -ENODEV; + } + dev_dbg(modem_spi_dev->dev, "registered modem net device\n"); + + net_iface_priv = (struct modem_spi_net_dev *)netdev_priv(nw_device); + net_iface_priv->modem_spi_dev = modem_spi_dev; + net_iface_priv->iface_num = 0; + return err; +} +EXPORT_SYMBOL_GPL(modem_net_init); + +int modem_net_stop(struct net_device *dev) +{ + netif_stop_queue(dev); + return 0; +} + +int modem_net_restart(struct net_device *dev) +{ + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + return 0; +} +EXPORT_SYMBOL_GPL(modem_net_restart); + +int modem_net_start(struct net_device *dev) +{ + struct modem_spi_net_dev *net_iface_priv = + (struct modem_spi_net_dev *)netdev_priv(dev); + struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev; + + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + netif_start_queue(dev); + modem_spi_dev->netdev_flag_up = 1; + return 0; +} +EXPORT_SYMBOL_GPL(modem_net_start); + +int modem_net_suspend(struct net_device *dev) +{ + if (netif_running(dev)) { + netif_stop_queue(dev); + netif_carrier_off(dev); + } + netif_device_detach(dev); + return 0; +} +EXPORT_SYMBOL_GPL(modem_net_suspend); + +int modem_net_resume(struct net_device *dev) +{ + netif_device_attach(dev); + if (netif_running(dev)) { + netif_carrier_on(dev); + netif_wake_queue(dev); + } + return 0; +} +EXPORT_SYMBOL_GPL(modem_net_resume); + +void modem_net_exit(struct modem_spi_dev *modem_spi_dev) +{ + if (modem_spi_dev && modem_spi_dev->ndev) { + unregister_netdev(modem_spi_dev->ndev); + modem_spi_dev->ndev = NULL; + dev_dbg(modem_spi_dev->dev, "removed modem net device\n"); + } +} +EXPORT_SYMBOL_GPL(modem_net_exit); + +MODULE_AUTHOR("Chris Blair <chris.blair@stericsson.com>"); +MODULE_DESCRIPTION("M6718 modem IPC net device interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/u8500_shrm.c b/drivers/net/u8500_shrm.c new file mode 100644 index 00000000000..0e813bbb3cc --- /dev/null +++ b/drivers/net/u8500_shrm.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) ST-Ericsson SA 2009 + * + * Author: Biju Das <biju.das@stericsson.com> for ST-Ericsson + * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> for ST-Ericsson + * Author: Arun Murthy <arun.murthy@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/if_ether.h> +#include <linux/netdevice.h> +#include <linux/phonet.h> +#include <linux/if_phonet.h> +#include <linux/if_arp.h> +#include <linux/modem/shrm/shrm_driver.h> +#include <linux/modem/shrm/shrm_private.h> +#include <linux/modem/shrm/shrm_config.h> +#include <linux/modem/shrm/shrm_net.h> +#include <linux/modem/shrm/shrm.h> +#include <net/sock.h> +#include <net/phonet/phonet.h> +#include <net/phonet/pep.h> + + +/** + * shrm_net_receive() - receive data and copy to user space buffer + * @dev: pointer to the network device structure + * + * Copy data from ISI queue to the user space buffer. + */ +int shrm_net_receive(struct net_device *dev) +{ + struct sk_buff *skb; + struct isadev_context *isadev; + struct message_queue *q; + u32 msgsize; + u32 size = 0; + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + struct shrm_dev *shrm = net_iface_priv->shrm_device; + + isadev = &shrm->isa_context->isadev[ISI_MESSAGING]; + q = &isadev->dl_queue; + + spin_lock_bh(&q->update_lock); + if (list_empty(&q->msg_list)) { + spin_unlock_bh(&q->update_lock); + dev_dbg(shrm->dev, "Empty Shrm queue\n"); + return 0; + } + spin_unlock_bh(&q->update_lock); + + msgsize = get_size_of_new_msg(q); + if (msgsize <= 0) + return msgsize; + + /* + * The packet has been retrieved from the transmission + * medium. Build an skb around it, so upper layers can handle it + */ + skb = dev_alloc_skb(msgsize); + if (!skb) { + if (printk_ratelimit()) + dev_notice(shrm->dev, + "isa rx: low on mem - packet dropped\n"); + dev->stats.rx_dropped++; + goto out; + } + + if ((q->readptr+msgsize) >= q->size) { + size = (q->size-q->readptr); + /*Copy First Part of msg*/ + skb_copy_to_linear_data(skb, + (u8 *)(q->fifo_base + q->readptr), size); + skb_put(skb, size); + + /*Copy Second Part of msg at the top of fifo*/ + skb_copy_to_linear_data_offset(skb, size, + (u8 *)(q->fifo_base), (msgsize - size)); + skb_put(skb, msgsize-size); + + } else { + skb_copy_to_linear_data(skb, + (u8 *)(q->fifo_base+q->readptr), msgsize); + skb_put(skb, msgsize); + } + + spin_lock_bh(&q->update_lock); + remove_msg_from_queue(q); + spin_unlock_bh(&q->update_lock); + + skb_reset_mac_header(skb); + __skb_pull(skb, dev->hard_header_len); + /*Write metadata, and then pass to the receive level*/ + skb->dev = dev;/*kmalloc(sizeof(struct net_device), GFP_ATOMIC);*/ + skb->protocol = htons(ETH_P_PHONET); + skb->priority = 0; + skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ + if (likely(netif_rx_ni(skb) == NET_RX_SUCCESS)) { + dev->stats.rx_packets++; + dev->stats.rx_bytes += msgsize; + } else + dev->stats.rx_dropped++; + + return msgsize; +out: + return -ENOMEM; +} + +static int netdev_isa_open(struct net_device *dev) +{ + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + struct shrm_dev *shrm = net_iface_priv->shrm_device; + + shrm->netdev_flag_up = 1; + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + netif_wake_queue(dev); + return 0; +} + +static int netdev_isa_close(struct net_device *dev) +{ + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + struct shrm_dev *shrm = net_iface_priv->shrm_device; + + shrm->netdev_flag_up = 0; + netif_stop_queue(dev); + netif_carrier_off(dev); + return 0; +} + +static int netdev_isa_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct if_phonet_req *req = (struct if_phonet_req *)ifr; + + switch (cmd) { + case SIOCPNGAUTOCONF: + req->ifr_phonet_autoconf.device = PN_DEV_HOST; + return 0; + } + return -ENOIOCTLCMD; +} + +static struct net_device_stats *netdev_isa_stats(struct net_device *dev) +{ + return &dev->stats; +} + +/** + * netdev_isa_write() - write through the net interface + * @skb: pointer to the socket buffer + * @dev: pointer to the network device structure + * + * Copies data(ISI message) from the user buffer to the kernel buffer and + * schedule transfer thread to transmit the message to the modem via FIFO. + */ +static netdev_tx_t netdev_isa_write(struct sk_buff *skb, struct net_device *dev) +{ + int err; + int retval = 0; + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + struct shrm_dev *shrm = net_iface_priv->shrm_device; + + /* + * FIXME: + * U8500 modem requires that Pipe created/enabled Indication should + * be sent from the port corresponding to GPRS socket. + * Also, the U8500 modem does not implement Pipe controller + * which takes care of port manipulations for GPRS traffic. + * + * Now, APE has GPRS socket and the socket for sending + * Indication msgs bound to different ports. + * Phonet stack does not allow an indication msg to be sent + * from GPRS socket, since Phonet stack assumes the presence + * of Pipe controller in modem. + * + * So, due to lack of Pipe controller implementation in the + * U8500 modem, carry out the port manipulation related to + * GPRS traffic here. + * Ideally, it should be done either by Pipe controller in + * modem OR some implementation of Pipe controller on APE side + */ + if (skb->data[RESOURCE_ID_INDEX] == PN_PIPE) { + if ((skb->data[MSG_ID_INDEX] == PNS_PIPE_CREATED_IND) || + (skb->data[MSG_ID_INDEX] == PNS_PIPE_ENABLED_IND) || + (skb->data[MSG_ID_INDEX] == PNS_PIPE_DISABLED_IND)) + skb->data[SRC_OBJ_INDEX] = skb->data[PIPE_HDL_INDEX]; + } + + spin_lock_bh(&shrm->isa_context->common_tx); + err = shm_write_msg(shrm, ISI_MESSAGING, skb->data, + skb->len); + if (!err) { + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + retval = NETDEV_TX_OK; + dev_kfree_skb(skb); + } else { + dev->stats.tx_dropped++; + retval = NETDEV_TX_BUSY; + } + spin_unlock_bh(&shrm->isa_context->common_tx); + + return retval; +} + +static const struct net_device_ops shrm_netdev_ops = { + .ndo_open = netdev_isa_open, + .ndo_stop = netdev_isa_close, + .ndo_do_ioctl = netdev_isa_ioctl, + .ndo_start_xmit = netdev_isa_write, + .ndo_get_stats = netdev_isa_stats, +}; + +static void shm_net_init(struct net_device *dev) +{ + struct shrm_net_iface_priv *net_iface_priv; + + dev->netdev_ops = &shrm_netdev_ops; + dev->header_ops = &phonet_header_ops; + dev->type = ARPHRD_PHONET; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; + dev->mtu = PHONET_MAX_MTU; + dev->hard_header_len = SHRM_HLEN; + dev->addr_len = PHONET_ALEN; + dev->tx_queue_len = PN_TX_QUEUE_LEN; + dev->destructor = free_netdev; + dev->dev_addr[0] = PN_LINK_ADDR; + net_iface_priv = netdev_priv(dev); + memset(net_iface_priv, 0 , sizeof(struct shrm_net_iface_priv)); +} + +int shrm_register_netdev(struct shrm_dev *shrm) +{ + struct net_device *nw_device; + struct shrm_net_iface_priv *net_iface_priv; + char *devname = "shrm%d"; + int err; + + /* allocate the net device */ + nw_device = shrm->ndev = alloc_netdev( + sizeof(struct shrm_net_iface_priv), + devname, shm_net_init); + if (nw_device == NULL) { + dev_err(shrm->dev, "Failed to allocate SHRM Netdev\n"); + return -ENOMEM; + } + err = register_netdev(shrm->ndev); + if (err) { + dev_err(shrm->dev, "Err %i in reg shrm-netdev\n", err); + free_netdev(shrm->ndev); + return -ENODEV; + } + dev_info(shrm->dev, "Registered shrm netdev\n"); + + net_iface_priv = (struct shrm_net_iface_priv *)netdev_priv(nw_device); + net_iface_priv->shrm_device = shrm; + net_iface_priv->iface_num = 0; + + return err; +} + +int shrm_stop_netdev(struct net_device *dev) +{ + netif_stop_queue(dev); + return 0; +} + +int shrm_restart_netdev(struct net_device *dev) +{ + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + return 0; +} + +int shrm_start_netdev(struct net_device *dev) +{ + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + struct shrm_dev *shrm = net_iface_priv->shrm_device; + + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + netif_start_queue(dev); + shrm->netdev_flag_up = 1; + return 0; +} + +int shrm_suspend_netdev(struct net_device *dev) +{ + if (netif_running(dev)) { + netif_stop_queue(dev); + netif_carrier_off(dev); + } + netif_device_detach(dev); + + return 0; +} + +int shrm_resume_netdev(struct net_device *dev) +{ + netif_device_attach(dev); + if (netif_running(dev)) { + netif_carrier_on(dev); + netif_wake_queue(dev); + } + + return 0; +} + +void shrm_unregister_netdev(struct shrm_dev *shrm) +{ + unregister_netdev(shrm->ndev); +} diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index c2b724d675c..350f6df2bf4 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -24,6 +24,16 @@ menuconfig STAGING if STAGING +config AB5500_SIM + bool "ST-Ericsson AB5500 SIM Interface driver" + depends on AB5500_CORE + help + SIM Interface driver provides interface to configure + various parameters of AB5550 SIM Level Shifter.Support provided are: + Configure Pull up on sim lines + Configure Operation Mode + Notify Sim Insert/Extract Interrupt + source "drivers/staging/serial/Kconfig" source "drivers/staging/et131x/Kconfig" diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index e1e3f2a72ca..f1f5eaad334 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -60,3 +60,4 @@ obj-$(CONFIG_CW1200) += cw1200/ obj-$(CONFIG_U8500_MMIO) += mmio/ obj-$(CONFIG_U8500_FLASH) += camera_flash/ obj-$(CONFIG_U8500_CM) += nmf-cm/ +obj-$(CONFIG_AB5500_SIM) += ab5500_sim/ diff --git a/drivers/staging/ab5500_sim/Makefile b/drivers/staging/ab5500_sim/Makefile new file mode 100644 index 00000000000..520717e4dd7 --- /dev/null +++ b/drivers/staging/ab5500_sim/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_AB5500_SIM) += ab5500-sim.o diff --git a/drivers/staging/ab5500_sim/ab5500-sim.c b/drivers/staging/ab5500_sim/ab5500-sim.c new file mode 100644 index 00000000000..d222a22ed24 --- /dev/null +++ b/drivers/staging/ab5500_sim/ab5500-sim.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) ST Ericsson SA 2010 + * + * Sim Interface driver for AB5500 + * + * License Terms: GNU General Public License v2 + * Author: Bibek Basu <bibek.basu@stericsson.com> + */ +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> +#include <linux/kobject.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/io.h> +#include <linux/err.h> + +#define USIM_SUP2_REG 0x13 +#define USIM_SUP_REG 0x14 +#define USIM_SIMCTRL_REG 0x17 +#define USIM_SIMCTRL2_REG 0x18 +#define USIM_USBUICC_REG 0x19 +#define USIM_USBUICC2_REG 0x20 +#define SIM_DAT_PULLUP_10K 0x0F +#define SIM_LDO_1_8V 1875000 +#define SIM_LDO_2_8V 2800000 +#define SIM_LDO_2_9V 2900000 + +enum shift { + SHIFT0, + SHIFT1, + SHIFT2, + SHIFT3, + SHIFT4, + SHIFT5, + SHIFT6, + SHIFT7, +}; + +enum mask { + MASK1 = 1, + MASK3 = 3, + MASK7 = 7, +}; + +enum sim_mode { + OFF_MODE, + LOW_PWR, + PWRCTRL, + FULL_PWR, +}; +/** + * struct ab5500_sim - ab5500 Sim Interface device information + * @dev: pointer to the structure device + * @lock: mutex lock + * @sim_int_status: Sim presence status + * @irq_base: Base of the two irqs + */ +struct ab5500_sim { + struct device *dev; + struct mutex lock; + bool sim_int_status; + u8 irq_base; +}; + +/* Exposure to the sysfs interface */ +int ab5500_sim_weak_pulldforce(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + unsigned long user_val; + int err; + bool enable; + + err = strict_strtoul(user_buf, 0, &user_val); + if (err) + return -EINVAL; + enable = user_val ? true : false; + err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM, + USIM_USBUICC2_REG, MASK1 << SHIFT5, user_val << SHIFT5); + if (err) + return -EINVAL; + return count; +} + +int ab5500_sim_load_sel(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + unsigned long user_val; + int err; + bool enable; + + err = strict_strtoul(user_buf, 0, &user_val); + if (err) + return -EINVAL; + enable = user_val ? true : false; + err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM, + USIM_USBUICC_REG, MASK1 << SHIFT1, user_val << SHIFT1); + if (err) + return -EINVAL; + return count; +} + +int ab5500_sim_mode_sel(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + unsigned long user_val; + int err; + + err = strict_strtoul(user_buf, 0, &user_val); + if (err) + return -EINVAL; + err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM, + USIM_SIMCTRL2_REG, MASK3 << SHIFT4, user_val << SHIFT4); + if (err) + return -EINVAL; + return count; +} + +int ab5500_sim_dat_pullup(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + unsigned long user_val; + int err; + + err = strict_strtoul(user_buf, 0, &user_val); + if (err) + return -EINVAL; + err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM, + USIM_SIMCTRL_REG, MASK7, user_val); + if (err) + return -EINVAL; + return count; +} + +int ab5500_sim_enable_pullup(struct device *dev, + struct device_attribute *attr, + const char *user_buf, size_t count) +{ + unsigned long user_val; + int err; + bool enable; + + err = strict_strtoul(user_buf, 0, &user_val); + if (err) + return -EINVAL; + enable = user_val ? true : false; + err = abx500_mask_and_set(dev, AB5500_BANK_SIM_USBSIM, + USIM_SIMCTRL_REG, MASK1 << SHIFT3, enable << SHIFT3); + if (err) + return -EINVAL; + return count; +} + +static ssize_t ab5500_simoff_int(struct device *dev, + struct device_attribute *devattr, char *user_buf) +{ + struct ab5500_sim *di = dev_get_drvdata(dev); + int len; + + mutex_lock(&di->lock); + len = sprintf(user_buf, "%d\n", di->sim_int_status); + mutex_unlock(&di->lock); + return len; +} + +static DEVICE_ATTR(enable_pullup, S_IWUSR, NULL, ab5500_sim_enable_pullup); +static DEVICE_ATTR(dat_pullup, S_IWUSR, NULL, ab5500_sim_dat_pullup); +static DEVICE_ATTR(mode_sel, S_IWUSR, NULL, ab5500_sim_mode_sel); +static DEVICE_ATTR(load_sel, S_IWUSR, NULL, ab5500_sim_load_sel); +static DEVICE_ATTR(weak_pulldforce, S_IWUSR, NULL, ab5500_sim_weak_pulldforce); +static DEVICE_ATTR(simoff_int, S_IRUGO, ab5500_simoff_int, NULL); + +static struct attribute *ab5500_sim_attributes[] = { + &dev_attr_enable_pullup.attr, + &dev_attr_dat_pullup.attr, + &dev_attr_mode_sel.attr, + &dev_attr_load_sel.attr, + &dev_attr_weak_pulldforce.attr, + &dev_attr_simoff_int.attr, + NULL +}; + +static const struct attribute_group ab5500sim_attr_grp = { + .attrs = ab5500_sim_attributes, +}; + +static irqreturn_t ab5500_sim_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + struct ab5500_sim *data = platform_get_drvdata(pdev); + + if (irq == data->irq_base) + data->sim_int_status = true; + else + data->sim_int_status = false; + sysfs_notify(&pdev->dev.kobj, NULL, "simoff_int"); + + return IRQ_HANDLED; +} + +static int __devexit ab5500_sim_remove(struct platform_device *pdev) +{ + struct ab5500_sim *di = platform_get_drvdata(pdev); + int irq = platform_get_irq_byname(pdev, "SIMOFF"); + + if (irq >= 0) { + free_irq(irq, di); + irq++; + free_irq(irq, di); + } + sysfs_remove_group(&pdev->dev.kobj, &ab5500sim_attr_grp); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab5500_sim_probe(struct platform_device *pdev) +{ + int ret = 0; + int irq; + struct ab5500_sim *di = + kzalloc(sizeof(struct ab5500_sim), GFP_KERNEL); + if (!di) { + ret = -ENOMEM; + goto error_alloc; + } + dev_info(&pdev->dev, "ab5500_sim_driver PROBE\n"); + irq = platform_get_irq_byname(pdev, "SIMOFF"); + if (irq < 0) { + dev_err(&pdev->dev, "Get irq by name failed\n"); + ret = irq; + goto exit; + } + di->irq_base = irq; + di->dev = &pdev->dev; + mutex_init(&di->lock); + platform_set_drvdata(pdev, di); + /* sysfs interface to configure sim reg from user space */ + if (sysfs_create_group(&pdev->dev.kobj, &ab5500sim_attr_grp) < 0) { + dev_err(&pdev->dev, " Failed creating sysfs group\n"); + ret = -ENOMEM; + goto error_sysfs; + } + ret = request_threaded_irq(irq, NULL, ab5500_sim_irq_handler, + IRQF_NO_SUSPEND , "ab5500-sim", pdev); + if (ret < 0) { + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + goto error_irq; + } + /* this is the contiguous irq for sim removal,falling edge */ + irq = irq + 1; + ret = request_threaded_irq(irq, NULL, ab5500_sim_irq_handler, + IRQF_NO_SUSPEND , "ab5500-sim", pdev); + if (ret < 0) { + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + free_irq(--irq, di); + goto error_irq; + } + return ret; +error_irq: + sysfs_remove_group(&pdev->dev.kobj, &ab5500sim_attr_grp); +error_sysfs: + platform_set_drvdata(pdev, NULL); +exit: + kfree(di); +error_alloc: + return ret; +} + +static struct platform_driver ab5500_sim_driver = { + .probe = ab5500_sim_probe, + .remove = __devexit_p(ab5500_sim_remove), + .driver = { + .name = "ab5500-sim", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_sim_init(void) +{ + return platform_driver_register(&ab5500_sim_driver); +} + +static void __exit ab5500_sim_exit(void) +{ + platform_driver_unregister(&ab5500_sim_driver); +} + +module_init(ab5500_sim_init); +module_exit(ab5500_sim_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bibek Basu"); +MODULE_ALIAS("platform:ab5500-sim"); +MODULE_DESCRIPTION("AB5500 sim interface driver"); diff --git a/drivers/staging/ab5500_sim/sysfs-sim b/drivers/staging/ab5500_sim/sysfs-sim new file mode 100644 index 00000000000..b809b21e39e --- /dev/null +++ b/drivers/staging/ab5500_sim/sysfs-sim @@ -0,0 +1,83 @@ +What: /sys/devices/platform/ab5500-core.0/ab5500-sim.4/ +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4 directory contains attributes + allowing the user space to check and configure ab5500 sim level + shifter interface caracteristics for communication to SIM card + +What: /sys/devices/.../enable_pullup +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/enable_pullup attribute allows + the user space to configure if internal pull up in SIMIO lines + has to be enabled or disabled. For enabling write 1 to the file + and 0 for disabling + + +What: /sys/devices/.../dat_pullup +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/dat_pullup attribute allows + the user space to configure the resistance value for internal + pull up in SIMIO lines. Following value can be written on the file + 0 SIM_DAT pull-up disabled + 1 SIM_DAT pull-up 4kOhm + 2 SIM_DAT pull-up 5kOhm + 3 SIM_DAT pull-up 6kOhm + 4 SIM_DAT pull-up 7kOhm + 5 SIM_DAT pull-up 8kOhm + 6 SIM_DAT pull-up 9kOhm + 7 SIM_DAT pull-up 10kOhm + +What: /sys/devices/.../mode_sel +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/mode_sel attribute allows + the user space to configure the mode at which the level shifter + will work. Following value can be written on the file + 0 TG mode and LI mode off + 1 TG mode on + 2 LI mode on + 3 TG mode and LI mode off + +What: /sys/devices/.../load_sel +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/load_sel attribute allows + the user space to configure the load on the USBUICC lines. + Following value can be written on the file. + 0 Data line load < 21pF + 1 Data line load 21-30pF + +What: /sys/devices/.../weak_pulldforce +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/weak_pulldforce attribute allows + the user space to configure the weak pull down on the USBUICC lines. + Following value can be written on the file. + 0 USB-UICC data lines weak pull down active + 1 USB-UICC data lines weak pull down not active + +What: /sys/devices/.../simoff_int +Date: June 2011 +KernelVersion: 2.6.35 +Contact: Bibek Basu <bibek.basu@stericsson.com> +Description: + The /sys/devices/.../ab5500-sim.4/simoff_int attribute allows + the user space to poll this file and get notified in case a sim + hot swap has happened. a zero means sim extracetd and a one means + inserted. + + |