summaryrefslogtreecommitdiff
path: root/drivers/misc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/Kconfig31
-rw-r--r--drivers/misc/Makefile5
-rw-r--r--drivers/misc/db8500-modem-trace.c273
-rw-r--r--drivers/misc/dbx500-mloader.c288
-rw-r--r--drivers/misc/mbox.c867
-rw-r--r--drivers/misc/mbox_channels-db5500.c1273
-rw-r--r--drivers/misc/modem_audio/Kconfig6
-rw-r--r--drivers/misc/modem_audio/Makefile2
-rw-r--r--drivers/misc/modem_audio/mad.c506
-rw-r--r--drivers/misc/sim_detect.c306
10 files changed, 3557 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index f92560ef750..4fbe2c3f0f9 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
@@ -523,6 +537,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
@@ -549,4 +579,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 2741a01610f..fc02851cbe8 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -51,6 +51,11 @@ obj-$(CONFIG_HWMEM) += hwmem/
obj-$(CONFIG_DISPDEV) += dispdev/
obj-$(CONFIG_COMPDEV) += compdev/
obj-$(CONFIG_CLONEDEV) += clonedev/
+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..408d1a1a29e
--- /dev/null
+++ b/drivers/misc/dbx500-mloader.c
@@ -0,0 +1,288 @@
+/*
+ * 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>
+#include <mach/hardware.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;
+ void __iomem *uid_base;
+ u8 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;
+ }
+ case ML_GET_FUSEINFO: {
+ ret = copy_to_user(argp, (void *) mloader_priv->uid_base,
+ mloader_priv->size) ? -EFAULT : 0;
+ 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;
+ struct resource *res = NULL;
+
+ 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;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mloader_priv->size = resource_size(res);
+ mloader_priv->uid_base = ioremap(res->start, mloader_priv->size);
+
+ if (!mloader_priv->uid_base) {
+ ret = -ENOMEM;
+ goto err_free_priv;
+ }
+
+ 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..e67f4fad3db
--- /dev/null
+++ b/drivers/misc/sim_detect.c
@@ -0,0 +1,306 @@
+/*
+ * 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 -EINVAL;
+ }
+
+ switch (val) {
+ case -1:
+ case 0:
+ case 1800000:
+ case 3000000:
+ break;
+ default:
+ dev_err(dev, "Invalid voltage class configured.\n");
+ return -EINVAL;
+ }
+
+ /* 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_IWUGO | 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 |
+ IRQF_NO_SUSPEND,
+ "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");