aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc/modem_if_na
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/modem_if_na')
-rw-r--r--drivers/misc/modem_if_na/Kconfig30
-rw-r--r--drivers/misc/modem_if_na/Makefile8
-rw-r--r--drivers/misc/modem_if_na/lte_modem_bootloader.c320
-rw-r--r--drivers/misc/modem_if_na/modem.c221
-rw-r--r--drivers/misc/modem_if_na/modem_io_device.c965
-rw-r--r--drivers/misc/modem_if_na/modem_link_device_dpram.c1504
-rw-r--r--drivers/misc/modem_if_na/modem_link_device_dpram.h165
-rw-r--r--drivers/misc/modem_if_na/modem_link_device_usb.c1031
-rw-r--r--drivers/misc/modem_if_na/modem_link_device_usb.h148
-rw-r--r--drivers/misc/modem_if_na/modem_link_pm_usb.c370
-rw-r--r--drivers/misc/modem_if_na/modem_link_pm_usb.h88
-rw-r--r--drivers/misc/modem_if_na/modem_modemctl_device_cbp71.c269
-rw-r--r--drivers/misc/modem_if_na/modem_modemctl_device_cmc220.c254
-rw-r--r--drivers/misc/modem_if_na/modem_net_flowcontrol_device.c115
-rw-r--r--drivers/misc/modem_if_na/modem_prj.h239
-rw-r--r--drivers/misc/modem_if_na/modem_utils.c391
-rw-r--r--drivers/misc/modem_if_na/modem_utils.h158
-rw-r--r--drivers/misc/modem_if_na/modem_variation.h124
18 files changed, 6400 insertions, 0 deletions
diff --git a/drivers/misc/modem_if_na/Kconfig b/drivers/misc/modem_if_na/Kconfig
new file mode 100644
index 0000000..d2679e4
--- /dev/null
+++ b/drivers/misc/modem_if_na/Kconfig
@@ -0,0 +1,30 @@
+menuconfig SEC_MODEM
+ bool "Samsung Mobile Modem Interface"
+ default n
+ ---help---
+ Samsung Modem Interface Driver.
+
+config CDMA_MODEM_CBP71
+ bool "modem chip : VIA CBP7.1"
+ depends on SEC_MODEM
+ default n
+
+config LTE_MODEM_CMC220
+ bool "modem chip : cmc220"
+ depends on SEC_MODEM
+ default n
+
+config LINK_DEVICE_DPRAM
+ bool "modem driver link device DPRAM"
+ depends on SEC_MODEM
+ default n
+
+config LINK_DEVICE_USB
+ bool "modem driver link device USB"
+ depends on SEC_MODEM
+ default n
+
+config INTERNAL_MODEM_IF
+ bool "modem feature for INTERNAL MODEM IF"
+ depends on SEC_MODEM
+ default n
diff --git a/drivers/misc/modem_if_na/Makefile b/drivers/misc/modem_if_na/Makefile
new file mode 100644
index 0000000..d680f41
--- /dev/null
+++ b/drivers/misc/modem_if_na/Makefile
@@ -0,0 +1,8 @@
+# Makefile of modem_if
+
+obj-y += modem.o modem_io_device.o modem_net_flowcontrol_device.o
+
+obj-$(CONFIG_CDMA_MODEM_CBP71) += modem_modemctl_device_cbp71.o
+obj-$(CONFIG_LTE_MODEM_CMC220) += modem_modemctl_device_cmc220.o lte_modem_bootloader.o
+obj-$(CONFIG_LINK_DEVICE_DPRAM) += modem_link_device_dpram.o
+obj-$(CONFIG_LINK_DEVICE_USB) += modem_link_device_usb.o modem_link_pm_usb.o
diff --git a/drivers/misc/modem_if_na/lte_modem_bootloader.c b/drivers/misc/modem_if_na/lte_modem_bootloader.c
new file mode 100644
index 0000000..0798b38
--- /dev/null
+++ b/drivers/misc/modem_if_na/lte_modem_bootloader.c
@@ -0,0 +1,320 @@
+/* Lte modem bootloader support for Samsung Tuna Board.
+ *
+ * Copyright (C) 2011 Google, Inc.
+ * Copyright (C) 2011 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/wakelock.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+
+#include <linux/platform_data/lte_modem_bootloader.h>
+
+#define LEN_XMIT_DELEY 10
+#define MAX_XMIT_SIZE 16
+
+int factory_mode;
+
+enum xmit_bootloader_status {
+ XMIT_BOOT_READY,
+ XMIT_LOADER_READY,
+};
+
+struct lte_modem_bootloader {
+ struct spi_device *spi_dev;
+ struct miscdevice dev;
+
+ struct mutex lock;
+
+ unsigned int gpio_lte2ap_status;
+ enum xmit_bootloader_status xmit_status;
+};
+#define to_loader(misc) container_of(misc, struct lte_modem_bootloader, dev);
+
+static inline
+int spi_xmit(struct lte_modem_bootloader *loader,
+ const char *buf, int size_per_xmit)
+{
+ int i;
+ int ret;
+ unsigned char xmit_buf[MAX_XMIT_SIZE];
+ struct spi_message msg;
+ struct spi_transfer xfers[MAX_XMIT_SIZE];
+
+ memcpy(xmit_buf, buf, sizeof(xmit_buf));
+ spi_message_init(&msg);
+ memset(xfers, 0, sizeof(xfers));
+ for (i = 0; i < size_per_xmit ; i++) {
+ xfers[i].cs_change = 1;
+ xfers[i].len = 1;
+ xfers[i].tx_buf = xmit_buf + i;
+ spi_message_add_tail(&xfers[i], &msg);
+ }
+ ret = spi_sync(loader->spi_dev, &msg);
+
+ if (ret < 0)
+ dev_err(&loader->spi_dev->dev,
+ "%s - error %d\n", __func__, ret);
+
+ return ret;
+}
+
+
+static
+int bootloader_write(struct lte_modem_bootloader *loader,
+ const char *addr, const int len)
+{
+ int i;
+ int ret = 0;
+ unsigned char lenbuf[4];
+
+ if (loader->xmit_status == XMIT_LOADER_READY) {
+ memcpy(lenbuf, &len, ARRAY_SIZE(lenbuf));
+ ret = spi_xmit(loader, lenbuf,
+ ARRAY_SIZE(lenbuf));
+ if (ret < 0)
+ return ret;
+ msleep(LEN_XMIT_DELEY);
+ }
+
+ for (i = 0 ; i < len / MAX_XMIT_SIZE ; i++) {
+ ret = spi_xmit(loader,
+ addr + i * MAX_XMIT_SIZE,
+ MAX_XMIT_SIZE);
+ if (ret < 0)
+ return ret;
+ }
+ ret = spi_xmit(loader, addr + i * MAX_XMIT_SIZE , len % MAX_XMIT_SIZE);
+
+ return 0;
+}
+
+
+static
+int bootloader_open(struct inode *inode, struct file *flip)
+{
+ struct lte_modem_bootloader *loader = to_loader(flip->private_data);
+ flip->private_data = loader;
+
+ return 0;
+}
+
+static
+long bootloader_ioctl(struct file *flip,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+ int status;
+ struct lte_modem_bootloader_param param;
+ struct lte_modem_bootloader *loader = flip->private_data;
+
+ mutex_lock(&loader->lock);
+ switch (cmd) {
+ case IOCTL_LTE_MODEM_XMIT_BOOT:
+
+ ret = copy_from_user(&param, (const void __user *)arg,
+ sizeof(param));
+ if (ret) {
+ dev_err(&loader->spi_dev->dev, "%s - can not copy userdata\n",
+ __func__);
+ ret = -EFAULT;
+ goto exit_err;
+ }
+
+ dev_info(&loader->spi_dev->dev,
+ "IOCTL_LTE_MODEM_XMIT_BOOT - bin size: %d\n",
+ param.len);
+
+ ret = bootloader_write(loader, param.buf, param.len);
+ if (ret < 0) {
+ dev_err(&loader->spi_dev->dev, "failed to xmit boot bin\n");
+ } else {
+ if (loader->xmit_status == XMIT_BOOT_READY)
+ loader->xmit_status = XMIT_LOADER_READY;
+ else
+ loader->xmit_status = XMIT_BOOT_READY;
+ }
+
+ break;
+ case IOCTL_LTE_MODEM_LTE2AP_STATUS:
+ status = gpio_get_value(loader->gpio_lte2ap_status);
+ pr_debug("LTE2AP status :%d\n", status);
+ ret = copy_to_user((unsigned int *)arg, &status,
+ sizeof(status));
+
+ break;
+
+ case IOCTL_LTE_MODEM_FACTORY_MODE_ON:
+ factory_mode = 1;
+ pr_info("usb %s, Factory Mode On\n", __func__);
+ break;
+
+ case IOCTL_LTE_MODEM_FACTORY_MODE_OFF:
+ factory_mode = 0;
+ pr_info("usb %s, Factory Mode Off\n", __func__);
+ break;
+
+ default:
+ dev_err(&loader->spi_dev->dev,
+ "%s - ioctl cmd error\n",
+ __func__);
+ ret = -ENOIOCTLCMD;
+
+ break;
+ }
+ mutex_unlock(&loader->lock);
+
+exit_err:
+ return ret;
+}
+
+static const struct file_operations lte_modem_bootloader_fops = {
+ .owner = THIS_MODULE,
+ .open = bootloader_open,
+ .unlocked_ioctl = bootloader_ioctl,
+};
+
+static
+int bootloader_gpio_setup(struct lte_modem_bootloader *loader)
+{
+ if (!loader->gpio_lte2ap_status)
+ return -EINVAL;
+
+ gpio_request(loader->gpio_lte2ap_status, "GPIO_LTE2AP_STATUS");
+ gpio_direction_input(loader->gpio_lte2ap_status);
+
+ return 0;
+}
+
+static
+int __devinit lte_modem_bootloader_probe(struct spi_device *spi)
+{
+ int ret;
+
+ struct lte_modem_bootloader *loader;
+ struct lte_modem_bootloader_platform_data *pdata;
+
+ loader = kzalloc(sizeof(*loader), GFP_KERNEL);
+ if (!loader) {
+ pr_err("failed to allocate for lte_modem_bootloader\n");
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+ mutex_init(&loader->lock);
+
+ spi->bits_per_word = 8;
+
+ if (spi_setup(spi)) {
+ pr_err("failed to setup spi for lte_modem_bootloader\n");
+ ret = -EINVAL;
+ goto err_setup;
+ }
+
+ loader->spi_dev = spi;
+
+ if (!spi->dev.platform_data) {
+ pr_err("failed to get platform data for lte_modem_bootloader\n");
+ ret = -EINVAL;
+ goto err_setup;
+ }
+ pdata = (struct lte_modem_bootloader_platform_data *) \
+ spi->dev.platform_data;
+ loader->gpio_lte2ap_status = pdata->gpio_lte2ap_status;
+
+ ret = bootloader_gpio_setup(loader);
+ if (ret) {
+ pr_err("failed to set gpio for lte_modem_boot_loader\n");
+ goto err_setup;
+ }
+
+ loader->gpio_lte2ap_status = pdata->gpio_lte2ap_status;
+ loader->xmit_status = XMIT_BOOT_READY;
+
+ spi_set_drvdata(spi, loader);
+
+ loader->dev.minor = MISC_DYNAMIC_MINOR;
+ loader->dev.name = "lte_spi";
+ loader->dev.fops = &lte_modem_bootloader_fops;
+ ret = misc_register(&loader->dev);
+ if (ret) {
+ pr_err("failed to register misc dev for lte_modem_bootloader\n");
+ goto err_setup;
+ }
+ pr_info("lte_modem_bootloader successfully probed\n");
+
+ factory_mode = 0;
+
+ return 0;
+
+err_setup:
+ mutex_destroy(&loader->lock);
+ kfree(loader);
+
+err_alloc:
+
+ return ret;
+}
+
+static
+int __devexit lte_modem_bootloader_remove(struct spi_device *spi)
+{
+ struct lte_modem_bootloader *loader = spi_get_drvdata(spi);
+
+ misc_deregister(&loader->dev);
+ mutex_destroy(&loader->lock);
+ kfree(loader);
+
+ return 0;
+}
+
+static
+struct spi_driver lte_modem_bootloader_driver = {
+ .driver = {
+ .name = "lte_modem_spi",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = lte_modem_bootloader_probe,
+ .remove = __devexit_p(lte_modem_bootloader_remove),
+};
+
+static
+int __init lte_modem_bootloader_init(void)
+{
+ return spi_register_driver(&lte_modem_bootloader_driver);
+}
+
+static
+void __exit lte_modem_bootloader_exit(void)
+{
+ spi_unregister_driver(&lte_modem_bootloader_driver);
+}
+
+module_init(lte_modem_bootloader_init);
+module_exit(lte_modem_bootloader_exit);
+
+MODULE_DESCRIPTION("LTE Modem Bootloader driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/modem_if_na/modem.c b/drivers/misc/modem_if_na/modem.c
new file mode 100644
index 0000000..dde1ea1
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem.c
@@ -0,0 +1,221 @@
+/* linux/drivers/modem/modem.c
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+
+#include <linux/platform_data/modem_na.h>
+#include "modem_prj.h"
+#include "modem_variation.h"
+
+
+static struct modem_ctl *create_modemctl_device(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct modem_data *pdata;
+ struct modem_ctl *modemctl;
+ struct device *dev = &pdev->dev;
+
+ /* create modem control device */
+ modemctl = kzalloc(sizeof(struct modem_ctl), GFP_KERNEL);
+ if (!modemctl)
+ return NULL;
+
+ modemctl->dev = dev;
+ modemctl->phone_state = STATE_OFFLINE;
+
+ pdata = pdev->dev.platform_data;
+ modemctl->name = pdata->name;
+
+ /* init modemctl device for getting modemctl operations */
+ ret = call_modem_init_func(modemctl, pdata);
+ if (ret) {
+ printk(KERN_ERR "[MODEM_IF] call_modem_init_func is failed\n");
+ kfree(modemctl);
+ return NULL;
+ }
+
+ pr_debug("[MODEM_IF] %s:create_modemctl_device DONE\n", modemctl->name);
+ return modemctl;
+}
+
+static struct io_device *create_io_device(struct modem_io_t *io_t,
+ struct modem_ctl *modemctl, enum modem_network modem_net)
+{
+ int ret = 0;
+ struct io_device *iod = NULL;
+
+ iod = kzalloc(sizeof(struct io_device), GFP_KERNEL);
+ if (!iod) {
+ pr_err("[MODEM_IF] io device memory alloc fail\n");
+ return NULL;
+ }
+
+ iod->name = io_t->name;
+ iod->id = io_t->id;
+ iod->format = io_t->format;
+ iod->io_typ = io_t->io_type;
+ iod->net_typ = modem_net;
+
+ /* link between io device and modem control */
+ iod->mc = modemctl;
+ if (iod->format == IPC_FMT)
+ modemctl->iod = iod;
+
+ /* register misc device or net device */
+ ret = init_io_device(iod);
+ if (ret) {
+ kfree(iod);
+ return NULL;
+ }
+
+ pr_debug("[MODEM_IF] %s : create_io_device DONE\n", io_t->name);
+ return iod;
+}
+static int __devinit modem_probe(struct platform_device *pdev)
+{
+ int i;
+ struct modem_data *pdata;
+ struct modem_ctl *modemctl;
+ struct io_device *iod[MAX_NUM_IO_DEV];
+ struct link_device *ld;
+ struct io_raw_devices *io_raw_devs = NULL;
+
+ pdata = pdev->dev.platform_data;
+ memset(iod, 0, sizeof(iod));
+
+ modemctl = create_modemctl_device(pdev);
+ if (!modemctl) {
+ printk(KERN_ERR "[MODEM_IF] modemctl is null\n");
+ return -ENOMEM;
+ }
+ /* create link device */
+ ld = call_link_init_func(pdev, pdata->link_type);
+ if (!ld)
+ goto err_free_modemctl;
+
+ io_raw_devs = kzalloc(sizeof(struct io_raw_devices), GFP_KERNEL);
+ if (!io_raw_devs) {
+ printk(KERN_ERR "[MODEM_IF] io_raw_devs is null\n");
+ return -ENOMEM;
+ }
+
+ /* create io deivces and connect to modemctl device */
+ for (i = 0; i < pdata->num_iodevs; i++) {
+ iod[i] = create_io_device(&pdata->iodevs[i], modemctl,
+ pdata->modem_net);
+ if (!iod[i])
+ goto err_free_modemctl;
+
+ if (iod[i]->format == IPC_RAW) {
+ int ch = iod[i]->id & 0x1F;
+ io_raw_devs->raw_devices[ch] = iod[i];
+ io_raw_devs->num_of_raw_devs++;
+ iod[i]->link = ld;
+ } else {
+ /* connect io devices to one link device */
+ ld->attach(ld, iod[i]);
+ }
+
+ if (iod[i]->format == IPC_MULTI_RAW)
+ iod[i]->private_data = (void *)io_raw_devs;
+ }
+
+ platform_set_drvdata(pdev, modemctl);
+
+ pr_debug("[MODEM_IF] modem_probe DONE\n");
+ return 0;
+
+err_free_modemctl:
+ for (i = 0; i < pdata->num_iodevs; i++)
+ if (iod[i] != NULL)
+ kfree(iod[i]);
+
+ if (io_raw_devs != NULL)
+ kfree(io_raw_devs);
+
+ if (modemctl != NULL)
+ kfree(modemctl);
+
+ return -ENOMEM;
+}
+
+static void modem_shutdown(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct modem_ctl *mc = dev_get_drvdata(dev);
+
+ if (!mc)
+ return;
+
+ free_irq(mc->irq_phone_active, mc);
+
+ if (mc->ops.modem_off)
+ mc->ops.modem_off(mc);
+}
+
+static int modem_suspend(struct device *pdev)
+{
+ struct modem_ctl *mc = dev_get_drvdata(pdev);
+ gpio_set_value(mc->gpio_pda_active, 0);
+ return 0;
+}
+
+static int modem_resume(struct device *pdev)
+{
+ struct modem_ctl *mc = dev_get_drvdata(pdev);
+ gpio_set_value(mc->gpio_pda_active, 1);
+ return 0;
+}
+
+static const struct dev_pm_ops modem_pm_ops = {
+ .suspend = modem_suspend,
+ .resume = modem_resume,
+};
+
+static struct platform_driver modem_driver = {
+ .probe = modem_probe,
+ .shutdown = modem_shutdown,
+ .driver = {
+ .name = "modem_if",
+ .pm = &modem_pm_ops,
+ },
+};
+
+static int __init modem_init(void)
+{
+ return platform_driver_register(&modem_driver);
+}
+
+module_init(modem_init);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Samsung Modem Interface Driver");
diff --git a/drivers/misc/modem_if_na/modem_io_device.c b/drivers/misc/modem_if_na/modem_io_device.c
new file mode 100644
index 0000000..1a936b1
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_io_device.c
@@ -0,0 +1,965 @@
+/* /linux/drivers/misc/modem_if/modem_io_device.c
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/if_arp.h>
+#include <linux/ip.h>
+#include <linux/if_ether.h>
+#include <linux/etherdevice.h>
+#include <linux/ratelimit.h>
+
+#include <linux/platform_data/modem_na.h>
+#include "modem_prj.h"
+
+
+#define HDLC_START 0x7F
+#define HDLC_END 0x7E
+#define SIZE_OF_HDLC_START 1
+#define SIZE_OF_HDLC_END 1
+#define MAX_RXDATA_SIZE (4096 - 512)
+
+static const char hdlc_start[1] = { HDLC_START };
+static const char hdlc_end[1] = { HDLC_END };
+
+struct fmt_hdr {
+ u16 len;
+ u8 control;
+} __packed;
+
+struct raw_hdr {
+ u32 len;
+ u8 channel;
+ u8 control;
+} __packed;
+
+struct rfs_hdr {
+ u32 len;
+ u8 cmd;
+ u8 id;
+} __packed;
+
+static const char const *modem_state_name[] = {
+ [STATE_OFFLINE] = "OFFLINE",
+ [STATE_CRASH_EXIT] = "CRASH_EXIT",
+ [STATE_BOOTING] = "BOOTING",
+ [STATE_ONLINE] = "ONLINE",
+ [STATE_LOADER_DONE] = "LOADER_DONE",
+ [STATE_NV_REBUILDING] = "NV_REBUILDING",
+};
+
+static int rx_iodev_skb(struct io_device *iod);
+
+static int get_header_size(struct io_device *iod)
+{
+ switch (iod->format) {
+ case IPC_FMT:
+ return sizeof(struct fmt_hdr);
+
+ case IPC_RAW:
+ case IPC_MULTI_RAW:
+ return sizeof(struct raw_hdr);
+
+ case IPC_RFS:
+ return sizeof(struct rfs_hdr);
+
+ case IPC_BOOT:
+ /* minimum size for transaction align */
+ return 4;
+
+ case IPC_RAMDUMP:
+ default:
+ return 0;
+ }
+}
+
+static int get_hdlc_size(struct io_device *iod, char *buf)
+{
+ struct fmt_hdr *fmt_header;
+ struct raw_hdr *raw_header;
+ struct rfs_hdr *rfs_header;
+
+ pr_debug("[MODEM_IF] buf : %02x %02x %02x (%d)\n", *buf, *(buf + 1),
+ *(buf + 2), __LINE__);
+
+ switch (iod->format) {
+ case IPC_FMT:
+ fmt_header = (struct fmt_hdr *)buf;
+ return fmt_header->len;
+ case IPC_RAW:
+ case IPC_MULTI_RAW:
+ raw_header = (struct raw_hdr *)buf;
+ return raw_header->len;
+ case IPC_RFS:
+ rfs_header = (struct rfs_hdr *)buf;
+ return rfs_header->len;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static void *get_header(struct io_device *iod, size_t count,
+ char *frame_header_buf)
+{
+ struct fmt_hdr *fmt_h;
+ struct raw_hdr *raw_h;
+ struct rfs_hdr *rfs_h;
+
+ switch (iod->format) {
+ case IPC_FMT:
+ fmt_h = (struct fmt_hdr *)frame_header_buf;
+
+ fmt_h->len = count + sizeof(struct fmt_hdr);
+ fmt_h->control = 0;
+
+ return (void *)frame_header_buf;
+
+ case IPC_RAW:
+ case IPC_MULTI_RAW:
+ raw_h = (struct raw_hdr *)frame_header_buf;
+
+ raw_h->len = count + sizeof(struct raw_hdr);
+ raw_h->channel = iod->id & 0x1F;
+ raw_h->control = 0;
+
+ return (void *)frame_header_buf;
+
+ case IPC_RFS:
+ rfs_h = (struct rfs_hdr *)frame_header_buf;
+
+ rfs_h->len = count + sizeof(struct raw_hdr);
+ rfs_h->id = iod->id;
+
+ return (void *)frame_header_buf;
+
+ default:
+ return 0;
+ }
+}
+
+static inline int rx_hdlc_head_start_check(char *buf)
+{
+ /* check hdlc head and return size of start byte */
+ return (buf[0] == HDLC_START) ? SIZE_OF_HDLC_START : -EBADMSG;
+}
+
+static inline int rx_hdlc_tail_check(char *buf)
+{
+ /* check hdlc tail and return size of tail byte */
+ return (buf[0] == HDLC_END) ? SIZE_OF_HDLC_END : -EBADMSG;
+}
+
+/* remove hdlc header and store IPC header */
+static int rx_hdlc_head_check(struct io_device *iod, char *buf, unsigned rest)
+{
+ struct header_data *hdr = &iod->h_data;
+ int head_size = get_header_size(iod);
+ int done_len = 0;
+ int len = 0;
+ struct modem_data *md = (struct modem_data *)\
+ iod->mc->dev->platform_data;
+
+ /* first frame, remove start header 7F */
+ if (!hdr->start) {
+ len = rx_hdlc_head_start_check(buf);
+ if (len < 0) {
+ pr_err("[MODEM_IF] Wrong HDLC start: 0x%x(%s)\n",
+ *buf, iod->name);
+ return len; /*Wrong hdlc start*/
+ }
+
+ pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len,
+ rest, __LINE__);
+
+ /* set the start flag of current packet */
+ hdr->start = HDLC_START;
+ hdr->len = 0;
+
+ buf += len;
+ done_len += len;
+ rest -= len; /* rest, call by value */
+ }
+
+ pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest,
+ __LINE__);
+
+ /* store the IPC header to iod priv */
+ if (hdr->len < head_size) {
+ len = min(rest, head_size - hdr->len);
+ memcpy(hdr->hdr + hdr->len, buf, len);
+
+ /* Skip the dummy byte inserted for 2-byte alignment in header.
+ RAW format header size is 6 bytes. Start + 6 + 1 (skip byte) */
+ if (md->align == 1) {
+ if ((iod->format == IPC_RAW
+ || iod->format == IPC_MULTI_RAW)
+ && (iod->net_typ == CDMA_NETWORK)
+ && !(len & 0x01))
+ len++;
+ }
+ hdr->len += len;
+ done_len += len;
+ }
+
+ pr_debug("[MODEM_IF] check done_len : %d, rest : %d (%d)\n", done_len,
+ rest, __LINE__);
+ return done_len;
+}
+
+/* alloc skb and copy dat to skb */
+static int rx_hdlc_data_check(struct io_device *iod, char *buf, unsigned rest)
+{
+ struct header_data *hdr = &iod->h_data;
+ struct sk_buff *skb = iod->skb_recv;
+ int head_size = get_header_size(iod);
+ int data_size = get_hdlc_size(iod, hdr->hdr) - head_size;
+ int alloc_size = min(data_size, MAX_RXDATA_SIZE);
+ int len;
+ int done_len = 0;
+ int rest_len = data_size - hdr->flag_len;
+
+ /* first payload data - alloc skb */
+ if (!skb) {
+ switch (iod->format) {
+ case IPC_RFS:
+ alloc_size = min(data_size + head_size, \
+ MAX_RXDATA_SIZE);
+ skb = alloc_skb(alloc_size, GFP_ATOMIC);
+ if (unlikely(!skb))
+ return -ENOMEM;
+ /* copy the RFS haeder to skb->data */
+ memcpy(skb_put(skb, head_size), hdr->hdr, head_size);
+ break;
+
+ case IPC_MULTI_RAW:
+ if (data_size > MAX_RXDATA_SIZE) { \
+ pr_err("%s: %s: packet size too large (%d)\n",\
+ __func__, iod->name, data_size);
+ return -EINVAL;
+ }
+
+ if (iod->net_typ == UMTS_NETWORK)
+ skb = alloc_skb(alloc_size, GFP_ATOMIC);
+ else
+ skb = alloc_skb(alloc_size +
+ sizeof(struct ethhdr), GFP_ATOMIC);
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ if (iod->net_typ != UMTS_NETWORK)
+ skb_reserve(skb, sizeof(struct ethhdr));
+ break;
+
+ default:
+ skb = alloc_skb(alloc_size, GFP_ATOMIC);
+ if (unlikely(!skb))
+ return -ENOMEM;
+ break;
+ }
+ iod->skb_recv = skb;
+ }
+
+ while (rest > 0) {
+ len = min(rest, alloc_size - skb->len);
+ len = min(len, rest_len);
+ memcpy(skb_put(skb, len), buf, len);
+ buf += len;
+ done_len += len;
+ hdr->flag_len += len;
+ rest -= len;
+ rest_len -= len;
+
+ if (!rest_len || !rest)
+ break;
+
+ rx_iodev_skb(iod);
+ iod->skb_recv = NULL;
+
+ alloc_size = min(rest_len, MAX_RXDATA_SIZE);
+ skb = alloc_skb(alloc_size, GFP_ATOMIC);
+ if (unlikely(!skb))
+ return -ENOMEM;
+ iod->skb_recv = skb;
+ }
+
+ return done_len;
+}
+
+static int rx_iodev_skb_raw(struct io_device *iod)
+{
+ int err;
+ struct sk_buff *skb = iod->skb_recv;
+ struct net_device *ndev;
+ struct iphdr *ip_header;
+ struct ethhdr *ehdr;
+ const char source[ETH_ALEN] = SOURCE_MAC_ADDR;
+
+ switch (iod->io_typ) {
+ case IODEV_MISC:
+ skb_queue_tail(&iod->sk_rx_q, iod->skb_recv);
+ wake_up(&iod->wq);
+ return 0;
+
+ case IODEV_NET:
+ ndev = iod->ndev;
+ if (!ndev)
+ return NET_RX_DROP;
+
+ skb->dev = ndev;
+ ndev->stats.rx_packets++;
+ ndev->stats.rx_bytes += skb->len;
+
+ /* check the version of IP */
+ ip_header = (struct iphdr *)skb->data;
+ if (ip_header->version == IP6VERSION)
+ skb->protocol = htons(ETH_P_IPV6);
+ else
+ skb->protocol = htons(ETH_P_IP);
+
+ if (iod->net_typ == UMTS_NETWORK) {
+ skb_reset_mac_header(skb);
+ } else {
+ ehdr = (void *)skb_push(skb, sizeof(struct ethhdr));
+ memcpy(ehdr->h_dest, ndev->dev_addr, ETH_ALEN);
+ memcpy(ehdr->h_source, source, ETH_ALEN);
+ ehdr->h_proto = skb->protocol;
+ skb->ip_summed = CHECKSUM_NONE;
+ skb_reset_mac_header(skb);
+
+ skb_pull(skb, sizeof(struct ethhdr));
+ }
+
+ err = netif_rx_ni(skb);
+ if (err != NET_RX_SUCCESS)
+ dev_err(&ndev->dev, "rx error: %d\n", err);
+ return err;
+
+ default:
+ pr_err("[MODEM_IF] wrong io_type : %d\n", iod->io_typ);
+ return -EINVAL;
+ }
+}
+
+static void rx_iodev_work(struct work_struct *work)
+{
+ int ret;
+ struct sk_buff *skb;
+ struct io_device *real_iod;
+ struct io_device *iod = container_of(work, struct io_device,
+ rx_work.work);
+
+ skb = skb_dequeue(&iod->sk_rx_q);
+ while (skb) {
+ real_iod = *((struct io_device **)skb->cb);
+ real_iod->skb_recv = skb;
+
+ ret = rx_iodev_skb_raw(real_iod);
+ if (ret == NET_RX_DROP) {
+ pr_err("[MODEM_IF] %s: queue delayed work!\n",
+ __func__);
+ skb_queue_head(&iod->sk_rx_q, skb);
+ schedule_delayed_work(&iod->rx_work,
+ msecs_to_jiffies(20));
+ break;
+ } else if (ret < 0)
+ dev_kfree_skb_any(skb);
+
+ skb = skb_dequeue(&iod->sk_rx_q);
+ }
+}
+
+
+static int rx_multipdp(struct io_device *iod)
+{
+ u8 ch;
+ struct raw_hdr *raw_header = (struct raw_hdr *)&iod->h_data.hdr;
+ struct io_raw_devices *io_raw_devs =
+ (struct io_raw_devices *)iod->private_data;
+ struct io_device *real_iod;
+
+ ch = raw_header->channel;
+ real_iod = io_raw_devs->raw_devices[ch];
+ if (!real_iod) {
+ pr_err("[MODEM_IF] %s: wrong channel %d\n", __func__, ch);
+ return -1;
+ }
+
+ *((struct io_device **)iod->skb_recv->cb) = real_iod;
+ skb_queue_tail(&iod->sk_rx_q, iod->skb_recv);
+ pr_debug("sk_rx_qlen:%d\n", iod->sk_rx_q.qlen);
+
+ schedule_delayed_work(&iod->rx_work, 0);
+ return 0;
+}
+
+/* de-mux function draft */
+static int rx_iodev_skb(struct io_device *iod)
+{
+ switch (iod->format) {
+ case IPC_MULTI_RAW:
+ return rx_multipdp(iod);
+
+ case IPC_FMT:
+ case IPC_RFS:
+ default:
+ skb_queue_tail(&iod->sk_rx_q, iod->skb_recv);
+
+ pr_debug("[MODEM_IF] wake up fmt,rfs skb\n");
+ wake_up(&iod->wq);
+ return 0;
+ }
+}
+
+static int rx_hdlc_packet(struct io_device *iod, const char *data,
+ unsigned recv_size)
+{
+ unsigned rest = recv_size;
+ char *buf = (char *)data;
+ int err = 0;
+ int len;
+ struct modem_data *md = (struct modem_data *)\
+ iod->mc->dev->platform_data;
+
+
+ if (rest <= 0)
+ goto exit;
+
+ pr_debug("[MODEM_IF] RX_SIZE=%d\n", rest);
+
+ if (iod->h_data.flag_len)
+ goto data_check;
+
+next_frame:
+ err = len = rx_hdlc_head_check(iod, buf, rest);
+ if (err < 0)
+ goto exit; /* buf++; rest--; goto next_frame; */
+ pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest,
+ __LINE__);
+
+ buf += len;
+ rest -= len;
+ if (rest <= 0)
+ goto exit;
+
+data_check:
+ err = len = rx_hdlc_data_check(iod, buf, rest);
+ if (err < 0)
+ goto exit;
+ pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest,
+ __LINE__);
+
+ /* If the lenght of actual data is odd. Skip the dummy bit*/
+ if (md->align == 1) {
+ if ((iod->format == IPC_RAW || iod->format == IPC_MULTI_RAW)
+ && (iod->net_typ == CDMA_NETWORK) && (len & 0x01))
+ len++;
+ }
+ buf += len;
+ rest -= len;
+
+ if (!rest && iod->h_data.flag_len)
+ return 0;
+ else if (rest <= 0)
+ goto exit;
+
+ err = len = rx_hdlc_tail_check(buf);
+ if (err < 0) {
+ pr_err("[MODEM_IF] Wrong HDLC end: 0x%x(%s)\n",
+ *buf, iod->name);
+ goto exit;
+ }
+ pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest,
+ __LINE__);
+
+ /* Skip the dummy byte inserted for 2-byte alignment in header.
+ Ox7E 00.*/
+ if (md->align == 1) {
+ if ((iod->format == IPC_RAW || iod->format == IPC_MULTI_RAW)
+ && (iod->net_typ == CDMA_NETWORK))
+ len++;
+ }
+ buf += len;
+ rest -= len;
+ if (rest < 0)
+ goto exit;
+
+ err = rx_iodev_skb(iod);
+ if (err < 0)
+ goto exit;
+
+ /* initialize header & skb */
+ iod->skb_recv = NULL;
+ memset(&iod->h_data, 0x00, sizeof(struct header_data));
+
+ if (rest)
+ goto next_frame;
+
+exit:
+ /* free buffers. mipi-hsi re-use recv buf */
+ if (rest < 0)
+ err = -ERANGE;
+
+ if (err < 0) {
+ /* clear headers */
+ memset(&iod->h_data, 0x00, sizeof(struct header_data));
+
+ if (iod->skb_recv) {
+ dev_kfree_skb_any(iod->skb_recv);
+ iod->skb_recv = NULL;
+ }
+ }
+
+ return err;
+}
+
+/* called from link device when a packet arrives for this io device */
+static int io_dev_recv_data_from_link_dev(struct io_device *iod,
+ const char *data, unsigned int len)
+{
+ struct sk_buff *skb;
+ int err;
+
+ switch (iod->format) {
+ case IPC_FMT:
+ case IPC_RAW:
+ case IPC_RFS:
+ case IPC_MULTI_RAW:
+ err = rx_hdlc_packet(iod, data, len);
+ if (err < 0)
+ pr_err("[MODEM_IF] fail process hdlc fram\n");
+ return err;
+
+ case IPC_CMD:
+ /* TODO- handle flow control command from CP */
+ return 0;
+
+ case IPC_BOOT:
+ case IPC_RAMDUMP:
+ /* save packet to sk_buff */
+ skb = alloc_skb(len, GFP_ATOMIC);
+ if (!skb) {
+ pr_err("[MODEM_IF] fail alloc skb (%d)\n", __LINE__);
+ return -ENOMEM;
+ }
+
+ pr_debug("[MODEM_IF] boot len : %d\n", len);
+
+ memcpy(skb_put(skb, len), data, len);
+ skb_queue_tail(&iod->sk_rx_q, skb);
+ pr_debug("[MODEM_IF] skb len : %d\n", skb->len);
+
+ wake_up(&iod->wq);
+ return len;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+/* inform the IO device that the modem is now online or offline or
+ * crashing or whatever...
+ */
+static void io_dev_modem_state_changed(struct io_device *iod,
+ enum modem_state state)
+{
+ iod->mc->phone_state = state;
+ pr_info("[MODEM_IF] %s state changed: %s\n", \
+ iod->name, modem_state_name[state]);
+
+ if ((state == STATE_CRASH_EXIT) || (state == STATE_NV_REBUILDING))
+ wake_up(&iod->wq);
+}
+
+static int misc_open(struct inode *inode, struct file *filp)
+{
+ struct io_device *iod = to_io_device(filp->private_data);
+ filp->private_data = (void *)iod;
+
+ if (iod->format != IPC_BOOT && iod->format != IPC_RAMDUMP)
+ pr_info("[MODEM_IF] misc_open : %s\n", iod->name);
+
+ if (iod->link->init_comm)
+ return iod->link->init_comm(iod->link, iod);
+ return 0;
+}
+
+static int misc_release(struct inode *inode, struct file *filp)
+{
+ struct io_device *iod = (struct io_device *)filp->private_data;
+
+ if (iod->format != IPC_BOOT && iod->format != IPC_RAMDUMP)
+ pr_info("[MODEM_IF] misc_release : %s\n", iod->name);
+
+ if (iod->link->terminate_comm)
+ iod->link->terminate_comm(iod->link, iod);
+
+ skb_queue_purge(&iod->sk_rx_q);
+ return 0;
+}
+
+static unsigned int misc_poll(struct file *filp,
+ struct poll_table_struct *wait)
+{
+ struct io_device *iod = (struct io_device *)filp->private_data;
+
+ poll_wait(filp, &iod->wq, wait);
+
+ if ((!skb_queue_empty(&iod->sk_rx_q))
+ && (iod->mc->phone_state != STATE_OFFLINE))
+ return POLLIN | POLLRDNORM;
+ else if ((iod->mc->phone_state == STATE_CRASH_EXIT) ||
+ (iod->mc->phone_state == STATE_NV_REBUILDING))
+ return POLLHUP;
+ else
+ return 0;
+}
+
+static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long _arg)
+{
+ int p_state;
+ struct io_device *iod = (struct io_device *)filp->private_data;
+
+ pr_debug("[MODEM_IF] misc_ioctl : 0x%x\n", cmd);
+
+ switch (cmd) {
+ case IOCTL_MODEM_ON:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_ON\n");
+ return iod->mc->ops.modem_on(iod->mc);
+
+ case IOCTL_MODEM_OFF:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_OFF\n");
+ return iod->mc->ops.modem_off(iod->mc);
+
+ case IOCTL_MODEM_RESET:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_RESET\n");
+ return iod->mc->ops.modem_reset(iod->mc);
+
+ case IOCTL_MODEM_FORCE_CRASH_EXIT:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_FORCE_CRASH_EXIT\n");
+ return iod->mc->ops.modem_force_crash_exit(iod->mc);
+
+ case IOCTL_MODEM_DUMP_RESET:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_FORCE_CRASH_EXIT\n");
+ return iod->mc->ops.modem_dump_reset(iod->mc);
+
+ case IOCTL_MODEM_BOOT_ON:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_BOOT_ON\n");
+ return iod->mc->ops.modem_boot_on(iod->mc);
+
+ case IOCTL_MODEM_BOOT_OFF:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_BOOT_OFF\n");
+ return iod->mc->ops.modem_boot_off(iod->mc);
+
+ /* TODO - will remove this command after ril updated */
+ case IOCTL_MODEM_START:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_START\n");
+ return 0;
+
+ case IOCTL_MODEM_STATUS:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_STATUS\n");
+ p_state = iod->mc->phone_state;
+ if (p_state == STATE_NV_REBUILDING) {
+ pr_info("[MODEM_IF] nv rebuild state : %d\n", p_state);
+ iod->mc->phone_state = STATE_ONLINE;
+ }
+ return p_state;
+
+ case IOCTL_MODEM_DUMP_START:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_DUMP_START\n");
+ return iod->link->dump_start(iod->link, iod);
+
+ case IOCTL_MODEM_DUMP_UPDATE:
+ pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_DUMP_UPDATE\n");
+ return iod->link->dump_update(iod->link, iod, _arg);
+
+ case IOCTL_MODEM_GOTA_START:
+ pr_debug("[GOTA] misc_ioctl : IOCTL_MODEM_GOTA_START\n");
+ return iod->link->gota_start(iod->link, iod);
+
+ case IOCTL_MODEM_FW_UPDATE:
+ pr_debug("[GOTA] misc_ioctl : IOCTL_MODEM_FW_UPDATE\n");
+ return iod->link->modem_update(iod->link, iod, _arg);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t misc_write(struct file *filp, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ struct io_device *iod = (struct io_device *)filp->private_data;
+ int frame_len = 0;
+ char frame_header_buf[sizeof(struct raw_hdr)];
+ struct sk_buff *skb;
+
+ /* TODO - check here flow control for only raw data */
+
+ if (iod->format == IPC_BOOT || iod->format == IPC_RAMDUMP)
+ frame_len = count + get_header_size(iod);
+ else
+ frame_len = count + SIZE_OF_HDLC_START + get_header_size(iod)
+ + SIZE_OF_HDLC_END;
+
+ skb = alloc_skb(frame_len, GFP_KERNEL);
+ if (!skb) {
+ pr_err("[MODEM_IF] fail alloc skb (%d)\n", __LINE__);
+ return -ENOMEM;
+ }
+
+ switch (iod->format) {
+ case IPC_BOOT:
+ case IPC_RAMDUMP:
+ if (copy_from_user(skb_put(skb, count), buf, count) != 0) {
+ dev_kfree_skb_any(skb);
+ return -EFAULT;
+ }
+ break;
+
+ case IPC_RFS:
+ memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start,
+ SIZE_OF_HDLC_START);
+ if (copy_from_user(skb_put(skb, count), buf, count) != 0) {
+ dev_kfree_skb_any(skb);
+ return -EFAULT;
+ }
+ memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end,
+ SIZE_OF_HDLC_END);
+ break;
+
+ default:
+ memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start,
+ SIZE_OF_HDLC_START);
+ memcpy(skb_put(skb, get_header_size(iod)),
+ get_header(iod, count, frame_header_buf),
+ get_header_size(iod));
+ if (copy_from_user(skb_put(skb, count), buf, count) != 0) {
+ dev_kfree_skb_any(skb);
+ return -EFAULT;
+ }
+ memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end,
+ SIZE_OF_HDLC_END);
+ break;
+ }
+
+ /* send data with sk_buff, link device will put sk_buff
+ * into the specific sk_buff_q and run work-q to send data
+ */
+ return iod->link->send(iod->link, iod, skb);
+}
+
+static ssize_t misc_read(struct file *filp, char *buf, size_t count,
+ loff_t *f_pos)
+{
+ struct io_device *iod = (struct io_device *)filp->private_data;
+ struct sk_buff *skb;
+ int pktsize = 0;
+
+ skb = skb_dequeue(&iod->sk_rx_q);
+ if (!skb) {
+ printk_ratelimited(KERN_ERR "[MODEM_IF] no data from sk_rx_q, "
+ "modem_state : %s(%s)\n",
+ modem_state_name[iod->mc->phone_state], iod->name);
+ return 0;
+ }
+
+ if (skb->len > count) {
+ pr_err("[MODEM_IF] skb len is too big = %d,%d!(%d)\n",
+ count, skb->len, __LINE__);
+ dev_kfree_skb_any(skb);
+ return -EIO;
+ }
+ pr_debug("[MODEM_IF] skb len : %d\n", skb->len);
+
+ pktsize = skb->len;
+ if (copy_to_user(buf, skb->data, pktsize) != 0)
+ return -EIO;
+ dev_kfree_skb_any(skb);
+
+ pr_debug("[MODEM_IF] copy to user : %d\n", pktsize);
+
+ return pktsize;
+}
+
+static const struct file_operations misc_io_fops = {
+ .owner = THIS_MODULE,
+ .open = misc_open,
+ .release = misc_release,
+ .poll = misc_poll,
+ .unlocked_ioctl = misc_ioctl,
+ .write = misc_write,
+ .read = misc_read,
+};
+
+static int vnet_open(struct net_device *ndev)
+{
+ netif_start_queue(ndev);
+ return 0;
+}
+
+static int vnet_stop(struct net_device *ndev)
+{
+ netif_stop_queue(ndev);
+ return 0;
+}
+
+static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ int ret;
+ struct raw_hdr hd;
+ struct sk_buff *skb_new;
+ struct vnet *vnet = netdev_priv(ndev);
+ struct io_device *iod = vnet->iod;
+
+ /* umts doesn't need to discard ethernet header */
+ if (iod->net_typ != UMTS_NETWORK) {
+ if (iod->id >= PSD_DATA_CHID_BEGIN &&
+ iod->id <= PSD_DATA_CHID_END)
+ skb_pull(skb, sizeof(struct ethhdr));
+ }
+
+ hd.len = skb->len + sizeof(hd);
+ hd.control = 0;
+ hd.channel = iod->id & 0x1F;
+
+ skb_new = skb_copy_expand(skb, sizeof(hd) + sizeof(hdlc_start),
+ sizeof(hdlc_end), GFP_ATOMIC);
+ if (!skb_new) {
+ dev_kfree_skb_any(skb);
+ return -ENOMEM;
+ }
+
+ memcpy(skb_push(skb_new, sizeof(hd)), &hd, sizeof(hd));
+ memcpy(skb_push(skb_new, sizeof(hdlc_start)), hdlc_start,
+ sizeof(hdlc_start));
+ memcpy(skb_put(skb_new, sizeof(hdlc_end)), hdlc_end, sizeof(hdlc_end));
+
+ ret = iod->link->send(iod->link, iod, skb_new);
+ if (ret < 0) {
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_BUSY;
+ }
+
+ ndev->stats.tx_packets++;
+ ndev->stats.tx_bytes += skb->len;
+ dev_kfree_skb_any(skb);
+
+ return NETDEV_TX_OK;
+}
+
+static struct net_device_ops vnet_ops = {
+ .ndo_open = vnet_open,
+ .ndo_stop = vnet_stop,
+ .ndo_start_xmit = vnet_xmit,
+};
+
+static void vnet_setup(struct net_device *ndev)
+{
+ ndev->netdev_ops = &vnet_ops;
+ ndev->type = ARPHRD_PPP;
+ ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+ ndev->addr_len = 0;
+ ndev->hard_header_len = 0;
+ ndev->tx_queue_len = 1000;
+ ndev->mtu = ETH_DATA_LEN;
+ ndev->watchdog_timeo = 5 * HZ;
+}
+
+static void vnet_setup_ether(struct net_device *ndev)
+{
+ ndev->netdev_ops = &vnet_ops;
+ ndev->type = ARPHRD_ETHER;
+ ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST | IFF_SLAVE;
+ ndev->addr_len = ETH_ALEN;
+ random_ether_addr(ndev->dev_addr);
+ ndev->hard_header_len = 0;
+ ndev->tx_queue_len = 1000;
+ ndev->mtu = ETH_DATA_LEN;
+ ndev->watchdog_timeo = 5 * HZ;
+}
+
+int init_io_device(struct io_device *iod)
+{
+ int ret = 0;
+ struct vnet *vnet;
+
+ /* get modem state from modem control device */
+ iod->modem_state_changed = io_dev_modem_state_changed;
+ /* get data from link device */
+ iod->recv = io_dev_recv_data_from_link_dev;
+
+ INIT_LIST_HEAD(&iod->list);
+
+ /* register misc or net drv */
+ switch (iod->io_typ) {
+ case IODEV_MISC:
+ init_waitqueue_head(&iod->wq);
+ skb_queue_head_init(&iod->sk_rx_q);
+ INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work);
+
+ iod->miscdev.minor = MISC_DYNAMIC_MINOR;
+ iod->miscdev.name = iod->name;
+ iod->miscdev.fops = &misc_io_fops;
+
+ ret = misc_register(&iod->miscdev);
+ if (ret)
+ pr_err("failed to register misc io device : %s\n",
+ iod->name);
+
+ break;
+
+ case IODEV_NET:
+ if (iod->net_typ == UMTS_NETWORK)
+ iod->ndev = alloc_netdev(0, iod->name, vnet_setup);
+ else
+ iod->ndev = alloc_netdev(0, iod->name,
+ vnet_setup_ether);
+ if (!iod->ndev) {
+ pr_err("failed to alloc netdev\n");
+ return -ENOMEM;
+ }
+
+ ret = register_netdev(iod->ndev);
+ if (ret)
+ free_netdev(iod->ndev);
+
+ pr_debug("%s: %d(iod:0x%p)\n", __func__, __LINE__, iod);
+ vnet = netdev_priv(iod->ndev);
+ pr_debug("%s: %d(vnet:0x%p)\n", __func__, __LINE__, vnet);
+ vnet->iod = iod;
+
+ break;
+
+ case IODEV_DUMMY:
+ skb_queue_head_init(&iod->sk_rx_q);
+ INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work);
+
+ break;
+
+ default:
+ pr_err("wrong io_type : %d\n", iod->io_typ);
+ return -EINVAL;
+ }
+
+ pr_debug("[MODEM_IF] %s(%d) : init_io_device() done : %d\n",
+ iod->name, iod->io_typ, ret);
+ return ret;
+}
diff --git a/drivers/misc/modem_if_na/modem_link_device_dpram.c b/drivers/misc/modem_if_na/modem_link_device_dpram.c
new file mode 100644
index 0000000..bf4f61a
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_link_device_dpram.c
@@ -0,0 +1,1504 @@
+/*
+ * Copyright (C) 2011 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/wakelock.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/vmalloc.h>
+#include <linux/if_arp.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/modem_na.h>
+#include <linux/io.h>
+#include "modem_prj.h"
+#include "modem_link_device_dpram.h"
+
+/* interrupt masks.*/
+#define INT_MASK_VALID 0x0080
+#define INT_MASK_CMD 0x0040
+#define INT_MASK_REQ_ACK_F 0x0020
+#define INT_MASK_REQ_ACK_R 0x0010
+#define INT_MASK_RES_ACK_F 0x0008
+#define INT_MASK_RES_ACK_R 0x0004
+#define INT_MASK_SEND_F 0x0002
+#define INT_MASK_SEND_R 0x0001
+#define INT_VALID(x) ((x) & INT_MASK_VALID)
+#define INT_CMD_VALID(x) ((x) & INT_MASK_CMD)
+#define INT_NON_CMD(x) (INT_MASK_VALID | (x))
+#define INT_CMD(x) (INT_MASK_VALID | INT_MASK_CMD | (x))
+
+#define INT_CMD_MASK(x) ((x) & 0xF)
+#define INT_CMD_INIT_START 0x1
+#define INT_CMD_INIT_END 0x2
+#define INT_CMD_REQ_ACTIVE 0x3
+#define INT_CMD_RES_ACTIVE 0x4
+#define INT_CMD_REQ_TIME_SYNC 0x5
+#define INT_CMD_PHONE_START 0x8
+#define INT_CMD_ERR_DISPLAY 0x9
+#define INT_CMD_PHONE_DEEP_SLEEP 0xA
+#define INT_CMD_NV_REBUILDING 0xB
+#define INT_CMD_EMER_DOWN 0xC
+#define INT_CMD_PIF_INIT_DONE 0xD
+#define INT_CMD_SILENT_NV_REBUILDING 0xE
+#define INT_CMD_NORMAL_POWER_OFF 0xF
+
+/* special interrupt cmd indicating modem boot failure. */
+#define INT_POWERSAFE_FAIL 0xDEAD
+
+#define GOTA_CMD_VALID(x) (((x) & 0xA000) == 0xA000)
+#define GOTA_RESULT_FAIL 0x2
+#define GOTA_RESULT_SUCCESS 0x1
+#define GOTA_CMD_MASK(x) (((x) >> 8) & 0xF)
+#define GOTA_CMD_RECEIVE_READY 0x1
+#define GOTA_CMD_DOWNLOAD_START_REQ 0x2
+#define GOTA_CMD_DOWNLOAD_START_RESP 0x3
+#define GOTA_CMD_IMAGE_SEND_REQ 0x4
+#define GOTA_CMD_IMAGE_SEND_RESP 0x5
+#define GOTA_CMD_SEND_DONE_REQ 0x6
+#define GOTA_CMD_SEND_DONE_RESP 0x7
+#define GOTA_CMD_STATUS_UPDATE 0x8
+#define GOTA_CMD_UPDATE_DONE 0x9
+#define GOTA_CMD_EFS_CLEAR_RESP 0xB
+#define GOTA_CMD_ALARM_BOOT_OK 0xC
+#define GOTA_CMD_ALARM_BOOT_FAIL 0xD
+
+#define CMD_DL_START_REQ 0x9200
+#define CMD_IMG_SEND_REQ 0x9400
+#define CMD_DL_SEND_DONE_REQ 0x9600
+#define CMD_UL_RECEIVE_RESP 0x9601
+#define CMD_UL_RECEIVE_DONE_RESP 0x9801
+
+#define START_INDEX 0x7F
+#define END_INDEX 0x7E
+
+#define DP_MAGIC_CODE 0xAA
+#define DP_MAGIC_DMDL 0x4445444C
+#define DP_MAGIC_UMDL 0x4445444D
+#define DP_DPRAM_SIZE 0x4000
+#define DP_DEFAULT_WRITE_LEN 8168
+#define DP_DEFAULT_DUMP_LEN 16366
+#ifdef CONFIG_INTERNAL_MODEM_IF
+#define DP_DUMP_HEADER_SIZE 8
+#else
+#define DP_DUMP_HEADER_SIZE 7
+#endif
+#define GOTA_TIMEOUT (50 * HZ)
+#define GOTA_SEND_TIMEOUT (200 * HZ)
+#define DUMP_TIMEOUT (30 * HZ)
+#define DUMP_START_TIMEOUT (100 * HZ)
+#define IDPRAM_PHY_START 0x13A00000
+#define IDPRAM_SIZE 0x4000
+
+
+
+static int
+dpram_download(struct dpram_link_device *dpld, const char *buf, int len);
+static int
+dpram_upload(struct dpram_link_device *dpld, struct dpram_firmware *uploaddata);
+static inline void
+dpram_writeh(u16 value, void __iomem *p_dest);
+static void
+dpram_clear(struct dpram_link_device *dpld);
+static struct io_device *
+dpram_find_iod(struct dpram_link_device *dpld, int id);
+static void
+dpram_write_command(struct dpram_link_device *dpld, u16 cmd);
+static inline int
+dpram_readh(void __iomem *p_dest);
+
+#ifdef CONFIG_INTERNAL_MODEM_IF
+
+#define INT_MASK_CMD_PDA_SLEEP 0x0C
+#define INT_MASK_CMD_DPRAM_DOWN 0x0C
+#define INT_MASK_CMD_PDA_WAKEUP 0x0A
+#define INT_MASK_CMD_CP_WAKEUP_START 0x0A
+#define INT_MASK_CMD_DPRAM_DOWN_NACK 0x07
+
+#include <plat/gpio-cfg.h>
+#include <linux/suspend.h>
+
+struct idpram_link_pm_data *pm;
+
+void idpram_magickey_init(struct idpram_link_pm_data *pm_data)
+{
+ u16 acc_code = 0x01;
+
+ dpram_writeh(DP_MAGIC_CODE, &pm_data->dpld->dpram->magic);
+ dpram_writeh(acc_code, &pm_data->dpld->dpram->enable);
+}
+
+int idpram_get_write_lock(struct idpram_link_pm_data *pm_data)
+{
+ return atomic_read(&pm_data->write_lock);
+}
+
+static int idpram_write_lock(struct idpram_link_pm_data *pm_data, int lock)
+{
+ int lock_value = 0;
+
+ mif_info("MIF: idpram write_lock(%d)\n", lock);
+
+ switch (lock) {
+ case 0: /* unlock */
+ if (atomic_read(&pm_data->write_lock))
+ lock_value = atomic_dec_return(&pm_data->write_lock);
+ if (lock_value)
+ mif_err("MIF: ipdram write unlock but lock value=%d\n",
+ lock_value);
+ break;
+ case 1: /* lock */
+ if (!atomic_read(&pm_data->write_lock))
+ lock_value = atomic_inc_return(&pm_data->write_lock);
+ if (lock_value != 1)
+ mif_err("MIF: ipdram write lock but lock value=%d\n",
+ lock_value);
+ break;
+ }
+ return 0;
+}
+
+static int idpram_resume_init(struct idpram_link_pm_data *pm_data)
+{
+
+ pm_data->pm_states = IDPRAM_PM_RESUME_START;
+ pm_data->last_pm_mailbox = 0;
+
+ dpram_clear(pm_data->dpld);
+ idpram_magickey_init(pm_data);
+
+ /* Initialize the dpram controller */
+ pm_data->mdata->sfr_init();
+
+ /*re-initialize internal dpram gpios */
+ s3c_gpio_cfgpin(pm_data->mdata->gpio_mbx_intr, S3C_GPIO_SFN(0x2));
+
+ idpram_write_lock(pm_data, 0);
+ return 0;
+}
+
+
+void idpram_timeout_handler(struct idpram_link_pm_data *pm_data)
+{
+ struct io_device *iod = dpram_find_iod(pm_data->dpld, FMT_IDX);
+
+ mif_info("MIF: <%s>", __func__);
+
+ if (!gpio_get_value(pm_data->mdata->gpio_phone_active)) {
+ mif_err("MIF: <%s:%s> (Crash silent Reset)\n",
+ __func__, pm_data->dpld->ld.name);
+
+ if (iod && iod->modem_state_changed)
+ iod->modem_state_changed(iod, STATE_CRASH_EXIT);
+ }
+}
+
+static int idpram_resume_check(struct idpram_link_pm_data *pm_data)
+{
+ /* check last pm mailbox */
+ mif_info("MIF: idpram %s, last_pm_mailbox=%x\n", __func__,
+ pm_data->last_pm_mailbox);
+
+ if (pm_data->last_pm_mailbox == INT_CMD(INT_MASK_CMD_PDA_WAKEUP)) {
+ pm_data->last_pm_mailbox = 0;
+ return 0;
+ }
+
+ dpram_write_command(pm_data->dpld, INT_CMD(INT_MASK_CMD_PDA_WAKEUP));
+ mif_info("MIF: idpram sent PDA_WAKEUP Mailbox(0x%x)\n",
+ INT_CMD(INT_MASK_CMD_PDA_WAKEUP));
+
+ return -1;
+}
+
+static void idpram_resume_retry(struct work_struct *work)
+{
+ struct idpram_link_pm_data *pm_data =
+ container_of(work, struct idpram_link_pm_data, \
+ resume_work.work);
+
+ mif_debug("MIF: %s\n", __func__);
+
+ if (!idpram_resume_check(pm_data)) {
+ mif_info("MIF: idpram resume ok\n");
+ idpram_write_lock(pm_data, 0);
+ wake_lock_timeout(&pm_data->hold_wlock, msecs_to_jiffies(20));
+ return;
+ }
+ if (pm_data->resume_retry--) {
+ schedule_delayed_work(&pm_data->resume_work, \
+ msecs_to_jiffies(200));
+ wake_lock_timeout(&pm_data->hold_wlock, msecs_to_jiffies(260));
+ } else {
+ mif_info("MIF: idpram resume T-I-M-E-O-UT\n");
+ idpram_timeout_handler(pm_data);
+ /* hold wakelock until uevnet sent to rild */
+ wake_lock_timeout(&pm_data->hold_wlock, HZ*7);
+ idpram_write_lock(pm_data, 0);
+ }
+}
+
+
+static irqreturn_t link_ap_wakeup_handler(int irq, void *data)
+{
+ struct idpram_link_pm_data *pm_data = data;
+
+ mif_info("MIF: <%s> 5 seconds.\n", __func__);
+ wake_lock_timeout(&pm_data->host_wakeup_wlock, 5*HZ);
+
+ return IRQ_HANDLED;
+}
+
+static int idpram_pm_suspend(struct device *dev)
+{
+ struct idpram_link_pm_data *pm_data = pm;
+
+ pm_data->pm_states = IDPRAM_PM_SUSPEND_START;
+ gpio_set_value(pm_data->mdata->gpio_pda_active, 0);
+
+ mif_debug("MIF: <%s>\n", __func__);
+
+ return 0;
+}
+static int idpram_pm_resume(struct device *dev)
+{
+ struct idpram_link_pm_data *pm_data = pm;
+
+ idpram_resume_init(pm_data);
+ gpio_set_value(pm_data->mdata->gpio_pda_active, 1);
+ mif_debug("MIF: <%s>\n", __func__);
+ return 0;
+}
+
+static int __devinit idpram_pm_probe(struct platform_device *pdev)
+{
+ return 0;
+}
+static void idpram_pm_shutdown(struct platform_device *pdev)
+{
+}
+
+static const struct dev_pm_ops idpram_pm_ops = {
+ .suspend = idpram_pm_suspend,
+ .resume = idpram_pm_resume,
+};
+
+static struct platform_driver idpram_pm_driver = {
+ .probe = idpram_pm_probe,
+ .shutdown = idpram_pm_shutdown,
+ .driver = {
+ .name = "idparam_pm",
+ .pm = &idpram_pm_ops,
+ },
+};
+
+static void idpram_powerup_start(struct idpram_link_pm_data *pm_data)
+{
+ pm_data->last_pm_mailbox = INT_CMD(INT_MASK_CMD_PDA_WAKEUP);
+ pm_data->pm_states = IDPRAM_PM_ACTIVE;
+ mif_debug("MIF: <%s>\n", __func__);
+}
+
+static void idpram_power_down_nack(struct idpram_link_pm_data *pm_data)
+{
+ pm_data->last_pm_mailbox = INT_CMD(INT_MASK_CMD_DPRAM_DOWN_NACK);
+ complete(&pm_data->idpram_down);
+ mif_debug("MIF: <%s>\n", __func__);
+}
+
+static void idpram_power_down(struct idpram_link_pm_data *pm_data)
+{
+ pm_data->last_pm_mailbox = INT_CMD(INT_MASK_CMD_DPRAM_DOWN);
+ complete(&pm_data->idpram_down);
+ mif_debug("MIF: <%s>\n", __func__);
+}
+
+static int idpram_post_resume(struct idpram_link_pm_data *pm_data)
+{
+ int gpio_val = 0;
+
+ mif_info("MIF: idpram %s\n", __func__);
+
+ switch (pm_data->pm_states) {
+ /* schedule_work */
+ case IDPRAM_PM_DPRAM_POWER_DOWN:
+ gpio_set_value(pm_data->mdata->gpio_pda_active, 0);
+ mif_info("MIF: idpram PDA_ACTIVE LOW\n");
+
+ msleep(50);
+
+ idpram_resume_init(pm_data);
+
+ msleep(50);
+
+ gpio_set_value(pm_data->mdata->gpio_pda_active, 1);
+
+ msleep(20);
+
+ gpio_val = gpio_get_value(pm_data->mdata->gpio_pda_active);
+ mif_info("MIF: idpram PDA_ACTIVE (%d)\n", gpio_val);
+
+ if (gpio_val == 0) {
+ gpio_set_value(pm_data->mdata->gpio_pda_active, 1);
+ mif_info("MIF: idpram PDA_ACTIVE set again.\n");
+ }
+ break;
+
+ case IDPRAM_PM_RESUME_START:
+ break;
+
+ case IDPRAM_PM_SUSPEND_PREPARE:
+ break;
+ }
+ return 0;
+}
+
+
+static int idpram_pre_suspend(struct idpram_link_pm_data *pm_data)
+{
+ int timeout_ret = 0;
+ int suspend_retry = 2;
+ u16 intr_out = INT_CMD(INT_MASK_CMD_PDA_SLEEP);
+
+ pm_data->pm_states = IDPRAM_PM_SUSPEND_PREPARE;
+ pm_data->last_pm_mailbox = 0;
+ idpram_write_lock(pm_data, 1);
+
+ gpio_set_value(pm_data->mdata->gpio_mbx_intr, 1);
+
+ /* prevent PDA_ACTIVE ststus is low */
+ gpio_set_value(pm_data->mdata->gpio_pda_active, 1);
+
+ if (!atomic_read(&pm_data->read_lock)) {
+ do {
+ init_completion(&pm_data->idpram_down);
+ dpram_write_command(pm_data->dpld, intr_out);
+ mif_err("MIF: idpram sent PDA_SLEEP Mailbox(0x%X)\n",
+ intr_out);
+ timeout_ret =
+ wait_for_completion_timeout(&pm_data->idpram_down,
+ (HZ/5));
+ mif_err("MIF: suspend_enter cnt = %d\n",
+ suspend_retry);
+ } while (!timeout_ret && suspend_retry--);
+
+ switch (pm_data->last_pm_mailbox) {
+ case INT_CMD(INT_MASK_CMD_DPRAM_DOWN):
+ break;
+
+ /* if nack or other interrup, hold wakelock for DPM resume*/
+ case INT_CMD(INT_MASK_CMD_DPRAM_DOWN_NACK):
+ mif_err("MIF: idpram dpram down get NACK\n");
+
+ default:
+ mif_err("MIF: CP dpram Down not ready! intr=0x%X\n",
+ dpram_readh(&pm_data->dpld->dpram->mbx_cp2ap));
+ wake_lock_timeout(&pm_data->hold_wlock,
+ msecs_to_jiffies(500));
+ idpram_write_lock(pm_data, 0);
+ return 0;
+ }
+ /*
+ * Because, if dpram was powered down, cp dpram random intr was
+ * ocurred. so, fixed by muxing cp dpram intr pin to GPIO output
+ * high,..
+ */
+ gpio_set_value(pm_data->mdata->gpio_mbx_intr, 1);
+ s3c_gpio_cfgpin(pm_data->mdata->gpio_mbx_intr, S3C_GPIO_OUTPUT);
+ pm_data->pm_states = IDPRAM_PM_DPRAM_POWER_DOWN;
+
+ return 0;
+ } else {
+ mif_err("MIF: idpram hold read_lock\n");
+ return -EBUSY;
+ }
+}
+
+static int idpram_notifier_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ int err;
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ mif_debug("MIF: PM_SUSPEND_PREPARE+\n");
+ err = idpram_pre_suspend(pm);
+ if (err)
+ mif_err("MIF: pre-suspend err\n");
+ break;
+
+ case PM_POST_SUSPEND:
+ mif_debug("MIF: PM_POST_SUSPEND+\n");
+ err = idpram_post_resume(pm);
+ if (err)
+ mif_err("MIF: pre-suspend err\n");
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block idpram_link_pm_notifier = {
+ .notifier_call = idpram_notifier_event,
+};
+
+static int idpram_link_pm_init
+(
+struct dpram_link_device *idpram_ld,
+struct platform_device *pdev
+)
+{
+ int r = 0;
+ unsigned irq = 0;
+ struct modem_data *pdata =
+ (struct modem_data *)pdev->dev.platform_data;
+
+ mif_info("MIF: <%s>\n", __func__);
+
+ pm = kzalloc(sizeof(struct idpram_link_pm_data), GFP_KERNEL);
+ if (!pm) {
+ mif_err("MIF: %s: link_pm_data is NULL\n", __func__);
+ return -ENOMEM;
+ }
+
+ pm->mdata = pdata;
+ pm->dpld = idpram_ld;
+ idpram_ld->link_pm_data = pm;
+
+ /*for pm notifire*/
+ register_pm_notifier(&idpram_link_pm_notifier);
+
+
+ init_completion(&pm->idpram_down);
+ wake_lock_init(&pm->host_wakeup_wlock,
+ WAKE_LOCK_SUSPEND,
+ "HOST_WAKEUP_WLOCK");
+ wake_lock_init(&pm->rd_wlock, WAKE_LOCK_SUSPEND, "dpram_pwrdn");
+ wake_lock_init(&pm->hold_wlock, WAKE_LOCK_SUSPEND, "dpram_hold");
+ wake_lock_init(&pm->wakeup_wlock, WAKE_LOCK_SUSPEND, "dpram_wakeup");
+ atomic_set(&pm->read_lock, 0);
+ atomic_set(&pm->write_lock, 0);
+ INIT_DELAYED_WORK(&pm->resume_work, idpram_resume_retry);
+
+ r = platform_driver_register(&idpram_pm_driver);
+ if (r) {
+ mif_err("MIF: wakelocks_init: platform_driver_register failed\n");
+ goto err_platform_driver_register;
+ }
+
+ irq = gpio_to_irq(pdata->gpio_ap_wakeup);
+ r = request_irq(irq, link_ap_wakeup_handler,
+ IRQF_TRIGGER_RISING,
+ "idpram_host_wakeup", (void *)pm);
+
+ mif_info("MIF: <%s> DPRAM IRQ# = %d, %d\n", __func__,
+ irq,
+ pm->mdata->gpio_ap_wakeup);
+
+ if (r) {
+ mif_err("MIF: %s:fail to request irq(%d) host_wake_irq\n",
+ __func__, r);
+ goto err_request_irq;
+ }
+
+ r = enable_irq_wake(irq);
+ if (r) {
+ mif_err("MIF: %s: failed to enable_irq_wake:%d host_wake_irq\n",
+ __func__, r);
+ goto err_set_wake_irq;
+ }
+
+ mif_info("MIF: <%s> END\n", __func__);
+ return 0;
+
+err_set_wake_irq:
+ free_irq(irq, (void *)pm);
+err_request_irq:
+ platform_driver_unregister(&idpram_pm_driver);
+err_platform_driver_register:
+ kfree(pm);
+ return r;
+}
+#endif /*CONFIG_INTERNAL_MODEM_IF*/
+
+
+
+static inline int dpram_readh(void __iomem *p_dest)
+{
+ unsigned long dest = (unsigned long)p_dest;
+ return ioread16(dest);
+}
+
+static inline void dpram_writew(u32 value, void __iomem *p_dest)
+{
+ unsigned long dest = (unsigned long)p_dest;
+ iowrite32(value, dest);
+}
+
+static inline void dpram_writeh(u16 value, void __iomem *p_dest)
+{
+ unsigned long dest = (unsigned long)p_dest;
+ iowrite16(value, dest);
+}
+
+static inline void dpram_writeb(u8 value, void __iomem *p_dest)
+{
+ unsigned long dest = (unsigned long)p_dest;
+ iowrite8(value, dest);
+}
+
+
+static void dpram_write_command(struct dpram_link_device *dpld, u16 cmd)
+{
+ dpram_writeh(cmd, &dpld->dpram->mbx_ap2cp);
+}
+
+static void dpram_clear_interrupt(struct dpram_link_device *dpld)
+{
+ dpram_writeh(0, &dpld->dpram->mbx_cp2ap);
+}
+
+static void dpram_drop_data(struct dpram_device *device, u16 head)
+{
+ dpram_writeh(head, &device->in->tail);
+}
+
+static void dpram_zero_circ(struct dpram_circ *circ)
+{
+ dpram_writeh(0, &circ->head);
+ dpram_writeh(0, &circ->tail);
+}
+
+static void dpram_clear(struct dpram_link_device *dpld)
+{
+ dpram_zero_circ(&dpld->dpram->fmt_out);
+ dpram_zero_circ(&dpld->dpram->raw_out);
+ dpram_zero_circ(&dpld->dpram->fmt_in);
+ dpram_zero_circ(&dpld->dpram->raw_in);
+}
+
+static bool dpram_circ_valid(int size, u16 head, u16 tail)
+{
+ if (head >= size) {
+ mif_err("MIF: head(%d) >= size(%d)\n", head, size);
+ return false;
+ }
+ if (tail >= size) {
+ mif_err("MIF: tail(%d) >= size(%d)\n", tail, size);
+ return false;
+ }
+ return true;
+}
+
+static int dpram_init_and_report(struct dpram_link_device *dpld)
+{
+ const u16 init_end = INT_CMD(INT_CMD_INIT_END);
+ u16 magic;
+ u16 enable;
+
+ dpram_writeh(0, &dpld->dpram->enable);
+ dpram_clear(dpld);
+ dpram_writeh(DP_MAGIC_CODE, &dpld->dpram->magic);
+ dpram_writeh(1, &dpld->dpram->enable);
+
+ /* Send init end code to modem */
+ dpram_write_command(dpld, init_end);
+
+ magic = dpram_readh(&dpld->dpram->magic);
+ if (magic != DP_MAGIC_CODE) {
+ mif_err("MIF:: %s: Failed to check magic\n", __func__);
+ return -1;
+ }
+
+ enable = dpram_readh(&dpld->dpram->enable);
+ if (!enable) {
+ mif_err("MIF:: %s: DPRAM enable failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct io_device *dpram_find_iod(struct dpram_link_device *dpld, int id)
+{
+ struct io_device *iod;
+
+ list_for_each_entry(iod, &dpld->list_of_io_devices, list) {
+ if ((id == FMT_IDX && iod->format == IPC_FMT) ||
+ (id == RAW_IDX && iod->format == IPC_MULTI_RAW))
+ return iod;
+ }
+
+ return NULL;
+}
+
+static void cmd_req_active_handler(struct dpram_link_device *dpld)
+{
+ dpram_write_command(dpld, INT_CMD(INT_CMD_RES_ACTIVE));
+}
+
+static void cmd_error_display_handler(struct dpram_link_device *dpld)
+{
+ struct io_device *iod = dpram_find_iod(dpld, FMT_IDX);
+
+ mif_info("MIF: Received 0xc9 from modem (CP Crash)\n");
+ mif_info("MIF: %s\n", dpld->dpram->fmt_in_buff);
+
+ if (iod && iod->modem_state_changed)
+ iod->modem_state_changed(iod, STATE_CRASH_EXIT);
+}
+
+static void cmd_phone_start_handler(struct dpram_link_device *dpld)
+{
+ mif_debug("MIF: Received 0xc8 from modem (Boot OK)\n");
+ complete_all(&dpld->dpram_init_cmd);
+ dpram_init_and_report(dpld);
+}
+
+static void cmd_nv_rebuild_handler(struct dpram_link_device *dpld)
+{
+ struct io_device *iod = dpram_find_iod(dpld, FMT_IDX);
+
+ mif_info("MIF: Received nv rebuilding from modem\n");
+ mif_info("MIF: %s\n", dpld->dpram->fmt_in_buff);
+
+ if (iod && iod->modem_state_changed)
+ iod->modem_state_changed(iod, STATE_NV_REBUILDING);
+}
+
+
+static void command_handler(struct dpram_link_device *dpld, u16 cmd)
+{
+ mif_debug("MIF: %s: %x\n", __func__, cmd);
+
+ switch (INT_CMD_MASK(cmd)) {
+ case INT_CMD_REQ_ACTIVE:
+ cmd_req_active_handler(dpld);
+ break;
+
+ case INT_CMD_ERR_DISPLAY:
+ cmd_error_display_handler(dpld);
+ break;
+
+ case INT_CMD_PHONE_START:
+ cmd_phone_start_handler(dpld);
+ break;
+
+ case INT_CMD_NV_REBUILDING:
+ mif_err("[MODEM_IF] NV_REBUILDING\n");
+ cmd_nv_rebuild_handler(dpld);
+ break;
+
+ case INT_CMD_PIF_INIT_DONE:
+ complete_all(&dpld->modem_pif_init_done);
+ break;
+
+ case INT_CMD_SILENT_NV_REBUILDING:
+ mif_err("[MODEM_IF] SILENT_NV_REBUILDING\n");
+ break;
+
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ case INT_MASK_CMD_DPRAM_DOWN:
+ idpram_power_down(dpld->link_pm_data);
+ break;
+
+ case INT_MASK_CMD_DPRAM_DOWN_NACK:
+ idpram_power_down_nack(dpld->link_pm_data);
+ break;
+
+ case INT_MASK_CMD_CP_WAKEUP_START:
+ idpram_powerup_start(dpld->link_pm_data);
+ break;
+#else
+ case INT_CMD_NORMAL_POWER_OFF:
+ /*ToDo:*/
+ /*kernel_sec_set_cp_ack()*/;
+ break;
+
+ case INT_CMD_REQ_TIME_SYNC:
+ case INT_CMD_PHONE_DEEP_SLEEP:
+ case INT_CMD_EMER_DOWN:
+ break;
+#endif
+
+ default:
+ mif_err("Unknown command.. %x\n", cmd);
+ }
+}
+
+
+static int dpram_process_modem_update(struct dpram_link_device *dpld,
+ struct dpram_firmware *pfw)
+{
+ int ret = 0;
+ char *buff = vmalloc(pfw->size);
+
+ mif_debug("[GOTA] modem size =[%d]\n", pfw->size);
+
+ if (!buff)
+ return -ENOMEM;
+
+ ret = copy_from_user(buff, pfw->firmware, pfw->size);
+ if (ret < 0) {
+ mif_err("[%s:%d] Copy from user failed\n", __func__, __LINE__);
+ goto out;
+ }
+
+ ret = dpram_download(dpld, buff, pfw->size);
+ if (ret < 0)
+ mif_err("firmware write failed\n");
+
+out:
+ vfree(buff);
+ return ret;
+}
+
+
+static int dpram_modem_update(struct link_device *ld, struct io_device *iod,
+ unsigned long _arg)
+{
+ int ret;
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ struct dpram_firmware fw;
+
+ mif_debug("[GOTA] dpram_modem_update\n");
+
+ ret = copy_from_user(&fw, (void __user *)_arg, sizeof(fw));
+ if (ret < 0) {
+ mif_err("copy from user failed!");
+ return ret;
+ }
+
+ return dpram_process_modem_update(dpld, &fw);
+}
+
+static int dpram_dump_update(struct link_device *ld, struct io_device *iod,
+ unsigned long _arg)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ struct dpram_firmware *fw = (struct dpram_firmware *)_arg ;
+
+ mif_debug("MIF: dpram_dump_update()\n");
+
+ return dpram_upload(dpld, fw);
+}
+
+static int dpram_read(struct dpram_link_device *dpld,
+ struct dpram_device *device, int dev_idx)
+{
+ struct io_device *iod;
+ int size;
+ int tmp_size;
+ u16 head, tail;
+ char *buff;
+
+ head = dpram_readh(&device->in->head);
+ tail = dpram_readh(&device->in->tail);
+ mif_debug("=====> %s, head: %d, tail: %d\n", __func__, head, tail);
+
+ if (head == tail) {
+ mif_err("MIF: %s: head == tail\n", __func__);
+ goto err_dpram_read;
+ }
+
+ if (!dpram_circ_valid(device->in_buff_size, head, tail)) {
+ mif_err("MIF: %s: invalid circular buffer\n", __func__);
+ dpram_zero_circ(device->in);
+ goto err_dpram_read;
+ }
+
+ iod = dpram_find_iod(dpld, dev_idx);
+ if (!iod) {
+ mif_err("MIF: iod == NULL\n");
+ goto err_dpram_read;
+ }
+
+ /* Get data size in DPRAM*/
+ size = (head > tail) ? (head - tail) :
+ (device->in_buff_size - tail + head);
+
+ /* ----- (tail) 7f 00 00 7e (head) ----- */
+ if (head > tail) {
+ buff = device->in_buff_addr + tail;
+ if (iod->recv(iod, buff, size) < 0) {
+ mif_err("MIF: %s: recv error, dropping data\n",
+ __func__);
+ dpram_drop_data(device, head);
+ goto err_dpram_read;
+ }
+ } else { /* 00 7e (head) ----------- (tail) 7f 00 */
+ /* 1. tail -> buffer end.*/
+ tmp_size = device->in_buff_size - tail;
+ buff = device->in_buff_addr + tail;
+ if (iod->recv(iod, buff, tmp_size) < 0) {
+ mif_err("MIF: %s: recv error, dropping data\n",
+ __func__);
+ dpram_drop_data(device, head);
+ goto err_dpram_read;
+ }
+
+ /* 2. buffer start -> head.*/
+ if (size > tmp_size) {
+ buff = (char *)device->in_buff_addr;
+ if (iod->recv(iod, buff, (size - tmp_size)) < 0) {
+ mif_err("MIF: %s: recv error, dropping data\n",
+ __func__);
+ dpram_drop_data(device, head);
+ goto err_dpram_read;
+ }
+ }
+ }
+
+ /* new tail */
+ tail = (u16)((tail + size) % device->in_buff_size);
+ dpram_writeh(tail, &device->in->tail);
+
+ return size;
+
+err_dpram_read:
+ return -EINVAL;
+}
+
+static void non_command_handler(struct dpram_link_device *dpld,
+ u16 non_cmd)
+{
+ struct dpram_device *device = NULL;
+ u16 head, tail;
+ u16 magic, access;
+ int ret = 0;
+
+ mif_debug("MIF: Entering non_command_handler(0x%04X)\n", non_cmd);
+
+ magic = dpram_readh(&dpld->dpram->magic);
+ access = dpram_readh(&dpld->dpram->enable);
+
+ if (!access || magic != DP_MAGIC_CODE) {
+ mif_err("fmr recevie error!!!! access = 0x%x, magic =0x%x",
+ access, magic);
+ return;
+ }
+
+ /* Check formatted data region */
+ device = &dpld->dev_map[FMT_IDX];
+ head = dpram_readh(&device->in->head);
+ tail = dpram_readh(&device->in->tail);
+
+ if (!dpram_circ_valid(device->in_buff_size, head, tail)) {
+ mif_err("MIF: %s: invalid circular buffer\n", __func__);
+ dpram_zero_circ(device->in);
+ return;
+ }
+
+ if (head != tail) {
+ if (non_cmd & INT_MASK_REQ_ACK_F)
+ atomic_inc(&dpld->fmt_txq_req_ack_rcvd);
+
+ ret = dpram_read(dpld, device, FMT_IDX);
+ if (ret < 0)
+ mif_err("%s, dpram_read failed\n", __func__);
+
+ if (atomic_read(&dpld->fmt_txq_req_ack_rcvd) > 0) {
+ dpram_write_command(dpld,
+ INT_NON_CMD(INT_MASK_RES_ACK_F));
+ atomic_set(&dpld->fmt_txq_req_ack_rcvd, 0);
+ }
+ } else {
+ if (non_cmd & INT_MASK_REQ_ACK_F) {
+ dpram_write_command(dpld,
+ INT_NON_CMD(INT_MASK_RES_ACK_F));
+ atomic_set(&dpld->fmt_txq_req_ack_rcvd, 0);
+ }
+ }
+
+ /* Check raw data region */
+ device = &dpld->dev_map[RAW_IDX];
+ head = dpram_readh(&device->in->head);
+ tail = dpram_readh(&device->in->tail);
+
+ if (!dpram_circ_valid(device->in_buff_size, head, tail)) {
+ mif_err("MIF: %s: invalid circular buffer\n", __func__);
+ dpram_zero_circ(device->in);
+ return;
+ }
+
+ if (head != tail) {
+ if (non_cmd & INT_MASK_REQ_ACK_R)
+ atomic_inc(&dpld->raw_txq_req_ack_rcvd);
+
+ ret = dpram_read(dpld, device, RAW_IDX);
+ if (ret < 0)
+ mif_err("%s, dpram_read failed\n", __func__);
+
+ if (atomic_read(&dpld->raw_txq_req_ack_rcvd) > 0) {
+ dpram_write_command(dpld,
+ INT_NON_CMD(INT_MASK_RES_ACK_R));
+ atomic_set(&dpld->raw_txq_req_ack_rcvd, 0);
+ }
+ } else {
+ if (non_cmd & INT_MASK_REQ_ACK_R) {
+ dpram_write_command(dpld,
+ INT_NON_CMD(INT_MASK_RES_ACK_R));
+ atomic_set(&dpld->raw_txq_req_ack_rcvd, 0);
+ }
+ }
+}
+
+static void gota_cmd_handler(struct dpram_link_device *dpld, u16 cmd)
+{
+ if (cmd & GOTA_RESULT_FAIL) {
+ mif_err("[GOTA] Command failed: %04x\n", cmd);
+ return;
+ }
+
+ switch (GOTA_CMD_MASK(cmd)) {
+ case GOTA_CMD_RECEIVE_READY:
+ mif_debug("[GOTA] Send CP-->AP RECEIVE_READY\n");
+ dpram_write_command(dpld, CMD_DL_START_REQ);
+ break;
+
+ case GOTA_CMD_DOWNLOAD_START_RESP:
+ mif_debug("[GOTA] Send CP-->AP DOWNLOAD_START_RESP\n");
+ complete_all(&dpld->gota_download_start_complete);
+ break;
+
+ case GOTA_CMD_SEND_DONE_RESP:
+ mif_debug("[GOTA] Send CP-->AP SEND_DONE_RESP\n");
+ complete_all(&dpld->gota_send_done);
+ break;
+
+ case GOTA_CMD_UPDATE_DONE:
+ mif_debug("[GOTA] Send CP-->AP UPDATE_DONE\n");
+ complete_all(&dpld->gota_update_done);
+ break;
+
+ case GOTA_CMD_IMAGE_SEND_RESP:
+ mif_debug("MIF: Send CP-->AP IMAGE_SEND_RESP\n");
+ complete_all(&dpld->dump_receive_done);
+ break;
+
+ default:
+ mif_err("[GOTA] Unknown command.. %x\n", cmd);
+ }
+}
+
+static irqreturn_t dpram_irq_handler(int irq, void *p_ld)
+{
+ u16 cp2ap;
+
+ struct link_device *ld = (struct link_device *)p_ld;
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+
+ cp2ap = dpram_readh(&dpld->dpram->mbx_cp2ap);
+#if 0
+ mif_err("MIF: received CP2AP = 0x%x\n", cp2ap);
+#endif
+ if (cp2ap == INT_POWERSAFE_FAIL) {
+ mif_err("MIF: Received POWERSAFE_FAIL\n");
+ goto exit_irq;
+ }
+
+ if (GOTA_CMD_VALID(cp2ap))
+ gota_cmd_handler(dpld, cp2ap);
+ else if (INT_CMD_VALID(cp2ap))
+ command_handler(dpld, cp2ap);
+ else if (INT_VALID(cp2ap))
+ non_command_handler(dpld, cp2ap);
+ else
+ mif_err("MIF: Invalid command %04x\n", cp2ap);
+
+exit_irq:
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ dpld->clear_interrupt();
+#endif
+ return IRQ_HANDLED;
+}
+
+static int dpram_attach_io_dev(struct link_device *ld, struct io_device *iod)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+
+ iod->link = ld;
+ /* list up io devices */
+ list_add(&iod->list, &dpld->list_of_io_devices);
+
+ return 0;
+}
+
+static int dpram_write(struct dpram_link_device *dpld,
+ struct dpram_device *device,
+ const unsigned char *buf,
+ int len)
+{
+ u16 head;
+ u16 tail;
+ u16 irq_mask;
+ int free_space;
+ int last_size;
+
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ /* Internal DPRAM, check dpram ready?*/
+ if (idpram_get_write_lock(dpld->link_pm_data)) {
+ printk(KERN_INFO "dpram_write_net - not ready return -EAGAIN\n");
+ return -EAGAIN;
+ }
+#endif
+
+ head = dpram_readh(&device->out->head);
+ tail = dpram_readh(&device->out->tail);
+
+ if (!dpram_circ_valid(device->out_buff_size, head, tail)) {
+ mif_err("MIF: %s: invalid circular buffer\n", __func__);
+ dpram_zero_circ(device->out);
+ return -EINVAL;
+ }
+
+ free_space = (head < tail) ? tail - head - 1 :
+ device->out_buff_size + tail - head - 1;
+ if (len > free_space) {
+ mif_debug("WRITE: No space in Q\n"
+ "len[%d] free_space[%d] head[%u] tail[%u] out_buff_size =%d\n",
+ len, free_space, head, tail, device->out_buff_size);
+ return -EINVAL;
+ }
+
+ mif_debug("WRITE: len[%d] free_space[%d] head[%u] tail[%u] out_buff_size =%d\n",
+ len, free_space, head, tail, device->out_buff_size);
+
+ if (head < tail) {
+ /* +++++++++ head ---------- tail ++++++++++ */
+ memcpy((device->out_buff_addr + head), buf, len);
+ } else {
+ /* ------ tail +++++++++++ head ------------ */
+ last_size = device->out_buff_size - head;
+ memcpy((device->out_buff_addr + head), buf,
+ len > last_size ? last_size : len);
+ if (len > last_size) {
+ memcpy(device->out_buff_addr, (buf + last_size),
+ (len - last_size));
+ }
+ }
+
+ /* Update new head */
+ head = (u16)((head + len) % device->out_buff_size);
+ dpram_writeh(head, &device->out->head);
+
+ irq_mask = INT_MASK_VALID;
+
+ if (len > 0)
+ irq_mask |= device->mask_send;
+
+ dpram_write_command(dpld, irq_mask);
+
+ return len;
+}
+
+static void dpram_write_work(struct work_struct *work)
+{
+ struct link_device *ld =
+ container_of(work, struct link_device, tx_delayed_work.work);
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ struct dpram_device *device;
+ struct sk_buff *skb;
+ bool reschedule = false;
+ int ret;
+
+ device = &dpld->dev_map[FMT_IDX];
+ while ((skb = skb_dequeue(&ld->sk_fmt_tx_q))) {
+ ret = dpram_write(dpld, device, skb->data, skb->len);
+ if (ret < 0) {
+ skb_queue_head(&ld->sk_fmt_tx_q, skb);
+ reschedule = true;
+ break;
+ }
+ dev_kfree_skb_any(skb);
+ }
+
+ device = &dpld->dev_map[RAW_IDX];
+ while ((skb = skb_dequeue(&ld->sk_raw_tx_q))) {
+ ret = dpram_write(dpld, device, skb->data, skb->len);
+ if (ret < 0) {
+ skb_queue_head(&ld->sk_raw_tx_q, skb);
+ reschedule = true;
+ break;
+ }
+ dev_kfree_skb_any(skb);
+ }
+
+ if (reschedule)
+ schedule_delayed_work(&ld->tx_delayed_work,
+ msecs_to_jiffies(10));
+}
+
+static int dpram_send(struct link_device *ld, struct io_device *iod,
+ struct sk_buff *skb)
+{
+ int len = skb->len;
+ mif_debug("%s: iod->format = %d\n", __func__, iod->format);
+
+ switch (iod->format) {
+ case IPC_FMT:
+ skb_queue_tail(&ld->sk_fmt_tx_q, skb);
+ break;
+
+ case IPC_RAW:
+ skb_queue_tail(&ld->sk_raw_tx_q, skb);
+ break;
+
+ case IPC_BOOT:
+ case IPC_RFS:
+ default:
+ dev_kfree_skb_any(skb);
+ return 0;
+ }
+
+ schedule_delayed_work(&ld->tx_delayed_work, 0);
+ return len;
+}
+
+static void dpram_table_init(struct dpram_link_device *dpld)
+{
+ struct dpram_device *dev;
+ struct dpram_map __iomem *dpram = dpld->dpram;
+
+ dev = &dpld->dev_map[FMT_IDX];
+ dev->in = &dpram->fmt_in;
+ dev->in_buff_addr = dpram->fmt_in_buff;
+ dev->in_buff_size = DP_FMT_IN_BUFF_SIZE;
+ dev->out = &dpram->fmt_out;
+ dev->out_buff_addr = dpram->fmt_out_buff;
+ dev->out_buff_size = DP_FMT_OUT_BUFF_SIZE;
+ dev->mask_req_ack = INT_MASK_REQ_ACK_F;
+ dev->mask_res_ack = INT_MASK_RES_ACK_F;
+ dev->mask_send = INT_MASK_SEND_F;
+
+ dev = &dpld->dev_map[RAW_IDX];
+ dev->in = &dpram->raw_in;
+ dev->in_buff_addr = dpram->raw_in_buff;
+ dev->in_buff_size = DP_RAW_IN_BUFF_SIZE;
+ dev->out = &dpram->raw_out;
+ dev->out_buff_addr = dpram->raw_out_buff;
+ dev->out_buff_size = DP_RAW_OUT_BUFF_SIZE;
+ dev->mask_req_ack = INT_MASK_REQ_ACK_R;
+ dev->mask_res_ack = INT_MASK_RES_ACK_R;
+ dev->mask_send = INT_MASK_SEND_R;
+}
+
+static int dpram_set_dlmagic(struct link_device *ld, struct io_device *iod)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ dpram_writew(DP_MAGIC_DMDL, &dpld->dpram->magic);
+ return 0;
+}
+
+static int dpram_set_ulmagic(struct link_device *ld, struct io_device *iod)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ struct dpram_map *dpram = (void *)dpld->dpram;
+ u8 *dest;
+ dest = (u8 *)(&dpram->fmt_out);
+
+ dpram_writew(DP_MAGIC_UMDL, &dpld->dpram->magic);
+
+ dpram_writeb((u8)START_INDEX, dest + 0);
+ dpram_writeb((u8)0x1, dest + 1);
+ dpram_writeb((u8)0x1, dest + 2);
+ dpram_writeb((u8)0x0, dest + 3);
+ dpram_writeb((u8)END_INDEX, dest + 4);
+
+ init_completion(&dpld->gota_download_start_complete);
+ dpram_write_command(dpld, CMD_DL_START_REQ);
+
+ return 0;
+}
+
+static int
+dpram_download(struct dpram_link_device *dpld, const char *buf, int len)
+{
+ struct dpram_map *dpram = (void *)dpld->dpram;
+ struct dpram_ota_header header;
+ u16 nframes;
+ u16 curframe = 1;
+ u16 plen;
+ u8 *dest;
+ int ret;
+
+ nframes = DIV_ROUND_UP(len, DP_DEFAULT_WRITE_LEN);
+
+ mif_debug("[GOTA] download len = %d\n", len);
+
+ header.start_index = START_INDEX;
+ header.nframes = nframes;
+
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ dpld->board_ota_reset();
+#endif
+ while (len > 0) {
+ plen = min(len, DP_DEFAULT_WRITE_LEN);
+ dest = (u8 *)&dpram->fmt_out;
+
+ mif_debug("[GOTA] Start write frame %d/%d\n", \
+ curframe, nframes);
+
+ header.curframe = curframe;
+ header.len = plen;
+
+ memcpy(dest, &header, sizeof(header));
+ dest += sizeof(header);
+
+ memcpy(dest, buf, plen);
+ dest += plen;
+ buf += plen;
+ len -= plen;
+
+ dpram_writeb(END_INDEX, dest+3);
+
+ init_completion(&dpld->gota_send_done);
+
+ if (curframe == 1) {
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ init_completion(&dpld->gota_download_start_complete);
+ dpram_write_command(dpld, 0);
+#endif
+ ret = wait_for_completion_interruptible_timeout(
+ &dpld->gota_download_start_complete,
+ GOTA_TIMEOUT);
+ if (!ret) {
+ mif_err("[GOTA] CP didn't send DOWNLOAD_START\n");
+ return -ENXIO;
+ }
+ }
+
+ dpram_write_command(dpld, CMD_IMG_SEND_REQ);
+ ret = wait_for_completion_interruptible_timeout(
+ &dpld->gota_send_done, GOTA_SEND_TIMEOUT);
+ if (!ret) {
+ mif_err("[GOTA] CP didn't send SEND_DONE_RESP\n");
+ return -ENXIO;
+ }
+
+ curframe++;
+ }
+
+ dpram_write_command(dpld, CMD_DL_SEND_DONE_REQ);
+ ret = wait_for_completion_interruptible_timeout(
+ &dpld->gota_update_done, GOTA_TIMEOUT);
+ if (!ret) {
+ mif_err("[GOTA] CP didn't send UPDATE_DONE_NOTIFICATION\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int
+dpram_upload(struct dpram_link_device *dpld, struct dpram_firmware *uploaddata)
+{
+ struct dpram_map *dpram = (void *)dpld->dpram;
+ struct ul_header header;
+ u8 *dest;
+ u8 *buff = vmalloc(DP_DEFAULT_DUMP_LEN);
+ u16 plen = 0;
+ u32 tlen = 0;
+ int ret;
+ int region = 0;
+
+ mif_debug("MIF: dpram_upload()\n");
+
+ ret = wait_for_completion_interruptible_timeout(
+ &dpld->gota_download_start_complete,
+ DUMP_START_TIMEOUT);
+ if (!ret) {
+ mif_err("[GOTA] CP didn't send DOWNLOAD_START\n");
+ goto err_out;
+ }
+
+ wake_lock(&dpld->dpram_wake_lock);
+
+ memset(buff, 0, DP_DEFAULT_DUMP_LEN);
+
+ dpram_write_command(dpld, CMD_IMG_SEND_REQ);
+ mif_debug("MIF: write CMD_IMG_SEND_REQ(0x9400)\n");
+
+ while (1) {
+ init_completion(&dpld->dump_receive_done);
+ ret = wait_for_completion_interruptible_timeout(
+ &dpld->dump_receive_done, DUMP_TIMEOUT);
+ if (!ret) {
+ mif_err("MIF: CP didn't send DATA_SEND_DONE_RESP\n");
+ goto err_out;
+ }
+
+ dest = (u8 *)(&dpram->fmt_out);
+
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ header.bop = *(u16 *)(dest);
+ header.total_frame = *(u16 *)(dest + 2);
+ header.curr_frame = *(u16 *)(dest + 4);
+ header.len = *(u16 *)(dest + 6);
+#else
+ header.bop = *(u8 *)(dest);
+ header.total_frame = *(u16 *)(dest + 1);
+ header.curr_frame = *(u16 *)(dest + 3);
+ header.len = *(u16 *)(dest + 5);
+#endif
+
+ mif_err("total frame:%d, current frame:%d, data len:%d\n",
+ header.total_frame, header.curr_frame,
+ header.len);
+
+ dest += DP_DUMP_HEADER_SIZE;
+ plen = min(header.len, (u16)DP_DEFAULT_DUMP_LEN);
+
+ memcpy(buff, dest, plen);
+ dest += plen;
+
+ ret = copy_to_user(uploaddata->firmware + tlen, buff, plen);
+ if (ret < 0) {
+ mif_err("MIF: Copy to user failed\n");
+ goto err_out;
+ }
+
+ tlen += plen;
+
+ if (header.total_frame == header.curr_frame) {
+ if (region) {
+ uploaddata->is_delta = tlen - uploaddata->size;
+ dpram_write_command(dpld, CMD_UL_RECEIVE_RESP);
+ break;
+ } else {
+ uploaddata->size = tlen;
+ region = 1;
+ }
+ }
+ dpram_write_command(dpld, CMD_UL_RECEIVE_RESP);
+ }
+
+ mif_debug("1st dump region data size=%d\n", uploaddata->size);
+ mif_debug("2st dump region data size=%d\n", uploaddata->is_delta);
+
+ init_completion(&dpld->gota_send_done);
+ ret = wait_for_completion_interruptible_timeout(
+ &dpld->gota_send_done, DUMP_TIMEOUT);
+ if (!ret) {
+ mif_err("[GOTA] CP didn't send SEND_DONE_RESP\n");
+ goto err_out;
+ }
+
+ dpram_write_command(dpld, CMD_UL_RECEIVE_DONE_RESP);
+ mif_debug("MIF: write CMD_UL_RECEIVE_DONE_RESP(0x9801)\n");
+
+ dpram_writew(0, &dpld->dpram->magic); /*clear magic code */
+
+ wake_unlock(&dpld->dpram_wake_lock);
+
+ vfree(buff);
+ return 0;
+
+err_out:
+ vfree(buff);
+ dpram_writew(0, &dpld->dpram->magic);
+ mif_err("CDMA dump error out\n");
+ wake_unlock(&dpld->dpram_wake_lock);
+ return -EIO;
+}
+
+struct link_device *dpram_create_link_device(struct platform_device *pdev)
+{
+ int ret;
+ struct dpram_link_device *dpld;
+ struct link_device *ld;
+ struct resource *res;
+ unsigned long flag = 0;
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ struct modem_data *pdata = (struct modem_data *)pdev->dev.platform_data;
+#endif
+ BUILD_BUG_ON(sizeof(struct dpram_map) != DP_DPRAM_SIZE);
+
+ dpld = kzalloc(sizeof(struct dpram_link_device), GFP_KERNEL);
+ if (!dpld)
+ return NULL;
+ ld = &dpld->ld;
+
+ INIT_LIST_HEAD(&dpld->list_of_io_devices);
+ skb_queue_head_init(&ld->sk_fmt_tx_q);
+ skb_queue_head_init(&ld->sk_raw_tx_q);
+ INIT_DELAYED_WORK(&ld->tx_delayed_work, dpram_write_work);
+
+ wake_lock_init(&dpld->dpram_wake_lock, WAKE_LOCK_SUSPEND, "DPRAM");
+
+ init_completion(&dpld->modem_pif_init_done);
+ init_completion(&dpld->dpram_init_cmd);
+ init_completion(&dpld->gota_send_done);
+ init_completion(&dpld->gota_update_done);
+ init_completion(&dpld->gota_download_start_complete);
+ init_completion(&dpld->dump_receive_done);
+
+ ld->name = "dpram";
+ ld->attach = dpram_attach_io_dev;
+ ld->send = dpram_send;
+ ld->gota_start = dpram_set_dlmagic;
+ ld->modem_update = dpram_modem_update;
+ ld->dump_start = dpram_set_ulmagic;
+ ld->dump_update = dpram_dump_update;
+
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ dpld->clear_interrupt = pdata->clear_intr;
+ dpld->board_ota_reset = pdata->ota_reset;
+#endif
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ mif_err("MIF: Failed to get mem region\n");
+ goto err;
+ }
+ dpld->dpram = ioremap(res->start, resource_size(res));
+ printk(KERN_INFO " MIF: start address:0x%08x\n", \
+ res->start);
+ dpld->irq = platform_get_irq_byname(pdev, "dpram_irq");
+ if (!dpld->irq) {
+ mif_err("MIF: %s: Failed to get IRQ\n", __func__);
+ goto err;
+ }
+
+ dpram_table_init(dpld);
+
+ atomic_set(&dpld->raw_txq_req_ack_rcvd, 0);
+ atomic_set(&dpld->fmt_txq_req_ack_rcvd, 0);
+
+ dpram_writeh(0, &dpld->dpram->magic);
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ ret = idpram_link_pm_init(dpld, pdev);
+
+ if (ret)
+ mif_err("MIF: idpram_link_pm_init fail.(%d)\n", ret);
+#endif
+ flag = IRQF_DISABLED;
+ printk(KERN_ERR "dpram irq: %d\n", dpld->irq);
+ dpld->clear_interrupt();
+ ret = request_irq(dpld->irq, dpram_irq_handler, flag,
+ "dpram irq", ld);
+ if (ret) {
+ mif_err("MIF: DPRAM interrupt handler failed\n");
+ goto err;
+ }
+
+ return ld;
+
+err:
+ iounmap(dpld->dpram);
+ kfree(dpld);
+ return NULL;
+}
diff --git a/drivers/misc/modem_if_na/modem_link_device_dpram.h b/drivers/misc/modem_if_na/modem_link_device_dpram.h
new file mode 100644
index 0000000..aaa8e82
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_link_device_dpram.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2011 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/wakelock.h>
+
+#ifndef __MODEM_LINK_DEVICE_DPRAM_H__
+#define __MODEM_LINK_DEVICE_DPRAM_H__
+
+#define FMT_IDX 0
+#define RAW_IDX 1
+#define MAX_IDX 2
+#define DP_FMT_OUT_BUFF_SIZE 2044
+#define DP_RAW_OUT_BUFF_SIZE 6128
+#define DP_FMT_IN_BUFF_SIZE 2044
+#define DP_RAW_IN_BUFF_SIZE 6128
+struct dpram_circ {
+ u16 head;
+ u16 tail;
+};
+
+struct dpram_ota_header {
+ u8 start_index;
+ u16 nframes;
+ u16 curframe;
+ u16 len;
+
+} __packed;
+
+struct dpram_map {
+ u16 magic;
+ u16 enable;
+
+ struct dpram_circ fmt_out;
+ u8 fmt_out_buff[DP_FMT_OUT_BUFF_SIZE];
+
+ struct dpram_circ raw_out;
+ u8 raw_out_buff[DP_RAW_OUT_BUFF_SIZE];
+
+ struct dpram_circ fmt_in;
+ u8 fmt_in_buff[DP_FMT_IN_BUFF_SIZE];
+
+ struct dpram_circ raw_in;
+ u8 raw_in_buff[DP_RAW_IN_BUFF_SIZE];
+
+ u8 padding[16];
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ u16 mbx_ap2cp;
+ u16 mbx_cp2ap;
+#else
+ u16 mbx_cp2ap;
+ u16 mbx_ap2cp;
+#endif
+} __packed;
+
+struct dpram_device {
+ struct dpram_circ __iomem *in;
+ u8 __iomem *in_buff_addr;
+ int in_buff_size;
+
+ struct dpram_circ __iomem *out;
+ u8 __iomem *out_buff_addr;
+ int out_buff_size;
+
+ u16 mask_req_ack;
+ u16 mask_res_ack;
+ u16 mask_send;
+};
+
+struct ul_header {
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ u16 bop;
+ u16 total_frame;
+ u16 curr_frame;
+ u16 len;
+#else
+ u8 bop;
+ u16 total_frame;
+ u16 curr_frame;
+ u16 len;
+#endif
+};
+
+#ifdef CONFIG_INTERNAL_MODEM_IF
+enum idpram_link_pm_states {
+ IDPRAM_PM_SUSPEND_PREPARE,
+ IDPRAM_PM_DPRAM_POWER_DOWN,
+ IDPRAM_PM_SUSPEND_START,
+ IDPRAM_PM_RESUME_START,
+ IDPRAM_PM_ACTIVE,
+};
+
+struct idpram_link_pm_data {
+ struct dpram_link_device *dpld;
+ struct modem_data *mdata;
+
+ unsigned last_pm_mailbox; /* 0xCC or 0x CA*/
+ unsigned resume_retry;
+ unsigned pm_states;
+
+ int idpram_wpend;
+
+ struct completion idpram_down;
+
+ struct delayed_work resume_work;
+
+ atomic_t read_lock;
+ atomic_t write_lock;
+
+ struct wake_lock host_wakeup_wlock;
+ struct wake_lock rd_wlock;
+ struct wake_lock hold_wlock;
+ struct wake_lock wakeup_wlock;
+};
+#endif
+
+
+struct dpram_link_device {
+ struct link_device ld;
+
+ /* maybe -list of io devices for the link device to use
+ * to find where to send incoming packets to */
+ struct list_head list_of_io_devices;
+
+ atomic_t raw_txq_req_ack_rcvd;
+ atomic_t fmt_txq_req_ack_rcvd;
+
+ struct dpram_map __iomem *dpram;
+ struct dpram_device dev_map[MAX_IDX];
+
+ struct wake_lock dpram_wake_lock;
+
+ struct completion dpram_init_cmd;
+ struct completion modem_pif_init_done;
+ struct completion gota_download_start_complete;
+ struct completion gota_send_done;
+ struct completion gota_update_done;
+ struct completion dump_receive_done;
+
+ int irq;
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ void (*clear_interrupt)(void);
+ int (*board_ota_reset) (void);
+ struct idpram_link_pm_data *link_pm_data;
+ int (*init_magic_num)(struct dpram_link_device *dpld);
+#else
+ void (*clear_interrupt)(struct dpram_link_device *);
+#endif
+};
+
+/* converts from struct link_device* to struct xxx_link_device* */
+#define to_dpram_link_device(linkdev) \
+ container_of(linkdev, struct dpram_link_device, ld)
+
+#endif
diff --git a/drivers/misc/modem_if_na/modem_link_device_usb.c b/drivers/misc/modem_if_na/modem_link_device_usb.c
new file mode 100644
index 0000000..6bdd7b5
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_link_device_usb.c
@@ -0,0 +1,1031 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define DEBUG
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/irq.h>
+#include <linux/poll.h>
+#include <linux/gpio.h>
+#include <linux/if_arp.h>
+#include <linux/platform_device.h>
+#include <linux/suspend.h>
+
+#include <linux/platform_data/modem_na.h>
+#include "modem_prj.h"
+#include "modem_link_device_usb.h"
+#include "modem_link_pm_usb.h"
+
+#include <mach/regs-gpio.h>
+
+#define URB_COUNT 4
+
+extern int factory_mode;
+
+static int enable_autosuspend;
+static int wakelock_held;
+
+static int usb_tx_urb_with_skb(struct usb_link_device *usb_ld,
+ struct sk_buff *skb, struct if_usb_devdata *pipe_data);
+
+static void
+usb_change_modem_state(struct usb_link_device *usb_ld, enum modem_state state);
+
+static int usb_attach_io_dev(struct link_device *ld,
+ struct io_device *iod)
+{
+ struct usb_link_device *usb_ld = to_usb_link_device(ld);
+
+ iod->link = ld;
+
+ /* list up io devices */
+ list_add(&iod->list, &usb_ld->list_of_io_devices);
+
+ return 0;
+}
+
+static void
+usb_free_urbs(struct usb_link_device *usb_ld, struct if_usb_devdata *pipe)
+{
+ struct usb_device *usbdev = usb_ld->usbdev;
+ struct urb *urb;
+
+ while ((urb = usb_get_from_anchor(&pipe->urbs))) {
+ usb_poison_urb(urb);
+ usb_free_coherent(usbdev, pipe->rx_buf_size,
+ urb->transfer_buffer, urb->transfer_dma);
+ urb->transfer_buffer = NULL;
+ usb_put_urb(urb);
+ usb_free_urb(urb);
+ }
+}
+
+static int usb_init_communication(struct link_device *ld,
+ struct io_device *iod)
+{
+ int err = 0;
+ switch (iod->format) {
+ case IPC_BOOT:
+ ld->com_state = COM_BOOT;
+ skb_queue_purge(&ld->sk_fmt_tx_q);
+ break;
+
+ case IPC_RAMDUMP:
+ ld->com_state = COM_CRASH;
+ break;
+
+ case IPC_FMT:
+ case IPC_RFS:
+ case IPC_RAW:
+
+ default:
+ ld->com_state = COM_ONLINE;
+ break;
+ }
+
+ mif_debug("com_state = %d\n", ld->com_state);
+ return err;
+}
+
+static void usb_terminate_communication(
+ struct link_device *ld, struct io_device *iod)
+{
+ if (iod->format != IPC_BOOT && iod->format != IPC_RAMDUMP)
+ mif_debug("com_state = %d\n", ld->com_state);
+}
+
+static int usb_rx_submit(struct if_usb_devdata *pipe, struct urb *urb,
+ gfp_t gfp_flags)
+{
+ int ret;
+
+ usb_anchor_urb(urb, &pipe->reading);
+ ret = usb_submit_urb(urb, gfp_flags);
+ if (ret) {
+ usb_unanchor_urb(urb);
+ usb_anchor_urb(urb, &pipe->urbs);
+ mif_err("submit urb fail with ret (%d)\n", ret);
+ }
+
+ usb_mark_last_busy(urb->dev);
+ return ret;
+}
+
+static void usb_rx_complete(struct urb *urb)
+{
+ struct if_usb_devdata *pipe_data = urb->context;
+ struct usb_link_device *usb_ld = usb_get_intfdata(pipe_data->data_intf);
+ struct io_device *iod;
+ int iod_format = IPC_FMT;
+ int ret;
+
+ usb_mark_last_busy(urb->dev);
+
+ switch (urb->status) {
+ case 0:
+ case -ENOENT:
+ if (!urb->actual_length)
+ goto re_submit;
+ /* call iod recv */
+ /* how we can distinguish boot ch with fmt ch ?? */
+ switch (pipe_data->format) {
+ case IF_USB_FMT_EP:
+ iod_format = IPC_FMT;
+ /*
+ print_hex_dump(KERN_INFO, "[FMT-RX] ",
+ DUMP_PREFIX_OFFSET, 16, 1,
+ urb->transfer_buffer,
+ min(urb->actual_length, (u32)64), true);
+ */
+ break;
+ case IF_USB_RAW_EP:
+ iod_format = IPC_MULTI_RAW;
+ break;
+ case IF_USB_RFS_EP:
+ iod_format = IPC_RFS;
+ break;
+ default:
+ break;
+ }
+
+ list_for_each_entry(iod, &usb_ld->list_of_io_devices, list) {
+ /* during boot stage fmt end point */
+ /* shared with boot io device */
+ /* when we use fmt device only, at boot and ipc exchange
+ it can be reduced to 1 device */
+ if (iod_format == IPC_FMT &&
+ usb_ld->ld.com_state == COM_BOOT)
+ iod_format = IPC_BOOT;
+ if (iod_format == IPC_FMT &&
+ usb_ld->ld.com_state == COM_CRASH)
+ iod_format = IPC_RAMDUMP;
+
+ if (iod->format == iod_format) {
+ ret = iod->recv(iod,
+ (char *)urb->transfer_buffer,
+ urb->actual_length);
+ if (ret < 0)
+ mif_err("io device recv error :%d\n",
+ ret);
+ break;
+ }
+ }
+re_submit:
+ if (urb->status || atomic_read(&usb_ld->suspend_count))
+ break;
+
+ usb_mark_last_busy(urb->dev);
+ usb_rx_submit(pipe_data, urb, GFP_ATOMIC);
+ return;
+ case -ESHUTDOWN:
+ case -EPROTO:
+ break;
+ case -EOVERFLOW:
+ mif_err("RX overflow\n");
+ break;
+ default:
+ mif_err("RX complete Status (%d)\n", urb->status);
+ break;
+ }
+
+ usb_anchor_urb(urb, &pipe_data->urbs);
+}
+
+static int usb_send(struct link_device *ld, struct io_device *iod,
+ struct sk_buff *skb)
+{
+ struct sk_buff_head *txq;
+
+ if (iod->format == IPC_RAW)
+ txq = &ld->sk_raw_tx_q;
+ else
+ txq = &ld->sk_fmt_tx_q;
+
+ /* save io device into cb area */
+ *((struct io_device **)skb->cb) = iod;
+ /* en queue skb data */
+ skb_queue_tail(txq, skb);
+
+ queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0);
+
+ return skb->len;
+}
+
+static void usb_tx_complete(struct urb *urb)
+{
+ int ret = 0;
+ struct sk_buff *skb = urb->context;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ default:
+ mif_err("TX error (%d)\n", urb->status);
+ }
+
+ usb_mark_last_busy(urb->dev);
+ ret = pm_runtime_put_autosuspend(&urb->dev->dev);
+ if (ret < 0)
+ mif_debug("pm_runtime_put_autosuspend failed : ret(%d)\n", ret);
+ usb_free_urb(urb);
+ dev_kfree_skb_any(skb);
+}
+
+static void if_usb_force_disconnect(struct work_struct *work)
+{
+ struct usb_link_device *usb_ld =
+ container_of(work, struct usb_link_device, disconnect_work);
+ struct usb_device *udev = usb_ld->usbdev;
+
+ if (!udev || !(&udev->dev))
+ return;
+
+ pm_runtime_get_sync(&udev->dev);
+ if (udev->state != USB_STATE_NOTATTACHED) {
+ usb_force_disconnect(udev);
+ mif_info("force disconnect by modem not responding!!\n");
+ }
+ pm_runtime_put_autosuspend(&udev->dev);
+}
+
+static void
+usb_change_modem_state(struct usb_link_device *usb_ld, enum modem_state state)
+{
+ struct io_device *iod;
+
+ list_for_each_entry(iod, &usb_ld->list_of_io_devices, list) {
+ if (iod->format == IPC_FMT) {
+ iod->modem_state_changed(iod, state);
+ return;
+ }
+ }
+}
+
+static int usb_tx_urb_with_skb(struct usb_link_device *usb_ld,
+ struct sk_buff *skb, struct if_usb_devdata *pipe_data)
+{
+ int ret, cnt = 0;
+ struct urb *urb;
+ struct usb_device *usbdev = usb_ld->usbdev;
+ unsigned long flags;
+
+ if (!usbdev || (usbdev->state == USB_STATE_NOTATTACHED) ||
+ usb_ld->host_wake_timeout_flag)
+ return -ENODEV;
+
+ pm_runtime_get_noresume(&usbdev->dev);
+
+ if (usbdev->dev.power.runtime_status == RPM_SUSPENDED ||
+ usbdev->dev.power.runtime_status == RPM_SUSPENDING) {
+ usb_ld->resume_status = AP_INITIATED_RESUME;
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 1);
+
+ while (!wait_event_interruptible_timeout(usb_ld->l2_wait,
+ usbdev->dev.power.runtime_status == RPM_ACTIVE
+ || pipe_data->disconnected,
+ HOST_WAKEUP_TIMEOUT_JIFFIES)) {
+
+ if (cnt == MAX_RETRY) {
+ mif_err("host wakeup timeout !!\n");
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 0);
+ pm_runtime_put_autosuspend(&usbdev->dev);
+ schedule_work(&usb_ld->disconnect_work);
+ usb_ld->host_wake_timeout_flag = 1;
+ return -1;
+ }
+ mif_err("host wakeup timeout ! retry..\n");
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 0);
+ udelay(100);
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 1);
+ cnt++;
+ }
+
+ if (pipe_data->disconnected) {
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 0);
+ pm_runtime_put_autosuspend(&usbdev->dev);
+ return -ENODEV;
+ }
+
+ mif_debug("wait_q done (runtime_status=%d)\n",
+ usbdev->dev.power.runtime_status);
+ }
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ mif_err("alloc urb error\n");
+ if (pm_runtime_put_autosuspend(&usbdev->dev) < 0)
+ mif_debug("pm_runtime_put_autosuspend fail\n");
+ return -ENOMEM;
+ }
+
+ urb->transfer_flags = URB_ZERO_PACKET;
+ usb_fill_bulk_urb(urb, usbdev, pipe_data->tx_pipe, skb->data,
+ skb->len, usb_tx_complete, (void *)skb);
+
+ spin_lock_irqsave(&usb_ld->lock, flags);
+ if (atomic_read(&usb_ld->suspend_count)) {
+ /* transmission will be done in resume */
+ usb_anchor_urb(urb, &usb_ld->deferred);
+ usb_put_urb(urb);
+ mif_debug("anchor urb (0x%p)\n", urb);
+ spin_unlock_irqrestore(&usb_ld->lock, flags);
+ return 0;
+ }
+ spin_unlock_irqrestore(&usb_ld->lock, flags);
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret < 0) {
+ mif_err("usb_submit_urb with ret(%d)\n", ret);
+ if (pm_runtime_put_autosuspend(&usbdev->dev) < 0)
+ mif_debug("pm_runtime_put_autosuspend fail\n");
+ }
+ return ret;
+}
+
+static void usb_tx_work(struct work_struct *work)
+{
+ int ret = 0;
+ struct link_device *ld =
+ container_of(work, struct link_device, tx_delayed_work.work);
+ struct usb_link_device *usb_ld = to_usb_link_device(ld);
+ struct io_device *iod;
+ struct sk_buff *skb;
+ struct if_usb_devdata *pipe_data;
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+
+ /*TODO: check the PHONE ACTIVE STATES */
+ /* because tx data wait until hub on with wait_for_complettion, it
+ should queue to single_threaded work queue */
+ if (!link_pm_set_active(usb_ld))
+ return;
+
+ while (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) {
+ /* send skb from fmt_txq and raw_txq,
+ * one by one for fair flow control */
+ skb = skb_dequeue(&ld->sk_fmt_tx_q);
+ if (skb) {
+ iod = *((struct io_device **)skb->cb);
+ switch (iod->format) {
+ case IPC_FMT:
+ /*
+ print_hex_dump(KERN_INFO, "[FMT-TX] ",
+ DUMP_PREFIX_OFFSET, 16, 1,
+ skb->data,
+ min(skb->len, (u32)64), true);
+ */
+ case IPC_BOOT:
+ case IPC_RAMDUMP:
+ /* boot device uses same intf with fmt*/
+ pipe_data = &usb_ld->devdata[IF_USB_FMT_EP];
+ break;
+ case IPC_RFS:
+ pipe_data = &usb_ld->devdata[IF_USB_RFS_EP];
+ break;
+ default:
+ /* wrong packet for fmt tx q , drop it */
+ dev_kfree_skb_any(skb);
+ continue;
+ }
+
+ ret = usb_tx_urb_with_skb(usb_ld, skb, pipe_data);
+ if (ret < 0) {
+ mif_err("usb_tx_urb_with_skb, ret(%d)\n",
+ ret);
+ skb_queue_head(&ld->sk_fmt_tx_q, skb);
+
+ if (edc_inc(&usb_ld->urb_edc, EDC_MAX_ERRORS,
+ EDC_ERROR_TIMEFRAME)) {
+ mif_err("maximum error in URB exceeded\n");
+ usb_change_modem_state(usb_ld,
+ STATE_CRASH_EXIT);
+ }
+ return;
+ }
+ }
+
+ skb = skb_dequeue(&ld->sk_raw_tx_q);
+ if (skb) {
+ pipe_data = &usb_ld->devdata[IF_USB_RAW_EP];
+ ret = usb_tx_urb_with_skb(usb_ld, skb, pipe_data);
+ if (ret < 0) {
+ mif_err("usb_tx_urb_with_skb "
+ "for raw, ret(%d)\n",
+ ret);
+ skb_queue_head(&ld->sk_raw_tx_q, skb);
+
+ if (edc_inc(&usb_ld->urb_edc, EDC_MAX_ERRORS,
+ EDC_ERROR_TIMEFRAME)) {
+ mif_err("maximum error in URB exceeded\n");
+ usb_change_modem_state(usb_ld,
+ STATE_CRASH_EXIT);
+ }
+ return;
+ }
+ }
+ }
+}
+
+static int if_usb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct usb_link_device *usb_ld = usb_get_intfdata(intf);
+ int i;
+
+ if (atomic_inc_return(&usb_ld->suspend_count) == IF_USB_DEVNUM_MAX) {
+ mif_debug("L2\n");
+
+ for (i = 0; i < IF_USB_DEVNUM_MAX; i++)
+ usb_kill_anchored_urbs(&usb_ld->devdata[i].reading);
+
+ /*
+ if (usb_ld->link_pm_data->cpufreq_unlock)
+ usb_ld->link_pm_data->cpufreq_unlock();
+ */
+ if (wakelock_held) {
+ wakelock_held = 0;
+ wake_unlock(&usb_ld->susplock);
+ }
+ }
+
+ return 0;
+}
+
+static void runtime_pm_work(struct work_struct *work)
+{
+ struct usb_link_device *usb_ld = container_of(work,
+ struct usb_link_device, runtime_pm_work.work);
+ int ret;
+
+ ret = pm_request_autosuspend(&usb_ld->usbdev->dev);
+
+ if (ret == -EAGAIN || ret == 1)
+ queue_delayed_work(system_nrt_wq, &usb_ld->runtime_pm_work,
+ msecs_to_jiffies(50));
+}
+
+static void post_resume_work(struct work_struct *work)
+{
+ struct usb_link_device *usb_ld = container_of(work,
+ struct usb_link_device, post_resume_work.work);
+
+ /* lock cpu frequency when L2->L0 */
+ /*
+ if (usb_ld->link_pm_data->cpufreq_lock)
+ usb_ld->link_pm_data->cpufreq_lock();
+ */
+}
+
+static void wait_enumeration_work(struct work_struct *work)
+{
+ struct usb_link_device *usb_ld = container_of(work,
+ struct usb_link_device, wait_enumeration.work);
+
+ /* Work around code for factory device test & verification.
+ * To measure device sleep current speedly, do not call silent reset in
+ * factory mode. CMC22x disconect usb forcely and go sleep
+ * without normal L3 process if in factory mode. Also AP do. */
+ if (factory_mode)
+ return;
+
+ if (usb_ld->if_usb_connected == 0) {
+ mif_err("USB disconnected and not enumerated for long time\n");
+ usb_change_modem_state(usb_ld, STATE_CRASH_EXIT);
+ }
+}
+
+static int if_usb_resume(struct usb_interface *intf)
+{
+ int i, ret;
+ struct sk_buff *skb;
+ struct usb_link_device *usb_ld = usb_get_intfdata(intf);
+ struct if_usb_devdata *pipe;
+ struct urb *urb;
+
+ spin_lock_irq(&usb_ld->lock);
+ if (!atomic_dec_return(&usb_ld->suspend_count)) {
+ spin_unlock_irq(&usb_ld->lock);
+
+ mif_debug("\n");
+ wake_lock(&usb_ld->susplock);
+ wakelock_held = 1;
+
+ /* HACK: Runtime pm does not allow requesting autosuspend from
+ * resume callback, delayed it after resume */
+ queue_delayed_work(system_nrt_wq, &usb_ld->runtime_pm_work,
+ msecs_to_jiffies(50));
+
+ for (i = 0; i < IF_USB_DEVNUM_MAX; i++) {
+ pipe = &usb_ld->devdata[i];
+ while ((urb = usb_get_from_anchor(&pipe->urbs))) {
+ ret = usb_rx_submit(pipe, urb, GFP_KERNEL);
+ if (ret < 0) {
+ usb_put_urb(urb);
+ mif_err(
+ "usb_rx_submit error with (%d)\n",
+ ret);
+ return ret;
+ }
+ usb_put_urb(urb);
+ }
+ }
+
+ while ((urb = usb_get_from_anchor(&usb_ld->deferred))) {
+ mif_debug("got urb (0x%p) from anchor & resubmit\n",
+ urb);
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret < 0) {
+ mif_err("resubmit failed\n");
+ skb = urb->context;
+ dev_kfree_skb_any(skb);
+ usb_free_urb(urb);
+ if (pm_runtime_put_autosuspend(
+ &usb_ld->usbdev->dev) < 0)
+ mif_debug(
+ "pm_runtime_put_autosuspend fail\n");
+ }
+ }
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 1);
+ udelay(100);
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 0);
+
+ if (!link_pm_is_connected(usb_ld))
+ enable_autosuspend = 1;
+
+ /* if_usb_resume() is atomic. post_resume_work is
+ * a kind of bottom halves
+ */
+ /*
+ queue_delayed_work(system_nrt_wq, &usb_ld->post_resume_work, 0);
+ */
+
+ return 0;
+ }
+
+ spin_unlock_irq(&usb_ld->lock);
+ return 0;
+}
+
+static int if_usb_reset_resume(struct usb_interface *intf)
+{
+ int ret;
+
+ mif_debug("\n");
+ ret = if_usb_resume(intf);
+ return ret;
+}
+
+static struct usb_device_id if_usb_ids[] = {
+ { USB_DEVICE(0x04e8, 0x6999), /* CMC221 LTE Modem */
+ /*.driver_info = 0,*/
+ },
+ { } /* terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, if_usb_ids);
+
+static struct usb_driver if_usb_driver;
+static void if_usb_disconnect(struct usb_interface *intf)
+{
+ struct usb_link_device *usb_ld = usb_get_intfdata(intf);
+ struct usb_device *usbdev = usb_ld->usbdev;
+ int dev_id = intf->altsetting->desc.bInterfaceNumber;
+ struct if_usb_devdata *pipe_data = &usb_ld->devdata[dev_id];
+
+ usb_set_intfdata(intf, NULL);
+
+ pipe_data->disconnected = 1;
+ smp_wmb();
+
+ wake_up(&usb_ld->l2_wait);
+ if (wakelock_held) {
+ wakelock_held = 0;
+ wake_unlock(&usb_ld->susplock);
+ }
+ /*
+ if (usb_ld->if_usb_connected) {
+ disable_irq_wake(usb_ld->pdata->irq_host_wakeup);
+ free_irq(usb_ld->pdata->irq_host_wakeup, usb_ld);
+ }
+ */
+
+ usb_ld->if_usb_connected = 0;
+ usb_ld->flow_suspend = 1;
+
+ dev_dbg(&usbdev->dev, "%s\n", __func__);
+ usb_ld->dev_count--;
+ usb_driver_release_interface(&if_usb_driver, pipe_data->data_intf);
+
+ usb_kill_anchored_urbs(&pipe_data->reading);
+ usb_free_urbs(usb_ld, pipe_data);
+
+ if (usb_ld->dev_count == 0) {
+ cancel_delayed_work_sync(&usb_ld->runtime_pm_work);
+ /*
+ cancel_delayed_work_sync(&usb_ld->post_resume_work);
+ */
+ cancel_delayed_work_sync(&usb_ld->ld.tx_delayed_work);
+ cancel_work_sync(&usb_ld->disconnect_work);
+ usb_put_dev(usbdev);
+ usb_ld->usbdev = NULL;
+
+ wake_lock(&usb_ld->link_pm_data->boot_wake);
+
+ schedule_delayed_work(&usb_ld->wait_enumeration,
+ WAIT_ENUMURATION_TIMEOUT_JIFFIES);
+ }
+}
+
+static int __devinit if_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_host_interface *data_desc;
+ struct usb_link_device *usb_ld =
+ (struct usb_link_device *)id->driver_info;
+ struct link_device *ld = &usb_ld->ld;
+ struct usb_interface *data_intf;
+ struct usb_device *usbdev = interface_to_usbdev(intf);
+ struct device *dev;
+ struct if_usb_devdata *pipe;
+ struct urb *urb;
+ int i;
+ int j;
+ int dev_id;
+ int err;
+
+ /* To detect usb device order probed */
+ dev_id = intf->cur_altsetting->desc.bInterfaceNumber;
+
+ if (dev_id >= IF_USB_DEVNUM_MAX) {
+ dev_err(&intf->dev, "Device id %d cannot support\n",
+ dev_id);
+ return -EINVAL;
+ }
+
+ if (!usb_ld) {
+ dev_err(&intf->dev,
+ "if_usb device doesn't be allocated\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ mif_info("probe dev_id=%d usb_device_id(0x%p), usb_ld (0x%p)\n",
+ dev_id, id, usb_ld);
+
+ usb_ld->usbdev = usbdev;
+
+ usb_get_dev(usbdev);
+
+ for (i = 0; i < IF_USB_DEVNUM_MAX; i++) {
+ data_intf = usb_ifnum_to_if(usbdev, i);
+
+ /* remap endpoint of RAW to no.1 for LTE modem */
+ if (i == 0)
+ pipe = &usb_ld->devdata[1];
+ else if (i == 1)
+ pipe = &usb_ld->devdata[0];
+ else
+ pipe = &usb_ld->devdata[i];
+
+ pipe->disconnected = 0;
+ pipe->data_intf = data_intf;
+ data_desc = data_intf->cur_altsetting;
+
+ /* Endpoints */
+ if (usb_pipein(data_desc->endpoint[0].desc.bEndpointAddress)) {
+ pipe->rx_pipe = usb_rcvbulkpipe(usbdev,
+ data_desc->endpoint[0].desc.bEndpointAddress);
+ pipe->tx_pipe = usb_sndbulkpipe(usbdev,
+ data_desc->endpoint[1].desc.bEndpointAddress);
+ pipe->rx_buf_size = 1024*4;
+ } else {
+ pipe->rx_pipe = usb_rcvbulkpipe(usbdev,
+ data_desc->endpoint[1].desc.bEndpointAddress);
+ pipe->tx_pipe = usb_sndbulkpipe(usbdev,
+ data_desc->endpoint[0].desc.bEndpointAddress);
+ pipe->rx_buf_size = 1024*4;
+ }
+
+ if (i == 0) {
+ dev_info(&usbdev->dev, "USB IF USB device found\n");
+ } else {
+ err = usb_driver_claim_interface(&if_usb_driver,
+ data_intf, usb_ld);
+ if (err < 0) {
+ mif_err("failed to cliam usb interface\n");
+ goto out;
+ }
+ }
+
+ usb_set_intfdata(data_intf, usb_ld);
+ usb_ld->dev_count++;
+ pm_suspend_ignore_children(&data_intf->dev, true);
+
+ for (j = 0; j < URB_COUNT; j++) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ mif_err("alloc urb fail\n");
+ err = -ENOMEM;
+ goto out2;
+ }
+
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ urb->transfer_buffer = usb_alloc_coherent(usbdev,
+ pipe->rx_buf_size, GFP_KERNEL,
+ &urb->transfer_dma);
+ if (!urb->transfer_buffer) {
+ mif_err(
+ "Failed to allocate transfer buffer\n");
+ usb_free_urb(urb);
+ err = -ENOMEM;
+ goto out2;
+ }
+
+ usb_fill_bulk_urb(urb, usbdev, pipe->rx_pipe,
+ urb->transfer_buffer, pipe->rx_buf_size,
+ usb_rx_complete, pipe);
+ usb_anchor_urb(urb, &pipe->urbs);
+ }
+ }
+
+ /* temporary call reset_resume */
+ atomic_set(&usb_ld->suspend_count, 1);
+ if_usb_reset_resume(data_intf);
+ atomic_set(&usb_ld->suspend_count, 0);
+
+ SET_HOST_ACTIVE(usb_ld->pdata, 1);
+ usb_ld->host_wake_timeout_flag = 0;
+
+ if (gpio_get_value(usb_ld->pdata->gpio_phone_active)) {
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+ int delay = AUTOSUSPEND_DELAY_MS;
+ pm_runtime_set_autosuspend_delay(&usbdev->dev, delay);
+ dev = &usb_ld->usbdev->dev;
+ if (dev->parent) {
+ dev_dbg(&usbdev->dev, "if_usb Runtime PM Start!!\n");
+ usb_enable_autosuspend(usb_ld->usbdev);
+ pm_runtime_allow(dev);
+
+ if (pm_data->block_autosuspend)
+ pm_runtime_forbid(dev);
+ }
+
+ enable_irq_wake(usb_ld->pdata->irq_host_wakeup);
+
+ usb_ld->flow_suspend = 0;
+ /* Queue work if skbs were pending before a disconnect/probe */
+ if (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen)
+ queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0);
+
+ wake_unlock(&usb_ld->link_pm_data->boot_wake);
+
+ usb_ld->if_usb_connected = 1;
+ usb_change_modem_state(usb_ld, STATE_ONLINE);
+ } else {
+ usb_change_modem_state(usb_ld, STATE_LOADER_DONE);
+ }
+
+ edc_init(&usb_ld->urb_edc);
+
+ return 0;
+
+out2:
+ usb_ld->dev_count--;
+ for (i = 0; i < IF_USB_DEVNUM_MAX; i++)
+ usb_free_urbs(usb_ld, &usb_ld->devdata[i]);
+out:
+ usb_set_intfdata(intf, NULL);
+ return err;
+}
+
+irqreturn_t usb_resume_irq(int irq, void *data)
+{
+ int ret;
+ struct usb_link_device *usb_ld = data;
+ int hwup;
+ static int wake_status = -1;
+ struct device *dev;
+
+ hwup = gpio_get_value(usb_ld->pdata->gpio_host_wakeup);
+ if (hwup == wake_status) {
+ mif_err("Received spurious wake irq: %d", hwup);
+ return IRQ_HANDLED;
+ }
+ wake_status = hwup;
+
+ irq_set_irq_type(irq, hwup ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH);
+ /*
+ * exynos BSP has problem when using level interrupt.
+ * If we change irq type from interrupt handler,
+ * we can get level interrupt twice.
+ * this is temporary solution until SYS.LSI resolve this problem.
+ */
+ __raw_writel(eint_irq_to_bit(irq), S5P_EINT_PEND(EINT_REG_NR(irq)));
+ wake_lock_timeout(&usb_ld->gpiolock, 100);
+
+ mif_err("< H-WUP %d\n", hwup);
+
+ if (!link_pm_is_connected(usb_ld) &&
+ !(!hwup && enable_autosuspend)) {
+ return IRQ_HANDLED;
+ } else {
+ enable_autosuspend = 0;
+ }
+
+ if (hwup) {
+ dev = &usb_ld->usbdev->dev;
+ mif_info("runtime status=%d\n",
+ dev->power.runtime_status);
+
+ /* if usb3503 was on, usb_if was resumed by probe */
+ /*
+ if (has_hub(usb_ld) &&
+ (dev->power.runtime_status == RPM_ACTIVE ||
+ dev->power.runtime_status == RPM_RESUMING))
+ return IRQ_HANDLED;
+ */
+
+ device_lock(dev);
+ if (dev->power.is_prepared || dev->power.is_suspended) {
+ pm_runtime_get_noresume(dev);
+ ret = 0;
+ } else {
+ ret = pm_runtime_get_sync(dev);
+ }
+ device_unlock(dev);
+ if (ret < 0) {
+ mif_err("pm_runtime_get fail (%d)\n", ret);
+ return IRQ_HANDLED;
+ }
+ } else {
+ if (usb_ld->resume_status == AP_INITIATED_RESUME)
+ wake_up(&usb_ld->l2_wait);
+ usb_ld->resume_status = CP_INITIATED_RESUME;
+ pm_runtime_mark_last_busy(&usb_ld->usbdev->dev);
+ pm_runtime_put_autosuspend(&usb_ld->usbdev->dev);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int if_usb_init(struct usb_link_device *usb_ld)
+{
+ int ret;
+ int i;
+ struct if_usb_devdata *pipe;
+
+ /* give it to probe, or global variable needed */
+ if_usb_ids[0].driver_info = (unsigned long)usb_ld;
+
+ for (i = 0; i < IF_USB_DEVNUM_MAX; i++) {
+ pipe = &usb_ld->devdata[i];
+ pipe->format = i;
+ pipe->disconnected = 1;
+ init_usb_anchor(&pipe->urbs);
+ init_usb_anchor(&pipe->reading);
+ }
+
+ init_waitqueue_head(&usb_ld->l2_wait);
+ init_usb_anchor(&usb_ld->deferred);
+
+ ret = usb_register(&if_usb_driver);
+ if (ret) {
+ mif_err("usb_register_driver() fail : %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+struct link_device *usb_create_link_device(void *data)
+{
+ int ret;
+ struct modem_data *pdata;
+ struct platform_device *pdev = (struct platform_device *)data;
+ struct usb_link_device *usb_ld = NULL;
+ struct link_device *ld = NULL;
+
+ pdata = pdev->dev.platform_data;
+
+ usb_ld = kzalloc(sizeof(struct usb_link_device), GFP_KERNEL);
+ if (!usb_ld)
+ goto err;
+
+ INIT_LIST_HEAD(&usb_ld->list_of_io_devices);
+ skb_queue_head_init(&usb_ld->ld.sk_fmt_tx_q);
+ skb_queue_head_init(&usb_ld->ld.sk_raw_tx_q);
+ spin_lock_init(&usb_ld->lock);
+
+ ld = &usb_ld->ld;
+ usb_ld->pdata = pdata;
+
+ ld->name = "usb";
+ ld->attach = usb_attach_io_dev;
+ ld->init_comm = usb_init_communication;
+ ld->terminate_comm = usb_terminate_communication;
+ ld->send = usb_send;
+ ld->com_state = COM_NONE;
+
+ /*ld->tx_wq = create_singlethread_workqueue("usb_tx_wq");*/
+ ld->tx_wq = alloc_workqueue("usb_tx_wq",
+ WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1);
+
+ if (!ld->tx_wq) {
+ mif_err("fail to create work Q.\n");
+ goto err;
+ }
+
+ usb_ld->pdata->irq_host_wakeup = gpio_to_irq(platform_get_irq(pdev, 1));
+ wake_lock_init(&usb_ld->gpiolock, WAKE_LOCK_SUSPEND,
+ "modem_usb_gpio_wake");
+ wake_lock_init(&usb_ld->susplock, WAKE_LOCK_SUSPEND,
+ "modem_usb_suspend_block");
+
+ INIT_DELAYED_WORK(&ld->tx_delayed_work, usb_tx_work);
+ INIT_DELAYED_WORK(&usb_ld->runtime_pm_work, runtime_pm_work);
+ /*
+ INIT_DELAYED_WORK(&usb_ld->post_resume_work, post_resume_work);
+ */
+ INIT_DELAYED_WORK(&usb_ld->wait_enumeration, wait_enumeration_work);
+ INIT_WORK(&usb_ld->disconnect_work, if_usb_force_disconnect);
+
+ /* create link pm device */
+ ret = link_pm_init(usb_ld, data);
+ if (ret)
+ goto err;
+
+ ret = if_usb_init(usb_ld);
+ if (ret)
+ goto err;
+
+ return ld;
+err:
+ if (ld && ld->tx_wq)
+ destroy_workqueue(ld->tx_wq);
+
+ kfree(usb_ld);
+
+ return NULL;
+}
+
+static struct usb_driver if_usb_driver = {
+ .name = "if_usb_driver",
+ .probe = if_usb_probe,
+ .disconnect = if_usb_disconnect,
+ .id_table = if_usb_ids,
+ .suspend = if_usb_suspend,
+ .resume = if_usb_resume,
+ .reset_resume = if_usb_reset_resume,
+ .supports_autosuspend = 1,
+};
+
+static void __exit if_usb_exit(void)
+{
+ usb_deregister(&if_usb_driver);
+}
+
+
+/* lte specific functions */
+
+static int lte_wake_resume(struct device *pdev)
+{
+ struct modem_data *pdata = pdev->platform_data;
+ int val;
+
+ val = gpio_get_value(pdata->gpio_host_wakeup);
+ if (!val) {
+ mif_debug("> S-WUP 1\n");
+ gpio_set_value(pdata->gpio_slave_wakeup, 1);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops lte_wake_pm_ops = {
+ .resume = lte_wake_resume,
+};
+
+static struct platform_driver lte_wake_driver = {
+ .driver = {
+ .name = "modem_lte_wake",
+ .pm = &lte_wake_pm_ops,
+ },
+};
+
+static int __init lte_wake_init(void)
+{
+ return platform_driver_register(&lte_wake_driver);
+}
+module_init(lte_wake_init);
diff --git a/drivers/misc/modem_if_na/modem_link_device_usb.h b/drivers/misc/modem_if_na/modem_link_device_usb.h
new file mode 100644
index 0000000..0db83b1
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_link_device_usb.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __MODEM_LINK_DEVICE_USB_H__
+#define __MODEM_LINK_DEVICE_USB_H__
+
+#include <linux/usb.h>
+#include <linux/wakelock.h>
+
+#define IF_USB_DEVNUM_MAX 3
+
+#define IF_USB_FMT_EP 0
+#define IF_USB_RAW_EP 1
+#define IF_USB_RFS_EP 2
+
+#define AUTOSUSPEND_DELAY_MS 500
+#define HOST_WAKEUP_TIMEOUT_JIFFIES msecs_to_jiffies(500)
+#define WAIT_ENUMURATION_TIMEOUT_JIFFIES msecs_to_jiffies(30000)
+#define MAX_RETRY 3
+
+enum RESUME_STATUS {
+ CP_INITIATED_RESUME,
+ AP_INITIATED_RESUME,
+};
+
+enum {
+ EDC_MAX_ERRORS = 5,
+ EDC_ERROR_TIMEFRAME = HZ,
+};
+
+struct edc {
+ unsigned long timestart;
+ u16 errorcount;
+};
+
+static inline void edc_init(struct edc *edc)
+{
+ edc->timestart = jiffies;
+}
+
+static inline int edc_inc(struct edc *edc, u16 max_err, u16 timeframe)
+{
+ unsigned long now;
+
+ now = jiffies;
+ if (now - edc->timestart > timeframe) {
+ edc->errorcount = 1;
+ edc->timestart = now;
+ } else if (++edc->errorcount > max_err) {
+ edc->errorcount = 0;
+ edc->timestart = now;
+ return 1;
+ }
+ return 0;
+}
+
+struct if_usb_devdata {
+ struct usb_interface *data_intf;
+ unsigned int tx_pipe;
+ unsigned int rx_pipe;
+ u8 disconnected;
+
+ int format;
+ struct usb_anchor urbs;
+ struct usb_anchor reading;
+ unsigned int rx_buf_size;
+};
+
+struct usb_link_device {
+ /*COMMON LINK DEVICE*/
+ struct link_device ld;
+
+ struct modem_data *pdata;
+
+ /*USB SPECIFIC LINK DEVICE*/
+ struct usb_device *usbdev;
+ struct if_usb_devdata devdata[IF_USB_DEVNUM_MAX];
+ struct delayed_work runtime_pm_work;
+ struct delayed_work post_resume_work;
+ struct delayed_work wait_enumeration;
+ struct work_struct disconnect_work;
+
+ struct wake_lock gpiolock;
+ struct wake_lock susplock;
+
+ unsigned int dev_count;
+ unsigned int suspended;
+ atomic_t suspend_count;
+ enum RESUME_STATUS resume_status;
+ int if_usb_connected;
+ int flow_suspend;
+ int host_wake_timeout_flag;
+
+ unsigned gpio_slave_wakeup;
+ unsigned gpio_host_wakeup;
+ unsigned gpio_host_active;
+ int irq_host_wakeup;
+ struct delayed_work dwork;
+ struct work_struct resume_work;
+ int cpcrash_flag;
+ wait_queue_head_t l2_wait;
+
+ spinlock_t lock;
+ struct usb_anchor deferred;
+
+ /* LINK PM DEVICE DATA */
+ struct link_pm_data *link_pm_data;
+
+ /*COMMON LINK DEVICE*/
+ /* maybe -list of io devices for the link device to use */
+ /* to find where to send incoming packets to */
+ struct list_head list_of_io_devices;
+
+ struct edc urb_edc;
+};
+/* converts from struct link_device* to struct xxx_link_device* */
+#define to_usb_link_device(linkdev) \
+ container_of(linkdev, struct usb_link_device, ld)
+
+#define SET_SLAVE_WAKEUP(_pdata, _value) \
+do { \
+ gpio_set_value(_pdata->gpio_slave_wakeup, _value); \
+ mif_debug("> S-WUP %s\n", _value ? "1" : "0"); \
+} while (0)
+
+#define SET_HOST_ACTIVE(_pdata, _value) \
+do { \
+ gpio_set_value(_pdata->gpio_host_active, _value); \
+ mif_debug("> H-ACT %s\n", _value ? "1" : "0"); \
+} while (0)
+
+#define has_hub(usb_ld) ((usb_ld)->link_pm_data->has_usbhub)
+
+irqreturn_t usb_resume_irq(int irq, void *data);
+
+#endif
diff --git a/drivers/misc/modem_if_na/modem_link_pm_usb.c b/drivers/misc/modem_if_na/modem_link_pm_usb.c
new file mode 100644
index 0000000..4907de7
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_link_pm_usb.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * TODO (last updated Apr 13, 2012):
+ * - move usb hub feature to board file
+ * currently, some code was commented out by CONFIG_USBHUB_USB3503
+ * - Both usb_ld and link_pm_data have same gpio member.
+ * we have to remove it from one of them and
+ * make helper function for gpio handling.
+ */
+
+#define DEBUG
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/irq.h>
+#include <linux/poll.h>
+#include <linux/gpio.h>
+#include <linux/if_arp.h>
+#include <linux/platform_device.h>
+#include <linux/suspend.h>
+
+#include "modem_link_pm_usb.h"
+
+bool link_pm_is_connected(struct usb_link_device *usb_ld)
+{
+#ifdef CONFIG_USBHUB_USB3503
+ if (has_hub(usb_ld)) {
+ if (usb_ld->link_pm_data->hub_status == HUB_STATE_RESUMMING
+ || usb_ld->link_pm_data->hub_init_lock)
+ return false;
+
+ if (usb_ld->link_pm_data->hub_status == HUB_STATE_OFF) {
+ schedule_delayed_work(
+ &usb_ld->link_pm_data->link_pm_hub, 0);
+ return false;
+ }
+ }
+#endif
+ if (!usb_ld->if_usb_connected) {
+ mif_err("if not connected\n");
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef CONFIG_USBHUB_USB3503
+static void link_pm_hub_work(struct work_struct *work)
+{
+ int err;
+ struct link_pm_data *pm_data =
+ container_of(work, struct link_pm_data, link_pm_hub.work);
+
+ if (pm_data->hub_status == HUB_STATE_ACTIVE)
+ return;
+
+ if (!pm_data->port_enable) {
+ mif_err("mif: hub power func not assinged\n");
+ return;
+ }
+ wake_lock(&pm_data->hub_lock);
+
+ /* If kernel if suspend, wait the ehci resume */
+ if (pm_data->dpm_suspending) {
+ mif_info("dpm_suspending\n");
+ schedule_delayed_work(&pm_data->link_pm_hub,
+ msecs_to_jiffies(500));
+ goto exit;
+ }
+
+ switch (pm_data->hub_status) {
+ case HUB_STATE_OFF:
+ pm_data->hub_status = HUB_STATE_RESUMMING;
+ mif_trace("hub off->on\n");
+
+ /* skip 1st time before first probe */
+ if (pm_data->root_hub)
+ pm_runtime_get_sync(pm_data->root_hub);
+ err = pm_data->port_enable(2, 1);
+ if (err < 0) {
+ mif_err("hub on fail err=%d\n", err);
+ err = pm_data->port_enable(2, 0);
+ if (err < 0)
+ mif_err("hub off fail err=%d\n", err);
+ pm_data->hub_status = HUB_STATE_OFF;
+ if (pm_data->root_hub)
+ pm_runtime_put_sync(pm_data->root_hub);
+ goto exit;
+ }
+ /* resume root hub */
+ schedule_delayed_work(&pm_data->link_pm_hub,
+ msecs_to_jiffies(100));
+ break;
+ case HUB_STATE_RESUMMING:
+ if (pm_data->hub_on_retry_cnt++ > 50) {
+ pm_data->hub_on_retry_cnt = 0;
+ pm_data->hub_status = HUB_STATE_OFF;
+ if (pm_data->root_hub)
+ pm_runtime_put_sync(pm_data->root_hub);
+ }
+ mif_trace("hub resumming\n");
+ schedule_delayed_work(&pm_data->link_pm_hub,
+ msecs_to_jiffies(200));
+ break;
+ case HUB_STATE_PREACTIVE:
+ mif_trace("hub active\n");
+ pm_data->hub_on_retry_cnt = 0;
+ wake_unlock(&pm_data->hub_lock);
+ pm_data->hub_status = HUB_STATE_ACTIVE;
+ complete(&pm_data->hub_active);
+ if (pm_data->root_hub)
+ pm_runtime_put_sync(pm_data->root_hub);
+ break;
+ }
+exit:
+ return;
+}
+#endif
+
+static int link_pm_hub_standby(struct link_pm_data *pm_data)
+{
+ int err = 0;
+
+#ifdef CONFIG_USBHUB_USB3503
+ mif_info("wait hub standby\n");
+
+ if (!pm_data->port_enable) {
+ mif_err("hub power func not assinged\n");
+ return -ENODEV;
+ }
+
+ err = pm_data->port_enable(2, 0);
+ if (err < 0)
+ mif_err("hub off fail err=%d\n", err);
+
+ pm_data->hub_status = HUB_STATE_OFF;
+#endif
+ return err;
+}
+
+bool link_pm_set_active(struct usb_link_device *usb_ld)
+{
+ int ret;
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+
+ if (has_hub(usb_ld)) {
+ if (pm_data->hub_status != HUB_STATE_ACTIVE) {
+ INIT_COMPLETION(pm_data->hub_active);
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 1);
+ ret = wait_for_completion_timeout(&pm_data->hub_active,
+ msecs_to_jiffies(2000));
+ if (!ret) { /*timeout*/
+ mif_err("hub on timeout - retry\n");
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 0);
+ queue_delayed_work(usb_ld->ld.tx_wq,
+ &usb_ld->ld.tx_delayed_work, 0);
+ return false;
+ }
+ }
+ } else {
+ /* TODO do something */
+ }
+ return true;
+}
+
+static long link_pm_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int value, err = 0;
+ struct task_struct *task = get_current();
+ struct link_pm_data *pm_data = file->private_data;
+ struct usb_link_device *usb_ld = pm_data->usb_ld;
+ char taskname[TASK_COMM_LEN];
+
+ mif_info("cmd: 0x%08x\n", cmd);
+
+ switch (cmd) {
+ case IOCTL_LINK_CONTROL_ACTIVE:
+ if (copy_from_user(&value, (const void __user *)arg,
+ sizeof(int)))
+ return -EFAULT;
+ gpio_set_value(pm_data->gpio_link_active, value);
+ break;
+ case IOCTL_LINK_GET_HOSTWAKE:
+ return !gpio_get_value(pm_data->gpio_link_hostwake);
+ case IOCTL_LINK_CONNECTED:
+ return pm_data->usb_ld->if_usb_connected;
+ case IOCTL_LINK_PORT_ON: /* hub only */
+ /* ignore cp host wakeup irq, set the hub_init_lock when AP try
+ CP off and release hub_init_lock when CP boot done */
+ pm_data->hub_init_lock = 0;
+ if (pm_data->root_hub) {
+ pm_runtime_resume(pm_data->root_hub);
+ pm_runtime_forbid(pm_data->root_hub->parent);
+ }
+ if (pm_data->port_enable) {
+ err = pm_data->port_enable(2, 1);
+ if (err < 0) {
+ mif_err("hub on fail err=%d\n", err);
+ goto exit;
+ }
+ pm_data->hub_status = HUB_STATE_RESUMMING;
+ }
+ break;
+ case IOCTL_LINK_PORT_OFF: /* hub only */
+ err = link_pm_hub_standby(pm_data);
+ if (err < 0) {
+ mif_err("usb3503 active fail\n");
+ goto exit;
+ }
+ pm_data->hub_init_lock = 1;
+ pm_data->hub_handshake_done = 0;
+
+ break;
+ case IOCTL_LINK_BLOCK_AUTOSUSPEND: /* block autosuspend forever */
+ mif_info("blocked autosuspend by `%s(%d)'\n",
+ get_task_comm(taskname, task), task->pid);
+ pm_data->block_autosuspend = true;
+ if (usb_ld->usbdev)
+ pm_runtime_forbid(&usb_ld->usbdev->dev);
+ break;
+ case IOCTL_LINK_ENABLE_AUTOSUSPEND: /* Enable autosuspend */
+ mif_info("autosuspend enabled by `%s(%d)'\n",
+ get_task_comm(taskname, task), task->pid);
+ pm_data->block_autosuspend = false;
+ if (usb_ld->usbdev)
+ pm_runtime_allow(&usb_ld->usbdev->dev);
+ else {
+ mif_err("Enable autosuspend failed\n");
+ err = -ENODEV;
+ }
+ break;
+ default:
+ break;
+ }
+exit:
+ return err;
+}
+
+static int link_pm_open(struct inode *inode, struct file *file)
+{
+ struct link_pm_data *pm_data =
+ (struct link_pm_data *)file->private_data;
+ file->private_data = (void *)pm_data;
+ return 0;
+}
+
+static int link_pm_release(struct inode *inode, struct file *file)
+{
+ file->private_data = NULL;
+ return 0;
+}
+
+static const struct file_operations link_pm_fops = {
+ .owner = THIS_MODULE,
+ .open = link_pm_open,
+ .release = link_pm_release,
+ .unlocked_ioctl = link_pm_ioctl,
+};
+
+static int link_pm_notifier_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct link_pm_data *pm_data =
+ container_of(this, struct link_pm_data, pm_notifier);
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ pm_data->dpm_suspending = true;
+ link_pm_hub_standby(pm_data);
+ return NOTIFY_OK;
+ case PM_POST_SUSPEND:
+ pm_data->dpm_suspending = false;
+ return NOTIFY_OK;
+ }
+ return NOTIFY_DONE;
+}
+
+int link_pm_init(struct usb_link_device *usb_ld, void *data)
+{
+ int err;
+ int irq;
+ struct platform_device *pdev = (struct platform_device *)data;
+ struct modem_data *pdata =
+ (struct modem_data *)pdev->dev.platform_data;
+ struct modemlink_pm_data *pm_pdata = pdata->link_pm_data;
+ struct link_pm_data *pm_data =
+ kzalloc(sizeof(struct link_pm_data), GFP_KERNEL);
+ if (!pm_data) {
+ mif_err("link_pm_data is NULL\n");
+ return -ENOMEM;
+ }
+ /* get link pm data from modemcontrol's platform data */
+ pm_data->gpio_link_active = pm_pdata->gpio_link_active;
+ pm_data->gpio_link_hostwake = pm_pdata->gpio_link_hostwake;
+ pm_data->gpio_link_slavewake = pm_pdata->gpio_link_slavewake;
+ pm_data->link_reconnect = pm_pdata->link_reconnect;
+ pm_data->port_enable = pm_pdata->port_enable;
+ pm_data->cpufreq_lock = pm_pdata->cpufreq_lock;
+ pm_data->cpufreq_unlock = pm_pdata->cpufreq_unlock;
+ pm_data->autosuspend_delay_ms = pm_pdata->autosuspend_delay_ms;
+ pm_data->block_autosuspend = false;
+
+ pm_data->usb_ld = usb_ld;
+ pm_data->link_pm_active = false;
+ usb_ld->link_pm_data = pm_data;
+
+ pm_data->miscdev.minor = MISC_DYNAMIC_MINOR;
+ pm_data->miscdev.name = "link_pm";
+ pm_data->miscdev.fops = &link_pm_fops;
+
+ err = misc_register(&pm_data->miscdev);
+ if (err < 0) {
+ mif_err("fail to register pm device(%d)\n", err);
+ goto err_misc_register;
+ }
+
+ pm_data->hub_init_lock = 1;
+ irq = gpio_to_irq(usb_ld->pdata->gpio_host_wakeup);
+ err = request_threaded_irq(irq, NULL, usb_resume_irq,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "modem_usb_wake", usb_ld);
+ if (err) {
+ mif_err("Failed to allocate an interrupt(%d)\n", irq);
+ goto err_request_irq;
+ }
+ enable_irq_wake(irq);
+
+ pm_data->has_usbhub = pm_pdata->has_usbhub;
+
+#ifdef CONFIG_USBHUB_USB3503
+ if (has_hub(usb_ld)) {
+ init_completion(&pm_data->hub_active);
+ pm_data->hub_status = HUB_STATE_OFF;
+ pm_pdata->p_hub_status = &pm_data->hub_status;
+ pm_data->hub_handshake_done = 0;
+ pm_data->root_hub = NULL;
+ wake_lock_init(&pm_data->hub_lock, WAKE_LOCK_SUSPEND,
+ "modem_hub_enum_lock");
+ INIT_DELAYED_WORK(&pm_data->link_pm_hub, link_pm_hub_work);
+ }
+#endif
+
+ wake_lock_init(&pm_data->boot_wake, WAKE_LOCK_SUSPEND, "boot_usb");
+
+ pm_data->pm_notifier.notifier_call = link_pm_notifier_event;
+ register_pm_notifier(&pm_data->pm_notifier);
+
+ return 0;
+
+err_request_irq:
+ misc_deregister(&pm_data->miscdev);
+err_misc_register:
+ kfree(pm_data);
+ return err;
+}
diff --git a/drivers/misc/modem_if_na/modem_link_pm_usb.h b/drivers/misc/modem_if_na/modem_link_pm_usb.h
new file mode 100644
index 0000000..a4d4ea3
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_link_pm_usb.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __MODEM_LINK_PM_USB_H__
+#define __MODEM_LINK_PM_USB_H__
+
+#include <linux/platform_data/modem_na.h>
+#include "modem_prj.h"
+#include "modem_link_device_usb.h"
+
+#define IOCTL_LINK_CONTROL_ENABLE _IO('o', 0x30)
+#define IOCTL_LINK_CONTROL_ACTIVE _IO('o', 0x31)
+#define IOCTL_LINK_GET_HOSTWAKE _IO('o', 0x32)
+#define IOCTL_LINK_CONNECTED _IO('o', 0x33)
+#define IOCTL_LINK_SET_BIAS_CLEAR _IO('o', 0x34)
+
+#define IOCTL_LINK_PORT_ON _IO('o', 0x35)
+#define IOCTL_LINK_PORT_OFF _IO('o', 0x36)
+#define IOCTL_LINK_BLOCK_AUTOSUSPEND _IO('o', 0x37)
+#define IOCTL_LINK_ENABLE_AUTOSUSPEND _IO('o', 0x38)
+
+enum hub_status {
+ HUB_STATE_OFF, /* usb3503 0ff*/
+ HUB_STATE_RESUMMING, /* usb3503 on, but enummerattion was not yet*/
+ HUB_STATE_PREACTIVE,
+ HUB_STATE_ACTIVE, /* hub and CMC221 enumerate */
+};
+
+struct link_pm_data {
+ struct miscdevice miscdev;
+ struct usb_link_device *usb_ld;
+ unsigned gpio_link_active;
+ unsigned gpio_link_hostwake;
+ unsigned gpio_link_slavewake;
+ int (*link_reconnect)(void);
+ int link_reconnect_cnt;
+
+ struct workqueue_struct *wq;
+ struct completion active_done;
+/*USB3503*/
+ struct completion hub_active;
+ int hub_status;
+ bool has_usbhub;
+ /* ignore hub on by host wakeup irq before cp power on*/
+ int hub_init_lock;
+ /* C1 stay disconnect status after send 'a', skip 'a' next enumeration*/
+ int hub_handshake_done;
+ struct wake_lock hub_lock;
+ struct delayed_work link_pm_hub;
+ int hub_on_retry_cnt;
+ struct device *root_hub;
+
+ struct delayed_work link_pm_work;
+ struct delayed_work link_pm_start;
+ struct delayed_work link_reconnect_work;
+ bool resume_requested;
+ bool link_pm_active;
+
+ struct wake_lock l2_wake;
+ struct wake_lock boot_wake;
+ struct notifier_block pm_notifier;
+ bool dpm_suspending;
+
+ int (*port_enable)(int, int);
+
+ int (*cpufreq_lock)(void);
+ int (*cpufreq_unlock)(void);
+
+ int autosuspend_delay_ms; /* if zero, the default value is used */
+ bool block_autosuspend;
+};
+
+bool link_pm_set_active(struct usb_link_device *usb_ld);
+bool link_pm_is_connected(struct usb_link_device *usb_ld);
+int link_pm_init(struct usb_link_device *usb_ld, void *data);
+
+#endif
diff --git a/drivers/misc/modem_if_na/modem_modemctl_device_cbp71.c b/drivers/misc/modem_if_na/modem_modemctl_device_cbp71.c
new file mode 100644
index 0000000..17f493c
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_modemctl_device_cbp71.c
@@ -0,0 +1,269 @@
+/* /linux/drivers/misc/modem_if/modem_modemctl_device_cbp7.1.c
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/init.h>
+
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+
+#include <linux/platform_data/modem_na.h>
+#include "modem_prj.h"
+#include "modem_link_device_dpram.h"
+
+#define PIF_TIMEOUT (180 * HZ)
+#define DPRAM_INIT_TIMEOUT (15 * HZ)
+
+
+static irqreturn_t phone_active_irq_handler(int irq, void *_mc)
+{
+ int phone_reset = 0;
+ int phone_active_value = 0;
+ int phone_state = 0;
+ struct modem_ctl *mc = (struct modem_ctl *)_mc;
+
+ if (!mc->gpio_cp_reset || !mc->gpio_phone_active) {
+ pr_err("MIF :<%s> no gpio data\n", __func__);
+ return IRQ_HANDLED;
+ }
+ phone_reset = gpio_get_value(mc->gpio_cp_reset);
+ phone_active_value = gpio_get_value(mc->gpio_phone_active);
+ pr_info("MIF : <%s>phone_active : %d\n", \
+ __func__, phone_active_value);
+ if (phone_reset && phone_active_value) {
+ phone_state = STATE_ONLINE;
+ if (mc->iod && mc->iod->modem_state_changed)
+ mc->iod->modem_state_changed(mc->iod, phone_state);
+ } else if (phone_reset && !phone_active_value) {
+ if (mc->phone_state == STATE_ONLINE) {
+ phone_state = STATE_CRASH_EXIT;
+ if (mc->iod && mc->iod->modem_state_changed)
+ mc->iod->modem_state_changed(mc->iod,
+ phone_state);
+ }
+ } else {
+ phone_state = STATE_OFFLINE;
+ if (mc->iod && mc->iod->modem_state_changed)
+ mc->iod->modem_state_changed(mc->iod, phone_state);
+ }
+
+ if (phone_active_value)
+ irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_LOW);
+ else
+ irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_HIGH);
+
+ pr_info("MIF : <%s> phone_state=%d\n", \
+ __func__, phone_state);
+
+ return IRQ_HANDLED;
+}
+static int cbp71_on(struct modem_ctl *mc)
+{
+
+ int ret;
+
+ struct dpram_link_device *dpram_ld =
+ to_dpram_link_device(mc->iod->link);
+
+ pr_err("MIF : cbp71_on()\n");
+
+ if (!mc->gpio_cp_off || !mc->gpio_cp_reset || !mc->gpio_cp_on) {
+ pr_err("MIF : <%s>no gpio data\n", __func__);
+ return -ENXIO;
+ }
+ gpio_set_value(mc->gpio_cp_on, 1);
+ mdelay(10);
+ gpio_set_value(mc->gpio_cp_reset, GPIO_LEVEL_LOW);
+ gpio_set_value(mc->gpio_cp_off, GPIO_LEVEL_LOW);
+ mdelay(600);
+ gpio_set_value(mc->gpio_cp_reset, GPIO_LEVEL_HIGH);
+ gpio_set_value(mc->gpio_cp_on, GPIO_LEVEL_HIGH);
+
+
+ mc->iod->modem_state_changed(mc->iod, STATE_BOOTING);
+
+ /* Wait here until the PHONE is up.
+ * Waiting as the this called from IOCTL->UM thread */
+ pr_debug("MIF : power control waiting for INT_MASK_CMD_PIF_INIT_DONE\n");
+
+ mc->clear_intr();
+
+ msleep(100);
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+ printk(KERN_INFO "MIF : PDA_ACTIVE sets high.\n");
+
+ ret = wait_for_completion_interruptible_timeout(
+ &dpram_ld->dpram_init_cmd, DPRAM_INIT_TIMEOUT);
+ if (!ret) {
+ /* ret will be 0 on timeout, < zero if interrupted */
+ pr_warn("MIF : INIT_START cmd was not arrived.\n");
+ pr_warn("init_cmd_wait_condition is 0 and wait timeout happend\n");
+ return -ENXIO;
+ }
+
+ ret = wait_for_completion_interruptible_timeout(
+ &dpram_ld->modem_pif_init_done, PIF_TIMEOUT);
+ if (!ret) {
+ pr_warn("MIF : PIF init failed\n");
+ pr_warn("pif_init_wait_condition is 0 and wait timeout happend\n");
+ return -ENXIO;
+ }
+
+ pr_debug("MIF : complete cbp71_on\n");
+
+ mc->iod->modem_state_changed(mc->iod, STATE_ONLINE);
+
+ return 0;
+}
+
+static int cbp71_off(struct modem_ctl *mc)
+{
+ int phone_wait_cnt = 0;
+ pr_err("MIF : cbp71_off()\n");
+
+ if (!mc->gpio_cp_off || !mc->gpio_cp_reset || !mc->gpio_phone_active) {
+ pr_err("MIF : no gpio data\n");
+ return -ENXIO;
+ }
+
+ /* confirm phone off */
+ while (1) {
+ if (gpio_get_value(mc->gpio_phone_active)) {
+ if (phone_wait_cnt > 5) {
+ pr_info("MIF:<%s> Try to Turn Phone Off(%d)\n",
+ __func__,
+ gpio_get_value(mc->gpio_phone_active));
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_cp_off, \
+ 0);
+ }
+ if (phone_wait_cnt > 7) {
+ pr_err("MIF:<%s> PHONE OFF Failed\n", __func__);
+ break;
+ }
+ phone_wait_cnt++;
+ msleep(100);
+ } else {
+ pr_info("MIF:<%s> PHONE OFF Success\n", __func__);
+ break;
+ }
+ }
+
+ /* set VIA off again */
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_cp_off, 0);
+
+ mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE);
+
+ return 0;
+}
+
+static int cbp71_reset(struct modem_ctl *mc)
+{
+ int ret = 0;
+
+ pr_debug("MIF : cbp71_reset()\n");
+
+ ret = cbp71_off(mc);
+ if (ret)
+ return -ENXIO;
+
+ msleep(100);
+
+ ret = cbp71_on(mc);
+ if (ret)
+ return -ENXIO;
+
+ return 0;
+}
+
+static int cbp71_boot_on(struct modem_ctl *mc)
+{
+ pr_debug("MIF : cbp71_boot_on()\n");
+
+ if (!mc->gpio_cp_reset) {
+ pr_err("MIF : no gpio data\n");
+ return -ENXIO;
+ }
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(600);
+ gpio_set_value(mc->gpio_cp_reset, 1);
+
+ mc->iod->modem_state_changed(mc->iod, STATE_BOOTING);
+
+ return 0;
+}
+
+static int cbp71_boot_off(struct modem_ctl *mc)
+{
+ pr_debug("MIF : cbp71_boot_off()\n");
+ return 0;
+}
+
+static void cbp71_get_ops(struct modem_ctl *mc)
+{
+ mc->ops.modem_on = cbp71_on;
+ mc->ops.modem_off = cbp71_off;
+ mc->ops.modem_reset = cbp71_reset;
+ mc->ops.modem_boot_on = cbp71_boot_on;
+ mc->ops.modem_boot_off = cbp71_boot_off;
+}
+
+int cbp71_init_modemctl_device(struct modem_ctl *mc,
+ struct modem_data *pdata)
+{
+ int ret = 0;
+ unsigned long flag = 0;
+
+ mc->gpio_cp_on = pdata->gpio_cp_on;
+ mc->gpio_cp_off = pdata->gpio_cp_off;
+ mc->gpio_cp_reset = pdata->gpio_cp_reset;
+ mc->gpio_pda_active = pdata->gpio_pda_active;
+ mc->gpio_phone_active = pdata->gpio_phone_active;
+ mc->gpio_cp_off = pdata->gpio_cp_off;
+
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ mc->clear_intr = pdata->clear_intr;
+#endif
+ mc->irq_phone_active = gpio_to_irq(mc->gpio_phone_active);
+ pr_err("MIF : <%s> PHONE_ACTIVE IRQ# = %d\n",
+ __func__, mc->irq_phone_active);
+
+ cbp71_get_ops(mc);
+ flag = IRQF_TRIGGER_HIGH;
+
+ ret = request_irq(mc->irq_phone_active,
+ phone_active_irq_handler,
+ flag, "phone_active", mc);
+ if (ret) {
+ pr_err("MIF : failed to irq_phone_active request_irq: %d\n"
+ , ret);
+ return ret;
+ }
+
+ ret = enable_irq_wake(mc->irq_phone_active);
+ if (ret) {
+ pr_err("MIF :<%s> failed to enable_irq_wake:%d\n",
+ __func__, ret);
+ free_irq(mc->irq_phone_active, mc);
+ return ret;
+ }
+ return ret;
+}
diff --git a/drivers/misc/modem_if_na/modem_modemctl_device_cmc220.c b/drivers/misc/modem_if_na/modem_modemctl_device_cmc220.c
new file mode 100644
index 0000000..48639b8
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_modemctl_device_cmc220.c
@@ -0,0 +1,254 @@
+/* /linux/drivers/misc/modem_if/modem_modemctl_device_cmc220.c
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/init.h>
+
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+
+#include <linux/platform_data/modem_na.h>
+#include "modem_prj.h"
+#include "modem_link_device_usb.h"
+
+static int cmc220_on(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] %s()\n", __func__);
+
+ if (!mc->gpio_cp_off || !mc->gpio_cp_on || !mc->gpio_cp_reset) {
+ pr_err("[MODEM_IF] no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_on, 1);
+ msleep(300);
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ msleep(100);
+ gpio_set_value(mc->gpio_cp_off, 0);
+ msleep(300);
+ mc->phone_state = STATE_BOOTING;
+ return 0;
+}
+
+static int cmc220_off(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] %s()\n", __func__);
+
+ if (!mc->gpio_cp_off || !mc->gpio_cp_on || !mc->gpio_cp_reset) {
+ pr_err("[MODEM_IF] no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ msleep(100);
+ gpio_set_value(mc->gpio_cp_off, 1);
+ msleep(100);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+
+ mc->phone_state = STATE_OFFLINE;
+
+ return 0;
+}
+
+static int cmc220_force_crash_exit(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] %s: # %d\n", __func__, ++(mc->crash_cnt));
+
+ mc->phone_state = STATE_CRASH_EXIT;/* DUMP START */
+
+ if (mc->iod && mc->iod->modem_state_changed)
+ mc->iod->modem_state_changed(mc->iod, mc->phone_state);
+
+ return 0;
+}
+
+static int cmc220_dump_reset(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] %s()\n", __func__);
+
+ if (!mc->gpio_cp_reset)
+ return -ENXIO;
+
+ gpio_set_value(mc->gpio_host_active, 0);
+ mc->cpcrash_flag = 1;
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(100);
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ msleep(300);
+
+ mc->phone_state = STATE_BOOTING;
+
+ return 0;
+}
+
+static int cmc220_reset(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] %s()\n", __func__);
+
+ if (!mc->gpio_cp_reset)
+ return -ENXIO;
+
+ if (cmc220_off(mc))
+ return -ENXIO;
+ msleep(100);
+ if (cmc220_on(mc))
+ return -ENXIO;
+
+ mc->phone_state = STATE_BOOTING;
+
+ return 0;
+}
+
+static int cmc220_boot_on(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] %s()\n", __func__);
+ return 0;
+}
+
+static int cmc220_boot_off(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] %s()\n", __func__);
+ return 0;
+}
+
+static int cmc220_get_active(struct modem_ctl *mc)
+{
+ if (!mc->gpio_phone_active || !mc->gpio_cp_reset)
+ return -ENXIO;
+
+ pr_debug("cp %d phone %d\n",
+ gpio_get_value(mc->gpio_cp_reset),
+ gpio_get_value(mc->gpio_phone_active));
+
+ if (gpio_get_value(mc->gpio_cp_reset))
+ return gpio_get_value(mc->gpio_phone_active);
+
+ return 0;
+}
+
+
+static void mc_work(struct work_struct *work_arg)
+{
+
+ struct modem_ctl *mc = container_of(work_arg, struct modem_ctl,
+ dwork.work);
+
+ int phone_active;
+
+ phone_active = cmc220_get_active(mc);
+ if (phone_active < 0) {
+ pr_err("[MODEM_IF] gpio not initialized\n");
+ return;
+ }
+
+ switch (mc->phone_state) {
+ case STATE_CRASH_EXIT:
+ case STATE_BOOTING:
+ case STATE_LOADER_DONE:
+ if (phone_active) {
+ if (mc->cpcrash_flag) {
+ pr_info("[MODEM_IF] LTE DUMP END!!\n");
+ mc->cpcrash_flag = 0;
+ }
+ }
+ break;
+ case STATE_ONLINE:
+ if (!phone_active) {
+ pr_info("[MODEM_IF] LTE CRASHED!! LTE DUMP START!!\n");
+ mc->phone_state = STATE_CRASH_EXIT;
+ if (mc->iod && mc->iod->modem_state_changed)
+ mc->iod->modem_state_changed(mc->iod,
+ mc->phone_state);
+ }
+ break;
+ default:
+ mc->phone_state = STATE_OFFLINE;
+ pr_err("[MODEM_IF], phone_status changed to invalid!!\n");
+ break;
+ }
+}
+
+
+
+static irqreturn_t phone_active_irq_handler(int irq, void *_mc)
+{
+ struct modem_ctl *mc = (struct modem_ctl *)_mc;
+
+ schedule_delayed_work(&mc->dwork, 20);
+
+ return IRQ_HANDLED;
+}
+
+static void cmc220_get_ops(struct modem_ctl *mc)
+{
+ mc->ops.modem_on = cmc220_on;
+ mc->ops.modem_off = cmc220_off;
+ mc->ops.modem_reset = cmc220_reset;
+ mc->ops.modem_boot_on = cmc220_boot_on;
+ mc->ops.modem_boot_off = cmc220_boot_off;
+ mc->ops.modem_force_crash_exit = cmc220_force_crash_exit;
+ mc->ops.modem_dump_reset = cmc220_dump_reset;
+}
+
+int cmc220_init_modemctl_device(struct modem_ctl *mc,
+ struct modem_data *pdata)
+{
+ int ret = 0;
+ struct platform_device *pdev;
+
+ mc->gpio_cp_on = pdata->gpio_cp_on;
+ mc->gpio_reset_req_n = pdata->gpio_reset_req_n;
+ mc->gpio_cp_reset = pdata->gpio_cp_reset;
+ mc->gpio_pda_active = pdata->gpio_pda_active;
+ mc->gpio_phone_active = pdata->gpio_phone_active;
+ mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int;
+ mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel;
+ mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset;
+ mc->gpio_cp_off = pdata->gpio_cp_off;
+ mc->gpio_slave_wakeup = pdata->gpio_slave_wakeup;
+ mc->gpio_host_active = pdata->gpio_host_active;
+ mc->gpio_host_wakeup = pdata->gpio_host_wakeup;
+
+ pdev = to_platform_device(mc->dev);
+ mc->irq_phone_active = gpio_to_irq(mc->gpio_phone_active);
+
+ cmc220_get_ops(mc);
+
+ dev_set_drvdata(mc->dev, mc);
+
+ INIT_DELAYED_WORK(&mc->dwork, mc_work);
+
+ ret = request_irq(mc->irq_phone_active, phone_active_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "lte_phone_active", mc);
+ if (ret) {
+ pr_err("[MODEM_IF] Failed to allocate an interrupt(%d)\n",
+ mc->irq_phone_active);
+ goto irq_fail;
+ }
+ mc->irq[0] = mc->irq_phone_active;
+ enable_irq_wake(mc->irq_phone_active);
+ /*disable_irq(mc->irq_phone_active);*/
+
+ return ret;
+
+irq_fail:
+ kfree(mc);
+ return ret;
+}
diff --git a/drivers/misc/modem_if_na/modem_net_flowcontrol_device.c b/drivers/misc/modem_if_na/modem_net_flowcontrol_device.c
new file mode 100644
index 0000000..6660079
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_net_flowcontrol_device.c
@@ -0,0 +1,115 @@
+/* /linux/drivers/misc/modem_if/modem_net_flowcontrol_device.c
+ *
+ * Copyright (C) 2011 Google, Inc.
+ * Copyright (C) 2011 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/sched.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/platform_data/modem_na.h>
+
+#include "modem_prj.h"
+
+
+#define NET_FLOWCONTROL_DEV_NAME_LEN 8
+
+static int modem_net_flowcontrol_device_open(
+ struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+static int modem_net_flowcontrol_device_release(
+ struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+static long modem_net_flowcontrol_device_ioctl(
+ struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct net *this_net;
+ struct net_device *ndev;
+ char dev_name[NET_FLOWCONTROL_DEV_NAME_LEN];
+ u8 chan;
+
+ if (copy_from_user(&chan, (void __user *)arg, sizeof(char)))
+ return -EFAULT;
+
+ if (chan > 15)
+ return -ENODEV;
+
+ snprintf(dev_name, NET_FLOWCONTROL_DEV_NAME_LEN, "rmnet%d", (int)chan);
+ this_net = get_net_ns_by_pid(current->pid);
+ ndev = __dev_get_by_name(this_net, dev_name);
+ if (ndev == NULL) {
+ pr_err("[MODEM_IF] %s: device = %s not exist\n", __func__,
+ dev_name);
+ return -ENODEV;
+ }
+
+ switch (cmd) {
+ case IOCTL_MODEM_NET_SUSPEND:
+ netif_stop_queue(ndev);
+ pr_info("[MODEM_IF] NET SUSPEND(%s)\n", dev_name);
+ break;
+ case IOCTL_MODEM_NET_RESUME:
+ netif_wake_queue(ndev);
+ pr_info("[MODEM_IF] NET RESUME(%s)\n", dev_name);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct file_operations modem_net_flowcontrol_device_fops = {
+ .owner = THIS_MODULE,
+ .open = modem_net_flowcontrol_device_open,
+ .release = modem_net_flowcontrol_device_release,
+ .unlocked_ioctl = modem_net_flowcontrol_device_ioctl,
+};
+
+static int __init modem_net_flowcontrol_device_init(void)
+{
+ int ret = 0;
+ struct io_device *net_flowcontrol_dev;
+
+ net_flowcontrol_dev = kzalloc(sizeof(struct io_device), GFP_KERNEL);
+ if (!net_flowcontrol_dev) {
+ pr_err("[MODEM_IF] net_flowcontrol_dev io device memory alloc fail\n");
+ return -ENOMEM;
+ }
+
+ net_flowcontrol_dev->miscdev.minor = MISC_DYNAMIC_MINOR;
+ net_flowcontrol_dev->miscdev.name = "modem_br";
+ net_flowcontrol_dev->miscdev.fops = &modem_net_flowcontrol_device_fops;
+
+ ret = misc_register(&net_flowcontrol_dev->miscdev);
+ if (ret)
+ pr_err("[MODEM_IF] failed to register misc br device : %s\n",
+ net_flowcontrol_dev->miscdev.name);
+
+ return ret;
+}
+
+module_init(modem_net_flowcontrol_device_init);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Samsung Modem IF Net Flowcontrol Driver");
diff --git a/drivers/misc/modem_if_na/modem_prj.h b/drivers/misc/modem_if_na/modem_prj.h
new file mode 100644
index 0000000..e5f27d8
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_prj.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __MODEM_PRJ_H__
+#define __MODEM_PRJ_H__
+
+#include <linux/wait.h>
+#include <linux/miscdevice.h>
+#include <linux/skbuff.h>
+
+
+#define MAX_LINK_DEVTYPE 3
+#define MAX_RAW_DEVS 32
+#define MAX_NUM_IO_DEV (MAX_RAW_DEVS + 4)
+
+#define IOCTL_MODEM_ON _IO('o', 0x19)
+#define IOCTL_MODEM_OFF _IO('o', 0x20)
+#define IOCTL_MODEM_RESET _IO('o', 0x21)
+#define IOCTL_MODEM_BOOT_ON _IO('o', 0x22)
+#define IOCTL_MODEM_BOOT_OFF _IO('o', 0x23)
+#define IOCTL_MODEM_START _IO('o', 0x24)
+
+#define IOCTL_MODEM_SEND _IO('o', 0x25)
+#define IOCTL_MODEM_RECV _IO('o', 0x26)
+
+#define IOCTL_MODEM_STATUS _IO('o', 0x27)
+#define IOCTL_MODEM_GOTA_START _IO('o', 0x28)
+#define IOCTL_MODEM_FW_UPDATE _IO('o', 0x29)
+
+#define IOCTL_MODEM_NET_SUSPEND _IO('o', 0x30)
+#define IOCTL_MODEM_NET_RESUME _IO('o', 0x31)
+#define IOCTL_MODEM_DUMP_START _IO('o', 0x32)
+#define IOCTL_MODEM_DUMP_UPDATE _IO('o', 0x33)
+#define IOCTL_MODEM_FORCE_CRASH_EXIT _IO('o', 0x34)
+#define IOCTL_MODEM_DUMP_RESET _IO('o', 0x35)
+
+#define IPC_HEADER_MAX_SIZE 6 /* fmt 3, raw 6, rfs 6 */
+
+#define PSD_DATA_CHID_BEGIN 0x2A
+#define PSD_DATA_CHID_END 0x38
+
+#define IP6VERSION 6
+
+#define SOURCE_MAC_ADDR {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}
+
+/* Does modem ctl structure will use state ? or status defined below ?*/
+/* Be careful!! below sequence shouldn't be changed*/
+enum modem_state {
+ STATE_OFFLINE,
+ __UNUSED__,
+ STATE_CRASH_EXIT,
+ STATE_BOOTING,
+ STATE_ONLINE,
+ STATE_NV_REBUILDING,
+ STATE_LOADER_DONE,
+};
+
+enum {
+ COM_NONE,
+ COM_ONLINE,
+ COM_HANDSHAKE,
+ COM_BOOT,
+ COM_CRASH,
+ COM_BOOT_EBL,
+};
+
+struct header_data {
+ char hdr[IPC_HEADER_MAX_SIZE];
+ unsigned len;
+ unsigned flag_len;
+ char start; /*hdlc start header 0x7F*/
+};
+
+/* buffer type for modem image */
+struct dpram_firmware {
+ char *firmware;
+ int size;
+ int is_delta;
+};
+
+
+struct vnet {
+ struct io_device *iod;
+};
+
+struct io_device {
+ struct list_head list;
+ char *name;
+
+ wait_queue_head_t wq;
+
+ struct miscdevice miscdev;
+ struct net_device *ndev;
+
+ /* ID and Format for channel on the link */
+ unsigned id;
+ enum dev_format format;
+ enum modem_io io_typ;
+ enum modem_network net_typ;
+
+ struct sk_buff_head sk_rx_q;
+
+ /* work for each io device, when delayed work needed
+ * use this for private io device rx action
+ */
+ struct delayed_work rx_work;
+
+ /* for fragmentation data from link device */
+ struct sk_buff *skb_recv;
+ struct header_data h_data;
+
+ /* called from linkdevice when a packet arrives for this iodevice */
+ int (*recv)(struct io_device *iod, const char *data, unsigned int len);
+
+ /* inform the IO device that the modem is now online or offline or
+ * crashing or whatever...
+ */
+ void (*modem_state_changed)(struct io_device *iod, enum modem_state);
+
+ struct link_device *link;
+ struct modem_ctl *mc;
+
+ void *private_data;
+};
+#define to_io_device(misc) container_of(misc, struct io_device, miscdev)
+
+struct io_raw_devices {
+ struct io_device *raw_devices[MAX_RAW_DEVS];
+ int num_of_raw_devs;
+};
+
+struct link_device {
+ char *name;
+
+ struct sk_buff_head sk_fmt_tx_q;
+ struct sk_buff_head sk_raw_tx_q;
+
+ struct workqueue_struct *tx_wq;
+ struct workqueue_struct *tx_raw_wq;
+ struct work_struct tx_work;
+ struct delayed_work tx_delayed_work;
+
+ unsigned com_state;
+
+ /* called during init to associate an io device with this link */
+ int (*attach)(struct link_device *ld, struct io_device *iod);
+
+ /* init communication - setting link driver */
+ int (*init_comm)(struct link_device *ld, struct io_device *iod);
+ /* terminate communication */
+ void (*terminate_comm)(struct link_device *ld, struct io_device *iod);
+
+ /* called by an io_device when it has a packet to send over link
+ * - the io device is passed so the link device can look at id and
+ * format fields to determine how to route/format the packet
+ */
+ int (*send)(struct link_device *ld, struct io_device *iod,
+ struct sk_buff *skb);
+
+ int (*gota_start)(struct link_device *ld, struct io_device *iod);
+ int (*dump_start)(struct link_device *ld, struct io_device *iod);
+
+ int (*modem_update)(
+ struct link_device *ld,
+ struct io_device *iod,
+ unsigned long arg);
+ int (*dump_update)(
+ struct link_device *ld,
+ struct io_device *iod,
+ unsigned long arg);
+};
+
+struct modemctl_ops {
+ int (*modem_on) (struct modem_ctl *);
+ int (*modem_off) (struct modem_ctl *);
+ int (*modem_reset) (struct modem_ctl *);
+ int (*modem_boot_on) (struct modem_ctl *);
+ int (*modem_boot_off) (struct modem_ctl *);
+ int (*modem_force_crash_exit) (struct modem_ctl *);
+ int (*modem_dump_reset) (struct modem_ctl *);
+};
+
+struct modem_ctl {
+ struct device *dev;
+ char *name;
+
+ int phone_state;
+
+ unsigned gpio_cp_on;
+ unsigned gpio_reset_req_n;
+ unsigned gpio_cp_reset;
+ unsigned gpio_pda_active;
+ unsigned gpio_phone_active;
+ unsigned gpio_cp_dump_int;
+ unsigned gpio_flm_uart_sel;
+ unsigned gpio_cp_warm_reset;
+ unsigned gpio_cp_off;
+
+ int irq_phone_active;
+
+ struct work_struct work;
+
+#if defined(CONFIG_LTE_MODEM_CMC221) || defined(CONFIG_LTE_MODEM_CMC220)
+ const struct attribute_group *group;
+ unsigned gpio_slave_wakeup;
+ unsigned gpio_host_wakeup;
+ unsigned gpio_host_active;
+ int irq_host_wakeup;
+ struct delayed_work dwork;
+ struct work_struct resume_work;
+ int wakeup_flag; /*flag for CP boot GPIO sync flag*/
+ int cpcrash_flag;
+ int crash_cnt;
+ struct completion *l2_done;
+ int irq[3];
+#endif /*CONFIG_LTE_MODEM_CMC221*/
+
+#ifdef CONFIG_INTERNAL_MODEM_IF
+ void (*clear_intr)(void);
+#endif
+ struct modemctl_ops ops;
+ struct io_device *iod;
+};
+
+int init_io_device(struct io_device *iod);
+
+#endif
diff --git a/drivers/misc/modem_if_na/modem_utils.c b/drivers/misc/modem_if_na/modem_utils.c
new file mode 100644
index 0000000..594b7b0
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_utils.c
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/netdevice.h>
+#include <linux/platform_data/modem_na.h>
+#include <linux/platform_device.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <net/ip.h>
+
+#include "modem_prj.h"
+#include "modem_utils.h"
+
+#define CMD_SUSPEND ((unsigned short)(0x00CA))
+#define CMD_RESUME ((unsigned short)(0x00CB))
+
+static char ip_flags[16];
+static char tcp_flags[32];
+
+
+/* dump2hex
+ * dump data to hex as fast as possible.
+ * the length of @buf must be greater than "@len * 3"
+ * it need 3 bytes per one data byte to print.
+ */
+static inline int dump2hex(char *buf, const char *data, size_t len)
+{
+ static const char *hex = "0123456789abcdef";
+ char *dest = buf;
+ int i;
+
+ for (i = 0; i < len; i++) {
+ *dest++ = hex[(data[i] >> 4) & 0xf];
+ *dest++ = hex[data[i] & 0xf];
+ *dest++ = ' ';
+ }
+ if (likely(len > 0))
+ dest--; /* last space will be overwrited with null */
+
+ *dest = '\0';
+
+ return dest - buf;
+}
+
+/* print buffer as hex string */
+int pr_buffer(const char *tag, const char *data, size_t data_len,
+ size_t max_len)
+{
+ size_t len = min(data_len, max_len);
+ unsigned char hexstr[len ? len * 3 : 1]; /* 1 <= sizeof <= max_len*3 */
+ dump2hex(hexstr, data, len);
+ return printk(KERN_DEBUG "[MIF] %s(%u): %s%s\n", tag, data_len, hexstr,
+ len == data_len ? "" : " ...");
+}
+
+/* flow control CMD from CP, it use in serial devices */
+int link_rx_flowctl_cmd(struct link_device *ld, const char *data, size_t len)
+{
+ unsigned short *cmd, *end = (unsigned short *)(data + len);
+ struct io_device *iod = NULL, *multi_raw_iod;
+ int i;
+
+ pr_debug("[MODEM_IF] flow control cmd: size=%d\n", len);
+
+ multi_raw_iod = find_iodev(ld, IPC_MULTI_RAW);
+ if (!multi_raw_iod || !multi_raw_iod->private_data) {
+ pr_err("[MODEM_IF] %s: no multi raw device\n", __func__);
+ return -ENODEV;
+ }
+
+ for (cmd = (unsigned short *)data; cmd < end; cmd++) {
+ switch (*cmd) {
+ case CMD_SUSPEND:
+ raw_devs_for_each(multi_raw_iod, i, iod) {
+ if (iod->io_typ == IODEV_NET && iod->ndev)
+ netif_stop_queue(iod->ndev);
+ }
+ ld->raw_tx_suspended = true;
+ pr_info("[MODEM_IF] flowctl CMD_SUSPEND(%04X)\n", *cmd);
+ break;
+
+ case CMD_RESUME:
+ raw_devs_for_each(multi_raw_iod, i, iod) {
+ if (iod->io_typ == IODEV_NET && iod->ndev)
+ netif_wake_queue(iod->ndev);
+ }
+ ld->raw_tx_suspended = false;
+ complete_all(&ld->raw_tx_resumed_by_cp);
+ pr_info("[MODEM_IF] flowctl CMD_RESUME(%04X)\n", *cmd);
+ break;
+
+ default:
+ pr_err("[MODEM_IF] flowctl BAD CMD: %04X\n", *cmd);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+void mif_print_data(char *buf, int len)
+{
+ int words = len >> 4;
+ int residue = len - (words << 4);
+ int i;
+ char *b;
+ char last[80];
+ char tb[8];
+
+ /* Make the last line, if ((len % 16) > 0) */
+ if (residue > 0) {
+ memset(last, 0, sizeof(last));
+ memset(tb, 0, sizeof(tb));
+ b = buf + (words << 4);
+
+ sprintf(last, "%04X: ", (words << 4));
+ for (i = 0; i < residue; i++) {
+ sprintf(tb, "%02x ", b[i]);
+ strcat(last, tb);
+ if ((i & 0x3) == 0x3) {
+ sprintf(tb, " ");
+ strcat(last, tb);
+ }
+ }
+ }
+
+ for (i = 0; i < words; i++) {
+ b = buf + (i << 4);
+ printk(KERN_ERR "%04X: "
+ "%02x %02x %02x %02x %02x %02x %02x %02x "
+ "%02x %02x %02x %02x %02x %02x %02x %02x\n",
+ (i << 4),
+ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
+ b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
+ }
+
+ /* Print the last line */
+ if (residue > 0)
+ printk(KERN_ERR "%s\n", last);
+}
+
+void print_sipc4_hdlc_fmt_frame(const u8 *psrc)
+{
+ u8 *frm; /* HDLC Frame */
+ struct fmt_hdr *hh; /* HDLC Header */
+ struct sipc_fmt_hdr *fh; /* IPC Header */
+ u16 hh_len = sizeof(struct fmt_hdr);
+ u16 fh_len = sizeof(struct sipc_fmt_hdr);
+ u8 *data;
+ int dlen;
+
+ /* Actual HDLC header starts from after START flag (0x7F) */
+ frm = (u8 *)(psrc + 1);
+
+ /* Point HDLC header and IPC header */
+ hh = (struct fmt_hdr *)(frm);
+ fh = (struct sipc_fmt_hdr *)(frm + hh_len);
+
+ /* Point IPC data */
+ data = frm + (hh_len + fh_len);
+ dlen = hh->len - (hh_len + fh_len);
+
+ pr_err("--------------------HDLC & FMT HEADER----------------------\n");
+
+ pr_err("HDLC len = %d, HDLC control = 0x%02x\n", hh->len, hh->control);
+
+ pr_err("(M)0x%02X, (S)0x%02X, (T)0x%02X, mseq:%d, aseq:%d, len:%d\n",
+ fh->main_cmd, fh->sub_cmd, fh->cmd_type,
+ fh->msg_seq, fh->ack_seq, fh->len);
+
+ pr_err("-----------------------IPC FMT DATA------------------------\n");
+
+ if (dlen > 0) {
+ if (dlen > 64)
+ dlen = 64;
+ mif_print_data(data, dlen);
+ }
+
+ pr_err("-----------------------------------------------------------\n");
+}
+
+void print_sipc4_fmt_frame(const u8 *psrc)
+{
+ struct sipc_fmt_hdr *fh = (struct sipc_fmt_hdr *)psrc;
+ u16 fh_len = sizeof(struct sipc_fmt_hdr);
+ u8 *data;
+ int dlen;
+
+ /* Point IPC data */
+ data = (u8 *)(psrc + fh_len);
+ dlen = fh->len - fh_len;
+
+ pr_err("----------------------IPC FMT HEADER-----------------------\n");
+
+ pr_err("(M)0x%02X, (S)0x%02X, (T)0x%02X, mseq:%d, aseq:%d, len:%d\n",
+ fh->main_cmd, fh->sub_cmd, fh->cmd_type,
+ fh->msg_seq, fh->ack_seq, fh->len);
+
+ pr_err("-----------------------IPC FMT DATA------------------------\n");
+
+ if (dlen > 0)
+ mif_print_data(data, dlen);
+
+ pr_err("-----------------------------------------------------------\n");
+}
+
+static void print_tcp_header(u8 *pkt)
+{
+ int i;
+ struct tcphdr *tcph = (struct tcphdr *)pkt;
+ u8 *opt = pkt + TCP_HDR_SIZE;
+ unsigned opt_len = (tcph->doff << 2) - TCP_HDR_SIZE;
+
+/*-------------------------------------------------------------------------
+
+ TCP Header Format
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Source Port | Destination Port |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Sequence Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Acknowledgment Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Data | |C|E|U|A|P|R|S|F| |
+ | Offset| Rsvd |W|C|R|C|S|S|Y|I| Window |
+ | | |R|E|G|K|H|T|N|N| |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Checksum | Urgent Pointer |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Options | Padding |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | data |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+-------------------------------------------------------------------------*/
+
+ memset(tcp_flags, 0, sizeof(tcp_flags));
+ if (tcph->cwr)
+ strcat(tcp_flags, "CWR ");
+ if (tcph->ece)
+ strcat(tcp_flags, "ECE ");
+ if (tcph->urg)
+ strcat(tcp_flags, "URG ");
+ if (tcph->ack)
+ strcat(tcp_flags, "ACK ");
+ if (tcph->psh)
+ strcat(tcp_flags, "PSH ");
+ if (tcph->rst)
+ strcat(tcp_flags, "RST ");
+ if (tcph->syn)
+ strcat(tcp_flags, "SYN ");
+ if (tcph->fin)
+ strcat(tcp_flags, "FIN ");
+
+ pr_err("TCP:: Src Port = %u, Dst Port = %u\n",
+ ntohs(tcph->source), ntohs(tcph->dest));
+ pr_err("TCP:: SEQ = 0x%08X(%u), ACK = 0x%08X(%u)\n",
+ ntohs(tcph->seq), ntohs(tcph->seq),
+ ntohs(tcph->ack_seq), ntohs(tcph->ack_seq));
+ pr_err("TCP:: Flags = %s\n", tcp_flags);
+ pr_err("TCP:: Window = %u, Checksum = 0x%04X, Urg Pointer = %u\n",
+ ntohs(tcph->window), ntohs(tcph->check), ntohs(tcph->urg_ptr));
+
+ if (opt_len > 0) {
+ printk(KERN_ERR "TCP:: Options = ");
+ for (i = 0; i < opt_len; i++)
+ printk("%02X ", opt[i]);
+ printk("\n");
+ }
+}
+
+static void print_udp_header(u8 *pkt)
+{
+ struct udphdr *udph = (struct udphdr *)pkt;
+
+/*-------------------------------------------------------------------------
+
+ UDP Header Format
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Source Port | Destination Port |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Length | Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | data |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+-------------------------------------------------------------------------*/
+
+ pr_err("UDP:: Src Port = %u, Dst Prt = %u\n",
+ ntohs(udph->source), ntohs(udph->dest));
+ pr_err("UDP:: Length = %u, Checksum = 0x%04X\n",
+ ntohs(udph->len), ntohs(udph->check));
+
+ if (ntohs(udph->dest) == 53)
+ pr_err("UDP:: DNS query!!!\n");
+
+ if (ntohs(udph->source) == 53)
+ pr_err("UDP:: DNS response!!!\n");
+}
+
+void print_ip4_packet(u8 *ip_pkt)
+{
+ struct iphdr *iph = (struct iphdr *)ip_pkt;
+ u8 *pkt = ip_pkt + (iph->ihl << 2);
+ u16 flags = (ntohs(iph->frag_off) & 0xE000);
+ u16 frag_off = (ntohs(iph->frag_off) & 0x1FFF);
+
+ pr_err("-----------------------------------------------------------\n");
+
+/*---------------------------------------------------------------------------
+
+ IPv4 Header Format
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Version| IHL |Type of Service| Total Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identification |C|D|M| Fragment Offset |
+ | |E|F|F| |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Time to Live | Protocol | Header Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Source Address |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Destination Address |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Options | Padding |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ IHL - Header Length
+ Flags - Consist of 3 bits
+ The 1st bit is "Congestion" bit.
+ The 2nd bit is "Dont Fragment" bit.
+ The 3rd bit is "More Fragments" bit.
+
+---------------------------------------------------------------------------*/
+
+ memset(ip_flags, 0, sizeof(ip_flags));
+ if (flags & IP_CE)
+ strcat(ip_flags, "CE ");
+ if (flags & IP_DF)
+ strcat(ip_flags, "DF ");
+ if (flags & IP_MF)
+ strcat(ip_flags, "MF ");
+
+ pr_err("IP4:: Version = %u, Header Length = %u, TOS = %u, Length = %u\n",
+ iph->version, (iph->ihl << 2), iph->tos, ntohs(iph->tot_len));
+ pr_err("IP4:: ID = %u, Fragment Offset = %u\n",
+ ntohs(iph->id), frag_off);
+ pr_err("IP4:: Flags = %s\n", ip_flags);
+ pr_err("IP4:: TTL = %u, Protocol = %u, Header Checksum = 0x%04X\n",
+ iph->ttl, iph->protocol, ntohs(iph->check));
+ pr_err("IP4:: Src IP Addr = %u.%u.%u.%u, Dst IP Addr = %u.%u.%u.%u\n",
+ ip_pkt[12], ip_pkt[13], ip_pkt[14], ip_pkt[15],
+ ip_pkt[16], ip_pkt[17], ip_pkt[18], ip_pkt[19]);
+
+ switch (iph->protocol) {
+ case 6:
+ /* TCP */
+ print_tcp_header(pkt);
+ break;
+
+ case 17:
+ /* UDP */
+ print_udp_header(pkt);
+ break;
+
+ default:
+ break;
+ }
+
+ pr_err("-----------------------------------------------------------\n");
+}
diff --git a/drivers/misc/modem_if_na/modem_utils.h b/drivers/misc/modem_if_na/modem_utils.h
new file mode 100644
index 0000000..0c37e1b
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_utils.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __MODEM_UTILS_H__
+#define __MODEM_UTILS_H__
+
+#define RAW_DEV(rdevs, i) (((struct io_raw_devices *)rdevs)->raw_devices[i])
+
+/**
+ * raw_devs_for_each - iterate raw devices of multi raw device
+ * @iod: struct io_device *iod
+ * @index: int index
+ * @multiraw: struct io_device *multiraw
+ */
+#define raw_devs_for_each(multiraw, index, iod) \
+ for (index = 0; iod = RAW_DEV(multiraw->private_data, index), \
+ index < MAX_RAW_DEVS; index++) \
+ if (iod)
+
+/**
+ * io_devs_for_each - iterate io devices of list_of_io_devices
+ * @iod: struct io_device *iod
+ * @ld: struct link_device *ld
+ */
+#define io_devs_for_each(iod, ld) \
+ list_for_each_entry(iod, (ld)->list_of_io_devices, list) \
+ if (iod->link_types & LINKTYPE((ld)->link_type))
+
+
+static inline struct io_device *find_iodev(struct link_device *ld,
+ enum dev_format format)
+{
+ struct io_device *iod;
+
+ io_devs_for_each(iod, ld) {
+ if (iod->format == format)
+ return iod;
+ }
+ return NULL;
+}
+
+/** countbits - count number of 1 bits as fastest way
+ * @n: number
+ */
+static inline unsigned int countbits(unsigned int n)
+{
+ unsigned int i;
+ for (i = 0; n != 0; i++)
+ n &= (n - 1);
+ return i;
+}
+
+/* print buffer as hex string */
+int pr_buffer(const char *tag, const char *data, size_t data_len,
+ size_t max_len);
+
+/* print a sk_buff as hex string */
+#define pr_skb(tag, skb) \
+ pr_buffer(tag, (char *)((skb)->data), (size_t)((skb)->len), (size_t)16)
+
+/* print a urb as hex string */
+#define pr_urb(tag, urb) \
+ pr_buffer(tag, (char *)((urb)->transfer_buffer), \
+ (size_t)((urb)->actual_length), (size_t)16)
+
+/* flow control CMD from CP, it use in serial devices */
+int link_rx_flowctl_cmd(struct link_device *ld, const char *data, size_t len);
+
+void mif_print_data(char *buf, int len);
+void print_sipc4_hdlc_fmt_frame(const u8 *psrc);
+void print_sipc4_fmt_frame(const u8 *psrc);
+
+/*---------------------------------------------------------------------------
+
+ IPv4 Header Format
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Version| IHL |Type of Service| Total Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identification |C|D|M| Fragment Offset |
+ | |E|F|F| |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Time to Live | Protocol | Header Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Source Address |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Destination Address |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Options | Padding |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ IHL - Header Length
+ Flags - Consist of 3 bits
+ The 1st bit is "Congestion" bit.
+ The 2nd bit is "Dont Fragment" bit.
+ The 3rd bit is "More Fragments" bit.
+
+---------------------------------------------------------------------------*/
+#define IPV4_HDR_SIZE 20
+
+/*-------------------------------------------------------------------------
+
+ TCP Header Format
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Source Port | Destination Port |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Sequence Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Acknowledgment Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Data | |C|E|U|A|P|R|S|F| |
+ | Offset| Rsvd |W|C|R|C|S|S|Y|I| Window |
+ | | |R|E|G|K|H|T|N|N| |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Checksum | Urgent Pointer |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Options | Padding |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | data |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+-------------------------------------------------------------------------*/
+#define TCP_HDR_SIZE 20
+
+/*-------------------------------------------------------------------------
+
+ UDP Header Format
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Source Port | Destination Port |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Length | Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | data |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+-------------------------------------------------------------------------*/
+#define UDP_HDR_SIZE 8
+
+void print_ip4_packet(u8 *ip_pkt);
+
+#endif/*__MODEM_UTILS_H__*/
diff --git a/drivers/misc/modem_if_na/modem_variation.h b/drivers/misc/modem_if_na/modem_variation.h
new file mode 100644
index 0000000..1bf2d13
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_variation.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __MODEM_VARIATION_H__
+#define __MODEM_VARIATION_H__
+
+#define DECLARE_MODEM_INIT(type) \
+ int type ## _init_modemctl_device(struct modem_ctl *mc, \
+ struct modem_data *pdata)
+#define DECLARE_MODEM_INIT_DUMMY(type) \
+ DECLARE_MODEM_INIT(type) { return 0; }
+
+#define DECLARE_LINK_INIT(type) \
+ struct link_device *type ## _create_link_device( \
+ struct platform_device *pdev)
+#define DECLARE_LINK_INIT_DUMMY(type) \
+ DECLARE_LINK_INIT(type) { return NULL; }
+
+#define MODEM_INIT_CALL(type) type ## _init_modemctl_device
+#define LINK_INIT_CALL(type) type ## _create_link_device
+
+/* add declaration of modem & link type */
+/* modem device support */
+#ifdef CONFIG_UMTS_MODEM_XMM6260
+DECLARE_MODEM_INIT(xmm6260);
+#else
+DECLARE_MODEM_INIT_DUMMY(xmm6260)
+#endif
+
+#ifdef CONFIG_LTE_MODEM_CMC221
+DECLARE_MODEM_INIT(cmc221);
+#else
+DECLARE_MODEM_INIT_DUMMY(cmc221)
+#endif
+
+#ifdef CONFIG_CDMA_MODEM_CBP71
+DECLARE_MODEM_INIT(cbp71);
+#else
+DECLARE_MODEM_INIT_DUMMY(cbp71)
+#endif
+
+#ifdef CONFIG_LTE_MODEM_CMC220
+DECLARE_MODEM_INIT(cmc220);
+#else
+DECLARE_MODEM_INIT_DUMMY(cmc220)
+#endif
+/* link device support */
+#ifdef CONFIG_UMTS_LINK_MIPI
+DECLARE_LINK_INIT(mipi);
+#else
+DECLARE_LINK_INIT_DUMMY(mipi)
+#endif
+
+#ifdef CONFIG_LINK_DEVICE_DPRAM
+DECLARE_LINK_INIT(dpram);
+#else
+DECLARE_LINK_INIT_DUMMY(dpram)
+#endif
+
+#ifdef CONFIG_LINK_DEVICE_USB
+DECLARE_LINK_INIT(usb);
+#else
+DECLARE_LINK_INIT_DUMMY(usb)
+#endif
+
+#ifdef CONFIG_UMTS_LINK_HSIC
+DECLARE_LINK_INIT(hsic);
+#else
+DECLARE_LINK_INIT_DUMMY(hsic)
+#endif
+
+#ifdef CONFIG_UMTS_LINK_SPI
+DECLARE_LINK_INIT(spi);
+#else
+DECLARE_LINK_INIT_DUMMY(spi)
+#endif
+
+typedef int (*modem_init_call)(struct modem_ctl *, struct modem_data *);
+modem_init_call modem_init_func[] = {
+ MODEM_INIT_CALL(xmm6260),
+ MODEM_INIT_CALL(cbp71),
+ MODEM_INIT_CALL(cmc221),
+ MODEM_INIT_CALL(cmc220),
+};
+
+typedef struct link_device *(*link_init_call)(struct platform_device *);
+link_init_call link_init_func[] = {
+ LINK_INIT_CALL(mipi),
+ LINK_INIT_CALL(dpram),
+ LINK_INIT_CALL(spi),
+ LINK_INIT_CALL(usb),
+ LINK_INIT_CALL(hsic),
+};
+
+static int call_modem_init_func(struct modem_ctl *mc, struct modem_data *pdata)
+{
+ if (modem_init_func[pdata->modem_type])
+ return modem_init_func[pdata->modem_type](mc, pdata);
+ else
+ return -ENOTSUPP;
+}
+
+static struct link_device *call_link_init_func(struct platform_device *pdev,
+ enum modem_link link_type)
+{
+ if (link_init_func[link_type])
+ return link_init_func[link_type](pdev);
+ else
+ return NULL;
+}
+
+#endif