aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc/modem_if_v2
diff options
context:
space:
mode:
authorZiyan <jaraidaniel@gmail.com>2016-03-26 01:40:29 +0100
committerAndreas Blaesius <skate4life@gmx.de>2016-04-30 19:33:45 +0200
commite4bbc6d694824a359691f3cfbe428a93fc8d99fa (patch)
tree7d245475c40636833a381a78d826dd6ffd5ad3cf /drivers/misc/modem_if_v2
parent323dec6a9b47e758bb854dd307df283c5b1df148 (diff)
downloadkernel_samsung_espresso10-e4bbc6d694824a359691f3cfbe428a93fc8d99fa.tar.gz
kernel_samsung_espresso10-e4bbc6d694824a359691f3cfbe428a93fc8d99fa.tar.bz2
kernel_samsung_espresso10-e4bbc6d694824a359691f3cfbe428a93fc8d99fa.zip
drivers: misc: Samsung Modem Interface Driver V2
Based on the sources from: GT-P3110_JB_Opensource, cleaned up. Change-Id: Ic630e1d9a2d71110cee1be8166a90a347e338d5d
Diffstat (limited to 'drivers/misc/modem_if_v2')
-rw-r--r--drivers/misc/modem_if_v2/Kconfig16
-rw-r--r--drivers/misc/modem_if_v2/Makefile10
-rw-r--r--drivers/misc/modem_if_v2/modem_link_device_mipi.c1789
-rw-r--r--drivers/misc/modem_if_v2/modem_link_device_mipi.h182
-rw-r--r--drivers/misc/modem_if_v2/modem_modemctl_device_xmm6262.c255
-rw-r--r--drivers/misc/modem_if_v2/modem_net_flowcontrol_device.c115
-rw-r--r--drivers/misc/modem_if_v2/modem_prj.h353
-rw-r--r--drivers/misc/modem_if_v2/modem_utils.c301
-rw-r--r--drivers/misc/modem_if_v2/modem_utils.h188
-rw-r--r--drivers/misc/modem_if_v2/modem_variation.h81
-rw-r--r--drivers/misc/modem_if_v2/sipc4_io_device.c1193
-rw-r--r--drivers/misc/modem_if_v2/sipc4_modem.c321
12 files changed, 4804 insertions, 0 deletions
diff --git a/drivers/misc/modem_if_v2/Kconfig b/drivers/misc/modem_if_v2/Kconfig
new file mode 100644
index 00000000000..0a10756657f
--- /dev/null
+++ b/drivers/misc/modem_if_v2/Kconfig
@@ -0,0 +1,16 @@
+menuconfig SEC_MODEM_V2
+ bool "Samsung Mobile Modem Interface v2"
+ default n
+ ---help---
+ Samsung Modem Interface Driver V2.
+
+config UMTS_MODEM_XMM6262
+ bool "modem chip : IMC XMM6262"
+ depends on SEC_MODEM_V2
+ default n
+
+config LINK_DEVICE_MIPI
+ bool "modem driver link device MIPI-HSI"
+ depends on SEC_MODEM_V2
+ depends on OMAP_HSI
+ default n
diff --git a/drivers/misc/modem_if_v2/Makefile b/drivers/misc/modem_if_v2/Makefile
new file mode 100644
index 00000000000..274c17a96de
--- /dev/null
+++ b/drivers/misc/modem_if_v2/Makefile
@@ -0,0 +1,10 @@
+# Makefile of modem_if
+
+EXTRA_CFLAGS += -Idrivers/misc/modem_if_v2
+
+obj-y += sipc4_modem.o sipc4_io_device.o
+obj-y += modem_net_flowcontrol_device.o modem_utils.o
+
+obj-$(CONFIG_UMTS_MODEM_XMM6262) += modem_modemctl_device_xmm6262.o
+
+obj-$(CONFIG_LINK_DEVICE_MIPI) += modem_link_device_mipi.o
diff --git a/drivers/misc/modem_if_v2/modem_link_device_mipi.c b/drivers/misc/modem_if_v2/modem_link_device_mipi.c
new file mode 100644
index 00000000000..3a619d79921
--- /dev/null
+++ b/drivers/misc/modem_if_v2/modem_link_device_mipi.c
@@ -0,0 +1,1789 @@
+/* /linux/drivers/modem_if_v2/modem_link_device_mipi.c
+ *
+ * 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.
+ *
+ */
+
+#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/hsi_driver_if.h>
+
+#include <linux/platform_data/modem_v2.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:
+ /* to prevent modem back powering by mipi
+ * do not intialize mipi-link here !!
+ */
+ mipi_ld->modem_power_on = false;
+ return 0;
+
+ case IPC_BOOT_2:
+ return hsi_init_handshake(mipi_ld,
+ HSI_INIT_MODE_FLASHLESS_BOOT_EBL);
+
+ 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:
+ case IPC_BOOT_2:
+ if (mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened)
+ if_hsi_close_channel(&mipi_ld->hsi_channles[
+ HSI_FLASHLESS_CHANNEL]);
+ if (wake_lock_active(&mipi_ld->wlock)) {
+ wake_unlock(&mipi_ld->wlock);
+ mipi_debug("wake_unlock\n");
+ }
+ 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]);
+ if (wake_lock_active(&mipi_ld->wlock)) {
+ wake_unlock(&mipi_ld->wlock);
+ mipi_debug("wake_unlock\n");
+ }
+ 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;
+ size_t tx_size;
+
+ switch (iod->format) {
+ case IPC_RAW:
+ case IPC_MULTI_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) {
+ mipi_err("write fail : %d\n", ret);
+ dev_kfree_skb_any(skb);
+ return ret;
+ } else
+ mipi_debug("write Done\n");
+ dev_kfree_skb_any(skb);
+ return ret;
+
+ case IPC_BOOT:
+ if (unlikely(!mipi_ld->modem_power_on)) {
+ mipi_ld->modem_power_on = true;
+ ret = hsi_init_handshake(mipi_ld,
+ HSI_INIT_MODE_FLASHLESS_BOOT);
+ if (ret < 0) {
+ mipi_err("init fail : %d\n", ret);
+ return ret;
+ }
+ }
+
+ case IPC_BOOT_2:
+ ret = if_hsi_write(&mipi_ld->hsi_channles[
+ HSI_FLASHLESS_CHANNEL],
+ (u32 *)skb->data, skb->len);
+ if (ret < 0) {
+ mipi_err("write fail : %d\n", ret);
+ dev_kfree_skb_any(skb);
+ return ret;
+ } else
+ mipi_debug("write Done\n");
+ dev_kfree_skb_any(skb);
+ return ret;
+
+ case IPC_FMT:
+ case IPC_RFS:
+ default:
+ txq = &ld->sk_fmt_tx_q;
+ break;
+ }
+
+ /* set wake_lock to prevent to sleep before tx_work thread run */
+ if (!wake_lock_active(&mipi_ld->wlock)) {
+ wake_lock(&mipi_ld->wlock);
+ mipi_debug("wake_lock\n");
+ }
+
+ /* store the tx size before run the tx_delayed_work*/
+ tx_size = skb->len;
+
+ /* save io device into cb area */
+ *((struct io_device **)skb->cb) = iod;
+ /* en queue skb data */
+ skb_queue_tail(txq, skb);
+
+ if ((iod->format == IPC_RAW) || (iod->format == IPC_MULTI_RAW))
+ queue_delayed_work(ld->tx_raw_wq, &ld->tx_delayed_work, 0);
+ else
+ queue_work(ld->tx_wq, &ld->tx_work);
+
+ return tx_size;
+}
+
+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;
+ int send_channel = 0;
+
+ while (ld->sk_fmt_tx_q.qlen) {
+ mipi_debug("fmt qlen : %d\n", ld->sk_fmt_tx_q.qlen);
+
+ if (ld->com_state != COM_ONLINE) {
+ mipi_debug("fmt CP not ready\n");
+ return;
+ }
+
+ fmt_skb = skb_dequeue(&ld->sk_fmt_tx_q);
+ if (fmt_skb) {
+ iod = *((struct io_device **)fmt_skb->cb);
+
+ mipi_debug("dequeue. fmt qlen : %d\n",
+ ld->sk_fmt_tx_q.qlen);
+
+ switch (iod->format) {
+ case IPC_FMT:
+ send_channel = HSI_FMT_CHANNEL;
+ break;
+
+ case IPC_RFS:
+ send_channel = HSI_RFS_CHANNEL;
+ break;
+
+ case IPC_BOOT:
+ case IPC_BOOT_2:
+ 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 */
+ mipi_err("write fail : %d\n", ret);
+ } else {
+ mipi_debug("write Done\n");
+ }
+
+ dev_kfree_skb_any(fmt_skb);
+ }
+ }
+}
+
+static void mipi_hsi_tx_raw_work(struct work_struct *work)
+{
+ int ret;
+ struct link_device *ld = container_of(work, struct link_device,
+ tx_delayed_work.work);
+ struct mipi_link_device *mipi_ld = to_mipi_link_device(ld);
+ struct sk_buff *raw_skb;
+ unsigned bulk_size;
+
+ while (ld->sk_raw_tx_q.qlen) {
+ mipi_debug("raw qlen:%d\n", ld->sk_raw_tx_q.qlen);
+
+ if (ld->com_state != COM_ONLINE) {
+ mipi_debug("raw CP not ready\n");
+ return;
+ }
+
+ bulk_size = 0;
+ raw_skb = skb_dequeue(&ld->sk_raw_tx_q);
+ while (raw_skb) {
+ if (bulk_size + raw_skb->len < MIPI_BULK_TX_SIZE) {
+ memcpy(mipi_ld->bulk_tx_buf + bulk_size,
+ raw_skb->data, raw_skb->len);
+ bulk_size += raw_skb->len;
+ skb_queue_head(&mipi_ld->bulk_txq, raw_skb);
+ } else {
+ skb_queue_head(&ld->sk_raw_tx_q, raw_skb);
+ break;
+ }
+ raw_skb = skb_dequeue(&ld->sk_raw_tx_q);
+ }
+
+ ret = if_hsi_protocol_send(mipi_ld, HSI_RAW_CHANNEL,
+ (u32 *)mipi_ld->bulk_tx_buf, bulk_size);
+ if (ret < 0) {
+ raw_skb = skb_dequeue(&mipi_ld->bulk_txq);
+ while (raw_skb) {
+ skb_queue_head(&ld->sk_raw_tx_q, raw_skb);
+ raw_skb = skb_dequeue(&mipi_ld->bulk_txq);
+ }
+ } else
+ skb_queue_purge(&mipi_ld->bulk_txq);
+ }
+}
+
+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) {
+ if (ret != -EPERM)
+ mipi_err("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);
+
+ mipi_debug("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;
+
+ mipi_debug("%s\n", __func__);
+
+ 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);
+ return;
+ }
+ }
+}
+
+static int if_hsi_open_channel(struct if_hsi_channel *channel)
+{
+ int ret;
+
+ if (channel->opened) {
+ mipi_debug("ch=%d is already opened\n", channel->channel_id);
+ return 0;
+ }
+
+ ret = hsi_open(channel->dev);
+ if (ret) {
+ mipi_err("hsi_open fail : %d\n", ret);
+ if (ret == -EBUSY)
+ mipi_err("ch %d already opened\n", channel->channel_id);
+ else
+ return ret;
+ }
+ channel->opened = 1;
+
+ mipi_debug("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) {
+ mipi_debug("ch=%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;
+
+ mipi_debug("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);
+
+ mipi_ld->ld.com_state = COM_HANDSHAKE;
+ ret = if_hsi_protocol_send(mipi_ld, HSI_CMD_CHANNEL, &start_cmd, 1);
+ if (ret < 0) {
+ /* TODO: Re Enqueue */
+ mipi_err("First write fail : %d\n", ret);
+ } else {
+ mipi_info("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;
+ }
+ mipi_ld->hsi_channles[i].send_step = STEP_IDLE;
+ mipi_ld->hsi_channles[i].recv_step = STEP_IDLE;
+
+ 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);
+ mipi_debug("Set TX/RX MIPI-HSI\n");
+ }
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev,
+ HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, NULL);
+ mipi_debug("Set 4 WIRE MODE\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)
+ mipi_err("hsi_read fail : %d\n", ret);
+
+ if (mipi_ld->ld.com_state != COM_ONLINE)
+ schedule_delayed_work(&mipi_ld->start_work, 3 * HZ);
+
+ mipi_debug("hsi_init_handshake Done : MODE_NORMAL\n");
+ return 0;
+
+ case HSI_INIT_MODE_FLASHLESS_BOOT:
+ mipi_ld->ld.com_state = COM_BOOT;
+
+ if (timer_pending(&mipi_ld->hsi_acwake_down_timer))
+ del_timer(&mipi_ld->hsi_acwake_down_timer);
+
+ if (mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened) {
+ hsi_ioctl(mipi_ld->hsi_channles[
+ HSI_FLASHLESS_CHANNEL].dev, HSI_IOCTL_SW_RESET,
+ NULL);
+ for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++)
+ mipi_ld->hsi_channles[i].opened = 0;
+ }
+
+ if (!mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened)
+ if_hsi_open_channel(
+ &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL]);
+ mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].send_step
+ = STEP_IDLE;
+ mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].recv_step
+ = STEP_IDLE;
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ HSI_IOCTL_GET_TX, &tx_config);
+ tx_config.mode = 2;
+ tx_config.divisor = 3; /* Speed : 24MHz */
+ 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 = 3; /* Speed : 24MHz */
+ rx_config.channels = 1;
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ HSI_IOCTL_SET_RX, &rx_config);
+ mipi_debug("Set TX/RX MIPI-HSI\n");
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ HSI_IOCTL_SET_WAKE_RX_3WIRES_MODE, NULL);
+ mipi_debug("Set 3 WIRE MODE\n");
+
+ if (!wake_lock_active(&mipi_ld->wlock)) {
+ wake_lock(&mipi_ld->wlock);
+ mipi_debug("wake_lock\n");
+ }
+
+ ret = hsi_read(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].rx_data, 1);
+ if (ret)
+ mipi_err("hsi_read fail : %d\n", ret);
+
+ mipi_debug("hsi_init_handshake Done : FLASHLESS_BOOT\n");
+ return 0;
+
+ case HSI_INIT_MODE_FLASHLESS_BOOT_EBL:
+ mipi_ld->ld.com_state = COM_BOOT_EBL;
+
+ if (mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened) {
+ hsi_ioctl(mipi_ld->hsi_channles[
+ HSI_FLASHLESS_CHANNEL].dev, HSI_IOCTL_SW_RESET,
+ NULL);
+ for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++)
+ mipi_ld->hsi_channles[i].opened = 0;
+ }
+
+ if (!mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened)
+ if_hsi_open_channel(
+ &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL]);
+
+ 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);
+ mipi_debug("Set TX/RX MIPI-HSI\n");
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev,
+ HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, NULL);
+ mipi_debug("Set 4 WIRE MODE\n");
+
+ if (!wake_lock_active(&mipi_ld->wlock)) {
+ wake_lock(&mipi_ld->wlock);
+ mipi_debug("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, 1);
+ if (ret)
+ mipi_err("hsi_read fail : %d\n", ret);
+
+ mipi_debug("hsi_init_handshake Done : FLASHLESS_BOOT_EBL\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_ioctl(mipi_ld->hsi_channles[
+ HSI_CP_RAMDUMP_CHANNEL].dev, HSI_IOCTL_SW_RESET,
+ NULL);
+ for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++)
+ mipi_ld->hsi_channles[i].opened = 0;
+ }
+
+ if (!mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].opened)
+ if_hsi_open_channel(
+ &mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL]);
+ mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].send_step
+ = STEP_IDLE;
+ mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].recv_step
+ = STEP_IDLE;
+
+ 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);
+ mipi_debug("Set TX/RX MIPI-HSI\n");
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev,
+ HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, NULL);
+ mipi_debug("Set 4 WIRE MODE\n");
+
+ if (!wake_lock_active(&mipi_ld->wlock)) {
+ wake_lock(&mipi_ld->wlock);
+ mipi_debug("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)
+ mipi_err("hsi_read fail : %d\n", ret);
+
+ mipi_debug("hsi_init_handshake Done : RAMDUMP\n");
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void hsi_conn_err_recovery(struct mipi_link_device *mipi_ld)
+{
+ int i;
+ int ret;
+ struct hst_ctx tx_config;
+ struct hsr_ctx rx_config;
+ unsigned long int flags;
+ struct if_hsi_command *hsi_cmd;
+
+ /* Remove all tx-command in list */
+ 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);
+ } else {
+ spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags);
+ break;
+ }
+ } while (true);
+
+ if (timer_pending(&mipi_ld->hsi_acwake_down_timer))
+ del_timer(&mipi_ld->hsi_acwake_down_timer);
+
+ if (mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].opened) {
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev,
+ HSI_IOCTL_SW_RESET, NULL);
+ for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++)
+ mipi_ld->hsi_channles[i].opened = 0;
+ }
+
+ for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) {
+ if (!mipi_ld->hsi_channles[i].opened)
+ if_hsi_open_channel(&mipi_ld->hsi_channles[i]);
+
+ 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);
+ mipi_debug("Set TX/RX MIPI-HSI\n");
+ }
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev,
+ HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, NULL);
+ mipi_debug("Set 4 WIRE MODE\n");
+
+ ret = hsi_read(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev,
+ mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data, 1);
+ if (ret)
+ mipi_err("hsi_read fail : %d\n", ret);
+
+ for (i = 1; i < HSI_NUM_OF_USE_CHANNELS; i++) {
+ if ((mipi_ld->hsi_channles[i].recv_step ==
+ STEP_WAIT_FOR_CONN_READY) &&
+ (mipi_ld->hsi_channles[i].rx_count)) {
+ mipi_err("there was rx pending. ch:%d, len:%d", i,
+ mipi_ld->hsi_channles[i].rx_count);
+ ret = hsi_read(mipi_ld->hsi_channles[i].dev,
+ mipi_ld->hsi_channles[i].rx_data,
+ mipi_ld->hsi_channles[i].rx_count / 4);
+ if (ret)
+ mipi_err("hsi_read fail : %d\n", ret);
+ }
+ }
+
+ mipi_info("hsi_conn_err_recovery Done\n");
+}
+
+static void hsi_conn_reset(struct mipi_link_device *mipi_ld)
+{
+ int i;
+ struct hst_ctx tx_config;
+ struct hsr_ctx rx_config;
+ unsigned long int flags;
+ struct if_hsi_command *hsi_cmd;
+
+ /* Remove all tx-command in list */
+ 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);
+ } else {
+ spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags);
+ break;
+ }
+ } while (true);
+
+ if (timer_pending(&mipi_ld->hsi_acwake_down_timer))
+ del_timer(&mipi_ld->hsi_acwake_down_timer);
+
+ if (mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].opened) {
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev,
+ HSI_IOCTL_SW_RESET, NULL);
+ for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++)
+ mipi_ld->hsi_channles[i].opened = 0;
+ }
+
+ for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) {
+ if (!mipi_ld->hsi_channles[i].opened)
+ if_hsi_open_channel(&mipi_ld->hsi_channles[i]);
+
+ mipi_ld->hsi_channles[i].send_step = STEP_IDLE;
+ mipi_ld->hsi_channles[i].recv_step = STEP_IDLE;
+
+ 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);
+ mipi_debug("Set TX/RX MIPI-HSI\n");
+ }
+
+ hsi_ioctl(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev,
+ HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, NULL);
+
+ mipi_info("hsi_conn_reset Done\n");
+}
+
+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:
+ mipi_err("ERROR... CMD Not supported : %08x\n",
+ cmd_type);
+ return -EINVAL;
+ }
+}
+
+static void if_hsi_cmd_work(struct work_struct *work)
+{
+ int ret;
+ int retry_count = 0;
+ unsigned long int flags;
+ struct mipi_link_device *mipi_ld =
+ container_of(work, struct mipi_link_device, cmd_work.work);
+ struct if_hsi_channel *channel =
+ &mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL];
+ struct if_hsi_command *hsi_cmd;
+
+ mipi_debug("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;
+ }
+ mipi_debug("take command : %08x\n", hsi_cmd->command);
+
+ if (((hsi_cmd->command & 0xF0000000) >> 28) ==
+ HSI_LL_MSG_CONN_CLOSED)
+ mipi_ld->hsi_channles[(hsi_cmd->command & 0x0F000000)
+ >> 24].recv_step = STEP_SEND_TO_CONN_CLOSED;
+
+ ret = if_hsi_write(channel, &hsi_cmd->command, 4);
+ if (ret < 0) {
+ mipi_err("write command fail : %d\n", ret);
+
+ retry_count++;
+ if (retry_count > 5) {
+ channel->send_step = STEP_IDLE;
+ kfree(hsi_cmd);
+ return;
+ }
+
+ hsi_conn_err_recovery(mipi_ld);
+ channel->send_step = STEP_IDLE;
+
+ spin_lock_irqsave(&mipi_ld->list_cmd_lock, flags);
+ list_add(&hsi_cmd->list, &mipi_ld->list_of_hsi_cmd);
+ spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags);
+
+ mipi_err("retry write command : %d\n",
+ retry_count);
+ continue;
+ }
+ mipi_debug("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) {
+ mipi_err("hsi_cmd kmalloc fail\n");
+ return -ENOMEM;
+ }
+ INIT_LIST_HEAD(&hsi_cmd->list);
+
+ hsi_cmd->command = if_hsi_create_cmd(cmd_type, ch, &param);
+ mipi_debug("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);
+
+ mipi_debug("queue_work : cmd_work\n");
+ queue_delayed_work(mipi_ld->mipi_wq, &mipi_ld->cmd_work, 0);
+
+ return 0;
+}
+
+static int if_hsi_decode_cmd(struct mipi_link_device *mipi_ld,
+ 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:
+ mipi_ld->ld.com_state = COM_HANDSHAKE;
+ hsi_conn_reset(mipi_ld);
+ mipi_err("Command MSG_BREAK Received\n");
+
+ if_hsi_send_command(mipi_ld, HSI_LL_MSG_BREAK,
+ HSI_CONTROL_CHANNEL, 0);
+ mipi_err("Send MSG BREAK TO CP\n");
+
+ schedule_delayed_work(&mipi_ld->start_work, HZ / 100);
+ 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) {
+ mipi_err("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:
+ mipi_err("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];
+
+ mipi_debug("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);
+ mipi_debug("wake_lock\n");
+ }
+
+ if_hsi_set_wakeline(channel, 1);
+ mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies +
+ HSI_ACWAKE_DOWN_TIMEOUT);
+
+ ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_ACK, ch,
+ param);
+ if (ret) {
+ mipi_err("if_hsi_send_command fail=%d\n", ret);
+ return ret;
+ }
+
+ channel->packet_size = param;
+ channel->recv_step = STEP_WAIT_FOR_CONN_READY;
+ 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) {
+ mipi_err("hsi_read fail : %d\n", ret);
+ return ret;
+ }
+ return 0;
+
+ case STEP_NOT_READY:
+ case STEP_SEND_TO_CONN_CLOSED:
+ ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_NAK, ch,
+ param);
+ if (ret) {
+ mipi_err("if_hsi_send_command fail=%d\n", ret);
+ return ret;
+ }
+ return 0;
+
+ case STEP_RX:
+ mipi_err("wrong open cmd in rx step\n");
+ return -1;
+
+ default:
+ if (channel->packet_size != param) {
+ hsi_read_cancel(channel->dev);
+ mipi_err("read cancel\n");
+
+ mipi_err("%d open-cmd param changed "
+ "packet_size : %d, param : %d\n",
+ channel->channel_id,
+ channel->packet_size, param);
+
+ channel->packet_size = param;
+ channel->recv_step = STEP_WAIT_FOR_CONN_READY;
+ if (param % 4)
+ param += (4 - (param % 4));
+ channel->rx_count = param;
+ hsi_read(channel->dev, channel->rx_data,
+ channel->rx_count / 4);
+ mipi_err("read again with new len\n");
+ }
+
+ ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_ACK, ch,
+ param);
+ if (ret) {
+ mipi_err("if_hsi_send_command fail=%d\n", ret);
+ return ret;
+ }
+ mipi_debug("wrong state=%08x, recv_step=%d, size=%d\n",
+ cmd, channel->recv_step, param);
+
+ 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;
+ mipi_debug("got ack\n");
+ } else {
+ channel->send_step = STEP_WAIT_FOR_ACK;
+ channel->got_nack = 1;
+ mipi_debug("got nack\n");
+ }
+
+ up(&channel->ack_done_sem);
+ return 0;
+
+ default:
+ mipi_err("wrong state : %d, %08x(%d)\n",
+ channel->send_step, cmd, channel->channel_id);
+ return -1;
+ }
+
+ case HSI_LL_MSG_CONN_CLOSED:
+ switch (channel->send_step) {
+ case STEP_TX:
+ case STEP_WAIT_FOR_CONN_CLOSED:
+ mipi_debug("got close\n");
+
+ mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies +
+ HSI_ACWAKE_DOWN_TIMEOUT);
+ channel->send_step = STEP_IDLE;
+ up(&channel->close_conn_done_sem);
+ return 0;
+
+ default:
+ mipi_err("wrong state : %d, %08x(%d)\n",
+ channel->send_step, cmd, channel->channel_id);
+ return -1;
+ }
+
+ case HSI_LL_MSG_CANCEL_CONN:
+ mipi_err("HSI_LL_MSG_CANCEL_CONN\n");
+
+ ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_ACK,
+ HSI_CONTROL_CHANNEL, 0);
+ if (ret) {
+ mipi_err("if_hsi_send_command fail : %d\n", ret);
+ return ret;
+ }
+ mipi_err("RESET MIPI, SEND ACK\n");
+ return -1;
+
+ case HSI_LL_MSG_OPEN_CONN:
+ case HSI_LL_MSG_ECHO:
+ case HSI_LL_MSG_CONF_RATE:
+ default:
+ mipi_err("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) {
+ mipi_err("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);
+ mipi_debug("wake_lock\n");
+ }
+
+ if_hsi_set_wakeline(channel, 1);
+ mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies +
+ HSI_ACWAKE_DOWN_TIMEOUT);
+
+retry_send:
+
+ ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_OPEN_CONN_OCTET, ch,
+ len);
+ if (ret) {
+ mipi_err("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) {
+ mipi_err("ch=%d, ack_done timeout\n", channel->channel_id);
+
+ iod = link_get_iod_with_format(&mipi_ld->ld, IPC_FMT);
+ if (iod && iod->mc->phone_state == STATE_ONLINE &&
+ ((mipi_ld->ld.com_state == COM_ONLINE) ||
+ (mipi_ld->ld.com_state == COM_HANDSHAKE))) {
+ channel->send_step = STEP_SEND_OPEN_CONN;
+ hsi_conn_err_recovery(mipi_ld);
+
+ ack_timeout_cnt++;
+ if (ack_timeout_cnt < 5) {
+ mipi_err("check ack again. cnt:%d\n",
+ ack_timeout_cnt);
+ msleep(20);
+ if (down_trylock(&channel->ack_done_sem)) {
+ mipi_err("retry send open\n");
+ if_hsi_set_wakeline(channel, 0);
+ if_hsi_set_wakeline(channel, 1);
+ sema_init(&channel->ack_done_sem,
+ HSI_SEMAPHORE_COUNT);
+ goto retry_send;
+ } else {
+ mipi_err("got ack after sw-reset\n");
+ goto check_nack;
+ }
+ }
+
+ /* cp force crash to get cp ramdump */
+ if (iod->mc->gpio_ap_dump_int)
+ iod->mc->ops.modem_force_crash_exit(
+ iod->mc);
+ else if (iod->mc->bootd) /* cp force reset */
+ iod->mc->bootd->modem_state_changed(
+ iod->mc->bootd, STATE_CRASH_RESET);
+ }
+
+ channel->send_step = STEP_IDLE;
+ return -ETIMEDOUT;
+ }
+
+check_nack:
+
+ mipi_debug("ch=%d, got ack_done=%d\n", channel->channel_id,
+ channel->got_nack);
+
+ if (channel->got_nack && (retry_count < 10)) {
+ mipi_info("ch=%d, got nack=%d retry=%d\n", channel->channel_id,
+ channel->got_nack, retry_count);
+ retry_count++;
+ msleep(20);
+ goto retry_send;
+ }
+ retry_count = 0;
+
+ channel->send_step = STEP_TX;
+
+ ret = if_hsi_write(channel, data, len);
+ if (ret < 0) {
+ mipi_err("if_hsi_write fail : %d\n", ret);
+ if_hsi_set_wakeline(channel, 0);
+ channel->send_step = STEP_IDLE;
+ return ret;
+ }
+ mipi_debug("SEND DATA : %08x(%d)\n", *data, len);
+
+ channel->send_step = STEP_WAIT_FOR_CONN_CLOSED;
+ if (down_timeout(&channel->close_conn_done_sem,
+ HSI_CLOSE_CONN_DONE_TIMEOUT) < 0) {
+ mipi_err("ch=%d, close conn timeout\n", channel->channel_id);
+
+ channel->send_step = STEP_IDLE;
+ hsi_conn_err_recovery(mipi_ld);
+ }
+ mipi_debug("ch=%d, got close_conn_done\n",
+ channel->channel_id);
+
+ channel->send_step = STEP_IDLE;
+
+ mipi_debug("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;
+
+ mipi_debug("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) {
+ mipi_err("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) {
+ mipi_err("ch=%d, hsi_write_done timeout : %d\n",
+ channel->channel_id, size);
+
+ print_hex_dump_bytes("[HSI]", DUMP_PREFIX_OFFSET,
+ channel->tx_data, size);
+
+ 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)
+ mipi_err("ch:%d,write_done fail,write_size:%d,origin_size:%d\n",
+ channel->channel_id, channel->tx_count, size);
+
+#ifdef DEBUG
+ print_hex_dump_bytes("[HSI]", DUMP_PREFIX_OFFSET,
+ channel->tx_data, size);
+#endif
+
+ 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];
+
+ if ((channel->channel_id == HSI_CONTROL_CHANNEL) &&
+ (((*channel->tx_data & 0xF0000000) >> 28) ==
+ HSI_LL_MSG_CONN_CLOSED) &&
+ (mipi_ld->ld.com_state == COM_ONLINE ||
+ mipi_ld->ld.com_state == COM_HANDSHAKE)) {
+ mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies +
+ HSI_ACWAKE_DOWN_TIMEOUT);
+ mipi_ld->hsi_channles[
+ (*channel->tx_data & 0x0F000000) >> 24].recv_step = STEP_IDLE;
+ }
+
+ mipi_debug("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);
+
+ 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;
+
+ mipi_debug("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:
+ mipi_debug("RECV CMD : %08x\n", *channel->rx_data);
+
+ if (channel->rx_count != 4) {
+ mipi_err("wrong command len : %d\n",
+ channel->rx_count);
+ return;
+ }
+
+ ret = if_hsi_decode_cmd(mipi_ld, channel->rx_data,
+ &cmd, &ch, &param);
+ if (ret)
+ mipi_err("decode_cmd fail=%d, cmd=%x\n",
+ ret, cmd);
+ else {
+ mipi_debug("decode_cmd : %08x\n", cmd);
+ ret = if_hsi_rx_cmd_handle(mipi_ld, cmd, ch,
+ param);
+ if (ret)
+ mipi_debug("handle cmd cmd=%x\n", cmd);
+ }
+
+ ret = hsi_read(channel->dev, channel->rx_data, 1);
+ if (ret)
+ mipi_err("hsi_read fail : %d\n", ret);
+
+ return;
+
+ case COM_BOOT:
+ mipi_debug("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) {
+ ret = iod->recv(iod, &mipi_ld->ld,
+ (char *)channel->rx_data,
+ channel->rx_count);
+ if (ret < 0)
+ mipi_err("recv call fail : %d\n", ret);
+ }
+
+ ret = hsi_read(channel->dev, channel->rx_data, 1);
+ if (ret)
+ mipi_err("hsi_read fail : %d\n", ret);
+ return;
+
+ case COM_BOOT_EBL:
+ mipi_debug("receive data : 0x%x(%d)\n",
+ *channel->rx_data, channel->rx_count);
+
+ iod = link_get_iod_with_format(&mipi_ld->ld,
+ IPC_BOOT_2);
+ if (iod) {
+ ret = iod->recv(iod, &mipi_ld->ld,
+ (char *)channel->rx_data,
+ channel->rx_count);
+ if (ret < 0)
+ mipi_err("recv call fail : %d\n", ret);
+ }
+
+ ret = hsi_read(channel->dev, channel->rx_data, 1);
+ if (ret)
+ mipi_err("hsi_read fail : %d\n", ret);
+ return;
+
+ case COM_CRASH:
+ mipi_debug("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;
+ mipi_debug("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)
+ mipi_err("recv call fail : %d\n", ret);
+ }
+
+ ret = hsi_read(channel->dev, channel->rx_data,
+ DUMP_PACKET_SIZE);
+ if (ret)
+ mipi_err("hsi_read fail : %d\n", ret);
+ return;
+
+ case COM_NONE:
+ default:
+ mipi_err("receive data in wrong state : 0x%x(%d)\n",
+ *channel->rx_data, channel->rx_count);
+ return;
+ }
+ break;
+
+ case HSI_FMT_CHANNEL:
+ mipi_debug("iodevice format : IPC_FMT\n");
+ format_type = IPC_FMT;
+ break;
+ case HSI_RAW_CHANNEL:
+ mipi_debug("iodevice format : IPC_MULTI_RAW\n");
+ format_type = IPC_MULTI_RAW;
+ break;
+ case HSI_RFS_CHANNEL:
+ mipi_debug("iodevice format : IPC_RFS\n");
+ format_type = IPC_RFS;
+ break;
+
+ case HSI_CMD_CHANNEL:
+ mipi_debug("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)
+ mipi_err("send_cmd fail=%d\n", ret);
+ return;
+
+ default:
+ return;
+ }
+
+ iod = link_get_iod_with_format(&mipi_ld->ld, format_type);
+ if (iod) {
+ channel->recv_step = STEP_RX;
+
+ mipi_debug("RECV DATA : %08x(%d)-%d\n", *channel->rx_data,
+ channel->packet_size, iod->format);
+
+ ret = iod->recv(iod, &mipi_ld->ld, (char *)channel->rx_data,
+ channel->packet_size);
+ if (ret < 0) {
+ mipi_err("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)
+ mipi_err("send_cmd fail=%d\n", ret);
+
+ print_hex_dump_bytes("[HSI]", DUMP_PREFIX_OFFSET,
+ channel->rx_data, channel->packet_size);
+
+ /* to clean the all wrong packet */
+ channel->packet_size = 0;
+ hsi_conn_err_recovery(mipi_ld);
+ return;
+ }
+
+ channel->packet_size = 0;
+
+ ch = channel->channel_id;
+ param = 0;
+ ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_CONN_CLOSED,
+ ch, param);
+ if (ret)
+ mipi_err("send_cmd fail=%d\n", ret);
+ }
+}
+
+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:
+ mipi_err("HSI_EVENT_BREAK_DETECTED\n");
+ return;
+
+ case HSI_EVENT_HSR_DATAAVAILABLE:
+ mipi_err("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);
+ mipi_debug("wake_lock\n");
+ }
+ mipi_debug("CAWAKE_%d(1)\n", dev->n_ch);
+ }
+ return;
+
+ case HSI_EVENT_CAWAKE_DOWN:
+ if (dev->n_ch == HSI_CONTROL_CHANNEL)
+ mipi_debug("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);
+
+ mipi_debug("GET_ACWAKE. Ch : %d, level : %d\n",
+ dev->n_ch, acwake_level);
+
+ if (!acwake_level) {
+ wake_unlock(&mipi_ld->wlock);
+ mipi_debug("wake_unlock\n");
+ }
+ }
+ return;
+
+ case HSI_EVENT_ERROR:
+ mipi_err("HSI_EVENT_ERROR\n");
+ return;
+
+ default:
+ mipi_err("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,
+ HSI_SEMAPHORE_COUNT);
+ sema_init(&mipi_ld->hsi_channles[dev->n_ch].ack_done_sem,
+ HSI_SEMAPHORE_COUNT);
+ sema_init(&mipi_ld->hsi_channles[dev->n_ch].close_conn_done_sem,
+ HSI_SEMAPHORE_COUNT);
+ }
+
+ mipi_debug("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;
+
+ if_hsi_driver.priv_data = (void *)mipi_ld;
+ ret = hsi_register_driver(&if_hsi_driver);
+ if (ret) {
+ mipi_err("hsi_register_driver() fail : %d\n", ret);
+ return ret;
+ }
+
+ mipi_ld->mipi_wq = alloc_workqueue("mipi_cmd_wq",
+ WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1);
+ if (!mipi_ld->mipi_wq) {
+ mipi_err("fail to create work Q.\n");
+ return -ENOMEM;
+ }
+ INIT_DELAYED_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);
+
+ 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) {
+ mipi_err("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) {
+ mipi_err("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) {
+ mipi_err("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) {
+ mipi_err("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) {
+ mipi_err("alloc HSI_CMD_CHANNEL rx_data fail\n");
+ return -ENOMEM;
+ }
+
+ mipi_ld->bulk_tx_buf = kmalloc(MIPI_BULK_TX_SIZE, GFP_DMA | GFP_ATOMIC);
+ if (!mipi_ld->bulk_tx_buf) {
+ mipi_err("alloc bulk tx buffer fail\n");
+ return -ENOMEM;
+ }
+
+ skb_queue_head_init(&mipi_ld->bulk_txq);
+
+ 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;
+
+ 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;
+
+ ld->tx_wq = create_singlethread_workqueue("mipi_tx_wq");
+ if (!ld->tx_wq) {
+ mipi_err("fail to create work Q.\n");
+ return NULL;
+ }
+ INIT_WORK(&ld->tx_work, mipi_hsi_tx_work);
+
+ ld->tx_raw_wq = alloc_workqueue("mipi_tx_raw_wq",
+ WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1);
+ if (!ld->tx_raw_wq) {
+ mipi_err("fail to create raw work Q.\n");
+ return NULL;
+ }
+ INIT_DELAYED_WORK(&ld->tx_delayed_work, mipi_hsi_tx_raw_work);
+
+ ret = if_hsi_init(ld);
+ if (ret)
+ return NULL;
+
+ return ld;
+}
+
diff --git a/drivers/misc/modem_if_v2/modem_link_device_mipi.h b/drivers/misc/modem_if_v2/modem_link_device_mipi.h
new file mode 100644
index 00000000000..831dc67fcac
--- /dev/null
+++ b/drivers/misc/modem_if_v2/modem_link_device_mipi.h
@@ -0,0 +1,182 @@
+/*
+ * 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_DEVICE_MIPI_H__
+#define __MODEM_LINK_DEVICE_MIPI_H__
+
+#define HSI_SEMAPHORE_COUNT 0
+
+#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 / 2)
+#define HSI_CLOSE_CONN_DONE_TIMEOUT (HZ / 2)
+#define HSI_ACWAKE_DOWN_TIMEOUT (HZ)
+
+#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 DUMP_PACKET_SIZE 4097 /* (16K + 4 ) / 4 length, word unit */
+#define DUMP_ERR_INFO_SIZE 39 /* 150 bytes + 4 length , word unit */
+
+#define MIPI_BULK_TX_SIZE (8 * 1024)
+
+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,
+ STEP_SEND_TO_CONN_CLOSED,
+};
+
+
+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 delayed_work cmd_work;
+ struct delayed_work start_work;
+
+ struct wake_lock wlock;
+ struct timer_list hsi_acwake_down_timer;
+
+ /* maybe -list of io devices for the link device to use
+ * to find where to send incoming packets to */
+ struct list_head list_of_io_devices;
+
+ void *bulk_tx_buf;
+ struct sk_buff_head bulk_txq;
+
+ /* for mipi-link's first initialization
+ * link has to be initialized right after modem power on */
+ bool modem_power_on;
+};
+/* 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,
+ HSI_INIT_MODE_FLASHLESS_BOOT_EBL,
+};
+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);
+
+
+#define MIPI_LOG_TAG "mipi_link: "
+
+#define mipi_err(fmt, ...) \
+ pr_err(MIPI_LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__)
+#define mipi_debug(fmt, ...) \
+ pr_debug(MIPI_LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__)
+#define mipi_info(fmt, ...) \
+ pr_info(MIPI_LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__)
+
+#endif
diff --git a/drivers/misc/modem_if_v2/modem_modemctl_device_xmm6262.c b/drivers/misc/modem_if_v2/modem_modemctl_device_xmm6262.c
new file mode 100644
index 00000000000..e3a2a7d03ac
--- /dev/null
+++ b/drivers/misc/modem_if_v2/modem_modemctl_device_xmm6262.c
@@ -0,0 +1,255 @@
+/* /linux/drivers/misc/modem_if_v2/modem_modemctl_device_xmm6262.c
+ *
+ * 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.
+ *
+ */
+
+#include <linux/init.h>
+
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+
+#include <linux/platform_data/modem_v2.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;
+ }
+
+ 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);
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ if (mc->gpio_ap_dump_int)
+ gpio_set_value(mc->gpio_ap_dump_int, 0);
+
+ 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);
+
+ mc->phone_state = STATE_OFFLINE;
+ return 0;
+}
+
+
+static int xmm6262_reset(struct modem_ctl *mc)
+{
+ mif_info("\n");
+
+ if (!mc->gpio_cp_reset || !mc->gpio_reset_req_n || !mc->gpio_cp_on)
+ 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);
+ mc->phone_state = STATE_OFFLINE;
+
+ 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);
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ if (mc->gpio_ap_dump_int)
+ gpio_set_value(mc->gpio_ap_dump_int, 0);
+
+ mc->phone_state = STATE_BOOTING;
+ return 0;
+}
+
+static int xmm6262_force_crash_exit(struct modem_ctl *mc)
+{
+ mif_info("\n");
+
+ if (!mc->gpio_ap_dump_int)
+ return -ENXIO;
+
+ gpio_set_value(mc->gpio_ap_dump_int, 1);
+ mif_info("set ap_dump_int(%d) to high=%d\n",
+ mc->gpio_ap_dump_int, gpio_get_value(mc->gpio_ap_dump_int));
+ 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_ONLINE;
+ 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;
+
+ mif_info("SD EVENT : level=%d, online=%d, changed=%d\n",
+ gpio_get_value(mc->gpio_sim_detect), mc->sim_state.online,
+ mc->sim_state.changed);
+
+ 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 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;
+ mc->ops.modem_force_crash_exit = xmm6262_force_crash_exit;
+}
+
+int xmm6262_init_modemctl_device(struct modem_ctl *mc,
+ struct modem_data *pdata)
+{
+ int ret;
+
+ 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_ap_dump_int = pdata->gpio_ap_dump_int;
+ mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int;
+ mc->gpio_sim_detect = pdata->gpio_sim_detect;
+
+ 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);
+ return ret;
+ }
+
+ ret = enable_irq_wake(mc->irq_phone_active);
+ if (ret) {
+ mif_err("failed to enable_irq_wake:%d\n", ret);
+ goto err_exit;
+ }
+
+ /* 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 SD request_irq:%d\n", ret);
+ goto err_exit;
+ }
+
+ ret = enable_irq_wake(mc->irq_sim_detect);
+ if (ret) {
+ mif_err("failed to SD enable_irq:%d\n", ret);
+ free_irq(mc->irq_sim_detect, mc);
+ goto err_exit;
+ }
+
+ /* initialize sim_state => insert: gpio=0, remove: gpio=1 */
+ mc->sim_state.online = !gpio_get_value(mc->gpio_sim_detect);
+ mif_info("SIM detected online=%d\n", mc->sim_state.online);
+ }
+
+ return ret;
+
+err_exit:
+ free_irq(mc->irq_phone_active, mc);
+ return ret;
+}
diff --git a/drivers/misc/modem_if_v2/modem_net_flowcontrol_device.c b/drivers/misc/modem_if_v2/modem_net_flowcontrol_device.c
new file mode 100644
index 00000000000..54b949607a6
--- /dev/null
+++ b/drivers/misc/modem_if_v2/modem_net_flowcontrol_device.c
@@ -0,0 +1,115 @@
+/* /linux/drivers/misc/modem_if_v2/modem_net_flowcontrol_device.c
+ *
+ * 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.
+ *
+ */
+
+#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_v2.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 (unlikely(!ndev)) {
+ 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_v2/modem_prj.h b/drivers/misc/modem_if_v2/modem_prj.h
new file mode 100644
index 00000000000..90711fe6dd3
--- /dev/null
+++ b/drivers/misc/modem_if_v2/modem_prj.h
@@ -0,0 +1,353 @@
+/*
+ * 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_PRJ_H__
+#define __MODEM_PRJ_H__
+
+#include <linux/wait.h>
+#include <linux/miscdevice.h>
+#include <linux/skbuff.h>
+#include <linux/wakelock.h>
+#include <linux/rbtree.h>
+#include <linux/spinlock.h>
+#include <linux/cdev.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_START _IO('o', 0x24)
+#define __UNUSED__IOCTL_MODEM_SEND _IO('o', 0x25)
+#define __UNUSED__IOCTL_MODEM_RECV _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)
+
+/*
+ * 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_IPC_TX_SIZE 1024
+#define MAX_MTU_TX_DATA_SIZE 1550
+
+#define HDLC_HEADER_MAX_SIZE 6 /* fmt 3, raw 6, rfs 6 or sipc5 */
+#define MAX_LINK_PADDING_SIZE 3
+
+#define PSD_DATA_CHID_BEGIN 0x2A
+#define PSD_DATA_CHID_END 0x38
+#define IP6VERSION 6
+#define SOURCE_MAC_ADDR {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}
+
+#define FMT_WAKE_TIME (HZ/2)
+#define RFS_WAKE_TIME (HZ*3)
+#define RAW_WAKE_TIME (HZ*6)
+
+#define CP_LOOPBACK_CHANNEL 30
+
+
+/* Does modem ctl structure will use state ? or status defined below ?*/
+/* Be careful!! below sequence shouldn't be changed*/
+enum modem_state {
+ STATE_OFFLINE,
+ STATE_CRASH_RESET,
+ STATE_CRASH_EXIT,
+ STATE_BOOTING,
+ STATE_ONLINE,
+ STATE_NV_REBUILDING,
+ STATE_LOADER_DONE,
+ STATE_SIM_ATTACH,
+ STATE_SIM_DETACH,
+};
+
+enum com_state {
+ COM_NONE,
+ COM_ONLINE,
+ COM_HANDSHAKE,
+ COM_BOOT,
+ COM_CRASH,
+ COM_BOOT_EBL,
+};
+
+struct sim_state {
+ bool online; /* SIM is online? */
+ bool changed; /* online is changed? */
+};
+
+struct header_data {
+ char hdr[HDLC_HEADER_MAX_SIZE];
+ unsigned len;
+ unsigned frag_len;
+ char start; /* hdlc start header 0x7F or 0b11111000 */
+};
+
+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 vnet {
+ struct io_device *iod;
+};
+
+/* for fragmented data from link devices */
+struct fragmented_data {
+ struct sk_buff *skb_recv;
+ struct header_data h_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 */
+};
+
+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;
+
+ bool use_handover; /* handover 2+ link devices */
+
+ /* Rx queue of sk_buff */
+ struct sk_buff_head sk_rx_q;
+
+ struct fragmented_data fragments[LINKDEV_MAX];
+
+ /* 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);
+
+ /* 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;
+
+ /* Modem control */
+ struct modem_ctl *mc;
+
+ /* Modem shared data */
+ struct modem_shared *msd;
+
+ /* 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;
+
+ bool raw_tx_suspended; /* for misc dev */
+ struct completion raw_tx_resumed_by_cp;
+
+ struct workqueue_struct *tx_wq;
+ struct workqueue_struct *tx_raw_wq;
+ struct work_struct tx_work;
+ struct delayed_work tx_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);
+};
+
+/** 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_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;
+};
+
+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_sim_detect;
+
+ int irq_phone_active;
+ int irq_sim_detect;
+
+ struct modemctl_ops ops;
+ struct io_device *iod;
+ struct io_device *bootd;
+
+};
+
+int sipc4_init_io_device(struct io_device *iod);
+
+#endif
diff --git a/drivers/misc/modem_if_v2/modem_utils.c b/drivers/misc/modem_if_v2/modem_utils.c
new file mode 100644
index 00000000000..64fd9e349df
--- /dev/null
+++ b/drivers/misc/modem_if_v2/modem_utils.c
@@ -0,0 +1,301 @@
+/*
+ * 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.
+ *
+ */
+
+#include <linux/netdevice.h>
+#include <linux/platform_data/modem_v2.h>
+#include <linux/platform_device.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/rtc.h>
+#include <linux/time.h>
+#include <net/ip.h>
+
+#include "modem_prj.h"
+#include "modem_utils.h"
+
+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) {
+ pr_err("[MIF] <%s> alloc skb failed\n", __func__);
+ 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 @buf must be greater than "@len * 3"
+ * it need 3 bytes per one data byte to print.
+ */
+static inline int dump2hex(char *buf, const char *data, size_t len)
+{
+ static const char *hex = "0123456789abcdef";
+ char *dest = buf;
+ int i;
+
+ for (i = 0; i < len; i++) {
+ *dest++ = hex[(data[i] >> 4) & 0xf];
+ *dest++ = hex[data[i] & 0xf];
+ *dest++ = ' ';
+ }
+ if (likely(len > 0))
+ dest--; /* last space will be overwrited with null */
+
+ *dest = '\0';
+
+ return dest - buf;
+}
+
+/* print buffer as hex string */
+int pr_buffer(const char *tag, const char *data, size_t data_len,
+ size_t max_len)
+{
+ size_t len = min(data_len, max_len);
+ unsigned char hexstr[len ? len * 3 : 1]; /* 1 <= sizeof <= max_len*3 */
+ dump2hex(hexstr, data, len);
+
+ /* don't change this printk to mif_debug for print this as level7 */
+ return printk(KERN_INFO "%s(%u): %s%s\n", tag, data_len, hexstr,
+ len == data_len ? "" : " ...");
+}
+
+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);
+ }
+}
diff --git a/drivers/misc/modem_if_v2/modem_utils.h b/drivers/misc/modem_if_v2/modem_utils.h
new file mode 100644
index 00000000000..9bf9a86b439
--- /dev/null
+++ b/drivers/misc/modem_if_v2/modem_utils.h
@@ -0,0 +1,188 @@
+/*
+ * 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_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_IPC_SKB_SIZE 4096
+#define MAX_LOG_SIZE 64
+
+#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];
+};
+
+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 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)
+
+/* 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);
+
+#endif/*__MODEM_UTILS_H__*/
diff --git a/drivers/misc/modem_if_v2/modem_variation.h b/drivers/misc/modem_if_v2/modem_variation.h
new file mode 100644
index 00000000000..6ae56d2d31b
--- /dev/null
+++ b/drivers/misc/modem_if_v2/modem_variation.h
@@ -0,0 +1,81 @@
+/*
+ * 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_VARIATION_H__
+#define __MODEM_VARIATION_H__
+
+#define DECLARE_MODEM_INIT(type) \
+ int type ## _init_modemctl_device(struct modem_ctl *mc, \
+ struct modem_data *pdata)
+#define DECLARE_MODEM_INIT_DUMMY(type) \
+ 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_XMM6262
+DECLARE_MODEM_INIT(xmm6262);
+#else
+DECLARE_MODEM_INIT_DUMMY(xmm6262)
+#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
+
+typedef int (*modem_init_call)(struct modem_ctl *, struct modem_data *);
+static modem_init_call modem_init_func[] = {
+ MODEM_INIT_CALL(xmm6262),
+ 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),
+};
+
+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_v2/sipc4_io_device.c b/drivers/misc/modem_if_v2/sipc4_io_device.c
new file mode 100644
index 00000000000..42feaac47d9
--- /dev/null
+++ b/drivers/misc/modem_if_v2/sipc4_io_device.c
@@ -0,0 +1,1193 @@
+/* /linux/drivers/misc/modem_if_v2/sipc4_io_device.c
+ *
+ * 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.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/if_arp.h>
+#include <linux/ip.h>
+#include <linux/if_ether.h>
+#include <linux/etherdevice.h>
+#include <linux/ratelimit.h>
+#include <linux/device.h>
+
+#include <linux/platform_data/modem_v2.h>
+#include "modem_prj.h"
+#include "modem_utils.h"
+
+
+#define HDLC_START 0x7F
+#define HDLC_END 0x7E
+#define SIZE_OF_HDLC_START 1
+#define SIZE_OF_HDLC_END 1
+
+static const char hdlc_start[1] = { HDLC_START };
+static const char hdlc_end[1] = { HDLC_END };
+
+
+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 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:
+ case IPC_BOOT_2:
+ /* 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;
+ 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;
+
+ case IPC_CMD:
+ case IPC_BOOT:
+ case IPC_BOOT_2:
+ case IPC_RAMDUMP:
+ default:
+ 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;
+
+ case IPC_CMD:
+ case IPC_BOOT:
+ case IPC_BOOT_2:
+ case IPC_RAMDUMP:
+ default:
+ return NULL;
+ }
+}
+
+static inline int calc_padding_size(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;
+ 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(%s)\n",
+ *buf, iod->name);
+ 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;
+
+ buf += len;
+ done_len += len;
+ rest -= len; /* rest, call by value */
+ }
+ mif_debug("check len : %d, rest : %d (%d)\n", len, rest,
+ __LINE__);
+
+ /* get header size without HDLC Start size */
+ head_size = get_header_size(iod);
+
+ /* 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;
+}
+
+static int rx_iodev_skb(struct sk_buff *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_multipdp(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;
+
+ 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)
+ return NET_RX_DROP;
+
+ skb->dev = ndev;
+ ndev->stats.rx_packets++;
+ ndev->stats.rx_bytes += skb->len;
+
+ /* check the version of IP */
+ ip_header = (struct iphdr *)skb->data;
+ if (ip_header->version == IP6VERSION)
+ skb->protocol = htons(ETH_P_IPV6);
+ else
+ skb->protocol = htons(ETH_P_IP);
+
+ if (iod->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)
+ mif_err("ERR: <%s> netif_rx fail (err %d)\n",
+ iod->name, err);
+
+ return err;
+
+ default:
+ mif_err("wrong io_type : %d\n", iod->io_typ);
+ return -EINVAL;
+ }
+}
+
+/* handling modem intiated loopback packet
+ * packet path: Modem -> LINK -> IOD -> LINK -> Modem
+ */
+static int rx_data_loopback(struct sk_buff *skb, struct io_device *iod,
+ struct link_device *ld)
+{
+ int headroom, tailroom;
+ struct sk_buff *skb_new;
+ struct raw_hdr raw_header;
+
+ mif_debug("CP LB DATA Received: size=%d\n", skb->len);
+
+ headroom = sizeof(raw_header) + SIZE_OF_HDLC_START;
+ tailroom = SIZE_OF_HDLC_END;
+ if (unlikely(skb_headroom(skb) < headroom) ||
+ unlikely(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;
+
+ /* mark loopback header */
+ raw_header.len = skb_new->len + sizeof(raw_header);
+ raw_header.channel = CP_LOOPBACK_CHANNEL;
+ raw_header.control = 0x03;
+
+ /* fill header data and HDLC framing */
+ memcpy(skb_push(skb_new, sizeof(raw_header)), &raw_header,
+ sizeof(raw_header));
+ memcpy(skb_push(skb_new, SIZE_OF_HDLC_START), hdlc_start,
+ SIZE_OF_HDLC_START);
+ memcpy(skb_put(skb_new, SIZE_OF_HDLC_END), hdlc_end, SIZE_OF_HDLC_END);
+
+ skbpriv(skb_new)->iod = iod;
+ skbpriv(skb_new)->ld = ld;
+
+ ld->send(ld, iod, skb);
+ return 0;
+}
+
+/* de-mux function draft */
+static int rx_iodev_skb(struct sk_buff *skb)
+{
+ u8 ch;
+ struct io_device *iod = skbpriv(skb)->iod;
+ struct io_device *real_iod = NULL;
+ struct link_device *ld = skbpriv(skb)->ld;
+ struct raw_hdr *raw_header;
+
+ switch (iod->format) {
+ case IPC_RAW:
+ case IPC_MULTI_RAW:
+ raw_header = (struct raw_hdr *)fragdata(iod, ld)->h_data.hdr;
+ ch = raw_header->channel;
+
+ if (ch == CP_LOOPBACK_CHANNEL)
+ return rx_data_loopback(skb, iod, ld);
+
+ 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;
+
+ return rx_multipdp(skb);
+
+ case IPC_CMD:
+ case IPC_FMT:
+ case IPC_RFS:
+ case IPC_BOOT:
+ case IPC_BOOT_2:
+ case IPC_RAMDUMP:
+ 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%x(%s), rest: %d,"
+ " recv_size:%d\n", *buf, iod->name, rest, recv_size);
+ 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(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:
+ 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;
+}
+
+/* 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;
+
+ switch (iod->format) {
+ case IPC_RFS:
+ case IPC_FMT:
+ case IPC_RAW:
+ case IPC_MULTI_RAW:
+ if (iod->waketime)
+ wake_lock_timeout(&iod->wakelock, iod->waketime);
+ err = rx_hdlc_packet(iod, ld, data, len);
+ if (err < 0)
+ mif_err("fail process HDLC frame\n");
+ return err;
+
+ case IPC_CMD:
+ case IPC_BOOT:
+ case IPC_BOOT_2:
+ 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;
+ }
+ /* 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_info("iod is not opened: %s\n",
+ iod->name);
+ else if (iod->mc->sim_state.online == sim_online)
+ mif_info("sim state not changed.\n");
+ else {
+ iod->mc->sim_state.online = sim_online;
+ iod->mc->sim_state.changed = true;
+
+ 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)
+ return POLLHUP;
+ else
+ return 0;
+}
+
+static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int s_state;
+ struct io_device *iod = (struct io_device *)filp->private_data;
+ char cpinfo_buf[530] = "CP Crash ";
+ 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);
+
+ case IOCTL_MODEM_START:
+ mif_debug("misc_ioctl : IOCTL_MODEM_START\n");
+ return 0;
+
+ case IOCTL_MODEM_STATUS:
+ mif_debug("misc_ioctl : IOCTL_MODEM_STATUS\n");
+
+ if (iod->mc->sim_state.changed &&
+ !strcmp(get_task_comm(str, get_current()), "rild")) {
+ 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;
+ }
+
+ if (iod->mc->phone_state == STATE_NV_REBUILDING) {
+ mif_info("send nv rebuild state : %d\n",
+ iod->mc->phone_state);
+ iod->mc->phone_state = STATE_ONLINE;
+ }
+ return iod->mc->phone_state;
+
+ 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);
+
+ default:
+ mif_err("misc_ioctl : ioctl 0x%X is not defined.\n", cmd);
+ return -EINVAL;
+ }
+}
+
+static ssize_t misc_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct io_device *iod = (struct io_device *)filp->private_data;
+ 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 - Add handling for over size 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_CMD:
+ case IPC_BOOT:
+ case IPC_BOOT_2:
+ 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;
+
+ case IPC_FMT:
+ case IPC_RAW:
+ case IPC_MULTI_RAW:
+ 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;
+ }
+
+ if (ld->aligned)
+ skb_put(skb, calc_padding_size(ld, skb->len));
+
+ /* 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)
+ 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);
+
+ 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;
+
+ skb = skb_dequeue(&iod->sk_rx_q);
+ if (!skb) {
+ printk_ratelimited(KERN_ERR "mif: no data from sk_rx_q, "
+ "modem_state : %d(%s)\n",
+ iod->mc->phone_state, iod->name);
+ return 0;
+ }
+
+ if (skb->len > count) {
+ mif_err("skb len is too big = %d,%d!(%d)\n",
+ count, skb->len, __LINE__);
+ dev_kfree_skb_any(skb);
+ return -EIO;
+ }
+
+ pktsize = skb->len;
+ if (copy_to_user(buf, skb->data, pktsize) != 0) {
+ dev_kfree_skb_any(skb);
+ return -EIO;
+ }
+ dev_kfree_skb_any(skb);
+
+ return pktsize;
+}
+
+static const struct file_operations misc_io_fops = {
+ .owner = THIS_MODULE,
+ .open = misc_open,
+ .release = misc_release,
+ .poll = misc_poll,
+ .unlocked_ioctl = misc_ioctl,
+ .write = misc_write,
+ .read = misc_read,
+};
+
+static int vnet_open(struct net_device *ndev)
+{
+ 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;
+ int headroom = 0;
+ int tailroom = 0;
+ struct sk_buff *skb_new;
+ struct vnet *vnet = netdev_priv(ndev);
+ struct io_device *iod = vnet->iod;
+ struct link_device *ld = get_current_link(iod);
+ struct raw_hdr hd;
+
+ /* ToDo - Add handling for over size data */
+
+ /* 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));
+ }
+
+ hd.len = skb->len + sizeof(hd);
+ hd.control = 0;
+ hd.channel = iod->id & 0x1F;
+
+ 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(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;
+
+ /* to send SIM change event */
+ 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 drv */
+ 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_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);
+
+ 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 sysfs file : %s\n",
+ iod->name);
+
+ break;
+
+ default:
+ mif_err("wrong io_type : %d\n", iod->io_typ);
+ return -EINVAL;
+ }
+
+ mif_info("%s(%d) : init_io_device() done : %d\n",
+ iod->name, iod->io_typ, ret);
+ return ret;
+}
+
diff --git a/drivers/misc/modem_if_v2/sipc4_modem.c b/drivers/misc/modem_if_v2/sipc4_modem.c
new file mode 100644
index 00000000000..5c394392fce
--- /dev/null
+++ b/drivers/misc/modem_if_v2/sipc4_modem.c
@@ -0,0 +1,321 @@
+/* linux/drivers/misc/modem_if_v2/sipc4_modem.c
+ *
+ * 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.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+
+#include <linux/platform_data/modem_v2.h>
+#include "modem_prj.h"
+#include "modem_variation.h"
+#include "modem_utils.h"
+
+
+/* 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)
+
+
+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;
+
+ 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: create_modemctl_device DONE\n", modemctl->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("io device memory alloc fail\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->use_handover = pdata->use_handover;
+ atomic_set(&iod->opened, 0);
+
+ /* link between io device and modem control */
+ iod->mc = modemctl;
+ if (iod->format == IPC_FMT)
+ modemctl->iod = iod;
+ else if (iod->format == IPC_BOOT)
+ modemctl->bootd = iod;
+
+ /* 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: create_io_device DONE\n", io_t->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 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_RFS:
+ wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name);
+ iod->waketime = RFS_WAKE_TIME;
+ break;
+
+ case IPC_MULTI_RAW:
+ 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;
+
+ 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;
+
+ 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("modem_probe Done\n");
+ return 0;
+
+err_free_modemctl:
+ for (i = 0; i < pdata->num_iodevs; i++)
+ if (iod[i] != NULL)
+ kfree(iod[i]);
+
+ if (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);
+
+ if (mc->ops.modem_off)
+ mc->ops.modem_off(mc);
+ mc->phone_state = STATE_OFFLINE;
+}
+
+static int modem_suspend(struct device *pdev)
+{
+ struct modem_ctl *mc = dev_get_drvdata(pdev);
+
+ if (mc->gpio_pda_active)
+ gpio_set_value(mc->gpio_pda_active, 0);
+
+ return 0;
+}
+
+static int modem_resume(struct device *pdev)
+{
+ struct modem_ctl *mc = dev_get_drvdata(pdev);
+
+ if (mc->gpio_pda_active)
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ return 0;
+}
+
+static const struct dev_pm_ops modem_pm_ops = {
+ .suspend = modem_suspend,
+ .resume = modem_resume,
+};
+
+static struct platform_driver modem_driver = {
+ .probe = modem_probe,
+ .shutdown = modem_shutdown,
+ .driver = {
+ .name = "mif_sipc4",
+ .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");