diff options
author | Lee Jones <lee.jones@linaro.org> | 2012-01-05 14:19:50 +0000 |
---|---|---|
committer | Lee Jones <lee.jones@linaro.org> | 2012-01-05 14:19:50 +0000 |
commit | 90c7aee3617913742298b3e861fc139314e20ae6 (patch) | |
tree | a9c06e7fadcc56479ccaf560b100b2440a29724b /drivers/misc | |
parent | e3cdaa3535ace42f553684d9cff72f05faf24df2 (diff) | |
parent | fbdfb62fca47323f1bd3d9b2862d10cd6fc1b334 (diff) |
Automatically merging tracking-igloo_kernel-modem into merge-integration-linux-ux500
Conflicting files:
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/Kconfig | 31 | ||||
-rw-r--r-- | drivers/misc/Makefile | 5 | ||||
-rw-r--r-- | drivers/misc/db8500-modem-trace.c | 273 | ||||
-rw-r--r-- | drivers/misc/dbx500-mloader.c | 269 | ||||
-rw-r--r-- | drivers/misc/mbox.c | 646 | ||||
-rw-r--r-- | drivers/misc/mbox_channels-db5500.c | 1276 | ||||
-rw-r--r-- | drivers/misc/modem_audio/Kconfig | 6 | ||||
-rw-r--r-- | drivers/misc/modem_audio/Makefile | 2 | ||||
-rw-r--r-- | drivers/misc/modem_audio/mad.c | 501 | ||||
-rw-r--r-- | drivers/misc/sim_detect.c | 304 |
10 files changed, 3313 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index a013598fcfd..5c315e68f42 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -461,6 +461,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 @@ -508,6 +522,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 @@ -527,5 +557,6 @@ source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" source "drivers/misc/altera-stapl/Kconfig" source "drivers/misc/i2s/Kconfig" +source "drivers/misc/modem_audio/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index db9f716a3c1..9db8bccbd79 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -50,5 +50,10 @@ obj-$(CONFIG_STM_TRACE) += stm.o obj-$(CONFIG_HWMEM) += hwmem/ obj-$(CONFIG_DISPDEV) += dispdev/ obj-$(CONFIG_STM_I2S) += i2s/ +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-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..9bd5db9b23c --- /dev/null +++ b/drivers/misc/mbox.c @@ -0,0 +1,646 @@ +/* + * 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 <mach/mbox-db5500.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 + +/* Global list of all mailboxes */ +static struct list_head mboxs = LIST_HEAD_INIT(mboxs); + +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; + + spin_lock_irqsave(&mbox->lock, flag); + + 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); + + /* 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 + */ + 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 ((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; + } + + 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; + + 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)) { + /* 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; + } + + /* + * 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; + } + } + + /* 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; + } + + /* 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; + +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 defined(CONFIG_DEBUG_FS) + debugfs_remove(mbox->dentry); + device_remove_file(&mbox->pdev->dev, &dev_attr_fifo); +#endif + 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, NULL); + mbox->client_blocked = 0; + iounmap(mbox->virtbase_local); + iounmap(mbox->virtbase_peer); + mbox->cb = NULL; + mbox->client_data = NULL; + mbox->allocated = false; +} + +void mbox_reset_state(struct mbox *mbox) +{ + list_for_each_entry(mbox, &mboxs, list) { + mbox_shutdown(mbox); + } +} + + +/* 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; + + 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 exit; + } + 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 exit; + } + 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 exit; + } + 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 exit; + } + 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 exit; + } + + 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; + } + + /* 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); + +exit: + return mbox; +} +EXPORT_SYMBOL(mbox_setup); + + +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); + 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); + + 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; + } + 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) +{ + return platform_driver_probe(&mbox_driver, mbox_probe); +} + +module_init(mbox_init); + +void __exit mbox_exit(void) +{ + platform_driver_unregister(&mbox_driver); +} + +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..43a4d57caaf --- /dev/null +++ b/drivers/misc/mbox_channels-db5500.c @@ -0,0 +1,1276 @@ +/* + * 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) + +/* Number of buffers */ +#define NUM_DSP_BUFFER 4 + +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_and_test(&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..62bef3c75fd --- /dev/null +++ b/drivers/misc/modem_audio/mad.c @@ -0,0 +1,501 @@ +/* + * 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 3 +#define MAX_NUM_RX_BUFF 4 +#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 (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->rx_buff); + kfree(mad->tx_buff); + mad->data_written = 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__); + + 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..213645ca46d --- /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, 0666, 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"); |