diff options
author | Ziyan <jaraidaniel@gmail.com> | 2016-03-26 01:40:29 +0100 |
---|---|---|
committer | Andreas Blaesius <skate4life@gmx.de> | 2016-04-30 19:33:45 +0200 |
commit | e4bbc6d694824a359691f3cfbe428a93fc8d99fa (patch) | |
tree | 7d245475c40636833a381a78d826dd6ffd5ad3cf /drivers/misc | |
parent | 323dec6a9b47e758bb854dd307df283c5b1df148 (diff) | |
download | kernel_samsung_espresso10-e4bbc6d694824a359691f3cfbe428a93fc8d99fa.zip kernel_samsung_espresso10-e4bbc6d694824a359691f3cfbe428a93fc8d99fa.tar.gz kernel_samsung_espresso10-e4bbc6d694824a359691f3cfbe428a93fc8d99fa.tar.bz2 |
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')
-rw-r--r-- | drivers/misc/Kconfig | 1 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/Kconfig | 16 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/Makefile | 10 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/modem_link_device_mipi.c | 1789 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/modem_link_device_mipi.h | 182 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/modem_modemctl_device_xmm6262.c | 255 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/modem_net_flowcontrol_device.c | 115 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/modem_prj.h | 353 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/modem_utils.c | 301 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/modem_utils.h | 188 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/modem_variation.h | 81 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/sipc4_io_device.c | 1193 | ||||
-rw-r--r-- | drivers/misc/modem_if_v2/sipc4_modem.c | 321 |
14 files changed, 4806 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index a8dd0b4..4ef4399 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -561,5 +561,6 @@ source "drivers/misc/gcx/gccore/Kconfig" source "drivers/misc/gcx/gcioctl/Kconfig" source "drivers/misc/gcx/gcbv/Kconfig" source "drivers/misc/cache-2dmanager/Kconfig" +source "drivers/misc/modem_if_v2/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index cb238f5..5fa94f4 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -58,3 +58,4 @@ obj-$(CONFIG_GCBV) += gcx/gcbv/ obj-$(CONFIG_GCIOCTL) += gcx/gcioctl/ obj-$(CONFIG_CACHE2DMANAGER) += cache-2dmanager/ obj-$(CONFIG_UID_CPUTIME) += uid_cputime.o +obj-$(CONFIG_SEC_MODEM_V2) += modem_if_v2/ diff --git a/drivers/misc/modem_if_v2/Kconfig b/drivers/misc/modem_if_v2/Kconfig new file mode 100644 index 0000000..0a10756 --- /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 0000000..274c17a --- /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 0000000..3a619d7 --- /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, ¶m); + 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, ¶m); + 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 0000000..831dc67 --- /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 0000000..e3a2a7d --- /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 0000000..54b9496 --- /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 0000000..90711fe --- /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 0000000..64fd9e3 --- /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 0000000..9bf9a86 --- /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 0000000..6ae56d2 --- /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 0000000..42feaac --- /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 0000000..5c39439 --- /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"); |