summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorKamil Debski <k.debski@samsung.com>2013-05-22 14:34:06 +0200
committerChanho Park <chanho61.park@samsung.com>2014-11-18 11:43:20 +0900
commit174781e29962c90e81b032860a732c8b04e31a26 (patch)
treeec06b90c00829370bb2d7da4efb12c5d9d7b4de9 /drivers
parenteddf3808185f64b7474d0a85c903f599e613e673 (diff)
downloadlinux-3.10-174781e29962c90e81b032860a732c8b04e31a26.tar.gz
linux-3.10-174781e29962c90e81b032860a732c8b04e31a26.tar.bz2
linux-3.10-174781e29962c90e81b032860a732c8b04e31a26.zip
modem_if: Add modem_if driver files
All files were taken from exynos3.4 kernel. Signed-off-by: Kamil Debski <k.debski@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/misc/modem_if/Kconfig89
-rw-r--r--drivers/misc/modem_if/Makefile24
-rw-r--r--drivers/misc/modem_if/lte_modem_bootloader.c313
-rw-r--r--drivers/misc/modem_if/modem_link_device_c2c.c61
-rw-r--r--drivers/misc/modem_if/modem_link_device_c2c.h216
-rw-r--r--drivers/misc/modem_if/modem_link_device_dpram.c2111
-rw-r--r--drivers/misc/modem_if/modem_link_device_dpram.h252
-rw-r--r--drivers/misc/modem_if/modem_link_device_dpram_ext_op.c1306
-rw-r--r--drivers/misc/modem_if/modem_link_device_hsic.c1654
-rw-r--r--drivers/misc/modem_if/modem_link_device_hsic.h155
-rw-r--r--drivers/misc/modem_if/modem_link_device_memory.h501
-rw-r--r--drivers/misc/modem_if/modem_link_device_mipi.c1418
-rw-r--r--drivers/misc/modem_if/modem_link_device_mipi.h158
-rw-r--r--drivers/misc/modem_if/modem_link_device_pld.c1618
-rw-r--r--drivers/misc/modem_if/modem_link_device_pld.h231
-rw-r--r--drivers/misc/modem_if/modem_link_device_pld_ext_op.c556
-rw-r--r--drivers/misc/modem_if/modem_link_device_usb.c1027
-rw-r--r--drivers/misc/modem_if/modem_link_device_usb.h132
-rw-r--r--drivers/misc/modem_if/modem_link_pm_usb.c417
-rw-r--r--drivers/misc/modem_if/modem_link_pm_usb.h65
-rw-r--r--drivers/misc/modem_if/modem_modemctl_device_cbp71.c233
-rw-r--r--drivers/misc/modem_if/modem_modemctl_device_cbp72.c273
-rw-r--r--drivers/misc/modem_if/modem_modemctl_device_cmc221.c317
-rw-r--r--drivers/misc/modem_if/modem_modemctl_device_esc6270.c348
-rw-r--r--drivers/misc/modem_if/modem_modemctl_device_mdm6600.c801
-rw-r--r--drivers/misc/modem_if/modem_modemctl_device_xmm6260.c303
-rw-r--r--drivers/misc/modem_if/modem_modemctl_device_xmm6262.c261
-rw-r--r--drivers/misc/modem_if/modem_net_flowcontrol_device.c117
-rw-r--r--drivers/misc/modem_if/modem_prj.h725
-rw-r--r--drivers/misc/modem_if/modem_sim_slot_switch.c92
-rw-r--r--drivers/misc/modem_if/modem_utils.c1227
-rw-r--r--drivers/misc/modem_if/modem_utils.h315
-rw-r--r--drivers/misc/modem_if/modem_variation.h168
-rw-r--r--drivers/misc/modem_if/sipc4_io_device.c1668
-rw-r--r--drivers/misc/modem_if/sipc4_modem.c371
-rw-r--r--drivers/misc/modem_if/sipc5_io_device.c1620
-rw-r--r--drivers/misc/modem_if/sipc5_modem.c384
37 files changed, 21527 insertions, 0 deletions
diff --git a/drivers/misc/modem_if/Kconfig b/drivers/misc/modem_if/Kconfig
new file mode 100644
index 00000000000..234de2fad2b
--- /dev/null
+++ b/drivers/misc/modem_if/Kconfig
@@ -0,0 +1,89 @@
+menuconfig SEC_MODEM
+ bool "Samsung Mobile Modem Interface"
+ default n
+ ---help---
+ Samsung Modem Interface Driver.
+
+config UMTS_MODEM_XMM6260
+ bool "modem chip : IMC XMM6260"
+ depends on SEC_MODEM
+ default n
+
+config UMTS_MODEM_XMM6262
+ bool "modem chip : IMC XMM6262"
+ depends on SEC_MODEM
+ default n
+
+config CDMA_MODEM_CBP71
+ bool "modem chip : VIA CBP7.1"
+ depends on SEC_MODEM
+ default n
+
+config CDMA_MODEM_CBP72
+ bool "modem chip : VIA CBP7.2"
+ depends on SEC_MODEM
+ default n
+
+config LTE_MODEM_CMC221
+ bool "modem chip : SEC CMC221"
+ depends on SEC_MODEM
+ default n
+
+config CDMA_MODEM_MDM6600
+ bool "modem chip : QC MDM6600"
+ depends on SEC_MODEM
+ default n
+
+config GSM_MODEM_ESC6270
+ bool "modem chip : QC ESC6270"
+ depends on SEC_MODEM
+ default n
+
+config LINK_DEVICE_MIPI
+ bool "modem driver link device MIPI-HSI"
+ 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_PLD
+ bool "modem driver link device PLD"
+ depends on SEC_MODEM
+ default n
+config LINK_DEVICE_USB
+ bool "modem driver link device USB"
+ depends on SEC_MODEM
+ default n
+
+config LINK_DEVICE_HSIC
+ bool "modem driver link device HSIC"
+ depends on SEC_MODEM
+ default n
+
+config LINK_DEVICE_C2C
+ bool "modem driver link device C2C"
+ depends on SEC_MODEM
+ default n
+
+config IPC_CMC22x_OLD_RFS
+ bool "IPC: CMC22x ancient RFS"
+ depends on SEC_MODEM
+ default n
+
+config SIPC_VER_5
+ bool "IPC: Samsung IPC 5.0"
+ depends on SEC_MODEM
+ default n
+
+config SIM_DETECT
+ bool "SIM_DETECT pin"
+ depends on SEC_MODEM
+ default n
+
+config SIM_SLOT_SWITCH
+ bool "SIM_SLOT_SWITCH"
+ depends on SEC_MODEM
+ default n
diff --git a/drivers/misc/modem_if/Makefile b/drivers/misc/modem_if/Makefile
new file mode 100644
index 00000000000..9bedbe1c139
--- /dev/null
+++ b/drivers/misc/modem_if/Makefile
@@ -0,0 +1,24 @@
+# Makefile of modem_if
+
+EXTRA_CFLAGS += -Idrivers/misc/modem_if
+
+obj-y += sipc5_modem.o sipc5_io_device.o
+obj-y += sipc4_modem.o sipc4_io_device.o
+obj-y += modem_net_flowcontrol_device.o modem_utils.o
+
+obj-$(CONFIG_UMTS_MODEM_XMM6260) += modem_modemctl_device_xmm6260.o
+obj-$(CONFIG_UMTS_MODEM_XMM6262) += modem_modemctl_device_xmm6262.o
+obj-$(CONFIG_CDMA_MODEM_CBP71) += modem_modemctl_device_cbp71.o
+obj-$(CONFIG_CDMA_MODEM_CBP72) += modem_modemctl_device_cbp72.o
+obj-$(CONFIG_LTE_MODEM_CMC221) += modem_modemctl_device_cmc221.o lte_modem_bootloader.o
+obj-$(CONFIG_CDMA_MODEM_MDM6600) += modem_modemctl_device_mdm6600.o
+obj-$(CONFIG_GSM_MODEM_ESC6270) += modem_modemctl_device_esc6270.o
+
+obj-$(CONFIG_LINK_DEVICE_MIPI) += modem_link_device_mipi.o
+obj-$(CONFIG_LINK_DEVICE_DPRAM) += modem_link_device_dpram.o modem_link_device_dpram_ext_op.o
+obj-$(CONFIG_LINK_DEVICE_PLD) += modem_link_device_pld.o modem_link_device_pld_ext_op.o
+obj-$(CONFIG_LINK_DEVICE_USB) += modem_link_device_usb.o modem_link_pm_usb.o
+obj-$(CONFIG_LINK_DEVICE_HSIC) += modem_link_device_hsic.o
+obj-$(CONFIG_LINK_DEVICE_C2C) += modem_link_device_c2c.o
+
+obj-$(CONFIG_SIM_SLOT_SWITCH) += modem_sim_slot_switch.o
diff --git a/drivers/misc/modem_if/lte_modem_bootloader.c b/drivers/misc/modem_if/lte_modem_bootloader.c
new file mode 100644
index 00000000000..f259aaeb214
--- /dev/null
+++ b/drivers/misc/modem_if/lte_modem_bootloader.c
@@ -0,0 +1,313 @@
+/* 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/modem.h>
+#include <linux/platform_data/lte_modem_bootloader.h>
+
+#define LEN_XMIT_DELEY 100
+
+#ifdef AIRPLAIN_MODE_TEST
+int lte_airplain_mode;
+#endif
+
+enum xmit_bootloader_status {
+ XMIT_BOOT_READY = 0,
+ 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 unsigned char val)
+{
+ unsigned char buf[1];
+ int ret;
+ struct spi_message msg;
+
+ struct spi_transfer xfer = {
+ .len = 1,
+ .tx_buf = buf,
+ };
+
+ buf[0] = val;
+
+ spi_message_init(&msg);
+ spi_message_add_tail(&xfer, &msg);
+
+ ret = spi_sync(loader->spi_dev, &msg);
+
+ if (ret < 0)
+ mif_err("error %d\n", 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));
+ for (i = 0 ; i < ARRAY_SIZE(lenbuf) ; i++) {
+ ret = spi_xmit(loader, lenbuf[i]);
+ if (ret < 0)
+ return ret;
+ }
+ msleep(LEN_XMIT_DELEY);
+ }
+
+ for (i = 0 ; i < len ; i++) {
+ ret = spi_xmit(loader, addr[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ 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) {
+ mif_err("can not copy userdata\n");
+ 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)
+ mif_err("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);
+ mif_debug("LTE2AP status :%d\n", status);
+ ret = copy_to_user((unsigned int *)arg, &status,
+ sizeof(status));
+
+ break;
+#ifdef AIRPLAIN_MODE_TEST
+ case IOCTL_LTE_MODEM_AIRPLAIN_ON:
+ lte_airplain_mode = 1;
+ mif_info("IOCTL_LTE_MODEM LPM_ON\n");
+ break;
+ case IOCTL_LTE_MODEM_AIRPLAIN_OFF:
+ mif_info("IOCTL_LTE_MODEM LPM_OFF\n");
+ lte_airplain_mode = 0;
+ break;
+#endif
+ default:
+ mif_err("ioctl cmd error\n");
+ 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) {
+ mif_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)) {
+ mif_err("failed to setup spi for lte_modem_bootloader\n");
+ ret = -EINVAL;
+ goto err_setup;
+ }
+
+ loader->spi_dev = spi;
+
+ if (!spi->dev.platform_data) {
+ mif_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) {
+ mif_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) {
+ mif_err("failed to register misc dev for lte_modem_bootloader\n");
+ goto err_setup;
+ }
+ mif_info("lte_modem_bootloader successfully probed\n");
+#ifdef AIRPLAIN_MODE_TEST
+ lte_airplain_mode = 0;
+#endif
+ 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/modem_link_device_c2c.c b/drivers/misc/modem_if/modem_link_device_c2c.c
new file mode 100644
index 00000000000..acbaadf0f93
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_c2c.c
@@ -0,0 +1,61 @@
+/* /linux/drivers/new_modem_if/link_dev_c2c.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/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/proc_fs.h>
+#include <linux/if_arp.h>
+#include <linux/platform_device.h>
+
+#include <linux/platform_data/modem.h>
+#include <linux/platform_data/c2c.h>
+#include "modem_prj.h"
+#include "modem_link_device_c2c.h"
+
+struct link_device *c2c_create_link_device(struct platform_device *pdev)
+{
+ struct c2c_link_device *dpld;
+ struct link_device *ld;
+ struct modem_data *pdata;
+
+ pdata = pdev->dev.platform_data;
+
+ dpld = kzalloc(sizeof(struct c2c_link_device), GFP_KERNEL);
+ if (!dpld) {
+ mif_err("dpld == NULL\n");
+ return NULL;
+ }
+
+ wake_lock_init(&dpld->c2c_wake_lock, WAKE_LOCK_SUSPEND, "c2c_wakelock");
+ wake_lock(&dpld->c2c_wake_lock);
+
+ ld = &dpld->ld;
+ dpld->pdata = pdata;
+
+ ld->name = "c2c";
+
+ mif_info("%s is created!!!\n", dpld->ld.name);
+
+ return ld;
+}
diff --git a/drivers/misc/modem_if/modem_link_device_c2c.h b/drivers/misc/modem_if/modem_link_device_c2c.h
new file mode 100644
index 00000000000..7ec9aa635aa
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_c2c.h
@@ -0,0 +1,216 @@
+/*
+ * 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/wakelock.h>
+
+#ifndef __MODEM_LINK_DEVICE_C2C_H__
+#define __MODEM_LINK_DEVICE_C2C_H__
+
+#define DPRAM_ERR_MSG_LEN 128
+#define DPRAM_ERR_DEVICE "c2cerr"
+
+#define MAX_IDX 2
+
+#define DPRAM_BASE_PTR 0x4000000
+
+#define DPRAM_START_ADDRESS 0
+#define DPRAM_MAGIC_CODE_ADDRESS DPRAM_START_ADDRESS
+#define DPRAM_GOTA_MAGIC_CODE_SIZE 0x4
+#define DPRAM_PDA2PHONE_FORMATTED_START_ADDRESS \
+ (DPRAM_START_ADDRESS + DPRAM_GOTA_MAGIC_CODE_SIZE)
+#define BSP_DPRAM_BASE_SIZE 0x1ff8
+#define DPRAM_END_OF_ADDRESS (BSP_DPRAM_BASE_SIZE - 1)
+#define DPRAM_INTERRUPT_SIZE 0x2
+#define DPRAM_PDA2PHONE_INTERRUPT_ADDRESS \
+ (DPRAM_START_ADDRESS + BSP_DPRAM_BASE_SIZE - DPRAM_INTERRUPT_SIZE*2)
+#define DPRAM_PHONE2PDA_INTERRUPT_ADDRESS \
+ (DPRAM_START_ADDRESS + BSP_DPRAM_BASE_SIZE)
+#define DPRAM_BUFFER_SIZE \
+ (DPRAM_PHONE2PDA_INTERRUPT_ADDRESS -\
+ DPRAM_PDA2PHONE_FORMATTED_START_ADDRESS)
+#define DPRAM_INDEX_SIZE 0x2
+
+#define MAGIC_DMDL 0x4445444C
+#define MAGIC_UMDL 0x4445444D
+
+#define DPRAM_PACKET_DATA_SIZE 0x3f00
+#define DPRAM_PACKET_HEADER_SIZE 0x7
+
+#define INT_GOTA_MASK_VALID 0xA000
+#define INT_DPRAM_DUMP_MASK_VALID 0xA000
+#define MASK_CMD_RECEIVE_READY_NOTIFICATION 0xA100
+#define MASK_CMD_DOWNLOAD_START_REQUEST 0xA200
+#define MASK_CMD_DOWNLOAD_START_RESPONSE 0xA301
+#define MASK_CMD_IMAGE_SEND_REQUEST 0xA400
+#define MASK_CMD_IMAGE_SEND_RESPONSE 0xA500
+#define MASK_CMD_SEND_DONE_REQUEST 0xA600
+#define MASK_CMD_SEND_DONE_RESPONSE 0xA701
+#define MASK_CMD_STATUS_UPDATE_NOTIFICATION 0xA800
+#define MASK_CMD_UPDATE_DONE_NOTIFICATION 0xA900
+#define MASK_CMD_EFS_CLEAR_RESPONSE 0xAB00
+#define MASK_CMD_ALARM_BOOT_OK 0xAC00
+#define MASK_CMD_ALARM_BOOT_FAIL 0xAD00
+
+#define WRITEIMG_HEADER_SIZE 8
+#define WRITEIMG_TAIL_SIZE 4
+#define WRITEIMG_BODY_SIZE \
+ (DPRAM_BUFFER_SIZE - WRITEIMG_HEADER_SIZE - WRITEIMG_TAIL_SIZE)
+
+#define DPDN_DEFAULT_WRITE_LEN WRITEIMG_BODY_SIZE
+#define CMD_DL_START_REQ 0x9200
+#define CMD_IMG_SEND_REQ 0x9400
+#define CMD_DL_SEND_DONE_REQ 0x9600
+
+#define CMD_UL_START_REQ 0x9200
+#define CMD_UL_START_READY 0x9400
+#define CMD_UL_SEND_RESP 0x9601
+#define CMD_UL_SEND_DONE_RESP 0x9801
+#define CMD_UL_SEND_REQ 0xA500
+#define CMD_UL_START_RESPONSE 0xA301
+#define CMD_UL_SEND_DONE_REQ 0xA700
+#define CMD_RECEIVE_READY_NOTIFICATION 0xA100
+
+#define MASK_CMD_RESULT_FAIL 0x0002
+#define MASK_CMD_RESULT_SUCCESS 0x0001
+
+#define START_INDEX 0x007F
+#define END_INDEX 0x007E
+
+#define CMD_IMG_SEND_REQ 0x9400
+
+#define CRC_TAB_SIZE 256
+#define CRC_16_L_SEED 0xFFFF
+
+struct c2c_device {
+ /* DPRAM memory addresses */
+ u16 *in_head_addr;
+ u16 *in_tail_addr;
+ u8 *in_buff_addr;
+ unsigned long in_buff_size;
+
+ u16 *out_head_addr;
+ u16 *out_tail_addr;
+ u8 *out_buff_addr;
+ unsigned long out_buff_size;
+
+ unsigned long in_head_saved;
+ unsigned long in_tail_saved;
+ unsigned long out_head_saved;
+ unsigned long out_tail_saved;
+
+ u16 mask_req_ack;
+ u16 mask_res_ack;
+ u16 mask_send;
+};
+
+struct memory_region {
+ u8 *control;
+ u8 *fmt_out;
+ u8 *raw_out;
+ u8 *fmt_in;
+ u8 *raw_in;
+ u8 *mbx;
+};
+
+struct UldDataHeader {
+ u8 bop;
+ u16 total_frame;
+ u16 curr_frame;
+ u16 len;
+};
+
+struct c2c_link_device {
+ struct link_device ld;
+
+ struct modem_data *pdata;
+
+ /*only c2c*/
+ struct wake_lock c2c_wake_lock;
+ atomic_t raw_txq_req_ack_rcvd;
+ atomic_t fmt_txq_req_ack_rcvd;
+ u8 net_stop_flag;
+ int phone_sync;
+ u8 phone_status;
+
+ struct work_struct xmit_work_struct;
+
+ struct workqueue_struct *gota_wq;
+ struct work_struct gota_cmd_work;
+
+ struct c2c_device dev_map[MAX_IDX];
+
+ struct wake_lock dumplock;
+
+ u8 c2c_read_data[131072];
+
+ int c2c_init_cmd_wait_condition;
+ wait_queue_head_t c2c_init_cmd_wait_q;
+
+ int modem_pif_init_wait_condition;
+ wait_queue_head_t modem_pif_init_done_wait_q;
+
+ struct completion gota_download_start_complete;
+
+ int gota_send_done_cmd_wait_condition;
+ wait_queue_head_t gota_send_done_cmd_wait_q;
+
+ int gota_update_done_cmd_wait_condition;
+ wait_queue_head_t gota_update_done_cmd_wait_q;
+
+ int upload_send_req_wait_condition;
+ wait_queue_head_t upload_send_req_wait_q;
+
+ int upload_send_done_wait_condition;
+ wait_queue_head_t upload_send_done_wait_q;
+
+ int upload_start_req_wait_condition;
+ wait_queue_head_t upload_start_req_wait_q;
+
+ int upload_packet_start_condition;
+ wait_queue_head_t upload_packet_start_wait_q;
+
+ u16 gota_irq_handler_cmd;
+
+ u16 c2c_dump_handler_cmd;
+
+ int dump_region_number;
+
+ unsigned int is_c2c_err ;
+
+ int c2c_dump_start;
+ int gota_start;
+
+ char c2c_err_buf[DPRAM_ERR_MSG_LEN];
+
+ struct fasync_struct *c2c_err_async_q;
+
+ void (*clear_interrupt)(struct c2c_link_device *);
+
+ struct memory_region m_region;
+
+ unsigned long fmt_out_buff_size;
+ unsigned long raw_out_buff_size;
+ unsigned long fmt_in_buff_size;
+ unsigned long raw_in_buff_size;
+
+ struct delayed_work delayed_tx;
+ struct sk_buff *delayed_skb;
+ u8 delayed_count;
+};
+
+/* converts from struct link_device* to struct xxx_link_device* */
+#define to_c2c_link_device(linkdev) \
+ container_of(linkdev, struct c2c_link_device, ld)
+
+#endif
diff --git a/drivers/misc/modem_if/modem_link_device_dpram.c b/drivers/misc/modem_if/modem_link_device_dpram.c
new file mode 100644
index 00000000000..ea0b21e33bd
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_dpram.c
@@ -0,0 +1,2111 @@
+/*
+ * 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/time.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/kallsyms.h>
+#include <linux/platform_data/modem.h>
+
+#include "modem_prj.h"
+#include "modem_link_device_dpram.h"
+#include "modem_utils.h"
+
+/*
+** Function prototypes for basic DPRAM operations
+*/
+static inline void clear_intr(struct dpram_link_device *dpld);
+static inline u16 recv_intr(struct dpram_link_device *dpld);
+static inline void send_intr(struct dpram_link_device *dpld, u16 mask);
+
+static inline u16 get_magic(struct dpram_link_device *dpld);
+static inline void set_magic(struct dpram_link_device *dpld, u16 val);
+static inline u16 get_access(struct dpram_link_device *dpld);
+static inline void set_access(struct dpram_link_device *dpld, u16 val);
+
+static inline u32 get_tx_head(struct dpram_link_device *dpld, int id);
+static inline u32 get_tx_tail(struct dpram_link_device *dpld, int id);
+static inline void set_tx_head(struct dpram_link_device *dpld, int id, u32 in);
+static inline void set_tx_tail(struct dpram_link_device *dpld, int id, u32 out);
+static inline u8 *get_tx_buff(struct dpram_link_device *dpld, int id);
+static inline u32 get_tx_buff_size(struct dpram_link_device *dpld, int id);
+
+static inline u32 get_rx_head(struct dpram_link_device *dpld, int id);
+static inline u32 get_rx_tail(struct dpram_link_device *dpld, int id);
+static inline void set_rx_head(struct dpram_link_device *dpld, int id, u32 in);
+static inline void set_rx_tail(struct dpram_link_device *dpld, int id, u32 out);
+static inline u8 *get_rx_buff(struct dpram_link_device *dpld, int id);
+static inline u32 get_rx_buff_size(struct dpram_link_device *dpld, int id);
+
+static inline u16 get_mask_req_ack(struct dpram_link_device *dpld, int id);
+static inline u16 get_mask_res_ack(struct dpram_link_device *dpld, int id);
+static inline u16 get_mask_send(struct dpram_link_device *dpld, int id);
+
+static inline void reset_tx_circ(struct dpram_link_device *dpld, int dev);
+static inline void reset_rx_circ(struct dpram_link_device *dpld, int dev);
+
+static int trigger_force_cp_crash(struct dpram_link_device *dpld);
+
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+static inline void log_dpram_status(struct dpram_link_device *dpld, char *str)
+{
+ struct utc_time utc;
+
+ get_utc_time(&utc);
+
+ pr_info("%s%s: %s: [%02d:%02d:%02d.%03d] "
+ "ACC{%X %d} FMT{TI:%u TO:%u RI:%u RO:%u} "
+ "RAW{TI:%u TO:%u RI:%u RO:%u} INTR{0x%X}\n",
+ LOG_TAG, dpld->ld.mc->name, str,
+ utc.hour, utc.min, utc.sec, utc.msec,
+ get_magic(dpld), get_access(dpld),
+ get_tx_head(dpld, IPC_FMT), get_tx_tail(dpld, IPC_FMT),
+ get_rx_head(dpld, IPC_FMT), get_rx_tail(dpld, IPC_FMT),
+ get_tx_head(dpld, IPC_RAW), get_tx_tail(dpld, IPC_RAW),
+ get_rx_head(dpld, IPC_RAW), get_rx_tail(dpld, IPC_RAW),
+ recv_intr(dpld));
+}
+
+static void pr_trace(struct dpram_link_device *dpld, int dev,
+ struct timespec *ts, u8 *buff, u32 rcvd)
+{
+ struct link_device *ld = &dpld->ld;
+ struct utc_time utc;
+
+ ts2utc(ts, &utc);
+
+ pr_info("%s[%d-%02d-%02d %02d:%02d:%02d.%03d] %s trace (%s)\n",
+ LOG_TAG, utc.year, utc.mon, utc.day, utc.hour, utc.min, utc.sec,
+ utc.msec, get_dev_name(dev), ld->name);
+
+ mif_print_dump(buff, rcvd, 4);
+
+ return;
+}
+
+static void save_dpram_dump_work(struct work_struct *work)
+{
+ struct dpram_link_device *dpld;
+ struct link_device *ld;
+ struct trace_queue *trq;
+ struct trace_data *trd;
+ struct file *fp;
+ struct timespec *ts;
+ u8 *dump;
+ int rcvd;
+ char *path;
+ struct utc_time utc;
+
+ dpld = container_of(work, struct dpram_link_device, dump_dwork.work);
+ ld = &dpld->ld;
+ trq = &dpld->dump_list;
+ path = dpld->dump_path;
+
+ while (1) {
+ trd = trq_get_data_slot(trq);
+ if (!trd)
+ break;
+
+ ts = &trd->ts;
+ dump = trd->data;
+ rcvd = trd->size;
+
+ ts2utc(ts, &utc);
+ snprintf(path, MIF_MAX_PATH_LEN,
+ "%s/%s_dump_%d%02d%02d-%02d%02d%02d",
+ MIF_LOG_DIR, ld->name, utc.year, utc.mon, utc.day,
+ utc.hour, utc.min, utc.sec);
+
+ fp = mif_open_file(path);
+ if (fp) {
+ mif_save_file(fp, dump, rcvd);
+ mif_close_file(fp);
+ } else {
+ mif_err("%s: ERR! %s open fail\n", ld->name, path);
+ mif_print_dump(dump, rcvd, 16);
+ }
+
+ kfree(dump);
+ }
+}
+
+static void save_dpram_dump(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ struct trace_data *trd;
+ u8 *buff;
+ struct timespec ts;
+
+ buff = kzalloc(dpld->size, GFP_ATOMIC);
+ if (!buff) {
+ mif_err("%s: ERR! kzalloc fail\n", ld->name);
+ return;
+ }
+
+ getnstimeofday(&ts);
+
+ memcpy(buff, dpld->base, dpld->size);
+
+ trd = trq_get_free_slot(&dpld->dump_list);
+ if (!trd) {
+ mif_err("%s: ERR! trq_get_free_slot fail\n", ld->name);
+ mif_print_dump(buff, dpld->size, 16);
+ kfree(buff);
+ return;
+ }
+
+ memcpy(&trd->ts, &ts, sizeof(struct timespec));
+ trd->data = buff;
+ trd->size = dpld->size;
+
+ queue_delayed_work(system_nrt_wq, &dpld->dump_dwork, 0);
+}
+
+static void save_ipc_trace_work(struct work_struct *work)
+{
+ struct dpram_link_device *dpld;
+ struct link_device *ld;
+ struct trace_queue *trq;
+ struct trace_data *trd;
+ struct file *fp;
+ struct timespec *ts;
+ int dev;
+ u8 *dump;
+ int rcvd;
+ u8 *buff;
+ char *path;
+ struct utc_time utc;
+
+ dpld = container_of(work, struct dpram_link_device, trace_dwork.work);
+ ld = &dpld->ld;
+ trq = &dpld->trace_list;
+ path = dpld->trace_path;
+
+ buff = kzalloc(dpld->size << 3, GFP_KERNEL);
+ if (!buff) {
+ while (1) {
+ trd = trq_get_data_slot(trq);
+ if (!trd)
+ break;
+
+ ts = &trd->ts;
+ dev = trd->dev;
+ dump = trd->data;
+ rcvd = trd->size;
+ pr_trace(dpld, dev, ts, dump, rcvd);
+
+ kfree(dump);
+ }
+ return;
+ }
+
+ while (1) {
+ trd = trq_get_data_slot(trq);
+ if (!trd)
+ break;
+
+ ts = &trd->ts;
+ dev = trd->dev;
+ dump = trd->data;
+ rcvd = trd->size;
+
+ ts2utc(ts, &utc);
+ snprintf(path, MIF_MAX_PATH_LEN,
+ "%s/%s_%s_%d%02d%02d-%02d%02d%02d",
+ MIF_LOG_DIR, ld->name, get_dev_name(dev),
+ utc.year, utc.mon, utc.day, utc.hour, utc.min, utc.sec);
+
+ fp = mif_open_file(path);
+ if (fp) {
+ int len;
+
+ snprintf(buff, MIF_MAX_PATH_LEN,
+ "[%d-%02d-%02d %02d:%02d:%02d.%03d]\n",
+ utc.year, utc.mon, utc.day, utc.hour, utc.min,
+ utc.sec, utc.msec);
+ len = strlen(buff);
+ mif_dump2format4(dump, rcvd, (buff + len), NULL);
+ strcat(buff, "\n");
+ len = strlen(buff);
+
+ mif_save_file(fp, buff, len);
+
+ memset(buff, 0, len);
+ mif_close_file(fp);
+ } else {
+ mif_err("%s: %s open fail\n", ld->name, path);
+ pr_trace(dpld, dev, ts, dump, rcvd);
+ }
+
+ kfree(dump);
+ }
+
+ kfree(buff);
+}
+
+static void print_ipc_trace(struct dpram_link_device *dpld, int dev,
+ u8 __iomem *src, u32 qsize, u32 in, u32 out, u32 rcvd)
+{
+ u8 *buff = dpld->buff;
+ struct timespec ts;
+
+ getnstimeofday(&ts);
+
+ memset(buff, 0, dpld->size);
+ circ_read(buff, src, qsize, out, rcvd);
+
+ pr_trace(dpld, dev, &ts, buff, rcvd);
+}
+
+static void save_ipc_trace(struct dpram_link_device *dpld, int dev,
+ u8 __iomem *src, u32 qsize, u32 in, u32 out, u32 rcvd)
+{
+ struct link_device *ld = &dpld->ld;
+ struct trace_data *trd;
+ u8 *buff;
+ struct timespec ts;
+
+ buff = kzalloc(rcvd, GFP_ATOMIC);
+ if (!buff) {
+ mif_err("%s: %s: ERR! kzalloc fail\n",
+ ld->name, get_dev_name(dev));
+ print_ipc_trace(dpld, dev, src, qsize, in, out, rcvd);
+ return;
+ }
+
+ getnstimeofday(&ts);
+
+ circ_read(buff, src, qsize, out, rcvd);
+
+ trd = trq_get_free_slot(&dpld->trace_list);
+ if (!trd) {
+ mif_err("%s: %s: ERR! trq_get_free_slot fail\n",
+ ld->name, get_dev_name(dev));
+ pr_trace(dpld, dev, &ts, buff, rcvd);
+ kfree(buff);
+ return;
+ }
+
+ memcpy(&trd->ts, &ts, sizeof(struct timespec));
+ trd->dev = dev;
+ trd->data = buff;
+ trd->size = rcvd;
+
+ queue_delayed_work(system_nrt_wq, &dpld->trace_dwork, 0);
+}
+#endif
+
+static void set_dpram_map(struct dpram_link_device *dpld,
+ struct mif_irq_map *map)
+{
+ map->magic = get_magic(dpld);
+ map->access = get_access(dpld);
+
+ map->fmt_tx_in = get_tx_head(dpld, IPC_FMT);
+ map->fmt_tx_out = get_tx_tail(dpld, IPC_FMT);
+ map->fmt_rx_in = get_rx_head(dpld, IPC_FMT);
+ map->fmt_rx_out = get_rx_tail(dpld, IPC_FMT);
+ map->raw_tx_in = get_tx_head(dpld, IPC_RAW);
+ map->raw_tx_out = get_tx_tail(dpld, IPC_RAW);
+ map->raw_rx_in = get_rx_head(dpld, IPC_RAW);
+ map->raw_rx_out = get_rx_tail(dpld, IPC_RAW);
+
+ map->cp2ap = recv_intr(dpld);
+}
+
+/*
+** DPRAM operations
+*/
+static int register_isr(unsigned irq, irqreturn_t (*isr)(int, void*),
+ unsigned long flag, const char *name,
+ struct dpram_link_device *dpld)
+{
+ int ret;
+
+ ret = request_irq(irq, isr, flag, name, dpld);
+ if (ret) {
+ mif_info("%s: ERR! request_irq fail (err %d)\n", name, ret);
+ return ret;
+ }
+
+ ret = enable_irq_wake(irq);
+ if (ret)
+ mif_info("%s: ERR! enable_irq_wake fail (err %d)\n", name, ret);
+
+ mif_info("%s (#%d) handler registered\n", name, irq);
+
+ return 0;
+}
+
+static inline void clear_intr(struct dpram_link_device *dpld)
+{
+ if (likely(dpld->need_intr_clear))
+ dpld->ext_op->clear_intr(dpld);
+}
+
+static inline u16 recv_intr(struct dpram_link_device *dpld)
+{
+ return ioread16(dpld->mbx2ap);
+}
+
+static inline void send_intr(struct dpram_link_device *dpld, u16 mask)
+{
+ iowrite16(mask, dpld->mbx2cp);
+}
+
+static inline u16 get_magic(struct dpram_link_device *dpld)
+{
+ return ioread16(dpld->magic);
+}
+
+static inline void set_magic(struct dpram_link_device *dpld, u16 val)
+{
+ iowrite16(val, dpld->magic);
+}
+
+static inline u16 get_access(struct dpram_link_device *dpld)
+{
+ return ioread16(dpld->access);
+}
+
+static inline void set_access(struct dpram_link_device *dpld, u16 val)
+{
+ iowrite16(val, dpld->access);
+}
+
+static inline u32 get_tx_head(struct dpram_link_device *dpld, int id)
+{
+ return ioread16(dpld->dev[id]->txq.head);
+}
+
+static inline u32 get_tx_tail(struct dpram_link_device *dpld, int id)
+{
+ return ioread16(dpld->dev[id]->txq.tail);
+}
+
+static inline void set_tx_head(struct dpram_link_device *dpld, int id, u32 in)
+{
+ int cnt = 0;
+ u32 val = 0;
+
+ iowrite16((u16)in, dpld->dev[id]->txq.head);
+
+ while (1) {
+ /* Check head value written */
+ val = ioread16(dpld->dev[id]->txq.head);
+ if (likely(val == in))
+ return;
+
+ cnt++;
+ mif_err("ERR: %s txq.head(%d) != in(%d), count %d\n",
+ get_dev_name(id), val, in, cnt);
+ if (cnt >= MAX_RETRY_CNT)
+ break;
+
+ /* Write head value again */
+ udelay(100);
+ iowrite16((u16)in, dpld->dev[id]->txq.head);
+ }
+
+ trigger_force_cp_crash(dpld);
+}
+
+static inline void set_tx_tail(struct dpram_link_device *dpld, int id, u32 out)
+{
+ int cnt = 0;
+ u32 val = 0;
+
+ iowrite16((u16)out, dpld->dev[id]->txq.tail);
+
+ while (1) {
+ /* Check tail value written */
+ val = ioread16(dpld->dev[id]->txq.tail);
+ if (likely(val == out))
+ return;
+
+ cnt++;
+ mif_err("ERR: %s txq.tail(%d) != out(%d), count %d\n",
+ get_dev_name(id), val, out, cnt);
+ if (cnt >= MAX_RETRY_CNT)
+ break;
+
+ /* Write tail value again */
+ udelay(100);
+ iowrite16((u16)out, dpld->dev[id]->txq.tail);
+ }
+
+ trigger_force_cp_crash(dpld);
+}
+
+static inline u8 *get_tx_buff(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->txq.buff;
+}
+
+static inline u32 get_tx_buff_size(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->txq.size;
+}
+
+static inline u32 get_rx_head(struct dpram_link_device *dpld, int id)
+{
+ return ioread16(dpld->dev[id]->rxq.head);
+}
+
+static inline u32 get_rx_tail(struct dpram_link_device *dpld, int id)
+{
+ return ioread16(dpld->dev[id]->rxq.tail);
+}
+
+static inline void set_rx_head(struct dpram_link_device *dpld, int id, u32 in)
+{
+ int cnt = 0;
+ u32 val = 0;
+
+ iowrite16((u16)in, dpld->dev[id]->rxq.head);
+
+ while (1) {
+ /* Check head value written */
+ val = ioread16(dpld->dev[id]->rxq.head);
+ if (val == in)
+ return;
+
+ cnt++;
+ mif_err("ERR: %s rxq.head(%d) != in(%d), count %d\n",
+ get_dev_name(id), val, in, cnt);
+ if (cnt >= MAX_RETRY_CNT)
+ break;
+
+ /* Write head value again */
+ udelay(100);
+ iowrite16((u16)in, dpld->dev[id]->rxq.head);
+ }
+
+ trigger_force_cp_crash(dpld);
+}
+
+static inline void set_rx_tail(struct dpram_link_device *dpld, int id, u32 out)
+{
+ int cnt = 0;
+ u32 val = 0;
+
+ iowrite16((u16)out, dpld->dev[id]->rxq.tail);
+
+ while (1) {
+ /* Check tail value written */
+ val = ioread16(dpld->dev[id]->rxq.tail);
+ if (val == out)
+ return;
+
+ cnt++;
+ mif_err("ERR: %s rxq.tail(%d) != out(%d), count %d\n",
+ get_dev_name(id), val, out, cnt);
+ if (cnt >= MAX_RETRY_CNT)
+ break;
+
+ /* Write tail value again */
+ udelay(100);
+ iowrite16((u16)out, dpld->dev[id]->rxq.tail);
+ }
+
+ trigger_force_cp_crash(dpld);
+}
+
+static inline u8 *get_rx_buff(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->rxq.buff;
+}
+
+static inline u32 get_rx_buff_size(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->rxq.size;
+}
+
+static inline u16 get_mask_req_ack(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->mask_req_ack;
+}
+
+static inline u16 get_mask_res_ack(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->mask_res_ack;
+}
+
+static inline u16 get_mask_send(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->mask_send;
+}
+
+/* Get free space in the TXQ as well as in & out pointers */
+static int get_txq_space(struct dpram_link_device *dpld, int dev, u32 qsize,
+ u32 *in, u32 *out)
+{
+ struct link_device *ld = &dpld->ld;
+ int cnt = 0;
+ u32 head;
+ u32 tail;
+ int space;
+
+ while (1) {
+ head = get_tx_head(dpld, dev);
+ tail = get_tx_tail(dpld, dev);
+
+ space = circ_get_space(qsize, head, tail);
+ mif_debug("%s: %s_TXQ qsize[%u] in[%u] out[%u] space[%u]\n",
+ ld->name, get_dev_name(dev), qsize, head, tail, space);
+
+ if (circ_valid(qsize, head, tail)) {
+ *in = head;
+ *out = tail;
+ return space;
+ }
+
+ cnt++;
+ mif_err("%s: ERR! <%pf> "
+ "%s_TXQ invalid (size:%d in:%d out:%d), count %d\n",
+ ld->name, __builtin_return_address(0),
+ get_dev_name(dev), qsize, head, tail, cnt);
+ if (cnt >= MAX_RETRY_CNT)
+ break;
+
+ udelay(100);
+ }
+
+ *in = 0;
+ *out = 0;
+ return -EINVAL;
+}
+
+/* Get data size in the RXQ as well as in & out pointers */
+static int get_rxq_rcvd(struct dpram_link_device *dpld, int dev, u32 qsize,
+ u32 *in, u32 *out)
+{
+ struct link_device *ld = &dpld->ld;
+ int cnt = 0;
+ u32 head;
+ u32 tail;
+ u32 rcvd;
+
+ while (1) {
+ head = get_rx_head(dpld, dev);
+ tail = get_rx_tail(dpld, dev);
+ if (head == tail) {
+ *in = head;
+ *out = tail;
+ return 0;
+ }
+
+ rcvd = circ_get_usage(qsize, head, tail);
+ mif_debug("%s: %s_RXQ qsize[%u] in[%u] out[%u] rcvd[%u]\n",
+ ld->name, get_dev_name(dev), qsize, head, tail, rcvd);
+
+ if (circ_valid(qsize, head, tail)) {
+ *in = head;
+ *out = tail;
+ return rcvd;
+ }
+
+ cnt++;
+ mif_err("%s: ERR! <%pf> "
+ "%s_RXQ invalid (size:%d in:%d out:%d), count %d\n",
+ ld->name, __builtin_return_address(0),
+ get_dev_name(dev), qsize, head, tail, cnt);
+ if (cnt >= MAX_RETRY_CNT)
+ break;
+
+ udelay(100);
+ }
+
+ *in = 0;
+ *out = 0;
+ return -EINVAL;
+}
+
+static inline void reset_tx_circ(struct dpram_link_device *dpld, int dev)
+{
+ set_tx_head(dpld, dev, 0);
+ set_tx_tail(dpld, dev, 0);
+ if (dev == IPC_FMT)
+ trigger_force_cp_crash(dpld);
+}
+
+static inline void reset_rx_circ(struct dpram_link_device *dpld, int dev)
+{
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ trigger_force_cp_crash(dpld);
+#else
+ set_rx_tail(dpld, dev, get_rx_head(dpld, dev));
+ if (dev == IPC_FMT)
+ trigger_force_cp_crash(dpld);
+#endif
+}
+
+/*
+** CAUTION : dpram_allow_sleep() MUST be invoked after dpram_wake_up() success
+*/
+static inline bool dpram_can_sleep(struct dpram_link_device *dpld)
+{
+ return dpld->need_wake_up;
+}
+
+static int dpram_wake_up(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+
+ if (unlikely(!dpram_can_sleep(dpld)))
+ return 0;
+
+ if (dpld->ext_op->wakeup(dpld) < 0) {
+ mif_err("%s: ERR! <%pf> DPRAM wakeup fail once\n",
+ ld->name, __builtin_return_address(0));
+
+ dpld->ext_op->sleep(dpld);
+
+ udelay(10);
+
+ if (dpld->ext_op->wakeup(dpld) < 0) {
+ mif_err("%s: ERR! <%pf> DPRAM wakeup fail twice\n",
+ ld->name, __builtin_return_address(0));
+ return -EACCES;
+ }
+ }
+
+ atomic_inc(&dpld->accessing);
+ return 0;
+}
+
+static void dpram_allow_sleep(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+
+ if (unlikely(!dpram_can_sleep(dpld)))
+ return;
+
+ if (atomic_dec_return(&dpld->accessing) <= 0) {
+ dpld->ext_op->sleep(dpld);
+ atomic_set(&dpld->accessing, 0);
+ mif_debug("%s: DPRAM sleep possible\n", ld->name);
+ }
+}
+
+static int check_access(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ int i;
+ u16 magic = get_magic(dpld);
+ u16 access = get_access(dpld);
+
+ if (likely(magic == DPRAM_MAGIC_CODE && access == 1))
+ return 0;
+
+ for (i = 1; i <= 100; i++) {
+ mif_info("%s: ERR! magic:%X access:%X -> retry:%d\n",
+ ld->name, magic, access, i);
+ udelay(100);
+
+ magic = get_magic(dpld);
+ access = get_access(dpld);
+ if (likely(magic == DPRAM_MAGIC_CODE && access == 1))
+ return 0;
+ }
+
+ mif_info("%s: !CRISIS! magic:%X access:%X\n", ld->name, magic, access);
+ return -EACCES;
+}
+
+static bool ipc_active(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+
+ /* Check DPRAM mode */
+ if (ld->mode != LINK_MODE_IPC) {
+ mif_info("%s: <%pf> ld->mode != LINK_MODE_IPC\n",
+ ld->name, __builtin_return_address(0));
+ return false;
+ }
+
+ if (check_access(dpld) < 0) {
+ mif_info("%s: ERR! <%pf> check_access fail\n",
+ ld->name, __builtin_return_address(0));
+ return false;
+ }
+
+ return true;
+}
+
+static void dpram_ipc_write(struct dpram_link_device *dpld, int dev, u32 qsize,
+ u32 in, u32 out, struct sk_buff *skb)
+{
+ struct link_device *ld = &dpld->ld;
+ u8 __iomem *buff = get_tx_buff(dpld, dev);
+ u8 *src = skb->data;
+ u32 len = skb->len;
+ u32 inp;
+ struct mif_irq_map map;
+
+ circ_write(buff, src, qsize, in, len);
+
+ /* update new in pointer */
+ inp = in + len;
+ if (inp >= qsize)
+ inp -= qsize;
+ set_tx_head(dpld, dev, inp);
+
+ if (dev == IPC_FMT) {
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ char tag[MIF_MAX_STR_LEN];
+ snprintf(tag, MIF_MAX_STR_LEN, "%s: MIF2CP", ld->mc->name);
+ pr_ipc(tag, src, (len > 20 ? 20 : len));
+ log_dpram_status(dpld, "MIF2CP");
+#endif
+ set_dpram_map(dpld, &map);
+ mif_irq_log(ld->mc->msd, map, "ipc_write", sizeof("ipc_write"));
+ mif_ipc_log(MIF_IPC_AP2CP, ld->mc->msd, skb->data, skb->len);
+ }
+
+#if 1
+ if (ld->aligned && memcmp16_to_io((buff + in), src, 4)) {
+ mif_err("%s: memcmp16_to_io fail\n", ld->name);
+ trigger_force_cp_crash(dpld);
+ }
+#endif
+}
+
+static int dpram_ipc_tx(struct dpram_link_device *dpld, int dev)
+{
+ struct link_device *ld = &dpld->ld;
+ struct sk_buff_head *txq = ld->skb_txq[dev];
+ struct sk_buff *skb;
+ unsigned long int flags;
+ u32 qsize = get_tx_buff_size(dpld, dev);
+ u32 in;
+ u32 out;
+ int space;
+ int copied = 0;
+
+ spin_lock_irqsave(&dpld->tx_lock[dev], flags);
+
+ while (1) {
+ space = get_txq_space(dpld, dev, qsize, &in, &out);
+ if (unlikely(space < 0)) {
+ spin_unlock_irqrestore(&dpld->tx_lock[dev], flags);
+ reset_tx_circ(dpld, dev);
+ return space;
+ }
+
+ skb = skb_dequeue(txq);
+ if (unlikely(!skb))
+ break;
+
+ if (unlikely(space < skb->len)) {
+ atomic_set(&dpld->res_required[dev], 1);
+ skb_queue_head(txq, skb);
+ spin_unlock_irqrestore(&dpld->tx_lock[dev], flags);
+ mif_info("%s: %s "
+ "qsize[%u] in[%u] out[%u] free[%u] < len[%u]\n",
+ ld->name, get_dev_name(dev),
+ qsize, in, out, space, skb->len);
+ return -ENOSPC;
+ }
+
+ /* TX if there is enough room in the queue */
+ dpram_ipc_write(dpld, dev, qsize, in, out, skb);
+ copied += skb->len;
+ dev_kfree_skb_any(skb);
+ }
+
+ spin_unlock_irqrestore(&dpld->tx_lock[dev], flags);
+
+ return copied;
+}
+
+static int dpram_wait_for_res_ack(struct dpram_link_device *dpld, int dev)
+{
+ struct link_device *ld = &dpld->ld;
+ struct completion *cmpl = &dpld->res_ack_cmpl[dev];
+ unsigned long timeout = RES_ACK_WAIT_TIMEOUT;
+ int ret;
+ u16 mask;
+
+ mask = get_mask_req_ack(dpld, dev);
+ mif_info("%s: Send REQ_ACK 0x%04X\n", ld->name, mask);
+ send_intr(dpld, INT_NON_CMD(mask));
+
+ ret = wait_for_completion_interruptible_timeout(cmpl, timeout);
+ /* ret == 0 on timeout, ret < 0 if interrupted */
+ if (ret == 0) {
+ mif_err("%s: %s: TIMEOUT\n", ld->name, get_dev_name(dev));
+ queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], 0);
+ } else if (ret < 0) {
+ mif_err("%s: %s: interrupted (ret %d)\n",
+ ld->name, get_dev_name(dev), ret);
+ }
+
+ return ret;
+}
+
+static int dpram_process_res_ack(struct dpram_link_device *dpld, int dev)
+{
+ struct link_device *ld = &dpld->ld;
+ int ret;
+ u16 mask;
+
+ ret = dpram_ipc_tx(dpld, dev);
+ if (ret < 0) {
+ if (ret == -ENOSPC) {
+ /* dpld->res_required[dev] is set in dpram_ipc_tx() */
+ queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], 0);
+ } else {
+ mif_err("%s: ERR! ipc_tx fail (%d)\n", ld->name, ret);
+ }
+ } else {
+ if (ret > 0) {
+ mask = get_mask_send(dpld, dev);
+ send_intr(dpld, INT_NON_CMD(mask));
+ mif_debug("%s: send intr 0x%04X\n", ld->name, mask);
+ }
+ atomic_set(&dpld->res_required[dev], 0);
+ }
+
+ return ret;
+}
+
+static void dpram_fmt_tx_work(struct work_struct *work)
+{
+ struct link_device *ld;
+ struct dpram_link_device *dpld;
+ int ret;
+
+ ld = container_of(work, struct link_device, fmt_tx_dwork.work);
+ dpld = to_dpram_link_device(ld);
+
+ ret = dpram_wait_for_res_ack(dpld, IPC_FMT);
+ /* ret == 0 on timeout, ret < 0 if interrupted */
+ if (ret <= 0)
+ return;
+
+ ret = dpram_process_res_ack(dpld, IPC_FMT);
+}
+
+static void dpram_raw_tx_work(struct work_struct *work)
+{
+ struct link_device *ld;
+ struct dpram_link_device *dpld;
+ int ret;
+
+ ld = container_of(work, struct link_device, raw_tx_dwork.work);
+ dpld = to_dpram_link_device(ld);
+
+ ret = dpram_wait_for_res_ack(dpld, IPC_RAW);
+ /* ret == 0 on timeout, ret < 0 if interrupted */
+ if (ret <= 0)
+ return;
+
+ ret = dpram_process_res_ack(dpld, IPC_RAW);
+ if (ret > 0)
+ mif_netif_wake(ld);
+}
+
+static void dpram_rfs_tx_work(struct work_struct *work)
+{
+ struct link_device *ld;
+ struct dpram_link_device *dpld;
+ int ret;
+
+ ld = container_of(work, struct link_device, rfs_tx_dwork.work);
+ dpld = to_dpram_link_device(ld);
+
+ ret = dpram_wait_for_res_ack(dpld, IPC_RFS);
+ /* ret == 0 on timeout, ret < 0 if interrupted */
+ if (ret <= 0)
+ return;
+
+ ret = dpram_process_res_ack(dpld, IPC_RFS);
+}
+
+static void dpram_ipc_rx_task(unsigned long data)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)data;
+ struct link_device *ld = &dpld->ld;
+ struct io_device *iod;
+ struct mif_rxb *rxb;
+ unsigned qlen;
+ int i;
+
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ iod = dpld->iod[i];
+ qlen = rxbq_size(&dpld->rxbq[i]);
+ while (qlen > 0) {
+ rxb = rxbq_get_data_rxb(&dpld->rxbq[i]);
+ iod->recv(iod, ld, rxb->data, rxb->len);
+ rxb_clear(rxb);
+ qlen--;
+ }
+ }
+}
+
+/*
+ ret < 0 : error
+ ret == 0 : no data
+ ret > 0 : valid data
+*/
+static int dpram_recv_ipc_with_rxb(struct dpram_link_device *dpld, int dev)
+{
+ struct link_device *ld = &dpld->ld;
+ struct mif_rxb *rxb;
+ u8 __iomem *src = get_rx_buff(dpld, dev);
+ u32 qsize = get_rx_buff_size(dpld, dev);
+ u32 in;
+ u32 out;
+ u32 rcvd;
+ struct mif_irq_map map;
+
+ rcvd = get_rxq_rcvd(dpld, dev, qsize, &in, &out);
+ if (rcvd <= 0)
+ return rcvd;
+
+ if (dev == IPC_FMT) {
+ set_dpram_map(dpld, &map);
+ mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv"));
+ }
+
+ /* Allocate an rxb */
+ rxb = rxbq_get_free_rxb(&dpld->rxbq[dev]);
+ if (!rxb) {
+ mif_info("%s: ERR! %s rxbq_get_free_rxb fail\n",
+ ld->name, get_dev_name(dev));
+ return -ENOMEM;
+ }
+
+ /* Read data from each DPRAM buffer */
+ circ_read(rxb_put(rxb, rcvd), src, qsize, out, rcvd);
+
+ /* Update tail (out) pointer */
+ set_rx_tail(dpld, dev, in);
+
+ return rcvd;
+}
+
+/*
+ ret < 0 : error
+ ret == 0 : no data
+ ret > 0 : valid data
+*/
+static int dpram_recv_ipc_with_skb(struct dpram_link_device *dpld, int dev)
+{
+ struct link_device *ld = &dpld->ld;
+ struct io_device *iod = dpld->iod[dev];
+ struct sk_buff *skb;
+ u8 __iomem *src = get_rx_buff(dpld, dev);
+ u32 qsize = get_rx_buff_size(dpld, dev);
+ u32 in;
+ u32 out;
+ u32 rcvd;
+ int rest;
+ u8 *frm;
+ u8 *dst;
+ unsigned int len;
+ unsigned int pad;
+ unsigned int tot;
+ struct mif_irq_map map;
+
+ rcvd = get_rxq_rcvd(dpld, dev, qsize, &in, &out);
+ if (rcvd <= 0)
+ return rcvd;
+
+ if (dev == IPC_FMT) {
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ log_dpram_status(dpld, "CP2MIF");
+#endif
+ set_dpram_map(dpld, &map);
+ mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv"));
+ }
+
+ rest = rcvd;
+ while (rest > 0) {
+ /* Calculate the start of an SIPC5 frame */
+ frm = src + out;
+
+ /* Check the SIPC5 frame */
+ len = sipc5_check_frame_in_dev(ld, dev, frm, rest);
+ if (len <= 0) {
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ u32 tail = get_rx_tail(dpld, dev);
+ log_dpram_status(dpld, "CP2MIF");
+ save_ipc_trace(dpld, dev, src, qsize, in, tail, rcvd);
+ save_dpram_dump(dpld);
+#endif
+ return -EBADMSG;
+ }
+
+ /* Calculate total length with padding in DPRAM */
+ pad = sipc5_calc_padding_size(len);
+ tot = len + pad;
+
+ /* Allocate an skb */
+ skb = dev_alloc_skb(tot);
+ if (!skb) {
+ mif_err("%s: ERR! %s dev_alloc_skb fail\n",
+ ld->name, get_dev_name(dev));
+ return -ENOMEM;
+ }
+
+ /* Read data from each DPRAM buffer */
+ dst = skb_put(skb, tot);
+ circ_read(dst, src, qsize, out, tot);
+ skb_trim(skb, len);
+#if 1
+ if (ld->aligned && memcmp16_to_io((src + out), dst, 4)) {
+ mif_err("%s: memcmp16_to_io fail\n", ld->name);
+ trigger_force_cp_crash(dpld);
+ }
+#endif
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ if (unlikely(dev == IPC_FMT)) {
+ char str[MIF_MAX_STR_LEN];
+ snprintf(str, MIF_MAX_STR_LEN, "%s: CP2MIF",
+ ld->mc->name);
+ pr_ipc(str, skb->data, (skb->len > 20 ? 20 : skb->len));
+ }
+#endif
+
+ /* Pass the IPC frame to IOD */
+ iod->recv_skb(iod, ld, skb);
+
+ /* Calculate new 'out' pointer */
+ rest -= tot;
+ out += tot;
+ if (out >= qsize)
+ out -= qsize;
+ }
+
+ /* Update tail (out) pointer */
+ set_rx_tail(dpld, dev, in);
+
+ return rcvd;
+}
+
+static void non_command_handler(struct dpram_link_device *dpld, u16 intr)
+{
+ struct link_device *ld = &dpld->ld;
+ int i = 0;
+ int ret = 0;
+ u16 mask = 0;
+
+ if (!ipc_active(dpld))
+ return;
+
+ /* Read data from DPRAM */
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ if (dpld->use_skb)
+ ret = dpram_recv_ipc_with_skb(dpld, i);
+ else
+ ret = dpram_recv_ipc_with_rxb(dpld, i);
+ if (ret < 0)
+ reset_rx_circ(dpld, i);
+
+ /* Check and process REQ_ACK (at this time, in == out) */
+ if (intr & get_mask_req_ack(dpld, i)) {
+ mif_debug("%s: send %s_RES_ACK\n",
+ ld->name, get_dev_name(i));
+ mask |= get_mask_res_ack(dpld, i);
+ }
+ }
+
+ if (!dpld->use_skb) {
+ /* Schedule soft IRQ for RX */
+ tasklet_hi_schedule(&dpld->rx_tsk);
+ }
+
+ if (mask) {
+ send_intr(dpld, INT_NON_CMD(mask));
+ mif_debug("%s: send intr 0x%04X\n", ld->name, mask);
+ }
+
+ if (intr && INT_MASK_RES_ACK_SET) {
+ if (intr && INT_MASK_RES_ACK_R)
+ complete_all(&dpld->res_ack_cmpl[IPC_RAW]);
+ else if (intr && INT_MASK_RES_ACK_F)
+ complete_all(&dpld->res_ack_cmpl[IPC_FMT]);
+ else
+ complete_all(&dpld->res_ack_cmpl[IPC_RFS]);
+ }
+}
+
+static void handle_cp_crash(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ struct io_device *iod;
+ int i;
+
+ mif_netif_stop(ld);
+
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ mif_info("%s: purging %s_skb_txq\n", ld->name, get_dev_name(i));
+ skb_queue_purge(ld->skb_txq[i]);
+ }
+
+ iod = link_get_iod_with_format(ld, IPC_FMT);
+ iod->modem_state_changed(iod, STATE_CRASH_EXIT);
+
+ iod = link_get_iod_with_format(ld, IPC_BOOT);
+ iod->modem_state_changed(iod, STATE_CRASH_EXIT);
+}
+
+static void handle_no_crash_ack(unsigned long arg)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)arg;
+ struct link_device *ld = &dpld->ld;
+
+ mif_err("%s: ERR! No CRASH_EXIT ACK from CP\n", ld->mc->name);
+
+ if (!wake_lock_active(&dpld->wlock))
+ wake_lock(&dpld->wlock);
+
+ handle_cp_crash(dpld);
+}
+
+static int trigger_force_cp_crash(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+
+ if (ld->mode == LINK_MODE_ULOAD) {
+ mif_err("%s: CP crash is already in progress\n", ld->mc->name);
+ return 0;
+ }
+
+ disable_irq_nosync(dpld->irq);
+
+ ld->mode = LINK_MODE_ULOAD;
+ mif_err("%s: called by %pf\n", ld->name, __builtin_return_address(0));
+
+ dpram_wake_up(dpld);
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ save_dpram_dump(dpld);
+#endif
+
+ enable_irq(dpld->irq);
+
+ send_intr(dpld, INT_CMD(INT_CMD_CRASH_EXIT));
+
+ mif_add_timer(&dpld->crash_ack_timer, FORCE_CRASH_ACK_TIMEOUT,
+ handle_no_crash_ack, (unsigned long)dpld);
+
+ return 0;
+}
+
+static int dpram_init_ipc(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ int i;
+
+ if (ld->mode == LINK_MODE_IPC &&
+ get_magic(dpld) == DPRAM_MAGIC_CODE &&
+ get_access(dpld) == 1)
+ mif_info("%s: IPC already initialized\n", ld->name);
+
+ /* Clear pointers in every circular queue */
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ set_tx_head(dpld, i, 0);
+ set_tx_tail(dpld, i, 0);
+ set_rx_head(dpld, i, 0);
+ set_rx_tail(dpld, i, 0);
+ }
+
+ /* Initialize variables for efficient TX/RX processing */
+ for (i = 0; i < ld->max_ipc_dev; i++)
+ dpld->iod[i] = link_get_iod_with_format(ld, i);
+ dpld->iod[IPC_RAW] = link_get_iod_with_format(ld, IPC_MULTI_RAW);
+
+ if (dpld->iod[IPC_RAW]->recv_skb)
+ dpld->use_skb = true;
+
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ spin_lock_init(&dpld->tx_lock[i]);
+ atomic_set(&dpld->res_required[i], 0);
+ }
+
+ /* Enable IPC */
+ atomic_set(&dpld->accessing, 0);
+
+ set_magic(dpld, DPRAM_MAGIC_CODE);
+ set_access(dpld, 1);
+ if (get_magic(dpld) != DPRAM_MAGIC_CODE || get_access(dpld) != 1)
+ return -EACCES;
+
+ ld->mode = LINK_MODE_IPC;
+
+ if (wake_lock_active(&dpld->wlock))
+ wake_unlock(&dpld->wlock);
+
+ return 0;
+}
+
+static void cmd_req_active_handler(struct dpram_link_device *dpld)
+{
+ send_intr(dpld, INT_CMD(INT_CMD_RES_ACTIVE));
+}
+
+static void cmd_crash_reset_handler(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ struct io_device *iod = NULL;
+
+ ld->mode = LINK_MODE_ULOAD;
+
+ if (!wake_lock_active(&dpld->wlock))
+ wake_lock(&dpld->wlock);
+
+ mif_err("%s: Recv 0xC7 (CRASH_RESET)\n", ld->name);
+
+ iod = link_get_iod_with_format(ld, IPC_FMT);
+ iod->modem_state_changed(iod, STATE_CRASH_RESET);
+
+ iod = link_get_iod_with_format(ld, IPC_BOOT);
+ iod->modem_state_changed(iod, STATE_CRASH_RESET);
+}
+
+static void cmd_crash_exit_handler(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+
+ ld->mode = LINK_MODE_ULOAD;
+
+ if (!wake_lock_active(&dpld->wlock))
+ wake_lock(&dpld->wlock);
+
+ del_timer(&dpld->crash_ack_timer);
+
+ dpram_wake_up(dpld);
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ save_dpram_dump(dpld);
+#endif
+
+ if (dpld->ext_op && dpld->ext_op->crash_log)
+ dpld->ext_op->crash_log(dpld);
+
+ mif_err("%s: Recv 0xC9 (CRASH_EXIT)\n", ld->name);
+
+ handle_cp_crash(dpld);
+}
+
+static void cmd_phone_start_handler(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ struct io_device *iod = NULL;
+
+ mif_info("%s: Recv 0xC8 (CP_START)\n", ld->name);
+
+ dpram_init_ipc(dpld);
+
+ iod = link_get_iod_with_format(ld, IPC_FMT);
+ if (!iod) {
+ mif_info("%s: ERR! no iod\n", ld->name);
+ return;
+ }
+
+ if (dpld->ext_op && dpld->ext_op->cp_start_handler)
+ dpld->ext_op->cp_start_handler(dpld);
+
+ if (ld->mc->phone_state != STATE_ONLINE) {
+ mif_info("%s: phone_state: %d -> ONLINE\n",
+ ld->name, ld->mc->phone_state);
+ iod->modem_state_changed(iod, STATE_ONLINE);
+ }
+
+ mif_info("%s: Send 0xC2 (INIT_END)\n", ld->name);
+ send_intr(dpld, INT_CMD(INT_CMD_INIT_END));
+}
+
+static void command_handler(struct dpram_link_device *dpld, u16 cmd)
+{
+ struct link_device *ld = &dpld->ld;
+
+ switch (INT_CMD_MASK(cmd)) {
+ case INT_CMD_REQ_ACTIVE:
+ cmd_req_active_handler(dpld);
+ break;
+
+ case INT_CMD_CRASH_RESET:
+ dpld->init_status = DPRAM_INIT_STATE_NONE;
+ cmd_crash_reset_handler(dpld);
+ break;
+
+ case INT_CMD_CRASH_EXIT:
+ dpld->init_status = DPRAM_INIT_STATE_NONE;
+ cmd_crash_exit_handler(dpld);
+ break;
+
+ case INT_CMD_PHONE_START:
+ dpld->init_status = DPRAM_INIT_STATE_READY;
+ cmd_phone_start_handler(dpld);
+ complete_all(&dpld->dpram_init_cmd);
+ break;
+
+ case INT_CMD_NV_REBUILDING:
+ mif_info("%s: NV_REBUILDING\n", ld->name);
+ break;
+
+ case INT_CMD_PIF_INIT_DONE:
+ complete_all(&dpld->modem_pif_init_done);
+ break;
+
+ case INT_CMD_SILENT_NV_REBUILDING:
+ mif_info("%s: SILENT_NV_REBUILDING\n", ld->name);
+ break;
+
+ case INT_CMD_NORMAL_PWR_OFF:
+ /*ToDo:*/
+ /*kernel_sec_set_cp_ack()*/;
+ break;
+
+ case INT_CMD_REQ_TIME_SYNC:
+ case INT_CMD_CP_DEEP_SLEEP:
+ case INT_CMD_EMER_DOWN:
+ break;
+
+ default:
+ mif_info("%s: unknown command 0x%04X\n", ld->name, cmd);
+ }
+}
+
+static void ext_command_handler(struct dpram_link_device *dpld, u16 cmd)
+{
+ struct link_device *ld = &dpld->ld;
+ u16 resp;
+
+ switch (EXT_CMD_MASK(cmd)) {
+ case EXT_CMD_SET_SPEED_LOW:
+ if (dpld->dpctl->setup_speed) {
+ dpld->dpctl->setup_speed(DPRAM_SPEED_LOW);
+ resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_LOW);
+ send_intr(dpld, resp);
+ }
+ break;
+
+ case EXT_CMD_SET_SPEED_MID:
+ if (dpld->dpctl->setup_speed) {
+ dpld->dpctl->setup_speed(DPRAM_SPEED_MID);
+ resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_MID);
+ send_intr(dpld, resp);
+ }
+ break;
+
+ case EXT_CMD_SET_SPEED_HIGH:
+ if (dpld->dpctl->setup_speed) {
+ dpld->dpctl->setup_speed(DPRAM_SPEED_HIGH);
+ resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_HIGH);
+ send_intr(dpld, resp);
+ }
+ break;
+
+ default:
+ mif_info("%s: unknown command 0x%04X\n", ld->name, cmd);
+ break;
+ }
+}
+
+static void udl_command_handler(struct dpram_link_device *dpld, u16 cmd)
+{
+ struct link_device *ld = &dpld->ld;
+
+ if (cmd & UDL_RESULT_FAIL) {
+ mif_info("%s: ERR! Command failed: %04x\n", ld->name, cmd);
+ return;
+ }
+
+ switch (UDL_CMD_MASK(cmd)) {
+ case UDL_CMD_RECV_READY:
+ mif_debug("%s: Send CP-->AP RECEIVE_READY\n", ld->name);
+ send_intr(dpld, CMD_IMG_START_REQ);
+ break;
+ default:
+ complete_all(&dpld->udl_cmd_complete);
+ }
+}
+
+static inline void dpram_ipc_rx(struct dpram_link_device *dpld, u16 intr)
+{
+ if (unlikely(INT_CMD_VALID(intr)))
+ command_handler(dpld, intr);
+ else
+ non_command_handler(dpld, intr);
+}
+
+static inline void dpram_intr_handler(struct dpram_link_device *dpld, u16 intr)
+{
+ char *name = dpld->ld.name;
+
+ if (unlikely(intr == INT_POWERSAFE_FAIL)) {
+ mif_info("%s: intr == INT_POWERSAFE_FAIL\n", name);
+ return;
+ }
+
+ if (unlikely(EXT_UDL_CMD(intr))) {
+ if (likely(EXT_INT_VALID(intr))) {
+ if (UDL_CMD_VALID(intr))
+ udl_command_handler(dpld, intr);
+ else if (EXT_CMD_VALID(intr))
+ ext_command_handler(dpld, intr);
+ else
+ mif_info("%s: ERR! invalid intr 0x%04X\n",
+ name, intr);
+ } else {
+ mif_info("%s: ERR! invalid intr 0x%04X\n", name, intr);
+ }
+ return;
+ }
+
+ if (likely(INT_VALID(intr)))
+ dpram_ipc_rx(dpld, intr);
+ else
+ mif_info("%s: ERR! invalid intr 0x%04X\n", name, intr);
+}
+
+static irqreturn_t ap_idpram_irq_handler(int irq, void *data)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)data;
+ struct link_device *ld = (struct link_device *)&dpld->ld;
+ u16 int2ap = recv_intr(dpld);
+
+ if (unlikely(ld->mode == LINK_MODE_OFFLINE))
+ return IRQ_HANDLED;
+
+ dpram_intr_handler(dpld, int2ap);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t cp_idpram_irq_handler(int irq, void *data)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)data;
+ struct link_device *ld = (struct link_device *)&dpld->ld;
+ u16 int2ap;
+
+ if (unlikely(ld->mode == LINK_MODE_OFFLINE))
+ return IRQ_HANDLED;
+
+ if (dpram_wake_up(dpld) < 0) {
+ trigger_force_cp_crash(dpld);
+ return IRQ_HANDLED;
+ }
+
+ int2ap = recv_intr(dpld);
+
+ dpram_intr_handler(dpld, int2ap);
+
+ clear_intr(dpld);
+
+ dpram_allow_sleep(dpld);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ext_dpram_irq_handler(int irq, void *data)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)data;
+ struct link_device *ld = (struct link_device *)&dpld->ld;
+ u16 int2ap = recv_intr(dpld);
+
+ if (unlikely(ld->mode == LINK_MODE_OFFLINE))
+ return IRQ_HANDLED;
+
+ dpram_intr_handler(dpld, int2ap);
+
+ return IRQ_HANDLED;
+}
+
+static void dpram_send_ipc(struct link_device *ld, int dev,
+ struct io_device *iod, struct sk_buff *skb)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ struct sk_buff_head *txq = ld->skb_txq[dev];
+ int ret;
+ u16 mask;
+
+ if (unlikely(txq->qlen >= MAX_SKB_TXQ_DEPTH)) {
+ mif_err("%s: %s txq->qlen %d >= %d\n", ld->name,
+ get_dev_name(dev), txq->qlen, MAX_SKB_TXQ_DEPTH);
+ if (iod->io_typ == IODEV_NET || iod->format == IPC_MULTI_RAW) {
+ dev_kfree_skb_any(skb);
+ return;
+ }
+ }
+
+ skb_queue_tail(txq, skb);
+
+ if (dpram_wake_up(dpld) < 0) {
+ trigger_force_cp_crash(dpld);
+ return;
+ }
+
+ if (!ipc_active(dpld)) {
+ mif_info("%s: IPC is NOT active\n", ld->name);
+ goto exit;
+ }
+
+ if (atomic_read(&dpld->res_required[dev]) > 0) {
+ mif_info("%s: %s_TXQ is full\n", ld->name, get_dev_name(dev));
+ goto exit;
+ }
+
+ ret = dpram_ipc_tx(dpld, dev);
+ if (ret > 0) {
+ mask = get_mask_send(dpld, dev);
+ send_intr(dpld, INT_NON_CMD(mask));
+ } else if (ret == -ENOSPC) {
+ /*
+ ** dpld->res_required[dev] is set in dpram_ipc_tx()
+ */
+ if (dev == IPC_RAW)
+ mif_netif_stop(ld);
+ queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], 0);
+ } else {
+ mif_info("%s: dpram_ipc_tx fail (err %d)\n", ld->name, ret);
+ }
+
+exit:
+ dpram_allow_sleep(dpld);
+}
+
+static int dpram_send_cp_binary(struct link_device *ld, struct sk_buff *skb)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+
+ if (dpld->ext_op && dpld->ext_op->download_binary)
+ return dpld->ext_op->download_binary(dpld, skb);
+ else
+ return -ENODEV;
+}
+
+static int dpram_send(struct link_device *ld, struct io_device *iod,
+ struct sk_buff *skb)
+{
+ enum dev_format dev = iod->format;
+ int len = skb->len;
+
+ switch (dev) {
+ case IPC_FMT:
+ case IPC_RAW:
+ case IPC_RFS:
+ if (likely(ld->mode == LINK_MODE_IPC)) {
+ dpram_send_ipc(ld, dev, iod, skb);
+ } else {
+ mif_info("%s: ld->mode != LINK_MODE_IPC\n", ld->name);
+ dev_kfree_skb_any(skb);
+ }
+ return len;
+
+ case IPC_BOOT:
+ return dpram_send_cp_binary(ld, skb);
+
+ default:
+ mif_info("%s: ERR! no TXQ for %s\n", ld->name, iod->name);
+ dev_kfree_skb_any(skb);
+ return -ENODEV;
+ }
+}
+
+static int dpram_force_dump(struct link_device *ld, struct io_device *iod)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ trigger_force_cp_crash(dpld);
+ return 0;
+}
+
+static int dpram_dump_start(struct link_device *ld, struct io_device *iod)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+
+ if (dpld->ext_op && dpld->ext_op->dump_start)
+ return dpld->ext_op->dump_start(dpld);
+ else
+ return -ENODEV;
+}
+
+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);
+
+ if (dpld->ext_op && dpld->ext_op->dump_update)
+ return dpld->ext_op->dump_update(dpld, (void *)arg);
+ else
+ return -ENODEV;
+}
+
+static int dpram_ioctl(struct link_device *ld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ int err = 0;
+
+ mif_info("%s: cmd 0x%08X\n", ld->name, cmd);
+
+ switch (cmd) {
+ case IOCTL_DPRAM_INIT_STATUS:
+ mif_debug("%s: get dpram init status\n", ld->name);
+ return dpld->init_status;
+
+ default:
+ if (dpld->ext_ioctl) {
+ err = dpld->ext_ioctl(dpld, iod, cmd, arg);
+ } else {
+ mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd);
+ err = -EINVAL;
+ }
+
+ break;
+ }
+
+ return err;
+}
+
+static void dpram_dump_memory(struct link_device *ld, char *buff)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ dpram_wake_up(dpld);
+ memcpy(buff, dpld->base, dpld->size);
+ dpram_allow_sleep(dpld);
+}
+
+static void dpram_remap_std_16k_region(struct dpram_link_device *dpld)
+{
+ struct dpram_ipc_16k_map *dpram_map;
+ struct dpram_ipc_device *dev;
+
+ dpram_map = (struct dpram_ipc_16k_map *)dpld->base;
+
+ /* magic code and access enable fields */
+ dpld->ipc_map.magic = (u16 __iomem *)&dpram_map->magic;
+ dpld->ipc_map.access = (u16 __iomem *)&dpram_map->access;
+
+ /* FMT */
+ dev = &dpld->ipc_map.dev[IPC_FMT];
+
+ strcpy(dev->name, "FMT");
+ dev->id = IPC_FMT;
+
+ dev->txq.head = (u16 __iomem *)&dpram_map->fmt_tx_head;
+ dev->txq.tail = (u16 __iomem *)&dpram_map->fmt_tx_tail;
+ dev->txq.buff = (u8 __iomem *)&dpram_map->fmt_tx_buff[0];
+ dev->txq.size = DP_16K_FMT_TX_BUFF_SZ;
+
+ dev->rxq.head = (u16 __iomem *)&dpram_map->fmt_rx_head;
+ dev->rxq.tail = (u16 __iomem *)&dpram_map->fmt_rx_tail;
+ dev->rxq.buff = (u8 __iomem *)&dpram_map->fmt_rx_buff[0];
+ dev->rxq.size = DP_16K_FMT_RX_BUFF_SZ;
+
+ 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;
+
+ /* RAW */
+ dev = &dpld->ipc_map.dev[IPC_RAW];
+
+ strcpy(dev->name, "RAW");
+ dev->id = IPC_RAW;
+
+ dev->txq.head = (u16 __iomem *)&dpram_map->raw_tx_head;
+ dev->txq.tail = (u16 __iomem *)&dpram_map->raw_tx_tail;
+ dev->txq.buff = (u8 __iomem *)&dpram_map->raw_tx_buff[0];
+ dev->txq.size = DP_16K_RAW_TX_BUFF_SZ;
+
+ dev->rxq.head = (u16 __iomem *)&dpram_map->raw_rx_head;
+ dev->rxq.tail = (u16 __iomem *)&dpram_map->raw_rx_tail;
+ dev->rxq.buff = (u8 __iomem *)&dpram_map->raw_rx_buff[0];
+ dev->rxq.size = DP_16K_RAW_RX_BUFF_SZ;
+
+ 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;
+
+ /* interrupt ports */
+ dpld->ipc_map.mbx_cp2ap = (u16 __iomem *)&dpram_map->mbx_cp2ap;
+ dpld->ipc_map.mbx_ap2cp = (u16 __iomem *)&dpram_map->mbx_ap2cp;
+}
+
+static int dpram_table_init(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ u8 __iomem *dp_base;
+ int i;
+
+ if (!dpld->base) {
+ mif_info("%s: ERR! dpld->base == NULL\n", ld->name);
+ return -EINVAL;
+ }
+ dp_base = dpld->base;
+
+ /* Map for booting */
+ if (dpld->ext_op && dpld->ext_op->init_boot_map) {
+ dpld->ext_op->init_boot_map(dpld);
+ } else {
+ dpld->bt_map.magic = (u32 *)(dp_base);
+ dpld->bt_map.buff = (u8 *)(dp_base + DP_BOOT_BUFF_OFFSET);
+ dpld->bt_map.size = dpld->size - 8;
+ }
+
+ /* Map for download (FOTA, UDL, etc.) */
+ if (dpld->ext_op && dpld->ext_op->init_dl_map) {
+ dpld->ext_op->init_dl_map(dpld);
+ } else {
+ dpld->dl_map.magic = (u32 *)(dp_base);
+ dpld->dl_map.buff = (u8 *)(dp_base + DP_DLOAD_BUFF_OFFSET);
+ }
+
+ /* Map for upload mode */
+ if (dpld->ext_op && dpld->ext_op->init_ul_map) {
+ dpld->ext_op->init_ul_map(dpld);
+ } else {
+ dpld->ul_map.magic = (u32 *)(dp_base);
+ dpld->ul_map.buff = (u8 *)(dp_base + DP_ULOAD_BUFF_OFFSET);
+ }
+
+ /* Map for IPC */
+ if (dpld->ext_op && dpld->ext_op->init_ipc_map) {
+ dpld->ext_op->init_ipc_map(dpld);
+ } else if (dpld->dpctl->ipc_map) {
+ memcpy(&dpld->ipc_map, dpld->dpctl->ipc_map,
+ sizeof(struct dpram_ipc_map));
+ } else {
+ if (dpld->size == DPRAM_SIZE_16KB)
+ dpram_remap_std_16k_region(dpld);
+ else
+ return -EINVAL;
+ }
+
+ dpld->magic = dpld->ipc_map.magic;
+ dpld->access = dpld->ipc_map.access;
+ for (i = 0; i < ld->max_ipc_dev; i++)
+ dpld->dev[i] = &dpld->ipc_map.dev[i];
+ dpld->mbx2ap = dpld->ipc_map.mbx_cp2ap;
+ dpld->mbx2cp = dpld->ipc_map.mbx_ap2cp;
+
+ return 0;
+}
+
+static void dpram_setup_common_op(struct dpram_link_device *dpld)
+{
+ dpld->recv_intr = recv_intr;
+ dpld->send_intr = send_intr;
+ dpld->get_magic = get_magic;
+ dpld->set_magic = set_magic;
+ dpld->get_access = get_access;
+ dpld->set_access = set_access;
+ dpld->get_tx_head = get_tx_head;
+ dpld->get_tx_tail = get_tx_tail;
+ dpld->set_tx_head = set_tx_head;
+ dpld->set_tx_tail = set_tx_tail;
+ dpld->get_tx_buff = get_tx_buff;
+ dpld->get_tx_buff_size = get_tx_buff_size;
+ dpld->get_rx_head = get_rx_head;
+ dpld->get_rx_tail = get_rx_tail;
+ dpld->set_rx_head = set_rx_head;
+ dpld->set_rx_tail = set_rx_tail;
+ dpld->get_rx_buff = get_rx_buff;
+ dpld->get_rx_buff_size = get_rx_buff_size;
+ dpld->get_mask_req_ack = get_mask_req_ack;
+ dpld->get_mask_res_ack = get_mask_res_ack;
+ dpld->get_mask_send = get_mask_send;
+ dpld->ipc_rx_handler = dpram_intr_handler;
+}
+
+static int dpram_link_init(struct link_device *ld, struct io_device *iod)
+{
+ return 0;
+}
+
+static void dpram_link_terminate(struct link_device *ld, struct io_device *iod)
+{
+ if (iod->format == IPC_FMT && ld->mode == LINK_MODE_IPC) {
+ if (!atomic_read(&iod->opened)) {
+ ld->mode = LINK_MODE_OFFLINE;
+ mif_err("%s: %s: link mode is changed: IPC->OFFLINE\n",
+ iod->name, ld->name);
+ }
+ }
+
+ return;
+}
+
+struct link_device *dpram_create_link_device(struct platform_device *pdev)
+{
+ struct dpram_link_device *dpld = NULL;
+ struct link_device *ld = NULL;
+ struct modem_data *modem = NULL;
+ struct modemlink_dpram_control *dpctl = NULL;
+ struct resource *res = NULL;
+ resource_size_t res_size;
+ unsigned long task_data;
+ int ret = 0;
+ int i = 0;
+ int bsize;
+ int qsize;
+
+ /*
+ ** Alloc an instance of the DPRAM link device structure
+ */
+ dpld = kzalloc(sizeof(struct dpram_link_device), GFP_KERNEL);
+ if (!dpld) {
+ mif_err("ERR! kzalloc dpld fail\n");
+ goto err;
+ }
+ ld = &dpld->ld;
+
+ /*
+ ** Get the modem (platform) data
+ */
+ modem = (struct modem_data *)pdev->dev.platform_data;
+ if (!modem) {
+ mif_err("ERR! modem == NULL\n");
+ goto err;
+ }
+ mif_info("modem = %s\n", modem->name);
+ mif_info("link device = %s\n", modem->link_name);
+
+ /*
+ ** Retrieve modem data and DPRAM control data from the modem data
+ */
+ ld->mdm_data = modem;
+ ld->name = modem->link_name;
+ ld->ipc_version = modem->ipc_version;
+
+ if (!modem->dpram_ctl) {
+ mif_err("ERR! modem->dpram_ctl == NULL\n");
+ goto err;
+ }
+ dpctl = modem->dpram_ctl;
+
+ dpld->dpctl = dpctl;
+ dpld->type = dpctl->dp_type;
+
+ if (ld->ipc_version < SIPC_VER_50) {
+ if (!dpctl->max_ipc_dev) {
+ mif_err("%s: ERR! no max_ipc_dev\n", ld->name);
+ goto err;
+ }
+
+ ld->aligned = dpctl->aligned;
+ ld->max_ipc_dev = dpctl->max_ipc_dev;
+ } else {
+ ld->aligned = 1;
+ ld->max_ipc_dev = MAX_SIPC5_DEV;
+ }
+
+ /*
+ ** Set attributes as a link device
+ */
+ ld->init_comm = dpram_link_init;
+ ld->terminate_comm = dpram_link_terminate;
+ ld->send = dpram_send;
+ ld->force_dump = dpram_force_dump;
+ ld->dump_start = dpram_dump_start;
+ ld->dump_update = dpram_dump_update;
+ ld->ioctl = dpram_ioctl;
+
+ INIT_LIST_HEAD(&ld->list);
+
+ skb_queue_head_init(&ld->sk_fmt_tx_q);
+ skb_queue_head_init(&ld->sk_raw_tx_q);
+ skb_queue_head_init(&ld->sk_rfs_tx_q);
+ ld->skb_txq[IPC_FMT] = &ld->sk_fmt_tx_q;
+ ld->skb_txq[IPC_RAW] = &ld->sk_raw_tx_q;
+ ld->skb_txq[IPC_RFS] = &ld->sk_rfs_tx_q;
+
+ /*
+ ** Set up function pointers
+ */
+ dpram_setup_common_op(dpld);
+ dpld->dpram_dump = dpram_dump_memory;
+ dpld->ext_op = dpram_get_ext_op(modem->modem_type);
+ if (dpld->ext_op && dpld->ext_op->ioctl)
+ dpld->ext_ioctl = dpld->ext_op->ioctl;
+ if (dpld->ext_op && dpld->ext_op->wakeup && dpld->ext_op->sleep)
+ dpld->need_wake_up = true;
+ if (dpld->ext_op && dpld->ext_op->clear_intr)
+ dpld->need_intr_clear = true;
+
+ /*
+ ** Retrieve DPRAM resource
+ */
+ if (!dpctl->dp_base) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ STR_DPRAM_BASE);
+ if (!res) {
+ mif_err("%s: ERR! no DPRAM resource\n", ld->name);
+ goto err;
+ }
+ res_size = resource_size(res);
+
+ dpctl->dp_base = ioremap_nocache(res->start, res_size);
+ if (!dpctl->dp_base) {
+ mif_err("%s: ERR! ioremap_nocache for BASE fail\n",
+ ld->name);
+ goto err;
+ }
+ dpctl->dp_size = res_size;
+ }
+ dpld->base = dpctl->dp_base;
+ dpld->size = dpctl->dp_size;
+
+ mif_info("%s: type %d, aligned %d, base 0x%08X, size %d\n",
+ ld->name, dpld->type, ld->aligned, (int)dpld->base, dpld->size);
+
+ /*
+ ** Retrieve DPRAM SFR resource if exists
+ */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ STR_DPRAM_SFR_BASE);
+ if (res) {
+ res_size = resource_size(res);
+ dpld->sfr_base = ioremap_nocache(res->start, res_size);
+ if (!dpld->sfr_base) {
+ mif_err("%s: ERR! ioremap_nocache for SFR fail\n",
+ ld->name);
+ goto err;
+ }
+ }
+
+ /* Initialize DPRAM map (physical map -> logical map) */
+ ret = dpram_table_init(dpld);
+ if (ret < 0) {
+ mif_err("%s: ERR! dpram_table_init fail (err %d)\n",
+ ld->name, ret);
+ goto err;
+ }
+
+ /* Disable IPC */
+ set_magic(dpld, 0);
+ set_access(dpld, 0);
+ dpld->init_status = DPRAM_INIT_STATE_NONE;
+
+ /* Initialize locks, completions, and bottom halves */
+ snprintf(dpld->wlock_name, MIF_MAX_NAME_LEN, "%s_wlock", ld->name);
+ wake_lock_init(&dpld->wlock, WAKE_LOCK_SUSPEND, dpld->wlock_name);
+
+ init_completion(&dpld->dpram_init_cmd);
+ init_completion(&dpld->modem_pif_init_done);
+ init_completion(&dpld->udl_start_complete);
+ init_completion(&dpld->udl_cmd_complete);
+ init_completion(&dpld->crash_start_complete);
+ init_completion(&dpld->crash_recv_done);
+ for (i = 0; i < ld->max_ipc_dev; i++)
+ init_completion(&dpld->res_ack_cmpl[i]);
+
+ task_data = (unsigned long)dpld;
+ tasklet_init(&dpld->rx_tsk, dpram_ipc_rx_task, task_data);
+
+ ld->tx_wq = create_singlethread_workqueue("dpram_tx_wq");
+ if (!ld->tx_wq) {
+ mif_err("%s: ERR! fail to create tx_wq\n", ld->name);
+ goto err;
+ }
+ INIT_DELAYED_WORK(&ld->fmt_tx_dwork, dpram_fmt_tx_work);
+ INIT_DELAYED_WORK(&ld->raw_tx_dwork, dpram_raw_tx_work);
+ INIT_DELAYED_WORK(&ld->rfs_tx_dwork, dpram_rfs_tx_work);
+ ld->tx_dwork[IPC_FMT] = &ld->fmt_tx_dwork;
+ ld->tx_dwork[IPC_RAW] = &ld->raw_tx_dwork;
+ ld->tx_dwork[IPC_RFS] = &ld->rfs_tx_dwork;
+
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ INIT_DELAYED_WORK(&dpld->dump_dwork, save_dpram_dump_work);
+ INIT_DELAYED_WORK(&dpld->trace_dwork, save_ipc_trace_work);
+ spin_lock_init(&dpld->dump_list.lock);
+ spin_lock_init(&dpld->trace_list.lock);
+#endif
+
+ /* Prepare RXB queue */
+ qsize = DPRAM_MAX_RXBQ_SIZE;
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ bsize = rxbq_get_page_size(get_rx_buff_size(dpld, i));
+ dpld->rxbq[i].size = qsize;
+ dpld->rxbq[i].in = 0;
+ dpld->rxbq[i].out = 0;
+ dpld->rxbq[i].rxb = rxbq_create_pool(bsize, qsize);
+ if (!dpld->rxbq[i].rxb) {
+ mif_err("%s: ERR! %s rxbq_create_pool fail\n",
+ ld->name, get_dev_name(i));
+ goto err;
+ }
+ mif_info("%s: %s rxbq_pool created (bsize:%d, qsize:%d)\n",
+ ld->name, get_dev_name(i), bsize, qsize);
+ }
+
+ /* Prepare a multi-purpose miscellaneous buffer */
+ dpld->buff = kzalloc(dpld->size, GFP_KERNEL);
+ if (!dpld->buff) {
+ mif_err("%s: ERR! kzalloc dpld->buff fail\n", ld->name);
+ goto err;
+ }
+
+ /*
+ ** Retrieve DPRAM IRQ GPIO#, IRQ#, and IRQ flags
+ */
+ dpld->gpio_dpram_int = modem->gpio_dpram_int;
+
+ if (dpctl->dpram_irq) {
+ dpld->irq = dpctl->dpram_irq;
+ } else {
+ dpld->irq = platform_get_irq_byname(pdev, STR_DPRAM_IRQ);
+ if (dpld->irq < 0) {
+ mif_err("%s: ERR! no DPRAM IRQ resource\n", ld->name);
+ goto err;
+ }
+ }
+
+ if (dpctl->dpram_irq_flags)
+ dpld->irq_flags = dpctl->dpram_irq_flags;
+ else
+ dpld->irq_flags = (IRQF_NO_SUSPEND | IRQF_TRIGGER_LOW);
+
+ /*
+ ** Register DPRAM interrupt handler
+ */
+ snprintf(dpld->irq_name, MIF_MAX_NAME_LEN, "%s_irq", ld->name);
+ if (dpld->ext_op && dpld->ext_op->irq_handler)
+ dpld->irq_handler = dpld->ext_op->irq_handler;
+ else if (dpld->type == CP_IDPRAM)
+ dpld->irq_handler = cp_idpram_irq_handler;
+ else if (dpld->type == AP_IDPRAM)
+ dpld->irq_handler = ap_idpram_irq_handler;
+ else
+ dpld->irq_handler = ext_dpram_irq_handler;
+
+ ret = register_isr(dpld->irq, dpld->irq_handler, dpld->irq_flags,
+ dpld->irq_name, dpld);
+ if (ret)
+ goto err;
+
+ return ld;
+
+err:
+ if (dpld) {
+ if (dpld->buff)
+ kfree(dpld->buff);
+ kfree(dpld);
+ }
+
+ return NULL;
+}
+
diff --git a/drivers/misc/modem_if/modem_link_device_dpram.h b/drivers/misc/modem_if/modem_link_device_dpram.h
new file mode 100644
index 00000000000..8706fd0f339
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_dpram.h
@@ -0,0 +1,252 @@
+/*
+ * 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_DPRAM_H__
+#define __MODEM_LINK_DEVICE_DPRAM_H__
+
+#include "modem_link_device_memory.h"
+
+/*
+ magic_code +
+ access_enable +
+ fmt_tx_head + fmt_tx_tail + fmt_tx_buff +
+ raw_tx_head + raw_tx_tail + raw_tx_buff +
+ fmt_rx_head + fmt_rx_tail + fmt_rx_buff +
+ raw_rx_head + raw_rx_tail + raw_rx_buff +
+ mbx_cp2ap +
+ mbx_ap2cp
+ = 2 +
+ 2 +
+ 2 + 2 + 1336 +
+ 2 + 2 + 4564 +
+ 2 + 2 + 1336 +
+ 2 + 2 + 9124 +
+ 2 +
+ 2
+ = 16384
+*/
+#define DP_16K_FMT_TX_BUFF_SZ 1336
+#define DP_16K_RAW_TX_BUFF_SZ 4564
+#define DP_16K_FMT_RX_BUFF_SZ 1336
+#define DP_16K_RAW_RX_BUFF_SZ 9124
+
+struct dpram_ipc_16k_map {
+ u16 magic;
+ u16 access;
+
+ u16 fmt_tx_head;
+ u16 fmt_tx_tail;
+ u8 fmt_tx_buff[DP_16K_FMT_TX_BUFF_SZ];
+
+ u16 raw_tx_head;
+ u16 raw_tx_tail;
+ u8 raw_tx_buff[DP_16K_RAW_TX_BUFF_SZ];
+
+ u16 fmt_rx_head;
+ u16 fmt_rx_tail;
+ u8 fmt_rx_buff[DP_16K_FMT_RX_BUFF_SZ];
+
+ u16 raw_rx_head;
+ u16 raw_rx_tail;
+ u8 raw_rx_buff[DP_16K_RAW_RX_BUFF_SZ];
+
+ u16 mbx_cp2ap;
+ u16 mbx_ap2cp;
+};
+
+struct dpram_sfr {
+ u16 __iomem *int2cp;
+ u16 __iomem *int2ap;
+ u16 __iomem *clr_int2ap;
+ u16 __iomem *reset;
+ u16 __iomem *msg2cp;
+ u16 __iomem *msg2ap;
+};
+
+struct dpram_ext_op;
+
+struct dpram_link_device {
+ struct link_device ld;
+
+ /* DPRAM address and size */
+ enum dpram_type type; /* DPRAM type */
+ u8 __iomem *base; /* Virtual address of DPRAM */
+ u32 size; /* DPRAM size */
+
+ /* Whether or not this DPRAM can go asleep */
+ bool need_wake_up;
+
+ /* Whether or not this DPRAM needs interrupt clearing */
+ bool need_intr_clear;
+
+ /* DPRAM SFR */
+ u8 __iomem *sfr_base; /* Virtual address of SFR */
+ struct dpram_sfr sfr;
+
+ /* DPRAM IRQ GPIO# */
+ unsigned gpio_dpram_int;
+
+ /* DPRAM IRQ from CP */
+ int irq;
+ unsigned long irq_flags;
+ char irq_name[MIF_MAX_NAME_LEN];
+
+ /* Link to DPRAM control functions dependent on each platform */
+ struct modemlink_dpram_control *dpctl;
+
+ /* Physical configuration -> logical configuration */
+ union {
+ struct dpram_boot_map bt_map;
+ struct qc_dpram_boot_map qc_bt_map;
+ };
+
+ struct dpram_dload_map dl_map;
+ struct dpram_uload_map ul_map;
+
+ /* IPC device map */
+ struct dpram_ipc_map ipc_map;
+
+ /* Pointers (aliases) to IPC device map */
+ u16 __iomem *magic;
+ u16 __iomem *access;
+ struct dpram_ipc_device *dev[MAX_IPC_DEV];
+ u16 __iomem *mbx2ap;
+ u16 __iomem *mbx2cp;
+
+ /* Wakelock for DPRAM device */
+ struct wake_lock wlock;
+ char wlock_name[MIF_MAX_NAME_LEN];
+
+ /* For booting */
+ unsigned boot_start_complete;
+ struct completion dpram_init_cmd;
+ struct completion modem_pif_init_done;
+
+ /* For UDL */
+ struct tasklet_struct ul_tsk;
+ struct tasklet_struct dl_tsk;
+ struct completion udl_start_complete;
+ struct completion udl_cmd_complete;
+ struct dpram_udl_check udl_check;
+ struct dpram_udl_param udl_param;
+
+ /* For CP crash dump */
+ struct timer_list crash_ack_timer;
+ struct completion crash_start_complete;
+ struct completion crash_recv_done;
+ struct timer_list crash_timer;
+ int crash_rcvd; /* Count of CP crash dump packets received */
+
+ /* For locking TX process */
+ spinlock_t tx_lock[MAX_IPC_DEV];
+
+ /* For TX under DPRAM flow control */
+ struct completion res_ack_cmpl[MAX_IPC_DEV];
+
+ /* For efficient RX process */
+ struct tasklet_struct rx_tsk;
+ struct mif_rxb_queue rxbq[MAX_IPC_DEV];
+ struct io_device *iod[MAX_IPC_DEV];
+ bool use_skb;
+
+ /* For retransmission after buffer full state */
+ atomic_t res_required[MAX_IPC_DEV];
+
+ /* For wake-up/sleep control */
+ atomic_t accessing;
+
+ /* Multi-purpose miscellaneous buffer */
+ u8 *buff;
+
+ /* DPRAM IPC initialization status */
+ int init_status;
+
+ /* Alias to device-specific IOCTL function */
+ int (*ext_ioctl)(struct dpram_link_device *dpld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg);
+
+ /* Alias to DPRAM IRQ handler */
+ irqreturn_t (*irq_handler)(int irq, void *data);
+
+ /* For DPRAM dump */
+ u8 *dump_buff;
+ char dump_path[MIF_MAX_PATH_LEN];
+ char trace_path[MIF_MAX_PATH_LEN];
+ struct trace_queue dump_list;
+ struct trace_queue trace_list;
+ struct delayed_work dump_dwork;
+ struct delayed_work trace_dwork;
+ void (*dpram_dump)(struct link_device *ld, char *buff);
+
+ /* Common operations for each DPRAM */
+ u16 (*recv_intr)(struct dpram_link_device *dpld);
+ void (*send_intr)(struct dpram_link_device *dpld, u16 mask);
+ u16 (*get_magic)(struct dpram_link_device *dpld);
+ void (*set_magic)(struct dpram_link_device *dpld, u16 value);
+ u16 (*get_access)(struct dpram_link_device *dpld);
+ void (*set_access)(struct dpram_link_device *dpld, u16 value);
+ u32 (*get_tx_head)(struct dpram_link_device *dpld, int id);
+ u32 (*get_tx_tail)(struct dpram_link_device *dpld, int id);
+ void (*set_tx_head)(struct dpram_link_device *dpld, int id, u32 head);
+ void (*set_tx_tail)(struct dpram_link_device *dpld, int id, u32 tail);
+ u8 *(*get_tx_buff)(struct dpram_link_device *dpld, int id);
+ u32 (*get_tx_buff_size)(struct dpram_link_device *dpld, int id);
+ u32 (*get_rx_head)(struct dpram_link_device *dpld, int id);
+ u32 (*get_rx_tail)(struct dpram_link_device *dpld, int id);
+ void (*set_rx_head)(struct dpram_link_device *dpld, int id, u32 head);
+ void (*set_rx_tail)(struct dpram_link_device *dpld, int id, u32 tail);
+ u8 *(*get_rx_buff)(struct dpram_link_device *dpld, int id);
+ u32 (*get_rx_buff_size)(struct dpram_link_device *dpld, int id);
+ u16 (*get_mask_req_ack)(struct dpram_link_device *dpld, int id);
+ u16 (*get_mask_res_ack)(struct dpram_link_device *dpld, int id);
+ u16 (*get_mask_send)(struct dpram_link_device *dpld, int id);
+ void (*ipc_rx_handler)(struct dpram_link_device *dpld, u16 int2ap);
+
+ /* Extended operations for various modems */
+ struct dpram_ext_op *ext_op;
+};
+
+/* converts from struct link_device* to struct xxx_link_device* */
+#define to_dpram_link_device(linkdev) \
+ container_of(linkdev, struct dpram_link_device, ld)
+
+struct dpram_ext_op {
+ int exist;
+
+ void (*init_boot_map)(struct dpram_link_device *dpld);
+ void (*init_dl_map)(struct dpram_link_device *dpld);
+ void (*init_ul_map)(struct dpram_link_device *dpld);
+ void (*init_ipc_map)(struct dpram_link_device *dpld);
+
+ int (*download_binary)(struct dpram_link_device *dpld,
+ struct sk_buff *skb);
+
+ void (*cp_start_handler)(struct dpram_link_device *dpld);
+
+ void (*crash_log)(struct dpram_link_device *dpld);
+ int (*dump_start)(struct dpram_link_device *dpld);
+ int (*dump_update)(struct dpram_link_device *dpld, void *arg);
+
+ int (*ioctl)(struct dpram_link_device *dpld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg);
+
+ irqreturn_t (*irq_handler)(int irq, void *data);
+ void (*clear_intr)(struct dpram_link_device *dpld);
+
+ int (*wakeup)(struct dpram_link_device *dpld);
+ void (*sleep)(struct dpram_link_device *dpld);
+};
+
+struct dpram_ext_op *dpram_get_ext_op(enum modem_t modem);
+
+#endif
diff --git a/drivers/misc/modem_if/modem_link_device_dpram_ext_op.c b/drivers/misc/modem_if/modem_link_device_dpram_ext_op.c
new file mode 100644
index 00000000000..83f2cc302d8
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_dpram_ext_op.c
@@ -0,0 +1,1306 @@
+/*
+ * 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/time.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/kallsyms.h>
+#include <linux/platform_data/modem.h>
+
+#include "modem_prj.h"
+#include "modem_link_device_dpram.h"
+#include "modem_utils.h"
+
+#if defined(CONFIG_LTE_MODEM_CMC221)
+/*
+** For host (flashless) booting via DPRAM
+*/
+#define CMC22x_AP_BOOT_DOWN_DONE 0x54329876
+#define CMC22x_CP_REQ_MAIN_BIN 0xA5A5A5A5
+#define CMC22x_CP_REQ_NV_DATA 0x5A5A5A5A
+#define CMC22x_CP_DUMP_MAGIC 0xDEADDEAD
+
+#define CMC22x_HOST_DOWN_START 0x1234
+#define CMC22x_HOST_DOWN_END 0x4321
+#define CMC22x_REG_NV_DOWN_END 0xABCD
+#define CMC22x_CAL_NV_DOWN_END 0xDCBA
+
+#define CMC22x_1ST_BUFF_READY 0xAAAA
+#define CMC22x_2ND_BUFF_READY 0xBBBB
+#define CMC22x_1ST_BUFF_FULL 0x1111
+#define CMC22x_2ND_BUFF_FULL 0x2222
+
+#define CMC22x_CP_RECV_NV_END 0x8888
+#define CMC22x_CP_CAL_OK 0x4F4B
+#define CMC22x_CP_CAL_BAD 0x4552
+#define CMC22x_CP_DUMP_END 0xFADE
+
+#define CMC22x_DUMP_BUFF_SIZE 8192 /* 8 KB */
+#endif
+
+#if defined(CONFIG_CDMA_MODEM_CBP72)
+static void cbp72_init_boot_map(struct dpram_link_device *dpld)
+{
+ struct dpram_boot_map *bt_map = &dpld->bt_map;
+
+ bt_map->magic = (u32 *)dpld->base;
+ bt_map->buff = (u8 *)(dpld->base + DP_BOOT_BUFF_OFFSET);
+ bt_map->size = dpld->size - 4;
+}
+
+static void cbp72_init_dl_map(struct dpram_link_device *dpld)
+{
+ dpld->dl_map.magic = (u32 *)dpld->base;
+ dpld->dl_map.buff = (u8 *)(dpld->base + DP_DLOAD_BUFF_OFFSET);
+}
+
+static int _cbp72_edpram_wait_resp(struct dpram_link_device *dpld, u32 resp)
+{
+ struct link_device *ld = &dpld->ld;
+ int ret;
+ int int2cp;
+
+ ret = wait_for_completion_interruptible_timeout(
+ &dpld->udl_cmd_complete, UDL_TIMEOUT);
+ if (!ret) {
+ mif_info("%s: ERR! No UDL_CMD_RESP!!!\n", ld->name);
+ return -ENXIO;
+ }
+
+ int2cp = dpld->recv_intr(dpld);
+ mif_debug("%s: int2cp = 0x%x\n", ld->name, int2cp);
+ if (resp == int2cp || int2cp == 0xA700)
+ return int2cp;
+ else
+ return -EINVAL;
+}
+
+static int _cbp72_edpram_download_bin(struct dpram_link_device *dpld,
+ struct sk_buff *skb)
+{
+ struct link_device *ld = &dpld->ld;
+ struct dpram_boot_frame *bf = (struct dpram_boot_frame *)skb->data;
+ u8 __iomem *buff = dpld->bt_map.buff;
+ int err = 0;
+
+ if (bf->len > dpld->bt_map.size) {
+ mif_info("%s: ERR! Out of DPRAM boundary\n", ld->name);
+ err = -EINVAL;
+ goto exit;
+ }
+
+ if (bf->len)
+ memcpy(buff, bf->data, bf->len);
+
+ init_completion(&dpld->udl_cmd_complete);
+
+ if (bf->req)
+ dpld->send_intr(dpld, (u16)bf->req);
+
+ if (bf->resp) {
+ err = _cbp72_edpram_wait_resp(dpld, bf->resp);
+ if (err < 0) {
+ mif_info("%s: ERR! wait_response fail (%d)\n",
+ ld->name, err);
+ goto exit;
+ } else if (err == bf->resp) {
+ err = skb->len;
+ }
+ }
+
+exit:
+ dev_kfree_skb_any(skb);
+ return err;
+}
+
+static int cbp72_download_binary(struct dpram_link_device *dpld,
+ struct sk_buff *skb)
+{
+ if (dpld->type == EXT_DPRAM)
+ return _cbp72_edpram_download_bin(dpld, skb);
+ else
+ return -ENODEV;
+}
+
+static int cbp72_dump_start(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ u8 *dest = dpld->ul_map.buff;
+ int ret;
+
+ ld->mode = LINK_MODE_ULOAD;
+
+ ret = del_timer(&dpld->crash_timer);
+ wake_lock(&dpld->wlock);
+
+ iowrite32(DP_MAGIC_UMDL, dpld->ul_map.magic);
+
+ iowrite8((u8)START_FLAG, dest + 0);
+ iowrite8((u8)0x1, dest + 1);
+ iowrite8((u8)0x1, dest + 2);
+ iowrite8((u8)0x0, dest + 3);
+ iowrite8((u8)END_FLAG, dest + 4);
+
+ init_completion(&dpld->crash_start_complete);
+
+ return 0;
+}
+
+static int _cbp72_edpram_upload(struct dpram_link_device *dpld,
+ struct dpram_dump_arg *dump, unsigned char __user *target)
+{
+ struct link_device *ld = &dpld->ld;
+ struct ul_header header;
+ u8 *dest = NULL;
+ u8 *buff = NULL;
+ u16 plen = 0;
+ int err = 0;
+ int ret = 0;
+ int buff_size = 0;
+
+ mif_debug("\n");
+
+ init_completion(&dpld->udl_cmd_complete);
+
+ mif_debug("%s: req %x, resp %x", ld->name, dump->req, dump->resp);
+
+ if (dump->req)
+ dpld->send_intr(dpld, (u16)dump->req);
+
+ if (dump->resp) {
+ err = _cbp72_edpram_wait_resp(dpld, dump->resp);
+ if (err < 0) {
+ mif_info("%s: ERR! wait_response fail (%d)\n",
+ ld->name, err);
+ goto exit;
+ }
+ }
+
+ if (dump->cmd)
+ return err;
+
+ dest = (u8 *)dpld->ul_map.buff;
+
+ header.bop = *(u8 *)(dest);
+ header.total_frame = *(u16 *)(dest + 1);
+ header.curr_frame = *(u16 *)(dest + 3);
+ header.len = *(u16 *)(dest + 5);
+
+ mif_debug("%s: total frame:%d, current frame:%d, data len:%d\n",
+ ld->name, header.total_frame, header.curr_frame, header.len);
+
+ plen = min_t(u16, header.len, DP_DEFAULT_DUMP_LEN);
+
+ buff = vmalloc(DP_DEFAULT_DUMP_LEN);
+ if (!buff) {
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ memcpy(buff, dest + sizeof(struct ul_header), plen);
+ ret = copy_to_user(dump->buff, buff, plen);
+ if (ret < 0) {
+ mif_info("%s: ERR! dump copy_to_user fail\n", ld->name);
+ err = -EIO;
+ goto exit;
+ }
+ buff_size = plen;
+
+ ret = copy_to_user(target + 4, &buff_size, sizeof(int));
+ if (ret < 0) {
+ mif_info("%s: ERR! size copy_to_user fail\n", ld->name);
+ err = -EIO;
+ goto exit;
+ }
+
+ vfree(buff);
+ return err;
+
+exit:
+ if (buff)
+ vfree(buff);
+ iowrite32(0, dpld->ul_map.magic);
+ wake_unlock(&dpld->wlock);
+ return err;
+}
+
+static int cbp72_dump_update(struct dpram_link_device *dpld, void *arg)
+{
+ struct link_device *ld = &dpld->ld;
+ struct dpram_dump_arg dump;
+ int ret;
+
+ ret = copy_from_user(&dump, (void __user *)arg, sizeof(dump));
+ if (ret < 0) {
+ mif_info("%s: ERR! copy_from_user fail\n", ld->name);
+ return ret;
+ }
+
+ return _cbp72_edpram_upload(dpld, &dump, (unsigned char __user *)arg);
+}
+
+static int cbp72_set_dl_magic(struct link_device *ld, struct io_device *iod)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+
+ ld->mode = LINK_MODE_DLOAD;
+
+ iowrite32(DP_MAGIC_DMDL, dpld->dl_map.magic);
+
+ return 0;
+}
+
+static int cbp72_ioctl(struct dpram_link_device *dpld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg)
+{
+ struct link_device *ld = &dpld->ld;
+ int err = 0;
+
+ switch (cmd) {
+ case IOCTL_MODEM_DL_START:
+ err = cbp72_set_dl_magic(ld, iod);
+ if (err < 0)
+ mif_err("%s: ERR! set_dl_magic fail\n", ld->name);
+ break;
+
+ default:
+ mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd);
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+#endif
+
+#if defined(CONFIG_LTE_MODEM_CMC221)
+/* For CMC221 SFR for IDPRAM */
+#define CMC_INT2CP_REG 0x10 /* Interrupt to CP */
+#define CMC_INT2AP_REG 0x50
+#define CMC_CLR_INT_REG 0x28 /* Clear Interrupt to AP */
+#define CMC_RESET_REG 0x3C
+#define CMC_PUT_REG 0x40 /* AP->CP reg for hostbooting */
+#define CMC_GET_REG 0x50 /* CP->AP reg for hostbooting */
+
+static void cmc221_init_boot_map(struct dpram_link_device *dpld)
+{
+ struct dpram_boot_map *bt_map = &dpld->bt_map;
+
+ bt_map->buff = dpld->base;
+ bt_map->size = dpld->size;
+ bt_map->req = (u32 *)(dpld->base + DP_BOOT_REQ_OFFSET);
+ bt_map->resp = (u32 *)(dpld->base + DP_BOOT_RESP_OFFSET);
+}
+
+static void cmc221_init_dl_map(struct dpram_link_device *dpld)
+{
+ dpld->dl_map.magic = (u32 *)dpld->base;
+ dpld->dl_map.buff = (u8 *)dpld->base;
+}
+
+static void cmc221_init_ul_map(struct dpram_link_device *dpld)
+{
+ dpld->ul_map.magic = (u32 *)dpld->base;
+ dpld->ul_map.buff = (u8 *)dpld->base;
+}
+
+static void cmc221_init_ipc_map(struct dpram_link_device *dpld)
+{
+ struct dpram_ipc_16k_map *dpram_map;
+ struct dpram_ipc_device *dev;
+ u8 __iomem *sfr_base = dpld->sfr_base;
+
+ dpram_map = (struct dpram_ipc_16k_map *)dpld->base;
+
+ /* Magic code and access enable fields */
+ dpld->ipc_map.magic = (u16 __iomem *)&dpram_map->magic;
+ dpld->ipc_map.access = (u16 __iomem *)&dpram_map->access;
+
+ /* FMT */
+ dev = &dpld->ipc_map.dev[IPC_FMT];
+
+ strcpy(dev->name, "FMT");
+ dev->id = IPC_FMT;
+
+ dev->txq.head = (u16 __iomem *)&dpram_map->fmt_tx_head;
+ dev->txq.tail = (u16 __iomem *)&dpram_map->fmt_tx_tail;
+ dev->txq.buff = (u8 __iomem *)&dpram_map->fmt_tx_buff[0];
+ dev->txq.size = DP_16K_FMT_TX_BUFF_SZ;
+
+ dev->rxq.head = (u16 __iomem *)&dpram_map->fmt_rx_head;
+ dev->rxq.tail = (u16 __iomem *)&dpram_map->fmt_rx_tail;
+ dev->rxq.buff = (u8 __iomem *)&dpram_map->fmt_rx_buff[0];
+ dev->rxq.size = DP_16K_FMT_RX_BUFF_SZ;
+
+ 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;
+
+ /* RAW */
+ dev = &dpld->ipc_map.dev[IPC_RAW];
+
+ strcpy(dev->name, "RAW");
+ dev->id = IPC_RAW;
+
+ dev->txq.head = (u16 __iomem *)&dpram_map->raw_tx_head;
+ dev->txq.tail = (u16 __iomem *)&dpram_map->raw_tx_tail;
+ dev->txq.buff = (u8 __iomem *)&dpram_map->raw_tx_buff[0];
+ dev->txq.size = DP_16K_RAW_TX_BUFF_SZ;
+
+ dev->rxq.head = (u16 __iomem *)&dpram_map->raw_rx_head;
+ dev->rxq.tail = (u16 __iomem *)&dpram_map->raw_rx_tail;
+ dev->rxq.buff = (u8 __iomem *)&dpram_map->raw_rx_buff[0];
+ dev->rxq.size = DP_16K_RAW_RX_BUFF_SZ;
+
+ 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;
+
+ /* SFR */
+ dpld->sfr.int2cp = (u16 __iomem *)(sfr_base + CMC_INT2CP_REG);
+ dpld->sfr.int2ap = (u16 __iomem *)(sfr_base + CMC_INT2AP_REG);
+ dpld->sfr.clr_int2ap = (u16 __iomem *)(sfr_base + CMC_CLR_INT_REG);
+ dpld->sfr.reset = (u16 __iomem *)(sfr_base + CMC_RESET_REG);
+ dpld->sfr.msg2cp = (u16 __iomem *)(sfr_base + CMC_PUT_REG);
+ dpld->sfr.msg2ap = (u16 __iomem *)(sfr_base + CMC_GET_REG);
+
+ /* Interrupt ports */
+ dpld->ipc_map.mbx_cp2ap = dpld->sfr.int2ap;
+ dpld->ipc_map.mbx_ap2cp = dpld->sfr.int2cp;
+}
+
+static inline void cmc221_idpram_reset(struct dpram_link_device *dpld)
+{
+ iowrite16(1, dpld->sfr.reset);
+}
+
+static inline u16 cmc221_idpram_recv_msg(struct dpram_link_device *dpld)
+{
+ return ioread16(dpld->sfr.msg2ap);
+}
+
+static inline void cmc221_idpram_send_msg(struct dpram_link_device *dpld,
+ u16 msg)
+{
+ iowrite16(msg, dpld->sfr.msg2cp);
+}
+
+static int _cmc221_idpram_wait_resp(struct dpram_link_device *dpld, u32 resp)
+{
+ struct link_device *ld = &dpld->ld;
+ int count = 50000;
+ u32 rcvd = 0;
+
+ if (resp == CMC22x_CP_REQ_NV_DATA) {
+ while (1) {
+ rcvd = ioread32(dpld->bt_map.resp);
+ if (rcvd == resp)
+ break;
+
+ rcvd = cmc221_idpram_recv_msg(dpld);
+ if (rcvd == 0x9999) {
+ mif_info("%s: Invalid resp 0x%04X\n",
+ ld->name, rcvd);
+ panic("CP Crash ... BAD CRC in CP");
+ }
+
+ if (count-- < 0) {
+ mif_info("%s: Invalid resp 0x%08X\n",
+ ld->name, rcvd);
+ return -EAGAIN;
+ }
+
+ udelay(100);
+ }
+ } else {
+ while (1) {
+ rcvd = cmc221_idpram_recv_msg(dpld);
+
+ if (rcvd == resp)
+ break;
+
+ if (resp == CMC22x_CP_RECV_NV_END &&
+ rcvd == CMC22x_CP_CAL_BAD) {
+ mif_info("%s: CMC22x_CP_CAL_BAD\n", ld->name);
+ break;
+ }
+
+ if (count-- < 0) {
+ mif_info("%s: Invalid resp 0x%04X\n",
+ ld->name, rcvd);
+ return -EAGAIN;
+ }
+
+ udelay(100);
+ }
+ }
+
+ return rcvd;
+}
+
+static int _cmc221_idpram_send_boot(struct dpram_link_device *dpld, void *arg)
+{
+ struct link_device *ld = &dpld->ld;
+ u8 __iomem *bt_buff = dpld->bt_map.buff;
+ struct dpram_boot_img cp_img;
+ u8 *img_buff = NULL;
+ int err = 0;
+ int cnt = 0;
+
+ ld->mode = LINK_MODE_BOOT;
+
+ dpld->dpctl->setup_speed(DPRAM_SPEED_LOW);
+
+ /* Test memory... After testing, memory is cleared. */
+ if (mif_test_dpram(ld->name, bt_buff, dpld->bt_map.size) < 0) {
+ mif_info("%s: ERR! mif_test_dpram fail!\n", ld->name);
+ ld->mode = LINK_MODE_OFFLINE;
+ return -EIO;
+ }
+
+ memset(&cp_img, 0, sizeof(struct dpram_boot_img));
+
+ /* Get information about the boot image */
+ err = copy_from_user(&cp_img, arg, sizeof(cp_img));
+ mif_info("%s: CP image addr = 0x%08X, size = %d\n",
+ ld->name, (int)cp_img.addr, cp_img.size);
+
+ /* Alloc a buffer for the boot image */
+ img_buff = kzalloc(dpld->bt_map.size, GFP_KERNEL);
+ if (!img_buff) {
+ mif_info("%s: ERR! kzalloc fail\n", ld->name);
+ ld->mode = LINK_MODE_OFFLINE;
+ return -ENOMEM;
+ }
+
+ /* Copy boot image from the user space to the image buffer */
+ err = copy_from_user(img_buff, cp_img.addr, cp_img.size);
+
+ /* Copy boot image to DPRAM and verify it */
+ memcpy(bt_buff, img_buff, cp_img.size);
+ if (memcmp16_to_io(bt_buff, img_buff, cp_img.size)) {
+ mif_info("%s: ERR! Boot may be broken!!!\n", ld->name);
+ goto err;
+ }
+
+ cmc221_idpram_reset(dpld);
+ usleep_range(1000, 2000);
+
+ if (cp_img.mode == HOST_BOOT_MODE_NORMAL) {
+ mif_info("%s: HOST_BOOT_MODE_NORMAL\n", ld->name);
+ mif_info("%s: Send req 0x%08X\n", ld->name, cp_img.req);
+ iowrite32(cp_img.req, dpld->bt_map.req);
+
+ /* Wait for cp_img.resp for up to 2 seconds */
+ mif_info("%s: Wait resp 0x%08X\n", ld->name, cp_img.resp);
+ while (ioread32(dpld->bt_map.resp) != cp_img.resp) {
+ cnt++;
+ usleep_range(1000, 2000);
+
+ if (cnt > 1000) {
+ mif_info("%s: ERR! Invalid resp 0x%08X\n",
+ ld->name, ioread32(dpld->bt_map.resp));
+ goto err;
+ }
+ }
+ } else {
+ mif_info("%s: HOST_BOOT_MODE_DUMP\n", ld->name);
+ }
+
+ kfree(img_buff);
+
+ mif_info("%s: Send BOOT done\n", ld->name);
+
+ dpld->dpctl->setup_speed(DPRAM_SPEED_HIGH);
+
+ return 0;
+
+err:
+ ld->mode = LINK_MODE_OFFLINE;
+ kfree(img_buff);
+
+ mif_info("%s: ERR! Boot send fail!!!\n", ld->name);
+ return -EIO;
+}
+
+static int cmc221_download_boot(struct dpram_link_device *dpld, void *arg)
+{
+ if (dpld->type == CP_IDPRAM)
+ return _cmc221_idpram_send_boot(dpld, arg);
+ else
+ return -ENODEV;
+}
+
+static int _cmc221_idpram_download_bin(struct dpram_link_device *dpld,
+ struct sk_buff *skb)
+{
+ int err = 0;
+ int ret = 0;
+ struct link_device *ld = &dpld->ld;
+ struct dpram_boot_frame *bf = (struct dpram_boot_frame *)skb->data;
+ u8 __iomem *buff = (dpld->bt_map.buff + bf->offset);
+
+ if ((bf->offset + bf->len) > dpld->bt_map.size) {
+ mif_info("%s: ERR! Out of DPRAM boundary\n", ld->name);
+ err = -EINVAL;
+ goto exit;
+ }
+
+ if (bf->len)
+ memcpy(buff, bf->data, bf->len);
+
+ if (bf->req)
+ cmc221_idpram_send_msg(dpld, (u16)bf->req);
+
+ if (bf->resp) {
+ err = _cmc221_idpram_wait_resp(dpld, bf->resp);
+ if (err < 0)
+ mif_info("%s: ERR! wait_response fail (err %d)\n",
+ ld->name, err);
+ }
+
+ if (bf->req == CMC22x_CAL_NV_DOWN_END)
+ mif_info("%s: CMC22x_CAL_NV_DOWN_END\n", ld->name);
+
+exit:
+ if (err < 0)
+ ret = err;
+ else
+ ret = skb->len;
+
+ dev_kfree_skb_any(skb);
+
+ return ret;
+}
+
+static int cmc221_download_binary(struct dpram_link_device *dpld,
+ struct sk_buff *skb)
+{
+ if (dpld->type == CP_IDPRAM)
+ return _cmc221_idpram_download_bin(dpld, skb);
+ else
+ return -ENODEV;
+}
+
+static int cmc221_dump_start(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ int ret;
+
+ ld->mode = LINK_MODE_ULOAD;
+
+ ret = del_timer(&dpld->crash_timer);
+ wake_lock(&dpld->wlock);
+
+ dpld->crash_rcvd = 0;
+ iowrite32(CMC22x_CP_DUMP_MAGIC, dpld->ul_map.magic);
+
+ init_completion(&dpld->crash_start_complete);
+
+ return 0;
+}
+
+static void _cmc221_idpram_wait_dump(unsigned long arg)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)arg;
+ u16 msg;
+
+ msg = cmc221_idpram_recv_msg(dpld);
+ if (msg == CMC22x_CP_DUMP_END) {
+ complete_all(&dpld->crash_recv_done);
+ return;
+ }
+
+ if (((dpld->crash_rcvd & 0x1) == 0) && (msg == CMC22x_1ST_BUFF_FULL)) {
+ complete_all(&dpld->crash_recv_done);
+ return;
+ }
+
+ if (((dpld->crash_rcvd & 0x1) == 1) && (msg == CMC22x_2ND_BUFF_FULL)) {
+ complete_all(&dpld->crash_recv_done);
+ return;
+ }
+
+ mif_add_timer(&dpld->crash_timer, DUMP_WAIT_TIMEOUT,
+ _cmc221_idpram_wait_dump, (unsigned long)dpld);
+}
+
+static int _cmc221_idpram_upload(struct dpram_link_device *dpld,
+ struct dpram_dump_arg *dumparg)
+{
+ struct link_device *ld = &dpld->ld;
+ int ret;
+ u8 __iomem *src;
+ int buff_size = CMC22x_DUMP_BUFF_SIZE;
+
+ if ((dpld->crash_rcvd & 0x1) == 0)
+ cmc221_idpram_send_msg(dpld, CMC22x_1ST_BUFF_READY);
+ else
+ cmc221_idpram_send_msg(dpld, CMC22x_2ND_BUFF_READY);
+
+ init_completion(&dpld->crash_recv_done);
+
+ mif_add_timer(&dpld->crash_timer, DUMP_WAIT_TIMEOUT,
+ _cmc221_idpram_wait_dump, (unsigned long)dpld);
+
+ ret = wait_for_completion_interruptible_timeout(
+ &dpld->crash_recv_done, DUMP_TIMEOUT);
+ if (!ret) {
+ mif_info("%s: ERR! CP didn't send dump data!!!\n", ld->name);
+ goto err_out;
+ }
+
+ if (cmc221_idpram_recv_msg(dpld) == CMC22x_CP_DUMP_END) {
+ mif_info("%s: CMC22x_CP_DUMP_END\n", ld->name);
+ return 0;
+ }
+
+ if ((dpld->crash_rcvd & 0x1) == 0)
+ src = dpld->ul_map.buff;
+ else
+ src = dpld->ul_map.buff + CMC22x_DUMP_BUFF_SIZE;
+
+ memcpy(dpld->buff, src, buff_size);
+
+ ret = copy_to_user(dumparg->buff, dpld->buff, buff_size);
+ if (ret < 0) {
+ mif_info("%s: ERR! copy_to_user fail\n", ld->name);
+ goto err_out;
+ }
+
+ dpld->crash_rcvd++;
+ return buff_size;
+
+err_out:
+ return -EIO;
+}
+
+static int cmc221_dump_update(struct dpram_link_device *dpld, void *arg)
+{
+ struct link_device *ld = &dpld->ld;
+ struct dpram_dump_arg dump;
+ int ret;
+
+ ret = copy_from_user(&dump, (void __user *)arg, sizeof(dump));
+ if (ret < 0) {
+ mif_info("%s: ERR! copy_from_user fail\n", ld->name);
+ return ret;
+ }
+
+ return _cmc221_idpram_upload(dpld, &dump);
+}
+
+static int cmc221_ioctl(struct dpram_link_device *dpld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg)
+{
+ struct link_device *ld = &dpld->ld;
+ int err = 0;
+
+ switch (cmd) {
+ case IOCTL_DPRAM_SEND_BOOT:
+ err = cmc221_download_boot(dpld, (void *)arg);
+ if (err < 0)
+ mif_info("%s: ERR! download_boot fail\n", ld->name);
+ break;
+
+ default:
+ mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd);
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static void cmc221_idpram_clr_int2ap(struct dpram_link_device *dpld)
+{
+ iowrite16(0xFFFF, dpld->sfr.clr_int2ap);
+}
+
+static int cmc221_idpram_wakeup(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ struct modem_data *mdm_data = ld->mdm_data;
+ int cnt = 0;
+
+ gpio_set_value(mdm_data->gpio_dpram_wakeup, 1);
+
+ while (!gpio_get_value(mdm_data->gpio_dpram_status)) {
+ if (cnt++ > 10) {
+ if (in_irq())
+ mif_err("ERR! gpio_dpram_status == 0 in IRQ\n");
+ else
+ mif_err("ERR! gpio_dpram_status == 0\n");
+ return -EACCES;
+ }
+
+ mif_info("gpio_dpram_status == 0 (cnt %d)\n", cnt);
+ if (in_interrupt())
+ udelay(1000);
+ else
+ usleep_range(1000, 2000);
+ }
+
+ return 0;
+}
+
+static void cmc221_idpram_sleep(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ gpio_set_value(ld->mdm_data->gpio_dpram_wakeup, 0);
+}
+#endif
+
+#if defined(CONFIG_CDMA_MODEM_MDM6600) || defined(CONFIG_GSM_MODEM_ESC6270)
+enum qc_dload_tag {
+ QC_DLOAD_TAG_NONE = 0,
+ QC_DLOAD_TAG_BIN,
+ QC_DLOAD_TAG_NV,
+ QC_DLOAD_TAG_MAX
+};
+
+static void qc_dload_task(unsigned long data);
+
+static void qc_init_boot_map(struct dpram_link_device *dpld)
+{
+ struct qc_dpram_boot_map *qbt_map = &dpld->qc_bt_map;
+ struct modemlink_dpram_control *dpctl = dpld->dpctl;
+
+ qbt_map->buff = dpld->base;
+ qbt_map->frame_size = (u16 *)(dpld->base + dpctl->boot_size_offset);
+ qbt_map->tag = (u16 *)(dpld->base + dpctl->boot_tag_offset);
+ qbt_map->count = (u16 *)(dpld->base + dpctl->boot_count_offset);
+
+ tasklet_init(&dpld->dl_tsk, qc_dload_task, (unsigned long)dpld);
+}
+
+static int qc_prepare_download(struct dpram_link_device *dpld)
+{
+ int retval = 0;
+ int count = 0;
+
+ while (1) {
+ if (dpld->udl_check.copy_start) {
+ dpld->udl_check.copy_start = 0;
+ break;
+ }
+
+ usleep_range(10000, 11000);
+
+ count++;
+ if (count > 200) {
+ mif_err("ERR! count %d\n", count);
+ return -1;
+ }
+ }
+
+ return retval;
+}
+
+static void _qc_do_download(struct dpram_link_device *dpld,
+ struct dpram_udl_param *param)
+{
+ struct qc_dpram_boot_map *qbt_map = &dpld->qc_bt_map;
+
+ if (param->size <= dpld->dpctl->max_boot_frame_size) {
+ memcpy(qbt_map->buff, param->addr, param->size);
+ iowrite16(param->size, qbt_map->frame_size);
+ iowrite16(param->tag, qbt_map->tag);
+ iowrite16(param->count, qbt_map->count);
+ dpld->send_intr(dpld, 0xDB12);
+ } else {
+ mif_info("param->size %d\n", param->size);
+ }
+}
+
+static int _qc_download(struct dpram_link_device *dpld, void *arg,
+ enum qc_dload_tag tag)
+{
+ int retval = 0;
+ int count = 0;
+ int cnt_limit;
+ unsigned char *img;
+ struct dpram_udl_param param;
+
+ retval = copy_from_user((void *)&param, (void *)arg, sizeof(param));
+ if (retval < 0) {
+ mif_err("ERR! copy_from_user fail\n");
+ return -1;
+ }
+
+ img = vmalloc(param.size);
+ if (!img) {
+ mif_err("ERR! vmalloc fail\n");
+ return -1;
+ }
+ memset(img, 0, param.size);
+ memcpy(img, param.addr, param.size);
+
+ dpld->udl_check.total_size = param.size;
+ dpld->udl_check.rest_size = param.size;
+ dpld->udl_check.send_size = 0;
+ dpld->udl_check.copy_complete = 0;
+
+ dpld->udl_param.addr = img;
+ dpld->udl_param.size = dpld->dpctl->max_boot_frame_size;
+ if (tag == QC_DLOAD_TAG_NV)
+ dpld->udl_param.count = 1;
+ else
+ dpld->udl_param.count = param.count;
+ dpld->udl_param.tag = tag;
+
+ if (dpld->udl_check.rest_size < dpld->dpctl->max_boot_frame_size)
+ dpld->udl_param.size = dpld->udl_check.rest_size;
+
+ /* Download image (binary or NV) */
+ _qc_do_download(dpld, &dpld->udl_param);
+
+ /* Wait for completion
+ */
+ if (tag == QC_DLOAD_TAG_NV)
+ cnt_limit = 200;
+ else
+ cnt_limit = 1000;
+
+ while (1) {
+ if (dpld->udl_check.copy_complete) {
+ dpld->udl_check.copy_complete = 0;
+ retval = 0;
+ break;
+ }
+
+ usleep_range(10000, 11000);
+
+ count++;
+ if (count > cnt_limit) {
+ dpld->udl_check.total_size = 0;
+ dpld->udl_check.rest_size = 0;
+ mif_err("ERR! count %d\n", count);
+ retval = -1;
+ break;
+ }
+ }
+
+ vfree(img);
+
+ return retval;
+}
+
+static int qc_download_binary(struct dpram_link_device *dpld, void *arg)
+{
+ return _qc_download(dpld, arg, QC_DLOAD_TAG_BIN);
+}
+
+static int qc_download_nv(struct dpram_link_device *dpld, void *arg)
+{
+ return _qc_download(dpld, arg, QC_DLOAD_TAG_NV);
+}
+
+static void qc_dload_task(unsigned long data)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)data;
+
+ dpld->udl_check.send_size += dpld->udl_param.size;
+ dpld->udl_check.rest_size -= dpld->udl_param.size;
+
+ dpld->udl_param.addr += dpld->udl_param.size;
+
+ if (dpld->udl_check.send_size >= dpld->udl_check.total_size) {
+ dpld->udl_check.copy_complete = 1;
+ dpld->udl_param.tag = 0;
+ return;
+ }
+
+ if (dpld->udl_check.rest_size < dpld->dpctl->max_boot_frame_size)
+ dpld->udl_param.size = dpld->udl_check.rest_size;
+
+ dpld->udl_param.count += 1;
+
+ _qc_do_download(dpld, &dpld->udl_param);
+}
+
+static void qc_dload_cmd_handler(struct dpram_link_device *dpld, u16 cmd)
+{
+ switch (cmd) {
+ case 0x1234:
+ dpld->udl_check.copy_start = 1;
+ break;
+
+ case 0xDBAB:
+ if (dpld->udl_check.total_size)
+ tasklet_schedule(&dpld->dl_tsk);
+ break;
+
+ case 0xABCD:
+ dpld->udl_check.boot_complete = 1;
+ break;
+
+ default:
+ mif_err("ERR! unknown command 0x%04X\n", cmd);
+ }
+}
+
+static int qc_boot_start(struct dpram_link_device *dpld)
+{
+ u16 mask = 0;
+ int count = 0;
+
+ /* Send interrupt -> '0x4567' */
+ mask = 0x4567;
+ dpld->send_intr(dpld, mask);
+
+ while (1) {
+ if (dpld->udl_check.boot_complete) {
+ dpld->udl_check.boot_complete = 0;
+ break;
+ }
+
+ usleep_range(10000, 11000);
+
+ count++;
+ if (count > 200) {
+ mif_err("ERR! count %d\n", count);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int qc_boot_post_process(struct dpram_link_device *dpld)
+{
+ int count = 0;
+
+ while (1) {
+ if (dpld->boot_start_complete) {
+ dpld->boot_start_complete = 0;
+ break;
+ }
+
+ usleep_range(10000, 11000);
+
+ count++;
+ if (count > 200) {
+ mif_err("ERR! count %d\n", count);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void qc_start_handler(struct dpram_link_device *dpld)
+{
+ /*
+ * INT_MASK_VALID | INT_MASK_CMD | INT_MASK_CP_AIRPLANE_BOOT |
+ * INT_MASK_CP_AP_ANDROID | INT_MASK_CMD_INIT_END
+ */
+ u16 mask = (0x0080 | 0x0040 | 0x1000 | 0x0100 | 0x0002);
+
+ dpld->boot_start_complete = 1;
+
+ /* Send INIT_END code to CP */
+ mif_info("send 0x%04X (INIT_END)\n", mask);
+
+ dpld->send_intr(dpld, mask);
+}
+
+static void qc_crash_log(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ static unsigned char buf[151];
+ u8 __iomem *data = NULL;
+
+ data = dpld->get_rx_buff(dpld, IPC_FMT);
+ memcpy(buf, data, (sizeof(buf) - 1));
+
+ mif_info("PHONE ERR MSG\t| %s Crash\n", ld->mdm_data->name);
+ mif_info("PHONE ERR MSG\t| %s\n", buf);
+}
+
+static int _qc_data_upload(struct dpram_link_device *dpld,
+ struct dpram_udl_param *param)
+{
+ struct qc_dpram_boot_map *qbt_map = &dpld->qc_bt_map;
+ int retval = 0;
+ u16 intval = 0;
+ int count = 0;
+
+ while (1) {
+ if (!gpio_get_value(dpld->gpio_dpram_int)) {
+ intval = dpld->recv_intr(dpld);
+ if (intval == 0xDBAB) {
+ break;
+ } else {
+ mif_err("intr 0x%08x\n", intval);
+ return -1;
+ }
+ }
+
+ usleep_range(1000, 2000);
+
+ count++;
+ if (count > 200) {
+ mif_err("<%s:%d>\n", __func__, __LINE__);
+ return -1;
+ }
+ }
+
+ param->size = ioread16(qbt_map->frame_size);
+ memcpy(param->addr, qbt_map->buff, param->size);
+ param->tag = ioread16(qbt_map->tag);
+ param->count = ioread16(qbt_map->count);
+
+ dpld->send_intr(dpld, 0xDB12);
+
+ return retval;
+}
+
+static int qc_uload_step1(struct dpram_link_device *dpld)
+{
+ int retval = 0;
+ int count = 0;
+ u16 intval = 0;
+ u16 mask = 0;
+
+ mif_info("+---------------------------------------------+\n");
+ mif_info("| UPLOAD PHONE SDRAM |\n");
+ mif_info("+---------------------------------------------+\n");
+
+ while (1) {
+ if (!gpio_get_value(dpld->gpio_dpram_int)) {
+ intval = dpld->recv_intr(dpld);
+ mif_info("intr 0x%04x\n", intval);
+ if (intval == 0x1234) {
+ break;
+ } else {
+ mif_info("ERR! invalid intr\n");
+ return -1;
+ }
+ }
+
+ usleep_range(1000, 2000);
+
+ count++;
+ if (count > 200) {
+ intval = dpld->recv_intr(dpld);
+ mif_info("count %d, intr 0x%04x\n", count, intval);
+ if (intval == 0x1234)
+ break;
+ return -1;
+ }
+ }
+
+ mask = 0xDEAD;
+ dpld->send_intr(dpld, mask);
+
+ return retval;
+}
+
+static int qc_uload_step2(struct dpram_link_device *dpld, void *arg)
+{
+ int retval = 0;
+ struct dpram_udl_param param;
+
+ retval = copy_from_user((void *)&param, (void *)arg, sizeof(param));
+ if (retval < 0) {
+ mif_err("ERR! copy_from_user fail (err %d)\n", retval);
+ return -1;
+ }
+
+ retval = _qc_data_upload(dpld, &param);
+ if (retval < 0) {
+ mif_err("ERR! _qc_data_upload fail (err %d)\n", retval);
+ return -1;
+ }
+
+ if (!(param.count % 500))
+ mif_info("param->count = %d\n", param.count);
+
+ if (param.tag == 4) {
+ enable_irq(dpld->irq);
+ mif_info("param->tag = %d\n", param.tag);
+ }
+
+ retval = copy_to_user((unsigned long *)arg, &param, sizeof(param));
+ if (retval < 0) {
+ mif_err("ERR! copy_to_user fail (err %d)\n", retval);
+ return -1;
+ }
+
+ return retval;
+}
+
+static int qc_ioctl(struct dpram_link_device *dpld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg)
+{
+ struct link_device *ld = &dpld->ld;
+ int err = 0;
+
+ switch (cmd) {
+ case IOCTL_DPRAM_PHONE_POWON:
+ err = qc_prepare_download(dpld);
+ if (err < 0)
+ mif_info("%s: ERR! prepare_download fail\n", ld->name);
+ break;
+
+ case IOCTL_DPRAM_PHONEIMG_LOAD:
+ err = qc_download_binary(dpld, (void *)arg);
+ if (err < 0)
+ mif_info("%s: ERR! download_binary fail\n", ld->name);
+ break;
+
+ case IOCTL_DPRAM_NVDATA_LOAD:
+ err = qc_download_nv(dpld, (void *)arg);
+ if (err < 0)
+ mif_info("%s: ERR! download_nv fail\n", ld->name);
+ break;
+
+ case IOCTL_DPRAM_PHONE_BOOTSTART:
+ err = qc_boot_start(dpld);
+ if (err < 0) {
+ mif_info("%s: ERR! boot_start fail\n", ld->name);
+ break;
+ }
+
+ err = qc_boot_post_process(dpld);
+ if (err < 0)
+ mif_info("%s: ERR! boot_post_process fail\n", ld->name);
+
+ break;
+
+ case IOCTL_DPRAM_PHONE_UPLOAD_STEP1:
+ disable_irq_nosync(dpld->irq);
+ err = qc_uload_step1(dpld);
+ if (err < 0) {
+ enable_irq(dpld->irq);
+ mif_info("%s: ERR! upload_step1 fail\n", ld->name);
+ }
+ break;
+
+ case IOCTL_DPRAM_PHONE_UPLOAD_STEP2:
+ err = qc_uload_step2(dpld, (void *)arg);
+ if (err < 0) {
+ enable_irq(dpld->irq);
+ mif_info("%s: ERR! upload_step2 fail\n", ld->name);
+ }
+ break;
+
+ default:
+ mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd);
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static irqreturn_t qc_dpram_irq_handler(int irq, void *data)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)data;
+ struct link_device *ld = (struct link_device *)&dpld->ld;
+ u16 int2ap = 0;
+
+ if (unlikely(ld->mode == LINK_MODE_OFFLINE))
+ return IRQ_HANDLED;
+
+ int2ap = dpld->recv_intr(dpld);
+
+ if (int2ap == INT_POWERSAFE_FAIL) {
+ mif_info("%s: int2ap == INT_POWERSAFE_FAIL\n", ld->name);
+ goto exit;
+ }
+
+ if (int2ap == 0x1234 || int2ap == 0xDBAB || int2ap == 0xABCD) {
+ qc_dload_cmd_handler(dpld, int2ap);
+ goto exit;
+ }
+
+ if (likely(INT_VALID(int2ap)))
+ dpld->ipc_rx_handler(dpld, int2ap);
+ else
+ mif_info("%s: ERR! invalid intr 0x%04X\n", ld->name, int2ap);
+
+exit:
+ return IRQ_HANDLED;
+}
+#endif
+
+static struct dpram_ext_op ext_op_set[] = {
+#ifdef CONFIG_CDMA_MODEM_CBP72
+ [VIA_CBP72] = {
+ .exist = 1,
+ .init_boot_map = cbp72_init_boot_map,
+ .init_dl_map = cbp72_init_dl_map,
+ .download_binary = cbp72_download_binary,
+ .dump_start = cbp72_dump_start,
+ .dump_update = cbp72_dump_update,
+ .ioctl = cbp72_ioctl,
+ },
+#endif
+#ifdef CONFIG_LTE_MODEM_CMC221
+ [SEC_CMC221] = {
+ .exist = 1,
+ .init_boot_map = cmc221_init_boot_map,
+ .init_dl_map = cmc221_init_dl_map,
+ .init_ul_map = cmc221_init_ul_map,
+ .init_ipc_map = cmc221_init_ipc_map,
+ .download_binary = cmc221_download_binary,
+ .dump_start = cmc221_dump_start,
+ .dump_update = cmc221_dump_update,
+ .ioctl = cmc221_ioctl,
+ .clear_intr = cmc221_idpram_clr_int2ap,
+ .wakeup = cmc221_idpram_wakeup,
+ .sleep = cmc221_idpram_sleep,
+ },
+#endif
+#if defined(CONFIG_CDMA_MODEM_MDM6600)
+ [QC_MDM6600] = {
+ .exist = 1,
+ .init_boot_map = qc_init_boot_map,
+ .cp_start_handler = qc_start_handler,
+ .crash_log = qc_crash_log,
+ .ioctl = qc_ioctl,
+ .irq_handler = qc_dpram_irq_handler,
+ },
+#endif
+#if defined(CONFIG_GSM_MODEM_ESC6270)
+ [QC_ESC6270] = {
+ .exist = 1,
+ .init_boot_map = qc_init_boot_map,
+ .cp_start_handler = qc_start_handler,
+ .crash_log = qc_crash_log,
+ .ioctl = qc_ioctl,
+ .irq_handler = qc_dpram_irq_handler,
+ },
+#endif
+};
+
+struct dpram_ext_op *dpram_get_ext_op(enum modem_t modem)
+{
+ if (ext_op_set[modem].exist)
+ return &ext_op_set[modem];
+ else
+ return NULL;
+}
+
diff --git a/drivers/misc/modem_if/modem_link_device_hsic.c b/drivers/misc/modem_if/modem_link_device_hsic.c
new file mode 100644
index 00000000000..bb8066a3fa0
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_hsic.c
@@ -0,0 +1,1654 @@
+/* /linux/drivers/new_modem_if/link_dev_usb.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/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/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/pm_runtime.h>
+#include <linux/cdev.h>
+#include <linux/platform_device.h>
+#ifdef CONFIG_HAS_WAKELOCK
+#include <linux/wakelock.h>
+#endif
+#include <linux/suspend.h>
+#include <linux/version.h>
+
+#include <linux/platform_data/modem.h>
+#include "modem_prj.h"
+#include "modem_link_device_hsic.h"
+#include "modem_utils.h"
+
+static struct modem_ctl *if_usb_get_modemctl(struct link_pm_data *pm_data);
+static int link_pm_runtime_get_active(struct link_pm_data *pm_data);
+static int usb_tx_urb_with_skb(struct usb_device *usbdev, struct sk_buff *skb,
+ struct if_usb_devdata *pipe_data);
+#ifdef FOR_TEGRA
+#define ehci_vendor_txfilltuning tegra_ehci_txfilltuning
+#else
+#define ehci_vendor_txfilltuning()
+#endif
+static void usb_rx_complete(struct urb *urb);
+
+static int start_ipc(struct link_device *ld, struct io_device *iod)
+{
+ struct sk_buff *skb;
+ char data[1] = {'a'};
+ int err;
+ struct usb_link_device *usb_ld = to_usb_link_device(ld);
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+ struct device *dev = &usb_ld->usbdev->dev;
+ struct if_usb_devdata *pipe_data = &usb_ld->devdata[IF_USB_FMT_EP];
+
+ if (!usb_ld->if_usb_connected) {
+ mif_err("HSIC not connected, skip start ipc\n");
+ err = -ENODEV;
+ goto exit;
+ }
+
+retry:
+ if (ld->mc->phone_state != STATE_ONLINE) {
+ mif_err("MODEM is not online, skip start ipc\n");
+ err = -ENODEV;
+ goto exit;
+ }
+
+ /* check usb runtime pm first */
+ if (dev->power.runtime_status != RPM_ACTIVE) {
+ if (!pm_data->resume_requested) {
+ mif_debug("QW PM\n");
+ INIT_COMPLETION(pm_data->active_done);
+ queue_delayed_work(pm_data->wq,
+ &pm_data->link_pm_work, 0);
+ }
+ mif_debug("Wait pm\n");
+ err = wait_for_completion_timeout(&pm_data->active_done,
+ msecs_to_jiffies(500));
+ /* timeout or -ERESTARTSYS */
+ if (err <= 0)
+ goto retry;
+ }
+
+ pm_runtime_get_sync(dev);
+
+ mif_err("send 'a'\n");
+
+ skb = alloc_skb(16, GFP_ATOMIC);
+ if (unlikely(!skb)) {
+ pm_runtime_put(dev);
+ return -ENOMEM;
+ }
+ memcpy(skb_put(skb, 1), data, 1);
+ skbpriv(skb)->iod = iod;
+ skbpriv(skb)->ld = ld;
+
+ if (!usb_ld->if_usb_connected || !usb_ld->usbdev)
+ return -ENODEV;
+
+ usb_mark_last_busy(usb_ld->usbdev);
+ err = usb_tx_urb_with_skb(usb_ld->usbdev, skb, pipe_data);
+ if (err < 0) {
+ mif_err("usb_tx_urb fail\n");
+ dev_kfree_skb_any(skb);
+ }
+
+ pm_runtime_put(dev);
+exit:
+ return err;
+}
+
+static void stop_ipc(struct link_device *ld)
+{
+ ld->com_state = COM_NONE;
+}
+
+static int usb_init_communication(struct link_device *ld,
+ struct io_device *iod)
+{
+ struct task_struct *task = get_current();
+ char str[TASK_COMM_LEN];
+
+ mif_info("%d:%s\n", task->pid, get_task_comm(str, task));
+
+ /* Send IPC Start ASCII 'a' */
+ if (iod->id == 0x1)
+ return start_ipc(ld, iod);
+
+ return 0;
+}
+
+static void usb_terminate_communication(struct link_device *ld,
+ struct io_device *iod)
+{
+ if (iod->id != 0x1 || iod->format != IPC_FMT)
+ return;
+
+ if (iod->mc->phone_state == STATE_CRASH_RESET ||
+ iod->mc->phone_state == STATE_CRASH_EXIT)
+ stop_ipc(ld);
+}
+
+static int usb_rx_submit(struct usb_link_device *usb_ld,
+ struct if_usb_devdata *pipe_data,
+ gfp_t gfp_flags)
+{
+ int ret;
+ struct urb *urb;
+
+ if (pipe_data->disconnected)
+ return -ENOENT;
+
+ ehci_vendor_txfilltuning();
+
+ urb = pipe_data->urb;
+
+ urb->transfer_flags = 0;
+ usb_fill_bulk_urb(urb, pipe_data->usbdev,
+ pipe_data->rx_pipe, pipe_data->rx_buf,
+ pipe_data->rx_buf_size, usb_rx_complete,
+ (void *)pipe_data);
+
+ if (pipe_data->disconnected)
+ return -ENOENT;
+
+ usb_mark_last_busy(usb_ld->usbdev);
+ ret = usb_submit_urb(urb, gfp_flags);
+ if (ret)
+ mif_err("submit urb fail with ret (%d)\n", ret);
+
+ return ret;
+}
+
+static void usb_rx_retry_work(struct work_struct *work)
+{
+ int ret = 0;
+ struct usb_link_device *usb_ld =
+ container_of(work, struct usb_link_device, rx_retry_work.work);
+ struct urb *urb = usb_ld->retry_urb;
+ struct if_usb_devdata *pipe_data = urb->context;
+ struct io_device *iod;
+ int iod_format;
+
+ if (!usb_ld->if_usb_connected || !usb_ld->usbdev)
+ return;
+
+ if (usb_ld->usbdev)
+ usb_mark_last_busy(usb_ld->usbdev);
+ switch (pipe_data->format) {
+ case IF_USB_FMT_EP:
+ if (usb_ld->if_usb_is_main) {
+ pr_urb("IPC-RX, retry", urb);
+ iod_format = IPC_FMT;
+ } else {
+ iod_format = IPC_BOOT;
+ }
+ break;
+ case IF_USB_RAW_EP:
+ iod_format = IPC_MULTI_RAW;
+ break;
+ case IF_USB_RFS_EP:
+ iod_format = IPC_RFS;
+ pr_urb("RFS-RX, retry", urb);
+ break;
+ case IF_USB_CMD_EP:
+ iod_format = IPC_CMD;
+ break;
+ default:
+ iod_format = -1;
+ break;
+ }
+
+ iod = link_get_iod_with_format(&usb_ld->ld, iod_format);
+ if (iod) {
+ ret = iod->recv(iod, &usb_ld->ld, (char *)urb->transfer_buffer,
+ urb->actual_length);
+ if (ret == -ENOMEM) {
+ /* TODO: check the retry count */
+ /* retry the delay work after 20ms and resubit*/
+ mif_err("ENOMEM, +retry 20ms\n");
+ if (usb_ld->usbdev)
+ usb_mark_last_busy(usb_ld->usbdev);
+ usb_ld->retry_urb = urb;
+ if (usb_ld->rx_retry_cnt++ < 10)
+ queue_delayed_work(usb_ld->ld.tx_wq,
+ &usb_ld->rx_retry_work, 10);
+ return;
+ }
+ if (ret < 0)
+ mif_err("io device recv error (%d)\n", ret);
+ usb_ld->rx_retry_cnt = 0;
+ }
+
+ if (usb_ld->usbdev)
+ usb_mark_last_busy(usb_ld->usbdev);
+ usb_rx_submit(usb_ld, pipe_data, GFP_ATOMIC);
+}
+
+
+static void usb_rx_complete(struct urb *urb)
+{
+ struct if_usb_devdata *pipe_data = urb->context;
+ struct usb_link_device *usb_ld = pipe_data->usb_ld;
+ struct io_device *iod;
+ int iod_format;
+ int ret;
+
+ if (usb_ld->usbdev)
+ usb_mark_last_busy(usb_ld->usbdev);
+
+ switch (urb->status) {
+ case -ENOENT:
+ /* case for 'link pm suspended but rx data had remained' */
+ mif_debug("urb->status = -ENOENT\n");
+ case 0:
+ if (!urb->actual_length) {
+ mif_debug("urb has zero length!\n");
+ goto rx_submit;
+ }
+
+ usb_ld->link_pm_data->rx_cnt++;
+ /* call iod recv */
+ /* how we can distinguish boot ch with fmt ch ?? */
+ switch (pipe_data->format) {
+ case IF_USB_FMT_EP:
+ if (usb_ld->if_usb_is_main) {
+ pr_urb("IPC-RX", urb);
+ iod_format = IPC_FMT;
+ } else {
+ iod_format = IPC_BOOT;
+ }
+ break;
+ case IF_USB_RAW_EP:
+ iod_format = IPC_MULTI_RAW;
+ break;
+ case IF_USB_RFS_EP:
+ iod_format = IPC_RFS;
+ break;
+ case IF_USB_CMD_EP:
+ iod_format = IPC_CMD;
+ break;
+ default:
+ iod_format = -1;
+ break;
+ }
+
+ /* flow control CMD by CP, not use io device */
+ if (unlikely(iod_format == IPC_CMD)) {
+ ret = link_rx_flowctl_cmd(&usb_ld->ld,
+ (char *)urb->transfer_buffer,
+ urb->actual_length);
+ if (ret < 0)
+ mif_err("no multi raw device (%d)\n", ret);
+ goto rx_submit;
+ }
+
+ iod = link_get_iod_with_format(&usb_ld->ld, iod_format);
+ if (iod) {
+ ret = iod->recv(iod,
+ &usb_ld->ld,
+ (char *)urb->transfer_buffer,
+ urb->actual_length);
+ if (ret == -ENOMEM) {
+ /* retry the delay work and resubit*/
+ mif_err("ENOMEM, retry\n");
+ if (usb_ld->usbdev)
+ usb_mark_last_busy(usb_ld->usbdev);
+ usb_ld->retry_urb = urb;
+ queue_delayed_work(usb_ld->ld.tx_wq,
+ &usb_ld->rx_retry_work, 0);
+ return;
+ }
+ if (ret < 0)
+ mif_err("io device recv error (%d)\n", ret);
+ }
+rx_submit:
+ if (urb->status == 0) {
+ if (usb_ld->usbdev)
+ usb_mark_last_busy(usb_ld->usbdev);
+ usb_rx_submit(usb_ld, pipe_data, GFP_ATOMIC);
+ }
+ break;
+ default:
+ mif_err("urb err status = %d\n", urb->status);
+ break;
+ }
+}
+
+static int usb_send(struct link_device *ld, struct io_device *iod,
+ struct sk_buff *skb)
+{
+ struct sk_buff_head *txq;
+ size_t tx_size;
+ struct usb_link_device *usb_ld = to_usb_link_device(ld);
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+
+ switch (iod->format) {
+ case IPC_RAW:
+ txq = &ld->sk_raw_tx_q;
+
+ if (unlikely(ld->raw_tx_suspended)) {
+ /* Unlike misc_write, vnet_xmit is in interrupt.
+ * Despite call netif_stop_queue on CMD_SUSPEND,
+ * packets can be reached here.
+ */
+ if (in_irq()) {
+ mif_err("raw tx is suspended, "
+ "drop packet. size=%d",
+ skb->len);
+ return -EBUSY;
+ }
+
+ mif_err("wait RESUME CMD...\n");
+ INIT_COMPLETION(ld->raw_tx_resumed_by_cp);
+ wait_for_completion(&ld->raw_tx_resumed_by_cp);
+ mif_err("resumed done.\n");
+ }
+ break;
+ case IPC_BOOT:
+ case IPC_FMT:
+ case IPC_RFS:
+ default:
+ txq = &ld->sk_fmt_tx_q;
+ break;
+ }
+ /* store the tx size before run the tx_delayed_work*/
+ tx_size = skb->len;
+
+ /* drop packet, when link is not online */
+ if (ld->com_state == COM_BOOT && iod->format != IPC_BOOT) {
+ mif_err("%s: drop packet, size=%d, com_state=%d\n",
+ iod->name, skb->len, ld->com_state);
+ dev_kfree_skb_any(skb);
+ return 0;
+ }
+
+ /* en queue skb data */
+ skb_queue_tail(txq, skb);
+ /* Hold wake_lock for getting schedule the tx_work */
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock(&pm_data->tx_async_wake);
+#else
+ pm_stay_awake(pm_data->miscdev.this_device);
+#endif
+
+ if (!work_pending(&ld->tx_delayed_work.work))
+ queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0);
+
+ return tx_size;
+}
+
+static void usb_tx_complete(struct urb *urb)
+{
+ struct sk_buff *skb = urb->context;
+ struct io_device *iod = skbpriv(skb)->iod;
+ struct link_device *ld = skbpriv(skb)->ld;
+ struct usb_link_device *usb_ld = to_usb_link_device(ld);
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ENOENT:
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ default:
+ if (iod->format != IPC_BOOT)
+ mif_info("TX error (%d)\n", urb->status);
+ }
+
+ dev_kfree_skb_any(skb);
+ if (urb->dev && usb_ld->if_usb_connected)
+ usb_mark_last_busy(urb->dev);
+ usb_free_urb(urb);
+}
+
+/* Even if usb_tx_urb_with_skb is failed, does not release the skb to retry */
+static int usb_tx_urb_with_skb(struct usb_device *usbdev, struct sk_buff *skb,
+ struct if_usb_devdata *pipe_data)
+{
+ int ret;
+ struct urb *urb;
+
+ if (pipe_data->disconnected)
+ return -ENOENT;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ mif_err("alloc urb error\n");
+ return -ENOMEM;
+ }
+
+ urb->transfer_flags = URB_ZERO_PACKET;
+ usb_fill_bulk_urb(urb, pipe_data->usbdev, pipe_data->tx_pipe, skb->data,
+ skb->len, usb_tx_complete, (void *)skb);
+
+ usb_mark_last_busy(usbdev);
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret < 0) {
+ mif_err("usb_submit_urb with ret(%d)\n", ret);
+ usb_free_urb(urb);
+ return ret;
+ }
+ return 0;
+}
+
+
+static int _usb_tx_work(struct sk_buff *skb)
+{
+ struct sk_buff_head *txq;
+ struct io_device *iod = skbpriv(skb)->iod;
+ struct link_device *ld = skbpriv(skb)->ld;
+ struct usb_link_device *usb_ld = to_usb_link_device(ld);
+ struct if_usb_devdata *pipe_data;
+
+ switch (iod->format) {
+ case IPC_BOOT:
+ case IPC_FMT:
+ /* boot device uses same intf with fmt*/
+ pipe_data = &usb_ld->devdata[IF_USB_FMT_EP];
+ txq = &ld->sk_fmt_tx_q;
+ break;
+ case IPC_RAW:
+ pipe_data = &usb_ld->devdata[IF_USB_RAW_EP];
+ txq = &ld->sk_raw_tx_q;
+ break;
+ case IPC_RFS:
+ pipe_data = &usb_ld->devdata[IF_USB_RFS_EP];
+ txq = &ld->sk_fmt_tx_q;
+ break;
+ default:
+ /* wrong packet, drop it */
+ pipe_data = NULL;
+ txq = NULL;
+ break;
+ }
+
+ if (!pipe_data)
+ return -ENOENT;
+
+ if (iod->format == IPC_FMT && usb_ld->if_usb_is_main)
+ pr_skb("IPC-TX", skb);
+
+ if (iod->format == IPC_RAW)
+ mif_debug("TX[RAW]\n");
+
+ return usb_tx_urb_with_skb(usb_ld->usbdev, skb, pipe_data);
+}
+
+
+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 sk_buff *skb;
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+
+ if (!usb_ld->usbdev) {
+ mif_info("usbdev is invalid\n");
+ return;
+ }
+
+ pm_data->tx_cnt++;
+
+ while (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) {
+ /* request and check usb runtime pm first */
+ ret = link_pm_runtime_get_active(pm_data);
+ if (ret < 0) {
+ if (ret == -ENODEV) {
+ mif_err("link not avail, retry reconnect.\n");
+ goto exit;
+ }
+ goto retry_tx_work;
+ }
+
+ /* If AP try to tx when interface disconnect->reconnect probe,
+ * usbdev was created but one of interface channel device are
+ * probing, _usb_tx_work return to -ENOENT then runtime usage
+ * count allways positive and never enter to L2
+ */
+ if (!usb_ld->if_usb_connected) {
+ mif_info("link is available, but if was not readey\n");
+ goto retry_tx_work;
+ }
+ pm_runtime_get_sync(&usb_ld->usbdev->dev);
+
+ ret = 0;
+ /* 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)
+ ret = _usb_tx_work(skb);
+
+ if (ret) {
+ mif_err("usb_tx_urb_with_skb for fmt_q %d\n", ret);
+ skb_queue_head(&ld->sk_fmt_tx_q, skb);
+
+ if (ret == -ENODEV || ret == -ENOENT)
+ goto exit;
+
+ /* tx fail and usbdev alived, retry tx work */
+ pm_runtime_put(&usb_ld->usbdev->dev);
+ goto retry_tx_work;
+ }
+
+ skb = skb_dequeue(&ld->sk_raw_tx_q);
+ if (skb)
+ ret = _usb_tx_work(skb);
+
+ if (ret) {
+ mif_err("usb_tx_urb_with_skb for raw_q %d\n", ret);
+ skb_queue_head(&ld->sk_raw_tx_q, skb);
+
+ if (ret == -ENODEV || ret == -ENOENT)
+ goto exit;
+
+ pm_runtime_put(&usb_ld->usbdev->dev);
+ goto retry_tx_work;
+ }
+
+ pm_runtime_put(&usb_ld->usbdev->dev);
+ }
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_unlock(&pm_data->tx_async_wake);
+#else
+ pm_relax(pm_data->miscdev.this_device);
+#endif
+exit:
+ return;
+
+retry_tx_work:
+ queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work,
+ msecs_to_jiffies(20));
+ return;
+}
+
+/*
+#ifdef CONFIG_LINK_PM
+*/
+
+static int link_pm_runtime_get_active(struct link_pm_data *pm_data)
+{
+ int ret;
+ struct usb_link_device *usb_ld = pm_data->usb_ld;
+ struct device *dev = &usb_ld->usbdev->dev;
+
+ if (!usb_ld->if_usb_connected || usb_ld->ld.com_state == COM_NONE)
+ return -ENODEV;
+
+ if (pm_data->dpm_suspending) {
+ mif_err("Kernel in suspending try get_active later\n");
+ /* during dpm_suspending..
+ * if AP get tx data, wake up. */
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock(&pm_data->l2_wake);
+#else
+ pm_stay_awake(pm_data->miscdev.this_device);
+#endif
+ return -EAGAIN;
+ }
+
+ if (dev->power.runtime_status == RPM_ACTIVE) {
+ pm_data->resume_retry_cnt = 0;
+ return 0;
+ }
+
+ if (!pm_data->resume_requested) {
+ mif_debug("QW PM\n");
+ queue_delayed_work(pm_data->wq, &pm_data->link_pm_work, 0);
+ }
+ mif_debug("Wait pm\n");
+ INIT_COMPLETION(pm_data->active_done);
+ ret = wait_for_completion_timeout(&pm_data->active_done,
+ msecs_to_jiffies(500));
+
+ /* If usb link was disconnected while waiting ACTIVE State, usb device
+ * was removed, usb_ld->usbdev->dev is invalid and below
+ * dev->power.runtime_status is also invalid address.
+ * It will be occured LPA L3 -> AP iniated L0 -> disconnect -> link
+ * timeout
+ */
+ if (!usb_ld->if_usb_connected || usb_ld->ld.com_state == COM_NONE) {
+ mif_info("link disconnected after timed-out\n");
+ return -ENODEV;
+ }
+
+ if (dev->power.runtime_status != RPM_ACTIVE) {
+ mif_info("link_active (%d) retry\n",
+ dev->power.runtime_status);
+ return -EAGAIN;
+ }
+ mif_debug("link_active success(%d)\n", ret);
+ return 0;
+}
+
+static void link_pm_runtime_start(struct work_struct *work)
+{
+ struct link_pm_data *pm_data =
+ container_of(work, struct link_pm_data, link_pm_start.work);
+ struct usb_device *usbdev = pm_data->usb_ld->usbdev;
+ struct device *dev, *hdev;
+ struct link_device *ld = &pm_data->usb_ld->ld;
+
+ if (!pm_data->usb_ld->if_usb_connected
+ || pm_data->usb_ld->ld.com_state == COM_NONE) {
+ mif_debug("disconnect status, ignore\n");
+ return;
+ }
+
+ dev = &pm_data->usb_ld->usbdev->dev;
+
+ /* wait interface driver resumming */
+ if (dev->power.runtime_status == RPM_SUSPENDED) {
+ mif_info("suspended yet, delayed work\n");
+ queue_delayed_work(pm_data->wq, &pm_data->link_pm_start,
+ msecs_to_jiffies(20));
+ return;
+ }
+
+ if (pm_data->usb_ld->usbdev && dev->parent) {
+ mif_info("rpm_status: %d\n",
+ dev->power.runtime_status);
+ pm_runtime_set_autosuspend_delay(dev, 200);
+ hdev = usbdev->bus->root_hub->dev.parent;
+ mif_info("EHCI runtime %s, %s\n", dev_driver_string(hdev),
+ dev_name(hdev));
+ pm_runtime_allow(dev);
+ pm_runtime_allow(hdev);/*ehci*/
+ pm_data->link_pm_active = true;
+ pm_data->resume_requested = false;
+ pm_data->link_reconnect_cnt = 5;
+ pm_data->resume_retry_cnt = 0;
+
+ /* retry prvious link tx q */
+ queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0);
+ }
+}
+
+static void link_pm_force_cp_dump(struct link_pm_data *pm_data)
+{
+ struct modem_ctl *mc = if_usb_get_modemctl(pm_data);
+
+ mif_err("Set modem crash ap_dump_int by %pF\n",
+ __builtin_return_address(0));
+
+ if (mc->gpio_ap_dump_int) {
+ if (gpio_get_value(mc->gpio_ap_dump_int)) {
+ gpio_set_value(mc->gpio_ap_dump_int, 0);
+ msleep(20);
+ }
+ gpio_set_value(mc->gpio_ap_dump_int, 1);
+ msleep(20);
+ mif_err("AP_DUMP_INT(%d)\n",
+ gpio_get_value(mc->gpio_ap_dump_int));
+ gpio_set_value(mc->gpio_ap_dump_int, 0);
+ }
+}
+
+static void link_pm_change_modem_state(struct link_pm_data *pm_data,
+ enum modem_state state)
+{
+ struct modem_ctl *mc = if_usb_get_modemctl(pm_data);
+
+ if (!mc->iod || pm_data->usb_ld->ld.com_state != COM_ONLINE)
+ return;
+
+ mif_err("set modem state %d by %pF\n", state,
+ __builtin_return_address(0));
+ mc->iod->modem_state_changed(mc->iod, state);
+ mc->bootd->modem_state_changed(mc->bootd, state);
+}
+
+static void link_pm_reconnect_work(struct work_struct *work)
+{
+ struct link_pm_data *pm_data =
+ container_of(work, struct link_pm_data,
+ link_reconnect_work.work);
+ struct modem_ctl *mc = if_usb_get_modemctl(pm_data);
+
+ if (!mc || pm_data->usb_ld->if_usb_connected)
+ return;
+
+ if (pm_data->usb_ld->ld.com_state != COM_ONLINE)
+ return;
+
+ if (pm_data->link_reconnect_cnt--) {
+ if (mc->phone_state == STATE_ONLINE &&
+ !pm_data->link_reconnect())
+ /* try reconnect and check */
+ schedule_delayed_work(&pm_data->link_reconnect_work,
+ msecs_to_jiffies(500));
+ else /* under cp crash or reset, just return */
+ return;
+ } else {
+ /* try to recover cp */
+ mif_err("recover connection: silent reset\n");
+ link_pm_change_modem_state(pm_data, STATE_CRASH_RESET);
+ }
+}
+
+static inline int link_pm_slave_wake(struct link_pm_data *pm_data)
+{
+ int spin = 20;
+
+ /* when slave device is in sleep, wake up slave cpu first */
+ if (gpio_get_value(pm_data->gpio_link_hostwake)
+ != HOSTWAKE_TRIGLEVEL) {
+ if (gpio_get_value(pm_data->gpio_link_slavewake)) {
+ gpio_set_value(pm_data->gpio_link_slavewake, 0);
+ mif_info("gpio [SWK] set [0]\n");
+ mdelay(5);
+ }
+ gpio_set_value(pm_data->gpio_link_slavewake, 1);
+ mif_info("gpio [SWK] set [1]\n");
+ mdelay(5);
+
+ /* wait host wake signal*/
+ while (spin-- && gpio_get_value(pm_data->gpio_link_hostwake) !=
+ HOSTWAKE_TRIGLEVEL)
+ mdelay(5);
+ }
+ return spin;
+}
+
+static void link_pm_runtime_work(struct work_struct *work)
+{
+ int ret;
+ struct link_pm_data *pm_data =
+ container_of(work, struct link_pm_data, link_pm_work.work);
+ struct device *dev = &pm_data->usb_ld->usbdev->dev;
+
+ if (!pm_data->usb_ld->if_usb_connected || pm_data->dpm_suspending)
+ return;
+
+ if (pm_data->usb_ld->ld.com_state == COM_NONE)
+ return;
+
+ mif_debug("for dev 0x%p : current %d\n", dev,
+ dev->power.runtime_status);
+
+ switch (dev->power.runtime_status) {
+ case RPM_ACTIVE:
+ pm_data->resume_retry_cnt = 0;
+ pm_data->resume_requested = false;
+ complete(&pm_data->active_done);
+
+ return;
+ case RPM_SUSPENDED:
+ if (pm_data->resume_requested)
+ break;
+ pm_data->resume_requested = true;
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock(&pm_data->rpm_wake);
+#else
+ pm_stay_awake(pm_data->miscdev.this_device);
+#endif
+ ret = link_pm_slave_wake(pm_data);
+ if (ret < 0) {
+ mif_err("slave wake fail\n");
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_unlock(&pm_data->rpm_wake);
+#else
+ pm_relax(pm_data->miscdev.this_device);
+#endif
+ break;
+ }
+
+ if (!pm_data->usb_ld->if_usb_connected) {
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_unlock(&pm_data->rpm_wake);
+#else
+ pm_relax(pm_data->miscdev.this_device);
+#endif
+ return;
+ }
+
+ ret = pm_runtime_resume(dev);
+ if (ret < 0) {
+ mif_err("resume error(%d)\n", ret);
+ if (!pm_data->usb_ld->if_usb_connected) {
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_unlock(&pm_data->rpm_wake);
+#else
+ pm_relax(pm_data->miscdev.this_device);
+#endif
+ return;
+ }
+ /* force to go runtime idle before retry resume */
+ if (dev->power.timer_expires == 0 &&
+ !dev->power.request_pending) {
+ mif_debug("run time idle\n");
+ pm_runtime_idle(dev);
+ }
+ }
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_unlock(&pm_data->rpm_wake);
+#else
+ pm_relax(pm_data->miscdev.this_device);
+#endif
+ break;
+ case RPM_SUSPENDING:
+ /* Checking the usb_runtime_suspend running time.*/
+ mif_info("rpm_states=%d", dev->power.runtime_status);
+ msleep(20);
+ break;
+ default:
+ break;
+ }
+ pm_data->resume_requested = false;
+
+ /* check until runtime_status goes to active */
+ /* attemp 10 times, or re-establish modem-link */
+ /* if pm_runtime_resume run properly, rpm status must be in ACTIVE */
+ if (dev->power.runtime_status == RPM_ACTIVE) {
+ pm_data->resume_retry_cnt = 0;
+ complete(&pm_data->active_done);
+ } else if (pm_data->resume_retry_cnt++ > 10) {
+ mif_err("runtime_status(%d), retry_cnt(%d)\n",
+ dev->power.runtime_status, pm_data->resume_retry_cnt);
+ link_pm_change_modem_state(pm_data, STATE_CRASH_RESET);
+ } else
+ queue_delayed_work(pm_data->wq, &pm_data->link_pm_work,
+ msecs_to_jiffies(20));
+}
+
+static irqreturn_t link_pm_irq_handler(int irq, void *data)
+{
+ int value;
+ struct link_pm_data *pm_data = data;
+
+#if defined(CONFIG_SLP)
+ pm_wakeup_event(pm_data->miscdev.this_device, 0);
+#endif
+
+ if (!pm_data->link_pm_active)
+ return IRQ_HANDLED;
+
+ /* host wake up HIGH */
+ /*
+ resume usb runtime pm start
+ */
+ /* host wake up LOW */
+ /*
+ slave usb enumeration end,
+ host can send usb packet after
+ runtime pm status changes to ACTIVE
+ */
+ value = gpio_get_value(pm_data->gpio_link_hostwake);
+ mif_info("gpio [HWK] get [%d]\n", value);
+
+ /*
+ * igonore host wakeup interrupt at suspending kernel
+ */
+ if (pm_data->dpm_suspending) {
+ mif_info("ignore request by suspending\n");
+ /* Ignore HWK but AP got to L2 by suspending fail */
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock(&pm_data->l2_wake);
+#else
+ pm_stay_awake(pm_data->miscdev.this_device);
+#endif
+ return IRQ_HANDLED;
+ }
+
+ if (value == HOSTWAKE_TRIGLEVEL) {
+ /* move to slave wake function */
+ /* runtime pm goes to active */
+ /*
+ if (gpio_get_value(pm_data->gpio_link_active)) {
+ mif_err("gpio [H ACTV : %d] set 1\n",
+ gpio_get_value(pm_data->gpio_link_active));
+ gpio_set_value(pm_data->gpio_link_active, 1);
+ }
+ */
+ queue_delayed_work(pm_data->wq, &pm_data->link_pm_work, 0);
+ } else {
+ /* notification of enumeration process from slave device
+ * But it does not mean whole connection is in resume, so do not
+ * notify resume completion here.
+
+ if (pm_data->link_pm_active && !pm_data->active_done.done)
+ complete(&pm_data->active_done);
+ */
+ /* clear slave cpu wake up pin */
+ gpio_set_value(pm_data->gpio_link_slavewake, 0);
+ mif_debug("gpio [SWK] set [0]\n");
+ }
+ return IRQ_HANDLED;
+}
+
+static long link_pm_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int value;
+ struct link_pm_data *pm_data = file->private_data;
+ struct modem_ctl *mc = if_usb_get_modemctl(pm_data);
+
+ mif_info("%x\n", cmd);
+
+ switch (cmd) {
+ case IOCTL_LINK_CONTROL_ENABLE:
+ if (copy_from_user(&value, (const void __user *)arg,
+ sizeof(int)))
+ return -EFAULT;
+ if (pm_data->link_ldo_enable)
+ pm_data->link_ldo_enable(!!value);
+ if (pm_data->gpio_link_enable)
+ gpio_set_value(pm_data->gpio_link_enable, value);
+ break;
+ 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_SET_BIAS_CLEAR:
+ if (copy_from_user(&value, (const void __user *)arg,
+ sizeof(int)))
+ return -EFAULT;
+ if (value) {
+ gpio_direction_output(pm_data->gpio_link_slavewake, 0);
+ gpio_direction_output(pm_data->gpio_link_hostwake, 0);
+ } else {
+ gpio_direction_output(pm_data->gpio_link_slavewake, 0);
+ gpio_direction_input(pm_data->gpio_link_hostwake);
+ irq_set_irq_type(pm_data->irq_link_hostwake,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING);
+ }
+ case IOCTL_LINK_GET_PHONEACTIVE:
+ return gpio_get_value(mc->gpio_phone_active);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+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);
+#ifdef CONFIG_UMTS_MODEM_XMM6262
+ struct modem_ctl *mc = if_usb_get_modemctl(pm_data);
+#endif
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+#ifdef CONFIG_HIBERNATION
+ case PM_HIBERNATION_PREPARE:
+ case PM_RESTORE_PREPARE:
+#endif
+ pm_data->dpm_suspending = true;
+#ifdef CONFIG_UMTS_MODEM_XMM6262
+ /* set PDA Active High if previous state was LPA */
+ if (!gpio_get_value(pm_data->gpio_link_active)) {
+ mif_info("PDA active High to LPA suspend spot\n");
+ gpio_set_value(mc->gpio_pda_active, 1);
+ }
+#endif
+ mif_debug("dpm suspending set to true\n");
+ return NOTIFY_OK;
+ case PM_POST_SUSPEND:
+#ifdef CONFIG_HIBERNATION
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+#endif
+ pm_data->dpm_suspending = false;
+ if (gpio_get_value(pm_data->gpio_link_hostwake)
+ == HOSTWAKE_TRIGLEVEL) {
+ queue_delayed_work(pm_data->wq, &pm_data->link_pm_work,
+ 0);
+ mif_info("post resume\n");
+ }
+#ifdef CONFIG_UMTS_MODEM_XMM6262
+ /* LPA to Kernel suspend and User Freezing task fail resume,
+ restore to LPA GPIO states. */
+ if (!gpio_get_value(pm_data->gpio_link_active)) {
+ mif_info("PDA active low to LPA GPIO state\n");
+ gpio_set_value(mc->gpio_pda_active, 0);
+ }
+#endif
+ mif_debug("dpm suspending set to false\n");
+ return NOTIFY_OK;
+ }
+ return NOTIFY_DONE;
+}
+
+static struct modem_ctl *if_usb_get_modemctl(struct link_pm_data *pm_data)
+{
+ struct io_device *iod;
+
+ iod = link_get_iod_with_format(&pm_data->usb_ld->ld, IPC_FMT);
+ if (!iod) {
+ mif_err("no iodevice for modem control\n");
+ return NULL;
+ }
+
+ return iod->mc;
+}
+
+static int if_usb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct if_usb_devdata *devdata = usb_get_intfdata(intf);
+ struct link_pm_data *pm_data = devdata->usb_ld->link_pm_data;
+ if (!devdata->disconnected && devdata->state == STATE_RESUMED) {
+ usb_kill_urb(devdata->urb);
+ devdata->state = STATE_SUSPENDED;
+ }
+
+ devdata->usb_ld->suspended++;
+
+ if (devdata->usb_ld->suspended == LINKPM_DEV_NUM) {
+ mif_debug("[if_usb_suspended]\n");
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock_timeout(&pm_data->l2_wake, msecs_to_jiffies(50));
+#else
+ pm_wakeup_event(pm_data->miscdev.this_device, 50);
+#endif
+ /* XMM6262 Host wakeup toggle recovery */
+ if (!pm_data->rx_cnt && !pm_data->tx_cnt) {
+ if (pm_data->ipc_debug_cnt++ > 10) {
+ mif_err("No TX/RX after resume 10times\n");
+ link_pm_change_modem_state(pm_data,
+ STATE_CRASH_RESET);
+ }
+ } else {
+ pm_data->ipc_debug_cnt = 0;
+ pm_data->rx_cnt = 0;
+ pm_data->tx_cnt = 0;
+ }
+ }
+ return 0;
+}
+
+static int if_usb_resume(struct usb_interface *intf)
+{
+ int ret;
+ struct if_usb_devdata *devdata = usb_get_intfdata(intf);
+ struct link_pm_data *pm_data = devdata->usb_ld->link_pm_data;
+
+ if (!devdata->disconnected && devdata->state == STATE_SUSPENDED) {
+ ret = usb_rx_submit(devdata->usb_ld, devdata, GFP_ATOMIC);
+ if (ret < 0) {
+ mif_err("usb_rx_submit error with (%d)\n", ret);
+ return ret;
+ }
+ devdata->state = STATE_RESUMED;
+ }
+
+ /* For debugging - nomal case, never reach below... */
+ if (pm_data->resume_retry_cnt > 5) {
+ mif_err("retry_cnt=%d, rpm_status=%d",
+ pm_data->resume_retry_cnt,
+ devdata->usb_ld->usbdev->dev.power.runtime_status);
+ pm_data->resume_retry_cnt = 0;
+ }
+
+ devdata->usb_ld->suspended--;
+ if (!devdata->usb_ld->suspended) {
+ mif_debug("[if_usb_resumed]\n");
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock(&pm_data->l2_wake);
+#else
+ pm_stay_awake(pm_data->miscdev.this_device);
+#endif
+ }
+
+ return 0;
+}
+
+static int if_usb_reset_resume(struct usb_interface *intf)
+{
+ int ret;
+ struct if_usb_devdata *devdata = usb_get_intfdata(intf);
+ struct link_pm_data *pm_data = devdata->usb_ld->link_pm_data;
+
+ ret = if_usb_resume(intf);
+ pm_data->ipc_debug_cnt = 0;
+ /*
+ * for runtime suspend, kick runtime pm at L3 -> L0 reset resume
+ */
+ if (!devdata->usb_ld->suspended)
+ queue_delayed_work(pm_data->wq, &pm_data->link_pm_start, 0);
+ return ret;
+}
+
+static void if_usb_disconnect(struct usb_interface *intf)
+{
+ struct if_usb_devdata *devdata = usb_get_intfdata(intf);
+ struct link_pm_data *pm_data = devdata->usb_ld->link_pm_data;
+ struct device *dev, *hdev;
+ struct link_device *ld = &devdata->usb_ld->ld;
+
+ mif_info("\n");
+
+ if (devdata->disconnected)
+ return;
+
+ devdata->usb_ld->if_usb_connected = 0;
+
+ usb_driver_release_interface(to_usb_driver(intf->dev.driver), intf);
+
+ usb_kill_urb(devdata->urb);
+
+ hdev = devdata->usbdev->bus->root_hub->dev.parent;
+ pm_runtime_forbid(hdev); /*ehci*/
+
+ mif_info("put dev 0x%p\n", devdata->usbdev);
+ usb_put_dev(devdata->usbdev);
+
+ devdata->data_intf = NULL;
+ devdata->usbdev = NULL;
+ /* if possible, merge below 2 variables */
+ devdata->disconnected = 1;
+ devdata->state = STATE_SUSPENDED;
+ pm_data->ipc_debug_cnt = 0;
+
+ devdata->usb_ld->suspended = 0;
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock(&pm_data->boot_wake);
+#else
+ pm_stay_awake(pm_data->miscdev.this_device);
+#endif
+ usb_set_intfdata(intf, NULL);
+
+ /* cancel runtime start delayed works */
+ cancel_delayed_work_sync(&pm_data->link_pm_start);
+ cancel_delayed_work_sync(&ld->tx_delayed_work);
+
+ /* if reconnect function exist , try reconnect without reset modem
+ * reconnect function checks modem is under crash or not, so we don't
+ * need check crash state here. reconnect work checks and determine
+ * further works
+ */
+ if (!pm_data->link_reconnect)
+ return;
+
+ if (devdata->usb_ld->ld.com_state != COM_ONLINE) {
+ cancel_delayed_work(&pm_data->link_reconnect_work);
+ return;
+ } else {
+ if (pm_data->ehci_reg_dump)
+ pm_data->ehci_reg_dump(hdev);
+ schedule_delayed_work(&pm_data->link_reconnect_work,
+ msecs_to_jiffies(500));
+ }
+ return;
+}
+
+static int if_usb_set_pipe(struct usb_link_device *usb_ld,
+ const struct usb_host_interface *desc, int pipe)
+{
+ if (pipe < 0 || pipe >= IF_USB_DEVNUM_MAX) {
+ mif_err("undefined endpoint, exceed max\n");
+ return -EINVAL;
+ }
+
+ mif_info("set %d\n", pipe);
+
+ if ((usb_pipein(desc->endpoint[0].desc.bEndpointAddress)) &&
+ (usb_pipeout(desc->endpoint[1].desc.bEndpointAddress))) {
+ usb_ld->devdata[pipe].rx_pipe = usb_rcvbulkpipe(usb_ld->usbdev,
+ desc->endpoint[0].desc.bEndpointAddress);
+ usb_ld->devdata[pipe].tx_pipe = usb_sndbulkpipe(usb_ld->usbdev,
+ desc->endpoint[1].desc.bEndpointAddress);
+ } else if ((usb_pipeout(desc->endpoint[0].desc.bEndpointAddress)) &&
+ (usb_pipein(desc->endpoint[1].desc.bEndpointAddress))) {
+ usb_ld->devdata[pipe].rx_pipe = usb_rcvbulkpipe(usb_ld->usbdev,
+ desc->endpoint[1].desc.bEndpointAddress);
+ usb_ld->devdata[pipe].tx_pipe = usb_sndbulkpipe(usb_ld->usbdev,
+ desc->endpoint[0].desc.bEndpointAddress);
+ } else {
+ mif_err("undefined endpoint\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __devinit if_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ int err;
+ int pipe;
+ const struct usb_cdc_union_desc *union_hdr;
+ const struct usb_host_interface *data_desc;
+ unsigned char *buf = intf->altsetting->extra;
+ int buflen = intf->altsetting->extralen;
+ struct usb_interface *data_intf;
+ struct usb_device *usbdev = interface_to_usbdev(intf);
+ struct usb_driver *usbdrv = to_usb_driver(intf->dev.driver);
+ struct usb_id_info *info = (struct usb_id_info *)id->driver_info;
+ struct usb_link_device *usb_ld = info->usb_ld;
+
+ mif_info("usbdev = 0x%p\n", usbdev);
+
+ pr_debug("%s: Class=%d, SubClass=%d, Protocol=%d\n", __func__,
+ intf->altsetting->desc.bInterfaceClass,
+ intf->altsetting->desc.bInterfaceSubClass,
+ intf->altsetting->desc.bInterfaceProtocol);
+
+ /* if usb disconnected, AP try to reconnect 5 times.
+ * but because if_sub_connected is configured
+ * at the end of if_usb_probe, there was a chance
+ * that swk will be called again during enumeration.
+ * so.. cancel reconnect work_queue in this case. */
+ if (usb_ld->ld.com_state == COM_ONLINE)
+ cancel_delayed_work(&usb_ld->link_pm_data->link_reconnect_work);
+
+ usb_ld->usbdev = usbdev;
+ pm_runtime_forbid(&usbdev->dev);
+ usb_ld->link_pm_data->link_pm_active = false;
+ usb_ld->link_pm_data->dpm_suspending = false;
+ usb_ld->link_pm_data->ipc_debug_cnt = 0;
+ usb_ld->if_usb_is_main = (info->intf_id != BOOT_DOWN);
+
+ union_hdr = NULL;
+ /* for WMC-ACM compatibility, WMC-ACM use an end-point for control msg*/
+ if (intf->altsetting->desc.bInterfaceSubClass != USB_CDC_SUBCLASS_ACM) {
+ mif_err("ignore Non ACM end-point\n");
+ return -EINVAL;
+ }
+
+ if (!buflen) {
+ if (intf->cur_altsetting->endpoint->extralen &&
+ intf->cur_altsetting->endpoint->extra) {
+ buflen = intf->cur_altsetting->endpoint->extralen;
+ buf = intf->cur_altsetting->endpoint->extra;
+ } else {
+ mif_err("Zero len descriptor reference\n");
+ return -EINVAL;
+ }
+ }
+
+ while (buflen > 0) {
+ if (buf[1] == USB_DT_CS_INTERFACE) {
+ switch (buf[2]) {
+ case USB_CDC_UNION_TYPE:
+ if (union_hdr)
+ break;
+ union_hdr = (struct usb_cdc_union_desc *)buf;
+ break;
+ default:
+ break;
+ }
+ }
+ buf += buf[0];
+ buflen -= buf[0];
+ }
+
+ if (!union_hdr) {
+ mif_err("USB CDC is not union type\n");
+ return -EINVAL;
+ }
+
+ data_intf = usb_ifnum_to_if(usbdev, union_hdr->bSlaveInterface0);
+ if (!data_intf) {
+ mif_err("data_inferface is NULL\n");
+ return -ENODEV;
+ }
+
+ data_desc = data_intf->altsetting;
+ if (!data_desc) {
+ mif_err("data_desc is NULL\n");
+ return -ENODEV;
+ }
+
+ switch (info->intf_id) {
+ case BOOT_DOWN:
+ pipe = IF_USB_BOOT_EP;
+ usb_ld->ld.com_state = COM_BOOT;
+ /* purge previous boot fmt/raw tx q
+ clear all tx q*/
+ skb_queue_purge(&usb_ld->ld.sk_fmt_tx_q);
+ skb_queue_purge(&usb_ld->ld.sk_raw_tx_q);
+ break;
+ case IPC_CHANNEL:
+ pipe = intf->altsetting->desc.bInterfaceNumber / 2;
+ usb_ld->ld.com_state = COM_ONLINE;
+ break;
+ default:
+ pipe = -1;
+ break;
+ }
+
+ if (if_usb_set_pipe(usb_ld, data_desc, pipe) < 0)
+ return -EINVAL;
+
+ usb_ld->devdata[pipe].usbdev = usb_get_dev(usbdev);
+ mif_info("devdata usbdev = 0x%p\n",
+ usb_ld->devdata[pipe].usbdev);
+ usb_ld->devdata[pipe].usb_ld = usb_ld;
+ usb_ld->devdata[pipe].data_intf = data_intf;
+ usb_ld->devdata[pipe].format = pipe;
+ usb_ld->devdata[pipe].disconnected = 0;
+ usb_ld->devdata[pipe].state = STATE_RESUMED;
+
+ usb_ld->suspended = 0;
+
+ err = usb_driver_claim_interface(usbdrv, data_intf,
+ (void *)&usb_ld->devdata[pipe]);
+ if (err < 0) {
+ mif_err("usb_driver_claim() failed\n");
+ return err;
+ }
+
+ pm_suspend_ignore_children(&usbdev->dev, true);
+
+ usb_set_intfdata(intf, (void *)&usb_ld->devdata[pipe]);
+
+ /* rx start for this endpoint */
+ usb_rx_submit(usb_ld, &usb_ld->devdata[pipe], GFP_KERNEL);
+
+ if (info->intf_id == IPC_CHANNEL &&
+ !work_pending(&usb_ld->link_pm_data->link_pm_start.work)) {
+ queue_delayed_work(usb_ld->link_pm_data->wq,
+ &usb_ld->link_pm_data->link_pm_start,
+ msecs_to_jiffies(500));
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock(&usb_ld->link_pm_data->l2_wake);
+ wake_unlock(&usb_ld->link_pm_data->boot_wake);
+#else
+ pm_stay_awake
+ (usb_ld->link_pm_data->miscdev.this_device);
+#endif
+ }
+
+ /* HSIC main comm channel has been established */
+ if (pipe == IF_USB_CMD_EP)
+ link_pm_change_modem_state(usb_ld->link_pm_data, STATE_ONLINE);
+
+ if (pipe == IF_USB_CMD_EP || info->intf_id == BOOT_DOWN)
+ usb_ld->if_usb_connected = 1;
+
+ mif_info("successfully done\n");
+
+ return 0;
+}
+
+static void if_usb_free_pipe_data(struct usb_link_device *usb_ld)
+{
+ int i;
+ for (i = 0; i < IF_USB_DEVNUM_MAX; i++) {
+ kfree(usb_ld->devdata[i].rx_buf);
+ usb_kill_urb(usb_ld->devdata[i].urb);
+ }
+}
+
+static struct usb_id_info hsic_boot_down_info = {
+ .intf_id = BOOT_DOWN,
+};
+static struct usb_id_info hsic_channel_info = {
+ .intf_id = IPC_CHANNEL,
+};
+
+static struct usb_device_id if_usb_ids[] = {
+ {USB_DEVICE_AND_INTERFACE_INFO(IMC_BOOT_VID, IMC_BOOT_PID,
+ USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, USB_CDC_PROTO_NONE),
+ .driver_info = (unsigned long)&hsic_boot_down_info,},
+ {USB_DEVICE_AND_INTERFACE_INFO(IMC_MAIN_VID, IMC_MAIN_PID,
+ USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, 1),
+ .driver_info = (unsigned long)&hsic_channel_info,},
+ {USB_DEVICE(STE_BOOT_VID, STE_BOOT_PID),
+ .driver_info = (unsigned long)&hsic_boot_down_info,},
+ {USB_DEVICE(STE_MAIN_VID, STE_MAIN_PID),
+ .driver_info = (unsigned long)&hsic_channel_info,},
+ {}
+};
+MODULE_DEVICE_TABLE(usb, if_usb_ids);
+
+static struct usb_driver if_usb_driver = {
+ .name = "cdc_modem",
+ .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 int if_usb_init(struct link_device *ld)
+{
+ int ret;
+ int i;
+ struct usb_link_device *usb_ld = to_usb_link_device(ld);
+ struct if_usb_devdata *pipe_data;
+ struct usb_id_info *id_info;
+
+ /* to connect usb link device with usb interface driver */
+ for (i = 0; i < ARRAY_SIZE(if_usb_ids); i++) {
+ id_info = (struct usb_id_info *)if_usb_ids[i].driver_info;
+ if (id_info)
+ id_info->usb_ld = usb_ld;
+ }
+
+ ret = usb_register(&if_usb_driver);
+ if (ret) {
+ mif_err("usb_register_driver() fail : %d\n", ret);
+ return ret;
+ }
+
+ /* allocate rx buffer for usb receive */
+ for (i = 0; i < IF_USB_DEVNUM_MAX; i++) {
+ pipe_data = &usb_ld->devdata[i];
+ pipe_data->format = i;
+ pipe_data->rx_buf_size = 16 * 1024;
+
+ pipe_data->rx_buf = kmalloc(pipe_data->rx_buf_size,
+ GFP_DMA | GFP_KERNEL);
+ if (!pipe_data->rx_buf) {
+ if_usb_free_pipe_data(usb_ld);
+ ret = -ENOMEM;
+ break;
+ }
+
+ pipe_data->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!pipe_data->urb) {
+ mif_err("alloc urb fail\n");
+ if_usb_free_pipe_data(usb_ld);
+ return -ENOMEM;
+ }
+ }
+
+ mif_info("if_usb_init() done : %d, usb_ld (0x%p)\n",
+ ret, usb_ld);
+ return ret;
+}
+
+static int usb_link_pm_init(struct usb_link_device *usb_ld, void *data)
+{
+ int r;
+ 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;
+ struct link_pm_data *pm_data =
+ kzalloc(sizeof(struct link_pm_data), GFP_KERNEL);
+
+ if (!pdata || !pdata->link_pm_data) {
+ mif_err("platform data is NULL\n");
+ return -EINVAL;
+ }
+ pm_pdata = pdata->link_pm_data;
+
+ 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_enable = pm_pdata->gpio_link_enable;
+ pm_data->gpio_link_hostwake = pm_pdata->gpio_link_hostwake;
+ pm_data->gpio_link_slavewake = pm_pdata->gpio_link_slavewake;
+ pm_data->irq_link_hostwake = gpio_to_irq(pm_data->gpio_link_hostwake);
+ pm_data->link_ldo_enable = pm_pdata->link_ldo_enable;
+ pm_data->link_reconnect = pm_pdata->link_reconnect;
+ pm_data->ehci_reg_dump = pm_pdata->ehci_reg_dump;
+
+ pm_data->usb_ld = usb_ld;
+ pm_data->link_pm_active = false;
+ pm_data->ipc_debug_cnt = 0;
+ 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;
+
+ r = misc_register(&pm_data->miscdev);
+ if (r < 0) {
+ mif_err("fail to register pm device(%d)\n", r);
+ goto err_misc_register;
+ }
+
+ r = request_irq(pm_data->irq_link_hostwake, link_pm_irq_handler,
+ IRQF_NO_SUSPEND | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "hostwake", (void *)pm_data);
+ if (r) {
+ mif_err("fail to request irq(%d)\n", r);
+ goto err_request_irq;
+ }
+
+ r = enable_irq_wake(pm_data->irq_link_hostwake);
+ if (r) {
+ mif_err("failed to enable_irq_wake:%d\n", r);
+ goto err_set_wake_irq;
+ }
+
+ /* create work queue & init work for runtime pm */
+ pm_data->wq = create_singlethread_workqueue("linkpmd");
+ if (!pm_data->wq) {
+ mif_err("fail to create wq\n");
+ goto err_create_wq;
+ }
+
+ pm_data->pm_notifier.notifier_call = link_pm_notifier_event;
+ register_pm_notifier(&pm_data->pm_notifier);
+
+ init_completion(&pm_data->active_done);
+ INIT_DELAYED_WORK(&pm_data->link_pm_work, link_pm_runtime_work);
+ INIT_DELAYED_WORK(&pm_data->link_pm_start, link_pm_runtime_start);
+ INIT_DELAYED_WORK(&pm_data->link_reconnect_work,
+ link_pm_reconnect_work);
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock_init(&pm_data->l2_wake, WAKE_LOCK_SUSPEND, "l2_hsic");
+ wake_lock_init(&pm_data->boot_wake, WAKE_LOCK_SUSPEND, "boot_hsic");
+ wake_lock_init(&pm_data->rpm_wake, WAKE_LOCK_SUSPEND, "rpm_hsic");
+ wake_lock_init(&pm_data->tx_async_wake, WAKE_LOCK_SUSPEND, "tx_hsic");
+#else
+ device_init_wakeup(pm_data->miscdev.this_device, true);
+#endif
+
+ return 0;
+
+err_create_wq:
+ disable_irq_wake(pm_data->irq_link_hostwake);
+err_set_wake_irq:
+ free_irq(pm_data->irq_link_hostwake, (void *)pm_data);
+err_request_irq:
+ misc_deregister(&pm_data->miscdev);
+err_misc_register:
+ kfree(pm_data);
+ return r;
+}
+
+struct link_device *hsic_create_link_device(void *data)
+{
+ int ret;
+ struct usb_link_device *usb_ld;
+ struct link_device *ld;
+
+ usb_ld = kzalloc(sizeof(struct usb_link_device), GFP_KERNEL);
+ if (!usb_ld)
+ return NULL;
+
+ INIT_LIST_HEAD(&usb_ld->ld.list);
+ skb_queue_head_init(&usb_ld->ld.sk_fmt_tx_q);
+ skb_queue_head_init(&usb_ld->ld.sk_raw_tx_q);
+
+ ld = &usb_ld->ld;
+
+ ld->name = "usb";
+ ld->init_comm = usb_init_communication;
+ ld->terminate_comm = usb_terminate_communication;
+ ld->send = usb_send;
+ ld->com_state = COM_NONE;
+ ld->raw_tx_suspended = false;
+ init_completion(&ld->raw_tx_resumed_by_cp);
+
+ ld->tx_wq = create_singlethread_workqueue("usb_tx_wq");
+ if (!ld->tx_wq) {
+ mif_err("fail to create work Q.\n");
+ goto err;
+ }
+
+ INIT_DELAYED_WORK(&ld->tx_delayed_work, usb_tx_work);
+ INIT_DELAYED_WORK(&usb_ld->rx_retry_work, usb_rx_retry_work);
+ usb_ld->rx_retry_cnt = 0;
+
+ /* create link pm device */
+ ret = usb_link_pm_init(usb_ld, data);
+ if (ret)
+ goto err;
+
+ ret = if_usb_init(ld);
+ if (ret)
+ goto err;
+
+ mif_info("%s : create_link_device DONE\n", usb_ld->ld.name);
+ return (void *)ld;
+err:
+ kfree(usb_ld);
+ return NULL;
+}
+
+static void __exit if_usb_exit(void)
+{
+ usb_deregister(&if_usb_driver);
+}
diff --git a/drivers/misc/modem_if/modem_link_device_hsic.h b/drivers/misc/modem_if/modem_link_device_hsic.h
new file mode 100644
index 00000000000..604b06784e2
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_hsic.h
@@ -0,0 +1,155 @@
+/*
+ * 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__
+
+
+enum {
+ IF_USB_BOOT_EP = 0,
+ IF_USB_FMT_EP = 0,
+ IF_USB_RAW_EP,
+ IF_USB_RFS_EP,
+ IF_USB_CMD_EP,
+ IF_USB_DEVNUM_MAX,
+};
+
+/* each pipe has 2 ep for in/out */
+#define LINKPM_DEV_NUM (IF_USB_DEVNUM_MAX * 2)
+/******************/
+/* xmm6260 specific */
+
+#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_GET_PHONEACTIVE _IO('o', 0x35)
+
+/* VID,PID for IMC - XMM6260, XMM6262*/
+#define IMC_BOOT_VID 0x058b
+#define IMC_BOOT_PID 0x0041
+#define IMC_MAIN_VID 0x1519
+#define IMC_MAIN_PID 0x0020
+/* VID,PID for STE - M7400 */
+#define STE_BOOT_VID 0x04cc
+#define STE_BOOT_PID 0x7400
+#define STE_MAIN_VID 0x04cc
+#define STE_MAIN_PID 0x2333
+
+enum {
+ BOOT_DOWN = 0,
+ IPC_CHANNEL
+};
+
+enum ch_state {
+ STATE_SUSPENDED,
+ STATE_RESUMED,
+};
+
+#define HOSTWAKE_TRIGLEVEL 0
+/******************/
+
+struct link_pm_info {
+ struct usb_link_device *usb_ld;
+};
+
+struct usb_id_info {
+ int intf_id;
+ struct usb_link_device *usb_ld;
+};
+
+struct link_pm_data {
+ struct miscdevice miscdev;
+ struct usb_link_device *usb_ld;
+ unsigned irq_link_hostwake;
+ int (*link_ldo_enable)(bool);
+ unsigned gpio_link_enable;
+ 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;
+ 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;
+ int resume_retry_cnt;
+
+ struct wake_lock l2_wake;
+ struct wake_lock boot_wake;
+ struct wake_lock rpm_wake;
+ struct wake_lock tx_async_wake;
+ struct notifier_block pm_notifier;
+ bool dpm_suspending;
+
+ /* Host wakeup toggle debugging */
+ unsigned ipc_debug_cnt;
+ unsigned long tx_cnt;
+ unsigned long rx_cnt;
+
+ void (*ehci_reg_dump)(struct device *);
+};
+
+struct if_usb_devdata {
+ struct usb_interface *data_intf;
+ struct usb_link_device *usb_ld;
+ struct usb_device *usbdev;
+ unsigned int tx_pipe;
+ unsigned int rx_pipe;
+ u8 disconnected;
+
+ int format;
+ struct urb *urb;
+ void *rx_buf;
+ unsigned int rx_buf_size;
+ enum ch_state state;
+};
+
+struct usb_link_device {
+ /*COMMON LINK DEVICE*/
+ struct link_device ld;
+
+ /*USB SPECIFIC LINK DEVICE*/
+ struct usb_device *usbdev;
+ struct if_usb_devdata devdata[IF_USB_DEVNUM_MAX];
+ unsigned int dev_count;
+ unsigned int suspended;
+ int if_usb_connected;
+
+ bool if_usb_is_main; /* boot,down(false) or main(true) */
+
+ /* LINK PM DEVICE DATA */
+ struct link_pm_data *link_pm_data;
+
+ /*RX retry work by -ENOMEM*/
+ struct delayed_work rx_retry_work;
+ struct urb *retry_urb;
+ unsigned rx_retry_cnt;
+};
+/* converts from struct link_device* to struct xxx_link_device* */
+#define to_usb_link_device(linkdev) \
+ container_of(linkdev, struct usb_link_device, ld)
+
+
+#ifdef FOR_TEGRA
+extern void tegra_ehci_txfilltuning(void);
+#endif
+
+#endif
diff --git a/drivers/misc/modem_if/modem_link_device_memory.h b/drivers/misc/modem_if/modem_link_device_memory.h
new file mode 100644
index 00000000000..9ea01a74867
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_memory.h
@@ -0,0 +1,501 @@
+/*
+ * 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_MEMORY_H__
+#define __MODEM_LINK_DEVICE_MEMORY_H__
+
+#include <linux/spinlock.h>
+#include <linux/wakelock.h>
+#include <linux/workqueue.h>
+#include <linux/timer.h>
+#include <linux/platform_data/modem.h>
+
+#include "modem_prj.h"
+
+#define DPRAM_MAGIC_CODE 0xAA
+
+/* interrupt masks.*/
+#define INT_MASK_VALID 0x0080
+#define INT_MASK_CMD 0x0040
+#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 EXT_UDL_MASK 0xF000
+#define EXT_UDL_CMD(x) ((x) & EXT_UDL_MASK)
+#define EXT_INT_VALID_MASK 0x8000
+#define EXT_CMD_VALID_MASK 0x4000
+#define UDL_CMD_VALID_MASK 0x2000
+#define EXT_INT_VALID(x) ((x) & EXT_INT_VALID_MASK)
+#define EXT_CMD_VALID(x) ((x) & EXT_CMD_VALID_MASK)
+#define UDL_CMD_VALID(x) ((x) & UDL_CMD_VALID_MASK)
+#define INT_EXT_CMD(x) (EXT_INT_VALID_MASK | EXT_CMD_VALID_MASK | (x))
+
+#define EXT_CMD_MASK(x) ((x) & 0x0FFF)
+#define EXT_CMD_SET_SPEED_LOW 0x0011
+#define EXT_CMD_SET_SPEED_MID 0x0012
+#define EXT_CMD_SET_SPEED_HIGH 0x0013
+
+#define UDL_RESULT_SUCCESS 0x1
+#define UDL_RESULT_FAIL 0x2
+
+#define UDL_CMD_MASK(x) (((x) >> 8) & 0xF)
+#define UDL_CMD_RECV_READY 0x1
+#define UDL_CMD_DL_START_REQ 0x2
+#define UDL_CMD_DL_START_RESP 0x3
+#define UDL_CMD_IMAGE_SEND_REQ 0x4
+#define UDL_CMD_SEND_DONE_RESP 0x5
+#define UDL_CMD_SEND_DONE_REQ 0x6
+#define UDL_CMD_UPDATE_DONE 0x7
+#define UDL_CMD_STATUS_UPDATE 0x8
+#define UDL_CMD_IMAGE_SEND_RESP 0x9
+#define UDL_CMD_EFS_CLEAR_RESP 0xB
+#define UDL_CMD_ALARM_BOOT_OK 0xC
+#define UDL_CMD_ALARM_BOOT_FAIL 0xD
+
+#define CMD_IMG_START_REQ 0x9200
+#define CMD_IMG_SEND_REQ 0x9400
+#define CMD_DL_SEND_DONE_REQ 0x9600
+#define CMD_UL_RECV_RESP 0x9601
+#define CMD_UL_RECV_DONE_RESP 0x9801
+
+/* special interrupt cmd indicating modem boot failure. */
+#define INT_POWERSAFE_FAIL 0xDEAD
+
+#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_MASK_REQ_ACK_RFS 0x0400 /* Request RES_ACK_RFS */
+#define INT_MASK_RES_ACK_RFS 0x0200 /* Response of REQ_ACK_RFS */
+#define INT_MASK_SEND_RFS 0x0100 /* Indicate sending RFS data */
+
+#define INT_MASK_RES_ACK_SET \
+ (INT_MASK_RES_ACK_F | INT_MASK_RES_ACK_R | INT_MASK_RES_ACK_RFS)
+
+#define INT_MASK_SEND_SET \
+ (INT_MASK_SEND_F | INT_MASK_SEND_R | INT_MASK_SEND_RFS)
+
+#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_CRASH_RESET 0x7
+#define INT_CMD_PHONE_START 0x8
+#define INT_CMD_ERR_DISPLAY 0x9
+#define INT_CMD_CRASH_EXIT 0x9
+#define INT_CMD_CP_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_PWR_OFF 0xF
+
+#define START_FLAG 0x7F
+#define END_FLAG 0x7E
+
+#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 16128
+#define DP_DUMP_HEADER_SIZE 7
+
+#define UDL_TIMEOUT (50 * HZ)
+#define UDL_SEND_TIMEOUT (200 * HZ)
+#define FORCE_CRASH_ACK_TIMEOUT (5 * HZ)
+#define DUMP_TIMEOUT (30 * HZ)
+#define DUMP_START_TIMEOUT (100 * HZ)
+#define DUMP_WAIT_TIMEOUT (HZ >> 10) /* 1/1024 second */
+#define RES_ACK_WAIT_TIMEOUT (HZ >> 8) /* 1/256 second */
+
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+#define MAX_RETRY_CNT 1
+#else
+#define MAX_RETRY_CNT 3
+#endif
+
+#define MAX_SKB_TXQ_DEPTH 1024
+
+enum host_boot_mode {
+ HOST_BOOT_MODE_NORMAL,
+ HOST_BOOT_MODE_DUMP,
+};
+
+enum dpram_init_status {
+ DPRAM_INIT_STATE_NONE,
+ DPRAM_INIT_STATE_READY,
+};
+
+struct dpram_boot_img {
+ char *addr;
+ int size;
+ enum host_boot_mode mode;
+ unsigned req;
+ unsigned resp;
+};
+
+#define MAX_PAYLOAD_SIZE 0x2000
+struct dpram_boot_frame {
+ unsigned req; /* AP->CP request */
+ unsigned resp; /* response expected by AP */
+ ssize_t len; /* data size in the buffer */
+ unsigned offset; /* offset to write into DPRAM */
+ char data[MAX_PAYLOAD_SIZE];
+};
+
+/* buffer type for modem image */
+struct dpram_dump_arg {
+ char *buff; /* pointer to the buffer */
+ int buff_size; /* buffer size */
+ unsigned req; /* AP->CP request */
+ unsigned resp; /* CP->AP response */
+ bool cmd; /* AP->CP command */
+};
+
+struct dpram_boot_map {
+ u32 __iomem *magic;
+ u8 __iomem *buff;
+ u32 __iomem *req;
+ u32 __iomem *resp;
+ u32 size;
+};
+
+struct qc_dpram_boot_map {
+ u8 __iomem *buff;
+ u16 __iomem *frame_size;
+ u16 __iomem *tag;
+ u16 __iomem *count;
+};
+
+struct dpram_dload_map {
+ u32 __iomem *magic;
+ u8 __iomem *buff;
+};
+
+struct dpram_uload_map {
+ u32 __iomem *magic;
+ u8 __iomem *buff;
+};
+
+struct ul_header {
+ u8 bop;
+ u16 total_frame;
+ u16 curr_frame;
+ u16 len;
+} __packed;
+
+struct dpram_udl_param {
+ unsigned char *addr;
+ unsigned int size;
+ unsigned int count;
+ unsigned int tag;
+};
+
+struct dpram_udl_check {
+ unsigned int total_size;
+ unsigned int rest_size;
+ unsigned int send_size;
+ unsigned int copy_start;
+ unsigned int copy_complete;
+ unsigned int boot_complete;
+};
+
+#define DP_BOOT_BUFF_OFFSET 4
+#define DP_DLOAD_BUFF_OFFSET 4
+#define DP_ULOAD_BUFF_OFFSET 4
+#define DP_BOOT_REQ_OFFSET 0
+#define DP_BOOT_RESP_OFFSET 8
+
+static inline bool circ_valid(u32 qsize, u32 in, u32 out)
+{
+ if (in >= qsize)
+ return false;
+
+ if (out >= qsize)
+ return false;
+
+ return true;
+}
+
+static inline int circ_get_space(int qsize, int in, int out)
+{
+ return (in < out) ? (out - in - 1) : (qsize + out - in - 1);
+}
+
+static inline int circ_get_usage(int qsize, int in, int out)
+{
+ return (in >= out) ? (in - out) : (qsize - out + in);
+}
+
+/**
+ * circ_read
+ * @dst: pointer to the destination buffer
+ * @src: pointer to the start of the circular queue
+ * @qsize: size of the circular queue
+ * @out: offset to read
+ * @len: length of data to be read
+ *
+ * Should be invoked after checking data length
+ */
+static inline void circ_read(u8 *dst, u8 *src, u32 qsize, u32 out, u32 len)
+{
+ unsigned len1;
+
+ if ((out + len) <= qsize) {
+ /* ----- (out) (in) ----- */
+ /* ----- 7f 00 00 7e ----- */
+ memcpy(dst, (src + out), len);
+ } else {
+ /* (in) ----------- (out) */
+ /* 00 7e ----------- 7f 00 */
+
+ /* 1) data start (out) ~ buffer end */
+ len1 = qsize - out;
+ memcpy(dst, (src + out), len1);
+
+ /* 2) buffer start ~ data end (in?) */
+ memcpy((dst + len1), src, (len - len1));
+ }
+}
+
+/**
+ * circ_write
+ * @dst: pointer to the start of the circular queue
+ * @src: pointer to the source
+ * @qsize: size of the circular queue
+ * @in: offset to write
+ * @len: length of data to be written
+ *
+ * Should be invoked after checking free space
+ */
+static inline void circ_write(u8 *dst, u8 *src, u32 qsize, u32 in, u32 len)
+{
+ u32 space;
+
+ if ((in + len) < qsize) {
+ /* (in) ----------- (out) */
+ /* 00 7e ----------- 7f 00 */
+ memcpy((dst + in), src, len);
+ } else {
+ /* ----- (out) (in) ----- */
+ /* ----- 7f 00 00 7e ----- */
+
+ /* 1) space start (in) ~ buffer end */
+ space = qsize - in;
+ memcpy((dst + in), src, ((len > space) ? space : len));
+
+ /* 2) buffer start ~ data end */
+ if (len > space)
+ memcpy(dst, (src + space), (len - space));
+ }
+}
+
+#if 1
+#define DPRAM_MAX_RXBQ_SIZE 256
+
+struct mif_rxb {
+ u8 *buff;
+ unsigned size;
+
+ u8 *data;
+ unsigned len;
+};
+
+struct mif_rxb_queue {
+ int size;
+ int in;
+ int out;
+ struct mif_rxb *rxb;
+};
+
+/*
+** RXB (DPRAM RX buffer) functions
+*/
+static inline struct mif_rxb *rxbq_create_pool(unsigned size, int count)
+{
+ struct mif_rxb *rxb;
+ u8 *buff;
+ int i;
+
+ rxb = kzalloc(sizeof(struct mif_rxb) * count, GFP_KERNEL);
+ if (!rxb) {
+ mif_info("ERR! kzalloc rxb fail\n");
+ return NULL;
+ }
+
+ buff = kzalloc((size * count), GFP_KERNEL|GFP_DMA);
+ if (!buff) {
+ mif_info("ERR! kzalloc buff fail\n");
+ kfree(rxb);
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++) {
+ rxb[i].buff = buff;
+ rxb[i].size = size;
+ buff += size;
+ }
+
+ return rxb;
+}
+
+static inline unsigned rxbq_get_page_size(unsigned len)
+{
+ return ((len + PAGE_SIZE - 1) >> PAGE_SHIFT) << PAGE_SHIFT;
+}
+
+static inline bool rxbq_empty(struct mif_rxb_queue *rxbq)
+{
+ return (rxbq->in == rxbq->out) ? true : false;
+}
+
+static inline int rxbq_free_size(struct mif_rxb_queue *rxbq)
+{
+ int in = rxbq->in;
+ int out = rxbq->out;
+ int qsize = rxbq->size;
+ return (in < out) ? (out - in - 1) : (qsize + out - in - 1);
+}
+
+static inline struct mif_rxb *rxbq_get_free_rxb(struct mif_rxb_queue *rxbq)
+{
+ struct mif_rxb *rxb = NULL;
+
+ if (likely(rxbq_free_size(rxbq) > 0)) {
+ rxb = &rxbq->rxb[rxbq->in];
+ rxbq->in++;
+ if (rxbq->in >= rxbq->size)
+ rxbq->in -= rxbq->size;
+ rxb->data = rxb->buff;
+ }
+
+ return rxb;
+}
+
+static inline int rxbq_size(struct mif_rxb_queue *rxbq)
+{
+ int in = rxbq->in;
+ int out = rxbq->out;
+ int qsize = rxbq->size;
+ return (in >= out) ? (in - out) : (qsize - out + in);
+}
+
+static inline struct mif_rxb *rxbq_get_data_rxb(struct mif_rxb_queue *rxbq)
+{
+ struct mif_rxb *rxb = NULL;
+
+ if (likely(!rxbq_empty(rxbq))) {
+ rxb = &rxbq->rxb[rxbq->out];
+ rxbq->out++;
+ if (rxbq->out >= rxbq->size)
+ rxbq->out -= rxbq->size;
+ }
+
+ return rxb;
+}
+
+static inline u8 *rxb_put(struct mif_rxb *rxb, unsigned len)
+{
+ rxb->len = len;
+ return rxb->data;
+}
+
+static inline void rxb_clear(struct mif_rxb *rxb)
+{
+ rxb->data = NULL;
+ rxb->len = 0;
+}
+#endif
+
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+#define MAX_TRACE_SIZE 1024
+
+struct trace_data {
+ struct timespec ts;
+ enum dev_format dev;
+ u8 *data;
+ int size;
+};
+
+struct trace_queue {
+ spinlock_t lock;
+ int in;
+ int out;
+ struct trace_data trd[MAX_TRACE_SIZE];
+};
+
+static inline struct trace_data *trq_get_free_slot(struct trace_queue *trq)
+{
+ int in = trq->in;
+ int out = trq->out;
+ int qsize = MAX_TRACE_SIZE;
+ struct trace_data *trd = NULL;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&trq->lock, flags);
+
+ if (circ_get_space(qsize, in, out) < 1) {
+ spin_unlock_irqrestore(&trq->lock, flags);
+ return NULL;
+ }
+
+ trd = &trq->trd[in];
+
+ in++;
+ if (in == qsize)
+ trq->in = 0;
+ else
+ trq->in = in;
+
+ spin_unlock_irqrestore(&trq->lock, flags);
+
+ return trd;
+}
+
+static inline struct trace_data *trq_get_data_slot(struct trace_queue *trq)
+{
+ int in = trq->in;
+ int out = trq->out;
+ int qsize = MAX_TRACE_SIZE;
+ struct trace_data *trd = NULL;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&trq->lock, flags);
+
+ if (circ_get_usage(qsize, in, out) < 1) {
+ spin_unlock_irqrestore(&trq->lock, flags);
+ return NULL;
+ }
+
+ trd = &trq->trd[out];
+
+ out++;
+ if (out == qsize)
+ trq->out = 0;
+ else
+ trq->out = out;
+
+ spin_unlock_irqrestore(&trq->lock, flags);
+
+ return trd;
+}
+#endif
+
+#endif
diff --git a/drivers/misc/modem_if/modem_link_device_mipi.c b/drivers/misc/modem_if/modem_link_device_mipi.c
new file mode 100644
index 00000000000..f2804e94ee1
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_mipi.c
@@ -0,0 +1,1418 @@
+/* /linux/drivers/new_modem_if/link_dev_mipi.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/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/wakelock.h>
+#include <linux/semaphore.h>
+#include <linux/hsi_driver_if.h>
+
+#include <linux/platform_data/modem.h>
+#include "modem_prj.h"
+#include "modem_link_device_mipi.h"
+#include "modem_utils.h"
+
+static int mipi_hsi_init_communication(struct link_device *ld,
+ struct io_device *iod)
+{
+ struct mipi_link_device *mipi_ld = to_mipi_link_device(ld);
+
+ switch (iod->format) {
+ case IPC_FMT:
+ return hsi_init_handshake(mipi_ld, HSI_INIT_MODE_NORMAL);
+
+ case IPC_BOOT:
+ return hsi_init_handshake(mipi_ld,
+ HSI_INIT_MODE_FLASHLESS_BOOT);
+
+ case IPC_RAMDUMP:
+ return hsi_init_handshake(mipi_ld,
+ HSI_INIT_MODE_CP_RAMDUMP);
+
+ case IPC_RFS:
+ case IPC_RAW:
+ default:
+ return 0;
+ }
+}
+
+static void mipi_hsi_terminate_communication(
+ struct link_device *ld, struct io_device *iod)
+{
+ struct mipi_link_device *mipi_ld = to_mipi_link_device(ld);
+
+ switch (iod->format) {
+ case IPC_BOOT:
+ if (&mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened)
+ if_hsi_close_channel(&mipi_ld->hsi_channles[
+ HSI_FLASHLESS_CHANNEL]);
+ break;
+
+ case IPC_RAMDUMP:
+ if (&mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].opened)
+ if_hsi_close_channel(&mipi_ld->hsi_channles[
+ HSI_CP_RAMDUMP_CHANNEL]);
+ break;
+
+ case IPC_FMT:
+ case IPC_RFS:
+ case IPC_RAW:
+ default:
+ break;
+ }
+}
+
+static int mipi_hsi_send(struct link_device *ld, struct io_device *iod,
+ struct sk_buff *skb)
+{
+ int ret;
+ struct mipi_link_device *mipi_ld = to_mipi_link_device(ld);
+
+ struct sk_buff_head *txq;
+
+ switch (iod->format) {
+ case IPC_RAW:
+ txq = &ld->sk_raw_tx_q;
+ break;
+
+ case IPC_RAMDUMP:
+ ret = if_hsi_write(&mipi_ld->hsi_channles[
+ HSI_CP_RAMDUMP_CHANNEL],
+ (u32 *)skb->data, skb->len);
+ if (ret < 0) {
+ mif_err("[MIPI-HSI] write fail : %d\n", ret);
+ dev_kfree_skb_any(skb);
+ return ret;
+ } else
+ mif_debug("[MIPI-HSI] write Done\n");
+ dev_kfree_skb_any(skb);
+ return ret;
+
+ case IPC_BOOT:
+ ret = if_hsi_write(&mipi_ld->hsi_channles[
+ HSI_FLASHLESS_CHANNEL],
+ (u32 *)skb->data, skb->len);
+ if (ret < 0) {
+ mif_err("[MIPI-HSI] write fail : %d\n", ret);
+ dev_kfree_skb_any(skb);
+ return ret;
+ } else
+ mif_debug("[MIPI-HSI] write Done\n");
+ dev_kfree_skb_any(skb);
+ return ret;
+
+ case IPC_FMT:
+ case IPC_RFS:
+ default:
+ txq = &ld->sk_fmt_tx_q;
+ break;
+ }
+
+ /* save io device */
+ skbpriv(skb)->iod = iod;
+ /* en queue skb data */
+ skb_queue_tail(txq, skb);
+
+ queue_work(ld->tx_wq, &ld->tx_work);
+ return skb->len;
+}
+
+static void mipi_hsi_tx_work(struct work_struct *work)
+{
+ int ret;
+ struct link_device *ld = container_of(work, struct link_device,
+ tx_work);
+ struct mipi_link_device *mipi_ld = to_mipi_link_device(ld);
+ struct io_device *iod;
+ struct sk_buff *fmt_skb;
+ struct sk_buff *raw_skb;
+ int send_channel = 0;
+
+ while (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) {
+ mif_debug("[MIPI-HSI] fmt qlen : %d, raw qlen:%d\n",
+ ld->sk_fmt_tx_q.qlen, ld->sk_raw_tx_q.qlen);
+
+ fmt_skb = skb_dequeue(&ld->sk_fmt_tx_q);
+ if (fmt_skb) {
+ iod = skbpriv(fmt_skb)->iod;
+
+ mif_debug("[MIPI-HSI] dequeue. fmt qlen : %d\n",
+ ld->sk_fmt_tx_q.qlen);
+
+ if (ld->com_state != COM_ONLINE) {
+ mif_err("[MIPI-HSI] CP not ready\n");
+ skb_queue_head(&ld->sk_fmt_tx_q, fmt_skb);
+ return;
+ }
+
+ switch (iod->format) {
+ case IPC_FMT:
+ send_channel = HSI_FMT_CHANNEL;
+ break;
+
+ case IPC_RFS:
+ send_channel = HSI_RFS_CHANNEL;
+ break;
+
+ case IPC_BOOT:
+ send_channel = HSI_FLASHLESS_CHANNEL;
+ break;
+
+ case IPC_RAMDUMP:
+ send_channel = HSI_CP_RAMDUMP_CHANNEL;
+ break;
+
+ default:
+ break;
+ }
+ ret = if_hsi_protocol_send(mipi_ld, send_channel,
+ (u32 *)fmt_skb->data, fmt_skb->len);
+ if (ret < 0) {
+ /* TODO: Re Enqueue */
+ mif_err("[MIPI-HSI] write fail : %d\n", ret);
+ } else
+ mif_debug("[MIPI-HSI] write Done\n");
+
+ dev_kfree_skb_any(fmt_skb);
+ }
+
+ raw_skb = skb_dequeue(&ld->sk_raw_tx_q);
+ if (raw_skb) {
+ if (ld->com_state != COM_ONLINE) {
+ mif_err("[MIPI-HSI] RAW CP not ready\n");
+ skb_queue_head(&ld->sk_raw_tx_q, raw_skb);
+ return;
+ }
+
+ mif_debug("[MIPI-HSI] dequeue. raw qlen:%d\n",
+ ld->sk_raw_tx_q.qlen);
+
+ ret = if_hsi_protocol_send(mipi_ld, HSI_RAW_CHANNEL,
+ (u32 *)raw_skb->data, raw_skb->len);
+ if (ret < 0) {
+ /* TODO: Re Enqueue */
+ mif_err("[MIPI-HSI] write fail : %d\n", ret);
+ } else
+ mif_debug("[MIPI-HSI] write Done\n");
+
+ dev_kfree_skb_any(raw_skb);
+ }
+ }
+}
+
+static int __devinit if_hsi_probe(struct hsi_device *dev);
+static struct hsi_device_driver if_hsi_driver = {
+ .ctrl_mask = ANY_HSI_CONTROLLER,
+ .probe = if_hsi_probe,
+ .driver = {
+ .name = "if_hsi_driver"
+ },
+};
+
+static int if_hsi_set_wakeline(struct if_hsi_channel *channel,
+ unsigned int state)
+{
+ int ret;
+
+ spin_lock_bh(&channel->acwake_lock);
+ if (channel->acwake == state) {
+ spin_unlock_bh(&channel->acwake_lock);
+ return 0;
+ }
+
+ ret = hsi_ioctl(channel->dev, state ?
+ HSI_IOCTL_ACWAKE_UP : HSI_IOCTL_ACWAKE_DOWN, NULL);
+ if (ret) {
+ mif_err("[MIPI-HSI] ACWAKE(%d) setting fail : %d\n", state,
+ ret);
+ /* duplicate operation */
+ if (ret == -EPERM)
+ channel->acwake = state;
+ spin_unlock_bh(&channel->acwake_lock);
+ return ret;
+ }
+
+ channel->acwake = state;
+ spin_unlock_bh(&channel->acwake_lock);
+
+ mif_debug("[MIPI-HSI] ACWAKE_%d(%d)\n", channel->channel_id, state);
+ return 0;
+}
+
+static void if_hsi_acwake_down_func(unsigned long data)
+{
+ int i;
+ struct if_hsi_channel *channel;
+ struct mipi_link_device *mipi_ld = (struct mipi_link_device *)data;
+
+ mif_debug("[MIPI-HSI]\n");
+
+ for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) {
+ channel = &mipi_ld->hsi_channles[i];
+
+ if ((channel->send_step == STEP_IDLE) &&
+ (channel->recv_step == STEP_IDLE)) {
+ if_hsi_set_wakeline(channel, 0);
+ } else {
+ mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies +
+ HSI_ACWAKE_DOWN_TIMEOUT);
+ mif_debug("[MIPI-HSI] mod_timer done(%d)\n",
+ HSI_ACWAKE_DOWN_TIMEOUT);
+ return;
+ }
+ }
+}
+
+static int if_hsi_open_channel(struct if_hsi_channel *channel)
+{
+ int ret;
+
+ if (channel->opened) {
+ mif_debug("[MIPI-HSI] channel %d is already opened\n",
+ channel->channel_id);
+ return 0;
+ }
+
+ ret = hsi_open(channel->dev);
+ if (ret) {
+ mif_err("[MIPI-HSI] hsi_open fail : %d\n", ret);
+ return ret;
+ }
+ channel->opened = 1;
+
+ channel->send_step = STEP_IDLE;
+ channel->recv_step = STEP_IDLE;
+
+ mif_debug("[MIPI-HSI] hsi_open Done : %d\n", channel->channel_id);
+ return 0;
+}
+
+static int if_hsi_close_channel(struct if_hsi_channel *channel)
+{
+ unsigned long int flags;
+
+ if (!channel->opened) {
+ mif_debug("[MIPI-HSI] channel %d is already closed\n",
+ channel->channel_id);
+ return 0;
+ }
+
+ if_hsi_set_wakeline(channel, 0);
+ hsi_write_cancel(channel->dev);
+ hsi_read_cancel(channel->dev);
+
+ spin_lock_irqsave(&channel->tx_state_lock, flags);
+ channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING;
+ spin_unlock_irqrestore(&channel->tx_state_lock, flags);
+ spin_lock_irqsave(&channel->rx_state_lock, flags);
+ channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING;
+ spin_unlock_irqrestore(&channel->rx_state_lock, flags);
+
+ hsi_close(channel->dev);
+ channel->opened = 0;
+
+ channel->send_step = STEP_CLOSED;
+ channel->recv_step = STEP_CLOSED;
+
+ mif_debug("[MIPI-HSI] hsi_close Done : %d\n", channel->channel_id);
+ return 0;
+}
+
+static void mipi_hsi_start_work(struct work_struct *work)
+{
+ int ret;
+ u32 start_cmd = 0xC2;
+ struct mipi_link_device *mipi_ld =
+ container_of(work, struct mipi_link_device,
+ start_work.work);
+
+ ret = if_hsi_protocol_send(mipi_ld, HSI_CMD_CHANNEL, &start_cmd, 1);
+ if (ret < 0) {
+ /* TODO: Re Enqueue */
+ mif_err("[MIPI-HSI] First write fail : %d\n", ret);
+ } else {
+ mif_debug("[MIPI-HSI] First write Done : %d\n", ret);
+ mipi_ld->ld.com_state = COM_ONLINE;
+ }
+}
+
+static int hsi_init_handshake(struct mipi_link_device *mipi_ld, int mode)
+{
+ int ret;
+ int i;
+ struct hst_ctx tx_config;
+ struct hsr_ctx rx_config;
+
+ switch (mode) {
+ case HSI_INIT_MODE_NORMAL:
+ if (timer_pending(&mipi_ld->hsi_acwake_down_timer))
+ del_timer(&mipi_ld->hsi_acwake_down_timer);
+
+ for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) {
+ if (mipi_ld->hsi_channles[i].opened) {
+ hsi_write_cancel(mipi_ld->hsi_channles[i].dev);
+ hsi_read_cancel(mipi_ld->hsi_channles[i].dev);
+ } else {
+ ret = if_hsi_open_channel(
+ &mipi_ld->hsi_channles[i]);
+ if (ret)
+ return ret;
+ }
+
+ hsi_ioctl(mipi_ld->hsi_channles[i].dev,
+ HSI_IOCTL_GET_TX, &tx_config);
+ tx_config.mode = 2;
+ tx_config.divisor = 0; /* Speed : 96MHz */
+ tx_config.channels = HSI_MAX_CHANNELS;
+ hsi_ioctl(mipi_ld->hsi_channles[i].dev,
+ HSI_IOCTL_SET_TX, &tx_config);
+
+ hsi_ioctl(mipi_ld->hsi_channles[i].dev,
+ HSI_IOCTL_GET_RX, &rx_config);
+ rx_config.mode = 2;
+ rx_config.divisor = 0; /* Speed : 96MHz */
+ rx_config.channels = HSI_MAX_CHANNELS;
+ hsi_ioctl(mipi_ld->hsi_channles[i].dev,
+ HSI_IOCTL_SET_RX, &rx_config);
+ mif_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n");
+
+ hsi_ioctl(mipi_ld->hsi_channles[i].dev,
+ HSI_IOCTL_SET_ACREADY_NORMAL, NULL);
+ mif_debug("[MIPI-HSI] ACREADY_NORMAL\n");
+ }
+
+ if (mipi_ld->ld.com_state != COM_ONLINE)
+ mipi_ld->ld.com_state = COM_HANDSHAKE;
+
+ ret = hsi_read(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev,
+ mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data,
+ 1);
+ if (ret)
+ mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret);
+
+ if (mipi_ld->ld.com_state != COM_ONLINE)
+ schedule_delayed_work(&mipi_ld->start_work, 3 * HZ);
+
+ mif_debug("[MIPI-HSI] hsi_init_handshake Done : MODE_NORMAL\n");
+ return 0;
+
+ case HSI_INIT_MODE_FLASHLESS_BOOT:
+ mipi_ld->ld.com_state = COM_BOOT;
+
+ if (mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened) {
+ hsi_write_cancel(mipi_ld->hsi_channles[
+ HSI_FLASHLESS_CHANNEL].dev);
+ hsi_read_cancel(mipi_ld->hsi_channles[
+ HSI_FLASHLESS_CHANNEL].dev);
+ } else {
+ ret = if_hsi_open_channel(
+ &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL]);
+ if (ret)
+ return ret;
+ }
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ HSI_IOCTL_GET_TX, &tx_config);
+ tx_config.mode = 2;
+ tx_config.divisor = 0; /* Speed : 96MHz */
+ tx_config.channels = 1;
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ HSI_IOCTL_SET_TX, &tx_config);
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ HSI_IOCTL_GET_RX, &rx_config);
+ rx_config.mode = 2;
+ rx_config.divisor = 0; /* Speed : 96MHz */
+ rx_config.channels = 1;
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ HSI_IOCTL_SET_RX, &rx_config);
+ mif_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n");
+
+ if (!wake_lock_active(&mipi_ld->wlock)) {
+ wake_lock(&mipi_ld->wlock);
+ mif_debug("[MIPI-HSI] wake_lock\n");
+ }
+
+ if_hsi_set_wakeline(
+ &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL], 1);
+
+ ret = hsi_read(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].rx_data,
+ HSI_FLASHBOOT_ACK_LEN / 4);
+ if (ret)
+ mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret);
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ HSI_IOCTL_SET_ACREADY_NORMAL, NULL);
+
+ mif_debug("[MIPI-HSI] hsi_init_handshake Done : FLASHLESS_BOOT\n");
+ return 0;
+
+ case HSI_INIT_MODE_CP_RAMDUMP:
+ mipi_ld->ld.com_state = COM_CRASH;
+
+ if (mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].opened) {
+ hsi_write_cancel(mipi_ld->hsi_channles[
+ HSI_CP_RAMDUMP_CHANNEL].dev);
+ hsi_read_cancel(mipi_ld->hsi_channles[
+ HSI_CP_RAMDUMP_CHANNEL].dev);
+ } else {
+ ret = if_hsi_open_channel(
+ &mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL]);
+ if (ret)
+ return ret;
+ }
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev,
+ HSI_IOCTL_GET_TX, &tx_config);
+ tx_config.mode = 2;
+ tx_config.divisor = 0; /* Speed : 96MHz */
+ tx_config.channels = 1;
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev,
+ HSI_IOCTL_SET_TX, &tx_config);
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev,
+ HSI_IOCTL_GET_RX, &rx_config);
+ rx_config.mode = 2;
+ rx_config.divisor = 0; /* Speed : 96MHz */
+ rx_config.channels = 1;
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev,
+ HSI_IOCTL_SET_RX, &rx_config);
+ mif_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n");
+
+ if (!wake_lock_active(&mipi_ld->wlock)) {
+ wake_lock(&mipi_ld->wlock);
+ mif_debug("[MIPI-HSI] wake_lock\n");
+ }
+
+ if_hsi_set_wakeline(
+ &mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL], 1);
+
+ ret = hsi_read(
+ mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev,
+ mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].rx_data,
+ DUMP_ERR_INFO_SIZE);
+ if (ret)
+ mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret);
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev,
+ HSI_IOCTL_SET_ACREADY_NORMAL, NULL);
+
+ mif_debug("[MIPI-HSI] hsi_init_handshake Done : RAMDUMP\n");
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static u32 if_hsi_create_cmd(u32 cmd_type, int ch, void *arg)
+{
+ u32 cmd = 0;
+ unsigned int size = 0;
+
+ switch (cmd_type) {
+ case HSI_LL_MSG_BREAK:
+ return 0;
+
+ case HSI_LL_MSG_CONN_CLOSED:
+ cmd = ((HSI_LL_MSG_CONN_CLOSED & 0x0000000F) << 28)
+ |((ch & 0x000000FF) << 24);
+ return cmd;
+
+ case HSI_LL_MSG_ACK:
+ size = *(unsigned int *)arg;
+
+ cmd = ((HSI_LL_MSG_ACK & 0x0000000F) << 28)
+ |((ch & 0x000000FF) << 24) | ((size & 0x00FFFFFF));
+ return cmd;
+
+ case HSI_LL_MSG_NAK:
+ cmd = ((HSI_LL_MSG_NAK & 0x0000000F) << 28)
+ |((ch & 0x000000FF) << 24);
+ return cmd;
+
+ case HSI_LL_MSG_OPEN_CONN_OCTET:
+ size = *(unsigned int *)arg;
+
+ cmd = ((HSI_LL_MSG_OPEN_CONN_OCTET & 0x0000000F)
+ << 28) | ((ch & 0x000000FF) << 24)
+ | ((size & 0x00FFFFFF));
+ return cmd;
+
+ case HSI_LL_MSG_OPEN_CONN:
+ case HSI_LL_MSG_CONF_RATE:
+ case HSI_LL_MSG_CANCEL_CONN:
+ case HSI_LL_MSG_CONN_READY:
+ case HSI_LL_MSG_ECHO:
+ case HSI_LL_MSG_INFO_REQ:
+ case HSI_LL_MSG_INFO:
+ case HSI_LL_MSG_CONFIGURE:
+ case HSI_LL_MSG_ALLOCATE_CH:
+ case HSI_LL_MSG_RELEASE_CH:
+ case HSI_LL_MSG_INVALID:
+ default:
+ mif_err("[MIPI-HSI] ERROR... CMD Not supported : %08x\n",
+ cmd_type);
+ return -EINVAL;
+ }
+}
+
+static void if_hsi_cmd_work(struct work_struct *work)
+{
+ int ret;
+ unsigned long int flags;
+ struct mipi_link_device *mipi_ld =
+ container_of(work, struct mipi_link_device, cmd_work);
+ struct if_hsi_channel *channel =
+ &mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL];
+ struct if_hsi_command *hsi_cmd;
+
+ mif_debug("[MIPI-HSI] cmd_work\n");
+
+ do {
+ spin_lock_irqsave(&mipi_ld->list_cmd_lock, flags);
+ if (!list_empty(&mipi_ld->list_of_hsi_cmd)) {
+ hsi_cmd = list_entry(mipi_ld->list_of_hsi_cmd.next,
+ struct if_hsi_command, list);
+ list_del(&hsi_cmd->list);
+ spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags);
+
+ channel->send_step = STEP_TX;
+ if_hsi_set_wakeline(channel, 1);
+ mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies +
+ HSI_ACWAKE_DOWN_TIMEOUT);
+ } else {
+ spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags);
+ channel->send_step = STEP_IDLE;
+ break;
+ }
+ mif_debug("[MIPI-HSI] take command : %08x\n", hsi_cmd->command);
+
+ ret = if_hsi_write(channel, &hsi_cmd->command, 4);
+ if (ret < 0) {
+ mif_err("[MIPI-HSI] write command fail : %d\n", ret);
+ if_hsi_set_wakeline(channel, 0);
+ channel->send_step = STEP_IDLE;
+ return;
+ }
+ mif_debug("[MIPI-HSI] SEND CMD : %08x\n", hsi_cmd->command);
+
+ kfree(hsi_cmd);
+ } while (true);
+}
+
+static int if_hsi_send_command(struct mipi_link_device *mipi_ld,
+ u32 cmd_type, int ch, u32 param)
+{
+ unsigned long int flags;
+ struct if_hsi_command *hsi_cmd;
+
+ hsi_cmd = kmalloc(sizeof(struct if_hsi_command), GFP_ATOMIC);
+ if (!hsi_cmd) {
+ mif_err("[MIPI-HSI] hsi_cmd kmalloc fail\n");
+ return -ENOMEM;
+ }
+ INIT_LIST_HEAD(&hsi_cmd->list);
+
+ hsi_cmd->command = if_hsi_create_cmd(cmd_type, ch, &param);
+ mif_debug("[MIPI-HSI] made command : %08x\n", hsi_cmd->command);
+
+ spin_lock_irqsave(&mipi_ld->list_cmd_lock, flags);
+ list_add_tail(&hsi_cmd->list, &mipi_ld->list_of_hsi_cmd);
+ spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags);
+
+ mif_debug("[MIPI-HSI] queue_work : cmd_work\n");
+ queue_work(mipi_ld->mipi_wq, &mipi_ld->cmd_work);
+
+ return 0;
+}
+
+static int if_hsi_decode_cmd(u32 *cmd_data, u32 *cmd, u32 *ch,
+ u32 *param)
+{
+ u32 data = *cmd_data;
+ u8 lrc_cal, lrc_act;
+ u8 val1, val2, val3;
+
+ *cmd = ((data & 0xF0000000) >> 28);
+ switch (*cmd) {
+ case HSI_LL_MSG_BREAK:
+ mif_err("[MIPI-HSI] Command MSG_BREAK Received\n");
+ return -1;
+
+ case HSI_LL_MSG_OPEN_CONN:
+ *ch = ((data & 0x0F000000) >> 24);
+ *param = ((data & 0x00FFFF00) >> 8);
+ val1 = ((data & 0xFF000000) >> 24);
+ val2 = ((data & 0x00FF0000) >> 16);
+ val3 = ((data & 0x0000FF00) >> 8);
+ lrc_act = (data & 0x000000FF);
+ lrc_cal = val1 ^ val2 ^ val3;
+
+ if (lrc_cal != lrc_act) {
+ mif_err("[MIPI-HSI] CAL is broken\n");
+ return -1;
+ }
+ return 0;
+
+ case HSI_LL_MSG_CONN_READY:
+ case HSI_LL_MSG_CONN_CLOSED:
+ case HSI_LL_MSG_CANCEL_CONN:
+ case HSI_LL_MSG_NAK:
+ *ch = ((data & 0x0F000000) >> 24);
+ return 0;
+
+ case HSI_LL_MSG_ACK:
+ *ch = ((data & 0x0F000000) >> 24);
+ *param = (data & 0x00FFFFFF);
+ return 0;
+
+ case HSI_LL_MSG_CONF_RATE:
+ *ch = ((data & 0x0F000000) >> 24);
+ *param = ((data & 0x0F000000) >> 24);
+ return 0;
+
+ case HSI_LL_MSG_OPEN_CONN_OCTET:
+ *ch = ((data & 0x0F000000) >> 24);
+ *param = (data & 0x00FFFFFF);
+ return 0;
+
+ case HSI_LL_MSG_ECHO:
+ case HSI_LL_MSG_INFO_REQ:
+ case HSI_LL_MSG_INFO:
+ case HSI_LL_MSG_CONFIGURE:
+ case HSI_LL_MSG_ALLOCATE_CH:
+ case HSI_LL_MSG_RELEASE_CH:
+ case HSI_LL_MSG_INVALID:
+ default:
+ mif_err("[MIPI-HSI] Invalid command received : %08x\n", *cmd);
+ *cmd = HSI_LL_MSG_INVALID;
+ *ch = HSI_LL_INVALID_CHANNEL;
+ return -1;
+ }
+ return 0;
+}
+
+static int if_hsi_rx_cmd_handle(struct mipi_link_device *mipi_ld, u32 cmd,
+ u32 ch, u32 param)
+{
+ int ret;
+ struct if_hsi_channel *channel = &mipi_ld->hsi_channles[ch];
+
+ mif_debug("[MIPI-HSI] if_hsi_rx_cmd_handle cmd=0x%x, ch=%d, param=%d\n",
+ cmd, ch, param);
+
+ switch (cmd) {
+ case HSI_LL_MSG_OPEN_CONN_OCTET:
+ switch (channel->recv_step) {
+ case STEP_IDLE:
+ channel->recv_step = STEP_TO_ACK;
+
+ if (!wake_lock_active(&mipi_ld->wlock)) {
+ wake_lock(&mipi_ld->wlock);
+ mif_debug("[MIPI-HSI] wake_lock\n");
+ }
+
+ if_hsi_set_wakeline(channel, 1);
+ mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies +
+ HSI_ACWAKE_DOWN_TIMEOUT);
+ mif_debug("[MIPI-HSI] mod_timer done(%d)\n",
+ HSI_ACWAKE_DOWN_TIMEOUT);
+
+ ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_ACK, ch,
+ param);
+ if (ret) {
+ mif_err("[MIPI-HSI] if_hsi_send_command fail : %d\n",
+ ret);
+ return ret;
+ }
+
+ channel->packet_size = param;
+ channel->recv_step = STEP_RX;
+ if (param % 4)
+ param += (4 - (param % 4));
+ channel->rx_count = param;
+ ret = hsi_read(channel->dev, channel->rx_data,
+ channel->rx_count / 4);
+ if (ret) {
+ mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret);
+ return ret;
+ }
+ return 0;
+
+ case STEP_NOT_READY:
+ ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_NAK, ch,
+ param);
+ if (ret) {
+ mif_err("[MIPI-HSI] if_hsi_send_command fail : %d\n",
+ ret);
+ return ret;
+ }
+ return 0;
+
+ default:
+ mif_err("[MIPI-HSI] wrong state : %08x, recv_step : %d\n",
+ cmd, channel->recv_step);
+ return -1;
+ }
+
+ case HSI_LL_MSG_ACK:
+ case HSI_LL_MSG_NAK:
+ switch (channel->send_step) {
+ case STEP_WAIT_FOR_ACK:
+ case STEP_SEND_OPEN_CONN:
+ if (cmd == HSI_LL_MSG_ACK) {
+ channel->send_step = STEP_TX;
+ channel->got_nack = 0;
+ mif_debug("[MIPI-HSI] got ack\n");
+ } else {
+ channel->send_step = STEP_WAIT_FOR_ACK;
+ channel->got_nack = 1;
+ mif_debug("[MIPI-HSI] got nack\n");
+ }
+
+ up(&channel->ack_done_sem);
+ return 0;
+
+ default:
+ mif_err("[MIPI-HSI] wrong state : %08x\n", cmd);
+ return -1;
+ }
+
+ case HSI_LL_MSG_CONN_CLOSED:
+ switch (channel->send_step) {
+ case STEP_TX:
+ case STEP_WAIT_FOR_CONN_CLOSED:
+ mif_debug("[MIPI-HSI] got close\n");
+
+ channel->send_step = STEP_IDLE;
+ up(&channel->close_conn_done_sem);
+ return 0;
+
+ default:
+ mif_err("[MIPI-HSI] wrong state : %08x\n", cmd);
+ return -1;
+ }
+
+ case HSI_LL_MSG_OPEN_CONN:
+ case HSI_LL_MSG_ECHO:
+ case HSI_LL_MSG_CANCEL_CONN:
+ case HSI_LL_MSG_CONF_RATE:
+ default:
+ mif_err("[MIPI-HSI] ERROR... CMD Not supported : %08x\n", cmd);
+ return -EINVAL;
+ }
+}
+
+static int if_hsi_protocol_send(struct mipi_link_device *mipi_ld, int ch,
+ u32 *data, unsigned int len)
+{
+ int ret;
+ int retry_count = 0;
+ int ack_timeout_cnt = 0;
+ struct io_device *iod;
+ struct if_hsi_channel *channel = &mipi_ld->hsi_channles[ch];
+
+ if (channel->send_step != STEP_IDLE) {
+ mif_err("[MIPI-HSI] send step is not IDLE : %d\n",
+ channel->send_step);
+ return -EBUSY;
+ }
+ channel->send_step = STEP_SEND_OPEN_CONN;
+
+ if (!wake_lock_active(&mipi_ld->wlock)) {
+ wake_lock(&mipi_ld->wlock);
+ mif_debug("[MIPI-HSI] wake_lock\n");
+ }
+
+ if_hsi_set_wakeline(channel, 1);
+ mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies +
+ HSI_ACWAKE_DOWN_TIMEOUT);
+ mif_debug("[MIPI-HSI] mod_timer done(%d)\n",
+ HSI_ACWAKE_DOWN_TIMEOUT);
+
+retry_send:
+
+ ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_OPEN_CONN_OCTET, ch,
+ len);
+ if (ret) {
+ mif_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", ret);
+ if_hsi_set_wakeline(channel, 0);
+ channel->send_step = STEP_IDLE;
+ return -1;
+ }
+
+ channel->send_step = STEP_WAIT_FOR_ACK;
+
+ if (down_timeout(&channel->ack_done_sem, HSI_ACK_DONE_TIMEOUT) < 0) {
+ mif_err("[MIPI-HSI] ch=%d, ack_done timeout\n",
+ channel->channel_id);
+
+ if_hsi_set_wakeline(channel, 0);
+
+ if (mipi_ld->ld.com_state == COM_ONLINE) {
+ ack_timeout_cnt++;
+ if (ack_timeout_cnt < 10) {
+ if_hsi_set_wakeline(channel, 1);
+ mif_err("[MIPI-HSI] ch=%d, retry send open. cnt : %d\n",
+ channel->channel_id, ack_timeout_cnt);
+ goto retry_send;
+ }
+
+ /* try to recover cp */
+ iod = link_get_iod_with_format(&mipi_ld->ld, IPC_FMT);
+ if (iod)
+ iod->modem_state_changed(iod,
+ STATE_CRASH_RESET);
+ }
+
+ channel->send_step = STEP_IDLE;
+ return -ETIMEDOUT;
+ }
+ mif_debug("[MIPI-HSI] ch=%d, got ack_done=%d\n", channel->channel_id,
+ channel->got_nack);
+
+ if (channel->got_nack && (retry_count < 10)) {
+ mif_debug("[MIPI-HSI] ch=%d, got nack=%d retry=%d\n",
+ channel->channel_id, channel->got_nack,
+ retry_count);
+ retry_count++;
+ msleep_interruptible(1);
+ goto retry_send;
+ }
+ retry_count = 0;
+
+ channel->send_step = STEP_TX;
+
+ ret = if_hsi_write(channel, data, len);
+ if (ret < 0) {
+ mif_err("[MIPI-HSI] if_hsi_write fail : %d\n", ret);
+ if_hsi_set_wakeline(channel, 0);
+ channel->send_step = STEP_IDLE;
+ return ret;
+ }
+ mif_debug("[MIPI-HSI] SEND DATA : %08x(%d)\n", *data, len);
+
+ mif_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n",
+ *channel->tx_data, *(channel->tx_data + 1),
+ *(channel->tx_data + 2), *(channel->tx_data + 3),
+ *(channel->tx_data + 4), *(channel->tx_data + 5),
+ *(channel->tx_data + 6), *(channel->tx_data + 7));
+
+ channel->send_step = STEP_WAIT_FOR_CONN_CLOSED;
+ if (down_timeout(&channel->close_conn_done_sem,
+ HSI_CLOSE_CONN_DONE_TIMEOUT) < 0) {
+ mif_err("[MIPI-HSI] ch=%d, close conn timeout\n",
+ channel->channel_id);
+ if_hsi_set_wakeline(channel, 0);
+ channel->send_step = STEP_IDLE;
+ return -ETIMEDOUT;
+ }
+ mif_debug("[MIPI-HSI] ch=%d, got close_conn_done\n",
+ channel->channel_id);
+
+ channel->send_step = STEP_IDLE;
+
+ mif_debug("[MIPI-HSI] write protocol Done : %d\n", channel->tx_count);
+ return channel->tx_count;
+}
+
+static int if_hsi_write(struct if_hsi_channel *channel, u32 *data,
+ unsigned int size)
+{
+ int ret;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&channel->tx_state_lock, flags);
+ if (channel->tx_state & HSI_CHANNEL_TX_STATE_WRITING) {
+ spin_unlock_irqrestore(&channel->tx_state_lock, flags);
+ return -EBUSY;
+ }
+ channel->tx_state |= HSI_CHANNEL_TX_STATE_WRITING;
+ spin_unlock_irqrestore(&channel->tx_state_lock, flags);
+
+ channel->tx_data = data;
+ if (size % 4)
+ size += (4 - (size % 4));
+ channel->tx_count = size;
+
+ mif_debug("[MIPI-HSI] submit write data : 0x%x(%d)\n",
+ *(u32 *)channel->tx_data, channel->tx_count);
+ ret = hsi_write(channel->dev, channel->tx_data, channel->tx_count / 4);
+ if (ret) {
+ mif_err("[MIPI-HSI] ch=%d, hsi_write fail : %d\n",
+ channel->channel_id, ret);
+
+ spin_lock_irqsave(&channel->tx_state_lock, flags);
+ channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING;
+ spin_unlock_irqrestore(&channel->tx_state_lock, flags);
+
+ return ret;
+ }
+
+ if (down_timeout(&channel->write_done_sem,
+ HSI_WRITE_DONE_TIMEOUT) < 0) {
+ mif_err("[MIPI-HSI] ch=%d, hsi_write_done timeout : %d\n",
+ channel->channel_id, size);
+
+ mif_err("[MIPI-HSI] data : %08x %08x %08x %08x %08x ...\n",
+ *channel->tx_data, *(channel->tx_data + 1),
+ *(channel->tx_data + 2), *(channel->tx_data + 3),
+ *(channel->tx_data + 4));
+
+ hsi_write_cancel(channel->dev);
+
+ spin_lock_irqsave(&channel->tx_state_lock, flags);
+ channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING;
+ spin_unlock_irqrestore(&channel->tx_state_lock, flags);
+
+ return -ETIMEDOUT;
+ }
+
+ if (channel->tx_count != size)
+ mif_err("[MIPI-HSI] ch:%d,write_done fail,write_size:%d,origin_size:%d\n",
+ channel->channel_id, channel->tx_count, size);
+
+ mif_debug("[MIPI-HSI] len:%d, id:%d, data : %08x %08x %08x %08x %08x ...\n",
+ channel->tx_count, channel->channel_id, *channel->tx_data,
+ *(channel->tx_data + 1), *(channel->tx_data + 2),
+ *(channel->tx_data + 3), *(channel->tx_data + 4));
+
+ return channel->tx_count;
+}
+
+static void if_hsi_write_done(struct hsi_device *dev, unsigned int size)
+{
+ unsigned long int flags;
+ struct mipi_link_device *mipi_ld =
+ (struct mipi_link_device *)if_hsi_driver.priv_data;
+ struct if_hsi_channel *channel = &mipi_ld->hsi_channles[dev->n_ch];
+
+ mif_debug("[MIPI-HSI] got write data : 0x%x(%d)\n",
+ *(u32 *)channel->tx_data, size);
+
+ spin_lock_irqsave(&channel->tx_state_lock, flags);
+ channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING;
+ spin_unlock_irqrestore(&channel->tx_state_lock, flags);
+
+ mif_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n",
+ *channel->tx_data, *(channel->tx_data + 1),
+ *(channel->tx_data + 2), *(channel->tx_data + 3),
+ *(channel->tx_data + 4), *(channel->tx_data + 5),
+ *(channel->tx_data + 6), *(channel->tx_data + 7));
+
+ channel->tx_count = 4 * size;
+ up(&channel->write_done_sem);
+}
+
+static void if_hsi_read_done(struct hsi_device *dev, unsigned int size)
+{
+ int ret;
+ unsigned long int flags;
+ u32 cmd = 0, ch = 0, param = 0;
+ struct mipi_link_device *mipi_ld =
+ (struct mipi_link_device *)if_hsi_driver.priv_data;
+ struct if_hsi_channel *channel = &mipi_ld->hsi_channles[dev->n_ch];
+ struct io_device *iod;
+ enum dev_format format_type = 0;
+
+ mif_debug("[MIPI-HSI] got read data : 0x%x(%d)\n",
+ *(u32 *)channel->rx_data, size);
+
+ spin_lock_irqsave(&channel->rx_state_lock, flags);
+ channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING;
+ spin_unlock_irqrestore(&channel->rx_state_lock, flags);
+
+ channel->rx_count = 4 * size;
+
+ switch (channel->channel_id) {
+ case HSI_CONTROL_CHANNEL:
+ switch (mipi_ld->ld.com_state) {
+ case COM_HANDSHAKE:
+ case COM_ONLINE:
+ mif_debug("[MIPI-HSI] RECV CMD : %08x\n",
+ *channel->rx_data);
+
+ if (channel->rx_count != 4) {
+ mif_err("[MIPI-HSI] wrong command len : %d\n",
+ channel->rx_count);
+ return;
+ }
+
+ ret = if_hsi_decode_cmd(channel->rx_data, &cmd, &ch,
+ &param);
+ if (ret)
+ mif_err("[MIPI-HSI] decode_cmd fail=%d, "
+ "cmd=%x\n", ret, cmd);
+ else {
+ mif_debug("[MIPI-HSI] decode_cmd : %08x\n",
+ cmd);
+ ret = if_hsi_rx_cmd_handle(mipi_ld, cmd, ch,
+ param);
+ if (ret)
+ mif_err("[MIPI-HSI] handle cmd "
+ "cmd=%x\n", cmd);
+ }
+
+ ret = hsi_read(channel->dev, channel->rx_data, 1);
+ if (ret)
+ mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret);
+
+ return;
+
+ case COM_BOOT:
+ mif_debug("[MIPI-HSI] receive data : 0x%x(%d)\n",
+ *channel->rx_data, channel->rx_count);
+
+ iod = link_get_iod_with_format(&mipi_ld->ld, IPC_BOOT);
+ if (iod) {
+ channel->packet_size = *channel->rx_data;
+ mif_debug("[MIPI-HSI] flashless packet size : "
+ "%d\n", channel->packet_size);
+
+ ret = iod->recv(iod,
+ &mipi_ld->ld,
+ (char *)channel->rx_data + 4,
+ HSI_FLASHBOOT_ACK_LEN - 4);
+ if (ret < 0)
+ mif_err("[MIPI-HSI] recv call "
+ "fail : %d\n", ret);
+ }
+
+ ret = hsi_read(channel->dev, channel->rx_data,
+ HSI_FLASHBOOT_ACK_LEN / 4);
+ if (ret)
+ mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret);
+ return;
+
+ case COM_CRASH:
+ mif_debug("[MIPI-HSI] receive data : 0x%x(%d)\n",
+ *channel->rx_data, channel->rx_count);
+
+ iod = link_get_iod_with_format(&mipi_ld->ld,
+ IPC_RAMDUMP);
+ if (iod) {
+ channel->packet_size = *channel->rx_data;
+ mif_debug("[MIPI-HSI] ramdump packet size : "
+ "%d\n", channel->packet_size);
+
+ ret = iod->recv(iod,
+ &mipi_ld->ld,
+ (char *)channel->rx_data + 4,
+ channel->packet_size);
+ if (ret < 0)
+ mif_err("[MIPI-HSI] recv call "
+ "fail : %d\n", ret);
+ }
+
+ ret = hsi_read(channel->dev, channel->rx_data,
+ DUMP_PACKET_SIZE);
+ if (ret)
+ mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret);
+ return;
+
+ case COM_NONE:
+ default:
+ mif_err("[MIPI-HSI] receive data in wrong state : 0x%x(%d)\n",
+ *channel->rx_data, channel->rx_count);
+ return;
+ }
+ break;
+
+ case HSI_FMT_CHANNEL:
+ mif_debug("[MIPI-HSI] iodevice format : IPC_FMT\n");
+ format_type = IPC_FMT;
+ break;
+ case HSI_RAW_CHANNEL:
+ mif_debug("[MIPI-HSI] iodevice format : IPC_MULTI_RAW\n");
+ format_type = IPC_MULTI_RAW;
+ break;
+ case HSI_RFS_CHANNEL:
+ mif_debug("[MIPI-HSI] iodevice format : IPC_RFS\n");
+ format_type = IPC_RFS;
+ break;
+
+ case HSI_CMD_CHANNEL:
+ mif_debug("[MIPI-HSI] receive command data : 0x%x\n",
+ *channel->rx_data);
+
+ ch = channel->channel_id;
+ param = 0;
+ ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_CONN_CLOSED,
+ ch, param);
+ if (ret)
+ mif_err("[MIPI-HSI] send_cmd fail=%d\n", ret);
+
+ channel->recv_step = STEP_IDLE;
+ return;
+
+ default:
+ return;
+ }
+
+ iod = link_get_iod_with_format(&mipi_ld->ld, format_type);
+ if (iod) {
+ mif_debug("[MIPI-HSI] iodevice format : %d\n", iod->format);
+
+ channel->recv_step = STEP_NOT_READY;
+
+ mif_debug("[MIPI-HSI] RECV DATA : %08x(%d)-%d\n",
+ *channel->rx_data, channel->packet_size,
+ iod->format);
+
+ mif_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n",
+ *channel->rx_data, *(channel->rx_data + 1),
+ *(channel->rx_data + 2), *(channel->rx_data + 3),
+ *(channel->rx_data + 4), *(channel->rx_data + 5),
+ *(channel->rx_data + 6), *(channel->rx_data + 7));
+
+ ret = iod->recv(iod, &mipi_ld->ld,
+ (char *)channel->rx_data, channel->packet_size);
+ if (ret < 0)
+ mif_err("[MIPI-HSI] recv call fail : %d\n", ret);
+
+ ch = channel->channel_id;
+ param = 0;
+ ret = if_hsi_send_command(mipi_ld,
+ HSI_LL_MSG_CONN_CLOSED, ch, param);
+ if (ret)
+ mif_err("[MIPI-HSI] send_cmd fail=%d\n", ret);
+
+ channel->recv_step = STEP_IDLE;
+ }
+}
+
+static void if_hsi_port_event(struct hsi_device *dev, unsigned int event,
+ void *arg)
+{
+ int acwake_level = 1;
+ struct mipi_link_device *mipi_ld =
+ (struct mipi_link_device *)if_hsi_driver.priv_data;
+
+ switch (event) {
+ case HSI_EVENT_BREAK_DETECTED:
+ mif_err("[MIPI-HSI] HSI_EVENT_BREAK_DETECTED\n");
+ return;
+
+ case HSI_EVENT_HSR_DATAAVAILABLE:
+ mif_err("[MIPI-HSI] HSI_EVENT_HSR_DATAAVAILABLE\n");
+ return;
+
+ case HSI_EVENT_CAWAKE_UP:
+ if (dev->n_ch == HSI_CONTROL_CHANNEL) {
+ if (!wake_lock_active(&mipi_ld->wlock)) {
+ wake_lock(&mipi_ld->wlock);
+ mif_debug("[MIPI-HSI] wake_lock\n");
+ }
+ mif_debug("[MIPI-HSI] CAWAKE_%d(1)\n", dev->n_ch);
+ }
+ return;
+
+ case HSI_EVENT_CAWAKE_DOWN:
+ if (dev->n_ch == HSI_CONTROL_CHANNEL)
+ mif_debug("[MIPI-HSI] CAWAKE_%d(0)\n", dev->n_ch);
+
+ if ((dev->n_ch == HSI_CONTROL_CHANNEL) &&
+ mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].opened) {
+ hsi_ioctl(
+ mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev,
+ HSI_IOCTL_GET_ACWAKE, &acwake_level);
+
+ mif_debug("[MIPI-HSI] GET_ACWAKE. Ch : %d, level : %d\n",
+ dev->n_ch, acwake_level);
+
+ if (!acwake_level) {
+ wake_unlock(&mipi_ld->wlock);
+ mif_debug("[MIPI-HSI] wake_unlock\n");
+ }
+ }
+ return;
+
+ case HSI_EVENT_ERROR:
+ mif_err("[MIPI-HSI] HSI_EVENT_ERROR\n");
+ return;
+
+ default:
+ mif_err("[MIPI-HSI] Unknown Event : %d\n", event);
+ return;
+ }
+}
+
+static int __devinit if_hsi_probe(struct hsi_device *dev)
+{
+ int port = 0;
+ unsigned long *address;
+ struct mipi_link_device *mipi_ld =
+ (struct mipi_link_device *)if_hsi_driver.priv_data;
+
+ for (port = 0; port < HSI_MAX_PORTS; port++) {
+ if (if_hsi_driver.ch_mask[port])
+ break;
+ }
+ address = (unsigned long *)&if_hsi_driver.ch_mask[port];
+
+ if (test_bit(dev->n_ch, address) && (dev->n_p == port)) {
+ /* Register callback func */
+ hsi_set_write_cb(dev, if_hsi_write_done);
+ hsi_set_read_cb(dev, if_hsi_read_done);
+ hsi_set_port_event_cb(dev, if_hsi_port_event);
+
+ /* Init device data */
+ mipi_ld->hsi_channles[dev->n_ch].dev = dev;
+ mipi_ld->hsi_channles[dev->n_ch].tx_count = 0;
+ mipi_ld->hsi_channles[dev->n_ch].rx_count = 0;
+ mipi_ld->hsi_channles[dev->n_ch].tx_state = 0;
+ mipi_ld->hsi_channles[dev->n_ch].rx_state = 0;
+ mipi_ld->hsi_channles[dev->n_ch].packet_size = 0;
+ mipi_ld->hsi_channles[dev->n_ch].acwake = 0;
+ mipi_ld->hsi_channles[dev->n_ch].send_step = STEP_UNDEF;
+ mipi_ld->hsi_channles[dev->n_ch].recv_step = STEP_UNDEF;
+ spin_lock_init(&mipi_ld->hsi_channles[dev->n_ch].tx_state_lock);
+ spin_lock_init(&mipi_ld->hsi_channles[dev->n_ch].rx_state_lock);
+ spin_lock_init(&mipi_ld->hsi_channles[dev->n_ch].acwake_lock);
+ sema_init(&mipi_ld->hsi_channles[dev->n_ch].write_done_sem,
+ 0);
+ sema_init(&mipi_ld->hsi_channles[dev->n_ch].ack_done_sem,
+ 0);
+ sema_init(&mipi_ld->hsi_channles[dev->n_ch].close_conn_done_sem,
+ 0);
+ }
+
+ mif_debug("[MIPI-HSI] if_hsi_probe() done. ch : %d\n", dev->n_ch);
+ return 0;
+}
+
+static int if_hsi_init(struct link_device *ld)
+{
+ int ret;
+ int i = 0;
+ struct mipi_link_device *mipi_ld = to_mipi_link_device(ld);
+
+ for (i = 0; i < HSI_MAX_PORTS; i++)
+ if_hsi_driver.ch_mask[i] = 0;
+
+ for (i = 0; i < HSI_MAX_CHANNELS; i++) {
+ mipi_ld->hsi_channles[i].dev = NULL;
+ mipi_ld->hsi_channles[i].opened = 0;
+ mipi_ld->hsi_channles[i].channel_id = i;
+ }
+ if_hsi_driver.ch_mask[0] = CHANNEL_MASK;
+
+ /* TODO - need to get priv data (request to TI) */
+ if_hsi_driver.priv_data = (void *)mipi_ld;
+ ret = hsi_register_driver(&if_hsi_driver);
+ if (ret) {
+ mif_err("[MIPI-HSI] hsi_register_driver() fail : %d\n", ret);
+ return ret;
+ }
+
+ mipi_ld->mipi_wq = create_singlethread_workqueue("mipi_cmd_wq");
+ if (!mipi_ld->mipi_wq) {
+ mif_err("[MIPI-HSI] fail to create work Q.\n");
+ return -ENOMEM;
+ }
+ INIT_WORK(&mipi_ld->cmd_work, if_hsi_cmd_work);
+ INIT_DELAYED_WORK(&mipi_ld->start_work, mipi_hsi_start_work);
+
+ setup_timer(&mipi_ld->hsi_acwake_down_timer, if_hsi_acwake_down_func,
+ (unsigned long)mipi_ld);
+
+ /* TODO - allocate rx buff */
+ mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data =
+ kmalloc(64 * 1024, GFP_DMA | GFP_ATOMIC);
+ if (!mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data) {
+ mif_err("[MIPI-HSI] alloc HSI_CONTROL_CHANNEL rx_data fail\n");
+ return -ENOMEM;
+ }
+ mipi_ld->hsi_channles[HSI_FMT_CHANNEL].rx_data =
+ kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC);
+ if (!mipi_ld->hsi_channles[HSI_FMT_CHANNEL].rx_data) {
+ mif_err("[MIPI-HSI] alloc HSI_FMT_CHANNEL rx_data fail\n");
+ return -ENOMEM;
+ }
+ mipi_ld->hsi_channles[HSI_RAW_CHANNEL].rx_data =
+ kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC);
+ if (!mipi_ld->hsi_channles[HSI_RAW_CHANNEL].rx_data) {
+ mif_err("[MIPI-HSI] alloc HSI_RAW_CHANNEL rx_data fail\n");
+ return -ENOMEM;
+ }
+ mipi_ld->hsi_channles[HSI_RFS_CHANNEL].rx_data =
+ kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC);
+ if (!mipi_ld->hsi_channles[HSI_RFS_CHANNEL].rx_data) {
+ mif_err("[MIPI-HSI] alloc HSI_RFS_CHANNEL rx_data fail\n");
+ return -ENOMEM;
+ }
+ mipi_ld->hsi_channles[HSI_CMD_CHANNEL].rx_data =
+ kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC);
+ if (!mipi_ld->hsi_channles[HSI_CMD_CHANNEL].rx_data) {
+ mif_err("[MIPI-HSI] alloc HSI_CMD_CHANNEL rx_data fail\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+struct link_device *mipi_create_link_device(struct platform_device *pdev)
+{
+ int ret;
+ struct mipi_link_device *mipi_ld;
+ struct link_device *ld;
+
+ /* for dpram int */
+ /* struct modem_data *pdata = pdev->dev.platform_data; */
+
+ mipi_ld = kzalloc(sizeof(struct mipi_link_device), GFP_KERNEL);
+ if (!mipi_ld)
+ return NULL;
+
+ INIT_LIST_HEAD(&mipi_ld->list_of_hsi_cmd);
+ spin_lock_init(&mipi_ld->list_cmd_lock);
+ skb_queue_head_init(&mipi_ld->ld.sk_fmt_tx_q);
+ skb_queue_head_init(&mipi_ld->ld.sk_raw_tx_q);
+
+ wake_lock_init(&mipi_ld->wlock, WAKE_LOCK_SUSPEND, "mipi_link");
+
+ ld = &mipi_ld->ld;
+
+ ld->name = "mipi_hsi";
+ ld->init_comm = mipi_hsi_init_communication;
+ ld->terminate_comm = mipi_hsi_terminate_communication;
+ ld->send = mipi_hsi_send;
+ ld->com_state = COM_NONE;
+
+ /* for dpram int */
+ /* ld->irq = gpio_to_irq(pdata->gpio); s*/
+
+ ld->tx_wq = create_singlethread_workqueue("mipi_tx_wq");
+ if (!ld->tx_wq) {
+ mif_err("[MIPI-HSI] fail to create work Q.\n");
+ return NULL;
+ }
+ INIT_WORK(&ld->tx_work, mipi_hsi_tx_work);
+
+ ret = if_hsi_init(ld);
+ if (ret)
+ return NULL;
+
+ return ld;
+}
diff --git a/drivers/misc/modem_if/modem_link_device_mipi.h b/drivers/misc/modem_if/modem_link_device_mipi.h
new file mode 100644
index 00000000000..8ca4968b870
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_mipi.h
@@ -0,0 +1,158 @@
+/*
+ * 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_MIPI_H__
+#define __MODEM_LINK_DEVICE_MIPI_H__
+
+
+#define HSI_MAX_CHANNELS 16
+#define CHANNEL_MASK 0xFF
+
+#define HSI_CHANNEL_TX_STATE_UNAVAIL (1 << 0)
+#define HSI_CHANNEL_TX_STATE_WRITING (1 << 1)
+#define HSI_CHANNEL_RX_STATE_UNAVAIL (1 << 0)
+#define HSI_CHANNEL_RX_STATE_READING (1 << 1)
+
+#define HSI_WRITE_DONE_TIMEOUT (HZ)
+#define HSI_READ_DONE_TIMEOUT (HZ)
+#define HSI_ACK_DONE_TIMEOUT (HZ)
+#define HSI_CLOSE_CONN_DONE_TIMEOUT (HZ)
+#define HSI_ACWAKE_DOWN_TIMEOUT (HZ / 2)
+
+#define HSI_CONTROL_CHANNEL 0
+#define HSI_FLASHLESS_CHANNEL 0
+#define HSI_CP_RAMDUMP_CHANNEL 0
+#define HSI_FMT_CHANNEL 1
+#define HSI_RAW_CHANNEL 2
+#define HSI_RFS_CHANNEL 3
+#define HSI_CMD_CHANNEL 4
+#define HSI_NUM_OF_USE_CHANNELS 5
+
+#define HSI_LL_INVALID_CHANNEL 0xFF
+
+#define HSI_FLASHBOOT_ACK_LEN 16
+#define DUMP_PACKET_SIZE 12289 /* 48K + 4 length, word unit */
+#define DUMP_ERR_INFO_SIZE 39 /* 150 bytes + 4 length , word unit */
+
+enum {
+ HSI_LL_MSG_BREAK, /* 0x0 */
+ HSI_LL_MSG_ECHO,
+ HSI_LL_MSG_INFO_REQ,
+ HSI_LL_MSG_INFO,
+ HSI_LL_MSG_CONFIGURE,
+ HSI_LL_MSG_ALLOCATE_CH,
+ HSI_LL_MSG_RELEASE_CH,
+ HSI_LL_MSG_OPEN_CONN,
+ HSI_LL_MSG_CONN_READY,
+ HSI_LL_MSG_CONN_CLOSED, /* 0x9 */
+ HSI_LL_MSG_CANCEL_CONN,
+ HSI_LL_MSG_ACK, /* 0xB */
+ HSI_LL_MSG_NAK, /* 0xC */
+ HSI_LL_MSG_CONF_RATE,
+ HSI_LL_MSG_OPEN_CONN_OCTET, /* 0xE */
+ HSI_LL_MSG_INVALID = 0xFF,
+};
+
+enum {
+ STEP_UNDEF,
+ STEP_CLOSED,
+ STEP_NOT_READY,
+ STEP_IDLE,
+ STEP_ERROR,
+ STEP_SEND_OPEN_CONN,
+ STEP_SEND_ACK,
+ STEP_WAIT_FOR_ACK,
+ STEP_TO_ACK,
+ STEP_SEND_NACK,
+ STEP_GET_NACK,
+ STEP_SEND_CONN_READY,
+ STEP_WAIT_FOR_CONN_READY,
+ STEP_SEND_CONF_RATE,
+ STEP_WAIT_FOR_CONF_ACK,
+ STEP_TX,
+ STEP_RX,
+ STEP_SEND_CONN_CLOSED,
+ STEP_WAIT_FOR_CONN_CLOSED,
+ STEP_SEND_BREAK,
+};
+
+
+struct if_hsi_channel {
+ struct hsi_device *dev;
+ unsigned int channel_id;
+
+ u32 *tx_data;
+ unsigned int tx_count;
+ u32 *rx_data;
+ unsigned int rx_count;
+ unsigned int packet_size;
+
+ unsigned int tx_state;
+ unsigned int rx_state;
+ spinlock_t tx_state_lock;
+ spinlock_t rx_state_lock;
+
+ unsigned int send_step;
+ unsigned int recv_step;
+
+ unsigned int got_nack;
+ unsigned int acwake;
+ spinlock_t acwake_lock;
+
+ struct semaphore write_done_sem;
+ struct semaphore ack_done_sem;
+ struct semaphore close_conn_done_sem;
+
+ unsigned int opened;
+};
+
+struct if_hsi_command {
+ u32 command;
+ struct list_head list;
+};
+
+struct mipi_link_device {
+ struct link_device ld;
+
+ /* mipi specific link data */
+ struct if_hsi_channel hsi_channles[HSI_MAX_CHANNELS];
+ struct list_head list_of_hsi_cmd;
+ spinlock_t list_cmd_lock;
+
+ struct workqueue_struct *mipi_wq;
+ struct work_struct cmd_work;
+ struct delayed_work start_work;
+
+ struct wake_lock wlock;
+ struct timer_list hsi_acwake_down_timer;
+};
+/* converts from struct link_device* to struct xxx_link_device* */
+#define to_mipi_link_device(linkdev) \
+ container_of(linkdev, struct mipi_link_device, ld)
+
+
+enum {
+ HSI_INIT_MODE_NORMAL,
+ HSI_INIT_MODE_FLASHLESS_BOOT,
+ HSI_INIT_MODE_CP_RAMDUMP,
+};
+static int hsi_init_handshake(struct mipi_link_device *mipi_ld, int mode);
+static int if_hsi_write(struct if_hsi_channel *channel, u32 *data,
+ unsigned int size);
+static int if_hsi_protocol_send(struct mipi_link_device *mipi_ld, int ch,
+ u32 *data, unsigned int len);
+static int if_hsi_close_channel(struct if_hsi_channel *channel);
+
+#endif
diff --git a/drivers/misc/modem_if/modem_link_device_pld.c b/drivers/misc/modem_if/modem_link_device_pld.c
new file mode 100644
index 00000000000..23d4627b78a
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_pld.c
@@ -0,0 +1,1618 @@
+/*
+ * 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/time.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/kallsyms.h>
+#include <linux/platform_data/modem.h>
+
+#include "modem_prj.h"
+#include "modem_link_device_pld.h"
+#include "modem_utils.h"
+
+
+/*
+** Function prototypes for basic DPRAM operations
+*/
+static inline void clear_intr(struct pld_link_device *pld);
+static inline u16 recv_intr(struct pld_link_device *pld);
+static inline void send_intr(struct pld_link_device *pld, u16 mask);
+
+static inline u16 get_magic(struct pld_link_device *pld);
+static inline void set_magic(struct pld_link_device *pld, u16 val);
+static inline u16 get_access(struct pld_link_device *pld);
+static inline void set_access(struct pld_link_device *pld, u16 val);
+
+static inline u32 get_tx_head(struct pld_link_device *pld, int id);
+static inline u32 get_tx_tail(struct pld_link_device *pld, int id);
+static inline void set_tx_head(struct pld_link_device *pld, int id, u32 in);
+static inline void set_tx_tail(struct pld_link_device *pld, int id, u32 out);
+static inline u8 *get_tx_buff(struct pld_link_device *pld, int id);
+static inline u32 get_tx_buff_size(struct pld_link_device *pld, int id);
+
+static inline u32 get_rx_head(struct pld_link_device *pld, int id);
+static inline u32 get_rx_tail(struct pld_link_device *pld, int id);
+static inline void set_rx_head(struct pld_link_device *pld, int id, u32 in);
+static inline void set_rx_tail(struct pld_link_device *pld, int id, u32 out);
+static inline u8 *get_rx_buff(struct pld_link_device *pld, int id);
+static inline u32 get_rx_buff_size(struct pld_link_device *pld, int id);
+
+static inline u16 get_mask_req_ack(struct pld_link_device *pld, int id);
+static inline u16 get_mask_res_ack(struct pld_link_device *pld, int id);
+static inline u16 get_mask_send(struct pld_link_device *pld, int id);
+
+static void handle_cp_crash(struct pld_link_device *pld);
+static int trigger_force_cp_crash(struct pld_link_device *pld);
+
+/*
+** Functions for debugging
+*/
+static void set_dpram_map(struct pld_link_device *pld,
+ struct mif_irq_map *map)
+{
+ map->magic = get_magic(pld);
+ map->access = get_access(pld);
+
+ map->fmt_tx_in = get_tx_head(pld, IPC_FMT);
+ map->fmt_tx_out = get_tx_tail(pld, IPC_FMT);
+ map->fmt_rx_in = get_rx_head(pld, IPC_FMT);
+ map->fmt_rx_out = get_rx_tail(pld, IPC_FMT);
+ map->raw_tx_in = get_tx_head(pld, IPC_RAW);
+ map->raw_tx_out = get_tx_tail(pld, IPC_RAW);
+ map->raw_rx_in = get_rx_head(pld, IPC_RAW);
+ map->raw_rx_out = get_rx_tail(pld, IPC_RAW);
+
+ map->cp2ap = recv_intr(pld);
+}
+
+/*
+** DPRAM operations
+*/
+static int pld_register_isr(unsigned irq, irqreturn_t (*isr)(int, void*),
+ unsigned long flag, const char *name,
+ struct pld_link_device *pld)
+{
+ int ret = 0;
+
+ ret = request_irq(irq, isr, flag, name, pld);
+ if (ret) {
+ mif_info("%s: ERR! request_irq fail (err %d)\n", name, ret);
+ return ret;
+ }
+
+ ret = enable_irq_wake(irq);
+ if (ret)
+ mif_info("%s: ERR! enable_irq_wake fail (err %d)\n", name, ret);
+
+ mif_info("%s (#%d) handler registered\n", name, irq);
+
+ return 0;
+}
+
+static inline void clear_intr(struct pld_link_device *pld)
+{
+ if (pld->ext_op && pld->ext_op->clear_intr)
+ pld->ext_op->clear_intr(pld);
+}
+
+static inline u16 recv_intr(struct pld_link_device *pld)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&pld->mbx2ap[0]),
+ pld->address_buffer);
+ val1 = ioread16(pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&pld->mbx2ap[0]),
+ pld->address_buffer);
+ val2 = ioread16(pld->base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: intr1(%d) != intr1(%d)\n", val1, val2);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+
+ return val1;
+}
+
+static inline void send_intr(struct pld_link_device *pld, u16 mask)
+{
+ int cnt = 3;
+ u32 val = 0;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ iowrite16(PLD_ADDR_MASK(&pld->mbx2cp[0]),
+ pld->address_buffer);
+ iowrite16((u16)mask, pld->base);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&pld->mbx2cp[0]),
+ pld->address_buffer);
+ val = ioread16(pld->base);
+
+ if (likely(val == mask)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return;
+ }
+
+ mif_err("ERR: intr1(%d) != intr2(%d)\n", val, mask);
+ udelay(100);
+
+ /* Write head value again */
+ iowrite16(PLD_ADDR_MASK(&pld->mbx2cp[0]),
+ pld->address_buffer);
+ iowrite16((u16)mask, pld->base);
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+
+ return;
+}
+
+static inline u16 get_magic(struct pld_link_device *pld)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&pld->magic_ap2cp[0]),
+ pld->address_buffer);
+ val1 = ioread16(pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&pld->magic_ap2cp[0]),
+ pld->address_buffer);
+ val2 = ioread16(pld->base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: txq.head(%d) != in(%d)\n", val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+
+}
+
+static inline void set_magic(struct pld_link_device *pld, u16 in)
+{
+ int cnt = 3;
+ u32 val = 0;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ iowrite16(PLD_ADDR_MASK(&pld->magic_ap2cp[0]),
+ pld->address_buffer);
+ iowrite16((u16)in, pld->base);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&pld->magic_ap2cp[0]),
+ pld->address_buffer);
+ val = ioread16(pld->base);
+
+ if (likely(val == in)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return;
+ }
+
+ mif_err("ERR: magic1(%d) != magic2(%d)\n", val, in);
+ udelay(100);
+
+ /* Write head value again */
+ iowrite16(PLD_ADDR_MASK(&pld->magic_ap2cp[0]),
+ pld->address_buffer);
+ iowrite16((u16)in, pld->base);
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return;
+}
+
+static inline u16 get_access(struct pld_link_device *pld)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&pld->access_ap2cp[0]),
+ pld->address_buffer);
+ val1 = ioread16(pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&pld->access_ap2cp[0]),
+ pld->address_buffer);
+ val2 = ioread16(pld->base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: access1(%d) != access2(%d)\n", val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+
+}
+
+static inline void set_access(struct pld_link_device *pld, u16 in)
+{
+ int cnt = 3;
+ u32 val = 0;
+ unsigned long int flags;
+
+ iowrite16(PLD_ADDR_MASK(&pld->access_ap2cp[0]),
+ pld->address_buffer);
+ iowrite16((u16)in, pld->base);
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&pld->access_ap2cp[0]),
+ pld->address_buffer);
+ val = ioread16(pld->base);
+
+ if (likely(val == in)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return;
+ }
+
+ mif_err("ERR: access(%d) != access(%d)\n", val, in);
+ udelay(100);
+
+ /* Write head value again */
+ iowrite16(PLD_ADDR_MASK(&pld->access_ap2cp[0]),
+ pld->address_buffer);
+ iowrite16((u16)in, pld->base);
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return;
+}
+
+static inline u32 get_tx_head(struct pld_link_device *pld, int id)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.head)[0]),
+ pld->address_buffer);
+ val1 = ioread16(pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.head)[0]),
+ pld->address_buffer);
+ val2 = ioread16(pld->base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: %s txq.head(%d) != in(%d)\n",
+ get_dev_name(id), val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+}
+
+static inline u32 get_tx_tail(struct pld_link_device *pld, int id)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.tail)[0]),
+ pld->address_buffer);
+ val1 = ioread16(pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.tail)[0]),
+ pld->address_buffer);
+ val2 = ioread16(pld->base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: %s txq.tail(%d) != in(%d)\n",
+ get_dev_name(id), val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+}
+
+static inline void set_tx_head(struct pld_link_device *pld, int id, u32 in)
+{
+ int cnt = 3;
+ u32 val = 0;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.head)[0]),
+ pld->address_buffer);
+ iowrite16((u16)in, pld->base);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.head)[0]),
+ pld->address_buffer);
+ val = ioread16(pld->base);
+
+ if (likely(val == in)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return;
+ }
+
+ mif_err("ERR: %s txq.head(%d) != in(%d)\n",
+ get_dev_name(id), val, in);
+ udelay(100);
+
+ /* Write head value again */
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.head)[0]),
+ pld->address_buffer);
+ iowrite16((u16)in, pld->base);
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return;
+}
+
+static inline void set_tx_tail(struct pld_link_device *pld, int id, u32 out)
+{
+ return;
+}
+
+static inline u8 *get_tx_buff(struct pld_link_device *pld, int id)
+{
+ return pld->dev[id]->txq.buff;
+}
+
+static inline u32 get_tx_buff_size(struct pld_link_device *pld, int id)
+{
+ return pld->dev[id]->txq.size;
+}
+
+static inline u32 get_rx_head(struct pld_link_device *pld, int id)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.head)[0]),
+ pld->address_buffer);
+ val1 = ioread16(pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.head)[0]),
+ pld->address_buffer);
+ val2 = ioread16(pld->base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: %s rxq.head(%d) != in(%d)\n",
+ get_dev_name(id), val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+}
+
+static inline u32 get_rx_tail(struct pld_link_device *pld, int id)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.tail)[0]),
+ pld->address_buffer);
+ val1 = ioread16(pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.tail)[0]),
+ pld->address_buffer);
+ val2 = ioread16(pld->base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: %s rxq.tail(%d) != in(%d)\n",
+ get_dev_name(id), val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return val1;
+}
+
+static inline void set_rx_head(struct pld_link_device *pld, int id, u32 in)
+{
+ return;
+}
+
+static inline void set_rx_tail(struct pld_link_device *pld, int id, u32 out)
+{
+ int cnt = 3;
+ u32 val = 0;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->pld_lock, flags);
+
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.tail)[0]),
+ pld->address_buffer);
+ iowrite16((u16)out, pld->base);
+
+ do {
+ /* Check tail value written */
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.tail)[0]),
+ pld->address_buffer);
+ val = ioread16(pld->base);
+
+ if (val == out) {
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return;
+ }
+
+ mif_err("ERR: %s rxq.tail(%d) != out(%d)\n",
+ get_dev_name(id), val, out);
+ udelay(100);
+
+ /* Write tail value again */
+ iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.tail)[0]),
+ pld->address_buffer);
+ iowrite16((u16)out, pld->base);
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&pld->pld_lock, flags);
+ return;
+}
+
+static inline u8 *get_rx_buff(struct pld_link_device *pld, int id)
+{
+ return pld->dev[id]->rxq.buff;
+}
+
+static inline u32 get_rx_buff_size(struct pld_link_device *pld, int id)
+{
+ return pld->dev[id]->rxq.size;
+}
+
+static inline u16 get_mask_req_ack(struct pld_link_device *pld, int id)
+{
+ return pld->dev[id]->mask_req_ack;
+}
+
+static inline u16 get_mask_res_ack(struct pld_link_device *pld, int id)
+{
+ return pld->dev[id]->mask_res_ack;
+}
+
+static inline u16 get_mask_send(struct pld_link_device *pld, int id)
+{
+ return pld->dev[id]->mask_send;
+}
+
+/* Get free space in the TXQ as well as in & out pointers */
+static inline int get_txq_space(struct pld_link_device *pld, int dev, u32 qsize,
+ u32 *in, u32 *out)
+{
+ struct link_device *ld = &pld->ld;
+ int cnt = 3;
+ u32 head;
+ u32 tail;
+ int space;
+
+ do {
+ head = get_tx_head(pld, dev);
+ tail = get_tx_tail(pld, dev);
+
+ space = (head < tail) ? (tail - head - 1) :
+ (qsize + tail - head - 1);
+ mif_debug("%s: %s_TXQ qsize[%u] in[%u] out[%u] space[%u]\n",
+ ld->name, get_dev_name(dev), qsize, head, tail, space);
+
+ if (circ_valid(qsize, head, tail)) {
+ *in = head;
+ *out = tail;
+ return space;
+ }
+
+ mif_info("%s: CAUTION! <%pf> "
+ "%s_TXQ invalid (size:%d in:%d out:%d)\n",
+ ld->name, __builtin_return_address(0),
+ get_dev_name(dev), qsize, head, tail);
+
+ udelay(100);
+ } while (cnt--);
+
+ *in = 0;
+ *out = 0;
+ return -EINVAL;
+}
+
+static void reset_tx_circ(struct pld_link_device *pld, int dev)
+{
+ set_tx_head(pld, dev, 0);
+ set_tx_tail(pld, dev, 0);
+ if (dev == IPC_FMT)
+ trigger_force_cp_crash(pld);
+}
+
+/* Get data size in the RXQ as well as in & out pointers */
+static inline int get_rxq_rcvd(struct pld_link_device *pld, int dev, u32 qsize,
+ u32 *in, u32 *out)
+{
+ struct link_device *ld = &pld->ld;
+ int cnt = 3;
+ u32 head;
+ u32 tail;
+ u32 rcvd;
+
+ do {
+ head = get_rx_head(pld, dev);
+ tail = get_rx_tail(pld, dev);
+ if (head == tail) {
+ *in = head;
+ *out = tail;
+ return 0;
+ }
+
+ rcvd = (head > tail) ? (head - tail) : (qsize - tail + head);
+ mif_info("%s: %s_RXQ qsize[%u] in[%u] out[%u] rcvd[%u]\n",
+ ld->name, get_dev_name(dev), qsize, head, tail, rcvd);
+
+ if (circ_valid(qsize, head, tail)) {
+ *in = head;
+ *out = tail;
+ return rcvd;
+ }
+
+ mif_info("%s: CAUTION! <%pf> "
+ "%s_RXQ invalid (size:%d in:%d out:%d)\n",
+ ld->name, __builtin_return_address(0),
+ get_dev_name(dev), qsize, head, tail);
+
+ udelay(100);
+ } while (cnt--);
+
+ *in = 0;
+ *out = 0;
+ return -EINVAL;
+}
+
+static void reset_rx_circ(struct pld_link_device *pld, int dev)
+{
+ set_rx_head(pld, dev, 0);
+ set_rx_tail(pld, dev, 0);
+ if (dev == IPC_FMT)
+ trigger_force_cp_crash(pld);
+}
+
+static int check_access(struct pld_link_device *pld)
+{
+ struct link_device *ld = &pld->ld;
+ int i;
+ u16 magic = get_magic(pld);
+ u16 access = get_access(pld);
+
+ if (likely(magic == DPRAM_MAGIC_CODE && access == 1))
+ return 0;
+
+ for (i = 1; i <= 100; i++) {
+ mif_info("%s: ERR! magic:%X access:%X -> retry:%d\n",
+ ld->name, magic, access, i);
+ udelay(100);
+
+ magic = get_magic(pld);
+ access = get_access(pld);
+ if (likely(magic == DPRAM_MAGIC_CODE && access == 1))
+ return 0;
+ }
+
+ mif_info("%s: !CRISIS! magic:%X access:%X\n", ld->name, magic, access);
+ return -EACCES;
+}
+
+static bool ipc_active(struct pld_link_device *pld)
+{
+ struct link_device *ld = &pld->ld;
+
+ /* Check DPRAM mode */
+ if (ld->mode != LINK_MODE_IPC) {
+ mif_info("%s: <%pf> ld->mode != LINK_MODE_IPC\n",
+ ld->name, __builtin_return_address(0));
+ return false;
+ }
+
+ if (check_access(pld) < 0) {
+ mif_info("%s: ERR! <%pf> check_access fail\n",
+ ld->name, __builtin_return_address(0));
+ return false;
+ }
+
+ return true;
+}
+
+static void pld_ipc_write(struct pld_link_device *pld, int dev,
+ u32 qsize, u32 in, u32 out, struct sk_buff *skb)
+{
+ struct link_device *ld = &pld->ld;
+ u8 __iomem *buff = get_tx_buff(pld, dev);
+ u8 *src = skb->data;
+ u32 len = skb->len;
+ u32 inp;
+ struct mif_irq_map map;
+
+ if (in < out) {
+ /* +++++++++ in ---------- out ++++++++++ */
+ iowrite16(PLD_ADDR_MASK(&(buff+in)[0]), pld->address_buffer);
+ memcpy(pld->base, src, len);
+ } else {
+ /* ------ out +++++++++++ in ------------ */
+ u32 space = qsize - in;
+
+ /* 1) in -> buffer end */
+ iowrite16(PLD_ADDR_MASK(&(buff+in)[0]), pld->address_buffer);
+ memcpy(pld->base, src, ((len > space) ? space : len));
+
+ if (len > space) {
+ iowrite16(PLD_ADDR_MASK(&buff[0]),
+ pld->address_buffer);
+ memcpy(pld->base, (src+space), (len-space));
+ }
+ }
+
+ /* update new in pointer */
+ inp = in + len;
+ if (inp >= qsize)
+ inp -= qsize;
+ set_tx_head(pld, dev, inp);
+
+ if (dev == IPC_FMT) {
+ set_dpram_map(pld, &map);
+ mif_irq_log(ld->mc->msd, map, "ipc_write", sizeof("ipc_write"));
+ mif_ipc_log(MIF_IPC_AP2CP, ld->mc->msd, skb->data, skb->len);
+ }
+}
+
+static int pld_try_ipc_tx(struct pld_link_device *pld, int dev)
+{
+ struct link_device *ld = &pld->ld;
+ struct sk_buff_head *txq = ld->skb_txq[dev];
+ struct sk_buff *skb;
+ unsigned long int flags;
+ u32 qsize = get_tx_buff_size(pld, dev);
+ u32 in;
+ u32 out;
+ int space;
+ int copied = 0;
+
+ spin_lock_irqsave(&pld->tx_rx_lock, flags);
+
+ while (1) {
+ space = get_txq_space(pld, dev, qsize, &in, &out);
+ if (unlikely(space < 0)) {
+ spin_unlock_irqrestore(&pld->tx_rx_lock, flags);
+ reset_tx_circ(pld, dev);
+ return space;
+ }
+
+ skb = skb_dequeue(txq);
+ if (unlikely(!skb))
+ break;
+
+ if (unlikely(space < skb->len)) {
+ atomic_set(&pld->res_required[dev], 1);
+ skb_queue_head(txq, skb);
+ spin_unlock_irqrestore(&pld->tx_rx_lock, flags);
+ mif_info("%s: %s "
+ "qsize[%u] in[%u] out[%u] free[%u] < len[%u]\n",
+ ld->name, get_dev_name(dev),
+ qsize, in, out, space, skb->len);
+ return -ENOSPC;
+ }
+
+ /* TX if there is enough room in the queue */
+ pld_ipc_write(pld, dev, qsize, in, out, skb);
+ copied += skb->len;
+ dev_kfree_skb_any(skb);
+ }
+
+ spin_unlock_irqrestore(&pld->tx_rx_lock, flags);
+
+ return copied;
+}
+
+static void pld_ipc_rx_task(unsigned long data)
+{
+ struct pld_link_device *pld = (struct pld_link_device *)data;
+ struct link_device *ld = &pld->ld;
+ struct io_device *iod;
+ struct mif_rxb *rxb;
+ unsigned qlen;
+ int i;
+
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ iod = pld->iod[i];
+ qlen = rxbq_size(&pld->rxbq[i]);
+ while (qlen > 0) {
+ rxb = rxbq_get_data_rxb(&pld->rxbq[i]);
+ iod->recv(iod, ld, rxb->data, rxb->len);
+ rxb_clear(rxb);
+ qlen--;
+ }
+ }
+}
+
+static void pld_ipc_read(struct pld_link_device *pld, int dev, u8 *dst,
+ u8 __iomem *src, u32 out, u32 len, u32 qsize)
+{
+ u8 *ori_det = dst;
+
+ if ((out + len) <= qsize) {
+ /* ----- (out) (in) ----- */
+ /* ----- 7f 00 00 7e ----- */
+ iowrite16(PLD_ADDR_MASK(&(src+out)[0]), pld->address_buffer);
+ memcpy(dst, pld->base, len);
+ } else {
+ /* (in) ----------- (out) */
+ /* 00 7e ----------- 7f 00 */
+ unsigned len1 = qsize - out;
+
+ /* 1) out -> buffer end */
+ iowrite16(PLD_ADDR_MASK(&(src+out)[0]), pld->address_buffer);
+ memcpy(dst, pld->base, len1);
+
+ /* 2) buffer start -> in */
+ dst += len1;
+ iowrite16(PLD_ADDR_MASK(&src[0]), pld->address_buffer);
+ memcpy(dst, pld->base, (len - len1));
+ }
+
+ if (pld->ld.mode == LINK_MODE_IPC && ori_det[0] != 0x7F) {
+ mif_info("ipc read error!! in[%d], out[%d]\n",
+ get_rx_head(pld, dev),
+ get_rx_tail(pld, dev));
+ }
+
+}
+
+/*
+ ret < 0 : error
+ ret == 0 : no data
+ ret > 0 : valid data
+*/
+static int pld_ipc_recv_data_with_rxb(struct pld_link_device *pld, int dev)
+{
+ struct link_device *ld = &pld->ld;
+ struct mif_rxb *rxb;
+ u8 __iomem *src = get_rx_buff(pld, dev);
+ u32 qsize = get_rx_buff_size(pld, dev);
+ u32 in;
+ u32 out;
+ u32 rcvd;
+ struct mif_irq_map map;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&pld->tx_rx_lock, flags);
+
+ rcvd = get_rxq_rcvd(pld, dev, qsize, &in, &out);
+ if (rcvd <= 0) {
+ spin_unlock_irqrestore(&pld->tx_rx_lock, flags);
+ return rcvd;
+ }
+
+ if (dev == IPC_FMT) {
+ set_dpram_map(pld, &map);
+ mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv"));
+ }
+
+ /* Allocate an rxb */
+ rxb = rxbq_get_free_rxb(&pld->rxbq[dev]);
+ if (!rxb) {
+ mif_info("%s: ERR! %s rxbq_get_free_rxb fail\n",
+ ld->name, get_dev_name(dev));
+ spin_unlock_irqrestore(&pld->tx_rx_lock, flags);
+ return -ENOMEM;
+ }
+
+ /* Read data from each DPRAM buffer */
+ pld_ipc_read(pld, dev, rxb_put(rxb, rcvd), src, out, rcvd, qsize);
+
+ /* Calculate and set new out */
+ out += rcvd;
+ if (out >= qsize)
+ out -= qsize;
+ set_rx_tail(pld, dev, out);
+
+ spin_unlock_irqrestore(&pld->tx_rx_lock, flags);
+ return rcvd;
+}
+
+static void non_command_handler(struct pld_link_device *pld, u16 non_cmd)
+{
+ struct link_device *ld = &pld->ld;
+ int i = 0;
+ int ret = 0;
+ u16 mask = 0;
+
+ if (!ipc_active(pld))
+ return;
+
+ /* Read data from DPRAM */
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ ret = pld_ipc_recv_data_with_rxb(pld, i);
+ if (ret < 0)
+ reset_rx_circ(pld, i);
+
+ /* Check and process REQ_ACK (at this time, in == out) */
+ if (non_cmd & get_mask_req_ack(pld, i)) {
+ mif_debug("%s: send %s_RES_ACK\n",
+ ld->name, get_dev_name(i));
+ mask |= get_mask_res_ack(pld, i);
+ }
+ }
+
+ /* Schedule soft IRQ for RX */
+ tasklet_hi_schedule(&pld->rx_tsk);
+
+ /* Try TX via DPRAM */
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ if (atomic_read(&pld->res_required[i]) > 0) {
+ ret = pld_try_ipc_tx(pld, i);
+ if (ret > 0) {
+ atomic_set(&pld->res_required[i], 0);
+ mask |= get_mask_send(pld, i);
+ } else if (ret == -ENOSPC) {
+ mask |= get_mask_req_ack(pld, i);
+ }
+ }
+ }
+
+ if (mask) {
+ send_intr(pld, INT_NON_CMD(mask));
+ mif_debug("%s: send intr 0x%04X\n", ld->name, mask);
+ }
+}
+
+static void handle_cp_crash(struct pld_link_device *pld)
+{
+ struct link_device *ld = &pld->ld;
+ struct io_device *iod;
+ int i;
+
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ mif_info("%s: purging %s_skb_txq\b", ld->name, get_dev_name(i));
+ skb_queue_purge(ld->skb_txq[i]);
+ }
+
+ iod = link_get_iod_with_format(ld, IPC_FMT);
+ iod->modem_state_changed(iod, STATE_CRASH_EXIT);
+
+ iod = link_get_iod_with_format(ld, IPC_BOOT);
+ iod->modem_state_changed(iod, STATE_CRASH_EXIT);
+
+ iod = link_get_iod_with_channel(ld, RMNET0_CH_ID);
+ if (iod)
+ iodevs_for_each(iod->msd, iodev_netif_stop, 0);
+}
+
+static void handle_no_crash_ack(unsigned long arg)
+{
+ struct pld_link_device *pld = (struct pld_link_device *)arg;
+ struct link_device *ld = &pld->ld;
+
+ mif_err("%s: ERR! No CRASH_EXIT ACK from CP\n", ld->mc->name);
+
+ if (!wake_lock_active(&pld->wlock))
+ wake_lock(&pld->wlock);
+
+ handle_cp_crash(pld);
+}
+
+static int trigger_force_cp_crash(struct pld_link_device *pld)
+{
+ struct link_device *ld = &pld->ld;
+
+ if (ld->mode == LINK_MODE_ULOAD) {
+ mif_err("%s: CP crash is already in progress\n", ld->mc->name);
+ return 0;
+ }
+
+ ld->mode = LINK_MODE_ULOAD;
+ mif_err("%s: called by %pf\n", ld->name, __builtin_return_address(0));
+
+ send_intr(pld, INT_CMD(INT_CMD_CRASH_EXIT));
+
+ mif_add_timer(&pld->crash_ack_timer, FORCE_CRASH_ACK_TIMEOUT,
+ handle_no_crash_ack, (unsigned long)pld);
+
+ return 0;
+}
+
+static int pld_init_ipc(struct pld_link_device *pld)
+{
+ struct link_device *ld = &pld->ld;
+ int i;
+
+ if (ld->mode == LINK_MODE_IPC &&
+ get_magic(pld) == DPRAM_MAGIC_CODE &&
+ get_access(pld) == 1)
+ mif_info("%s: IPC already initialized\n", ld->name);
+
+ /* Clear pointers in every circular queue */
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ set_tx_head(pld, i, 0);
+ set_tx_tail(pld, i, 0);
+ set_rx_head(pld, i, 0);
+ set_rx_tail(pld, i, 0);
+ }
+
+ /* Initialize variables for efficient TX/RX processing */
+ for (i = 0; i < ld->max_ipc_dev; i++)
+ pld->iod[i] = link_get_iod_with_format(ld, i);
+ pld->iod[IPC_RAW] = link_get_iod_with_format(ld, IPC_MULTI_RAW);
+
+ for (i = 0; i < ld->max_ipc_dev; i++)
+ atomic_set(&pld->res_required[i], 0);
+
+ spin_lock_init(&pld->tx_rx_lock);
+
+ /* Enable IPC */
+ atomic_set(&pld->accessing, 0);
+
+ set_magic(pld, DPRAM_MAGIC_CODE);
+ set_access(pld, 1);
+ if (get_magic(pld) != DPRAM_MAGIC_CODE || get_access(pld) != 1)
+ return -EACCES;
+
+ ld->mode = LINK_MODE_IPC;
+
+ if (wake_lock_active(&pld->wlock))
+ wake_unlock(&pld->wlock);
+
+ return 0;
+}
+
+static void cmd_req_active_handler(struct pld_link_device *pld)
+{
+ send_intr(pld, INT_CMD(INT_CMD_RES_ACTIVE));
+}
+
+static void cmd_crash_reset_handler(struct pld_link_device *pld)
+{
+ struct link_device *ld = &pld->ld;
+ struct io_device *iod = NULL;
+
+ ld->mode = LINK_MODE_ULOAD;
+
+ if (!wake_lock_active(&pld->wlock))
+ wake_lock(&pld->wlock);
+
+ mif_err("%s: Recv 0xC7 (CRASH_RESET)\n", ld->name);
+
+ iod = link_get_iod_with_format(ld, IPC_FMT);
+ iod->modem_state_changed(iod, STATE_CRASH_RESET);
+
+ iod = link_get_iod_with_format(ld, IPC_BOOT);
+ iod->modem_state_changed(iod, STATE_CRASH_RESET);
+}
+
+static void cmd_crash_exit_handler(struct pld_link_device *pld)
+{
+ struct link_device *ld = &pld->ld;
+
+ ld->mode = LINK_MODE_ULOAD;
+
+ if (!wake_lock_active(&pld->wlock))
+ wake_lock(&pld->wlock);
+
+ mif_err("%s: Recv 0xC9 (CRASH_EXIT)\n", ld->name);
+
+ del_timer(&pld->crash_ack_timer);
+
+ if (pld->ext_op && pld->ext_op->crash_log)
+ pld->ext_op->crash_log(pld);
+
+ handle_cp_crash(pld);
+}
+
+static void cmd_phone_start_handler(struct pld_link_device *pld)
+{
+ struct link_device *ld = &pld->ld;
+ struct io_device *iod = NULL;
+
+ mif_info("%s: Recv 0xC8 (CP_START)\n", ld->name);
+
+ pld_init_ipc(pld);
+
+ iod = link_get_iod_with_format(ld, IPC_FMT);
+ if (!iod) {
+ mif_info("%s: ERR! no iod\n", ld->name);
+ return;
+ }
+
+ if (pld->ext_op && pld->ext_op->cp_start_handler)
+ pld->ext_op->cp_start_handler(pld);
+
+ if (ld->mc->phone_state != STATE_ONLINE) {
+ mif_info("%s: phone_state: %d -> ONLINE\n",
+ ld->name, ld->mc->phone_state);
+ iod->modem_state_changed(iod, STATE_ONLINE);
+ }
+
+ mif_info("%s: Send 0xC2 (INIT_END)\n", ld->name);
+ send_intr(pld, INT_CMD(INT_CMD_INIT_END));
+}
+
+static void command_handler(struct pld_link_device *pld, u16 cmd)
+{
+ struct link_device *ld = &pld->ld;
+
+ switch (INT_CMD_MASK(cmd)) {
+ case INT_CMD_REQ_ACTIVE:
+ cmd_req_active_handler(pld);
+ break;
+
+ case INT_CMD_CRASH_RESET:
+ pld->init_status = DPRAM_INIT_STATE_NONE;
+ cmd_crash_reset_handler(pld);
+ break;
+
+ case INT_CMD_CRASH_EXIT:
+ pld->init_status = DPRAM_INIT_STATE_NONE;
+ cmd_crash_exit_handler(pld);
+ break;
+
+ case INT_CMD_PHONE_START:
+ pld->init_status = DPRAM_INIT_STATE_READY;
+ cmd_phone_start_handler(pld);
+ complete_all(&pld->dpram_init_cmd);
+ break;
+
+ case INT_CMD_NV_REBUILDING:
+ mif_info("%s: NV_REBUILDING\n", ld->name);
+ break;
+
+ case INT_CMD_PIF_INIT_DONE:
+ complete_all(&pld->modem_pif_init_done);
+ break;
+
+ case INT_CMD_SILENT_NV_REBUILDING:
+ mif_info("%s: SILENT_NV_REBUILDING\n", ld->name);
+ break;
+
+ case INT_CMD_NORMAL_PWR_OFF:
+ /*ToDo:*/
+ /*kernel_sec_set_cp_ack()*/;
+ break;
+
+ case INT_CMD_REQ_TIME_SYNC:
+ case INT_CMD_CP_DEEP_SLEEP:
+ case INT_CMD_EMER_DOWN:
+ break;
+
+ default:
+ mif_info("%s: unknown command 0x%04X\n", ld->name, cmd);
+ }
+}
+
+static irqreturn_t pld_irq_handler(int irq, void *data)
+{
+ struct pld_link_device *pld = (struct pld_link_device *)data;
+ struct link_device *ld = (struct link_device *)&pld->ld;
+ u16 int2ap = 0;
+
+ if (unlikely(ld->mode == LINK_MODE_OFFLINE))
+ return IRQ_HANDLED;
+
+ int2ap = recv_intr(pld);
+
+ if (unlikely(int2ap == INT_POWERSAFE_FAIL)) {
+ mif_info("%s: int2ap == INT_POWERSAFE_FAIL\n", ld->name);
+ goto exit;
+ } else if (int2ap == 0x1234 || int2ap == 0xDBAB || int2ap == 0xABCD) {
+ if (pld->ext_op && pld->ext_op->dload_cmd_handler) {
+ pld->ext_op->dload_cmd_handler(pld, int2ap);
+ goto exit;
+ }
+ }
+
+ if (likely(INT_VALID(int2ap))) {
+ if (unlikely(INT_CMD_VALID(int2ap)))
+ command_handler(pld, int2ap);
+ else
+ non_command_handler(pld, int2ap);
+ } else {
+ mif_info("%s: ERR! invalid intr 0x%04X\n",
+ ld->name, int2ap);
+ }
+
+exit:
+ clear_intr(pld);
+ return IRQ_HANDLED;
+}
+
+static void pld_send_ipc(struct link_device *ld, int dev,
+ struct io_device *iod, struct sk_buff *skb)
+{
+ struct pld_link_device *pld = to_pld_link_device(ld);
+ struct sk_buff_head *txq = ld->skb_txq[dev];
+ int ret;
+ u16 mask;
+
+ skb_queue_tail(txq, skb);
+ if (txq->qlen > 1024) {
+ mif_debug("%s: %s txq->qlen %d > 1024\n",
+ ld->name, get_dev_name(dev), txq->qlen);
+ }
+
+ if (!ipc_active(pld))
+ goto exit;
+
+ if (atomic_read(&pld->res_required[dev]) > 0) {
+ mif_debug("%s: %s_TXQ is full\n", ld->name, get_dev_name(dev));
+ goto exit;
+ }
+
+ ret = pld_try_ipc_tx(pld, dev);
+ if (ret > 0) {
+ mask = get_mask_send(pld, dev);
+ send_intr(pld, INT_NON_CMD(mask));
+ } else if (ret == -ENOSPC) {
+ mask = get_mask_req_ack(pld, dev);
+ send_intr(pld, INT_NON_CMD(mask));
+ mif_info("%s: Send REQ_ACK 0x%04X\n", ld->name, mask);
+ } else {
+ mif_info("%s: pld_try_ipc_tx fail (err %d)\n", ld->name, ret);
+ }
+
+exit:
+ return;
+}
+
+static int pld_send(struct link_device *ld, struct io_device *iod,
+ struct sk_buff *skb)
+{
+ enum dev_format dev = iod->format;
+ int len = skb->len;
+
+ switch (dev) {
+ case IPC_FMT:
+ case IPC_RAW:
+ case IPC_RFS:
+ if (likely(ld->mode == LINK_MODE_IPC)) {
+ pld_send_ipc(ld, dev, iod, skb);
+ } else {
+ mif_info("%s: ld->mode != LINK_MODE_IPC\n", ld->name);
+ dev_kfree_skb_any(skb);
+ }
+ return len;
+
+ default:
+ mif_info("%s: ERR! no TXQ for %s\n", ld->name, iod->name);
+ dev_kfree_skb_any(skb);
+ return -ENODEV;
+ }
+}
+
+static int pld_force_dump(struct link_device *ld, struct io_device *iod)
+{
+ struct pld_link_device *pld = to_pld_link_device(ld);
+ trigger_force_cp_crash(pld);
+ return 0;
+}
+
+static int pld_dump_start(struct link_device *ld, struct io_device *iod)
+{
+ struct pld_link_device *pld = to_pld_link_device(ld);
+
+ if (pld->ext_op && pld->ext_op->dump_start)
+ return pld->ext_op->dump_start(pld);
+ else
+ return -ENODEV;
+}
+
+static int pld_dump_update(struct link_device *ld, struct io_device *iod,
+ unsigned long arg)
+{
+ struct pld_link_device *pld = to_pld_link_device(ld);
+
+ if (pld->ext_op && pld->ext_op->dump_update)
+ return pld->ext_op->dump_update(pld, (void *)arg);
+ else
+ return -ENODEV;
+}
+
+static int pld_ioctl(struct link_device *ld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg)
+{
+ struct pld_link_device *pld = to_pld_link_device(ld);
+ int err = 0;
+
+/*
+ mif_info("%s: cmd 0x%08X\n", ld->name, cmd);
+*/
+
+ switch (cmd) {
+ case IOCTL_DPRAM_INIT_STATUS:
+ mif_debug("%s: get dpram init status\n", ld->name);
+ return pld->init_status;
+
+ default:
+ if (pld->ext_ioctl) {
+ err = pld->ext_ioctl(pld, iod, cmd, arg);
+ } else {
+ mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd);
+ err = -EINVAL;
+ }
+
+ break;
+ }
+
+ return err;
+}
+
+static int pld_table_init(struct pld_link_device *pld)
+{
+ struct link_device *ld = &pld->ld;
+ u8 __iomem *dp_base;
+ int i;
+
+ if (!pld->base) {
+ mif_info("%s: ERR! pld->base == NULL\n", ld->name);
+ return -EINVAL;
+ }
+ dp_base = pld->base;
+
+ /* Map for IPC */
+ if (pld->dpctl->ipc_map) {
+ memcpy(&pld->ipc_map, pld->dpctl->ipc_map,
+ sizeof(struct dpram_ipc_map));
+ }
+
+ pld->magic_ap2cp = pld->ipc_map.magic_ap2cp;
+ pld->access_ap2cp = pld->ipc_map.access_ap2cp;
+
+ pld->magic_cp2ap = pld->ipc_map.magic_cp2ap;
+ pld->access_cp2ap = pld->ipc_map.access_cp2ap;
+
+ pld->address_buffer = pld->ipc_map.address_buffer;
+
+ for (i = 0; i < ld->max_ipc_dev; i++)
+ pld->dev[i] = &pld->ipc_map.dev[i];
+ pld->mbx2ap = pld->ipc_map.mbx_cp2ap;
+ pld->mbx2cp = pld->ipc_map.mbx_ap2cp;
+
+ /* Map for booting */
+ if (pld->ext_op && pld->ext_op->init_boot_map) {
+ pld->ext_op->init_boot_map(pld);
+ } else {
+ pld->bt_map.magic = (u32 *)(dp_base);
+ pld->bt_map.buff = (u8 *)(dp_base + DP_BOOT_BUFF_OFFSET);
+ pld->bt_map.size = pld->size - 8;
+ }
+
+ /* Map for download (FOTA, UDL, etc.) */
+ if (pld->ext_op && pld->ext_op->init_dl_map) {
+ pld->ext_op->init_dl_map(pld);
+ } else {
+ pld->dl_map.magic = (u32 *)(dp_base);
+ pld->dl_map.buff = (u8 *)(dp_base + DP_DLOAD_BUFF_OFFSET);
+ }
+
+ /* Map for upload mode */
+ if (pld->ext_op && pld->ext_op->init_ul_map) {
+ pld->ext_op->init_ul_map(pld);
+ } else {
+ pld->ul_map.magic = (u32 *)(dp_base);
+ pld->ul_map.buff = (u8 *)(dp_base + DP_ULOAD_BUFF_OFFSET);
+ }
+
+ return 0;
+}
+
+static void pld_setup_common_op(struct pld_link_device *pld)
+{
+ pld->clear_intr = clear_intr;
+ pld->recv_intr = recv_intr;
+ pld->send_intr = send_intr;
+ pld->get_magic = get_magic;
+ pld->set_magic = set_magic;
+ pld->get_access = get_access;
+ pld->set_access = set_access;
+ pld->get_tx_head = get_tx_head;
+ pld->get_tx_tail = get_tx_tail;
+ pld->set_tx_head = set_tx_head;
+ pld->set_tx_tail = set_tx_tail;
+ pld->get_tx_buff = get_tx_buff;
+ pld->get_tx_buff_size = get_tx_buff_size;
+ pld->get_rx_head = get_rx_head;
+ pld->get_rx_tail = get_rx_tail;
+ pld->set_rx_head = set_rx_head;
+ pld->set_rx_tail = set_rx_tail;
+ pld->get_rx_buff = get_rx_buff;
+ pld->get_rx_buff_size = get_rx_buff_size;
+ pld->get_mask_req_ack = get_mask_req_ack;
+ pld->get_mask_res_ack = get_mask_res_ack;
+ pld->get_mask_send = get_mask_send;
+}
+
+static int pld_link_init(struct link_device *ld, struct io_device *iod)
+{
+ return 0;
+}
+
+static void pld_link_terminate(struct link_device *ld, struct io_device *iod)
+{
+ return;
+}
+
+struct link_device *pld_create_link_device(struct platform_device *pdev)
+{
+ struct modem_data *mdm_data = NULL;
+ struct pld_link_device *pld = NULL;
+ struct link_device *ld = NULL;
+ struct resource *res = NULL;
+ resource_size_t res_size;
+ struct modemlink_dpram_control *dpctl = NULL;
+ unsigned long task_data;
+ int ret = 0;
+ int i = 0;
+ int bsize;
+ int qsize;
+
+ /* Get the platform data */
+ mdm_data = (struct modem_data *)pdev->dev.platform_data;
+ if (!mdm_data) {
+ mif_info("ERR! mdm_data == NULL\n");
+ goto err;
+ }
+ mif_info("modem = %s\n", mdm_data->name);
+ mif_info("link device = %s\n", mdm_data->link_name);
+
+ if (!mdm_data->dpram_ctl) {
+ mif_info("ERR! mdm_data->dpram_ctl == NULL\n");
+ goto err;
+ }
+ dpctl = mdm_data->dpram_ctl;
+
+ /* Alloc DPRAM link device structure */
+ pld = kzalloc(sizeof(struct pld_link_device), GFP_KERNEL);
+ if (!pld) {
+ mif_info("ERR! kzalloc pld fail\n");
+ goto err;
+ }
+ ld = &pld->ld;
+
+ /* Retrieve modem data and DPRAM control data from the modem data */
+ ld->mdm_data = mdm_data;
+ ld->name = mdm_data->link_name;
+ ld->ipc_version = mdm_data->ipc_version;
+
+ /* Retrieve the most basic data for IPC from the modem data */
+ pld->dpctl = dpctl;
+ pld->type = dpctl->dp_type;
+
+ if (mdm_data->ipc_version < SIPC_VER_50) {
+ if (!dpctl->max_ipc_dev) {
+ mif_info("ERR! no max_ipc_dev\n");
+ goto err;
+ }
+
+ ld->aligned = dpctl->aligned;
+ ld->max_ipc_dev = dpctl->max_ipc_dev;
+ } else {
+ ld->aligned = 1;
+ ld->max_ipc_dev = MAX_SIPC5_DEV;
+ }
+
+ /* Set attributes as a link device */
+ ld->init_comm = pld_link_init;
+ ld->terminate_comm = pld_link_terminate;
+ ld->send = pld_send;
+ ld->force_dump = pld_force_dump;
+ ld->dump_start = pld_dump_start;
+ ld->dump_update = pld_dump_update;
+ ld->ioctl = pld_ioctl;
+
+ INIT_LIST_HEAD(&ld->list);
+
+ skb_queue_head_init(&ld->sk_fmt_tx_q);
+ skb_queue_head_init(&ld->sk_raw_tx_q);
+ skb_queue_head_init(&ld->sk_rfs_tx_q);
+ ld->skb_txq[IPC_FMT] = &ld->sk_fmt_tx_q;
+ ld->skb_txq[IPC_RAW] = &ld->sk_raw_tx_q;
+ ld->skb_txq[IPC_RFS] = &ld->sk_rfs_tx_q;
+
+ /* Set up function pointers */
+ pld_setup_common_op(pld);
+ pld->ext_op = pld_get_ext_op(mdm_data->modem_type);
+ if (pld->ext_op && pld->ext_op->ioctl)
+ pld->ext_ioctl = pld->ext_op->ioctl;
+
+ /* Retrieve DPRAM resource */
+ if (!dpctl->dp_base) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ mif_info("%s: ERR! platform_get_resource fail\n",
+ ld->name);
+ goto err;
+ }
+ res_size = resource_size(res);
+
+ dpctl->dp_base = ioremap_nocache(res->start, res_size);
+ dpctl->dp_size = res_size;
+ }
+ pld->base = dpctl->dp_base;
+ pld->size = dpctl->dp_size;
+
+ mif_info("%s: type %d, aligned %d, base 0x%08X, size %d\n",
+ ld->name, pld->type, ld->aligned, (int)pld->base, pld->size);
+
+ /* Initialize DPRAM map (physical map -> logical map) */
+ ret = pld_table_init(pld);
+ if (ret < 0) {
+ mif_info("%s: ERR! pld_table_init fail (err %d)\n",
+ ld->name, ret);
+ goto err;
+ }
+
+ spin_lock_init(&pld->pld_lock);
+
+ /* Disable IPC */
+ set_magic(pld, 0);
+ set_access(pld, 0);
+
+ pld->init_status = DPRAM_INIT_STATE_NONE;
+
+ /* Initialize locks, completions, and bottom halves */
+ snprintf(pld->wlock_name, MIF_MAX_NAME_LEN, "%s_wlock", ld->name);
+ wake_lock_init(&pld->wlock, WAKE_LOCK_SUSPEND, pld->wlock_name);
+
+ init_completion(&pld->dpram_init_cmd);
+ init_completion(&pld->modem_pif_init_done);
+ init_completion(&pld->udl_start_complete);
+ init_completion(&pld->udl_cmd_complete);
+ init_completion(&pld->crash_start_complete);
+ init_completion(&pld->crash_recv_done);
+
+ task_data = (unsigned long)pld;
+ tasklet_init(&pld->rx_tsk, pld_ipc_rx_task, task_data);
+
+ /* Prepare RXB queue */
+ qsize = DPRAM_MAX_RXBQ_SIZE;
+ for (i = 0; i < ld->max_ipc_dev; i++) {
+ bsize = rxbq_get_page_size(get_rx_buff_size(pld, i));
+ pld->rxbq[i].size = qsize;
+ pld->rxbq[i].in = 0;
+ pld->rxbq[i].out = 0;
+ pld->rxbq[i].rxb = rxbq_create_pool(bsize, qsize);
+ if (!pld->rxbq[i].rxb) {
+ mif_info("%s: ERR! %s rxbq_create_pool fail\n",
+ ld->name, get_dev_name(i));
+ goto err;
+ }
+ mif_info("%s: %s rxbq_pool created (bsize:%d, qsize:%d)\n",
+ ld->name, get_dev_name(i), bsize, qsize);
+ }
+
+ /* Prepare a multi-purpose miscellaneous buffer */
+ pld->buff = kzalloc(pld->size, GFP_KERNEL);
+ if (!pld->buff) {
+ mif_info("%s: ERR! kzalloc pld->buff fail\n", ld->name);
+ goto err;
+ }
+
+ /* Retrieve DPRAM IRQ GPIO# */
+ pld->gpio_dpram_int = mdm_data->gpio_dpram_int;
+
+ /* Retrieve DPRAM IRQ# */
+ if (!dpctl->dpram_irq) {
+ dpctl->dpram_irq = platform_get_irq_byname(pdev, "dpram_irq");
+ if (dpctl->dpram_irq < 0) {
+ mif_info("%s: ERR! platform_get_irq_byname fail\n",
+ ld->name);
+ goto err;
+ }
+ }
+ pld->irq = dpctl->dpram_irq;
+
+ /* Retrieve DPRAM IRQ flags */
+ if (!dpctl->dpram_irq_flags)
+ dpctl->dpram_irq_flags = (IRQF_NO_SUSPEND | IRQF_TRIGGER_LOW);
+ pld->irq_flags = dpctl->dpram_irq_flags;
+
+ /* Register DPRAM interrupt handler */
+ snprintf(pld->irq_name, MIF_MAX_NAME_LEN, "%s_irq", ld->name);
+ ret = pld_register_isr(pld->irq, pld_irq_handler, pld->irq_flags,
+ pld->irq_name, pld);
+ if (ret)
+ goto err;
+
+ return ld;
+
+err:
+ if (pld) {
+ kfree(pld->buff);
+ kfree(pld);
+ }
+
+ return NULL;
+}
+
diff --git a/drivers/misc/modem_if/modem_link_device_pld.h b/drivers/misc/modem_if/modem_link_device_pld.h
new file mode 100644
index 00000000000..89c44b2a86b
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_pld.h
@@ -0,0 +1,231 @@
+/*
+ * 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_PLD_H__
+#define __MODEM_LINK_DEVICE_PLD_H__
+
+#include "modem_link_device_memory.h"
+
+#define PLD_ADDR_MASK(x) (0x00003FFF & (unsigned long)(x))
+
+/*
+ mbx_ap2cp + 0x0
+ magic_code +
+ access_enable +
+ padding +
+ mbx_cp2ap + 0x1000
+ magic_code +
+ access_enable +
+ padding +
+ fmt_tx_head + fmt_tx_tail + fmt_tx_buff + 0x2000
+ raw_tx_head + raw_tx_tail + raw_tx_buff +
+ fmt_rx_head + fmt_rx_tail + fmt_rx_buff + 0x3000
+ raw_rx_head + raw_rx_tail + raw_rx_buff +
+ = 2 +
+ 4094 +
+ 2 +
+ 4094 +
+ 2 +
+ 2 +
+ 2 + 2 + 1020 +
+ 2 + 2 + 3064 +
+ 2 + 2 + 1020 +
+ 2 + 2 + 3064
+ */
+
+#define PLD_FMT_TX_BUFF_SZ 1024
+#define PLD_RAW_TX_BUFF_SZ 3072
+#define PLD_FMT_RX_BUFF_SZ 1024
+#define PLD_RAW_RX_BUFF_SZ 3072
+
+#define MAX_MSM_EDPRAM_IPC_DEV 2 /* FMT, RAW */
+
+struct pld_ipc_map {
+ u16 mbx_ap2cp;
+ u16 magic_ap2cp;
+ u16 access_ap2cp;
+ u16 fmt_tx_head;
+ u16 raw_tx_head;
+ u16 fmt_rx_tail;
+ u16 raw_rx_tail;
+ u16 temp1;
+ u8 padding1[4080];
+
+ u16 mbx_cp2ap;
+ u16 magic_cp2ap;
+ u16 access_cp2ap;
+ u16 fmt_tx_tail;
+ u16 raw_tx_tail;
+ u16 fmt_rx_head;
+ u16 raw_rx_head;
+ u16 temp2;
+ u8 padding2[4080];
+
+ u8 fmt_tx_buff[PLD_FMT_TX_BUFF_SZ];
+ u8 raw_tx_buff[PLD_RAW_TX_BUFF_SZ];
+ u8 fmt_rx_buff[PLD_RAW_TX_BUFF_SZ];
+ u8 raw_rx_buff[PLD_RAW_RX_BUFF_SZ];
+
+ u8 padding3[16384];
+
+ u16 address_buffer;
+};
+
+struct pld_ext_op;
+
+struct pld_link_device {
+ struct link_device ld;
+
+ /* DPRAM address and size */
+ enum dpram_type type; /* DPRAM type */
+ u8 __iomem *base; /* DPRAM base virtual address */
+ u32 size; /* DPRAM size */
+
+ /* DPRAM IRQ GPIO# */
+ unsigned gpio_dpram_int;
+
+ /* DPRAM IRQ from CP */
+ int irq;
+ unsigned long irq_flags;
+ char irq_name[MIF_MAX_NAME_LEN];
+
+ /* Link to DPRAM control functions dependent on each platform */
+ struct modemlink_dpram_control *dpctl;
+
+ /* Physical configuration -> logical configuration */
+ union {
+ struct dpram_boot_map bt_map;
+ struct qc_dpram_boot_map qc_bt_map;
+ };
+
+ struct dpram_dload_map dl_map;
+ struct dpram_uload_map ul_map;
+
+ /* IPC device map */
+ struct dpram_ipc_map ipc_map;
+
+ /* Pointers (aliases) to IPC device map */
+ u16 __iomem *magic_ap2cp;
+ u16 __iomem *access_ap2cp;
+ u16 __iomem *magic_cp2ap;
+ u16 __iomem *access_cp2ap;
+ u16 __iomem *address_buffer;
+
+ struct dpram_ipc_device *dev[MAX_IPC_DEV];
+ u16 __iomem *mbx2ap;
+ u16 __iomem *mbx2cp;
+
+ /* Wakelock for DPRAM device */
+ struct wake_lock wlock;
+ char wlock_name[MIF_MAX_NAME_LEN];
+
+ /* For booting */
+ unsigned boot_start_complete;
+ struct completion dpram_init_cmd;
+ struct completion modem_pif_init_done;
+
+ /* For UDL */
+ struct tasklet_struct ul_tsk;
+ struct tasklet_struct dl_tsk;
+ struct completion udl_start_complete;
+ struct completion udl_cmd_complete;
+ struct dpram_udl_check udl_check;
+ struct dpram_udl_param udl_param;
+
+ /* For CP crash dump */
+ struct timer_list crash_ack_timer;
+ struct completion crash_start_complete;
+ struct completion crash_recv_done;
+ struct timer_list crash_timer;
+ int crash_rcvd; /* Count of CP crash dump packets received */
+
+ /* For locking TX process */
+ spinlock_t tx_rx_lock;
+ spinlock_t pld_lock;
+
+ /* For efficient RX process */
+ struct tasklet_struct rx_tsk;
+ struct mif_rxb_queue rxbq[MAX_IPC_DEV];
+ struct io_device *iod[MAX_IPC_DEV];
+
+ /* For retransmission after buffer full state */
+ atomic_t res_required[MAX_IPC_DEV];
+
+ /* For wake-up/sleep control */
+ atomic_t accessing;
+
+ /* Multi-purpose miscellaneous buffer */
+ u8 *buff;
+
+ /* PLD IPC initialization status */
+ int init_status;
+
+ /* Alias to device-specific IOCTL function */
+ int (*ext_ioctl)(struct pld_link_device *pld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg);
+
+ /* Common operations for each DPRAM */
+ void (*clear_intr)(struct pld_link_device *pld);
+ u16 (*recv_intr)(struct pld_link_device *pld);
+ void (*send_intr)(struct pld_link_device *pld, u16 mask);
+ u16 (*get_magic)(struct pld_link_device *pld);
+ void (*set_magic)(struct pld_link_device *pld, u16 value);
+ u16 (*get_access)(struct pld_link_device *pld);
+ void (*set_access)(struct pld_link_device *pld, u16 value);
+ u32 (*get_tx_head)(struct pld_link_device *pld, int id);
+ u32 (*get_tx_tail)(struct pld_link_device *pld, int id);
+ void (*set_tx_head)(struct pld_link_device *pld, int id, u32 head);
+ void (*set_tx_tail)(struct pld_link_device *pld, int id, u32 tail);
+ u8 *(*get_tx_buff)(struct pld_link_device *pld, int id);
+ u32 (*get_tx_buff_size)(struct pld_link_device *pld, int id);
+ u32 (*get_rx_head)(struct pld_link_device *pld, int id);
+ u32 (*get_rx_tail)(struct pld_link_device *pld, int id);
+ void (*set_rx_head)(struct pld_link_device *pld, int id, u32 head);
+ void (*set_rx_tail)(struct pld_link_device *pld, int id, u32 tail);
+ u8 *(*get_rx_buff)(struct pld_link_device *pld, int id);
+ u32 (*get_rx_buff_size)(struct pld_link_device *pld, int id);
+ u16 (*get_mask_req_ack)(struct pld_link_device *pld, int id);
+ u16 (*get_mask_res_ack)(struct pld_link_device *pld, int id);
+ u16 (*get_mask_send)(struct pld_link_device *pld, int id);
+
+ /* Extended operations for various modems */
+ struct pld_ext_op *ext_op;
+};
+
+/* converts from struct link_device* to struct xxx_link_device* */
+#define to_pld_link_device(linkdev) \
+ container_of(linkdev, struct pld_link_device, ld)
+
+struct pld_ext_op {
+ int exist;
+
+ void (*init_boot_map)(struct pld_link_device *pld);
+ void (*init_dl_map)(struct pld_link_device *pld);
+ void (*init_ul_map)(struct pld_link_device *pld);
+
+ void (*dload_cmd_handler)(struct pld_link_device *pld, u16 cmd);
+
+ void (*cp_start_handler)(struct pld_link_device *pld);
+
+ void (*crash_log)(struct pld_link_device *pld);
+ int (*dump_start)(struct pld_link_device *pld);
+ int (*dump_update)(struct pld_link_device *pld, void *arg);
+
+ int (*ioctl)(struct pld_link_device *pld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg);
+
+ void (*clear_intr)(struct pld_link_device *pld);
+};
+
+struct pld_ext_op *pld_get_ext_op(enum modem_t modem);
+#endif
diff --git a/drivers/misc/modem_if/modem_link_device_pld_ext_op.c b/drivers/misc/modem_if/modem_link_device_pld_ext_op.c
new file mode 100644
index 00000000000..4f2df8f447a
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_pld_ext_op.c
@@ -0,0 +1,556 @@
+/*
+ * 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/time.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/kallsyms.h>
+#include <linux/platform_data/modem.h>
+
+#include "modem_prj.h"
+#include "modem_link_device_pld.h"
+#include "modem_utils.h"
+
+#if defined(CONFIG_CDMA_MODEM_MDM6600) || defined(CONFIG_GSM_MODEM_ESC6270)
+enum qc_dload_tag {
+ QC_DLOAD_TAG_NONE = 0,
+ QC_DLOAD_TAG_BIN,
+ QC_DLOAD_TAG_NV,
+ QC_DLOAD_TAG_MAX
+};
+
+static void qc_dload_task(unsigned long data);
+
+static void qc_init_boot_map(struct pld_link_device *pld)
+{
+ struct qc_dpram_boot_map *qbt_map = &pld->qc_bt_map;
+ struct modemlink_dpram_control *dpctl = pld->dpctl;
+
+ qbt_map->buff = pld->dev[0]->txq.buff;
+ qbt_map->frame_size = (u16 *)(pld->base + dpctl->boot_size_offset);
+ qbt_map->tag = (u16 *)(pld->base + dpctl->boot_tag_offset);
+ qbt_map->count = (u16 *)(pld->base + dpctl->boot_count_offset);
+
+ tasklet_init(&pld->dl_tsk, qc_dload_task, (unsigned long)pld);
+}
+
+static void qc_dload_map(struct pld_link_device *pld, u8 is_upload)
+{
+ struct qc_dpram_boot_map *qbt_map = &pld->qc_bt_map;
+ struct modemlink_dpram_control *dpctl = pld->dpctl;
+ unsigned int upload_offset = 0;
+
+ if (is_upload == 1) {
+ upload_offset = 0x1000;
+ qbt_map->buff = pld->dev[0]->rxq.buff;
+ } else {
+ upload_offset = 0;
+ qbt_map->buff = pld->dev[0]->txq.buff;
+ }
+
+ qbt_map->frame_size = (u16 *)(pld->base +
+ dpctl->boot_size_offset + upload_offset);
+ qbt_map->tag = (u16 *)(pld->base +
+ dpctl->boot_tag_offset + upload_offset);
+ qbt_map->count = (u16 *)(pld->base +
+ dpctl->boot_count_offset + upload_offset);
+
+}
+
+static int qc_prepare_download(struct pld_link_device *pld)
+{
+ int retval = 0;
+ int count = 0;
+
+ qc_dload_map(pld, 0);
+
+ while (1) {
+ if (pld->udl_check.copy_start) {
+ pld->udl_check.copy_start = 0;
+ break;
+ }
+
+ usleep_range(10000, 11000);
+
+ count++;
+ if (count > 1000) {
+ mif_err("ERR! count %d\n", count);
+ return -1;
+ }
+ }
+
+ return retval;
+}
+
+static void _qc_do_download(struct pld_link_device *pld,
+ struct dpram_udl_param *param)
+{
+ struct qc_dpram_boot_map *qbt_map = &pld->qc_bt_map;
+
+ if (param->size <= pld->dpctl->max_boot_frame_size) {
+ iowrite16(PLD_ADDR_MASK(&qbt_map->buff[0]),
+ pld->address_buffer);
+ memcpy(pld->base, param->addr, param->size);
+
+ iowrite16(PLD_ADDR_MASK(&qbt_map->frame_size[0]),
+ pld->address_buffer);
+ iowrite16(param->size, pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&qbt_map->tag[0]),
+ pld->address_buffer);
+ iowrite16(param->tag, pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&qbt_map->count[0]),
+ pld->address_buffer);
+ iowrite16(param->count, pld->base);
+
+ pld->send_intr(pld, 0xDB12);
+ } else {
+ mif_info("param->size %d\n", param->size);
+ }
+}
+
+static int _qc_download(struct pld_link_device *pld, void *arg,
+ enum qc_dload_tag tag)
+{
+ int retval = 0;
+ int count = 0;
+ int cnt_limit;
+ unsigned char *img;
+ struct dpram_udl_param param;
+
+ retval = copy_from_user((void *)&param, (void *)arg, sizeof(param));
+ if (retval < 0) {
+ mif_err("ERR! copy_from_user fail\n");
+ return -1;
+ }
+
+ img = vmalloc(param.size);
+ if (!img) {
+ mif_err("ERR! vmalloc fail\n");
+ return -1;
+ }
+ memset(img, 0, param.size);
+ memcpy(img, param.addr, param.size);
+
+ pld->udl_check.total_size = param.size;
+ pld->udl_check.rest_size = param.size;
+ pld->udl_check.send_size = 0;
+ pld->udl_check.copy_complete = 0;
+
+ pld->udl_param.addr = img;
+ pld->udl_param.size = pld->dpctl->max_boot_frame_size;
+ if (tag == QC_DLOAD_TAG_NV)
+ pld->udl_param.count = 1;
+ else
+ pld->udl_param.count = param.count;
+ pld->udl_param.tag = tag;
+
+ if (pld->udl_check.rest_size < pld->dpctl->max_boot_frame_size)
+ pld->udl_param.size = pld->udl_check.rest_size;
+
+ /* Download image (binary or NV) */
+ _qc_do_download(pld, &pld->udl_param);
+
+ /* Wait for completion
+ */
+ if (tag == QC_DLOAD_TAG_NV)
+ cnt_limit = 200;
+ else
+ cnt_limit = 1000;
+
+ while (1) {
+ if (pld->udl_check.copy_complete) {
+ pld->udl_check.copy_complete = 0;
+ retval = 0;
+ break;
+ }
+
+ usleep_range(10000, 11000);
+
+ count++;
+ if (count > cnt_limit) {
+ mif_err("ERR! count %d\n", count);
+ retval = -1;
+ break;
+ }
+ }
+
+ vfree(img);
+
+ return retval;
+}
+
+static int qc_download_bin(struct pld_link_device *pld, void *arg)
+{
+ return _qc_download(pld, arg, QC_DLOAD_TAG_BIN);
+}
+
+static int qc_download_nv(struct pld_link_device *pld, void *arg)
+{
+ return _qc_download(pld, arg, QC_DLOAD_TAG_NV);
+}
+
+static void qc_dload_task(unsigned long data)
+{
+ struct pld_link_device *pld = (struct pld_link_device *)data;
+
+ pld->udl_check.send_size += pld->udl_param.size;
+ pld->udl_check.rest_size -= pld->udl_param.size;
+
+ pld->udl_param.addr += pld->udl_param.size;
+
+ if (pld->udl_check.send_size >= pld->udl_check.total_size) {
+ pld->udl_check.copy_complete = 1;
+ pld->udl_param.tag = 0;
+ return;
+ }
+
+ if (pld->udl_check.rest_size < pld->dpctl->max_boot_frame_size)
+ pld->udl_param.size = pld->udl_check.rest_size;
+
+ pld->udl_param.count += 1;
+
+ _qc_do_download(pld, &pld->udl_param);
+}
+
+static void qc_dload_cmd_handler(struct pld_link_device *pld, u16 cmd)
+{
+ switch (cmd) {
+ case 0x1234:
+ pld->udl_check.copy_start = 1;
+ break;
+
+ case 0xDBAB:
+ tasklet_schedule(&pld->dl_tsk);
+ break;
+
+ case 0xABCD:
+ mif_info("[%s] booting Start\n", pld->ld.name);
+ pld->udl_check.boot_complete = 1;
+ break;
+
+ default:
+ mif_err("ERR! unknown command 0x%04X\n", cmd);
+ }
+}
+
+static int qc_boot_start(struct pld_link_device *pld)
+{
+ u16 mask = 0;
+ int count = 0;
+
+ /* Send interrupt -> '0x4567' */
+ mask = 0x4567;
+ pld->send_intr(pld, mask);
+
+ while (1) {
+ if (pld->udl_check.boot_complete) {
+ pld->udl_check.boot_complete = 0;
+ break;
+ }
+
+ usleep_range(10000, 11000);
+
+ count++;
+ if (count > 200) {
+ mif_err("ERR! count %d\n", count);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int qc_boot_post_process(struct pld_link_device *pld)
+{
+ int count = 0;
+
+ while (1) {
+ if (pld->boot_start_complete) {
+ pld->boot_start_complete = 0;
+ break;
+ }
+
+ usleep_range(10000, 11000);
+
+ count++;
+ if (count > 200) {
+ mif_err("ERR! count %d\n", count);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void qc_start_handler(struct pld_link_device *pld)
+{
+ /*
+ * INT_MASK_VALID | INT_MASK_CMD | INT_MASK_CP_AIRPLANE_BOOT |
+ * INT_MASK_CP_AP_ANDROID | INT_MASK_CMD_INIT_END
+ */
+ u16 mask = (0x0080 | 0x0040 | 0x1000 | 0x0100 | 0x0002);
+
+ pld->boot_start_complete = 1;
+
+ /* Send INIT_END code to CP */
+ mif_info("send 0x%04X (INIT_END)\n", mask);
+
+ pld->send_intr(pld, mask);
+}
+
+static void qc_crash_log(struct pld_link_device *pld)
+{
+ struct link_device *ld = &pld->ld;
+ static unsigned char buf[151];
+ u8 __iomem *data = NULL;
+
+ data = pld->get_rx_buff(pld, IPC_FMT);
+ memcpy(buf, data, (sizeof(buf) - 1));
+
+ mif_info("PHONE ERR MSG\t| %s Crash\n", ld->mdm_data->name);
+ mif_info("PHONE ERR MSG\t| %s\n", buf);
+}
+
+static int _qc_data_upload(struct pld_link_device *pld,
+ struct dpram_udl_param *param)
+{
+ struct qc_dpram_boot_map *qbt_map = &pld->qc_bt_map;
+ int retval = 0;
+ u16 intval = 0;
+ int count = 0;
+
+ while (1) {
+ if (!gpio_get_value(pld->gpio_dpram_int)) {
+ intval = pld->recv_intr(pld);
+ if (intval == 0xDBAB) {
+ break;
+ } else {
+ mif_err("intr 0x%08x\n", intval);
+ return -1;
+ }
+ }
+
+ usleep_range(1000, 2000);
+
+ count++;
+ if (count > 200) {
+ mif_err("<%s:%d>\n", __func__, __LINE__);
+ return -1;
+ }
+ }
+
+ iowrite16(PLD_ADDR_MASK(&qbt_map->frame_size[0]),
+ pld->address_buffer);
+ param->size = ioread16(pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&qbt_map->tag[0]),
+ pld->address_buffer);
+ param->tag = ioread16(pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&qbt_map->count[0]),
+ pld->address_buffer);
+ param->count = ioread16(pld->base);
+
+ iowrite16(PLD_ADDR_MASK(&qbt_map->buff[0]),
+ pld->address_buffer);
+ memcpy(param->addr, pld->base, param->size);
+
+ pld->send_intr(pld, 0xDB12);
+
+ return retval;
+}
+
+static int qc_uload_step1(struct pld_link_device *pld)
+{
+ int retval = 0;
+ int count = 0;
+ u16 intval = 0;
+ u16 mask = 0;
+
+ qc_dload_map(pld, 1);
+
+ mif_info("+---------------------------------------------+\n");
+ mif_info("| UPLOAD PHONE SDRAM |\n");
+ mif_info("+---------------------------------------------+\n");
+
+ while (1) {
+ if (!gpio_get_value(pld->gpio_dpram_int)) {
+ intval = pld->recv_intr(pld);
+ mif_info("intr 0x%04x\n", intval);
+ if (intval == 0x1234) {
+ break;
+ } else {
+ mif_info("ERR! invalid intr\n");
+ return -1;
+ }
+ }
+
+ usleep_range(1000, 2000);
+
+ count++;
+ if (count > 200) {
+ intval = pld->recv_intr(pld);
+ mif_info("count %d, intr 0x%04x\n", count, intval);
+ if (intval == 0x1234)
+ break;
+ return -1;
+ }
+ }
+
+ mask = 0xDEAD;
+ pld->send_intr(pld, mask);
+
+ return retval;
+}
+
+static int qc_uload_step2(struct pld_link_device *pld, void *arg)
+{
+ int retval = 0;
+ struct dpram_udl_param param;
+
+ retval = copy_from_user((void *)&param, (void *)arg, sizeof(param));
+ if (retval < 0) {
+ mif_err("ERR! copy_from_user fail (err %d)\n", retval);
+ return -1;
+ }
+
+ retval = _qc_data_upload(pld, &param);
+ if (retval < 0) {
+ mif_err("ERR! _qc_data_upload fail (err %d)\n", retval);
+ return -1;
+ }
+
+ if (!(param.count % 500))
+ mif_info("param->count = %d\n", param.count);
+
+ if (param.tag == 4) {
+ enable_irq(pld->irq);
+ mif_info("param->tag = %d\n", param.tag);
+ }
+
+ retval = copy_to_user((unsigned long *)arg, &param, sizeof(param));
+ if (retval < 0) {
+ mif_err("ERR! copy_to_user fail (err %d)\n", retval);
+ return -1;
+ }
+
+ return retval;
+}
+
+static int qc_ioctl(struct pld_link_device *pld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg)
+{
+ struct link_device *ld = &pld->ld;
+ int err = 0;
+
+ switch (cmd) {
+ case IOCTL_DPRAM_PHONE_POWON:
+ err = qc_prepare_download(pld);
+ if (err < 0)
+ mif_info("%s: ERR! prepare_download fail\n", ld->name);
+ break;
+
+ case IOCTL_DPRAM_PHONEIMG_LOAD:
+ err = qc_download_bin(pld, (void *)arg);
+ if (err < 0)
+ mif_info("%s: ERR! download_bin fail\n", ld->name);
+ break;
+
+ case IOCTL_DPRAM_NVDATA_LOAD:
+ err = qc_download_nv(pld, (void *)arg);
+ if (err < 0)
+ mif_info("%s: ERR! download_nv fail\n", ld->name);
+ break;
+
+ case IOCTL_DPRAM_PHONE_BOOTSTART:
+ err = qc_boot_start(pld);
+ if (err < 0) {
+ mif_info("%s: ERR! boot_start fail\n", ld->name);
+ break;
+ }
+
+ err = qc_boot_post_process(pld);
+ if (err < 0)
+ mif_info("%s: ERR! boot_post_process fail\n", ld->name);
+
+ break;
+
+ case IOCTL_DPRAM_PHONE_UPLOAD_STEP1:
+ disable_irq_nosync(pld->irq);
+ err = qc_uload_step1(pld);
+ if (err < 0) {
+ enable_irq(pld->irq);
+ mif_info("%s: ERR! upload_step1 fail\n", ld->name);
+ }
+ break;
+
+ case IOCTL_DPRAM_PHONE_UPLOAD_STEP2:
+ err = qc_uload_step2(pld, (void *)arg);
+ if (err < 0) {
+ enable_irq(pld->irq);
+ mif_info("%s: ERR! upload_step2 fail\n", ld->name);
+ }
+ break;
+
+ default:
+ mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd);
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+#endif
+
+static struct pld_ext_op ext_op_set[] = {
+#if defined(CONFIG_CDMA_MODEM_MDM6600)
+ [QC_MDM6600] = {
+ .exist = 1,
+ .init_boot_map = qc_init_boot_map,
+ .dload_cmd_handler = qc_dload_cmd_handler,
+ .cp_start_handler = qc_start_handler,
+ .crash_log = qc_crash_log,
+ .ioctl = qc_ioctl,
+ },
+#endif
+#if defined(CONFIG_GSM_MODEM_ESC6270)
+ [QC_ESC6270] = {
+ .exist = 1,
+ .init_boot_map = qc_init_boot_map,
+ .dload_cmd_handler = qc_dload_cmd_handler,
+ .cp_start_handler = qc_start_handler,
+ .crash_log = qc_crash_log,
+ .ioctl = qc_ioctl,
+ },
+#endif
+};
+
+struct pld_ext_op *pld_get_ext_op(enum modem_t modem)
+{
+ if (ext_op_set[modem].exist)
+ return &ext_op_set[modem];
+ else
+ return NULL;
+}
diff --git a/drivers/misc/modem_if/modem_link_device_usb.c b/drivers/misc/modem_if/modem_link_device_usb.c
new file mode 100644
index 00000000000..5b7c98bebea
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_usb.c
@@ -0,0 +1,1027 @@
+/*
+ * 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.h>
+#include "modem_prj.h"
+#include "modem_link_device_usb.h"
+#include "modem_utils.h"
+#include "modem_link_pm_usb.h"
+
+#include <mach/regs-gpio.h>
+
+#define URB_COUNT 4
+
+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_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 start_ipc(struct link_device *ld, struct io_device *iod)
+{
+ struct sk_buff *skb;
+ char data[1] = {'a'};
+ int err;
+ struct usb_link_device *usb_ld = to_usb_link_device(ld);
+ struct if_usb_devdata *pipe_data = &usb_ld->devdata[IF_USB_FMT_EP];
+
+ if (has_hub(usb_ld) && usb_ld->link_pm_data->hub_handshake_done) {
+ mif_err("Already send start ipc, skip start ipc\n");
+ err = 0;
+ goto exit;
+ }
+
+ if (!usb_ld->if_usb_connected) {
+ mif_err("HSIC/USB not connected, skip start ipc\n");
+ err = -ENODEV;
+ goto exit;
+ }
+
+ if (has_hub(usb_ld) &&
+ usb_ld->if_usb_initstates == INIT_IPC_START_DONE) {
+ mif_debug("Already IPC started\n");
+ err = 0;
+ goto exit;
+ }
+
+ mif_info("send 'a'\n");
+
+ skb = alloc_skb(16, GFP_ATOMIC);
+ if (unlikely(!skb))
+ return -ENOMEM;
+ memcpy(skb_put(skb, 1), data, 1);
+
+ skbpriv(skb)->iod = iod;
+ skbpriv(skb)->ld = &usb_ld->ld;
+ err = usb_tx_urb_with_skb(usb_ld, skb, pipe_data);
+ if (err < 0) {
+ mif_err("usb_tx_urb fail\n");
+ goto exit;
+ }
+ usb_ld->link_pm_data->hub_handshake_done = true;
+ usb_ld->if_usb_initstates = INIT_IPC_START_DONE;
+exit:
+ return err;
+}
+
+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:
+ err = start_ipc(ld, iod);
+ break;
+
+ 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)
+{
+ 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;
+ pr_buffer("rx", (char *)urb->transfer_buffer,
+ (size_t)urb->actual_length, 16);
+ break;
+ case IF_USB_RAW_EP:
+ iod_format = IPC_MULTI_RAW;
+ break;
+ case IF_USB_RFS_EP:
+ iod_format = IPC_RFS;
+ break;
+ default:
+ break;
+ }
+
+ /* 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;
+
+ iod = link_get_iod_with_format(&usb_ld->ld, iod_format);
+ if (iod) {
+ ret = iod->recv(iod,
+ &usb_ld->ld,
+ (char *)urb->transfer_buffer,
+ urb->actual_length);
+ if (ret < 0)
+ mif_err("io device recv error :%d\n", ret);
+ }
+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;
+ size_t tx_size;
+
+ if (iod->format == IPC_RAW)
+ txq = &ld->sk_raw_tx_q;
+ else
+ txq = &ld->sk_fmt_tx_q;
+
+ /* store the tx size before run the tx_delayed_work*/
+ tx_size = skb->len;
+
+ /* en queue skb data */
+ skb_queue_tail(txq, skb);
+
+ queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0);
+
+ return tx_size;
+}
+
+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 && ret != -EAGAIN)
+ mif_debug("pm_runtime_put_autosuspend failed: %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 already disconnected before run this workqueue */
+ if (!udev || !(&udev->dev) || !usb_ld->if_usb_connected)
+ return;
+
+ /* disconnect udev's parent if usb hub used */
+ if (has_hub(usb_ld))
+ udev = udev->parent;
+
+ pm_runtime_get_sync(&udev->dev);
+ if (udev->state != USB_STATE_NOTATTACHED) {
+ usb_force_disconnect(udev);
+ mif_info("force disconnect\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;
+
+ iod = link_get_iod_with_format(&usb_ld->ld, IPC_FMT);
+ if (iod)
+ iod->modem_state_changed(iod, state);
+}
+
+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 = skbpriv(skb)->iod;
+ switch (iod->format) {
+ case IPC_BOOT:
+ case IPC_RAMDUMP:
+ case IPC_FMT:
+ /* 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);
+ 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);
+ return;
+ }
+ }
+ }
+}
+
+static int if_usb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct usb_link_device *usb_ld = usb_get_intfdata(intf);
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+ 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 (pm_data->freq_unlock)
+ pm_data->freq_unlock(&usb_ld->usbdev->dev);
+
+ 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);
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+ struct usb_device *udev = usb_ld->usbdev;
+
+ /* if already disconnected before run this workqueue */
+ if (!udev || !(&udev->dev) || !usb_ld->if_usb_connected)
+ return;
+
+ /* lock cpu/bus frequency when L2->L0 */
+ if (pm_data->freq_lock)
+ pm_data->freq_lock(&udev->dev);
+}
+
+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);
+ 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);
+
+ /* 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);
+ ret = pm_runtime_put_autosuspend(
+ &usb_ld->usbdev->dev);
+ if (ret < 0 && ret != -EAGAIN)
+ mif_debug("pm_runtime_put_autosuspend "
+ "failed: %d\n", ret);
+ }
+ }
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 1);
+ udelay(100);
+ SET_SLAVE_WAKEUP(usb_ld->pdata, 0);
+
+ /* 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;
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+ 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);
+
+ 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);
+ usb_put_dev(usbdev);
+ usb_ld->usbdev = NULL;
+ pm_runtime_forbid(pm_data->root_hub);
+ }
+}
+
+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, *ehci_dev, *root_hub;
+ 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 = pm_data->autosuspend_delay_ms ?:
+ DEFAULT_AUTOSUSPEND_DELAY_MS;
+ pm_runtime_set_autosuspend_delay(&usbdev->dev, delay);
+ dev = &usbdev->dev;
+ if (dev->parent) {
+ dev_dbg(&usbdev->dev, "if_usb Runtime PM Start!!\n");
+ usb_enable_autosuspend(usb_ld->usbdev);
+ /* s5p-ehci runtime pm allow - usb phy suspend mode */
+ root_hub = &usbdev->bus->root_hub->dev;
+ ehci_dev = root_hub->parent;
+ mif_debug("ehci device = %s, %s\n",
+ dev_driver_string(ehci_dev),
+ dev_name(ehci_dev));
+ pm_runtime_allow(ehci_dev);
+
+ if (!pm_data->autosuspend)
+ pm_runtime_forbid(dev);
+
+ if (has_hub(usb_ld))
+ link_pm_preactive(pm_data);
+
+ pm_data->root_hub = root_hub;
+ }
+
+ 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);
+
+ usb_ld->if_usb_connected = 1;
+ /*USB3503*/
+ mif_debug("hub active complete\n");
+
+ usb_change_modem_state(usb_ld, STATE_ONLINE);
+ } else {
+ usb_change_modem_state(usb_ld, STATE_LOADER_DONE);
+ }
+
+ /* check dynamic switching gpio received
+ * before usb enumeration is completed
+ */
+ if (ld->mc->need_switch_to_usb) {
+ ld->mc->need_switch_to_usb = false;
+ rawdevs_set_tx_link(ld->msd, LINKDEV_USB);
+ }
+
+ 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))
+ return IRQ_HANDLED;
+
+ 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->ld.list);
+ 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->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 = 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);
+}
+
+bool usb_is_enumerated(struct modem_shared *msd)
+{
+ struct link_device *ld = find_linkdev(msd, LINKDEV_USB);
+ if (ld)
+ return to_usb_link_device(ld)->usbdev != NULL;
+ else
+ return false;
+}
+
+
+/* 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/modem_link_device_usb.h b/drivers/misc/modem_if/modem_link_device_usb.h
new file mode 100644
index 00000000000..8233fd11548
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_usb.h
@@ -0,0 +1,132 @@
+/*
+ * 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 DEFAULT_AUTOSUSPEND_DELAY_MS 500
+#define HOST_WAKEUP_TIMEOUT_JIFFIES msecs_to_jiffies(500)
+#define WAIT_ENUMURATION_TIMEOUT_JIFFIES msecs_to_jiffies(15000)
+#define MAX_RETRY 30
+
+#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)
+
+enum RESUME_STATUS {
+ CP_INITIATED_RESUME,
+ AP_INITIATED_RESUME,
+};
+
+enum IPC_INIT_STATUS {
+ INIT_IPC_NOT_READY,
+ INIT_IPC_START_DONE, /* send 'a' done */
+};
+
+enum hub_status {
+ HUB_STATE_OFF, /* usb3503 0ff*/
+ HUB_STATE_RESUMMING, /* usb3503 on, but enummerattion was not yet*/
+ HUB_STATE_ACTIVE, /* hub and CMC221 enumerate */
+};
+
+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 if_usb_initstates;
+ 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;
+};
+/* 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);
+bool usb_is_enumerated(struct modem_shared *msd);
+
+#endif
diff --git a/drivers/misc/modem_if/modem_link_pm_usb.c b/drivers/misc/modem_if/modem_link_pm_usb.c
new file mode 100644
index 00000000000..1b2614eb58d
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_pm_usb.c
@@ -0,0 +1,417 @@
+/*
+ * 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.
+ *
+ */
+
+#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"
+
+static inline void start_hub_work(struct link_pm_data *pm_data, int delay)
+{
+ if (pm_data->hub_work_running == false) {
+ pm_data->hub_work_running = true;
+ wake_lock(&pm_data->hub_lock);
+ mif_debug("link_pm_hub_work is started\n");
+ }
+
+ schedule_delayed_work(&pm_data->link_pm_hub, msecs_to_jiffies(delay));
+}
+
+static inline void end_hub_work(struct link_pm_data *pm_data)
+{
+ wake_unlock(&pm_data->hub_lock);
+ pm_data->hub_work_running = false;
+ mif_debug("link_pm_hub_work is done\n");
+}
+
+bool link_pm_is_connected(struct usb_link_device *usb_ld)
+{
+ if (has_hub(usb_ld)) {
+ struct link_pm_data *pm_data = usb_ld->link_pm_data;
+ if (pm_data->hub_init_lock)
+ return false;
+
+ if (pm_data->hub_status == HUB_STATE_OFF) {
+ if (pm_data->hub_work_running == false)
+ start_hub_work(pm_data, 0);
+ return false;
+ }
+ }
+
+ if (!usb_ld->if_usb_connected) {
+ mif_err("mif: if not connected\n");
+ return false;
+ }
+
+ return true;
+}
+
+void link_pm_preactive(struct link_pm_data *pm_data)
+{
+ if (pm_data->root_hub) {
+ mif_info("pre-active\n");
+ pm_data->hub_on_retry_cnt = 0;
+ complete(&pm_data->hub_active);
+ pm_runtime_put_sync(pm_data->root_hub);
+ }
+
+ pm_data->hub_status = HUB_STATE_ACTIVE;
+}
+
+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) {
+ end_hub_work(pm_data);
+ return;
+ }
+
+ if (!pm_data->port_enable) {
+ mif_err("mif: hub power func not assinged\n");
+ end_hub_work(pm_data);
+ return;
+ }
+
+ /* If kernel if suspend, wait the ehci resume */
+ if (pm_data->dpm_suspending) {
+ mif_info("dpm_suspending\n");
+ start_hub_work(pm_data, 500);
+ return;
+ }
+
+ 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);
+ end_hub_work(pm_data);
+ } else {
+ /* resume root hub */
+ start_hub_work(pm_data, 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);
+ end_hub_work(pm_data);
+ } else {
+ mif_info("hub resumming: %d\n",
+ pm_data->hub_on_retry_cnt);
+ start_hub_work(pm_data, 200);
+ }
+ break;
+ }
+exit:
+ return;
+}
+
+static int link_pm_hub_standby(void *args)
+{
+ struct link_pm_data *pm_data = args;
+ struct usb_link_device *usb_ld = pm_data->usb_ld;
+ int err = 0;
+
+ if (!pm_data->port_enable) {
+ mif_err("port 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;
+
+ /* this function is atomic.
+ * make force disconnect in workqueue..
+ */
+ if (pm_data->usb_ld->if_usb_connected)
+ schedule_work(&usb_ld->disconnect_work);
+
+ 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 link_pm_data *pm_data = file->private_data;
+ struct usb_link_device *usb_ld = pm_data->usb_ld;
+
+ 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 usb_ld->if_usb_connected;
+ case IOCTL_LINK_PORT_ON:
+ /* 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_get_sync(pm_data->root_hub);
+ 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:
+ 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;
+ default:
+ break;
+ }
+exit:
+ return err;
+}
+
+static ssize_t show_autosuspend(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ char *p = buf;
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct link_pm_data *pm_data = container_of(miscdev,
+ struct link_pm_data, miscdev);
+ struct usb_link_device *usb_ld = pm_data->usb_ld;
+
+ p += sprintf(buf, "%s\n", pm_data->autosuspend ? "on" : "off");
+
+ return p - buf;
+}
+
+static ssize_t store_autosuspend(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct link_pm_data *pm_data = container_of(miscdev,
+ struct link_pm_data, miscdev);
+ struct usb_link_device *usb_ld = pm_data->usb_ld;
+ struct task_struct *task = get_current();
+ char taskname[TASK_COMM_LEN];
+
+ mif_info("autosuspend: %s: %s(%d)'\n",
+ buf, get_task_comm(taskname, task), task->pid);
+
+ if (!strncmp(buf, "on", 2)) {
+ pm_data->autosuspend = true;
+ if (usb_ld->usbdev)
+ pm_runtime_allow(&usb_ld->usbdev->dev);
+ } else if (!strncmp(buf, "off", 3)) {
+ pm_data->autosuspend = false;
+ if (usb_ld->usbdev)
+ pm_runtime_forbid(&usb_ld->usbdev->dev);
+ }
+
+ return count;
+}
+
+static struct device_attribute attr_autosuspend =
+ __ATTR(autosuspend, S_IRUGO | S_IWUSR,
+ show_autosuspend, store_autosuspend);
+
+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);
+ struct usb_link_device *usb_ld = pm_data->usb_ld;
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ pm_data->dpm_suspending = true;
+ if (has_hub(usb_ld))
+ 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->freq_lock = pm_pdata->freq_lock;
+ pm_data->freq_unlock = pm_pdata->freq_unlock;
+ pm_data->autosuspend_delay_ms = pm_pdata->autosuspend_delay_ms;
+ pm_data->autosuspend = true;
+
+ pm_data->usb_ld = usb_ld;
+ 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;
+ }
+
+ err = device_create_file(pm_data->miscdev.this_device,
+ &attr_autosuspend);
+ if (err) {
+ mif_err("fail to create file: autosuspend: %d\n", err);
+ goto err_create_file;
+ }
+
+ 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;
+
+ if (has_hub(usb_ld)) {
+ init_completion(&pm_data->hub_active);
+ pm_data->hub_status = HUB_STATE_OFF;
+ pm_data->hub_handshake_done = 0;
+ pm_data->root_hub = NULL;
+
+ pm_pdata->hub_standby = link_pm_hub_standby;
+ pm_pdata->hub_pm_data = pm_data;
+
+ 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);
+ pm_data->hub_work_running = false;
+ }
+
+ pm_data->pm_notifier.notifier_call = link_pm_notifier_event;
+ register_pm_notifier(&pm_data->pm_notifier);
+
+ return 0;
+
+err_request_irq:
+err_create_file:
+ misc_deregister(&pm_data->miscdev);
+err_misc_register:
+ kfree(pm_data);
+ return err;
+}
diff --git a/drivers/misc/modem_if/modem_link_pm_usb.h b/drivers/misc/modem_if/modem_link_pm_usb.h
new file mode 100644
index 00000000000..d26af761114
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_pm_usb.h
@@ -0,0 +1,65 @@
+/*
+ * 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.h>
+#include "modem_prj.h"
+#include "modem_link_device_usb.h"
+
+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;
+ bool hub_work_running;
+ int hub_on_retry_cnt;
+ struct device *root_hub;
+
+ struct notifier_block pm_notifier;
+ bool dpm_suspending;
+
+ int (*port_enable)(int, int);
+
+ int (*freq_lock)(struct device *dev);
+ int (*freq_unlock)(struct device *dev);
+
+ int autosuspend_delay_ms; /* if zero, the default value is used */
+ bool autosuspend;
+};
+
+bool link_pm_set_active(struct usb_link_device *usb_ld);
+bool link_pm_is_connected(struct usb_link_device *usb_ld);
+void link_pm_preactive(struct link_pm_data *pm_data);
+int link_pm_init(struct usb_link_device *usb_ld, void *data);
+
+#endif
diff --git a/drivers/misc/modem_if/modem_modemctl_device_cbp71.c b/drivers/misc/modem_if/modem_modemctl_device_cbp71.c
new file mode 100644
index 00000000000..28f2ce7736f
--- /dev/null
+++ b/drivers/misc/modem_if/modem_modemctl_device_cbp71.c
@@ -0,0 +1,233 @@
+/* /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.h>
+#include "modem_prj.h"
+#include "modem_link_device_dpram.h"
+
+#define PIF_TIMEOUT (180 * HZ)
+#define DPRAM_INIT_TIMEOUT (15 * HZ)
+
+static int cbp71_on(struct modem_ctl *mc)
+{
+ int RetVal = 0;
+ int dpram_init_RetVal = 0;
+ struct link_device *ld = get_current_link(mc->iod);
+ struct dpram_link_device *dpram_ld = to_dpram_link_device(ld);
+
+ mif_info("cbp71_on()\n");
+
+ if (!mc->gpio_cp_off || !mc->gpio_cp_reset) {
+ mif_err("no gpio data\n");
+ return -ENXIO;
+ }
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(600);
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ msleep(100);
+ gpio_set_value(mc->gpio_cp_off, 0);
+ msleep(300);
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ 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 */
+ mif_debug("power control waiting for INT_MASK_CMD_PIF_INIT_DONE\n");
+
+ /* 1HZ = 1 clock tick, 100 default */
+ dpram_ld->clear_interrupt(dpram_ld);
+
+ dpram_init_RetVal =
+ wait_event_interruptible_timeout(
+ dpram_ld->dpram_init_cmd_wait_q,
+ dpram_ld->dpram_init_cmd_wait_condition,
+ DPRAM_INIT_TIMEOUT);
+
+ if (!dpram_init_RetVal) {
+ /*RetVal will be 0 on timeout, non zero if interrupted */
+ mif_err("INIT_START cmd was not arrived.\n");
+ mif_err("init_cmd_wait_condition is 0 and wait timeout happend\n");
+ return -ENXIO;
+ }
+
+ RetVal = wait_event_interruptible_timeout(
+ dpram_ld->modem_pif_init_done_wait_q,
+ dpram_ld->modem_pif_init_wait_condition,
+ PIF_TIMEOUT);
+
+ if (!RetVal) {
+ /*RetVal will be 0 on timeout, non zero if interrupted */
+ mif_err("PIF init failed\n");
+ mif_err("pif_init_wait_condition is 0 and wait timeout happend\n");
+ return -ENXIO;
+ }
+
+ mif_debug("complete cbp71_on\n");
+
+ mc->iod->modem_state_changed(mc->iod, STATE_ONLINE);
+
+ return 0;
+}
+
+static int cbp71_off(struct modem_ctl *mc)
+{
+ mif_debug("cbp71_off()\n");
+
+ if (!mc->gpio_cp_off || !mc->gpio_cp_reset) {
+ mif_err("no gpio data\n");
+ return -ENXIO;
+ }
+
+ mif_err("Phone power Off. - do nothing\n");
+
+ mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE);
+
+ return 0;
+}
+
+static int cbp71_reset(struct modem_ctl *mc)
+{
+ int ret = 0;
+
+ mif_debug("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)
+{
+ mif_debug("cbp71_boot_on()\n");
+
+ if (!mc->gpio_cp_reset) {
+ mif_err("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)
+{
+ mif_debug("cbp71_boot_off()\n");
+ return 0;
+}
+
+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) {
+ mif_err("no gpio data\n");
+ return IRQ_HANDLED;
+ }
+
+ phone_reset = gpio_get_value(mc->gpio_cp_reset);
+ phone_active_value = gpio_get_value(mc->gpio_phone_active);
+
+ if (phone_reset && phone_active_value)
+ phone_state = STATE_ONLINE;
+ else if (phone_reset && !phone_active_value)
+ phone_state = STATE_CRASH_EXIT;
+ 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);
+
+ mif_info("phone_active_irq_handler : phone_state=%d\n", phone_state);
+
+ return IRQ_HANDLED;
+}
+
+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;
+ 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;
+
+ pdev = to_platform_device(mc->dev);
+ mc->irq_phone_active = platform_get_irq(pdev, 0);
+
+ cbp71_get_ops(mc);
+
+ /*TODO: check*/
+ ret = request_irq(mc->irq_phone_active, phone_active_irq_handler,
+ IRQF_TRIGGER_HIGH, "phone_active", mc);
+ if (ret) {
+ mif_err("failed to irq_phone_active request_irq: %d\n"
+ , ret);
+ return ret;
+ }
+
+ ret = enable_irq_wake(mc->irq_phone_active);
+ if (ret)
+ mif_err("failed to enable_irq_wake:%d\n", ret);
+
+ return ret;
+}
diff --git a/drivers/misc/modem_if/modem_modemctl_device_cbp72.c b/drivers/misc/modem_if/modem_modemctl_device_cbp72.c
new file mode 100644
index 00000000000..2617be8abaa
--- /dev/null
+++ b/drivers/misc/modem_if/modem_modemctl_device_cbp72.c
@@ -0,0 +1,273 @@
+/* /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_device.h>
+
+#include <linux/platform_data/modem.h>
+#include "modem_prj.h"
+#include "modem_link_device_dpram.h"
+
+#define PIF_TIMEOUT (180 * HZ)
+#define DPRAM_INIT_TIMEOUT (30 * HZ)
+
+
+static irqreturn_t phone_active_handler(int irq, void *arg)
+{
+ struct modem_ctl *mc = (struct modem_ctl *)arg;
+ int phone_reset = gpio_get_value(mc->gpio_cp_reset);
+ int phone_active = gpio_get_value(mc->gpio_phone_active);
+ int phone_state = mc->phone_state;
+
+ mif_info("state = %d, phone_reset = %d, phone_active = %d\n",
+ phone_state, phone_reset, phone_active);
+
+ if (phone_reset && phone_active) {
+ if (mc->phone_state == STATE_BOOTING) {
+ phone_state = STATE_ONLINE;
+ mc->bootd->modem_state_changed(mc->bootd, phone_state);
+ }
+ } else if (phone_reset && !phone_active) {
+ if (mc->phone_state == STATE_ONLINE) {
+ phone_state = STATE_CRASH_EXIT;
+ mc->bootd->modem_state_changed(mc->bootd, phone_state);
+ }
+ } else {
+ phone_state = STATE_OFFLINE;
+ if (mc->bootd && mc->bootd->modem_state_changed)
+ mc->bootd->modem_state_changed(mc->bootd, phone_state);
+ }
+
+ if (phone_active)
+ 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);
+
+ mif_info("phone_state = %d\n", phone_state);
+
+ return IRQ_HANDLED;
+}
+
+static int cbp72_on(struct modem_ctl *mc)
+{
+ mif_info("start!!!\n");
+
+ /* prevent sleep during bootloader downloading */
+ if (!wake_lock_active(&mc->mc_wake_lock))
+ wake_lock(&mc->mc_wake_lock);
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ if (mc->gpio_cp_off)
+ gpio_set_value(mc->gpio_cp_off, 1);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+
+ msleep(500);
+
+ gpio_set_value(mc->gpio_cp_on, 1);
+ if (mc->gpio_cp_off)
+ gpio_set_value(mc->gpio_cp_off, 0);
+
+ msleep(100);
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+
+ msleep(300);
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ mc->bootd->modem_state_changed(mc->bootd, STATE_BOOTING);
+
+ mif_info("complete!!!\n");
+
+ return 0;
+}
+
+static int cbp72_off(struct modem_ctl *mc)
+{
+ mif_info("cbp72_off()\n");
+
+ if (!mc->gpio_cp_off || !mc->gpio_cp_reset) {
+ mif_err("no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_cp_on, 0);
+ gpio_set_value(mc->gpio_cp_off, 1);
+
+ mc->bootd->modem_state_changed(mc->bootd, STATE_OFFLINE);
+
+ return 0;
+}
+
+static int cbp72_reset(struct modem_ctl *mc)
+{
+ int ret = 0;
+
+ mif_debug("cbp72_reset()\n");
+
+ ret = cbp72_off(mc);
+ if (ret)
+ return -ENXIO;
+
+ msleep(100);
+
+ ret = cbp72_on(mc);
+ if (ret)
+ return -ENXIO;
+
+ return 0;
+}
+
+static int cbp72_boot_on(struct modem_ctl *mc)
+{
+ mif_info("\n");
+
+ if (!mc->gpio_cp_reset) {
+ mif_err("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->bootd->modem_state_changed(mc->bootd, STATE_BOOTING);
+
+ return 0;
+}
+
+static int cbp72_boot_off(struct modem_ctl *mc)
+{
+ int ret;
+ struct link_device *ld = get_current_link(mc->bootd);
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ mif_debug("\n");
+ /* Wait here until the PHONE is up.
+ * Waiting as the this called from IOCTL->UM thread */
+ mif_info("Waiting for INT_CMD_PHONE_START\n");
+ ret = wait_for_completion_interruptible_timeout(
+ &dpld->dpram_init_cmd, DPRAM_INIT_TIMEOUT);
+ if (!ret) {
+ /* ret == 0 on timeout, ret < 0 if interrupted */
+ mif_err("Timeout!!! (PHONE_START was not arrived.)\n");
+ return -ENXIO;
+ }
+
+ mif_info("Waiting for INT_CMD_PIF_INIT_DONE\n");
+ ret = wait_for_completion_interruptible_timeout(
+ &dpld->modem_pif_init_done, PIF_TIMEOUT);
+ if (!ret) {
+ mif_err("Timeout!!! (PIF_INIT_DONE was not arrived.)\n");
+ return -ENXIO;
+ }
+ mc->bootd->modem_state_changed(mc->bootd, STATE_ONLINE);
+
+ wake_unlock(&mc->mc_wake_lock);
+
+ return 0;
+}
+
+static int cbp72_force_crash_exit(struct modem_ctl *mc)
+{
+ struct link_device *ld = get_current_link(mc->bootd);
+
+ mif_err("device = %s\n", mc->bootd->name);
+
+ /* Make DUMP start */
+ ld->force_dump(ld, mc->bootd);
+
+ msleep_interruptible(1000);
+
+ mc->bootd->modem_state_changed(mc->bootd, STATE_CRASH_EXIT);
+
+ return 0;
+}
+
+static void cbp72_get_ops(struct modem_ctl *mc)
+{
+ mc->ops.modem_on = cbp72_on;
+ mc->ops.modem_off = cbp72_off;
+ mc->ops.modem_reset = cbp72_reset;
+ mc->ops.modem_boot_on = cbp72_boot_on;
+ mc->ops.modem_boot_off = cbp72_boot_off;
+ mc->ops.modem_force_crash_exit = cbp72_force_crash_exit;
+}
+
+int cbp72_init_modemctl_device(struct modem_ctl *mc, struct modem_data *pdata)
+{
+ int ret = 0;
+ int irq = 0;
+ unsigned long flag = 0;
+ struct platform_device *pdev = NULL;
+
+ mc->gpio_cp_on = pdata->gpio_cp_on;
+ mc->gpio_cp_off = pdata->gpio_cp_off;
+ 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;
+
+ if (!mc->gpio_cp_on || !mc->gpio_cp_reset || !mc->gpio_phone_active) {
+ mif_err("no GPIO data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ if (mc->gpio_cp_off)
+ gpio_set_value(mc->gpio_cp_off, 1);
+ gpio_set_value(mc->gpio_cp_on, 0);
+
+ cbp72_get_ops(mc);
+
+ pdev = to_platform_device(mc->dev);
+ mc->irq_phone_active = platform_get_irq_byname(pdev, "cp_active_irq");
+ if (!mc->irq_phone_active) {
+ mif_err("get irq fail\n");
+ return -1;
+ }
+
+ irq = mc->irq_phone_active;
+ mif_info("PHONE_ACTIVE IRQ# = %d\n", irq);
+
+ flag = IRQF_TRIGGER_HIGH;
+ ret = request_irq(irq, phone_active_handler, flag, "cbp_active", mc);
+ if (ret) {
+ mif_err("request_irq fail (%d)\n", ret);
+ return ret;
+ }
+
+ wake_lock_init(&mc->mc_wake_lock, WAKE_LOCK_SUSPEND, "cbp72_wake_lock");
+
+ ret = enable_irq_wake(irq);
+ if (ret)
+ mif_err("enable_irq_wake fail (%d)\n", ret);
+
+ return 0;
+}
+
diff --git a/drivers/misc/modem_if/modem_modemctl_device_cmc221.c b/drivers/misc/modem_if/modem_modemctl_device_cmc221.c
new file mode 100644
index 00000000000..eddc91ae15b
--- /dev/null
+++ b/drivers/misc/modem_if/modem_modemctl_device_cmc221.c
@@ -0,0 +1,317 @@
+/* /linux/drivers/misc/modem_if/modem_modemctl_device_cmc221.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.h>
+#include "modem_prj.h"
+#include "modem_link_device_usb.h"
+#include "modem_link_device_dpram.h"
+#include "modem_utils.h"
+
+#define PIF_TIMEOUT (180 * HZ)
+#define DPRAM_INIT_TIMEOUT (30 * HZ)
+
+static void mc_state_fsm(struct modem_ctl *mc)
+{
+ struct link_device *ld = get_current_link(mc->iod);
+ int cp_on = gpio_get_value(mc->gpio_cp_on);
+ int cp_reset = gpio_get_value(mc->gpio_cp_reset);
+ int cp_active = gpio_get_value(mc->gpio_phone_active);
+ int old_state = mc->phone_state;
+ int new_state = mc->phone_state;
+
+ mif_err("%s: old_state:%d cp_on:%d cp_reset:%d cp_active:%d\n",
+ mc->name, old_state, cp_on, cp_reset, cp_active);
+
+ if (!cp_active) {
+ if (!cp_on) {
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ new_state = STATE_OFFLINE;
+ ld->mode = LINK_MODE_OFFLINE;
+ mif_err("%s: new_state = PHONE_PWR_OFF\n", mc->name);
+ } else if (old_state == STATE_ONLINE) {
+ new_state = STATE_CRASH_EXIT;
+ mif_err("%s: new_state = CRASH_EXIT\n", mc->name);
+ } else {
+ mif_err("%s: Don't care!!!\n", mc->name);
+ }
+ }
+
+ if (old_state != new_state) {
+ mc->bootd->modem_state_changed(mc->bootd, new_state);
+ mc->iod->modem_state_changed(mc->iod, new_state);
+ }
+}
+
+static irqreturn_t phone_active_handler(int irq, void *arg)
+{
+ struct modem_ctl *mc = (struct modem_ctl *)arg;
+ int cp_reset = gpio_get_value(mc->gpio_cp_reset);
+
+ if (cp_reset)
+ mc_state_fsm(mc);
+
+ return IRQ_HANDLED;
+}
+
+/* TX dynamic switching between DPRAM and USB in one modem */
+static irqreturn_t dynamic_switching_handler(int irq, void *arg)
+{
+ struct modem_ctl *mc = (struct modem_ctl *)arg;
+ int txpath = gpio_get_value(mc->gpio_dynamic_switching);
+ bool enumerated = usb_is_enumerated(mc->msd);
+
+ mif_err("txpath=%d, enumeration=%d\n", txpath, enumerated);
+
+ /* do not switch to USB, when USB is not enumerated. */
+ if (!enumerated && txpath) {
+ mc->need_switch_to_usb = true;
+ return IRQ_HANDLED;
+ }
+
+ mc->need_switch_to_usb = false;
+ rawdevs_set_tx_link(mc->msd, txpath ? LINKDEV_USB : LINKDEV_DPRAM);
+
+ return IRQ_HANDLED;
+}
+
+static int cmc221_on(struct modem_ctl *mc)
+{
+ struct link_device *ld = get_current_link(mc->iod);
+
+ if (!wake_lock_active(&mc->mc_wake_lock))
+ wake_lock(&mc->mc_wake_lock);
+ set_sromc_access(true);
+
+ mc->phone_state = STATE_OFFLINE;
+ ld->mode = LINK_MODE_OFFLINE;
+
+ mif_err("%s\n", mc->name);
+
+ disable_irq_nosync(mc->irq_phone_active);
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ msleep(100);
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(500);
+
+ gpio_set_value(mc->gpio_cp_on, 1);
+ msleep(100);
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+
+ return 0;
+}
+
+static int cmc221_off(struct modem_ctl *mc)
+{
+ int cp_on = gpio_get_value(mc->gpio_cp_on);
+
+ mif_err("%s\n", mc->name);
+
+ if (mc->phone_state == STATE_OFFLINE || cp_on == 0)
+ return 0;
+
+ if (!wake_lock_active(&mc->mc_wake_lock))
+ wake_lock(&mc->mc_wake_lock);
+ set_sromc_access(true);
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+
+ return 0;
+}
+
+static int cmc221_force_crash_exit(struct modem_ctl *mc)
+{
+ struct link_device *ld = get_current_link(mc->bootd);
+
+ mif_err("%s\n", mc->name);
+
+ /* Make DUMP start */
+ ld->force_dump(ld, mc->bootd);
+
+ return 0;
+}
+
+static int cmc221_dump_reset(struct modem_ctl *mc)
+{
+ mif_err("%s\n", mc->name);
+
+ if (!wake_lock_active(&mc->mc_wake_lock))
+ wake_lock(&mc->mc_wake_lock);
+ set_sromc_access(true);
+
+ gpio_set_value(mc->gpio_host_active, 0);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+
+ udelay(200);
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+
+ msleep(300);
+
+ return 0;
+}
+
+static int cmc221_reset(struct modem_ctl *mc)
+{
+ mif_err("%s\n", mc->name);
+
+ if (cmc221_off(mc))
+ return -ENXIO;
+
+ msleep(100);
+
+ if (cmc221_on(mc))
+ return -ENXIO;
+
+ return 0;
+}
+
+static int cmc221_boot_on(struct modem_ctl *mc)
+{
+ mif_err("%s\n", mc->name);
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ mc->bootd->modem_state_changed(mc->bootd, STATE_BOOTING);
+ mc->iod->modem_state_changed(mc->iod, STATE_BOOTING);
+
+ return 0;
+}
+
+static int cmc221_boot_off(struct modem_ctl *mc)
+{
+ int ret;
+ struct link_device *ld = get_current_link(mc->bootd);
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+
+ mif_err("%s\n", mc->name);
+
+ ret = wait_for_completion_interruptible_timeout(&dpld->dpram_init_cmd,
+ DPRAM_INIT_TIMEOUT);
+ if (!ret) {
+ /* ret == 0 on timeout, ret < 0 if interrupted */
+ mif_err("%s: ERR! timeout (CP_START not arrived)\n", mc->name);
+ return -ENXIO;
+ }
+
+ enable_irq(mc->irq_phone_active);
+
+ return 0;
+}
+
+static int cmc221_boot_done(struct modem_ctl *mc)
+{
+ mif_err("%s\n", mc->name);
+
+ set_sromc_access(false);
+ if (wake_lock_active(&mc->mc_wake_lock))
+ wake_unlock(&mc->mc_wake_lock);
+
+ return 0;
+}
+
+static void cmc221_get_ops(struct modem_ctl *mc)
+{
+ mc->ops.modem_on = cmc221_on;
+ mc->ops.modem_off = cmc221_off;
+ mc->ops.modem_reset = cmc221_reset;
+ mc->ops.modem_boot_on = cmc221_boot_on;
+ mc->ops.modem_boot_off = cmc221_boot_off;
+ mc->ops.modem_boot_done = cmc221_boot_done;
+ mc->ops.modem_force_crash_exit = cmc221_force_crash_exit;
+ mc->ops.modem_dump_reset = cmc221_dump_reset;
+}
+
+int cmc221_init_modemctl_device(struct modem_ctl *mc, struct modem_data *pdata)
+{
+ int ret = 0;
+ int irq = 0;
+ unsigned long flag = 0;
+ struct platform_device *pdev = NULL;
+
+ mc->gpio_cp_on = pdata->gpio_cp_on;
+ mc->gpio_cp_reset = pdata->gpio_cp_reset;
+ mc->gpio_phone_active = pdata->gpio_phone_active;
+ mc->gpio_pda_active = pdata->gpio_pda_active;
+#if 0 /*TODO: check the GPIO map*/
+ mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int;
+ mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel;
+ mc->gpio_slave_wakeup = pdata->gpio_slave_wakeup;
+ mc->gpio_host_active = pdata->gpio_host_active;
+ mc->gpio_host_wakeup = pdata->gpio_host_wakeup;
+#endif
+ mc->gpio_dynamic_switching = pdata->gpio_dynamic_switching;
+ mc->need_switch_to_usb = false;
+
+ if (!mc->gpio_cp_on || !mc->gpio_cp_reset || !mc->gpio_phone_active) {
+ mif_err("%s: ERR! no GPIO data\n", mc->name);
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_cp_on, 0);
+
+ cmc221_get_ops(mc);
+ dev_set_drvdata(mc->dev, mc);
+
+ pdev = to_platform_device(mc->dev);
+ mc->irq_phone_active = platform_get_irq_byname(pdev, STR_CP_ACTIVE_IRQ);
+ if (!mc->irq_phone_active) {
+ mif_err("%s: ERR! get cp_active_irq fail\n", mc->name);
+ return -1;
+ }
+ mif_err("%s: PHONE_ACTIVE IRQ# = %d\n", mc->name, mc->irq_phone_active);
+
+ wake_lock_init(&mc->mc_wake_lock, WAKE_LOCK_SUSPEND, "cmc_wake_lock");
+
+ flag = IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND;
+ irq = mc->irq_phone_active;
+ ret = request_irq(irq, phone_active_handler, flag, "cmc_active", mc);
+ if (ret) {
+ mif_err("%s: ERR! request_irq(#%d) fail (err %d)\n",
+ mc->name, irq, ret);
+ return ret;
+ }
+ ret = enable_irq_wake(irq);
+ if (ret) {
+ mif_err("%s: WARNING! enable_irq_wake(#%d) fail (err %d)\n",
+ mc->name, irq, ret);
+ }
+
+ flag = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND;
+ if (mc->gpio_dynamic_switching) {
+ irq = gpio_to_irq(mc->gpio_dynamic_switching);
+ mif_err("%s: DYNAMIC_SWITCH IRQ# = %d\n", mc->name, irq);
+ ret = request_irq(irq, dynamic_switching_handler, flag,
+ "dynamic_switching", mc);
+ if (ret) {
+ mif_err("%s: ERR! request_irq(#%d) fail (err %d)\n",
+ mc->name, irq, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
diff --git a/drivers/misc/modem_if/modem_modemctl_device_esc6270.c b/drivers/misc/modem_if/modem_modemctl_device_esc6270.c
new file mode 100644
index 00000000000..f35e4ccd95c
--- /dev/null
+++ b/drivers/misc/modem_if/modem_modemctl_device_esc6270.c
@@ -0,0 +1,348 @@
+/* /linux/drivers/misc/modem_if/modem_modemctl_device_esc6270.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_device.h>
+
+#include <linux/platform_data/modem.h>
+#include "modem_prj.h"
+#include <linux/regulator/consumer.h>
+
+#include <plat/gpio-cfg.h>
+
+#if defined(CONFIG_LINK_DEVICE_DPRAM)
+#include "modem_link_device_dpram.h"
+#elif defined(CONFIG_LINK_DEVICE_PLD)
+#include "modem_link_device_pld.h"
+#endif
+
+#if defined(CONFIG_LINK_DEVICE_DPRAM) || defined(CONFIG_LINK_DEVICE_PLD)
+#include <linux/mfd/max77693.h>
+
+#define PIF_TIMEOUT (180 * HZ)
+#define DPRAM_INIT_TIMEOUT (30 * HZ)
+
+static int esc6270_on(struct modem_ctl *mc)
+{
+ int ret;
+ struct link_device *ld = get_current_link(mc->iod);
+
+ pr_info("[MODEM_IF:ESC] <%s> start!!!\n", __func__);
+
+ if (!mc->gpio_cp_reset) {
+ pr_err("[MODEM_IF:ESC] no gpio data\n");
+ return -ENXIO;
+ }
+
+ if (mc->gpio_reset_req_n)
+ gpio_set_value(mc->gpio_reset_req_n, 1);
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ msleep(30);
+
+ gpio_set_value(mc->gpio_cp_on, 1);
+ msleep(500);
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ msleep(500);
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ mc->iod->modem_state_changed(mc->iod, STATE_BOOTING);
+ ld->mode = LINK_MODE_BOOT;
+
+ return 0;
+}
+
+static int esc6270_off(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF:ESC] esc6270_off()\n");
+
+#if 1
+ if (!mc->gpio_cp_reset) {
+ pr_err("[MODEM_IF:ESC] no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_cp_on, 0);
+#endif
+
+ mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE);
+
+ return 0;
+}
+
+static int esc6270_reset(struct modem_ctl *mc)
+{
+ int ret = 0;
+
+ pr_debug("[MODEM_IF:ESC] esc6270_reset()\n");
+
+ ret = esc6270_off(mc);
+ if (ret)
+ return -ENXIO;
+
+ msleep(100);
+
+ ret = esc6270_on(mc);
+ if (ret)
+ return -ENXIO;
+
+ return 0;
+}
+
+int esc6270_boot_on(struct modem_ctl *mc)
+{
+ struct link_device *ld = get_current_link(mc->iod);
+
+ pr_info("[MODEM_IF:ESC] <%s>\n", __func__);
+
+ /* Need to init uart byt gpio_flm_uart_sel GPIO */
+ if (!mc->gpio_cp_reset || !mc->gpio_flm_uart_sel) {
+ pr_err("[MODEM_IF:ESC] no gpio data\n");
+ return -ENXIO;
+ }
+ gpio_set_value(mc->gpio_flm_uart_sel, 1);
+
+ pr_info(" - ESC_PHONE_ON : %d, ESC_RESET_N : %d\n",
+ gpio_get_value(mc->gpio_cp_on),
+ gpio_get_value(mc->gpio_cp_reset));
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ gpio_direction_output(mc->gpio_cp_reset, 0);
+ msleep(100);
+
+ gpio_direction_output(mc->gpio_cp_on, 1);
+ msleep(44);
+
+ pr_info(" - ESC_PHONE_ON : %d, ESC_RESET_N : %d\n",
+ gpio_get_value(mc->gpio_cp_on),
+ gpio_get_value(mc->gpio_cp_reset));
+
+ gpio_direction_input(mc->gpio_cp_reset);
+ msleep(600);
+ gpio_direction_output(mc->gpio_cp_on, 0);
+
+ msleep(20);
+ pr_info(" - ESC_PHONE_ON : %d, ESC_RESET_N : %d\n",
+ gpio_get_value(mc->gpio_cp_on),
+ gpio_get_value(mc->gpio_cp_reset));
+
+ mc->iod->modem_state_changed(mc->iod, STATE_BOOTING);
+ ld->mode = LINK_MODE_BOOT;
+
+ return 0;
+}
+
+static int esc6270_boot_off(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF:ESC] <%s>\n", __func__);
+
+ if (!mc->gpio_flm_uart_sel) {
+ pr_err("[MODEM_IF:ESC] no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_flm_uart_sel, 0);
+
+ mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE);
+
+ return 0;
+}
+
+static int esc6270_active_count;
+
+static irqreturn_t phone_active_irq_handler(int irq, void *arg)
+{
+ struct modem_ctl *mc = (struct modem_ctl *)arg;
+ int phone_reset = 0;
+ int phone_active = 0;
+ int phone_state = 0;
+ int cp_dump_int = 0;
+
+ if (!mc->gpio_cp_reset ||
+ !mc->gpio_phone_active) { /* || !mc->gpio_cp_dump_int) { */
+ pr_err("[MODEM_IF:ESC] no gpio data\n");
+ return IRQ_HANDLED;
+ }
+
+ phone_reset = gpio_get_value(mc->gpio_cp_reset);
+ phone_active = gpio_get_value(mc->gpio_phone_active);
+ cp_dump_int = gpio_get_value(mc->gpio_cp_dump_int);
+
+ pr_info("[MODEM_IF:ESC] <%s> phone_reset=%d, phone_active=%d, cp_dump_int=%d\n",
+ __func__, phone_reset, phone_active, cp_dump_int);
+
+ if (phone_reset && phone_active) {
+ 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) {
+ 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)
+ 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("[MODEM_IF::ESC] <%s> phone_state = %d\n",
+ __func__, phone_state);
+
+ return IRQ_HANDLED;
+}
+
+#if defined(CONFIG_SIM_DETECT)
+#if defined(CONFIG_MACH_GRANDE)
+static void sim_detect_work(struct work_struct *work)
+{
+ struct modem_ctl *mc =
+ container_of(work, struct modem_ctl, sim_det_dwork.work);
+
+ pr_info("[MODEM_IF:ESC] <%s> gpio_sim_detect = %d\n",
+ __func__, gpio_get_value(mc->gpio_sim_detect));
+
+ if (mc->iod && mc->iod->sim_state_changed)
+ mc->iod->sim_state_changed(mc->iod,
+ !gpio_get_value(mc->gpio_sim_detect));
+}
+#endif
+static irqreturn_t sim_detect_irq_handler(int irq, void *_mc)
+{
+ struct modem_ctl *mc = (struct modem_ctl *)_mc;
+
+ pr_info("[MODEM_IF:ESC] <%s> gpio_sim_detect = %d\n",
+ __func__, gpio_get_value(mc->gpio_sim_detect));
+
+#if defined(CONFIG_MACH_GRANDE)
+ schedule_delayed_work(&mc->sim_det_dwork, msecs_to_jiffies(400));
+#else
+ if (mc->iod && mc->iod->sim_state_changed)
+ mc->iod->sim_state_changed(mc->iod,
+ !gpio_get_value(mc->gpio_sim_detect));
+#endif
+ return IRQ_HANDLED;
+}
+#endif
+
+static void esc6270_get_ops(struct modem_ctl *mc)
+{
+ mc->ops.modem_on = esc6270_on;
+ mc->ops.modem_off = esc6270_off;
+ mc->ops.modem_reset = esc6270_reset;
+ mc->ops.modem_boot_on = esc6270_boot_on;
+ mc->ops.modem_boot_off = esc6270_boot_off;
+}
+
+int esc6270_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_sim_detect = pdata->gpio_sim_detect;
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_cp_on, 0);
+
+ pdev = to_platform_device(mc->dev);
+ mc->irq_phone_active = platform_get_irq_byname(pdev, "cp_active_irq");
+ pr_info("[MODEM_IF:ESC] <%s> PHONE_ACTIVE IRQ# = %d\n",
+ __func__, mc->irq_phone_active);
+
+ esc6270_get_ops(mc);
+
+ if (mc->irq_phone_active) {
+ ret = request_irq(mc->irq_phone_active,
+ phone_active_irq_handler,
+ IRQF_TRIGGER_HIGH,
+ "esc_active",
+ mc);
+ if (ret) {
+ pr_err("[MODEM_IF:ESC] <%s> failed to request_irq IRQ# %d (err=%d)\n",
+ __func__, mc->irq_phone_active, ret);
+ return ret;
+ }
+
+ ret = enable_irq_wake(mc->irq_phone_active);
+ if (ret) {
+ pr_err("[MODEM_IF:ESC] %s: failed to enable_irq_wake IRQ# %d (err=%d)\n",
+ __func__, mc->irq_phone_active, ret);
+ free_irq(mc->irq_phone_active, mc);
+ return ret;
+ }
+ }
+
+#if defined(CONFIG_SIM_DETECT)
+#if defined(CONFIG_MACH_GRANDE)
+ INIT_DELAYED_WORK(&mc->sim_det_dwork, sim_detect_work);
+#endif
+ mc->irq_sim_detect = platform_get_irq_byname(pdev, "sim_irq");
+ pr_info("[MODEM_IF:ESC] <%s> SIM_DECTCT IRQ# = %d\n",
+ __func__, mc->irq_sim_detect);
+
+ if (mc->irq_sim_detect) {
+ ret = request_irq(mc->irq_sim_detect, sim_detect_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "esc_sim_detect", mc);
+ if (ret) {
+ mif_err("failed to request_irq: %d\n", ret);
+ mc->sim_state.online = false;
+ mc->sim_state.changed = false;
+ return ret;
+ }
+
+ ret = enable_irq_wake(mc->irq_sim_detect);
+ if (ret) {
+ mif_err("failed to enable_irq_wake: %d\n", ret);
+ free_irq(mc->irq_sim_detect, mc);
+ mc->sim_state.online = false;
+ mc->sim_state.changed = false;
+ return ret;
+ }
+
+ /* initialize sim_state => insert: gpio=0, remove: gpio=1 */
+ mc->sim_state.online = !gpio_get_value(mc->gpio_sim_detect);
+ }
+#endif
+
+ return ret;
+}
+#endif
diff --git a/drivers/misc/modem_if/modem_modemctl_device_mdm6600.c b/drivers/misc/modem_if/modem_modemctl_device_mdm6600.c
new file mode 100644
index 00000000000..129f790fdf8
--- /dev/null
+++ b/drivers/misc/modem_if/modem_modemctl_device_mdm6600.c
@@ -0,0 +1,801 @@
+/* /linux/drivers/misc/modem_if/modem_modemctl_device_mdm6600.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_device.h>
+
+#include <linux/platform_data/modem.h>
+#include "modem_prj.h"
+#include <linux/regulator/consumer.h>
+
+#include <plat/gpio-cfg.h>
+
+#if defined(CONFIG_MACH_M0_CTC)
+#include <linux/mfd/max77693.h>
+#endif
+
+#if defined(CONFIG_MACH_U1_KOR_LGT)
+#include <linux/mfd/max8997.h>
+
+static int mdm6600_on(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] mdm6600_on()\n");
+
+ if (!mc->gpio_cp_reset || !mc->gpio_cp_reset_msm || !mc->gpio_cp_on) {
+ pr_err("[MODEM_IF] no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_pda_active, 0);
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ gpio_set_value(mc->gpio_cp_reset_msm, 1);
+ msleep(30);
+ gpio_set_value(mc->gpio_cp_on, 1);
+ msleep(300);
+ gpio_set_value(mc->gpio_cp_on, 0);
+ msleep(500);
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ mc->iod->modem_state_changed(mc->iod, STATE_BOOTING);
+
+ return 0;
+}
+
+static int mdm6600_off(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] mdm6600_off()\n");
+
+ if (!mc->gpio_cp_reset || !mc->gpio_cp_reset_msm || !mc->gpio_cp_on) {
+ pr_err("[MODEM_IF] no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_cp_reset_msm, 0);
+
+ mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE);
+
+ return 0;
+}
+
+static int mdm6600_reset(struct modem_ctl *mc)
+{
+ int ret;
+
+ pr_info("[MODEM_IF] mdm6600_reset()\n");
+
+ if (!mc->gpio_cp_reset || !mc->gpio_cp_reset_msm || !mc->gpio_cp_on) {
+ pr_err("[MODEM_IF] no gpio data\n");
+ return -ENXIO;
+ }
+
+ if (system_rev >= 0x05) {
+ dev_err(mc->dev, "[%s] system_rev: %d\n", __func__, system_rev);
+
+ gpio_set_value(mc->gpio_cp_reset_msm, 0);
+ msleep(100); /* no spec, confirm later exactly how much time
+ needed to initialize CP with RESET_PMU_N */
+ gpio_set_value(mc->gpio_cp_reset_msm, 1);
+ msleep(40); /* > 37.2 + 2 msec */
+ } else {
+ dev_err(mc->dev, "[%s] system_rev: %d\n", __func__, system_rev);
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(500); /* no spec, confirm later exactly how much time
+ needed to initialize CP with RESET_PMU_N */
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ msleep(40); /* > 37.2 + 2 msec */
+ }
+
+ return 0;
+}
+
+static int mdm6600_boot_on(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] mdm6600_boot_on()\n");
+
+ if (!mc->gpio_boot_sw_sel) {
+ pr_err("[MODEM_IF] no gpio data\n");
+ return -ENXIO;
+ }
+
+ if (mc->vbus_on)
+ mc->vbus_on();
+
+ if (mc->gpio_boot_sw_sel)
+ gpio_set_value(mc->gpio_boot_sw_sel, 0);
+ mc->usb_boot = true;
+
+ return 0;
+}
+
+static int mdm6600_boot_off(struct modem_ctl *mc)
+{
+ pr_info("[MODEM_IF] mdm6600_boot_off()\n");
+
+ if (!mc->gpio_boot_sw_sel) {
+ pr_err("[MODEM_IF] no gpio data\n");
+ return -ENXIO;
+ }
+
+ if (mc->vbus_off)
+ mc->vbus_off();
+
+ if (mc->gpio_boot_sw_sel)
+ gpio_set_value(mc->gpio_boot_sw_sel, 1);
+ mc->usb_boot = false;
+
+ return 0;
+}
+
+static int count;
+
+static irqreturn_t phone_active_irq_handler(int irq, void *_mc)
+{
+ int phone_reset = 0;
+ int phone_active_value = 0;
+ int cp_dump_value = 0;
+ int phone_state = 0;
+ struct modem_ctl *mc = (struct modem_ctl *)_mc;
+
+ if (!mc->gpio_cp_reset || !mc->gpio_phone_active
+/*|| !mc->gpio_cp_dump_int */) {
+ pr_err("[MODEM_IF] no gpio data\n");
+ return IRQ_HANDLED;
+ }
+
+ phone_reset = gpio_get_value(mc->gpio_cp_reset);
+ phone_active_value = gpio_get_value(mc->gpio_phone_active);
+
+ pr_info("[MODEM_IF] PA EVENT : reset =%d, pa=%d, cp_dump=%d\n",
+ phone_reset, phone_active_value, cp_dump_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 (count == 1) {
+ phone_state = STATE_CRASH_EXIT;
+ if (mc->iod) {
+ ld = get_current_link(mc->iod);
+ if (ld->terminate_comm)
+ ld->terminate_comm(ld, mc->iod);
+ }
+ if (mc->iod && mc->iod->modem_state_changed)
+ mc->iod->modem_state_changed
+ (mc->iod, phone_state);
+ count = 0;
+ } else {
+ count++;
+ }
+ } else {
+ phone_state = STATE_OFFLINE;
+ if (mc->iod && mc->iod->modem_state_changed)
+ mc->iod->modem_state_changed(mc->iod, phone_state);
+ }
+
+ pr_info("phone_active_irq_handler : phone_state=%d\n", phone_state);
+
+ return IRQ_HANDLED;
+}
+
+static void mdm6600_get_ops(struct modem_ctl *mc)
+{
+ mc->ops.modem_on = mdm6600_on;
+ mc->ops.modem_off = mdm6600_off;
+ mc->ops.modem_reset = mdm6600_reset;
+ mc->ops.modem_boot_on = mdm6600_boot_on;
+ mc->ops.modem_boot_off = mdm6600_boot_off;
+}
+
+int mdm6600_init_modemctl_device(struct modem_ctl *mc, struct modem_data *pdata)
+{
+ int ret;
+ struct platform_device *pdev;
+
+ mc->gpio_cp_on = pdata->gpio_cp_on;
+ 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_reset_msm = pdata->gpio_cp_reset_msm;
+ mc->gpio_boot_sw_sel = pdata->gpio_boot_sw_sel;
+
+ mc->vbus_on = pdata->vbus_on;
+ mc->vbus_off = pdata->vbus_off;
+
+ pdev = to_platform_device(mc->dev);
+ mc->irq_phone_active = platform_get_irq_byname(pdev, "cp_active_irq");
+ pr_info("[MODEM_IF] <%s> PHONE_ACTIVE IRQ# = %d\n",
+ __func__, mc->irq_phone_active);
+
+ mdm6600_get_ops(mc);
+
+ ret = request_irq(mc->irq_phone_active, phone_active_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "phone_active", mc);
+ if (ret) {
+ pr_err("[MODEM_IF] %s: failed to request_irq:%d\n",
+ __func__, ret);
+ goto err_request_irq;
+ }
+
+ ret = enable_irq_wake(mc->irq_phone_active);
+ if (ret) {
+ pr_err("[MODEM_IF] %s: failed to enable_irq_wake:%d\n",
+ __func__, ret);
+ goto err_set_wake_irq;
+ }
+
+ return ret;
+
+ err_set_wake_irq:
+ free_irq(mc->irq_phone_active, mc);
+ err_request_irq:
+ return ret;
+}
+#endif /* CONFIG_MACH_U1_KOR_LGT */
+
+#if defined(CONFIG_MACH_M0_CTC) || defined(CONFIG_MACH_T0_CHN_CTC)
+
+#if defined(CONFIG_LINK_DEVICE_DPRAM)
+#include "modem_link_device_dpram.h"
+#elif defined(CONFIG_LINK_DEVICE_PLD)
+#include "modem_link_device_pld.h"
+#endif
+
+#define PIF_TIMEOUT (180 * HZ)
+#define DPRAM_INIT_TIMEOUT (30 * HZ)
+
+#if defined(CONFIG_MACH_M0_DUOSCTC) || defined(CONFIG_MACH_M0_GRANDECTC) || \
+ defined(CONFIG_MACH_T0_CHN_CTC)
+static void mdm6600_vbus_on(void)
+{
+ struct regulator *regulator;
+
+ pr_info("[MSM] <%s>\n", __func__);
+
+#if defined(CONFIG_MACH_T0_CHN_CTC)
+ if (system_rev == 4)
+ regulator = regulator_get(NULL, "vcc_1.8v_lcd");
+ else
+ regulator = regulator_get(NULL, "vcc_1.8v_usb");
+#else
+ regulator = regulator_get(NULL, "vusbhub_osc_1.8v");
+#endif
+ if (IS_ERR(regulator)) {
+ pr_err("[MSM] error getting regulator_get <%s>\n", __func__);
+ return ;
+ }
+ regulator_enable(regulator);
+ regulator_put(regulator);
+
+ pr_info("[MSM] <%s> enable\n", __func__);
+}
+
+static void mdm6600_vbus_off(void)
+{
+ struct regulator *regulator;
+
+ pr_info("[MSM] <%s>\n", __func__);
+
+#if defined(CONFIG_MACH_T0_CHN_CTC)
+ if (system_rev == 4)
+ regulator = regulator_get(NULL, "vcc_1.8v_lcd");
+ else
+ regulator = regulator_get(NULL, "vcc_1.8v_usb");
+#else
+ regulator = regulator_get(NULL, "vusbhub_osc_1.8v");
+#endif
+ if (IS_ERR(regulator)) {
+ pr_err("[MSM] error getting regulator_get <%s>\n", __func__);
+ return ;
+ }
+ regulator_disable(regulator);
+ regulator_put(regulator);
+
+ pr_info("[MSM] <%s> disable\n", __func__);
+}
+#endif
+
+static int mdm6600_on(struct modem_ctl *mc)
+{
+ struct link_device *ld = get_current_link(mc->iod);
+
+ pr_info("[MSM] <%s>\n", __func__);
+
+ if (!mc->gpio_reset_req_n || !mc->gpio_cp_reset
+ || !mc->gpio_cp_on || !mc->gpio_pda_active) {
+ pr_err("[MSM] no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_pda_active, 0);
+
+ gpio_set_value(mc->gpio_cp_on, 1);
+ msleep(500);
+
+ gpio_set_value(mc->gpio_reset_req_n, 1);
+ msleep(50);
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ msleep(50);
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ msleep(500);
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ mc->iod->modem_state_changed(mc->iod, STATE_BOOTING);
+ ld->mode = LINK_MODE_BOOT;
+
+ return 0;
+}
+
+static int mdm6600_off(struct modem_ctl *mc)
+{
+ pr_info("[MSM] <%s>\n", __func__);
+
+ if (!mc->gpio_cp_reset || !mc->gpio_cp_on) {
+ pr_err("[MSM] no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_reset_req_n, 0);
+ gpio_set_value(mc->gpio_cp_on, 0);
+
+ msleep(200);
+
+ mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE);
+
+ return 0;
+}
+
+static int mdm6600_reset(struct modem_ctl *mc)
+{
+ int ret = 0;
+ struct link_device *ld = get_current_link(mc->iod);
+
+ pr_info("[MSM] <%s>\n", __func__);
+
+ if (!mc->gpio_reset_req_n || !mc->gpio_cp_reset
+ || !mc->gpio_cp_on || !mc->gpio_pda_active) {
+ pr_err("[MSM] no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_reset_req_n, 0);
+ gpio_set_value(mc->gpio_cp_on, 0);
+
+ msleep(100);
+
+ gpio_set_value(mc->gpio_cp_on, 1);
+ msleep(300);
+
+ gpio_set_value(mc->gpio_reset_req_n, 1);
+ msleep(50);
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ msleep(50);
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ msleep(100);
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ mc->iod->modem_state_changed(mc->iod, STATE_BOOTING);
+ ld->mode = LINK_MODE_BOOT;
+
+ return 0;
+}
+
+static int mdm6600_boot_on(struct modem_ctl *mc)
+{
+ struct regulator *regulator;
+
+ pr_info("[MSM] <%s>\n", __func__);
+
+ if (!mc->gpio_flm_uart_sel) {
+ pr_err("[MSM] no gpio data\n");
+ return -ENXIO;
+ }
+
+#if defined(CONFIG_MACH_M0_DUOSCTC) || defined(CONFIG_MACH_T0_CHN_CTC)
+ mdm6600_vbus_on();
+#elif defined(CONFIG_MACH_M0_GRANDECTC)
+ if (system_rev >= 14)
+ mdm6600_vbus_on();
+#endif
+
+ pr_info("[MSM] <%s> %s\n", __func__, "USB_BOOT_EN initializing");
+ if (system_rev < 11) {
+
+ gpio_direction_output(GPIO_USB_BOOT_EN, 0);
+ s3c_gpio_setpull(GPIO_USB_BOOT_EN, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_USB_BOOT_EN, 0);
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL, 0);
+ s3c_gpio_setpull(GPIO_BOOT_SW_SEL, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_BOOT_SW_SEL, 0);
+
+ msleep(100);
+
+ gpio_direction_output(GPIO_USB_BOOT_EN, 1);
+ gpio_set_value(GPIO_USB_BOOT_EN, 1);
+
+ pr_info("[MSM] <%s> USB_BOOT_EN:[%d]\n", __func__,
+ gpio_get_value(GPIO_USB_BOOT_EN));
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL, 1);
+ gpio_set_value(GPIO_BOOT_SW_SEL, 1);
+
+ pr_info("[MSM] <%s> BOOT_SW_SEL : [%d]\n", __func__,
+ gpio_get_value(GPIO_BOOT_SW_SEL));
+ } else if (system_rev == 11) {
+ gpio_direction_output(GPIO_USB_BOOT_EN, 0);
+ s3c_gpio_setpull(GPIO_USB_BOOT_EN, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_USB_BOOT_EN, 0);
+
+ gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 0);
+ s3c_gpio_setpull(GPIO_USB_BOOT_EN_REV06, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_USB_BOOT_EN_REV06, 0);
+
+ msleep(100);
+
+ gpio_direction_output(GPIO_USB_BOOT_EN, 1);
+ gpio_set_value(GPIO_USB_BOOT_EN, 1);
+
+ pr_info("[MSM] <%s> USB_BOOT_EN:[%d]\n", __func__,
+ gpio_get_value(GPIO_USB_BOOT_EN));
+
+ gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 1);
+ gpio_set_value(GPIO_USB_BOOT_EN_REV06, 1);
+
+ pr_info("[MSM(%d)] <%s> USB_BOOT_EN:[%d]\n", system_rev,
+ __func__, gpio_get_value(GPIO_USB_BOOT_EN_REV06));
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL, 0);
+ s3c_gpio_setpull(GPIO_BOOT_SW_SEL, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_BOOT_SW_SEL, 0);
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 0);
+ s3c_gpio_setpull(GPIO_BOOT_SW_SEL_REV06, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 0);
+
+ msleep(100);
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL, 1);
+ gpio_set_value(GPIO_BOOT_SW_SEL, 1);
+
+ pr_info("[MSM] <%s> BOOT_SW_SEL : [%d]\n", __func__,
+ gpio_get_value(GPIO_BOOT_SW_SEL));
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 1);
+ gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 1);
+
+ pr_info("[MSM(%d)] <%s> BOOT_SW_SEL : [%d]\n", system_rev,
+ __func__, gpio_get_value(GPIO_BOOT_SW_SEL_REV06));
+
+ } else { /* system_rev>11 */
+ gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 0);
+ s3c_gpio_setpull(GPIO_USB_BOOT_EN_REV06, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_USB_BOOT_EN_REV06, 0);
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 0);
+ s3c_gpio_setpull(GPIO_BOOT_SW_SEL_REV06, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 0);
+
+ msleep(100);
+
+ gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 1);
+ gpio_set_value(GPIO_USB_BOOT_EN_REV06, 1);
+
+ pr_info("[MSM] <%s> USB_BOOT_EN:[%d]\n", __func__,
+ gpio_get_value(GPIO_USB_BOOT_EN_REV06));
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 1);
+ gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 1);
+
+ pr_info("[MSM] <%s> BOOT_SW_SEL : [%d]\n", __func__,
+ gpio_get_value(GPIO_BOOT_SW_SEL_REV06));
+
+ }
+
+ mc->iod->modem_state_changed(mc->iod, STATE_BOOTING);
+
+ return 0;
+}
+
+static int mdm6600_boot_off(struct modem_ctl *mc)
+{
+ pr_info("[MSM] <%s>\n", __func__);
+
+ if (!mc->gpio_flm_uart_sel
+#if defined(CONFIG_MACH_M0_CTC)
+ || !mc->gpio_flm_uart_sel_rev06
+#endif
+ ) {
+ pr_err("[MSM] no gpio data\n");
+ return -ENXIO;
+ }
+
+#if defined(CONFIG_MACH_M0_DUOSCTC) || defined(CONFIG_MACH_T0_CHN_CTC)
+ mdm6600_vbus_off();
+#elif defined(CONFIG_MACH_M0_GRANDECTC)
+ if (system_rev >= 14)
+ mdm6600_vbus_off();
+#endif
+
+ if (system_rev < 11) {
+ gpio_direction_output(GPIO_USB_BOOT_EN, 0);
+ s3c_gpio_setpull(GPIO_USB_BOOT_EN, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_USB_BOOT_EN, 0);
+ gpio_direction_output(GPIO_BOOT_SW_SEL, 0);
+ s3c_gpio_setpull(GPIO_BOOT_SW_SEL, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_BOOT_SW_SEL, 0);
+
+ } else if (system_rev == 11) {
+ gpio_direction_output(GPIO_USB_BOOT_EN, 0);
+ s3c_gpio_setpull(GPIO_USB_BOOT_EN, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_USB_BOOT_EN, 0);
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL, 0);
+ s3c_gpio_setpull(GPIO_BOOT_SW_SEL, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_BOOT_SW_SEL, 0);
+
+#if defined(CONFIG_MACH_M0_CTC)
+ gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 0);
+ s3c_gpio_setpull(GPIO_USB_BOOT_EN_REV06, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_USB_BOOT_EN_REV06, 0);
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 0);
+ s3c_gpio_setpull(GPIO_BOOT_SW_SEL_REV06, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 0);
+#endif
+
+ } else { /* system_rev>11 */
+#if defined(CONFIG_MACH_M0_CTC)
+ gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 0);
+ s3c_gpio_setpull(GPIO_USB_BOOT_EN_REV06, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_USB_BOOT_EN_REV06, 0);
+
+ gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 0);
+ s3c_gpio_setpull(GPIO_BOOT_SW_SEL_REV06, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 0);
+#endif
+ }
+
+#if defined(CONFIG_MACH_M0_CTC)
+ if (max7693_muic_cp_usb_state()) {
+ msleep(30);
+ gpio_direction_output(GPIO_USB_BOOT_EN, 1);
+ s3c_gpio_setpull(GPIO_USB_BOOT_EN, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_USB_BOOT_EN, 1);
+ gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 1);
+ s3c_gpio_setpull(GPIO_USB_BOOT_EN_REV06, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_USB_BOOT_EN_REV06, 1);
+ }
+#endif
+
+ gpio_set_value(GPIO_BOOT_SW_SEL, 0);
+
+ return 0;
+}
+
+
+static int mdm6600_force_crash_exit(struct modem_ctl *mc)
+{
+ pr_info("[MSM] <%s>\n", __func__);
+
+ if (!mc->gpio_cp_reset || !mc->gpio_cp_on) {
+ pr_err("[MSM] no gpio data\n");
+ return -ENXIO;
+ }
+
+ s3c_gpio_cfgpin(mc->gpio_cp_dump_int, S3C_GPIO_OUTPUT);
+ gpio_direction_output(mc->gpio_cp_dump_int, 1);
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_cp_on, 0);
+ gpio_set_value(mc->gpio_cp_reset, 1);
+
+ return 0;
+}
+
+static irqreturn_t phone_active_irq_handler(int irq, void *arg)
+{
+ struct modem_ctl *mc = (struct modem_ctl *)arg;
+ int phone_reset = 0;
+ int phone_active = 0;
+ int phone_state = 0;
+ int cp_dump_int = 0;
+
+ if (!mc->gpio_cp_reset ||
+ !mc->gpio_phone_active || !mc->gpio_cp_dump_int) {
+ pr_err("[MSM] no gpio data\n");
+ return IRQ_HANDLED;
+ }
+
+ phone_reset = gpio_get_value(mc->gpio_cp_reset);
+ phone_active = gpio_get_value(mc->gpio_phone_active);
+ cp_dump_int = gpio_get_value(mc->gpio_cp_dump_int);
+
+ pr_info("[MSM] <%s> phone_reset=%d, phone_active=%d, cp_dump_int=%d\n",
+ __func__, phone_reset, phone_active, cp_dump_int);
+
+ if (phone_reset && phone_active) {
+ 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) {
+ 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)
+ 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("[MSM] <%s> phone_state = %d\n", __func__, phone_state);
+
+ return IRQ_HANDLED;
+}
+
+#if defined(CONFIG_SIM_DETECT)
+#if defined(CONFIG_MACH_GRANDE)
+static void sim_detect_work(struct work_struct *work)
+{
+ struct modem_ctl *mc =
+ container_of(work, struct modem_ctl, sim_det_dwork.work);
+
+ pr_info("[MSM] <%s> gpio_sim_detect = %d\n",
+ __func__, gpio_get_value(mc->gpio_sim_detect));
+
+ if (mc->iod && mc->iod->sim_state_changed)
+ mc->iod->sim_state_changed(mc->iod,
+ !gpio_get_value(mc->gpio_sim_detect));
+}
+#endif
+static irqreturn_t sim_detect_irq_handler(int irq, void *_mc)
+{
+ struct modem_ctl *mc = (struct modem_ctl *)_mc;
+
+ pr_info("[MSM] <%s> gpio_sim_detect = %d\n",
+ __func__, gpio_get_value(mc->gpio_sim_detect));
+
+#if defined(CONFIG_MACH_GRANDE)
+ schedule_delayed_work(&mc->sim_det_dwork, msecs_to_jiffies(400));
+#else
+ if (mc->iod && mc->iod->sim_state_changed)
+ mc->iod->sim_state_changed(mc->iod,
+ !gpio_get_value(mc->gpio_sim_detect));
+#endif
+ return IRQ_HANDLED;
+}
+#endif
+
+static void mdm6600_get_ops(struct modem_ctl *mc)
+{
+ mc->ops.modem_on = mdm6600_on;
+ mc->ops.modem_off = mdm6600_off;
+ mc->ops.modem_reset = mdm6600_reset;
+ mc->ops.modem_boot_on = mdm6600_boot_on;
+ mc->ops.modem_boot_off = mdm6600_boot_off;
+ mc->ops.modem_force_crash_exit = mdm6600_force_crash_exit;
+}
+
+int mdm6600_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;
+#if defined(CONFIG_MACH_M0_CTC)
+ mc->gpio_flm_uart_sel_rev06 = pdata->gpio_flm_uart_sel_rev06;
+#endif
+ mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset;
+ mc->gpio_sim_detect = pdata->gpio_sim_detect;
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_cp_on, 0);
+
+ pdev = to_platform_device(mc->dev);
+ mc->irq_phone_active = platform_get_irq_byname(pdev, "cp_active_irq");
+ pr_info("[MSM] <%s> PHONE_ACTIVE IRQ# = %d\n",
+ __func__, mc->irq_phone_active);
+
+ mdm6600_get_ops(mc);
+
+ ret = request_irq(mc->irq_phone_active,
+ phone_active_irq_handler,
+ IRQF_TRIGGER_HIGH, "msm_active", mc);
+ if (ret) {
+ pr_err("[MSM] <%s> failed to request_irq IRQ# %d (err=%d)\n",
+ __func__, mc->irq_phone_active, ret);
+ return ret;
+ }
+
+ ret = enable_irq_wake(mc->irq_phone_active);
+ if (ret) {
+ pr_err("[MSM] %s: failed to enable_irq_wake IRQ# %d (err=%d)\n",
+ __func__, mc->irq_phone_active, ret);
+ free_irq(mc->irq_phone_active, mc);
+ return ret;
+ }
+
+#if defined(CONFIG_SIM_DETECT)
+#if defined(CONFIG_MACH_GRANDE)
+ INIT_DELAYED_WORK(&mc->sim_det_dwork, sim_detect_work);
+#endif
+ mc->irq_sim_detect = platform_get_irq_byname(pdev, "sim_irq");
+ pr_info("[MSM] <%s> SIM_DECTCT IRQ# = %d\n",
+ __func__, mc->irq_sim_detect);
+
+ if (mc->irq_sim_detect) {
+ ret = request_irq(mc->irq_sim_detect, sim_detect_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "msm_sim_detect", mc);
+ if (ret) {
+ mif_err("[MSM] failed to request_irq: %d\n", ret);
+ mc->sim_state.online = false;
+ mc->sim_state.changed = false;
+ return ret;
+ }
+
+ ret = enable_irq_wake(mc->irq_sim_detect);
+ if (ret) {
+ mif_err("[MSM] failed to enable_irq_wake: %d\n", ret);
+ free_irq(mc->irq_sim_detect, mc);
+ mc->sim_state.online = false;
+ mc->sim_state.changed = false;
+ return ret;
+ }
+
+ /* initialize sim_state => insert: gpio=0, remove: gpio=1 */
+ mc->sim_state.online = !gpio_get_value(mc->gpio_sim_detect);
+ }
+#endif
+
+ return ret;
+}
+#endif
diff --git a/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c b/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c
new file mode 100644
index 00000000000..c2d50677aa6
--- /dev/null
+++ b/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c
@@ -0,0 +1,303 @@
+/* /linux/drivers/misc/modem_if/modem_modemctl_device_xmm6260.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.h>
+#include "modem_prj.h"
+
+static int xmm6260_on(struct modem_ctl *mc)
+{
+ mif_info("xmm6260_on()\n");
+
+ if (!mc->gpio_cp_reset || !mc->gpio_cp_on || !mc->gpio_reset_req_n) {
+ mif_err("no gpio data\n");
+ return -ENXIO;
+ }
+
+ if (mc->gpio_revers_bias_clear)
+ mc->gpio_revers_bias_clear();
+
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ udelay(160);
+ gpio_set_value(mc->gpio_pda_active, 0);
+ msleep(500); /* must be >500ms for CP can boot up under -20 degrees */
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ udelay(160);
+ gpio_set_value(mc->gpio_reset_req_n, 1);
+ udelay(160);
+ gpio_set_value(mc->gpio_cp_on, 1);
+ udelay(60);
+ gpio_set_value(mc->gpio_cp_on, 0);
+ msleep(20);
+
+ if (mc->gpio_revers_bias_restore)
+ mc->gpio_revers_bias_restore();
+
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ mc->phone_state = STATE_BOOTING;
+
+ return 0;
+
+}
+
+static int xmm6260_off(struct modem_ctl *mc)
+{
+ mif_info("xmm6260_off()\n");
+
+ if (!mc->gpio_cp_reset || !mc->gpio_cp_on) {
+ mif_err("no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+
+ mc->phone_state = STATE_OFFLINE;
+
+ return 0;
+}
+
+
+static int xmm6260_reset(struct modem_ctl *mc)
+{
+
+ mif_info("xmm6260_reset()\n");
+
+ if (!mc->gpio_cp_reset || !mc->gpio_reset_req_n)
+ return -ENXIO;
+
+ if (mc->gpio_revers_bias_clear)
+ mc->gpio_revers_bias_clear();
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_reset_req_n, 0);
+
+ mc->phone_state = STATE_OFFLINE;
+
+ msleep(20);
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+/* TODO: check the reset timming with C2C connection */
+ udelay(160);
+
+ gpio_set_value(mc->gpio_reset_req_n, 1);
+ udelay(100);
+
+ gpio_set_value(mc->gpio_cp_on, 1);
+ udelay(60);
+ gpio_set_value(mc->gpio_cp_on, 0);
+ msleep(20);
+
+ if (mc->gpio_revers_bias_restore)
+ mc->gpio_revers_bias_restore();
+
+ mc->phone_state = STATE_BOOTING;
+
+ return 0;
+}
+
+static int xmm6260_boot_on(struct modem_ctl *mc)
+{
+ mif_info("xmm6260_boot_on()\n");
+
+ if (!mc->gpio_flm_uart_sel) {
+ mif_err("no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_flm_uart_sel, 0);
+
+ return 0;
+}
+
+static int xmm6260_boot_off(struct modem_ctl *mc)
+{
+ mif_info("xmm6260_boot_off()\n");
+
+ if (!mc->gpio_flm_uart_sel) {
+ mif_err("no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_flm_uart_sel, 1);
+
+ return 0;
+}
+
+static irqreturn_t phone_active_irq_handler(int irq, void *_mc)
+{
+ int phone_reset = 0;
+ int phone_active_value = 0;
+ int cp_dump_value = 0;
+ int phone_state = 0;
+ struct modem_ctl *mc = (struct modem_ctl *)_mc;
+ struct link_device *ld;
+
+ disable_irq_nosync(mc->irq_phone_active);
+
+ if (!mc->gpio_cp_reset || !mc->gpio_phone_active ||
+ !mc->gpio_cp_dump_int) {
+ mif_err("no gpio data\n");
+ return IRQ_HANDLED;
+ }
+
+ phone_reset = gpio_get_value(mc->gpio_cp_reset);
+ phone_active_value = gpio_get_value(mc->gpio_phone_active);
+ cp_dump_value = gpio_get_value(mc->gpio_cp_dump_int);
+
+ mif_info("PA EVENT : reset =%d, pa=%d, cp_dump=%d\n",
+ phone_reset, phone_active_value, cp_dump_value);
+
+ if (phone_reset && phone_active_value)
+ phone_state = STATE_BOOTING;
+ else if (phone_reset && !phone_active_value) {
+ if (mc->phone_state == STATE_BOOTING)
+ goto set_type;
+ if (cp_dump_value)
+ phone_state = STATE_CRASH_EXIT;
+ else
+ phone_state = STATE_CRASH_RESET;
+ if (mc->iod) {
+ ld = get_current_link(mc->iod);
+ if (ld->terminate_comm)
+ ld->terminate_comm(ld, mc->iod);
+ }
+ } else
+ phone_state = STATE_OFFLINE;
+
+ if (mc->iod && mc->iod->modem_state_changed)
+ mc->iod->modem_state_changed(mc->iod, phone_state);
+
+ if (mc->bootd && mc->bootd->modem_state_changed)
+ mc->bootd->modem_state_changed(mc->bootd, phone_state);
+
+set_type:
+ 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);
+ enable_irq(mc->irq_phone_active);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t sim_detect_irq_handler(int irq, void *_mc)
+{
+ struct modem_ctl *mc = (struct modem_ctl *)_mc;
+
+ if (mc->iod && mc->iod->sim_state_changed)
+ mc->iod->sim_state_changed(mc->iod,
+ !gpio_get_value(mc->gpio_sim_detect));
+
+ return IRQ_HANDLED;
+}
+
+static void xmm6260_get_ops(struct modem_ctl *mc)
+{
+ mc->ops.modem_on = xmm6260_on;
+ mc->ops.modem_off = xmm6260_off;
+ mc->ops.modem_reset = xmm6260_reset;
+ mc->ops.modem_boot_on = xmm6260_boot_on;
+ mc->ops.modem_boot_off = xmm6260_boot_off;
+}
+
+int xmm6260_init_modemctl_device(struct modem_ctl *mc,
+ struct modem_data *pdata)
+{
+ int ret;
+ 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_revers_bias_clear = pdata->gpio_revers_bias_clear;
+ mc->gpio_revers_bias_restore = pdata->gpio_revers_bias_restore;
+ mc->gpio_sim_detect = pdata->gpio_sim_detect;
+
+ pdev = to_platform_device(mc->dev);
+ /* mc->irq_phone_active = platform_get_irq(pdev, 0); */
+ mc->irq_phone_active = gpio_to_irq(mc->gpio_phone_active);
+
+ if (mc->gpio_sim_detect)
+ mc->irq_sim_detect = gpio_to_irq(mc->gpio_sim_detect);
+
+ xmm6260_get_ops(mc);
+
+ /* initialize phone active */
+ ret = request_irq(mc->irq_phone_active, phone_active_irq_handler,
+ IRQF_NO_SUSPEND | IRQF_TRIGGER_HIGH,
+ "phone_active", mc);
+ if (ret) {
+ mif_err("failed to request_irq:%d\n", ret);
+ goto err_phone_active_request_irq;
+ }
+
+ ret = enable_irq_wake(mc->irq_phone_active);
+ if (ret) {
+ mif_err("failed to enable_irq_wake:%d\n", ret);
+ goto err_phone_active_set_wake_irq;
+ }
+
+ /* initialize sim_state if gpio_sim_detect exists */
+ mc->sim_state.online = false;
+ mc->sim_state.changed = false;
+ if (mc->gpio_sim_detect) {
+ ret = request_irq(mc->irq_sim_detect, sim_detect_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "sim_detect", mc);
+ if (ret) {
+ mif_err("failed to request_irq: %d\n", ret);
+ goto err_sim_detect_request_irq;
+ }
+
+ ret = enable_irq_wake(mc->irq_sim_detect);
+ if (ret) {
+ mif_err("failed to enable_irq_wake: %d\n", ret);
+ goto err_sim_detect_set_wake_irq;
+ }
+
+ /* initialize sim_state => insert: gpio=0, remove: gpio=1 */
+ mc->sim_state.online = !gpio_get_value(mc->gpio_sim_detect);
+ }
+
+ return ret;
+
+err_sim_detect_set_wake_irq:
+ free_irq(mc->irq_sim_detect, mc);
+err_sim_detect_request_irq:
+ mc->sim_state.online = false;
+ mc->sim_state.changed = false;
+err_phone_active_set_wake_irq:
+ free_irq(mc->irq_phone_active, mc);
+err_phone_active_request_irq:
+ return ret;
+}
diff --git a/drivers/misc/modem_if/modem_modemctl_device_xmm6262.c b/drivers/misc/modem_if/modem_modemctl_device_xmm6262.c
new file mode 100644
index 00000000000..e4000468b2c
--- /dev/null
+++ b/drivers/misc/modem_if/modem_modemctl_device_xmm6262.c
@@ -0,0 +1,261 @@
+/* /linux/drivers/misc/modem_if/modem_modemctl_device_xmm6262.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.
+ *
+ */
+
+#define DEBUG
+
+#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/cma.h>
+#include <plat/devs.h>
+#include <linux/platform_data/modem.h>
+#include "modem_prj.h"
+
+static int xmm6262_on(struct modem_ctl *mc)
+{
+ mif_info("\n");
+
+ if (!mc->gpio_cp_reset || !mc->gpio_cp_on || !mc->gpio_reset_req_n) {
+ mif_err("no gpio data\n");
+ return -ENXIO;
+ }
+
+ if (mc->gpio_revers_bias_clear)
+ mc->gpio_revers_bias_clear();
+
+ /* TODO */
+ gpio_set_value(mc->gpio_reset_req_n, 0);
+ gpio_set_value(mc->gpio_cp_on, 0);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(100);
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ /* If XMM6262 was connected with C2C, AP wait 50ms to BB Reset*/
+ msleep(50);
+ gpio_set_value(mc->gpio_reset_req_n, 1);
+
+ gpio_set_value(mc->gpio_cp_on, 1);
+ udelay(60);
+ gpio_set_value(mc->gpio_cp_on, 0);
+ msleep(20);
+ if (mc->gpio_revers_bias_restore)
+ mc->gpio_revers_bias_restore();
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ mc->phone_state = STATE_BOOTING;
+
+ return 0;
+}
+
+static int xmm6262_off(struct modem_ctl *mc)
+{
+ mif_info("\n");
+
+ if (!mc->gpio_cp_reset || !mc->gpio_cp_on) {
+ mif_err("no gpio data\n");
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_cp_on, 0);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+
+ return 0;
+}
+
+static int xmm6262_reset(struct modem_ctl *mc)
+{
+ mif_info("\n");
+
+ if (!mc->gpio_cp_reset || !mc->gpio_reset_req_n)
+ return -ENXIO;
+
+ if (mc->gpio_revers_bias_clear)
+ mc->gpio_revers_bias_clear();
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ gpio_set_value(mc->gpio_reset_req_n, 0);
+
+ mc->phone_state = STATE_OFFLINE;
+
+ msleep(20);
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ /* TODO: check the reset timming with C2C connection */
+ udelay(160);
+
+ gpio_set_value(mc->gpio_reset_req_n, 1);
+ udelay(100);
+
+ gpio_set_value(mc->gpio_cp_on, 1);
+ udelay(60);
+ gpio_set_value(mc->gpio_cp_on, 0);
+ msleep(20);
+
+ if (mc->gpio_revers_bias_restore)
+ mc->gpio_revers_bias_restore();
+
+ mc->phone_state = STATE_BOOTING;
+
+ return 0;
+}
+
+static irqreturn_t phone_active_irq_handler(int irq, void *_mc)
+{
+ int phone_reset = 0;
+ int phone_active_value = 0;
+ int cp_dump_value = 0;
+ int phone_state = 0;
+ struct modem_ctl *mc = (struct modem_ctl *)_mc;
+
+ disable_irq_nosync(mc->irq_phone_active);
+
+ if (!mc->gpio_cp_reset || !mc->gpio_phone_active ||
+ !mc->gpio_cp_dump_int) {
+ mif_err("no gpio data\n");
+ return IRQ_HANDLED;
+ }
+
+ phone_reset = gpio_get_value(mc->gpio_cp_reset);
+ phone_active_value = gpio_get_value(mc->gpio_phone_active);
+ cp_dump_value = gpio_get_value(mc->gpio_cp_dump_int);
+
+ mif_info("PA EVENT : reset =%d, pa=%d, cp_dump=%d\n",
+ phone_reset, phone_active_value, cp_dump_value);
+
+ if (phone_reset && phone_active_value)
+ phone_state = STATE_BOOTING;
+ else if (phone_reset && !phone_active_value) {
+ if (cp_dump_value)
+ phone_state = STATE_CRASH_EXIT;
+ else
+ phone_state = STATE_CRASH_RESET;
+ } else
+ phone_state = STATE_OFFLINE;
+
+ if (mc->iod && mc->iod->modem_state_changed)
+ mc->iod->modem_state_changed(mc->iod, phone_state);
+
+ if (mc->bootd && mc->bootd->modem_state_changed)
+ mc->bootd->modem_state_changed(mc->bootd, 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);
+ enable_irq(mc->irq_phone_active);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t sim_detect_irq_handler(int irq, void *_mc)
+{
+ struct modem_ctl *mc = (struct modem_ctl *)_mc;
+
+ if (mc->iod && mc->iod->sim_state_changed)
+ mc->iod->sim_state_changed(mc->iod,
+ gpio_get_value(mc->gpio_sim_detect) == mc->sim_polarity
+ );
+
+ return IRQ_HANDLED;
+}
+
+static void xmm6262_get_ops(struct modem_ctl *mc)
+{
+ mc->ops.modem_on = xmm6262_on;
+ mc->ops.modem_off = xmm6262_off;
+ mc->ops.modem_reset = xmm6262_reset;
+}
+
+int xmm6262_init_modemctl_device(struct modem_ctl *mc,
+ struct modem_data *pdata)
+{
+ int ret = 0;
+ struct platform_device *pdev;
+
+ mc->gpio_reset_req_n = pdata->gpio_reset_req_n;
+ mc->gpio_cp_on = pdata->gpio_cp_on;
+ 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_ap_dump_int = pdata->gpio_ap_dump_int;
+ mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel;
+ mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset;
+ mc->gpio_sim_detect = pdata->gpio_sim_detect;
+ mc->sim_polarity = pdata->sim_polarity;
+
+ mc->gpio_revers_bias_clear = pdata->gpio_revers_bias_clear;
+ mc->gpio_revers_bias_restore = pdata->gpio_revers_bias_restore;
+
+ pdev = to_platform_device(mc->dev);
+ mc->irq_phone_active = gpio_to_irq(mc->gpio_phone_active);
+
+ if (mc->gpio_sim_detect)
+ mc->irq_sim_detect = gpio_to_irq(mc->gpio_sim_detect);
+
+ xmm6262_get_ops(mc);
+
+ ret = request_irq(mc->irq_phone_active, phone_active_irq_handler,
+ IRQF_NO_SUSPEND | IRQF_TRIGGER_HIGH,
+ "phone_active", mc);
+ if (ret) {
+ mif_err("failed to request_irq:%d\n", ret);
+ goto err_phone_active_request_irq;
+ }
+
+ ret = enable_irq_wake(mc->irq_phone_active);
+ if (ret) {
+ mif_err("failed to enable_irq_wake:%d\n", ret);
+ goto err_phone_active_set_wake_irq;
+ }
+
+ /* initialize sim_state if gpio_sim_detect exists */
+ mc->sim_state.online = false;
+ mc->sim_state.changed = false;
+ if (mc->gpio_sim_detect) {
+ ret = request_irq(mc->irq_sim_detect, sim_detect_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "sim_detect", mc);
+ if (ret) {
+ mif_err("failed to request_irq: %d\n", ret);
+ goto err_sim_detect_request_irq;
+ }
+
+ ret = enable_irq_wake(mc->irq_sim_detect);
+ if (ret) {
+ mif_err("failed to enable_irq_wake: %d\n", ret);
+ goto err_sim_detect_set_wake_irq;
+ }
+
+ /* initialize sim_state => insert: gpio=0, remove: gpio=1 */
+ mc->sim_state.online =
+ gpio_get_value(mc->gpio_sim_detect) == mc->sim_polarity;
+ }
+
+ return ret;
+
+err_sim_detect_set_wake_irq:
+ free_irq(mc->irq_sim_detect, mc);
+err_sim_detect_request_irq:
+ mc->sim_state.online = false;
+ mc->sim_state.changed = false;
+err_phone_active_set_wake_irq:
+ free_irq(mc->irq_phone_active, mc);
+err_phone_active_request_irq:
+ return ret;
+}
diff --git a/drivers/misc/modem_if/modem_net_flowcontrol_device.c b/drivers/misc/modem_if/modem_net_flowcontrol_device.c
new file mode 100644
index 00000000000..164f4711c25
--- /dev/null
+++ b/drivers/misc/modem_if/modem_net_flowcontrol_device.c
@@ -0,0 +1,117 @@
+/* /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.h>
+#include <linux/module.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) {
+ mif_err("device = %s not exist\n", dev_name);
+ return -ENODEV;
+ }
+
+ switch (cmd) {
+ case IOCTL_MODEM_NET_SUSPEND:
+ netif_stop_queue(ndev);
+ mif_info("NET SUSPEND(%s)\n", dev_name);
+ break;
+ case IOCTL_MODEM_NET_RESUME:
+ netif_wake_queue(ndev);
+ mif_info("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) {
+ mif_err("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) {
+ mif_err("failed to register misc br device : %s\n",
+ net_flowcontrol_dev->miscdev.name);
+ kfree(net_flowcontrol_dev);
+ }
+
+ 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/modem_prj.h b/drivers/misc/modem_if/modem_prj.h
new file mode 100644
index 00000000000..4780bef3676
--- /dev/null
+++ b/drivers/misc/modem_if/modem_prj.h
@@ -0,0 +1,725 @@
+/*
+ * 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>
+#include <linux/completion.h>
+#include <linux/wakelock.h>
+#include <linux/rbtree.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#define MAX_CPINFO_SIZE 512
+
+#define MAX_LINK_DEVTYPE 3
+
+#define MAX_FMT_DEVS 10
+#define MAX_RAW_DEVS 32
+#define MAX_RFS_DEVS 10
+#define MAX_NUM_IO_DEV (MAX_FMT_DEVS + MAX_RAW_DEVS + MAX_RFS_DEVS)
+
+#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_BOOT_DONE _IO('o', 0x24)
+
+#define IOCTL_MODEM_PROTOCOL_SUSPEND _IO('o', 0x25)
+#define IOCTL_MODEM_PROTOCOL_RESUME _IO('o', 0x26)
+
+#define IOCTL_MODEM_STATUS _IO('o', 0x27)
+#define IOCTL_MODEM_DL_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_CP_UPLOAD _IO('o', 0x35)
+#define IOCTL_MODEM_DUMP_RESET _IO('o', 0x36)
+
+#define IOCTL_DPRAM_SEND_BOOT _IO('o', 0x40)
+#define IOCTL_DPRAM_INIT_STATUS _IO('o', 0x43)
+
+/* ioctl command definitions. */
+#define IOCTL_DPRAM_PHONE_POWON _IO('o', 0xd0)
+#define IOCTL_DPRAM_PHONEIMG_LOAD _IO('o', 0xd1)
+#define IOCTL_DPRAM_NVDATA_LOAD _IO('o', 0xd2)
+#define IOCTL_DPRAM_PHONE_BOOTSTART _IO('o', 0xd3)
+
+#define IOCTL_DPRAM_PHONE_UPLOAD_STEP1 _IO('o', 0xde)
+#define IOCTL_DPRAM_PHONE_UPLOAD_STEP2 _IO('o', 0xdf)
+
+/* ioctl command for IPC Logger */
+#define IOCTL_MIF_LOG_DUMP _IO('o', 0x51)
+#define IOCTL_MIF_DPRAM_DUMP _IO('o', 0x52)
+
+/* modem status */
+#define MODEM_OFF 0
+#define MODEM_CRASHED 1
+#define MODEM_RAMDUMP 2
+#define MODEM_POWER_ON 3
+#define MODEM_BOOTING_NORMAL 4
+#define MODEM_BOOTING_RAMDUMP 5
+#define MODEM_DUMPING 6
+#define MODEM_RUNNING 7
+
+#define HDLC_HEADER_MAX_SIZE 6 /* fmt 3, raw 6, rfs 6 */
+
+#define PSD_DATA_CHID_BEGIN 0x2A
+#define PSD_DATA_CHID_END 0x38
+
+#define PS_DATA_CH_0 10
+#define PS_DATA_CH_LAST 24
+#define RMNET0_CH_ID PS_DATA_CH_0
+
+#define IP6VERSION 6
+
+#define SOURCE_MAC_ADDR {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}
+
+/* IP loopback */
+#define CP2AP_LOOPBACK_CHANNEL 30 /* CP -> AP -> CP */
+#define DATA_LOOPBACK_CHANNEL 31 /* AP -> CP -> AP */
+
+/* Debugging features */
+#define MIF_LOG_DIR "/sdcard/log"
+#define MIF_MAX_PATH_LEN 256
+#define MIF_MAX_NAME_LEN 64
+#define MIF_MAX_STR_LEN 32
+
+/* Does modem ctl structure will use state ? or status defined below ?*/
+enum modem_state {
+ STATE_OFFLINE,
+ STATE_CRASH_RESET, /* silent reset */
+ STATE_CRASH_EXIT, /* cp ramdump */
+ STATE_BOOTING,
+ STATE_ONLINE,
+ STATE_NV_REBUILDING, /* <= rebuilding start */
+ STATE_LOADER_DONE,
+ STATE_SIM_ATTACH,
+ STATE_SIM_DETACH,
+};
+
+enum com_state {
+ COM_NONE,
+ COM_ONLINE,
+ COM_HANDSHAKE,
+ COM_BOOT,
+ COM_CRASH,
+};
+
+enum link_mode {
+ LINK_MODE_OFFLINE = 0,
+ LINK_MODE_BOOT,
+ LINK_MODE_IPC,
+ LINK_MODE_DLOAD,
+ LINK_MODE_ULOAD,
+};
+
+struct sim_state {
+ bool online; /* SIM is online? */
+ bool changed; /* online is changed? */
+};
+
+#define HDLC_START 0x7F
+#define HDLC_END 0x7E
+#define SIZE_OF_HDLC_START 1
+#define SIZE_OF_HDLC_END 1
+#define MAX_LINK_PADDING_SIZE 3
+
+struct header_data {
+ char hdr[HDLC_HEADER_MAX_SIZE];
+ unsigned len;
+ unsigned frag_len;
+ char start; /*hdlc start header 0x7F*/
+};
+
+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;
+
+struct sipc_fmt_hdr {
+ u16 len;
+ u8 msg_seq;
+ u8 ack_seq;
+ u8 main_cmd;
+ u8 sub_cmd;
+ u8 cmd_type;
+} __packed;
+
+#define SIPC5_START_MASK 0b11111000
+#define SIPC5_CONFIG_MASK 0b00000111
+#define SIPC5_EXT_FIELD_MASK 0b00000011
+
+#define SIPC5_PADDING_EXIST 0b00000100
+#define SIPC5_EXT_FIELD_EXIST 0b00000010
+#define SIPC5_CTL_FIELD_EXIST 0b00000001
+
+#define SIPC5_MAX_HEADER_SIZE 6
+#define SIPC5_HEADER_SIZE_WITH_EXT_LEN 6
+#define SIPC5_HEADER_SIZE_WITH_CTL_FLD 5
+#define SIPC5_MIN_HEADER_SIZE 4
+#define SIPC5_CONFIG_SIZE 1
+#define SIPC5_CH_ID_SIZE 1
+
+#define SIPC5_CONFIG_OFFSET 0
+#define SIPC5_CH_ID_OFFSET 1
+#define SIPC5_LEN_OFFSET 2
+#define SIPC5_CTL_OFFSET 4
+
+#define SIPC5_CH_ID_RAW_0 0
+#define SIPC5_CH_ID_PDP_0 10
+#define SIPC5_CH_ID_PDP_LAST 24
+#define SIPC5_CH_ID_FMT_0 235
+#define SIPC5_CH_ID_RFS_0 245
+#define SIPC5_CH_ID_MAX 255
+
+/* If iod->id is 0, do not need to store to `iodevs_tree_fmt' in SIPC4 */
+#define sipc4_is_not_reserved_channel(ch) ((ch) != 0)
+
+/* Channel 0, 5, 6, 27, 255 are reserved in SIPC5.
+ * see SIPC5 spec: 2.2.2 Channel Identification (Ch ID) Field.
+ * They do not need to store in `iodevs_tree_fmt'
+ */
+#define sipc5_is_not_reserved_channel(ch) \
+ ((ch) != 0 && (ch) != 5 && (ch) != 6 && (ch) != 27 && (ch) != 255)
+
+struct sipc5_link_hdr {
+ u8 cfg;
+ u8 ch;
+ u16 len;
+ union {
+ u8 ctl;
+ u16 ext_len;
+ };
+} __packed;
+
+struct sipc5_frame_data {
+ /* Config octet */
+ u8 config;
+
+ /* Channel ID */
+ u8 ch_id;
+
+ /* Control for multiple FMT frame */
+ u8 control;
+
+ /* Frame configuration set by header analysis */
+ bool padding;
+ bool ctl_fld;
+ bool ext_len;
+
+ /* Frame length calculated from the length fields */
+ unsigned len;
+
+ /* The length of link layer header */
+ unsigned hdr_len;
+
+ /* The length of received header */
+ unsigned hdr_rcvd;
+
+ /* The length of data payload */
+ unsigned data_len;
+
+ /* The length of received data */
+ unsigned data_rcvd;
+
+ /* Header buffer */
+ u8 hdr[SIPC5_MAX_HEADER_SIZE];
+};
+
+struct vnet {
+ struct io_device *iod;
+};
+
+/* for fragmented data from link devices */
+struct fragmented_data {
+ struct sk_buff *skb_recv;
+ struct header_data h_data;
+ struct sipc5_frame_data f_data;
+ /* page alloc fail retry*/
+ unsigned realloc_offset;
+};
+#define fragdata(iod, ld) (&(iod)->fragments[(ld)->link_type])
+
+/** struct skbuff_priv - private data of struct sk_buff
+ * this is matched to char cb[48] of struct sk_buff
+ */
+struct skbuff_private {
+ struct io_device *iod;
+ struct link_device *ld;
+ struct io_device *real_iod; /* for rx multipdp */
+ u8 ch_id;
+ u8 control;
+} __packed;
+
+static inline struct skbuff_private *skbpriv(struct sk_buff *skb)
+{
+ BUILD_BUG_ON(sizeof(struct skbuff_private) > sizeof(skb->cb));
+ return (struct skbuff_private *)&skb->cb;
+}
+
+struct io_device {
+ /* rb_tree node for an io device */
+ struct rb_node node_chan;
+ struct rb_node node_fmt;
+
+ /* Name of the IO device */
+ char *name;
+
+ atomic_t opened;
+
+ /* Wait queue for the IO device */
+ wait_queue_head_t wq;
+
+ /* Misc and net device structures for the IO device */
+ struct miscdevice miscdev;
+ struct net_device *ndev;
+
+ /* ID and Format for channel on the link */
+ unsigned id;
+ enum modem_link link_types;
+ enum dev_format format;
+ enum modem_io io_typ;
+ enum modem_network net_typ;
+
+ bool use_handover; /* handover 2+ link devices */
+
+ /* SIPC version */
+ enum sipc_ver ipc_version;
+
+ /* Rx queue of sk_buff */
+ 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;
+
+ struct fragmented_data fragments[LINKDEV_MAX];
+
+ /* for multi-frame */
+ struct sk_buff *skb[128];
+
+ /* called from linkdevice when a packet arrives for this iodevice */
+ int (*recv)(struct io_device *iod, struct link_device *ld,
+ const char *data, unsigned int len);
+ int (*recv_skb)(struct io_device *iod, struct link_device *ld,
+ struct sk_buff *skb);
+
+ /* 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);
+
+ /* inform the IO device that the SIM is not inserting or removing */
+ void (*sim_state_changed)(struct io_device *iod, bool sim_online);
+
+ struct modem_ctl *mc;
+ struct modem_shared *msd;
+
+ struct wake_lock wakelock;
+ long waketime;
+
+ /* DO NOT use __current_link directly
+ * you MUST use skbpriv(skb)->ld in mc, link, etc..
+ */
+ struct link_device *__current_link;
+};
+#define to_io_device(misc) container_of(misc, struct io_device, miscdev)
+
+/* get_current_link, set_current_link don't need to use locks.
+ * In ARM, set_current_link and get_current_link are compiled to
+ * each one instruction (str, ldr) as atomic_set, atomic_read.
+ * And, the order of set_current_link and get_current_link is not important.
+ */
+#define get_current_link(iod) ((iod)->__current_link)
+#define set_current_link(iod, ld) ((iod)->__current_link = (ld))
+
+struct link_device {
+ struct list_head list;
+ char *name;
+
+ enum modem_link link_type;
+ unsigned aligned;
+
+ /* Maximum IPC device = the last IPC device (e.g. IPC_RFS) + 1 */
+ int max_ipc_dev;
+
+ /* SIPC version */
+ enum sipc_ver ipc_version;
+
+ /* Modem data */
+ struct modem_data *mdm_data;
+
+ /* Modem control */
+ struct modem_ctl *mc;
+
+ /* Modem shared data */
+ struct modem_shared *msd;
+
+ /* Operation mode of the link device */
+ enum link_mode mode;
+
+ struct io_device *fmt_iods[4];
+
+ /* TX queue of socket buffers */
+ struct sk_buff_head sk_fmt_tx_q;
+ struct sk_buff_head sk_raw_tx_q;
+ struct sk_buff_head sk_rfs_tx_q;
+
+ struct sk_buff_head *skb_txq[MAX_IPC_DEV];
+
+ bool raw_tx_suspended; /* for misc dev */
+ struct completion raw_tx_resumed_by_cp;
+
+ struct workqueue_struct *tx_wq;
+ struct work_struct tx_work;
+ struct delayed_work tx_delayed_work;
+
+ struct delayed_work *tx_dwork[MAX_IPC_DEV];
+ struct delayed_work fmt_tx_dwork;
+ struct delayed_work raw_tx_dwork;
+ struct delayed_work rfs_tx_dwork;
+
+ struct workqueue_struct *rx_wq;
+ struct work_struct rx_work;
+ struct delayed_work rx_delayed_work;
+
+ enum com_state com_state;
+
+ /* 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 (*udl_start)(struct link_device *ld, struct io_device *iod);
+
+ int (*force_dump)(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);
+
+ int (*ioctl)(struct link_device *ld, struct io_device *iod,
+ unsigned cmd, unsigned long _arg);
+};
+
+/** rx_alloc_skb - allocate an skbuff and set skb's iod, ld
+ * @length: length to allocate
+ * @iod: struct io_device *
+ * @ld: struct link_device *
+ *
+ * %NULL is returned if there is no free memory.
+ */
+static inline struct sk_buff *rx_alloc_skb(unsigned int length,
+ struct io_device *iod, struct link_device *ld)
+{
+ struct sk_buff *skb;
+
+ if (iod->format == IPC_MULTI_RAW || iod->format == IPC_RAW)
+ skb = dev_alloc_skb(length);
+ else
+ skb = alloc_skb(length, GFP_ATOMIC);
+
+ if (likely(skb)) {
+ skbpriv(skb)->iod = iod;
+ skbpriv(skb)->ld = ld;
+ }
+ return skb;
+}
+
+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_boot_done) (struct modem_ctl *);
+ int (*modem_force_crash_exit) (struct modem_ctl *);
+ int (*modem_dump_reset) (struct modem_ctl *);
+};
+
+/* for IPC Logger */
+struct mif_storage {
+ char *addr;
+ unsigned int cnt;
+};
+
+/* modem_shared - shared data for all io/link devices and a modem ctl
+ * msd : mc : iod : ld = 1 : 1 : M : N
+ */
+struct modem_shared {
+ /* list of link devices */
+ struct list_head link_dev_list;
+
+ /* rb_tree root of io devices. */
+ struct rb_root iodevs_tree_chan; /* group by channel */
+ struct rb_root iodevs_tree_fmt; /* group by dev_format */
+
+ /* for IPC Logger */
+ struct mif_storage storage;
+ spinlock_t lock;
+
+ /* loopbacked IP address
+ * default is 0.0.0.0 (disabled)
+ * after you setted this, you can use IP packet loopback using this IP.
+ * exam: echo 1.2.3.4 > /sys/devices/virtual/misc/umts_multipdp/loopback
+ */
+ __be32 loopback_ipaddr;
+};
+
+struct modem_ctl {
+ struct device *dev;
+ char *name;
+ struct modem_data *mdm_data;
+
+ struct modem_shared *msd;
+
+ enum modem_state phone_state;
+ struct sim_state sim_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_ap_dump_int;
+ unsigned gpio_flm_uart_sel;
+#if defined(CONFIG_MACH_M0_CTC)
+ unsigned gpio_flm_uart_sel_rev06;
+#endif
+ unsigned gpio_cp_warm_reset;
+ unsigned gpio_cp_off;
+ unsigned gpio_sim_detect;
+ unsigned gpio_dynamic_switching;
+
+ int irq_phone_active;
+ int irq_sim_detect;
+
+#ifdef CONFIG_LTE_MODEM_CMC221
+ 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;
+#endif /*CONFIG_LTE_MODEM_CMC221*/
+#if defined(CONFIG_MACH_GRANDE)
+ struct delayed_work sim_det_dwork;
+#endif /* For checking sim detect pin */
+ struct work_struct work;
+
+#if defined(CONFIG_MACH_U1_KOR_LGT)
+ unsigned gpio_cp_reset_msm;
+ unsigned gpio_boot_sw_sel;
+ void (*vbus_on)(void);
+ void (*vbus_off)(void);
+ bool usb_boot;
+#endif
+
+ struct modemctl_ops ops;
+ struct io_device *iod;
+ struct io_device *bootd;
+
+ /* Wakelock for modem_ctl */
+ struct wake_lock mc_wake_lock;
+
+ void (*gpio_revers_bias_clear)(void);
+ void (*gpio_revers_bias_restore)(void);
+
+ bool need_switch_to_usb;
+ bool sim_polarity;
+};
+
+int sipc4_init_io_device(struct io_device *iod);
+int sipc5_init_io_device(struct io_device *iod);
+
+/**
+ * get_dev_name
+ * @dev: IPC device (enum dev_format)
+ *
+ * Returns IPC device name as a string.
+ *
+ */
+static const inline char *get_dev_name(int dev)
+{
+ if (dev == IPC_FMT)
+ return "FMT";
+ else if (dev == IPC_RAW)
+ return "RAW";
+ else if (dev == IPC_RFS)
+ return "RFS";
+ else if (dev == IPC_BOOT)
+ return "BOOT";
+ else if (dev == IPC_RAMDUMP)
+ return "DUMP";
+ else
+ return "NONE";
+}
+
+/**
+ * sipc5_start_valid
+ * @cfg: configuration field of an SIPC5 link frame
+ *
+ * Returns TRUE if the start (configuration field) of an SIPC5 link frame
+ * is valid or returns FALSE if it is not valid.
+ *
+ */
+static inline int sipc5_start_valid(u8 cfg)
+{
+ return (cfg & SIPC5_START_MASK) == SIPC5_START_MASK;
+}
+
+/**
+ * sipc5_get_hdr_len
+ * @cfg: configuration field of an SIPC5 link frame
+ *
+ * Returns the length of SIPC5 link layer header in an SIPC5 link frame
+ *
+ */
+static inline unsigned sipc5_get_hdr_len(u8 cfg)
+{
+ if (cfg & SIPC5_EXT_FIELD_EXIST) {
+ if (cfg & SIPC5_CTL_FIELD_EXIST)
+ return SIPC5_HEADER_SIZE_WITH_CTL_FLD;
+ else
+ return SIPC5_HEADER_SIZE_WITH_EXT_LEN;
+ } else {
+ return SIPC5_MIN_HEADER_SIZE;
+ }
+}
+
+/**
+ * sipc5_get_ch_id
+ * @frm: pointer to an SIPC5 frame
+ *
+ * Returns the channel ID in an SIPC5 link frame
+ *
+ */
+static inline u8 sipc5_get_ch_id(u8 *frm)
+{
+ return *(frm + SIPC5_CH_ID_OFFSET);
+}
+
+/**
+ * sipc5_get_frame_sz16
+ * @frm: pointer to an SIPC5 link frame
+ *
+ * Returns the length of an SIPC5 link frame without the extended length field
+ *
+ */
+static inline unsigned sipc5_get_frame_sz16(u8 *frm)
+{
+ return *((u16 *)(frm + SIPC5_LEN_OFFSET));
+}
+
+/**
+ * sipc5_get_frame_sz32
+ * @frm: pointer to an SIPC5 frame
+ *
+ * Returns the length of an SIPC5 link frame with the extended length field
+ *
+ */
+static inline unsigned sipc5_get_frame_sz32(u8 *frm)
+{
+ return *((u32 *)(frm + SIPC5_LEN_OFFSET));
+}
+
+/**
+ * sipc5_calc_padding_size
+ * @len: length of an SIPC5 link frame
+ *
+ * Returns the padding size for an SIPC5 link frame
+ *
+ */
+static inline unsigned sipc5_calc_padding_size(unsigned len)
+{
+ unsigned residue = len & 0x3;
+ return residue ? (4 - residue) : 0;
+}
+
+/**
+ * sipc5_check_frame_in_dev
+ * @ld: pointer to the link device structure
+ * @dev: IPC device (enum dev_format)
+ * @frm: pointer to the start of an SIPC5 frame
+ * @rest: size of the rest data in the device buffer including this frame
+ *
+ * Returns
+ * < 0 : error
+ * == 0 : no data
+ * > 0 : valid data
+ *
+ */
+static inline int sipc5_check_frame_in_dev(struct link_device *ld, int dev,
+ u8 *frm, int rest)
+{
+ unsigned int len;
+
+ if (unlikely(!sipc5_start_valid(frm[0]))) {
+ mif_err("%s: ERR! %s invalid start 0x%02X\n",
+ ld->name, get_dev_name(dev), frm[0]);
+ return -EBADMSG;
+ }
+
+ len = sipc5_get_frame_sz16(frm);
+ if (unlikely(len > rest)) {
+ mif_err("%s: ERR! %s len %d > rest %d\n",
+ ld->name, get_dev_name(dev), len, rest);
+ return -EBADMSG;
+ }
+
+ return len;
+}
+
+extern void set_sromc_access(bool access);
+
+#endif
diff --git a/drivers/misc/modem_if/modem_sim_slot_switch.c b/drivers/misc/modem_if/modem_sim_slot_switch.c
new file mode 100644
index 00000000000..1dd4c6764ce
--- /dev/null
+++ b/drivers/misc/modem_if/modem_sim_slot_switch.c
@@ -0,0 +1,92 @@
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+
+#include <plat/gpio-cfg.h>
+
+#include <mach/gpio.h>
+
+extern struct class *sec_class;
+struct device *slot_switch_dev;
+
+static ssize_t get_slot_switch(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int value;
+
+ //return '0' slot path is '||', return '1' slot path is 'X'
+ value = gpio_get_value(GPIO_UIM_SIM_SEL);
+ printk("Current Slot is %x\n", value);
+
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t set_slot_switch(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ int value;
+
+ sscanf(buf, "%d", &value);
+
+ switch(value) {
+ case 0:
+ gpio_set_value(GPIO_UIM_SIM_SEL, 0);
+ printk("set slot switch to %x\n", gpio_get_value(GPIO_UIM_SIM_SEL));
+ break;
+ case 1:
+ gpio_set_value(GPIO_UIM_SIM_SEL, 1);
+ printk("set slot switch to %x\n", gpio_get_value(GPIO_UIM_SIM_SEL));
+ break;
+ default:
+ printk("Enter 0 or 1!!\n");
+ }
+
+ return size;
+}
+
+static DEVICE_ATTR(slot_sel, S_IRUGO |S_IWUGO | S_IRUSR | S_IWUSR, get_slot_switch, set_slot_switch);
+
+static int __init slot_switch_manager_init(void)
+{
+ int ret = 0;
+ int err = 0;
+
+ printk("slot_switch_manager_init\n");
+
+ //initailize uim_sim_switch gpio
+ err = gpio_request(GPIO_UIM_SIM_SEL, "PDA_ACTIVE");
+ if (err) {
+ pr_err("fail to request gpio %s, gpio %d, errno %d\n",
+ "PDA_ACTIVE", GPIO_UIM_SIM_SEL, err);
+ } else {
+ gpio_direction_output(GPIO_UIM_SIM_SEL, 1);
+ s3c_gpio_setpull(GPIO_UIM_SIM_SEL, S3C_GPIO_PULL_NONE);
+#if defined(CONFIG_MACH_T0_CHN_CTC)
+ gpio_set_value(GPIO_UIM_SIM_SEL, 1);
+#else
+ gpio_set_value(GPIO_UIM_SIM_SEL, 0);
+#endif
+ }
+
+ //initailize slot switch device
+ slot_switch_dev = device_create(sec_class,
+ NULL, 0, NULL, "slot_switch");
+ if (IS_ERR(slot_switch_dev))
+ pr_err("Failed to create device(switch)!\n");
+
+ if (device_create_file(slot_switch_dev, &dev_attr_slot_sel) < 0)
+ pr_err("Failed to create device file(%s)!\n",
+ dev_attr_slot_sel.attr.name);
+
+ return ret;
+}
+
+static void __exit slot_switch_manager_exit(void)
+{
+}
+
+module_init(slot_switch_manager_init);
+module_exit(slot_switch_manager_exit);
+
+MODULE_AUTHOR("SAMSUNG ELECTRONICS CO., LTD");
+MODULE_DESCRIPTION("Slot Switch");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/modem_if/modem_utils.c b/drivers/misc/modem_if/modem_utils.c
new file mode 100644
index 00000000000..a2def11dc7e
--- /dev/null
+++ b/drivers/misc/modem_if/modem_utils.c
@@ -0,0 +1,1227 @@
+/*
+ * 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/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/miscdevice.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <net/ip.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/rtc.h>
+#include <linux/time.h>
+
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/time.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.h>
+#include "modem_prj.h"
+#include "modem_variation.h"
+#include "modem_utils.h"
+
+#define CMD_SUSPEND ((unsigned short)(0x00CA))
+#define CMD_RESUME ((unsigned short)(0x00CB))
+
+#define TX_SEPARATOR "mif: >>>>>>>>>> Outgoing packet "
+#define RX_SEPARATOR "mif: Incoming packet <<<<<<<<<<"
+#define LINE_SEPARATOR \
+ "mif: ------------------------------------------------------------"
+#define LINE_BUFF_SIZE 80
+
+static const char *hex = "0123456789abcdef";
+
+void ts2utc(struct timespec *ts, struct utc_time *utc)
+{
+ struct tm tm;
+
+ time_to_tm((ts->tv_sec - (sys_tz.tz_minuteswest * 60)), 0, &tm);
+ utc->year = 1900 + tm.tm_year;
+ utc->mon = 1 + tm.tm_mon;
+ utc->day = tm.tm_mday;
+ utc->hour = tm.tm_hour;
+ utc->min = tm.tm_min;
+ utc->sec = tm.tm_sec;
+ utc->msec = (ts->tv_nsec > 0) ? (ts->tv_nsec / 1000000) : 0;
+}
+
+void get_utc_time(struct utc_time *utc)
+{
+ struct timespec ts;
+ getnstimeofday(&ts);
+ ts2utc(&ts, utc);
+}
+
+#ifdef CONFIG_LINK_DEVICE_DPRAM
+#include "modem_link_device_dpram.h"
+int mif_dump_dpram(struct io_device *iod)
+{
+ struct link_device *ld = get_current_link(iod);
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ u32 size = dpld->size;
+ unsigned long read_len = 0;
+ struct sk_buff *skb;
+ char *buff;
+
+ buff = kzalloc(size, GFP_ATOMIC);
+ if (!buff) {
+ mif_err("ERR! kzalloc fail\n");
+ return -ENOMEM;
+ } else {
+ dpld->dpram_dump(ld, buff);
+ }
+
+ while (read_len < size) {
+ skb = alloc_skb(MAX_IPC_SKB_SIZE, GFP_ATOMIC);
+ if (!skb) {
+ mif_err("ERR! alloc_skb fail\n");
+ kfree(buff);
+ return -ENOMEM;
+ }
+ memcpy(skb_put(skb, MAX_IPC_SKB_SIZE),
+ buff + read_len, MAX_IPC_SKB_SIZE);
+ skb_queue_tail(&iod->sk_rx_q, skb);
+ read_len += MAX_IPC_SKB_SIZE;
+ wake_up(&iod->wq);
+ }
+ kfree(buff);
+ return 0;
+}
+#endif
+
+int mif_dump_log(struct modem_shared *msd, struct io_device *iod)
+{
+ struct sk_buff *skb;
+ unsigned long read_len = 0;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&msd->lock, flags);
+ while (read_len < MAX_MIF_BUFF_SIZE) {
+ skb = alloc_skb(MAX_IPC_SKB_SIZE, GFP_ATOMIC);
+ if (!skb) {
+ mif_err("ERR! alloc_skb fail\n");
+ spin_unlock_irqrestore(&msd->lock, flags);
+ return -ENOMEM;
+ }
+ memcpy(skb_put(skb, MAX_IPC_SKB_SIZE),
+ msd->storage.addr + read_len, MAX_IPC_SKB_SIZE);
+ skb_queue_tail(&iod->sk_rx_q, skb);
+ read_len += MAX_IPC_SKB_SIZE;
+ wake_up(&iod->wq);
+ }
+ spin_unlock_irqrestore(&msd->lock, flags);
+ return 0;
+}
+
+static unsigned long long get_kernel_time(void)
+{
+ int this_cpu;
+ unsigned long flags;
+ unsigned long long time;
+
+ preempt_disable();
+ raw_local_irq_save(flags);
+
+ this_cpu = smp_processor_id();
+ time = cpu_clock(this_cpu);
+
+ preempt_enable();
+ raw_local_irq_restore(flags);
+
+ return time;
+}
+
+void mif_ipc_log(enum mif_log_id id,
+ struct modem_shared *msd, const char *data, size_t len)
+{
+ struct mif_ipc_block *block;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&msd->lock, flags);
+
+ block = (struct mif_ipc_block *)
+ (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt));
+ msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ?
+ msd->storage.cnt + 1 : 0;
+
+ spin_unlock_irqrestore(&msd->lock, flags);
+
+ block->id = id;
+ block->time = get_kernel_time();
+ block->len = (len > MAX_IPC_LOG_SIZE) ? MAX_IPC_LOG_SIZE : len;
+ memcpy(block->buff, data, block->len);
+}
+
+void _mif_irq_log(enum mif_log_id id, struct modem_shared *msd,
+ struct mif_irq_map map, const char *data, size_t len)
+{
+ struct mif_irq_block *block;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&msd->lock, flags);
+
+ block = (struct mif_irq_block *)
+ (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt));
+ msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ?
+ msd->storage.cnt + 1 : 0;
+
+ spin_unlock_irqrestore(&msd->lock, flags);
+
+ block->id = id;
+ block->time = get_kernel_time();
+ memcpy(&(block->map), &map, sizeof(struct mif_irq_map));
+ if (data)
+ memcpy(block->buff, data,
+ (len > MAX_IRQ_LOG_SIZE) ? MAX_IRQ_LOG_SIZE : len);
+}
+
+void _mif_com_log(enum mif_log_id id,
+ struct modem_shared *msd, const char *format, ...)
+{
+ struct mif_common_block *block;
+ unsigned long int flags;
+ va_list args;
+ int ret;
+
+ spin_lock_irqsave(&msd->lock, flags);
+
+ block = (struct mif_common_block *)
+ (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt));
+ msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ?
+ msd->storage.cnt + 1 : 0;
+
+ spin_unlock_irqrestore(&msd->lock, flags);
+
+ block->id = id;
+ block->time = get_kernel_time();
+
+ va_start(args, format);
+ ret = vsnprintf(block->buff, MAX_COM_LOG_SIZE, format, args);
+ va_end(args);
+}
+
+void _mif_time_log(enum mif_log_id id, struct modem_shared *msd,
+ struct timespec epoch, const char *data, size_t len)
+{
+ struct mif_time_block *block;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&msd->lock, flags);
+
+ block = (struct mif_time_block *)
+ (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt));
+ msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ?
+ msd->storage.cnt + 1 : 0;
+
+ spin_unlock_irqrestore(&msd->lock, flags);
+
+ block->id = id;
+ block->time = get_kernel_time();
+ memcpy(&block->epoch, &epoch, sizeof(struct timespec));
+
+ if (data)
+ memcpy(block->buff, data,
+ (len > MAX_IRQ_LOG_SIZE) ? MAX_IRQ_LOG_SIZE : len);
+}
+
+/* dump2hex
+ * dump data to hex as fast as possible.
+ * the length of @buff must be greater than "@len * 3"
+ * it need 3 bytes per one data byte to print.
+ */
+static inline int dump2hex(char *buff, const char *data, size_t len)
+{
+ char *dest = buff;
+ 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 - buff;
+}
+
+void pr_ipc(const char *tag, const char *data, size_t len)
+{
+ struct utc_time utc;
+ unsigned char str[128];
+
+ get_utc_time(&utc);
+ dump2hex(str, data, (len > 32 ? 32 : len));
+ pr_info("%s: %s: [%02d:%02d:%02d.%03d] %s\n",
+ MIF_TAG, tag, utc.hour, utc.min, utc.sec, utc.msec, str);
+}
+
+/* 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 str[len ? len * 3 : 1]; /* 1 <= sizeof <= max_len*3 */
+ dump2hex(str, data, len);
+
+ /* don't change this printk to mif_debug for print this as level7 */
+ return printk(KERN_INFO "%s: %s(%u): %s%s\n", MIF_TAG, tag, data_len,
+ str, (len == data_len) ? "" : " ...");
+}
+
+/* flow control CM from CP, it use in serial devices */
+int link_rx_flowctl_cmd(struct link_device *ld, const char *data, size_t len)
+{
+ struct modem_shared *msd = ld->msd;
+ unsigned short *cmd, *end = (unsigned short *)(data + len);
+
+ mif_debug("flow control cmd: size=%d\n", len);
+
+ for (cmd = (unsigned short *)data; cmd < end; cmd++) {
+ switch (*cmd) {
+ case CMD_SUSPEND:
+ iodevs_for_each(msd, iodev_netif_stop, 0);
+ ld->raw_tx_suspended = true;
+ mif_info("flowctl CMD_SUSPEND(%04X)\n", *cmd);
+ break;
+
+ case CMD_RESUME:
+ iodevs_for_each(msd, iodev_netif_wake, 0);
+ ld->raw_tx_suspended = false;
+ complete_all(&ld->raw_tx_resumed_by_cp);
+ mif_info("flowctl CMD_RESUME(%04X)\n", *cmd);
+ break;
+
+ default:
+ mif_err("flowctl BACMD: %04X\n", *cmd);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+struct io_device *get_iod_with_channel(struct modem_shared *msd,
+ unsigned channel)
+{
+ struct rb_node *n = msd->iodevs_tree_chan.rb_node;
+ struct io_device *iodev;
+ while (n) {
+ iodev = rb_entry(n, struct io_device, node_chan);
+ if (channel < iodev->id)
+ n = n->rb_left;
+ else if (channel > iodev->id)
+ n = n->rb_right;
+ else
+ return iodev;
+ }
+ return NULL;
+}
+
+struct io_device *get_iod_with_format(struct modem_shared *msd,
+ enum dev_format format)
+{
+ struct rb_node *n = msd->iodevs_tree_fmt.rb_node;
+ struct io_device *iodev;
+ while (n) {
+ iodev = rb_entry(n, struct io_device, node_fmt);
+ if (format < iodev->format)
+ n = n->rb_left;
+ else if (format > iodev->format)
+ n = n->rb_right;
+ else
+ return iodev;
+ }
+ return NULL;
+}
+
+struct io_device *insert_iod_with_channel(struct modem_shared *msd,
+ unsigned channel, struct io_device *iod)
+{
+ struct rb_node **p = &msd->iodevs_tree_chan.rb_node;
+ struct rb_node *parent = NULL;
+ struct io_device *iodev;
+ while (*p) {
+ parent = *p;
+ iodev = rb_entry(parent, struct io_device, node_chan);
+ if (channel < iodev->id)
+ p = &(*p)->rb_left;
+ else if (channel > iodev->id)
+ p = &(*p)->rb_right;
+ else
+ return iodev;
+ }
+ rb_link_node(&iod->node_chan, parent, p);
+ rb_insert_color(&iod->node_chan, &msd->iodevs_tree_chan);
+ return NULL;
+}
+
+struct io_device *insert_iod_with_format(struct modem_shared *msd,
+ enum dev_format format, struct io_device *iod)
+{
+ struct rb_node **p = &msd->iodevs_tree_fmt.rb_node;
+ struct rb_node *parent = NULL;
+ struct io_device *iodev;
+ while (*p) {
+ parent = *p;
+ iodev = rb_entry(parent, struct io_device, node_fmt);
+ if (format < iodev->format)
+ p = &(*p)->rb_left;
+ else if (format > iodev->format)
+ p = &(*p)->rb_right;
+ else
+ return iodev;
+ }
+ rb_link_node(&iod->node_fmt, parent, p);
+ rb_insert_color(&iod->node_fmt, &msd->iodevs_tree_fmt);
+ return NULL;
+}
+
+void iodevs_for_each(struct modem_shared *msd, action_fn action, void *args)
+{
+ struct io_device *iod;
+ struct rb_node *node = rb_first(&msd->iodevs_tree_chan);
+ for (; node; node = rb_next(node)) {
+ iod = rb_entry(node, struct io_device, node_chan);
+ action(iod, args);
+ }
+}
+
+void iodev_netif_wake(struct io_device *iod, void *args)
+{
+ if (iod->io_typ == IODEV_NET && iod->ndev) {
+ netif_wake_queue(iod->ndev);
+ mif_info("%s\n", iod->name);
+ }
+}
+
+void iodev_netif_stop(struct io_device *iod, void *args)
+{
+ if (iod->io_typ == IODEV_NET && iod->ndev) {
+ netif_stop_queue(iod->ndev);
+ mif_info("%s\n", iod->name);
+ }
+}
+
+static void iodev_set_tx_link(struct io_device *iod, void *args)
+{
+ struct link_device *ld = (struct link_device *)args;
+ if (iod->format == IPC_RAW && IS_CONNECTED(iod, ld)) {
+ set_current_link(iod, ld);
+ mif_err("%s -> %s\n", iod->name, ld->name);
+ }
+}
+
+void rawdevs_set_tx_link(struct modem_shared *msd, enum modem_link link_type)
+{
+ struct link_device *ld = find_linkdev(msd, link_type);
+ if (ld)
+ iodevs_for_each(msd, iodev_set_tx_link, ld);
+}
+
+void mif_netif_stop(struct link_device *ld)
+{
+ struct io_device *iod;
+
+ if (ld->ipc_version < SIPC_VER_50)
+ iod = link_get_iod_with_channel(ld, 0x20 | RMNET0_CH_ID);
+ else
+ iod = link_get_iod_with_channel(ld, RMNET0_CH_ID);
+
+ if (iod)
+ iodevs_for_each(iod->msd, iodev_netif_stop, 0);
+}
+
+void mif_netif_wake(struct link_device *ld)
+{
+ struct io_device *iod;
+
+ if (ld->ipc_version < SIPC_VER_50)
+ iod = link_get_iod_with_channel(ld, 0x20 | RMNET0_CH_ID);
+ else
+ iod = link_get_iod_with_channel(ld, RMNET0_CH_ID);
+
+ if (iod)
+ iodevs_for_each(iod->msd, iodev_netif_wake, 0);
+}
+
+/**
+ * ipv4str_to_be32 - ipv4 string to be32 (big endian 32bits integer)
+ * @return: return zero when errors occurred
+ */
+__be32 ipv4str_to_be32(const char *ipv4str, size_t count)
+{
+ unsigned char ip[4];
+ char ipstr[16]; /* == strlen("xxx.xxx.xxx.xxx") + 1 */
+ char *next = ipstr;
+ char *p;
+ int i;
+
+ strncpy(ipstr, ipv4str, ARRAY_SIZE(ipstr));
+
+ for (i = 0; i < 4; i++) {
+ p = strsep(&next, ".");
+ if (kstrtou8(p, 10, &ip[i]) < 0)
+ return 0; /* == 0.0.0.0 */
+ }
+
+ return *((__be32 *)ip);
+}
+
+void mif_add_timer(struct timer_list *timer, unsigned long expire,
+ void (*function)(unsigned long), unsigned long data)
+{
+ if (timer_pending(timer))
+ return;
+
+ init_timer(timer);
+ timer->expires = get_jiffies_64() + expire;
+ timer->function = function;
+ timer->data = data;
+ add_timer(timer);
+}
+
+void mif_print_data(const char *buff, 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 = (char *)buff + (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 = (char *)buff + (i << 4);
+ mif_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)
+ mif_err("%s\n", last);
+}
+
+void mif_dump2format16(const char *data, int len, char *buff, char *tag)
+{
+ char *d;
+ int i;
+ int words = len >> 4;
+ int residue = len - (words << 4);
+ char line[LINE_BUFF_SIZE];
+ char tb[8];
+
+ for (i = 0; i < words; i++) {
+ memset(line, 0, LINE_BUFF_SIZE);
+ d = (char *)data + (i << 4);
+
+ if (tag)
+ sprintf(line, "%s%04X| "
+ "%02x %02x %02x %02x "
+ "%02x %02x %02x %02x "
+ "%02x %02x %02x %02x "
+ "%02x %02x %02x %02x\n",
+ tag, (i << 4),
+ d[0], d[1], d[2], d[3],
+ d[4], d[5], d[6], d[7],
+ d[8], d[9], d[10], d[11],
+ d[12], d[13], d[14], d[15]);
+ else
+ sprintf(line, "%04X| "
+ "%02x %02x %02x %02x "
+ "%02x %02x %02x %02x "
+ "%02x %02x %02x %02x "
+ "%02x %02x %02x %02x\n",
+ (i << 4),
+ d[0], d[1], d[2], d[3],
+ d[4], d[5], d[6], d[7],
+ d[8], d[9], d[10], d[11],
+ d[12], d[13], d[14], d[15]);
+
+ strcat(buff, line);
+ }
+
+ /* Make the last line, if (len % 16) > 0 */
+ if (residue > 0) {
+ memset(line, 0, LINE_BUFF_SIZE);
+ memset(tb, 0, sizeof(tb));
+ d = (char *)data + (words << 4);
+
+ if (tag)
+ sprintf(line, "%s%04X|", tag, (words << 4));
+ else
+ sprintf(line, "%04X|", (words << 4));
+
+ for (i = 0; i < residue; i++) {
+ sprintf(tb, " %02x", d[i]);
+ strcat(line, tb);
+ if ((i & 0x3) == 0x3) {
+ sprintf(tb, " ");
+ strcat(line, tb);
+ }
+ }
+ strcat(line, "\n");
+
+ strcat(buff, line);
+ }
+}
+
+void mif_dump2format4(const char *data, int len, char *buff, char *tag)
+{
+ char *d;
+ int i;
+ int words = len >> 2;
+ int residue = len - (words << 2);
+ char line[LINE_BUFF_SIZE];
+ char tb[8];
+
+ for (i = 0; i < words; i++) {
+ memset(line, 0, LINE_BUFF_SIZE);
+ d = (char *)data + (i << 2);
+
+ if (tag)
+ sprintf(line, "%s%04X| %02x %02x %02x %02x\n",
+ tag, (i << 2), d[0], d[1], d[2], d[3]);
+ else
+ sprintf(line, "%04X| %02x %02x %02x %02x\n",
+ (i << 2), d[0], d[1], d[2], d[3]);
+
+ strcat(buff, line);
+ }
+
+ /* Make the last line, if (len % 4) > 0 */
+ if (residue > 0) {
+ memset(line, 0, LINE_BUFF_SIZE);
+ memset(tb, 0, sizeof(tb));
+ d = (char *)data + (words << 2);
+
+ if (tag)
+ sprintf(line, "%s%04X|", tag, (words << 2));
+ else
+ sprintf(line, "%04X|", (words << 2));
+
+ for (i = 0; i < residue; i++) {
+ sprintf(tb, " %02x", d[i]);
+ strcat(line, tb);
+ }
+ strcat(line, "\n");
+
+ strcat(buff, line);
+ }
+}
+
+void mif_print_dump(const char *data, int len, int width)
+{
+ char *buff;
+
+ buff = kzalloc(len << 3, GFP_ATOMIC);
+ if (!buff) {
+ mif_err("ERR! kzalloc fail\n");
+ return;
+ }
+
+ if (width == 16)
+ mif_dump2format16(data, len, buff, LOG_TAG);
+ else
+ mif_dump2format4(data, len, buff, LOG_TAG);
+
+ pr_info("%s", buff);
+
+ kfree(buff);
+}
+
+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);
+
+ mif_err("--------------------HDLC & FMT HEADER----------------------\n");
+
+ mif_err("HDLC: length %d, control 0x%02x\n", hh->len, hh->control);
+
+ mif_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);
+
+ mif_err("-----------------------IPC FMT DATA------------------------\n");
+
+ if (dlen > 0) {
+ if (dlen > 64)
+ dlen = 64;
+ mif_print_data(data, dlen);
+ }
+
+ mif_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;
+
+ mif_err("----------------------IPC FMT HEADER-----------------------\n");
+
+ mif_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);
+
+ mif_err("-----------------------IPC FMT DATA------------------------\n");
+
+ if (dlen > 0)
+ mif_print_data(data, dlen);
+
+ mif_err("-----------------------------------------------------------\n");
+}
+
+void print_sipc5_link_fmt_frame(const u8 *psrc)
+{
+ u8 *lf; /* Link Frame */
+ struct sipc5_link_hdr *lh; /* Link Header */
+ struct sipc_fmt_hdr *fh; /* IPC Header */
+ u16 lh_len;
+ u16 fh_len;
+ u8 *data;
+ int dlen;
+
+ lf = (u8 *)psrc;
+
+ /* Point HDLC header and IPC header */
+ lh = (struct sipc5_link_hdr *)lf;
+ if (lh->cfg & SIPC5_CTL_FIELD_EXIST)
+ lh_len = SIPC5_HEADER_SIZE_WITH_CTL_FLD;
+ else
+ lh_len = SIPC5_MIN_HEADER_SIZE;
+ fh = (struct sipc_fmt_hdr *)(lf + lh_len);
+ fh_len = sizeof(struct sipc_fmt_hdr);
+
+ /* Point IPC data */
+ data = lf + (lh_len + fh_len);
+ dlen = lh->len - (lh_len + fh_len);
+
+ mif_err("--------------------LINK & FMT HEADER----------------------\n");
+
+ mif_err("LINK: cfg 0x%02X, ch %d, len %d\n", lh->cfg, lh->ch, lh->len);
+
+ mif_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);
+
+ mif_err("-----------------------IPC FMT DATA------------------------\n");
+
+ if (dlen > 0) {
+ if (dlen > 64)
+ dlen = 64;
+ mif_print_data(data, dlen);
+ }
+
+ mif_err("-----------------------------------------------------------\n");
+}
+
+static void strcat_tcp_header(char *buff, u8 *pkt)
+{
+ struct tcphdr *tcph = (struct tcphdr *)pkt;
+ int eol;
+ char line[LINE_BUFF_SIZE];
+ char flag_str[32];
+
+/*-------------------------------------------------------------------------
+
+ 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(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE,
+ "%s: TCP:: Src.Port %u, Dst.Port %u\n",
+ MIF_TAG, ntohs(tcph->source), ntohs(tcph->dest));
+ strcat(buff, line);
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE,
+ "%s: TCP:: SEQ 0x%08X(%u), ACK 0x%08X(%u)\n",
+ MIF_TAG, ntohs(tcph->seq), ntohs(tcph->seq),
+ ntohs(tcph->ack_seq), ntohs(tcph->ack_seq));
+ strcat(buff, line);
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ memset(flag_str, 0, sizeof(flag_str));
+ if (tcph->cwr)
+ strcat(flag_str, "CWR ");
+ if (tcph->ece)
+ strcat(flag_str, "ECE");
+ if (tcph->urg)
+ strcat(flag_str, "URG ");
+ if (tcph->ack)
+ strcat(flag_str, "ACK ");
+ if (tcph->psh)
+ strcat(flag_str, "PSH ");
+ if (tcph->rst)
+ strcat(flag_str, "RST ");
+ if (tcph->syn)
+ strcat(flag_str, "SYN ");
+ if (tcph->fin)
+ strcat(flag_str, "FIN ");
+ eol = strlen(flag_str) - 1;
+ if (eol > 0)
+ flag_str[eol] = 0;
+ snprintf(line, LINE_BUFF_SIZE, "%s: TCP:: Flags {%s}\n",
+ MIF_TAG, flag_str);
+ strcat(buff, line);
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE,
+ "%s: TCP:: Window %u, Checksum 0x%04X, Urgent %u\n", MIF_TAG,
+ ntohs(tcph->window), ntohs(tcph->check), ntohs(tcph->urg_ptr));
+ strcat(buff, line);
+}
+
+static void strcat_udp_header(char *buff, u8 *pkt)
+{
+ struct udphdr *udph = (struct udphdr *)pkt;
+ char line[LINE_BUFF_SIZE];
+
+/*-------------------------------------------------------------------------
+
+ UDP Header Format
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Source Port | Destination Port |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Length | Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | data |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+-------------------------------------------------------------------------*/
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE,
+ "%s: UDP:: Src.Port %u, Dst.Port %u\n",
+ MIF_TAG, ntohs(udph->source), ntohs(udph->dest));
+ strcat(buff, line);
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE,
+ "%s: UDP:: Length %u, Checksum 0x%04X\n",
+ MIF_TAG, ntohs(udph->len), ntohs(udph->check));
+ strcat(buff, line);
+
+ if (ntohs(udph->dest) == 53) {
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE, "%s: UDP:: DNS query!!!\n",
+ MIF_TAG);
+ strcat(buff, line);
+ }
+
+ if (ntohs(udph->source) == 53) {
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE, "%s: UDP:: DNS response!!!\n",
+ MIF_TAG);
+ strcat(buff, line);
+ }
+}
+
+void print_ip4_packet(const u8 *ip_pkt, bool tx)
+{
+ char *buff;
+ struct iphdr *iph = (struct iphdr *)ip_pkt;
+ u8 *pkt = (u8 *)ip_pkt + (iph->ihl << 2);
+ u16 flags = (ntohs(iph->frag_off) & 0xE000);
+ u16 frag_off = (ntohs(iph->frag_off) & 0x1FFF);
+ int eol;
+ char line[LINE_BUFF_SIZE];
+ char flag_str[16];
+
+/*---------------------------------------------------------------------------
+ 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.
+
+---------------------------------------------------------------------------*/
+
+ if (iph->version != 4)
+ return;
+
+ buff = kzalloc(4096, GFP_ATOMIC);
+ if (!buff)
+ return;
+
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ if (tx)
+ snprintf(line, LINE_BUFF_SIZE, "\n%s\n", TX_SEPARATOR);
+ else
+ snprintf(line, LINE_BUFF_SIZE, "\n%s\n", RX_SEPARATOR);
+ strcat(buff, line);
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE, "%s\n", LINE_SEPARATOR);
+ strcat(buff, line);
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE,
+ "%s: IP4:: Version %u, Header Length %u, TOS %u, Length %u\n",
+ MIF_TAG, iph->version, (iph->ihl << 2), iph->tos,
+ ntohs(iph->tot_len));
+ strcat(buff, line);
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE, "%s: IP4:: ID %u, Fragment Offset %u\n",
+ MIF_TAG, ntohs(iph->id), frag_off);
+ strcat(buff, line);
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ memset(flag_str, 0, sizeof(flag_str));
+ if (flags & IP_CE)
+ strcat(flag_str, "CE ");
+ if (flags & IP_DF)
+ strcat(flag_str, "DF ");
+ if (flags & IP_MF)
+ strcat(flag_str, "MF ");
+ eol = strlen(flag_str) - 1;
+ if (eol > 0)
+ flag_str[eol] = 0;
+ snprintf(line, LINE_BUFF_SIZE, "%s: IP4:: Flags {%s}\n",
+ MIF_TAG, flag_str);
+ strcat(buff, line);
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE,
+ "%s: IP4:: TTL %u, Protocol %u, Header Checksum 0x%04X\n",
+ MIF_TAG, iph->ttl, iph->protocol, ntohs(iph->check));
+ strcat(buff, line);
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE,
+ "%s: IP4:: Src.IP %u.%u.%u.%u, Dst.IP %u.%u.%u.%u\n",
+ MIF_TAG, ip_pkt[12], ip_pkt[13], ip_pkt[14], ip_pkt[15],
+ ip_pkt[16], ip_pkt[17], ip_pkt[18], ip_pkt[19]);
+ strcat(buff, line);
+
+ switch (iph->protocol) {
+ case 6: /* TCP */
+ strcat_tcp_header(buff, pkt);
+ break;
+
+ case 17: /* UDP */
+ strcat_udp_header(buff, pkt);
+ break;
+
+ default:
+ break;
+ }
+
+ memset(line, 0, LINE_BUFF_SIZE);
+ snprintf(line, LINE_BUFF_SIZE, "%s\n", LINE_SEPARATOR);
+ strcat(buff, line);
+
+ pr_info("%s", buff);
+
+ kfree(buff);
+}
+
+bool is_dns_packet(const u8 *ip_pkt)
+{
+ struct iphdr *iph = (struct iphdr *)ip_pkt;
+ struct udphdr *udph = (struct udphdr *)(ip_pkt + (iph->ihl << 2));
+
+ /* If this packet is not a UDP packet, return here. */
+ if (iph->protocol != 17)
+ return false;
+
+ if (ntohs(udph->dest) == 53 || ntohs(udph->source) == 53)
+ return true;
+ else
+ return false;
+}
+
+bool is_syn_packet(const u8 *ip_pkt)
+{
+ struct iphdr *iph = (struct iphdr *)ip_pkt;
+ struct tcphdr *tcph = (struct tcphdr *)(ip_pkt + (iph->ihl << 2));
+
+ /* If this packet is not a TCP packet, return here. */
+ if (iph->protocol != 6)
+ return false;
+
+ if (tcph->syn || tcph->fin)
+ return true;
+ else
+ return false;
+}
+
+int memcmp16_to_io(const void __iomem *to, void *from, int size)
+{
+ u16 *d = (u16 *)to;
+ u16 *s = (u16 *)from;
+ int count = size >> 1;
+ int diff = 0;
+ int i;
+ u16 d1;
+ u16 s1;
+
+ for (i = 0; i < count; i++) {
+ d1 = ioread16(d);
+ s1 = *s;
+ if (d1 != s1) {
+ diff++;
+ mif_err("ERR! [%d] d:0x%04X != s:0x%04X\n", i, d1, s1);
+ }
+ d++;
+ s++;
+ }
+
+ return diff;
+}
+
+int mif_test_dpram(char *dp_name, u8 __iomem *start, u32 size)
+{
+ u8 __iomem *dst;
+ int i;
+ u16 val;
+
+ mif_info("%s: start = 0x%p, size = %d\n", dp_name, start, size);
+
+ dst = start;
+ for (i = 0; i < (size >> 1); i++) {
+ iowrite16((i & 0xFFFF), dst);
+ dst += 2;
+ }
+
+ dst = start;
+ for (i = 0; i < (size >> 1); i++) {
+ val = ioread16(dst);
+ if (val != (i & 0xFFFF)) {
+ mif_info("%s: ERR! dst[%d] 0x%04X != 0x%04X\n",
+ dp_name, i, val, (i & 0xFFFF));
+ return -EINVAL;
+ }
+ dst += 2;
+ }
+
+ dst = start;
+ for (i = 0; i < (size >> 1); i++) {
+ iowrite16(0x00FF, dst);
+ dst += 2;
+ }
+
+ dst = start;
+ for (i = 0; i < (size >> 1); i++) {
+ val = ioread16(dst);
+ if (val != 0x00FF) {
+ mif_info("%s: ERR! dst[%d] 0x%04X != 0x00FF\n",
+ dp_name, i, val);
+ return -EINVAL;
+ }
+ dst += 2;
+ }
+
+ dst = start;
+ for (i = 0; i < (size >> 1); i++) {
+ iowrite16(0x0FF0, dst);
+ dst += 2;
+ }
+
+ dst = start;
+ for (i = 0; i < (size >> 1); i++) {
+ val = ioread16(dst);
+ if (val != 0x0FF0) {
+ mif_info("%s: ERR! dst[%d] 0x%04X != 0x0FF0\n",
+ dp_name, i, val);
+ return -EINVAL;
+ }
+ dst += 2;
+ }
+
+ dst = start;
+ for (i = 0; i < (size >> 1); i++) {
+ iowrite16(0xFF00, dst);
+ dst += 2;
+ }
+
+ dst = start;
+ for (i = 0; i < (size >> 1); i++) {
+ val = ioread16(dst);
+ if (val != 0xFF00) {
+ mif_info("%s: ERR! dst[%d] 0x%04X != 0xFF00\n",
+ dp_name, i, val);
+ return -EINVAL;
+ }
+ dst += 2;
+ }
+
+ dst = start;
+ for (i = 0; i < (size >> 1); i++) {
+ iowrite16(0, dst);
+ dst += 2;
+ }
+
+ dst = start;
+ for (i = 0; i < (size >> 1); i++) {
+ val = ioread16(dst);
+ if (val != 0) {
+ mif_info("%s: ERR! dst[%d] 0x%04X != 0\n",
+ dp_name, i, val);
+ return -EINVAL;
+ }
+ dst += 2;
+ }
+
+ mif_info("%s: PASS!!!\n", dp_name);
+ return 0;
+}
+
+struct file *mif_open_file(const char *path)
+{
+ struct file *fp;
+ mm_segment_t old_fs;
+
+ old_fs = get_fs();
+ set_fs(get_ds());
+
+ fp = filp_open(path, O_RDWR|O_CREAT|O_APPEND, 0666);
+
+ set_fs(old_fs);
+
+ if (IS_ERR(fp))
+ return NULL;
+
+ return fp;
+}
+
+void mif_save_file(struct file *fp, const char *buff, size_t size)
+{
+ int ret;
+ mm_segment_t old_fs;
+
+ old_fs = get_fs();
+ set_fs(get_ds());
+
+ ret = fp->f_op->write(fp, buff, size, &fp->f_pos);
+ if (ret < 0)
+ mif_err("ERR! write fail\n");
+
+ set_fs(old_fs);
+}
+
+void mif_close_file(struct file *fp)
+{
+ mm_segment_t old_fs;
+
+ old_fs = get_fs();
+ set_fs(get_ds());
+
+ filp_close(fp, NULL);
+
+ set_fs(old_fs);
+}
+
diff --git a/drivers/misc/modem_if/modem_utils.h b/drivers/misc/modem_if/modem_utils.h
new file mode 100644
index 00000000000..219e0597ec2
--- /dev/null
+++ b/drivers/misc/modem_if/modem_utils.h
@@ -0,0 +1,315 @@
+/*
+ * 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__
+
+#include <linux/rbtree.h>
+
+#define IS_CONNECTED(iod, ld) ((iod)->link_types & LINKTYPE((ld)->link_type))
+
+#define MAX_MIF_BUFF_SIZE 0x80000 /* 512kb */
+#define MAX_MIF_SEPA_SIZE 32
+#define MIF_SEPARATOR "IPC_LOGGER(VER1.1)"
+#define MIF_SEPARATOR_DPRAM "DPRAM_LOGGER(VER1.1)"
+#define MAX_IPC_SKB_SIZE 4096
+#define MAX_LOG_SIZE 64
+
+#define MIF_TAG "mif"
+
+#define MAX_LOG_CNT (MAX_MIF_BUFF_SIZE / MAX_LOG_SIZE)
+#define MIF_ID_SIZE sizeof(enum mif_log_id)
+
+#define MAX_IPC_LOG_SIZE \
+ (MAX_LOG_SIZE - sizeof(enum mif_log_id) \
+ - sizeof(unsigned long long) - sizeof(size_t))
+#define MAX_IRQ_LOG_SIZE \
+ (MAX_LOG_SIZE - sizeof(enum mif_log_id) \
+ - sizeof(unsigned long long) - sizeof(struct mif_irq_map))
+#define MAX_COM_LOG_SIZE \
+ (MAX_LOG_SIZE - sizeof(enum mif_log_id) \
+ - sizeof(unsigned long long))
+#define MAX_TIM_LOG_SIZE \
+ (MAX_LOG_SIZE - sizeof(enum mif_log_id) \
+ - sizeof(unsigned long long) - sizeof(struct timespec))
+
+enum mif_log_id {
+ MIF_IPC_RL2AP = 1,
+ MIF_IPC_AP2CP,
+ MIF_IPC_CP2AP,
+ MIF_IPC_AP2RL,
+ MIF_IRQ,
+ MIF_COM,
+ MIF_TIME
+};
+
+struct mif_irq_map {
+ u16 magic;
+ u16 access;
+
+ u16 fmt_tx_in;
+ u16 fmt_tx_out;
+ u16 fmt_rx_in;
+ u16 fmt_rx_out;
+
+ u16 raw_tx_in;
+ u16 raw_tx_out;
+ u16 raw_rx_in;
+ u16 raw_rx_out;
+
+ u16 cp2ap;
+};
+
+struct mif_ipc_block {
+ enum mif_log_id id;
+ unsigned long long time;
+ size_t len;
+ char buff[MAX_IPC_LOG_SIZE];
+};
+
+struct mif_irq_block {
+ enum mif_log_id id;
+ unsigned long long time;
+ struct mif_irq_map map;
+ char buff[MAX_IRQ_LOG_SIZE];
+};
+
+struct mif_common_block {
+ enum mif_log_id id;
+ unsigned long long time;
+ char buff[MAX_COM_LOG_SIZE];
+};
+
+struct mif_time_block {
+ enum mif_log_id id;
+ unsigned long long time;
+ struct timespec epoch;
+ char buff[MAX_TIM_LOG_SIZE];
+};
+
+struct utc_time {
+ u16 year;
+ u8 mon:4,
+ day:4;
+ u8 hour;
+ u8 min;
+ u8 sec;
+ u16 msec;
+} __packed;
+
+void ts2utc(struct timespec *ts, struct utc_time *utc);
+void get_utc_time(struct utc_time *utc);
+
+int mif_dump_dpram(struct io_device *);
+int mif_dump_log(struct modem_shared *, struct io_device *);
+
+#define mif_irq_log(msd, map, data, len) \
+ _mif_irq_log(MIF_IRQ, msd, map, data, len)
+#define mif_com_log(msd, format, ...) \
+ _mif_com_log(MIF_COM, msd, pr_fmt(format), ##__VA_ARGS__)
+#define mif_time_log(msd, epoch, data, len) \
+ _mif_time_log(MIF_TIME, msd, epoch, data, len)
+
+void mif_ipc_log(enum mif_log_id,
+ struct modem_shared *, const char *, size_t);
+void _mif_irq_log(enum mif_log_id,
+ struct modem_shared *, struct mif_irq_map, const char *, size_t);
+void _mif_com_log(enum mif_log_id,
+ struct modem_shared *, const char *, ...);
+void _mif_time_log(enum mif_log_id,
+ struct modem_shared *, struct timespec, const char *, size_t);
+
+/** find_linkdev - find a link device
+ * @msd: struct modem_shared *
+ */
+static inline struct link_device *find_linkdev(struct modem_shared *msd,
+ enum modem_link link_type)
+{
+ struct link_device *ld;
+ list_for_each_entry(ld, &msd->link_dev_list, list) {
+ if (ld->link_type == link_type)
+ return ld;
+ }
+ 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 IPC message as hex string with UTC time */
+void pr_ipc(const char *tag, const char *data, size_t len);
+
+/* 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)
+
+/* Stop/wake all TX queues in network interfaces */
+void mif_netif_stop(struct link_device *ld);
+void mif_netif_wake(struct link_device *ld);
+
+/* 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);
+
+/* get iod from tree functions */
+
+struct io_device *get_iod_with_format(struct modem_shared *msd,
+ enum dev_format format);
+struct io_device *get_iod_with_channel(struct modem_shared *msd,
+ unsigned channel);
+
+static inline struct io_device *link_get_iod_with_format(
+ struct link_device *ld, enum dev_format format)
+{
+ struct io_device *iod = get_iod_with_format(ld->msd, format);
+ return (iod && IS_CONNECTED(iod, ld)) ? iod : NULL;
+}
+
+static inline struct io_device *link_get_iod_with_channel(
+ struct link_device *ld, unsigned channel)
+{
+ struct io_device *iod = get_iod_with_channel(ld->msd, channel);
+ return (iod && IS_CONNECTED(iod, ld)) ? iod : NULL;
+}
+
+/* insert iod to tree functions */
+struct io_device *insert_iod_with_format(struct modem_shared *msd,
+ enum dev_format format, struct io_device *iod);
+struct io_device *insert_iod_with_channel(struct modem_shared *msd,
+ unsigned channel, struct io_device *iod);
+
+/* iodev for each */
+typedef void (*action_fn)(struct io_device *iod, void *args);
+void iodevs_for_each(struct modem_shared *msd, action_fn action, void *args);
+
+/* netif wake/stop queue of iod */
+void iodev_netif_wake(struct io_device *iod, void *args);
+void iodev_netif_stop(struct io_device *iod, void *args);
+
+/* change tx_link of raw devices */
+void rawdevs_set_tx_link(struct modem_shared *msd, enum modem_link link_type);
+
+__be32 ipv4str_to_be32(const char *ipv4str, size_t count);
+
+void mif_add_timer(struct timer_list *timer, unsigned long expire,
+ void (*function)(unsigned long), unsigned long data);
+
+/* debug helper functions for sipc4, sipc5 */
+void mif_print_data(const char *buff, int len);
+void mif_dump2format16(const char *data, int len, char *buff, char *tag);
+void mif_dump2format4(const char *data, int len, char *buff, char *tag);
+void mif_print_dump(const char *data, int len, int width);
+void print_sipc4_hdlc_fmt_frame(const u8 *psrc);
+void print_sipc4_fmt_frame(const u8 *psrc);
+void print_sipc5_link_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(const u8 *ip_pkt, bool tx);
+bool is_dns_packet(const u8 *ip_pkt);
+bool is_syn_packet(const u8 *ip_pkt);
+
+int memcmp16_to_io(const void __iomem *to, void *from, int size);
+int mif_test_dpram(char *dp_name, u8 __iomem *start, u32 size);
+struct file *mif_open_file(const char *path);
+void mif_save_file(struct file *fp, const char *buff, size_t size);
+void mif_close_file(struct file *fp);
+
+#endif/*__MODEM_UTILS_H__*/
diff --git a/drivers/misc/modem_if/modem_variation.h b/drivers/misc/modem_if/modem_variation.h
new file mode 100644
index 00000000000..2865f7bc10e
--- /dev/null
+++ b/drivers/misc/modem_if/modem_variation.h
@@ -0,0 +1,168 @@
+/*
+ * 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__
+
+#include <linux/platform_data/modem.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) \
+ static 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) \
+ static 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 */
+DECLARE_MODEM_INIT_DUMMY(dummy)
+
+#ifdef CONFIG_UMTS_MODEM_XMM6260
+DECLARE_MODEM_INIT(xmm6260);
+#else
+DECLARE_MODEM_INIT_DUMMY(xmm6260)
+#endif
+
+#ifdef CONFIG_UMTS_MODEM_XMM6262
+DECLARE_MODEM_INIT(xmm6262);
+#else
+DECLARE_MODEM_INIT_DUMMY(xmm6262)
+#endif
+
+#ifdef CONFIG_CDMA_MODEM_CBP71
+DECLARE_MODEM_INIT(cbp71);
+#else
+DECLARE_MODEM_INIT_DUMMY(cbp71)
+#endif
+
+#ifdef CONFIG_CDMA_MODEM_CBP72
+DECLARE_MODEM_INIT(cbp72);
+#else
+DECLARE_MODEM_INIT_DUMMY(cbp72)
+#endif
+
+#ifdef CONFIG_LTE_MODEM_CMC221
+DECLARE_MODEM_INIT(cmc221);
+#else
+DECLARE_MODEM_INIT_DUMMY(cmc221)
+#endif
+
+#ifdef CONFIG_CDMA_MODEM_MDM6600
+DECLARE_MODEM_INIT(mdm6600);
+#else
+DECLARE_MODEM_INIT_DUMMY(mdm6600)
+#endif
+
+#ifdef CONFIG_GSM_MODEM_ESC6270
+DECLARE_MODEM_INIT(esc6270);
+#else
+DECLARE_MODEM_INIT_DUMMY(esc6270)
+#endif
+
+/* link device support */
+DECLARE_LINK_INIT_DUMMY(undefined)
+
+#ifdef CONFIG_LINK_DEVICE_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_PLD
+DECLARE_LINK_INIT(pld);
+#else
+DECLARE_LINK_INIT_DUMMY(pld)
+#endif
+
+#ifdef CONFIG_LINK_DEVICE_SPI
+DECLARE_LINK_INIT(spi);
+#else
+DECLARE_LINK_INIT_DUMMY(spi)
+#endif
+
+#ifdef CONFIG_LINK_DEVICE_USB
+DECLARE_LINK_INIT(usb);
+#else
+DECLARE_LINK_INIT_DUMMY(usb)
+#endif
+
+#ifdef CONFIG_LINK_DEVICE_HSIC
+DECLARE_LINK_INIT(hsic);
+#else
+DECLARE_LINK_INIT_DUMMY(hsic)
+#endif
+
+#ifdef CONFIG_LINK_DEVICE_C2C
+DECLARE_LINK_INIT(c2c);
+#else
+DECLARE_LINK_INIT_DUMMY(c2c)
+#endif
+
+typedef int (*modem_init_call)(struct modem_ctl *, struct modem_data *);
+static modem_init_call modem_init_func[] = {
+ MODEM_INIT_CALL(xmm6260),
+ MODEM_INIT_CALL(xmm6262),
+ MODEM_INIT_CALL(cbp71),
+ MODEM_INIT_CALL(cbp72),
+ MODEM_INIT_CALL(cmc221),
+ MODEM_INIT_CALL(mdm6600),
+ MODEM_INIT_CALL(esc6270),
+ MODEM_INIT_CALL(dummy),
+};
+
+typedef struct link_device *(*link_init_call)(struct platform_device *);
+static link_init_call link_init_func[] = {
+ LINK_INIT_CALL(undefined),
+ LINK_INIT_CALL(mipi),
+ LINK_INIT_CALL(dpram),
+ LINK_INIT_CALL(spi),
+ LINK_INIT_CALL(usb),
+ LINK_INIT_CALL(hsic),
+ LINK_INIT_CALL(c2c),
+ LINK_INIT_CALL(pld),
+};
+
+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
diff --git a/drivers/misc/modem_if/sipc4_io_device.c b/drivers/misc/modem_if/sipc4_io_device.c
new file mode 100644
index 00000000000..56b9afd4cee
--- /dev/null
+++ b/drivers/misc/modem_if/sipc4_io_device.c
@@ -0,0 +1,1668 @@
+/* /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/device.h>
+#include <linux/module.h>
+
+#include <linux/platform_data/modem.h>
+#ifdef CONFIG_LINK_DEVICE_C2C
+#include <linux/platform_data/c2c.h>
+#endif
+#include "modem_prj.h"
+#include "modem_utils.h"
+
+/*
+ * MAX_RXDATA_SIZE is used at making skb, when it called with page size
+ * it need more bytes to allocate itself (Ex, cache byte, shared info,
+ * padding...)
+ * So, give restriction to allocation size below 1 page to prevent
+ * big pages broken.
+ */
+#define MAX_RXDATA_SIZE 0x0E00 /* 4 * 1024 - 512 */
+#define MAX_MULTI_FMT_SIZE 0x4000 /* 16 * 1024 */
+
+static const char hdlc_start[1] = { HDLC_START };
+static const char hdlc_end[1] = { HDLC_END };
+
+static int rx_iodev_skb(struct sk_buff *skb);
+
+static ssize_t show_waketime(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int msec;
+ char *p = buf;
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct io_device *iod = container_of(miscdev, struct io_device,
+ miscdev);
+
+ msec = jiffies_to_msecs(iod->waketime);
+
+ p += sprintf(buf, "raw waketime : %ums\n", msec);
+
+ return p - buf;
+}
+
+static ssize_t store_waketime(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned long msec;
+ int ret;
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct io_device *iod = container_of(miscdev, struct io_device,
+ miscdev);
+
+ ret = strict_strtoul(buf, 10, &msec);
+ if (ret)
+ return count;
+
+ iod->waketime = msecs_to_jiffies(msec);
+
+ return count;
+}
+
+static struct device_attribute attr_waketime =
+ __ATTR(waketime, S_IRUGO | S_IWUSR, show_waketime, store_waketime);
+
+static ssize_t show_loopback(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct modem_shared *msd =
+ container_of(miscdev, struct io_device, miscdev)->msd;
+ unsigned char *ip = (unsigned char *)&msd->loopback_ipaddr;
+ char *p = buf;
+
+ p += sprintf(buf, "%u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
+
+ return p - buf;
+}
+
+static ssize_t store_loopback(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct modem_shared *msd =
+ container_of(miscdev, struct io_device, miscdev)->msd;
+
+ msd->loopback_ipaddr = ipv4str_to_be32(buf, count);
+
+ return count;
+}
+
+static struct device_attribute attr_loopback =
+ __ATTR(loopback, S_IRUGO | S_IWUSR, show_loopback, store_loopback);
+
+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;
+
+ mif_debug("buf : %02x %02x %02x (%d)\n", *buf, *(buf + 1),
+ *(buf + 2), __LINE__);
+
+ switch (iod->format) {
+ case IPC_FMT:
+ fmt_header = (struct fmt_hdr *)buf;
+ if (iod->mc->mdm_data->ipc_version == SIPC_VER_42)
+ return fmt_header->len & 0x3FFF;
+ else
+ 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 calc_padding_size(struct io_device *iod,
+ struct link_device *ld, unsigned len)
+{
+ if (ld->aligned)
+ return (4 - (len & 0x3)) & 0x3;
+ else
+ 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, struct link_device *ld,
+ char *buf, unsigned rest)
+{
+ struct header_data *hdr = &fragdata(iod, ld)->h_data;
+ int head_size = get_header_size(iod);
+ int done_len = 0;
+ int len = 0;
+
+ /* first frame, remove start header 7F */
+ if (!hdr->start) {
+ len = rx_hdlc_head_start_check(buf);
+ if (len < 0) {
+ mif_err("Wrong HDLC start: 0x%x\n", *buf);
+ return len; /*Wrong hdlc start*/
+ }
+
+ mif_debug("check len : %d, rest : %d (%d)\n", len,
+ rest, __LINE__);
+
+ /* set the start flag of current packet */
+ hdr->start = HDLC_START;
+ hdr->len = 0;
+
+ /* debug print */
+ switch (iod->format) {
+ case IPC_FMT:
+ case IPC_RAW:
+ case IPC_MULTI_RAW:
+ case IPC_RFS:
+ /* TODO: print buf... */
+ break;
+
+ case IPC_CMD:
+ case IPC_BOOT:
+ case IPC_RAMDUMP:
+ default:
+ break;
+ }
+
+ buf += len;
+ done_len += len;
+ rest -= len; /* rest, call by value */
+ }
+
+ mif_debug("check len : %d, rest : %d (%d)\n",
+ len, rest, __LINE__);
+
+ /* store the HDLC header to iod priv */
+ if (hdr->len < head_size) {
+ len = min(rest, head_size - hdr->len);
+ memcpy(hdr->hdr + hdr->len, buf, len);
+ hdr->len += len;
+ done_len += len;
+ }
+
+ mif_debug("check done_len : %d, rest : %d (%d)\n", done_len,
+ rest, __LINE__);
+ return done_len;
+}
+
+/* alloc skb and copy data to skb */
+static int rx_hdlc_data_check(struct io_device *iod, struct link_device *ld,
+ char *buf, unsigned rest)
+{
+ struct header_data *hdr = &fragdata(iod, ld)->h_data;
+ struct sk_buff *skb = fragdata(iod, ld)->skb_recv;
+ int head_size = get_header_size(iod);
+ int data_size = get_hdlc_size(iod, hdr->hdr) - head_size;
+ int alloc_size;
+ int len = 0;
+ int done_len = 0;
+ int rest_len = data_size - hdr->frag_len;
+ int continue_len = fragdata(iod, ld)->realloc_offset;
+
+ mif_debug("head_size : %d, data_size : %d (%d)\n", head_size,
+ data_size, __LINE__);
+
+ if (continue_len) {
+ /* check the HDLC header*/
+ if (rx_hdlc_head_start_check(buf) == SIZE_OF_HDLC_START) {
+ rest_len -= (head_size + SIZE_OF_HDLC_START);
+ continue_len += (head_size + SIZE_OF_HDLC_START);
+ }
+
+ buf += continue_len;
+ rest -= continue_len;
+ done_len += continue_len;
+ fragdata(iod, ld)->realloc_offset = 0;
+
+ mif_debug("realloc_offset = %d\n", continue_len);
+ }
+
+ /* first payload data - alloc skb */
+ if (!skb) {
+ /* make skb data size under MAX_RXDATA_SIZE */
+ alloc_size = min(data_size, MAX_RXDATA_SIZE);
+ alloc_size = min(alloc_size, rest_len);
+
+ /* exceptional case for RFS channel
+ * make skb for header info first
+ */
+ if (iod->format == IPC_RFS && !hdr->frag_len) {
+ skb = rx_alloc_skb(head_size, iod, ld);
+ if (unlikely(!skb))
+ return -ENOMEM;
+ memcpy(skb_put(skb, head_size), hdr->hdr, head_size);
+ rx_iodev_skb(skb);
+ }
+
+ /* allocate first packet for data, when its size exceed
+ * MAX_RXDATA_SIZE, this packet will split to
+ * multiple packets
+ */
+ skb = rx_alloc_skb(alloc_size, iod, ld);
+ if (unlikely(!skb)) {
+ fragdata(iod, ld)->realloc_offset = continue_len;
+ return -ENOMEM;
+ }
+ fragdata(iod, ld)->skb_recv = skb;
+ }
+
+ while (rest) {
+ /* copy length cannot exceed rest_len */
+ len = min_t(int, rest_len, rest);
+ /* copy length should be under skb tailroom size */
+ len = min(len, skb_tailroom(skb));
+ /* when skb tailroom is bigger than MAX_RXDATA_SIZE
+ * restrict its size to MAX_RXDATA_SIZE just for convinience */
+ len = min(len, MAX_RXDATA_SIZE);
+
+ /* copy bytes to skb */
+ memcpy(skb_put(skb, len), buf, len);
+
+ /* adjusting variables */
+ buf += len;
+ rest -= len;
+ done_len += len;
+ rest_len -= len;
+ hdr->frag_len += len;
+
+ /* check if it is final for this packet sequence */
+ if (!rest_len || !rest)
+ break;
+
+ /* more bytes are remain for this packet sequence
+ * pass fully loaded skb to rx queue
+ * and allocate another skb for continues data recv chain
+ */
+ rx_iodev_skb(skb);
+ fragdata(iod, ld)->skb_recv = NULL;
+
+ alloc_size = min(rest_len, MAX_RXDATA_SIZE);
+
+ skb = rx_alloc_skb(alloc_size, iod, ld);
+ if (unlikely(!skb)) {
+ fragdata(iod, ld)->realloc_offset = done_len;
+ return -ENOMEM;
+ }
+ fragdata(iod, ld)->skb_recv = skb;
+ }
+
+ mif_debug("rest : %d, alloc_size : %d , len : %d (%d)\n",
+ rest, alloc_size, skb->len, __LINE__);
+
+ return done_len;
+}
+
+static int rx_multi_fmt_frame(struct sk_buff *rx_skb)
+{
+ struct io_device *iod = skbpriv(rx_skb)->iod;
+ struct link_device *ld = skbpriv(rx_skb)->ld;
+ struct fmt_hdr *fh =
+ (struct fmt_hdr *)fragdata(iod, ld)->h_data.hdr;
+ unsigned int id = fh->control & 0x7F;
+ struct sk_buff *skb = iod->skb[id];
+ unsigned char *data = fragdata(iod, ld)->skb_recv->data;
+ unsigned int rcvd = fragdata(iod, ld)->skb_recv->len;
+
+ if (!skb) {
+ /* If there has been no multiple frame with this ID */
+ if (!(fh->control & 0x80)) {
+ /* It is a single frame because the "more" bit is 0. */
+#if 0
+ mif_err("\n<%s> Rx FMT frame (len %d)\n",
+ iod->name, rcvd);
+ print_sipc4_fmt_frame(data);
+ mif_err("\n");
+#endif
+ skb_queue_tail(&iod->sk_rx_q,
+ fragdata(iod, ld)->skb_recv);
+ mif_debug("wake up wq of %s\n", iod->name);
+ wake_up(&iod->wq);
+ return 0;
+ } else {
+ struct fmt_hdr *fh = NULL;
+ skb = rx_alloc_skb(MAX_MULTI_FMT_SIZE, iod, ld);
+ if (!skb) {
+ mif_err("<%d> alloc_skb fail\n",
+ __LINE__);
+ return -ENOMEM;
+ }
+ iod->skb[id] = skb;
+
+ fh = (struct fmt_hdr *)data;
+ mif_info("Start multi-frame (ID %d, len %d)",
+ id, fh->len);
+ }
+ }
+
+ /* Start multi-frame processing */
+
+ memcpy(skb_put(skb, rcvd), data, rcvd);
+ dev_kfree_skb_any(fragdata(iod, ld)->skb_recv);
+
+ if (fh->control & 0x80) {
+ /* The last frame has not arrived yet. */
+ mif_info("Receiving (ID %d, %d bytes)\n",
+ id, skb->len);
+ } else {
+ /* It is the last frame because the "more" bit is 0. */
+ mif_info("The Last (ID %d, %d bytes received)\n",
+ id, skb->len);
+#if 0
+ mif_err("\n<%s> Rx FMT frame (len %d)\n",
+ iod->name, skb->len);
+ print_sipc4_fmt_frame(skb->data);
+ mif_err("\n");
+#endif
+ skb_queue_tail(&iod->sk_rx_q, skb);
+ iod->skb[id] = NULL;
+ mif_info("wake up wq of %s\n", iod->name);
+ wake_up(&iod->wq);
+ }
+
+ return 0;
+}
+
+static int rx_multi_fmt_frame_sipc42(struct sk_buff *rx_skb)
+{
+ struct io_device *iod = skbpriv(rx_skb)->iod;
+ struct link_device *ld = skbpriv(rx_skb)->ld;
+ struct fmt_hdr *fh =
+ (struct fmt_hdr *)fragdata(iod, ld)->h_data.hdr;
+ unsigned int id = fh->control & 0x7F;
+ struct sk_buff *skb = iod->skb[id];
+ unsigned char *data = fragdata(iod, ld)->skb_recv->data;
+ unsigned int rcvd = fragdata(iod, ld)->skb_recv->len;
+
+ u8 ch;
+ struct io_device *real_iod = NULL;
+
+ ch = (fh->len & 0xC000) >> 14;
+ fh->len = fh->len & 0x3FFF;
+ real_iod = ld->fmt_iods[ch];
+ if (!real_iod) {
+ mif_err("wrong channel %d\n", ch);
+ return -1;
+ }
+ skbpriv(rx_skb)->real_iod = real_iod;
+
+ if (!skb) {
+ /* If there has been no multiple frame with this ID */
+ if (!(fh->control & 0x80)) {
+ /* It is a single frame because the "more" bit is 0. */
+#if 0
+ mif_err("\n<%s> Rx FMT frame (len %d)\n",
+ iod->name, rcvd);
+ print_sipc4_fmt_frame(data);
+ mif_err("\n");
+#endif
+ skb_queue_tail(&real_iod->sk_rx_q,
+ fragdata(iod, ld)->skb_recv);
+ mif_debug("wake up wq of %s\n", iod->name);
+ wake_up(&real_iod->wq);
+ return 0;
+ } else {
+ struct fmt_hdr *fh = NULL;
+ skb = rx_alloc_skb(MAX_MULTI_FMT_SIZE, real_iod, ld);
+ if (!skb) {
+ mif_err("alloc_skb fail\n");
+ return -ENOMEM;
+ }
+ real_iod->skb[id] = skb;
+
+ fh = (struct fmt_hdr *)data;
+ mif_err("Start multi-frame (ID %d, len %d)",
+ id, fh->len);
+ }
+ }
+
+ /* Start multi-frame processing */
+
+ memcpy(skb_put(skb, rcvd), data, rcvd);
+ dev_kfree_skb_any(fragdata(real_iod, ld)->skb_recv);
+
+ if (fh->control & 0x80) {
+ /* The last frame has not arrived yet. */
+ mif_err("Receiving (ID %d, %d bytes)\n",
+ id, skb->len);
+ } else {
+ /* It is the last frame because the "more" bit is 0. */
+ mif_err("The Last (ID %d, %d bytes received)\n",
+ id, skb->len);
+#if 0
+ mif_err("\n<%s> Rx FMT frame (len %d)\n",
+ iod->name, skb->len);
+ print_sipc4_fmt_frame(skb->data);
+ mif_err("\n");
+#endif
+ skb_queue_tail(&real_iod->sk_rx_q, skb);
+ real_iod->skb[id] = NULL;
+ mif_info("wake up wq of %s\n", real_iod->name);
+ wake_up(&real_iod->wq);
+ }
+
+ return 0;
+}
+
+static int rx_iodev_skb_raw(struct sk_buff *skb)
+{
+ int err = 0;
+ struct io_device *iod = skbpriv(skb)->real_iod;
+ struct net_device *ndev = NULL;
+ struct iphdr *ip_header = NULL;
+ struct ethhdr *ehdr = NULL;
+ const char source[ETH_ALEN] = SOURCE_MAC_ADDR;
+
+ /* check the real_iod is open? */
+ /*
+ if (atomic_read(&iod->opened) == 0) {
+ mif_err("<%s> is not opened.\n",
+ iod->name);
+ pr_skb("drop packet", skb);
+ return -ENOENT;
+ }
+ */
+
+ switch (iod->io_typ) {
+ case IODEV_MISC:
+ mif_debug("<%s> sk_rx_q.qlen = %d\n",
+ iod->name, iod->sk_rx_q.qlen);
+ skb_queue_tail(&iod->sk_rx_q, skb);
+ wake_up(&iod->wq);
+ return 0;
+
+ case IODEV_NET:
+ ndev = iod->ndev;
+ if (!ndev) {
+ mif_err("<%s> ndev == NULL",
+ iod->name);
+ return -EINVAL;
+ }
+
+ 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->use_handover) {
+ skb_push(skb, sizeof(struct ethhdr));
+ ehdr = (void *)skb->data;
+ 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_UNNECESSARY;
+ skb_reset_mac_header(skb);
+
+ skb_pull(skb, sizeof(struct ethhdr));
+ }
+
+ if (in_irq())
+ err = netif_rx(skb);
+ else
+ err = netif_rx_ni(skb);
+
+ if (err != NET_RX_SUCCESS)
+ dev_err(&ndev->dev, "rx error: %d\n", err);
+
+ return err;
+
+ default:
+ mif_err("wrong io_type : %d\n", iod->io_typ);
+ return -EINVAL;
+ }
+}
+
+static void rx_iodev_work(struct work_struct *work)
+{
+ int ret = 0;
+ struct sk_buff *skb = NULL;
+ struct io_device *iod = container_of(work, struct io_device,
+ rx_work.work);
+
+ while ((skb = skb_dequeue(&iod->sk_rx_q)) != NULL) {
+ ret = rx_iodev_skb_raw(skb);
+ if (ret < 0) {
+ mif_err("<%s> rx_iodev_skb_raw err = %d",
+ iod->name, ret);
+ dev_kfree_skb_any(skb);
+ } else if (ret == NET_RX_DROP) {
+ mif_err("<%s> ret == NET_RX_DROP\n",
+ iod->name);
+ schedule_delayed_work(&iod->rx_work,
+ msecs_to_jiffies(100));
+ break;
+ }
+ }
+}
+
+static int rx_multipdp(struct sk_buff *skb)
+{
+ u8 ch;
+ struct io_device *iod = skbpriv(skb)->iod;
+ struct link_device *ld = skbpriv(skb)->ld;
+ struct raw_hdr *raw_header =
+ (struct raw_hdr *)fragdata(iod, ld)->h_data.hdr;
+ struct io_device *real_iod = NULL;
+
+ ch = raw_header->channel;
+ if (ch == DATA_LOOPBACK_CHANNEL && ld->msd->loopback_ipaddr)
+ ch = RMNET0_CH_ID;
+
+ real_iod = link_get_iod_with_channel(ld, 0x20 | ch);
+ if (!real_iod) {
+ mif_err("wrong channel %d\n", ch);
+ return -1;
+ }
+
+ skbpriv(skb)->real_iod = real_iod;
+ skb_queue_tail(&iod->sk_rx_q, skb);
+ mif_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 sk_buff *skb)
+{
+ struct io_device *iod = skbpriv(skb)->iod;
+
+ switch (iod->format) {
+ case IPC_MULTI_RAW:
+ return rx_multipdp(skb);
+
+ case IPC_FMT:
+ if (iod->mc->mdm_data->ipc_version == SIPC_VER_42)
+ return rx_multi_fmt_frame_sipc42(skb);
+ else
+ return rx_multi_fmt_frame(skb);
+
+ case IPC_RFS:
+ default:
+ skb_queue_tail(&iod->sk_rx_q, skb);
+ mif_debug("wake up wq of %s\n", iod->name);
+ wake_up(&iod->wq);
+ return 0;
+ }
+}
+
+static int rx_hdlc_packet(struct io_device *iod, struct link_device *ld,
+ const char *data, unsigned recv_size)
+{
+ int rest = (int)recv_size;
+ char *buf = (char *)data;
+ int err = 0;
+ int len = 0;
+ unsigned rcvd = 0;
+
+ if (rest <= 0)
+ goto exit;
+
+ mif_debug("RX_SIZE = %d, ld: %s\n", rest, ld->name);
+
+ if (fragdata(iod, ld)->h_data.frag_len) {
+ /*
+ If the fragdata(iod, ld)->h_data.frag_len field is
+ not zero, there is a HDLC frame that is waiting for more data
+ or HDLC_END in the skb (fragdata(iod, ld)->skb_recv).
+ In this case, rx_hdlc_head_check() must be skipped.
+ */
+ goto data_check;
+ }
+
+next_frame:
+ err = len = rx_hdlc_head_check(iod, ld, buf, rest);
+ if (err < 0)
+ goto exit;
+ mif_debug("check len : %d, rest : %d (%d)\n", len, rest,
+ __LINE__);
+
+ buf += len;
+ rest -= len;
+ if (rest <= 0)
+ goto exit;
+
+data_check:
+ /*
+ If the return value of rx_hdlc_data_check() is zero, there remains
+ only HDLC_END that will be received.
+ */
+ err = len = rx_hdlc_data_check(iod, ld, buf, rest);
+ if (err < 0)
+ goto exit;
+ mif_debug("check len : %d, rest : %d (%d)\n", len, rest,
+ __LINE__);
+
+ buf += len;
+ rest -= len;
+
+ if (!rest && fragdata(iod, ld)->h_data.frag_len) {
+ /*
+ Data is being received and more data or HDLC_END does not
+ arrive yet, but there is no more data in the buffer. More
+ data may come within the next frame from the link device.
+ */
+ return 0;
+ } else if (rest <= 0)
+ goto exit;
+
+ /* At this point, one HDLC frame except HDLC_END has been received. */
+
+ err = len = rx_hdlc_tail_check(buf);
+ if (err < 0) {
+ mif_err("Wrong HDLC end: 0x%02X\n", *buf);
+ goto exit;
+ }
+ mif_debug("check len : %d, rest : %d (%d)\n", len, rest,
+ __LINE__);
+ buf += len;
+ rest -= len;
+
+ /* At this point, one complete HDLC frame has been received. */
+
+ /*
+ The padding size is applied for the next HDLC frame. Zero will be
+ returned by calc_padding_size() if the link device does not require
+ 4-byte aligned access.
+ */
+ rcvd = get_hdlc_size(iod, fragdata(iod, ld)->h_data.hdr) +
+ (SIZE_OF_HDLC_START + SIZE_OF_HDLC_END);
+ len = calc_padding_size(iod, ld, rcvd);
+ buf += len;
+ rest -= len;
+ if (rest < 0)
+ goto exit;
+
+ err = rx_iodev_skb(fragdata(iod, ld)->skb_recv);
+ if (err < 0)
+ goto exit;
+
+ /* initialize header & skb */
+ fragdata(iod, ld)->skb_recv = NULL;
+ memset(&fragdata(iod, ld)->h_data, 0x00,
+ sizeof(struct header_data));
+ fragdata(iod, ld)->realloc_offset = 0;
+
+ if (rest)
+ goto next_frame;
+
+exit:
+ /* free buffers. mipi-hsi re-use recv buf */
+
+ if (rest < 0)
+ err = -ERANGE;
+
+ if (err == -ENOMEM) {
+ if (!(fragdata(iod, ld)->h_data.frag_len))
+ memset(&fragdata(iod, ld)->h_data, 0x00,
+ sizeof(struct header_data));
+ return err;
+ }
+
+ if (err < 0 && fragdata(iod, ld)->skb_recv) {
+ dev_kfree_skb_any(fragdata(iod, ld)->skb_recv);
+ fragdata(iod, ld)->skb_recv = NULL;
+
+ /* clear headers */
+ memset(&fragdata(iod, ld)->h_data, 0x00,
+ sizeof(struct header_data));
+ fragdata(iod, ld)->realloc_offset = 0;
+ }
+
+ return err;
+}
+
+static int rx_rfs_packet(struct io_device *iod, struct link_device *ld,
+ const char *data, unsigned size)
+{
+ int err = 0;
+ int pad = 0;
+ int rcvd = 0;
+ struct sk_buff *skb;
+
+ if (data[0] != HDLC_START) {
+ mif_err("Dropping RFS packet ... "
+ "size = %d, start = %02X %02X %02X %02X\n",
+ size,
+ data[0], data[1], data[2], data[3]);
+ return -EINVAL;
+ }
+
+ if (data[size-1] != HDLC_END) {
+ for (pad = 1; pad < 4; pad++)
+ if (data[(size-1)-pad] == HDLC_END)
+ break;
+
+ if (pad >= 4) {
+ char *b = (char *)data;
+ unsigned sz = size;
+ mif_err("size %d, No END_FLAG!!!\n", size);
+ mif_err("end = %02X %02X %02X %02X\n",
+ b[sz-4], b[sz-3], b[sz-2], b[sz-1]);
+ return -EINVAL;
+ } else {
+ mif_info("padding = %d\n", pad);
+ }
+ }
+
+ skb = rx_alloc_skb(size, iod, ld);
+ if (unlikely(!skb)) {
+ mif_err("alloc_skb fail\n");
+ return -ENOMEM;
+ }
+
+ /* copy the RFS haeder to skb->data */
+ rcvd = size - sizeof(hdlc_start) - sizeof(hdlc_end) - pad;
+ memcpy(skb_put(skb, rcvd), ((char *)data + sizeof(hdlc_start)), rcvd);
+
+ fragdata(iod, ld)->skb_recv = skb;
+ err = rx_iodev_skb(fragdata(iod, ld)->skb_recv);
+
+ 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,
+ struct link_device *ld, const char *data, unsigned int len)
+{
+ struct sk_buff *skb;
+ int err;
+ unsigned int alloc_size, rest_len;
+ char *cur;
+
+
+ /* check the iod(except IODEV_DUMMY) is open?
+ * if the iod is MULTIPDP, check this data on rx_iodev_skb_raw()
+ * because, we cannot know the channel no in here.
+ */
+ /*
+ if (iod->io_typ != IODEV_DUMMY && atomic_read(&iod->opened) == 0) {
+ mif_err("<%s> is not opened.\n", iod->name);
+ pr_buffer("drop packet", data, len, 16u);
+ return -ENOENT;
+ }
+ */
+
+ switch (iod->format) {
+ case IPC_RFS:
+#ifdef CONFIG_IPC_CMC22x_OLD_RFS
+ err = rx_rfs_packet(iod, ld, data, len);
+ return err;
+#endif
+
+ case IPC_FMT:
+ case IPC_RAW:
+ case IPC_MULTI_RAW:
+ if (iod->waketime)
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock_timeout(&iod->wakelock, iod->waketime);
+#else
+ pm_wakeup_event(iod->miscdev.this_device,
+ jiffies_to_msecs(iod->waketime));
+#endif
+ err = rx_hdlc_packet(iod, ld, data, len);
+ if (err < 0)
+ mif_err("fail process HDLC frame\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 = rx_alloc_skb(len, iod, ld);
+ if (skb) {
+ mif_debug("boot len : %d\n", len);
+
+ memcpy(skb_put(skb, len), data, len);
+ skb_queue_tail(&iod->sk_rx_q, skb);
+ mif_debug("skb len : %d\n", skb->len);
+
+ wake_up(&iod->wq);
+ return len;
+ }
+ /* 32KB page alloc fail case, alloc 3.5K a page.. */
+ mif_info("(%d)page fail, alloc fragment pages\n", len);
+
+ rest_len = len;
+ cur = (char *)data;
+ while (rest_len) {
+ alloc_size = min_t(unsigned int, MAX_RXDATA_SIZE,
+ rest_len);
+ skb = rx_alloc_skb(alloc_size, iod, ld);
+ if (!skb) {
+ mif_err("fail alloc skb (%d)\n", __LINE__);
+ return -ENOMEM;
+ }
+ mif_debug("boot len : %d\n", alloc_size);
+
+ memcpy(skb_put(skb, alloc_size), cur, alloc_size);
+ skb_queue_tail(&iod->sk_rx_q, skb);
+ mif_debug("skb len : %d\n", skb->len);
+
+ rest_len -= alloc_size;
+ cur += alloc_size;
+ }
+ 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;
+ mif_err("modem state changed. (iod: %s, state: %d)\n",
+ iod->name, state);
+
+ if ((state == STATE_CRASH_RESET) || (state == STATE_CRASH_EXIT)
+ || (state == STATE_NV_REBUILDING))
+ wake_up(&iod->wq);
+}
+
+/**
+ * io_dev_sim_state_changed
+ * @iod: IPC's io_device
+ * @sim_online: SIM is online?
+ */
+static void io_dev_sim_state_changed(struct io_device *iod, bool sim_online)
+{
+ if (atomic_read(&iod->opened) == 0) {
+ mif_err("iod is not opened: %s\n",
+ iod->name);
+ } else if (iod->mc->sim_state.online == sim_online) {
+ mif_err("sim state not changed.\n");
+ } else {
+ iod->mc->sim_state.online = sim_online;
+ iod->mc->sim_state.changed = true;
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock_timeout(&iod->mc->bootd->wakelock,
+ iod->mc->bootd->waketime);
+#else
+ pm_wakeup_event(iod->mc->bootd->miscdev.this_device,
+ jiffies_to_msecs(iod->mc->bootd->waketime));
+#endif
+ mif_err("sim state changed. (iod: %s, state: "
+ "[online=%d, changed=%d])\n",
+ iod->name, iod->mc->sim_state.online,
+ iod->mc->sim_state.changed);
+ wake_up(&iod->wq);
+ }
+}
+
+static int misc_open(struct inode *inode, struct file *filp)
+{
+ struct io_device *iod = to_io_device(filp->private_data);
+ struct modem_shared *msd = iod->msd;
+ struct link_device *ld;
+ int ret;
+ filp->private_data = (void *)iod;
+
+ mif_err("iod = %s\n", iod->name);
+ atomic_inc(&iod->opened);
+
+ list_for_each_entry(ld, &msd->link_dev_list, list) {
+ if (IS_CONNECTED(iod, ld) && ld->init_comm) {
+ ret = ld->init_comm(ld, iod);
+ if (ret < 0) {
+ mif_err("%s: init_comm error: %d\n",
+ ld->name, ret);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int misc_release(struct inode *inode, struct file *filp)
+{
+ struct io_device *iod = (struct io_device *)filp->private_data;
+ struct modem_shared *msd = iod->msd;
+ struct link_device *ld;
+
+ mif_err("iod = %s\n", iod->name);
+ atomic_dec(&iod->opened);
+ skb_queue_purge(&iod->sk_rx_q);
+
+ list_for_each_entry(ld, &msd->link_dev_list, list) {
+ if (IS_CONNECTED(iod, ld) && ld->terminate_comm)
+ ld->terminate_comm(ld, iod);
+ }
+
+ 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_RESET) ||
+ (iod->mc->phone_state == STATE_CRASH_EXIT) ||
+ (iod->mc->phone_state == STATE_NV_REBUILDING) ||
+ (iod->mc->sim_state.changed)) {
+ if (iod->format == IPC_RAW) {
+ msleep(20);
+ return 0;
+ }
+ 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;
+ struct link_device *ld = get_current_link(iod);
+ char cpinfo_buf[530] = "CP Crash ";
+ unsigned long size;
+ int ret;
+ char str[TASK_COMM_LEN];
+
+ mif_debug("cmd = 0x%x\n", cmd);
+
+ switch (cmd) {
+ case IOCTL_MODEM_ON:
+ mif_debug("misc_ioctl : IOCTL_MODEM_ON\n");
+ return iod->mc->ops.modem_on(iod->mc);
+
+ case IOCTL_MODEM_OFF:
+ mif_debug("misc_ioctl : IOCTL_MODEM_OFF\n");
+ return iod->mc->ops.modem_off(iod->mc);
+
+ case IOCTL_MODEM_RESET:
+ mif_debug("misc_ioctl : IOCTL_MODEM_RESET\n");
+ return iod->mc->ops.modem_reset(iod->mc);
+
+ case IOCTL_MODEM_BOOT_ON:
+ mif_debug("misc_ioctl : IOCTL_MODEM_BOOT_ON\n");
+ return iod->mc->ops.modem_boot_on(iod->mc);
+
+ case IOCTL_MODEM_BOOT_OFF:
+ mif_debug("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_BOOT_DONE:
+ mif_debug("misc_ioctl : IOCTL_MODEM_BOOT_DONE\n");
+ return 0;
+
+ case IOCTL_MODEM_STATUS:
+ mif_debug("misc_ioctl : IOCTL_MODEM_STATUS\n");
+
+ p_state = iod->mc->phone_state;
+ if ((p_state == STATE_CRASH_RESET) ||
+ (p_state == STATE_CRASH_EXIT)) {
+ mif_err("<%s> send err state : %d\n",
+ iod->name, p_state);
+ } else if (iod->mc->sim_state.changed &&
+ !strcmp(get_task_comm(str, get_current()), "rild")) {
+ int s_state = iod->mc->sim_state.online ?
+ STATE_SIM_ATTACH : STATE_SIM_DETACH;
+ iod->mc->sim_state.changed = false;
+
+ mif_info("SIM states (%d) to %s\n", s_state, str);
+ return s_state;
+ } else if (p_state == STATE_NV_REBUILDING) {
+ mif_info("send nv rebuild state : %d\n",
+ p_state);
+ iod->mc->phone_state = STATE_ONLINE;
+ }
+ return p_state;
+
+ case IOCTL_MODEM_PROTOCOL_SUSPEND:
+ mif_info("misc_ioctl : IOCTL_MODEM_PROTOCOL_SUSPEND\n");
+
+ if (iod->format != IPC_MULTI_RAW)
+ return -EINVAL;
+
+ iodevs_for_each(iod->msd, iodev_netif_stop, 0);
+ return 0;
+
+ case IOCTL_MODEM_PROTOCOL_RESUME:
+ mif_info("misc_ioctl : IOCTL_MODEM_PROTOCOL_RESUME\n");
+
+ if (iod->format != IPC_MULTI_RAW)
+ return -EINVAL;
+
+ iodevs_for_each(iod->msd, iodev_netif_wake, 0);
+ return 0;
+
+ case IOCTL_MODEM_DUMP_START:
+ mif_err("misc_ioctl : IOCTL_MODEM_DUMP_START\n");
+ return ld->dump_start(ld, iod);
+
+ case IOCTL_MODEM_DUMP_UPDATE:
+ mif_debug("misc_ioctl : IOCTL_MODEM_DUMP_UPDATE\n");
+ return ld->dump_update(ld, iod, arg);
+
+ case IOCTL_MODEM_FORCE_CRASH_EXIT:
+ mif_debug("misc_ioctl : IOCTL_MODEM_FORCE_CRASH_EXIT\n");
+ if (iod->mc->ops.modem_force_crash_exit)
+ return iod->mc->ops.modem_force_crash_exit(iod->mc);
+ return -EINVAL;
+
+ case IOCTL_MODEM_CP_UPLOAD:
+ mif_err("misc_ioctl : IOCTL_MODEM_CP_UPLOAD\n");
+ if (copy_from_user(cpinfo_buf + strlen(cpinfo_buf),
+ (void __user *)arg, MAX_CPINFO_SIZE) != 0)
+ panic("CP Crash");
+ else
+ panic(cpinfo_buf);
+ return 0;
+
+ case IOCTL_MODEM_DUMP_RESET:
+ mif_err("misc_ioctl : IOCTL_MODEM_DUMP_RESET\n");
+ return iod->mc->ops.modem_dump_reset(iod->mc);
+
+ case IOCTL_MIF_LOG_DUMP:
+ size = MAX_MIF_BUFF_SIZE;
+ ret = copy_to_user((void __user *)arg, &size,
+ sizeof(unsigned long));
+ if (ret < 0)
+ return -EFAULT;
+
+ mif_dump_log(iod->mc->msd, iod);
+ return 0;
+
+ case IOCTL_MIF_DPRAM_DUMP:
+#ifdef CONFIG_LINK_DEVICE_DPRAM
+ if (iod->mc->mdm_data->link_types & LINKTYPE(LINKDEV_DPRAM)) {
+ size = iod->mc->mdm_data->dpram_ctl->dp_size;
+ ret = copy_to_user((void __user *)arg, &size,
+ sizeof(unsigned long));
+ if (ret < 0)
+ return -EFAULT;
+ mif_dump_dpram(iod);
+ return 0;
+ }
+#endif
+ return -EINVAL;
+
+ default:
+ /* If you need to handle the ioctl for specific link device,
+ * then assign the link ioctl handler to ld->ioctl
+ * It will be call for specific link ioctl */
+ if (ld->ioctl)
+ return ld->ioctl(ld, iod, cmd, arg);
+
+ mif_err("misc_ioctl : ioctl 0x%X is not defined.\n", cmd);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+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;
+ struct link_device *ld = get_current_link(iod);
+ int frame_len = 0;
+ char frame_header_buf[sizeof(struct raw_hdr)];
+ struct sk_buff *skb;
+ int err;
+ size_t tx_size;
+
+ /* TODO - check here flow control for only raw data */
+
+ frame_len = SIZE_OF_HDLC_START +
+ get_header_size(iod) +
+ count +
+ SIZE_OF_HDLC_END;
+ if (ld->aligned)
+ frame_len += MAX_LINK_PADDING_SIZE;
+
+ skb = alloc_skb(frame_len, GFP_KERNEL);
+ if (!skb) {
+ mif_err("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;
+ }
+
+ skb_put(skb, calc_padding_size(iod, ld, skb->len));
+
+#if 0
+ if (iod->format == IPC_FMT) {
+ mif_err("\n<%s> Tx HDLC FMT frame (len %d)\n",
+ iod->name, skb->len);
+ print_sipc4_hdlc_fmt_frame(skb->data);
+ mif_err("\n");
+ }
+#endif
+#if 0
+ if (iod->format == IPC_RAW) {
+ mif_err("\n<%s> Tx HDLC RAW frame (len %d)\n",
+ iod->name, skb->len);
+ mif_print_data(skb->data, (skb->len < 64 ? skb->len : 64));
+ mif_err("\n");
+ }
+#endif
+#if 0
+ if (iod->format == IPC_RFS) {
+ mif_err("\n<%s> Tx HDLC RFS frame (len %d)\n",
+ iod->name, skb->len);
+ mif_print_data(skb->data, (skb->len < 64 ? skb->len : 64));
+ mif_err("\n");
+ }
+#endif
+
+ /* send data with sk_buff, link device will put sk_buff
+ * into the specific sk_buff_q and run work-q to send data
+ */
+ tx_size = skb->len;
+
+ skbpriv(skb)->iod = iod;
+ skbpriv(skb)->ld = ld;
+
+ err = ld->send(ld, iod, skb);
+ if (err < 0) {
+ dev_kfree_skb_any(skb);
+ return err;
+ }
+
+ if (err != tx_size)
+ mif_err("WARNNING: wrong tx size: %s, format=%d "
+ "count=%d, tx_size=%d, return_size=%d",
+ iod->name, iod->format, count, tx_size, err);
+
+ /* Temporaly enable t he RFS log for debugging IPC RX pedding issue */
+ if (iod->format == IPC_RFS)
+ mif_info("write rfs size = %d\n", count);
+
+ return count;
+}
+
+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 = NULL;
+ int pktsize = 0;
+ unsigned int rest_len, copy_len;
+ char *cur = buf;
+
+ skb = skb_dequeue(&iod->sk_rx_q);
+ if (!skb) {
+ mif_err("<%s> no data from sk_rx_q\n", iod->name);
+ return 0;
+ }
+ mif_debug("<%s> skb->len : %d\n", iod->name, skb->len);
+
+ if (iod->format == IPC_BOOT) {
+ pktsize = rest_len = count;
+ while (rest_len) {
+ if (skb->len > rest_len) {
+ /* BOOT device receviced rx data as serial
+ stream, return data by User requested size */
+ mif_err("skb->len %d > count %d\n", skb->len,
+ rest_len);
+ pr_skb("BOOT-wRX", skb);
+ if (copy_to_user(cur, skb->data, rest_len)
+ != 0) {
+ dev_kfree_skb_any(skb);
+ return -EFAULT;
+ }
+ cur += rest_len;
+ skb_pull(skb, rest_len);
+ if (skb->len) {
+ mif_info("queue-head, skb->len = %d\n",
+ skb->len);
+ skb_queue_head(&iod->sk_rx_q, skb);
+ }
+ mif_debug("return %u\n", rest_len);
+ return rest_len;
+ }
+
+ copy_len = min(rest_len, skb->len);
+ if (copy_to_user(cur, skb->data, copy_len) != 0) {
+ dev_kfree_skb_any(skb);
+ return -EFAULT;
+ }
+ cur += skb->len;
+ dev_kfree_skb_any(skb);
+ rest_len -= copy_len;
+
+ if (!rest_len)
+ break;
+
+ skb = skb_dequeue(&iod->sk_rx_q);
+ if (!skb) {
+ mif_err("<%s> %d / %d sk_rx_q\n", iod->name,
+ (count - rest_len), count);
+ return count - rest_len;
+ }
+ }
+ } else {
+ if (skb->len > count) {
+ mif_err("<%s> skb->len %d > count %d\n", iod->name,
+ skb->len, count);
+ dev_kfree_skb_any(skb);
+ return -EFAULT;
+ }
+ pktsize = skb->len;
+ if (copy_to_user(buf, skb->data, pktsize) != 0) {
+ dev_kfree_skb_any(skb);
+ return -EFAULT;
+ }
+ if (iod->format == IPC_FMT)
+ mif_debug("copied %d bytes to user\n", pktsize);
+
+ dev_kfree_skb_any(skb);
+ }
+ return pktsize;
+}
+
+#ifdef CONFIG_LINK_DEVICE_C2C
+static int misc_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ int r = 0;
+ unsigned long size = 0;
+ unsigned long pfn = 0;
+ unsigned long offset = 0;
+ struct io_device *iod = (struct io_device *)filp->private_data;
+
+ if (!vma)
+ return -EFAULT;
+
+ size = vma->vm_end - vma->vm_start;
+ offset = vma->vm_pgoff << PAGE_SHIFT;
+ if (offset + size > (C2C_CP_RGN_SIZE + C2C_SH_RGN_SIZE)) {
+ mif_err("offset + size > C2C_CP_RGN_SIZE\n");
+ return -EINVAL;
+ }
+
+ /* Set the noncacheable property to the region */
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ vma->vm_flags |= VM_RESERVED | VM_IO;
+
+ pfn = __phys_to_pfn(C2C_CP_RGN_ADDR + offset);
+ r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
+ if (r) {
+ mif_err("Failed in remap_pfn_range()!!!\n");
+ return -EAGAIN;
+ }
+
+ mif_err("VA = 0x%08lx, offset = 0x%lx, size = %lu\n",
+ vma->vm_start, offset, size);
+
+ return 0;
+}
+#endif
+
+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,
+#ifdef CONFIG_LINK_DEVICE_C2C
+ .mmap = misc_mmap,
+#endif
+};
+
+static int vnet_open(struct net_device *ndev)
+{
+ struct vnet *vnet = netdev_priv(ndev);
+ netif_start_queue(ndev);
+ atomic_inc(&vnet->iod->opened);
+ return 0;
+}
+
+static int vnet_stop(struct net_device *ndev)
+{
+ struct vnet *vnet = netdev_priv(ndev);
+ atomic_dec(&vnet->iod->opened);
+ netif_stop_queue(ndev);
+ return 0;
+}
+
+static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ int ret = 0;
+ int headroom = 0;
+ int tailroom = 0;
+ struct sk_buff *skb_new = NULL;
+ struct vnet *vnet = netdev_priv(ndev);
+ struct io_device *iod = vnet->iod;
+ struct link_device *ld = get_current_link(iod);
+ struct raw_hdr hd;
+ struct iphdr *ip_header = NULL;
+
+ /* When use `handover' with Network Bridge,
+ * user -> TCP/IP(kernel) -> bridge device -> TCP/IP(kernel) -> this.
+ *
+ * We remove the one ethernet header of skb before using skb->len,
+ * because the skb has two ethernet headers.
+ */
+ if (iod->use_handover) {
+ if (iod->id >= PSD_DATA_CHID_BEGIN &&
+ iod->id <= PSD_DATA_CHID_END)
+ skb_pull(skb, sizeof(struct ethhdr));
+ }
+
+ /* ip loop-back */
+ ip_header = (struct iphdr *)skb->data;
+ if (iod->msd->loopback_ipaddr &&
+ ip_header->daddr == iod->msd->loopback_ipaddr) {
+ swap(ip_header->saddr, ip_header->daddr);
+ hd.channel = DATA_LOOPBACK_CHANNEL;
+ } else {
+ hd.channel = iod->id & 0x1F;
+ }
+ hd.len = skb->len + sizeof(hd);
+ hd.control = 0;
+
+ headroom = sizeof(hd) + sizeof(hdlc_start);
+ tailroom = sizeof(hdlc_end);
+ if (ld->aligned)
+ tailroom += MAX_LINK_PADDING_SIZE;
+ if (skb_headroom(skb) < headroom || skb_tailroom(skb) < tailroom) {
+ skb_new = skb_copy_expand(skb, headroom, tailroom, GFP_ATOMIC);
+ /* skb_copy_expand success or not, free old skb from caller */
+ dev_kfree_skb_any(skb);
+ if (!skb_new)
+ return -ENOMEM;
+ } else
+ skb_new = skb;
+
+ 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));
+ skb_put(skb_new, calc_padding_size(iod, ld, skb_new->len));
+
+ skbpriv(skb_new)->iod = iod;
+ skbpriv(skb_new)->ld = ld;
+
+ ret = ld->send(ld, iod, skb_new);
+ if (ret < 0) {
+ netif_stop_queue(ndev);
+ dev_kfree_skb_any(skb_new);
+ return NETDEV_TX_BUSY;
+ }
+
+ ndev->stats.tx_packets++;
+ ndev->stats.tx_bytes += skb->len;
+
+ 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 sipc4_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;
+
+ iod->sim_state_changed = io_dev_sim_state_changed;
+
+ /* Get data from link device */
+ iod->recv = io_dev_recv_data_from_link_dev;
+
+ /* Register misc or net device */
+ 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)
+ mif_err("failed to register misc io device : %s\n",
+ iod->name);
+
+ break;
+
+ case IODEV_NET:
+ skb_queue_head_init(&iod->sk_rx_q);
+ if (iod->use_handover)
+ iod->ndev = alloc_netdev(0, iod->name,
+ vnet_setup_ether);
+ else
+ iod->ndev = alloc_netdev(0, iod->name, vnet_setup);
+
+ if (!iod->ndev) {
+ mif_err("failed to alloc netdev\n");
+ return -ENOMEM;
+ }
+
+ ret = register_netdev(iod->ndev);
+ if (ret)
+ free_netdev(iod->ndev);
+
+ mif_debug("(iod:0x%p)\n", iod);
+ vnet = netdev_priv(iod->ndev);
+ mif_debug("(vnet:0x%p)\n", 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);
+
+ iod->miscdev.minor = MISC_DYNAMIC_MINOR;
+ iod->miscdev.name = iod->name;
+ iod->miscdev.fops = &misc_io_fops;
+
+ ret = misc_register(&iod->miscdev);
+ if (ret)
+ mif_err("failed to register misc io device : %s\n",
+ iod->name);
+ ret = device_create_file(iod->miscdev.this_device,
+ &attr_waketime);
+ if (ret)
+ mif_err("failed to create `waketime' file : %s\n",
+ iod->name);
+ ret = device_create_file(iod->miscdev.this_device,
+ &attr_loopback);
+ if (ret)
+ mif_err("failed to create `loopback file' : %s\n",
+ iod->name);
+ break;
+
+ default:
+ mif_err("wrong io_type : %d\n", iod->io_typ);
+ return -EINVAL;
+ }
+
+ mif_debug("%s(%d) : init_io_device() done : %d\n",
+ iod->name, iod->io_typ, ret);
+ return ret;
+}
+
diff --git a/drivers/misc/modem_if/sipc4_modem.c b/drivers/misc/modem_if/sipc4_modem.c
new file mode 100644
index 00000000000..58b3fafaf0c
--- /dev/null
+++ b/drivers/misc/modem_if/sipc4_modem.c
@@ -0,0 +1,371 @@
+/* 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>
+#ifdef CONFIG_HAS_WAKELOCK
+#include <linux/wakelock.h>
+#endif
+
+#include <linux/platform_data/modem.h>
+#include "modem_prj.h"
+#include "modem_variation.h"
+#include "modem_utils.h"
+
+#define FMT_WAKE_TIME (HZ/2)
+#define RFS_WAKE_TIME (HZ*3)
+#define RAW_WAKE_TIME (HZ*6)
+
+static struct modem_shared *create_modem_shared_data(void)
+{
+ struct modem_shared *msd;
+
+ msd = kzalloc(sizeof(struct modem_shared), GFP_KERNEL);
+ if (!msd)
+ return NULL;
+
+ /* initialize link device list */
+ INIT_LIST_HEAD(&msd->link_dev_list);
+
+ /* initialize tree of io devices */
+ msd->iodevs_tree_chan = RB_ROOT;
+ msd->iodevs_tree_fmt = RB_ROOT;
+
+ msd->storage.cnt = 0;
+ msd->storage.addr =
+ kzalloc(MAX_MIF_BUFF_SIZE + MAX_MIF_SEPA_SIZE, GFP_KERNEL);
+ if (!msd->storage.addr) {
+ mif_err("IPC logger buff alloc failed!!\n");
+ return NULL;
+ }
+ memset(msd->storage.addr, 0, MAX_MIF_BUFF_SIZE);
+ memcpy(msd->storage.addr, MIF_SEPARATOR, MAX_MIF_SEPA_SIZE);
+ msd->storage.addr += MAX_MIF_SEPA_SIZE;
+ spin_lock_init(&msd->lock);
+
+ return msd;
+}
+
+static struct modem_ctl *create_modemctl_device(struct platform_device *pdev,
+ struct modem_shared *msd)
+{
+ 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->msd = msd;
+ modemctl->dev = dev;
+ modemctl->phone_state = STATE_OFFLINE;
+
+ pdata = pdev->dev.platform_data;
+ modemctl->mdm_data = pdata;
+ modemctl->name = pdata->name;
+
+ /* init modemctl device for getting modemctl operations */
+ ret = call_modem_init_func(modemctl, pdata);
+ if (ret) {
+ kfree(modemctl);
+ return NULL;
+ }
+
+ mif_info("%s is created!!!\n", pdata->name);
+
+ return modemctl;
+}
+
+static struct io_device *create_io_device(struct modem_io_t *io_t,
+ struct modem_shared *msd, struct modem_ctl *modemctl,
+ struct modem_data *pdata)
+{
+ int ret = 0;
+ struct io_device *iod = NULL;
+
+ iod = kzalloc(sizeof(struct io_device), GFP_KERNEL);
+ if (!iod) {
+ mif_err("iod == NULL\n");
+ return NULL;
+ }
+
+ rb_init_node(&iod->node_chan);
+ rb_init_node(&iod->node_fmt);
+
+ iod->name = io_t->name;
+ iod->id = io_t->id;
+ iod->format = io_t->format;
+ iod->io_typ = io_t->io_type;
+ iod->link_types = io_t->links;
+ iod->net_typ = pdata->modem_net;
+ iod->use_handover = pdata->use_handover;
+ iod->ipc_version = pdata->ipc_version;
+ atomic_set(&iod->opened, 0);
+
+ /* link between io device and modem control */
+ iod->mc = modemctl;
+ if (iod->format == IPC_FMT)
+ modemctl->iod = iod;
+ if (iod->format == IPC_BOOT) {
+ modemctl->bootd = iod;
+ mif_info("Bood device = %s\n", iod->name);
+ }
+
+ /* link between io device and modem shared */
+ iod->msd = msd;
+
+ /* add iod to rb_tree */
+ if (iod->format != IPC_RAW)
+ insert_iod_with_format(msd, iod->format, iod);
+
+ if (sipc4_is_not_reserved_channel(iod->id))
+ insert_iod_with_channel(msd, iod->id, iod);
+
+ /* register misc device or net device */
+ ret = sipc4_init_io_device(iod);
+ if (ret) {
+ kfree(iod);
+ mif_err("sipc4_init_io_device fail (%d)\n", ret);
+ return NULL;
+ }
+
+ mif_debug("%s is created!!!\n", iod->name);
+ return iod;
+}
+
+static int attach_devices(struct io_device *iod, enum modem_link tx_link)
+{
+ struct modem_shared *msd = iod->msd;
+ struct link_device *ld;
+
+ /* find link type for this io device */
+ list_for_each_entry(ld, &msd->link_dev_list, list) {
+ if (IS_CONNECTED(iod, ld)) {
+ /* The count 1 bits of iod->link_types is count
+ * of link devices of this iod.
+ * If use one link device,
+ * or, 2+ link devices and this link is tx_link,
+ * set iod's link device with ld
+ */
+ if ((countbits(iod->link_types) <= 1) ||
+ (tx_link == ld->link_type)) {
+ mif_debug("set %s->%s\n", iod->name, ld->name);
+
+ set_current_link(iod, ld);
+
+ if (iod->ipc_version == SIPC_VER_42) {
+ if (iod->format == IPC_FMT) {
+ int ch = iod->id & 0x03;
+ ld->fmt_iods[ch] = iod;
+ }
+ }
+ }
+ }
+ }
+
+ /* if use rx dynamic switch, set tx_link at modem_io_t of
+ * board-*-modems.c
+ */
+ if (!get_current_link(iod)) {
+ mif_err("%s->link == NULL\n", iod->name);
+ BUG();
+ }
+
+ switch (iod->format) {
+ case IPC_FMT:
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name);
+#else
+ device_init_wakeup(iod->miscdev.this_device, true);
+#endif
+ iod->waketime = FMT_WAKE_TIME;
+ break;
+
+ case IPC_RFS:
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name);
+#else
+ device_init_wakeup(iod->miscdev.this_device, true);
+#endif
+ iod->waketime = RFS_WAKE_TIME;
+ break;
+
+ case IPC_MULTI_RAW:
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name);
+#else
+ device_init_wakeup(iod->miscdev.this_device, true);
+#endif
+ iod->waketime = RAW_WAKE_TIME;
+ break;
+ case IPC_BOOT:
+#ifdef CONFIG_HAS_WAKELOCK
+ wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name);
+#else
+ device_init_wakeup(iod->miscdev.this_device, true);
+#endif
+ iod->waketime = 3 * HZ;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int __devinit modem_probe(struct platform_device *pdev)
+{
+ int i;
+ struct modem_data *pdata = pdev->dev.platform_data;
+ struct modem_shared *msd = NULL;
+ struct modem_ctl *modemctl = NULL;
+ struct io_device *iod[pdata->num_iodevs];
+ struct link_device *ld;
+
+ mif_err("%s\n", pdev->name);
+ memset(iod, 0, sizeof(iod));
+
+ msd = create_modem_shared_data();
+ if (!msd) {
+ mif_err("msd == NULL\n");
+ goto err_free_modemctl;
+ }
+
+ modemctl = create_modemctl_device(pdev, msd);
+ if (!modemctl) {
+ mif_err("modemctl == NULL\n");
+ goto err_free_modemctl;
+ }
+
+ /* create link device */
+ /* support multi-link device */
+ for (i = 0; i < LINKDEV_MAX ; i++) {
+ /* find matching link type */
+ if (pdata->link_types & LINKTYPE(i)) {
+ ld = call_link_init_func(pdev, i);
+ if (!ld)
+ goto err_free_modemctl;
+
+ mif_err("link created: %s\n", ld->name);
+ ld->link_type = i;
+ ld->mc = modemctl;
+ ld->msd = msd;
+ list_add(&ld->list, &msd->link_dev_list);
+ }
+ }
+
+ /* create io deivces and connect to modemctl device */
+ for (i = 0; i < pdata->num_iodevs; i++) {
+ iod[i] = create_io_device(&pdata->iodevs[i], msd, modemctl,
+ pdata);
+ if (!iod[i]) {
+ mif_err("iod[%d] == NULL\n", i);
+ goto err_free_modemctl;
+ }
+
+ attach_devices(iod[i], pdata->iodevs[i].tx_link);
+ }
+
+ platform_set_drvdata(pdev, modemctl);
+
+ mif_info("Complete!!!\n");
+ return 0;
+
+err_free_modemctl:
+ for (i = 0; i < pdata->num_iodevs; i++)
+ if (iod[i] != NULL)
+ kfree(iod[i]);
+
+ if (modemctl != NULL)
+ kfree(modemctl);
+
+ if (msd != NULL)
+ kfree(msd);
+
+ return -ENOMEM;
+}
+
+static void modem_shutdown(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct modem_ctl *mc = dev_get_drvdata(dev);
+ mc->ops.modem_off(mc);
+ mc->phone_state = STATE_OFFLINE;
+}
+
+static int modem_suspend(struct device *pdev)
+{
+#ifndef CONFIG_LINK_DEVICE_HSIC
+ struct modem_ctl *mc = dev_get_drvdata(pdev);
+
+ if (mc->gpio_pda_active)
+ gpio_set_value(mc->gpio_pda_active, 0);
+#endif
+
+ return 0;
+}
+
+static int modem_resume(struct device *pdev)
+{
+#ifndef CONFIG_LINK_DEVICE_HSIC
+ struct modem_ctl *mc = dev_get_drvdata(pdev);
+
+ if (mc->gpio_pda_active)
+ gpio_set_value(mc->gpio_pda_active, 1);
+#endif
+
+ 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/sipc5_io_device.c b/drivers/misc/modem_if/sipc5_io_device.c
new file mode 100644
index 00000000000..9e22e9cb718
--- /dev/null
+++ b/drivers/misc/modem_if/sipc5_io_device.c
@@ -0,0 +1,1620 @@
+/* /linux/drivers/misc/modem_if/sipc5_io_device.c
+ *
+ * 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/device.h>
+#include <linux/module.h>
+
+#include <linux/platform_data/modem.h>
+#ifdef CONFIG_LINK_DEVICE_C2C
+#include <linux/platform_data/c2c.h>
+#endif
+#include "modem_prj.h"
+#include "modem_utils.h"
+
+static ssize_t show_waketime(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int msec;
+ char *p = buf;
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct io_device *iod = container_of(miscdev, struct io_device,
+ miscdev);
+
+ msec = jiffies_to_msecs(iod->waketime);
+
+ p += sprintf(buf, "raw waketime : %ums\n", msec);
+
+ return p - buf;
+}
+
+static ssize_t store_waketime(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned long msec;
+ int ret;
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct io_device *iod = container_of(miscdev, struct io_device,
+ miscdev);
+
+ ret = strict_strtoul(buf, 10, &msec);
+ if (ret)
+ return count;
+
+ iod->waketime = msecs_to_jiffies(msec);
+
+ return count;
+}
+
+static struct device_attribute attr_waketime =
+ __ATTR(waketime, S_IRUGO | S_IWUSR, show_waketime, store_waketime);
+
+static ssize_t show_loopback(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct modem_shared *msd =
+ container_of(miscdev, struct io_device, miscdev)->msd;
+ unsigned char *ip = (unsigned char *)&msd->loopback_ipaddr;
+ char *p = buf;
+
+ p += sprintf(buf, "%u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
+
+ return p - buf;
+}
+
+static ssize_t store_loopback(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct modem_shared *msd =
+ container_of(miscdev, struct io_device, miscdev)->msd;
+
+ msd->loopback_ipaddr = ipv4str_to_be32(buf, count);
+
+ return count;
+}
+
+static struct device_attribute attr_loopback =
+ __ATTR(loopback, S_IRUGO | S_IWUSR, show_loopback, store_loopback);
+
+static void iodev_showtxlink(struct io_device *iod, void *args)
+{
+ char **p = (char **)args;
+ struct link_device *ld = get_current_link(iod);
+
+ if (iod->io_typ == IODEV_NET && IS_CONNECTED(iod, ld))
+ *p += sprintf(*p, "%s: %s\n", iod->name, ld->name);
+}
+
+static ssize_t show_txlink(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct miscdevice *miscdev = dev_get_drvdata(dev);
+ struct modem_shared *msd =
+ container_of(miscdev, struct io_device, miscdev)->msd;
+ char *p = buf;
+
+ iodevs_for_each(msd, iodev_showtxlink, &p);
+
+ return p - buf;
+}
+
+static ssize_t store_txlink(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ /* don't change without gpio dynamic switching */
+ return -EINVAL;
+}
+
+static struct device_attribute attr_txlink =
+ __ATTR(txlink, S_IRUGO | S_IWUSR, show_txlink, store_txlink);
+
+/**
+ * rx_check_frame_cfg
+ * @cfg: configuration field of a link layer header
+ * @frm: pointer to the sipc5_frame_data buffer
+ *
+ * 1) Checks whether or not an extended field exists
+ * 2) Calculates the length of a link layer header
+ *
+ * Returns the size of a link layer header
+ *
+ * Must be invoked only when the configuration field of the link layer header
+ * is validated with sipc5_start_valid() function
+ */
+static int rx_check_frame_cfg(u8 cfg, struct sipc5_frame_data *frm)
+{
+ frm->config = cfg;
+
+ if (likely(cfg & SIPC5_PADDING_EXIST))
+ frm->padding = true;
+
+ if (unlikely(cfg & SIPC5_EXT_FIELD_EXIST)) {
+ if (cfg & SIPC5_CTL_FIELD_EXIST) {
+ frm->ctl_fld = true;
+ frm->hdr_len = SIPC5_HEADER_SIZE_WITH_CTL_FLD;
+ } else {
+ frm->ext_len = true;
+ frm->hdr_len = SIPC5_HEADER_SIZE_WITH_EXT_LEN;
+ }
+ } else {
+ frm->hdr_len = SIPC5_MIN_HEADER_SIZE;
+ }
+
+ return frm->hdr_len;
+}
+
+/**
+ * rx_build_meta_data
+ * @ld: pointer to the link device
+ * @frm: pointer to the sipc5_frame_data buffer
+ *
+ * Fills each field of sipc5_frame_data from a link layer header
+ * 1) Extracts the channel ID
+ * 2) Calculates the length of a link layer frame
+ * 3) Extracts a control field if exists
+ * 4) Calculates the length of an IPC message packet in the link layer frame
+ *
+ */
+static void rx_build_meta_data(struct link_device *ld,
+ struct sipc5_frame_data *frm)
+{
+ u16 *sz16 = (u16 *)(frm->hdr + SIPC5_LEN_OFFSET);
+ u32 *sz32 = (u32 *)(frm->hdr + SIPC5_LEN_OFFSET);
+
+ frm->ch_id = frm->hdr[SIPC5_CH_ID_OFFSET];
+
+ if (unlikely(frm->ext_len))
+ frm->len = *sz32;
+ else
+ frm->len = *sz16;
+
+ if (unlikely(frm->ctl_fld))
+ frm->control = frm->hdr[SIPC5_CTL_OFFSET];
+
+ frm->data_len = frm->len - frm->hdr_len;
+
+ mif_debug("%s: FRM ch:%d len:%d ctl:%02X data.len:%d\n",
+ ld->name, frm->ch_id, frm->len, frm->control, frm->data_len);
+}
+
+/**
+ * tx_build_link_header
+ * @frm: pointer to the sipc5_frame_data buffer
+ * @iod: pointer to the IO device
+ * @ld: pointer to the link device
+ * @count: length of the data to be transmitted
+ *
+ * Builds the meta data for an SIPC5 frame and the link layer header of it
+ * Returns the link layer header length for an SIPC5 frame or 0 for other frame
+ */
+static unsigned tx_build_link_header(struct sipc5_frame_data *frm,
+ struct io_device *iod, struct link_device *ld, ssize_t count)
+{
+ u8 *buff = frm->hdr;
+ u16 *sz16 = (u16 *)(buff + SIPC5_LEN_OFFSET);
+ u32 *sz32 = (u32 *)(buff + SIPC5_LEN_OFFSET);
+
+ memset(frm, 0, sizeof(struct sipc5_frame_data));
+
+ if (iod->format == IPC_CMD ||
+ iod->format == IPC_BOOT ||
+ iod->format == IPC_RAMDUMP) {
+ frm->len = count;
+ return 0;
+ }
+
+ frm->config = SIPC5_START_MASK;
+
+ if (iod->format == IPC_FMT && count > 2048) {
+ frm->ctl_fld = true;
+ frm->config |= SIPC5_EXT_FIELD_EXIST;
+ frm->config |= SIPC5_CTL_FIELD_EXIST;
+ }
+
+ if (iod->id >= SIPC5_CH_ID_RFS_0 && count > 0xFFFF) {
+ frm->ext_len = true;
+ frm->config |= SIPC5_EXT_FIELD_EXIST;
+ }
+
+ if (ld->aligned)
+ frm->config |= SIPC5_PADDING_EXIST;
+
+ frm->ch_id = iod->id;
+
+ frm->hdr_len = sipc5_get_hdr_len(frm->config);
+ frm->data_len = count;
+ frm->len = frm->hdr_len + frm->data_len;
+
+ buff[SIPC5_CONFIG_OFFSET] = frm->config;
+ buff[SIPC5_CH_ID_OFFSET] = frm->ch_id;
+
+ if (unlikely(frm->ext_len))
+ *sz32 = (u32)frm->len;
+ else
+ *sz16 = (u16)frm->len;
+
+ if (unlikely(frm->ctl_fld))
+ buff[SIPC5_CTL_OFFSET] = frm->control;
+
+ return frm->hdr_len;
+}
+
+static int rx_fmt_frame(struct sk_buff *skb)
+{
+ struct io_device *iod = skbpriv(skb)->iod;
+ struct link_device *ld = skbpriv(skb)->ld;
+ struct sk_buff_head *rxq = &iod->sk_rx_q;
+ struct sipc_fmt_hdr *fh;
+ struct sk_buff *rx_skb;
+ u8 ctrl = skbpriv(skb)->control;
+ unsigned id = ctrl & 0x7F;
+
+ if (iod->skb[id] == NULL) {
+ /*
+ ** There has been no multiple frame with this ID.
+ */
+ if ((ctrl & 0x80) == 0) {
+ /*
+ ** It is a single frame because the "more" bit is 0.
+ */
+ skb_queue_tail(rxq, skb);
+ if (unlikely(rxq->qlen > 2048)) {
+ struct sk_buff *victim;
+ mif_info("%s: WARNING! rxq->qlen %d > 2048\n",
+ iod->name, rxq->qlen);
+ victim = skb_dequeue(rxq);
+ dev_kfree_skb_any(victim);
+ } else {
+ mif_debug("%s: rxq->qlen = %d\n",
+ iod->name, rxq->qlen);
+ }
+
+ wake_up(&iod->wq);
+ return 0;
+ }
+
+ /*
+ ** The start of multiple frames
+ */
+ fh = (struct sipc_fmt_hdr *)skb->data;
+ mif_debug("%s: start multi-frame (ID:%d len:%d)\n",
+ iod->name, id, fh->len);
+
+ rx_skb = rx_alloc_skb(fh->len, iod, ld);
+ if (!rx_skb) {
+ mif_info("%s: ERR! rx_alloc_skb fail\n", iod->name);
+ return -ENOMEM;
+ }
+
+ iod->skb[id] = rx_skb;
+ } else {
+ rx_skb = iod->skb[id];
+ }
+
+ /*
+ ** Start multi-frame processing
+ */
+ memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
+ dev_kfree_skb_any(skb);
+
+ if (ctrl & 0x80) {
+ /* The last frame has not arrived yet. */
+ mif_debug("%s: recv multi-frame (ID:%d rcvd:%d)\n",
+ iod->name, id, rx_skb->len);
+ } else {
+ /* It is the last frame because the "more" bit is 0. */
+ mif_debug("%s: end multi-frame (ID:%d rcvd:%d)\n",
+ iod->name, id, rx_skb->len);
+ skb_queue_tail(rxq, rx_skb);
+ if (unlikely(rxq->qlen > 2048)) {
+ struct sk_buff *victim;
+ mif_info("%s: WARNING! rxq->qlen %d > 2048\n",
+ iod->name, rxq->qlen);
+ victim = skb_dequeue(rxq);
+ dev_kfree_skb_any(victim);
+ } else {
+ mif_debug("%s: rxq->qlen = %d\n", iod->name, rxq->qlen);
+ }
+
+ iod->skb[id] = NULL;
+ wake_up(&iod->wq);
+ }
+
+ return 0;
+}
+
+static int rx_rfs_frame(struct sk_buff *skb)
+{
+ struct io_device *iod = skbpriv(skb)->iod;
+ struct sk_buff_head *rxq = &iod->sk_rx_q;
+
+ skb_queue_tail(rxq, skb);
+ if (unlikely(rxq->qlen > 2048)) {
+ struct sk_buff *victim;
+ mif_debug("%s: rxq->qlen %d > 2048\n", iod->name, rxq->qlen);
+ victim = skb_dequeue(rxq);
+ dev_kfree_skb_any(victim);
+ } else {
+ mif_debug("%s: rxq->qlen %d\n", iod->name, rxq->qlen);
+ }
+
+ wake_up(&iod->wq);
+
+ return 0;
+}
+
+static int rx_loopback(struct sk_buff *skb)
+{
+ struct io_device *iod = skbpriv(skb)->iod;
+ struct link_device *ld = get_current_link(iod);
+ struct sipc5_frame_data frm;
+ unsigned headroom;
+ unsigned tailroom = 0;
+ int ret;
+
+ headroom = tx_build_link_header(&frm, iod, ld, skb->len);
+
+ if (ld->aligned)
+ tailroom = sipc5_calc_padding_size(headroom + skb->len);
+
+ /* We need not to expand skb in here. dev_alloc_skb (in rx_alloc_skb)
+ * already alloc 32bytes padding in headroom. 32bytes are enough.
+ */
+
+ /* store IPC link header to start of skb
+ * this is skb_push not skb_put. different with misc_write.
+ */
+ memcpy(skb_push(skb, headroom), frm.hdr, headroom);
+
+ /* store padding */
+ if (tailroom)
+ skb_put(skb, tailroom);
+
+ /* forward */
+ ret = ld->send(ld, iod, skb);
+ if (ret < 0)
+ mif_err("%s->%s: ld->send fail: %d\n", iod->name,
+ ld->name, ret);
+ return ret;
+}
+
+static int rx_raw_misc(struct sk_buff *skb)
+{
+ struct io_device *iod = skbpriv(skb)->iod; /* same with real_iod */
+ struct sk_buff_head *rxq = &iod->sk_rx_q;
+
+ skb_queue_tail(rxq, skb);
+ if (unlikely(rxq->qlen > 2048)) {
+ struct sk_buff *victim;
+ mif_debug("%s: rxq->qlen %d > 2048\n", iod->name, rxq->qlen);
+ victim = skb_dequeue(rxq);
+ dev_kfree_skb_any(victim);
+ } else {
+ mif_debug("%s: rxq->qlen %d\n", iod->name, rxq->qlen);
+ }
+
+ wake_up(&iod->wq);
+
+ return 0;
+}
+
+static int rx_multi_pdp(struct sk_buff *skb)
+{
+ struct io_device *iod = skbpriv(skb)->iod; /* same with real_iod */
+ struct net_device *ndev;
+ struct iphdr *iphdr;
+ struct ethhdr *ehdr;
+ int ret;
+ const char source[ETH_ALEN] = SOURCE_MAC_ADDR;
+
+ ndev = iod->ndev;
+ if (!ndev) {
+ mif_info("%s: ERR! no iod->ndev\n", iod->name);
+ return -ENODEV;
+ }
+
+ skb->dev = ndev;
+ ndev->stats.rx_packets++;
+ ndev->stats.rx_bytes += skb->len;
+
+ /* check the version of IP */
+ iphdr = (struct iphdr *)skb->data;
+ if (iphdr->version == IP6VERSION)
+ skb->protocol = htons(ETH_P_IPV6);
+ else
+ skb->protocol = htons(ETH_P_IP);
+
+ if (iod->use_handover) {
+ skb_push(skb, sizeof(struct ethhdr));
+ ehdr = (void *)skb->data;
+ 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_UNNECESSARY;
+ skb_reset_mac_header(skb);
+
+ skb_pull(skb, sizeof(struct ethhdr));
+ }
+
+ if (in_interrupt())
+ ret = netif_rx(skb);
+ else
+ ret = netif_rx_ni(skb);
+
+ if (ret != NET_RX_SUCCESS)
+ mif_info("%s: ERR! netif_rx fail (err %d)\n", iod->name, ret);
+
+ return ret;
+}
+
+static int rx_demux(struct link_device *ld, struct sk_buff *skb)
+{
+ struct io_device *iod = NULL;
+ char *link = ld->name;
+ u8 ch = skbpriv(skb)->ch_id;
+
+ if (unlikely(ch == SIPC5_CH_ID_MAX || ch == 0)) {
+ mif_info("%s: ERR! invalid ch# %d\n", link, ch);
+ return -ENODEV;
+ }
+
+ /* IP loopback */
+ if (ch == DATA_LOOPBACK_CHANNEL && ld->msd->loopback_ipaddr)
+ ch = RMNET0_CH_ID;
+
+ iod = link_get_iod_with_channel(ld, ch);
+ if (unlikely(!iod)) {
+ mif_info("%s: ERR! no iod for ch# %d\n", link, ch);
+ return -ENODEV;
+ }
+
+ skbpriv(skb)->ld = ld;
+ skbpriv(skb)->iod = iod;
+ skbpriv(skb)->real_iod = iod;
+
+ /* don't care about CP2AP_LOOPBACK_CHANNEL is opened */
+ if (unlikely(iod->id == CP2AP_LOOPBACK_CHANNEL))
+ return rx_loopback(skb);
+
+ if (atomic_read(&iod->opened) <= 0) {
+ mif_info("%s: ERR! %s is not opened\n", link, iod->name);
+ return -ENODEV;
+ }
+
+ if (ch >= SIPC5_CH_ID_RFS_0)
+ return rx_rfs_frame(skb);
+ else if (ch >= SIPC5_CH_ID_FMT_0)
+ return rx_fmt_frame(skb);
+ else if (iod->io_typ == IODEV_MISC)
+ return rx_raw_misc(skb);
+ else
+ return rx_multi_pdp(skb);
+}
+
+/* Check and store link layer header, then alloc an skb */
+static int rx_header_from_serial(struct io_device *iod, struct link_device *ld,
+ u8 *buff, unsigned size, struct sipc5_frame_data *frm)
+{
+ char *link = ld->name;
+ struct sk_buff *skb;
+ int len;
+ u8 cfg = buff[0];
+
+ mif_debug("%s: size %d\n", link, size);
+
+ if (!frm->config) {
+ if (unlikely(!sipc5_start_valid(cfg))) {
+ mif_info("%s: ERR! wrong start (0x%02x)\n", link, cfg);
+ return -EBADMSG;
+ }
+ rx_check_frame_cfg(cfg, frm);
+
+ /* Copy the link layer header to the header buffer */
+ len = min(frm->hdr_len, size);
+ memcpy(frm->hdr, buff, len);
+ } else {
+ /* Copy the link layer header to the header buffer */
+ len = min((frm->hdr_len - frm->hdr_rcvd), size);
+ memcpy((frm->hdr + frm->hdr_rcvd), buff, len);
+ }
+
+ frm->hdr_rcvd += len;
+
+ mif_debug("%s: FRM hdr_len:%d, hdr_rcvd:%d\n",
+ link, frm->hdr_len, frm->hdr_rcvd);
+
+ if (frm->hdr_rcvd >= frm->hdr_len) {
+ rx_build_meta_data(ld, frm);
+ skb = rx_alloc_skb(frm->data_len, iod, ld);
+ fragdata(iod, ld)->skb_recv = skb;
+ skbpriv(skb)->ch_id = frm->ch_id;
+ skbpriv(skb)->control = frm->control;
+ }
+
+ return len;
+}
+
+/* copy data to skb */
+static int rx_payload_from_serial(struct io_device *iod, struct link_device *ld,
+ u8 *buff, unsigned size, struct sipc5_frame_data *frm)
+{
+ struct sk_buff *skb = fragdata(iod, ld)->skb_recv;
+ char *link = ld->name;
+ unsigned rest = frm->data_len - frm->data_rcvd;
+ unsigned len;
+
+ /* rest == (frm->data_len - frm->data_rcvd) == tailroom of skb */
+ rest = frm->data_len - frm->data_rcvd;
+ mif_debug("%s: FRM data.len:%d data.rcvd:%d rest:%d size:%d\n",
+ link, frm->data_len, frm->data_rcvd, rest, size);
+
+ /* If there is no skb, data must be dropped. */
+ len = min(rest, size);
+ if (skb)
+ memcpy(skb_put(skb, len), buff, len);
+
+ frm->data_rcvd += len;
+
+ mif_debug("%s: FRM data_len:%d, data_rcvd:%d\n",
+ link, frm->data_len, frm->data_rcvd);
+
+ return len;
+}
+
+static int rx_frame_from_serial(struct io_device *iod, struct link_device *ld,
+ const char *data, unsigned size)
+{
+ struct sipc5_frame_data *frm = &fragdata(iod, ld)->f_data;
+ struct sk_buff *skb;
+ char *link = ld->name;
+ u8 *buff = (u8 *)data;
+ int rest = (int)size;
+ int err = 0;
+ int done = 0;
+
+ mif_debug("%s: size = %d\n", link, size);
+
+ if (frm->hdr_rcvd >= frm->hdr_len && frm->data_rcvd < frm->data_len) {
+ /*
+ ** There is an skb that is waiting for more SIPC5 data.
+ ** In this case, rx_header_from_serial() must be skipped.
+ */
+ mif_debug("%s: FRM data.len:%d data.rcvd:%d -> recv_data\n",
+ link, frm->data_len, frm->data_rcvd);
+ goto recv_data;
+ }
+
+next_frame:
+ /* Receive and analyze header, then prepare an akb */
+ err = done = rx_header_from_serial(iod, ld, buff, rest, frm);
+ if (err < 0)
+ goto err_exit;
+
+ buff += done;
+ rest -= done;
+ mif_debug("%s: rx_header() -> done:%d rest:%d\n", link, done, rest);
+ if (rest < 0)
+ goto err_range;
+
+ if (rest == 0)
+ return size;
+
+recv_data:
+ err = 0;
+
+ mif_debug("%s: done:%d rest:%d -> rx_payload()\n", link, done, rest);
+
+ done = rx_payload_from_serial(iod, ld, buff, rest, frm);
+ buff += done;
+ rest -= done;
+
+ mif_debug("%s: rx_payload() -> done:%d rest:%d\n", link, done, rest);
+
+ if (rest == 0 && frm->data_rcvd < frm->data_len) {
+ /*
+ Data is being received and more data will come within the next
+ frame from the link device.
+ */
+ return size;
+ }
+
+ /* At this point, one complete link layer frame has been received. */
+
+ /* A padding size is applied to access the next IPC frame. */
+ if (frm->padding) {
+ done = sipc5_calc_padding_size(frm->len);
+ if (done > rest) {
+ mif_info("%s: ERR! padding %d > rest %d\n",
+ link, done, rest);
+ goto err_exit;
+ }
+
+ buff += done;
+ rest -= done;
+
+ mif_debug("%s: padding:%d -> rest:%d\n", link, done, rest);
+
+ if (rest < 0)
+ goto err_range;
+
+ }
+
+ skb = fragdata(iod, ld)->skb_recv;
+ if (likely(skb)) {
+ mif_debug("%s: len:%d -> rx_demux()\n", link, skb->len);
+ err = rx_demux(ld, skb);
+ if (err < 0)
+ dev_kfree_skb_any(skb);
+ } else {
+ mif_debug("%s: len:%d -> drop\n", link, skb->len);
+ }
+
+ /* initialize the skb_recv and the frame_data buffer */
+ fragdata(iod, ld)->skb_recv = NULL;
+ memset(frm, 0, sizeof(struct sipc5_frame_data));
+
+ if (rest > 0)
+ goto next_frame;
+
+ if (rest <= 0)
+ return size;
+
+err_exit:
+ if (fragdata(iod, ld)->skb_recv &&
+ frm->hdr_rcvd >= frm->hdr_len && frm->data_rcvd >= frm->data_len) {
+ dev_kfree_skb_any(fragdata(iod, ld)->skb_recv);
+ memset(frm, 0, sizeof(struct sipc5_frame_data));
+ fragdata(iod, ld)->skb_recv = NULL;
+ mif_info("%s: ERR! clear frag\n", link);
+ }
+ return err;
+
+err_range:
+ mif_info("%s: ERR! size:%d vs. rest:%d\n", link, size, rest);
+ return size;
+}
+
+/**
+ * rx_header_from_mem
+ * @ld: pointer to the link device
+ * @buff: pointer to the frame
+ * @rest: size of the frame
+ * @frm: pointer to the sipc5_frame_data buffer
+ *
+ * 1) Verifies a link layer header configuration of a frame
+ * 2) Stores the link layer header to the header buffer
+ * 3) Builds and stores the meta data of the frame into a meta data buffer
+ * 4) Verifies the length of the frame
+ *
+ * Returns SIPC5 header length
+ */
+static int rx_header_from_mem(struct link_device *ld, u8 *buff, unsigned rest,
+ struct sipc5_frame_data *frm)
+{
+ char *link = ld->name;
+ u8 cfg = buff[0];
+
+ /* Verify link layer header configuration */
+ if (unlikely(!sipc5_start_valid(cfg))) {
+ mif_info("%s: ERR! wrong start (0x%02x)\n", link, cfg);
+ return -EBADMSG;
+ }
+ rx_check_frame_cfg(cfg, frm);
+
+ /* Store the link layer header to the header buffer */
+ memcpy(frm->hdr, buff, frm->hdr_len);
+ frm->hdr_rcvd = frm->hdr_len;
+
+ /* Build and store the meta data of this frame */
+ rx_build_meta_data(ld, frm);
+
+ /* Verify frame length */
+ if (unlikely(frm->len > rest)) {
+ mif_info("%s: ERR! frame length %d > rest %d\n",
+ link, frm->len, rest);
+ return -EBADMSG;
+ }
+
+ return frm->hdr_rcvd;
+}
+
+/* copy data to skb */
+static int rx_payload_from_mem(struct sk_buff *skb, u8 *buff, unsigned len)
+{
+ /* If there is no skb, data must be dropped. */
+ if (skb)
+ memcpy(skb_put(skb, len), buff, len);
+ return len;
+}
+
+static int rx_frame_from_mem(struct io_device *iod, struct link_device *ld,
+ const char *data, unsigned size)
+{
+ struct sipc5_frame_data *frm = &fragdata(iod, ld)->f_data;
+ struct sk_buff *skb;
+ char *link = ld->name;
+ u8 *buff = (u8 *)data;
+ int rest = (int)size;
+ int len;
+ int done;
+
+ mif_debug("%s: size = %d\n", link, size);
+
+ while (rest > 0) {
+ /* Initialize the frame data buffer */
+ memset(frm, 0, sizeof(struct sipc5_frame_data));
+ skb = NULL;
+
+ /* Receive and analyze link layer header */
+ done = rx_header_from_mem(ld, buff, rest, frm);
+ if (unlikely(done < 0))
+ return -EBADMSG;
+
+ /* Verify rest size */
+ rest -= done;
+ if (rest < 0) {
+ mif_info("%s: ERR! rx_header -> rest %d\n", link, rest);
+ return -ERANGE;
+ }
+
+ /* Move buff pointer to the payload */
+ buff += done;
+
+ /* Prepare an akb */
+ len = frm->data_len;
+ skb = rx_alloc_skb(len, iod, ld);
+
+ /* Store channel ID and control fields to the CB of the skb */
+ skbpriv(skb)->ch_id = frm->ch_id;
+ skbpriv(skb)->control = frm->control;
+
+ /* Receive payload */
+ mif_debug("%s: done:%d rest:%d len:%d -> rx_payload()\n",
+ link, done, rest, len);
+ done = rx_payload_from_mem(skb, buff, len);
+ rest -= done;
+ if (rest < 0) {
+ mif_info("%s: ERR! rx_payload() -> rest %d\n",
+ link, rest);
+ if (skb)
+ dev_kfree_skb_any(skb);
+ return -ERANGE;
+ }
+ buff += done;
+
+ /* A padding size is applied to access the next IPC frame. */
+ if (frm->padding) {
+ done = sipc5_calc_padding_size(frm->len);
+ if (done > rest) {
+ mif_info("%s: ERR! padding %d > rest %d\n",
+ link, done, rest);
+ if (skb)
+ dev_kfree_skb_any(skb);
+ return -ERANGE;
+ }
+ buff += done;
+ rest -= done;
+ }
+
+ if (likely(skb)) {
+ mif_debug("%s: len:%d -> rx_demux()\n", link, skb->len);
+ if (rx_demux(ld, skb) < 0)
+ dev_kfree_skb_any(skb);
+ } else {
+ mif_debug("%s: len:%d -> drop\n", link, skb->len);
+ }
+ }
+
+ return 0;
+}
+
+/* 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,
+ struct link_device *ld, const char *data, unsigned int len)
+{
+ struct sk_buff_head *rxq = &iod->sk_rx_q;
+ struct sk_buff *skb;
+ char *link = ld->name;
+ int err;
+
+ if (!data) {
+ mif_info("%s: ERR! !data\n", link);
+ return -EINVAL;
+ }
+
+ if (len <= 0) {
+ mif_info("%s: ERR! len %d <= 0\n", link, len);
+ return -EINVAL;
+ }
+
+ switch (iod->format) {
+ case IPC_FMT:
+ case IPC_RAW:
+ case IPC_RFS:
+ case IPC_MULTI_RAW:
+ if (iod->waketime)
+ wake_lock_timeout(&iod->wakelock, iod->waketime);
+
+ if (ld->link_type == LINKDEV_DPRAM && ld->aligned)
+ err = rx_frame_from_mem(iod, ld, data, len);
+ else
+ err = rx_frame_from_serial(iod, ld, data, len);
+
+ if (err < 0)
+ mif_info("%s: ERR! rx_frame_from_link fail (err %d)\n",
+ link, err);
+
+ return err;
+
+ case IPC_CMD:
+ case IPC_BOOT:
+ case IPC_RAMDUMP:
+ /* save packet to sk_buff */
+ skb = rx_alloc_skb(len, iod, ld);
+ if (!skb) {
+ mif_info("%s: ERR! rx_alloc_skb fail\n", link);
+ return -ENOMEM;
+ }
+
+ mif_debug("%s: len:%d -> iod:%s\n", link, len, iod->name);
+
+ memcpy(skb_put(skb, len), data, len);
+ skb_queue_tail(rxq, skb);
+ if (unlikely(rxq->qlen > 2048)) {
+ struct sk_buff *victim;
+ mif_info("%s: ERR! rxq->qlen %d > 2048\n",
+ iod->name, rxq->qlen);
+ victim = skb_dequeue(rxq);
+ dev_kfree_skb_any(victim);
+ }
+ wake_up(&iod->wq);
+
+ return len;
+
+ default:
+ mif_info("%s: ERR! unknown format %d\n", link, iod->format);
+ return -EINVAL;
+ }
+}
+
+static int rx_frame_from_skb(struct io_device *iod, struct link_device *ld,
+ struct sk_buff *skb)
+{
+ struct sipc5_frame_data *frm = &fragdata(iod, ld)->f_data;
+ u8 cfg = skb->data[0];
+
+ /* Initialize the frame data buffer */
+ memset(frm, 0, sizeof(struct sipc5_frame_data));
+
+ /*
+ ** The start of a link layer header has already been checked in the
+ ** link device.
+ */
+
+ /* Analyze the configuration of the link layer header */
+ rx_check_frame_cfg(cfg, frm);
+
+ /* Store the link layer header to the header buffer */
+ memcpy(frm->hdr, skb->data, frm->hdr_len);
+ frm->hdr_rcvd = frm->hdr_len;
+
+ /* Build and store the meta data of this frame */
+ rx_build_meta_data(ld, frm);
+
+ /*
+ ** The length of the frame has already been checked in the link device.
+ */
+
+ /* Trim the link layer header off the frame */
+ skb_pull(skb, frm->hdr_len);
+
+ /* Store channel ID and control fields to the CB of the skb */
+ skbpriv(skb)->ch_id = frm->ch_id;
+ skbpriv(skb)->control = frm->control;
+
+ /* Demux the frame */
+ if (rx_demux(ld, skb) < 0) {
+ mif_err("%s: ERR! rx_demux fail\n", ld->name);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* called from link device when a packet arrives for this io device */
+static int io_dev_recv_skb_from_link_dev(struct io_device *iod,
+ struct link_device *ld, struct sk_buff *skb)
+{
+ char *link = ld->name;
+ enum dev_format dev = iod->format;
+ int err;
+
+ switch (dev) {
+ case IPC_FMT:
+ case IPC_RAW:
+ case IPC_RFS:
+ case IPC_MULTI_RAW:
+ if (iod->waketime)
+ wake_lock_timeout(&iod->wakelock, iod->waketime);
+
+ err = rx_frame_from_skb(iod, ld, skb);
+ if (err < 0) {
+ dev_kfree_skb_any(skb);
+ mif_info("%s: ERR! rx_frame_from_skb fail (err %d)\n",
+ link, err);
+ }
+
+ return err;
+
+ default:
+ mif_info("%s: ERR! unknown device %d\n", link, dev);
+ 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)
+{
+ mif_info("%s: %s state changed (state %d)\n",
+ iod->name, iod->mc->name, state);
+
+ iod->mc->phone_state = state;
+
+ if (state == STATE_CRASH_RESET || state == STATE_CRASH_EXIT ||
+ state == STATE_NV_REBUILDING)
+ wake_up(&iod->wq);
+}
+
+/**
+ * io_dev_sim_state_changed
+ * @iod: IPC's io_device
+ * @sim_online: SIM is online?
+ */
+static void io_dev_sim_state_changed(struct io_device *iod, bool sim_online)
+{
+ if (atomic_read(&iod->opened) == 0) {
+ mif_info("%s: ERR! not opened\n", iod->name);
+ } else if (iod->mc->sim_state.online == sim_online) {
+ mif_info("%s: SIM state not changed\n", iod->name);
+ } else {
+ iod->mc->sim_state.online = sim_online;
+ iod->mc->sim_state.changed = true;
+ mif_info("%s: SIM state changed {online %d, changed %d}\n",
+ iod->name, iod->mc->sim_state.online,
+ iod->mc->sim_state.changed);
+ wake_up(&iod->wq);
+ }
+}
+
+static void iodev_dump_status(struct io_device *iod, void *args)
+{
+ if (iod->format == IPC_RAW && iod->io_typ == IODEV_NET) {
+ struct link_device *ld = get_current_link(iod);
+ mif_com_log(iod->mc->msd, "%s: %s\n", iod->name, ld->name);
+ }
+}
+
+static int misc_open(struct inode *inode, struct file *filp)
+{
+ struct io_device *iod = to_io_device(filp->private_data);
+ struct modem_shared *msd = iod->msd;
+ struct link_device *ld;
+ int ret;
+ filp->private_data = (void *)iod;
+
+ atomic_inc(&iod->opened);
+
+ list_for_each_entry(ld, &msd->link_dev_list, list) {
+ if (IS_CONNECTED(iod, ld) && ld->init_comm) {
+ ret = ld->init_comm(ld, iod);
+ if (ret < 0) {
+ mif_info("%s: init_comm fail(%d)\n",
+ ld->name, ret);
+ return ret;
+ }
+ }
+ }
+
+ mif_err("%s (opened %d)\n", iod->name, atomic_read(&iod->opened));
+
+ return 0;
+}
+
+static int misc_release(struct inode *inode, struct file *filp)
+{
+ struct io_device *iod = (struct io_device *)filp->private_data;
+ struct modem_shared *msd = iod->msd;
+ struct link_device *ld;
+
+ atomic_dec(&iod->opened);
+ skb_queue_purge(&iod->sk_rx_q);
+
+ list_for_each_entry(ld, &msd->link_dev_list, list) {
+ if (IS_CONNECTED(iod, ld) && ld->terminate_comm)
+ ld->terminate_comm(ld, iod);
+ }
+
+ mif_err("%s (opened %d)\n", iod->name, atomic_read(&iod->opened));
+
+ 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_RESET) ||
+ (iod->mc->phone_state == STATE_CRASH_EXIT) ||
+ (iod->mc->phone_state == STATE_NV_REBUILDING) ||
+ (iod->mc->sim_state.changed)) {
+ if (iod->format == IPC_RAW) {
+ msleep(20);
+ return 0;
+ }
+ 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;
+ struct link_device *ld = get_current_link(iod);
+ char cpinfo_buf[530] = "CP Crash ";
+ unsigned long size;
+ int ret;
+
+ switch (cmd) {
+ case IOCTL_MODEM_ON:
+ mif_info("%s: IOCTL_MODEM_ON\n", iod->name);
+ return iod->mc->ops.modem_on(iod->mc);
+
+ case IOCTL_MODEM_OFF:
+ mif_info("%s: IOCTL_MODEM_OFF\n", iod->name);
+ return iod->mc->ops.modem_off(iod->mc);
+
+ case IOCTL_MODEM_RESET:
+ mif_info("%s: IOCTL_MODEM_RESET\n", iod->name);
+ return iod->mc->ops.modem_reset(iod->mc);
+
+ case IOCTL_MODEM_BOOT_ON:
+ mif_info("%s: IOCTL_MODEM_BOOT_ON\n", iod->name);
+ return iod->mc->ops.modem_boot_on(iod->mc);
+
+ case IOCTL_MODEM_BOOT_OFF:
+ mif_info("%s: IOCTL_MODEM_BOOT_OFF\n", iod->name);
+ return iod->mc->ops.modem_boot_off(iod->mc);
+
+ case IOCTL_MODEM_BOOT_DONE:
+ mif_err("%s: IOCTL_MODEM_BOOT_DONE\n", iod->name);
+ if (iod->mc->ops.modem_boot_done)
+ return iod->mc->ops.modem_boot_done(iod->mc);
+ else
+ return 0;
+
+ case IOCTL_MODEM_STATUS:
+ mif_debug("%s: IOCTL_MODEM_STATUS\n", iod->name);
+
+ p_state = iod->mc->phone_state;
+ if ((p_state == STATE_CRASH_RESET) ||
+ (p_state == STATE_CRASH_EXIT)) {
+ mif_info("%s: IOCTL_MODEM_STATUS (state %d)\n",
+ iod->name, p_state);
+ } else if (iod->mc->sim_state.changed) {
+ int s_state = iod->mc->sim_state.online ?
+ STATE_SIM_ATTACH : STATE_SIM_DETACH;
+ iod->mc->sim_state.changed = false;
+ return s_state;
+ } else if (p_state == STATE_NV_REBUILDING) {
+ mif_info("%s: IOCTL_MODEM_STATUS (state %d)\n",
+ iod->name, p_state);
+ iod->mc->phone_state = STATE_ONLINE;
+ }
+ return p_state;
+
+ case IOCTL_MODEM_PROTOCOL_SUSPEND:
+ mif_debug("%s: IOCTL_MODEM_PROTOCOL_SUSPEND\n",
+ iod->name);
+
+ if (iod->format != IPC_MULTI_RAW)
+ return -EINVAL;
+
+ iodevs_for_each(iod->msd, iodev_netif_stop, 0);
+ return 0;
+
+ case IOCTL_MODEM_PROTOCOL_RESUME:
+ mif_info("%s: IOCTL_MODEM_PROTOCOL_RESUME\n",
+ iod->name);
+
+ if (iod->format != IPC_MULTI_RAW)
+ return -EINVAL;
+
+ iodevs_for_each(iod->msd, iodev_netif_wake, 0);
+ return 0;
+
+ case IOCTL_MODEM_DUMP_START:
+ mif_info("%s: IOCTL_MODEM_DUMP_START\n", iod->name);
+ return ld->dump_start(ld, iod);
+
+ case IOCTL_MODEM_DUMP_UPDATE:
+ mif_debug("%s: IOCTL_MODEM_DUMP_UPDATE\n", iod->name);
+ return ld->dump_update(ld, iod, arg);
+
+ case IOCTL_MODEM_FORCE_CRASH_EXIT:
+ mif_info("%s: IOCTL_MODEM_FORCE_CRASH_EXIT\n", iod->name);
+ if (iod->mc->ops.modem_force_crash_exit)
+ return iod->mc->ops.modem_force_crash_exit(iod->mc);
+ return -EINVAL;
+
+ case IOCTL_MODEM_CP_UPLOAD:
+ mif_info("%s: IOCTL_MODEM_CP_UPLOAD\n", iod->name);
+ if (copy_from_user(cpinfo_buf + strlen(cpinfo_buf),
+ (void __user *)arg, MAX_CPINFO_SIZE) != 0)
+ return -EFAULT;
+ panic(cpinfo_buf);
+ return 0;
+
+ case IOCTL_MODEM_DUMP_RESET:
+ mif_info("%s: IOCTL_MODEM_DUMP_RESET\n", iod->name);
+ return iod->mc->ops.modem_dump_reset(iod->mc);
+
+ case IOCTL_MIF_LOG_DUMP:
+ iodevs_for_each(iod->msd, iodev_dump_status, 0);
+ size = MAX_MIF_BUFF_SIZE;
+ ret = copy_to_user((void __user *)arg, &size,
+ sizeof(unsigned long));
+ if (ret < 0)
+ return -EFAULT;
+
+ mif_dump_log(iod->mc->msd, iod);
+ return 0;
+
+ case IOCTL_MIF_DPRAM_DUMP:
+#ifdef CONFIG_LINK_DEVICE_DPRAM
+ if (iod->mc->mdm_data->link_types & LINKTYPE(LINKDEV_DPRAM)) {
+ size = iod->mc->mdm_data->dpram_ctl->dp_size;
+ ret = copy_to_user((void __user *)arg, &size,
+ sizeof(unsigned long));
+ if (ret < 0)
+ return -EFAULT;
+ mif_dump_dpram(iod);
+ return 0;
+ }
+#endif
+ return -EINVAL;
+
+ default:
+ /* If you need to handle the ioctl for specific link device,
+ * then assign the link ioctl handler to ld->ioctl
+ * It will be call for specific link ioctl */
+ if (ld->ioctl)
+ return ld->ioctl(ld, iod, cmd, arg);
+
+ mif_info("%s: ERR! cmd 0x%X not defined.\n", iod->name, cmd);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static ssize_t misc_write(struct file *filp, const char __user *data,
+ size_t count, loff_t *fpos)
+{
+ struct io_device *iod = (struct io_device *)filp->private_data;
+ struct link_device *ld = get_current_link(iod);
+ struct sk_buff *skb;
+ int ret;
+ unsigned headroom = 0;
+ unsigned tailroom = 0;
+ size_t tx_size;
+ struct sipc5_frame_data frm;
+
+ if (iod->format <= IPC_RFS && iod->id == 0)
+ return -EINVAL;
+
+ headroom = tx_build_link_header(&frm, iod, ld, count);
+
+ if (ld->aligned)
+ tailroom = sipc5_calc_padding_size(headroom + count);
+
+ tx_size = headroom + count + tailroom;
+
+ skb = alloc_skb(tx_size, GFP_KERNEL);
+ if (!skb) {
+ mif_info("%s: ERR! alloc_skb fail (tx_size:%d)\n",
+ iod->name, tx_size);
+ return -ENOMEM;
+ }
+
+ /* store IPC link header*/
+ memcpy(skb_put(skb, headroom), frm.hdr, headroom);
+
+ /* store IPC message */
+ if (copy_from_user(skb_put(skb, count), data, count) != 0) {
+ if (skb)
+ dev_kfree_skb_any(skb);
+ return -EFAULT;
+ }
+
+ if (iod->format == IPC_FMT) {
+ struct timespec epoch;
+ u8 *msg = (skb->data + headroom);
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ char str[MIF_MAX_STR_LEN];
+ snprintf(str, MIF_MAX_STR_LEN, "%s: RL2MIF", iod->mc->name);
+ pr_ipc(str, msg, (count > 16 ? 16 : count));
+#endif
+ getnstimeofday(&epoch);
+ mif_time_log(iod->mc->msd, epoch, NULL, 0);
+ mif_ipc_log(MIF_IPC_RL2AP, iod->mc->msd, msg, count);
+ }
+
+ /* store padding */
+ if (tailroom)
+ skb_put(skb, tailroom);
+
+ /* send data with sk_buff, link device will put sk_buff
+ * into the specific sk_buff_q and run work-q to send data
+ */
+ skbpriv(skb)->iod = iod;
+ skbpriv(skb)->ld = ld;
+
+ ret = ld->send(ld, iod, skb);
+ if (ret < 0) {
+ mif_info("%s: ERR! ld->send fail (err %d)\n", iod->name, ret);
+ return ret;
+ }
+
+ if (ret != tx_size)
+ mif_info("%s: wrong tx size (count:%d tx_size:%d ret:%d)\n",
+ iod->name, count, tx_size, ret);
+
+ return count;
+}
+
+static ssize_t misc_read(struct file *filp, char *buf, size_t count,
+ loff_t *fpos)
+{
+ struct io_device *iod = (struct io_device *)filp->private_data;
+ struct sk_buff_head *rxq = &iod->sk_rx_q;
+ struct sk_buff *skb;
+ int copied = 0;
+
+ skb = skb_dequeue(rxq);
+ if (!skb) {
+ mif_info("%s: ERR! no data in rxq\n", iod->name);
+ return 0;
+ }
+
+ if (iod->format == IPC_FMT) {
+ struct timespec epoch;
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ char str[MIF_MAX_STR_LEN];
+ snprintf(str, MIF_MAX_STR_LEN, "%s: MIF2RL", iod->mc->name);
+ pr_ipc(str, skb->data, (skb->len > 16 ? 16 : skb->len));
+#endif
+ getnstimeofday(&epoch);
+ mif_time_log(iod->mc->msd, epoch, NULL, 0);
+ mif_ipc_log(MIF_IPC_AP2RL, iod->mc->msd, skb->data, skb->len);
+ }
+
+ copied = skb->len > count ? count : skb->len;
+
+ if (copy_to_user(buf, skb->data, copied)) {
+ mif_info("%s: ERR! copy_to_user fail\n", iod->name);
+ dev_kfree_skb_any(skb);
+ return -EFAULT;
+ }
+
+ mif_debug("%s: data:%d copied:%d qlen:%d\n",
+ iod->name, skb->len, copied, rxq->qlen);
+
+ if (skb->len > count) {
+ skb_pull(skb, count);
+ skb_queue_head(rxq, skb);
+ } else {
+ dev_kfree_skb_any(skb);
+ }
+
+ return copied;
+}
+
+#ifdef CONFIG_LINK_DEVICE_C2C
+static int misc_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ int r = 0;
+ unsigned long size = 0;
+ unsigned long pfn = 0;
+ unsigned long offset = 0;
+ struct io_device *iod = (struct io_device *)filp->private_data;
+
+ if (!vma)
+ return -EFAULT;
+
+ size = vma->vm_end - vma->vm_start;
+ offset = vma->vm_pgoff << PAGE_SHIFT;
+ if (offset + size > (C2C_CP_RGN_SIZE + C2C_SH_RGN_SIZE)) {
+ mif_info("ERR: offset + size > C2C_CP_RGN_SIZE\n");
+ return -EINVAL;
+ }
+
+ /* Set the noncacheable property to the region */
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ vma->vm_flags |= VM_RESERVED | VM_IO;
+
+ pfn = __phys_to_pfn(C2C_CP_RGN_ADDR + offset);
+ r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
+ if (r) {
+ mif_info("ERR: Failed in remap_pfn_range()!!!\n");
+ return -EAGAIN;
+ }
+
+ mif_info("%s: VA = 0x%08lx, offset = 0x%lx, size = %lu\n",
+ iod->name, vma->vm_start, offset, size);
+
+ return 0;
+}
+#endif
+
+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,
+#ifdef CONFIG_LINK_DEVICE_C2C
+ .mmap = misc_mmap,
+#endif
+};
+
+static int vnet_open(struct net_device *ndev)
+{
+ struct vnet *vnet = netdev_priv(ndev);
+
+ mif_err("%s\n", vnet->iod->name);
+
+ netif_start_queue(ndev);
+ atomic_inc(&vnet->iod->opened);
+ return 0;
+}
+
+static int vnet_stop(struct net_device *ndev)
+{
+ struct vnet *vnet = netdev_priv(ndev);
+
+ mif_err("%s\n", vnet->iod->name);
+
+ atomic_dec(&vnet->iod->opened);
+ netif_stop_queue(ndev);
+ skb_queue_purge(&vnet->iod->sk_rx_q);
+ return 0;
+}
+
+static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct vnet *vnet = netdev_priv(ndev);
+ struct io_device *iod = vnet->iod;
+ struct link_device *ld = get_current_link(iod);
+ struct sk_buff *skb_new;
+ int ret;
+ unsigned headroom = 0;
+ unsigned tailroom = 0;
+ unsigned long tx_bytes = skb->len;
+ struct iphdr *ip_header = NULL;
+ struct sipc5_frame_data frm;
+
+ /* When use `handover' with Network Bridge,
+ * user -> bridge device(rmnet0) -> real rmnet(xxxx_rmnet0) -> here.
+ * bridge device is ethernet device unlike xxxx_rmnet(net device).
+ * We remove the an ethernet header of skb before using skb->len,
+ * because bridge device added an ethernet header to skb.
+ */
+ if (iod->use_handover) {
+ if (iod->id >= PS_DATA_CH_0 && iod->id <= PS_DATA_CH_LAST)
+ skb_pull(skb, sizeof(struct ethhdr));
+ }
+
+ headroom = tx_build_link_header(&frm, iod, ld, skb->len);
+
+ /* ip loop-back */
+ ip_header = (struct iphdr *)skb->data;
+ if (iod->msd->loopback_ipaddr &&
+ ip_header->daddr == iod->msd->loopback_ipaddr) {
+ swap(ip_header->saddr, ip_header->daddr);
+ frm.ch_id = DATA_LOOPBACK_CHANNEL;
+ frm.hdr[SIPC5_CH_ID_OFFSET] = DATA_LOOPBACK_CHANNEL;
+ }
+
+ if (ld->aligned)
+ tailroom = sipc5_calc_padding_size(frm.len);
+
+ if (skb_headroom(skb) < headroom || skb_tailroom(skb) < tailroom) {
+ mif_debug("%s: skb_copy_expand needed\n", iod->name);
+ skb_new = skb_copy_expand(skb, headroom, tailroom, GFP_ATOMIC);
+ /* skb_copy_expand success or not, free old skb from caller */
+ dev_kfree_skb_any(skb);
+ if (!skb_new) {
+ mif_info("%s: ERR! skb_copy_expand fail\n", iod->name);
+ return NETDEV_TX_BUSY;
+ }
+ } else {
+ skb_new = skb;
+ }
+
+ memcpy(skb_push(skb_new, headroom), frm.hdr, headroom);
+ if (tailroom)
+ skb_put(skb_new, tailroom);
+
+ skbpriv(skb_new)->iod = iod;
+ skbpriv(skb_new)->ld = ld;
+
+ ret = ld->send(ld, iod, skb_new);
+ if (ret < 0) {
+ netif_stop_queue(ndev);
+ mif_info("%s: ERR! ld->send fail (err %d)\n", iod->name, ret);
+ return NETDEV_TX_BUSY;
+ }
+
+ ndev->stats.tx_packets++;
+ ndev->stats.tx_bytes += tx_bytes;
+
+ 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 sipc5_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;
+
+ iod->sim_state_changed = io_dev_sim_state_changed;
+
+ /* Get data from link device */
+ mif_debug("%s: SIPC version = %d\n", iod->name, iod->ipc_version);
+ iod->recv = io_dev_recv_data_from_link_dev;
+ iod->recv_skb = io_dev_recv_skb_from_link_dev;
+
+ /* Register misc or net device */
+ switch (iod->io_typ) {
+ case IODEV_MISC:
+ init_waitqueue_head(&iod->wq);
+ skb_queue_head_init(&iod->sk_rx_q);
+
+ iod->miscdev.minor = MISC_DYNAMIC_MINOR;
+ iod->miscdev.name = iod->name;
+ iod->miscdev.fops = &misc_io_fops;
+
+ ret = misc_register(&iod->miscdev);
+ if (ret)
+ mif_info("%s: ERR! misc_register failed\n", iod->name);
+
+ break;
+
+ case IODEV_NET:
+ skb_queue_head_init(&iod->sk_rx_q);
+ if (iod->use_handover)
+ iod->ndev = alloc_netdev(0, iod->name,
+ vnet_setup_ether);
+ else
+ iod->ndev = alloc_netdev(0, iod->name, vnet_setup);
+
+ if (!iod->ndev) {
+ mif_info("%s: ERR! alloc_netdev fail\n", iod->name);
+ return -ENOMEM;
+ }
+
+ ret = register_netdev(iod->ndev);
+ if (ret) {
+ mif_info("%s: ERR! register_netdev fail\n", iod->name);
+ free_netdev(iod->ndev);
+ }
+
+ mif_debug("iod 0x%p\n", iod);
+ vnet = netdev_priv(iod->ndev);
+ mif_debug("vnet 0x%p\n", vnet);
+ vnet->iod = iod;
+
+ break;
+
+ case IODEV_DUMMY:
+ skb_queue_head_init(&iod->sk_rx_q);
+
+ iod->miscdev.minor = MISC_DYNAMIC_MINOR;
+ iod->miscdev.name = iod->name;
+ iod->miscdev.fops = &misc_io_fops;
+
+ ret = misc_register(&iod->miscdev);
+ if (ret)
+ mif_info("%s: ERR! misc_register fail\n", iod->name);
+ ret = device_create_file(iod->miscdev.this_device,
+ &attr_waketime);
+ if (ret)
+ mif_info("%s: ERR! device_create_file fail\n",
+ iod->name);
+ ret = device_create_file(iod->miscdev.this_device,
+ &attr_loopback);
+ if (ret)
+ mif_err("failed to create `loopback file' : %s\n",
+ iod->name);
+ ret = device_create_file(iod->miscdev.this_device,
+ &attr_txlink);
+ if (ret)
+ mif_err("failed to create `txlink file' : %s\n",
+ iod->name);
+ break;
+
+ default:
+ mif_info("%s: ERR! wrong io_type %d\n", iod->name, iod->io_typ);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
diff --git a/drivers/misc/modem_if/sipc5_modem.c b/drivers/misc/modem_if/sipc5_modem.c
new file mode 100644
index 00000000000..98987516d02
--- /dev/null
+++ b/drivers/misc/modem_if/sipc5_modem.c
@@ -0,0 +1,384 @@
+/* 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/if_arp.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.h>
+#include "modem_prj.h"
+#include "modem_variation.h"
+#include "modem_utils.h"
+
+#define FMT_WAKE_TIME (HZ/2)
+#define RAW_WAKE_TIME (HZ*6)
+
+static struct modem_shared *create_modem_shared_data(void)
+{
+ struct modem_shared *msd;
+ int size = MAX_MIF_BUFF_SIZE;
+
+ msd = kzalloc(sizeof(struct modem_shared), GFP_KERNEL);
+ if (!msd)
+ return NULL;
+
+ /* initialize link device list */
+ INIT_LIST_HEAD(&msd->link_dev_list);
+
+ /* initialize tree of io devices */
+ msd->iodevs_tree_chan = RB_ROOT;
+ msd->iodevs_tree_fmt = RB_ROOT;
+
+ msd->storage.cnt = 0;
+ msd->storage.addr = kzalloc(MAX_MIF_BUFF_SIZE +
+ (MAX_MIF_SEPA_SIZE * 2), GFP_KERNEL);
+ if (!msd->storage.addr) {
+ mif_err("IPC logger buff alloc failed!!\n");
+ return NULL;
+ }
+ memset(msd->storage.addr, 0, size + (MAX_MIF_SEPA_SIZE * 2));
+ memcpy(msd->storage.addr, MIF_SEPARATOR, MAX_MIF_SEPA_SIZE);
+ msd->storage.addr += MAX_MIF_SEPA_SIZE;
+ memcpy(msd->storage.addr, &size, MAX_MIF_SEPA_SIZE);
+ msd->storage.addr += MAX_MIF_SEPA_SIZE;
+ spin_lock_init(&msd->lock);
+
+ return msd;
+}
+
+static struct modem_ctl *create_modemctl_device(struct platform_device *pdev,
+ struct modem_shared *msd)
+{
+ 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->msd = msd;
+ modemctl->dev = dev;
+ modemctl->phone_state = STATE_OFFLINE;
+
+ pdata = pdev->dev.platform_data;
+ modemctl->mdm_data = pdata;
+ modemctl->name = pdata->name;
+
+ /* init modemctl device for getting modemctl operations */
+ ret = call_modem_init_func(modemctl, pdata);
+ if (ret) {
+ kfree(modemctl);
+ return NULL;
+ }
+
+ mif_info("%s is created!!!\n", pdata->name);
+
+ return modemctl;
+}
+
+static struct io_device *create_io_device(struct modem_io_t *io_t,
+ struct modem_shared *msd, struct modem_ctl *modemctl,
+ struct modem_data *pdata)
+{
+ int ret = 0;
+ struct io_device *iod = NULL;
+
+ iod = kzalloc(sizeof(struct io_device), GFP_KERNEL);
+ if (!iod) {
+ mif_err("iod == NULL\n");
+ return NULL;
+ }
+
+ rb_init_node(&iod->node_chan);
+ rb_init_node(&iod->node_fmt);
+
+ iod->name = io_t->name;
+ iod->id = io_t->id;
+ iod->format = io_t->format;
+ iod->io_typ = io_t->io_type;
+ iod->link_types = io_t->links;
+ iod->net_typ = pdata->modem_net;
+ iod->use_handover = pdata->use_handover;
+ iod->ipc_version = pdata->ipc_version;
+ atomic_set(&iod->opened, 0);
+
+ /* link between io device and modem control */
+ iod->mc = modemctl;
+ if (iod->format == IPC_FMT)
+ modemctl->iod = iod;
+ if (iod->format == IPC_BOOT) {
+ modemctl->bootd = iod;
+ mif_info("Bood device = %s\n", iod->name);
+ }
+
+ /* link between io device and modem shared */
+ iod->msd = msd;
+
+ /* add iod to rb_tree */
+ if (iod->format != IPC_RAW)
+ insert_iod_with_format(msd, iod->format, iod);
+
+ if (sipc5_is_not_reserved_channel(iod->id))
+ insert_iod_with_channel(msd, iod->id, iod);
+
+ /* register misc device or net device */
+ ret = sipc5_init_io_device(iod);
+ if (ret) {
+ kfree(iod);
+ mif_err("sipc5_init_io_device fail (%d)\n", ret);
+ return NULL;
+ }
+
+ mif_debug("%s is created!!!\n", iod->name);
+ return iod;
+}
+
+static int attach_devices(struct io_device *iod, enum modem_link tx_link)
+{
+ struct modem_shared *msd = iod->msd;
+ struct link_device *ld;
+ unsigned ch;
+
+ /* find link type for this io device */
+ list_for_each_entry(ld, &msd->link_dev_list, list) {
+ if (IS_CONNECTED(iod, ld)) {
+ /* The count 1 bits of iod->link_types is count
+ * of link devices of this iod.
+ * If use one link device,
+ * or, 2+ link devices and this link is tx_link,
+ * set iod's link device with ld
+ */
+ if ((countbits(iod->link_types) <= 1) ||
+ (tx_link == ld->link_type)) {
+ mif_debug("set %s->%s\n", iod->name, ld->name);
+ set_current_link(iod, ld);
+ }
+ }
+ }
+
+ /* if use rx dynamic switch, set tx_link at modem_io_t of
+ * board-*-modems.c
+ */
+ if (!get_current_link(iod)) {
+ mif_err("%s->link == NULL\n", iod->name);
+ BUG();
+ }
+
+ switch (iod->format) {
+ case IPC_FMT:
+ wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name);
+ iod->waketime = FMT_WAKE_TIME;
+ break;
+
+ case IPC_RAW:
+ wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name);
+ iod->waketime = RAW_WAKE_TIME;
+ break;
+
+ case IPC_RFS:
+ wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name);
+ iod->waketime = RAW_WAKE_TIME;
+ break;
+
+ case IPC_MULTI_RAW:
+ wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name);
+ iod->waketime = RAW_WAKE_TIME;
+ break;
+
+ case IPC_BOOT:
+ wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name);
+ iod->waketime = RAW_WAKE_TIME;
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int __devinit modem_probe(struct platform_device *pdev)
+{
+ int i;
+ struct modem_data *pdata = pdev->dev.platform_data;
+ struct modem_shared *msd = NULL;
+ struct modem_ctl *modemctl = NULL;
+ struct io_device *iod[pdata->num_iodevs];
+ struct link_device *ld;
+
+ mif_err("%s\n", pdev->name);
+ memset(iod, 0, sizeof(iod));
+
+ msd = create_modem_shared_data();
+ if (!msd) {
+ mif_err("msd == NULL\n");
+ goto err_free_modemctl;
+ }
+
+ modemctl = create_modemctl_device(pdev, msd);
+ if (!modemctl) {
+ mif_err("modemctl == NULL\n");
+ goto err_free_modemctl;
+ }
+
+ /* create link device */
+ /* support multi-link device */
+ for (i = 0; i < LINKDEV_MAX ; i++) {
+ /* find matching link type */
+ if (pdata->link_types & LINKTYPE(i)) {
+ ld = call_link_init_func(pdev, i);
+ if (!ld)
+ goto err_free_modemctl;
+
+ mif_err("link created: %s\n", ld->name);
+ ld->link_type = i;
+ ld->mc = modemctl;
+ ld->msd = msd;
+ list_add(&ld->list, &msd->link_dev_list);
+ }
+ }
+
+ /* create io deivces and connect to modemctl device */
+ for (i = 0; i < pdata->num_iodevs; i++) {
+ iod[i] = create_io_device(&pdata->iodevs[i], msd, modemctl,
+ pdata);
+ if (!iod[i]) {
+ mif_err("iod[%d] == NULL\n", i);
+ goto err_free_modemctl;
+ }
+
+ attach_devices(iod[i], pdata->iodevs[i].tx_link);
+ }
+
+ platform_set_drvdata(pdev, modemctl);
+
+ mif_err("Complete!!!\n");
+
+ return 0;
+
+err_free_modemctl:
+ for (i = 0; i < pdata->num_iodevs; i++)
+ if (iod[i] != NULL)
+ kfree(iod[i]);
+
+ if (modemctl != NULL)
+ kfree(modemctl);
+
+ if (msd != NULL)
+ kfree(msd);
+
+ return -ENOMEM;
+}
+
+static void modem_shutdown(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct modem_ctl *mc = dev_get_drvdata(dev);
+ struct utc_time utc;
+
+ mc->ops.modem_off(mc);
+ mc->phone_state = STATE_OFFLINE;
+
+ get_utc_time(&utc);
+ mif_info("%s: at [%02d:%02d:%02d.%03d]\n",
+ mc->name, utc.hour, utc.min, utc.sec, utc.msec);
+}
+
+static int modem_suspend(struct device *pdev)
+{
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ struct utc_time utc;
+#endif
+
+#ifndef CONFIG_LINK_DEVICE_HSIC
+ struct modem_ctl *mc = dev_get_drvdata(pdev);
+
+ if (mc->gpio_pda_active) {
+ gpio_set_value(mc->gpio_pda_active, 0);
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ get_utc_time(&utc);
+ mif_info("%s: at [%02d:%02d:%02d.%03d]\n",
+ mc->name, utc.hour, utc.min, utc.sec, utc.msec);
+#endif
+ }
+#endif
+
+ return 0;
+}
+
+static int modem_resume(struct device *pdev)
+{
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ struct utc_time utc;
+#endif
+
+#ifndef CONFIG_LINK_DEVICE_HSIC
+ struct modem_ctl *mc = dev_get_drvdata(pdev);
+
+ if (mc->gpio_pda_active) {
+ gpio_set_value(mc->gpio_pda_active, 1);
+#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP
+ get_utc_time(&utc);
+ mif_info("%s: at [%02d:%02d:%02d.%03d]\n",
+ mc->name, utc.hour, utc.min, utc.sec, utc.msec);
+#endif
+ }
+#endif
+
+ 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 = "mif_sipc5",
+ .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");