diff options
| author | wonhee <wonhee48.seo@samsung.com> | 2011-06-18 03:39:23 +0900 |
|---|---|---|
| committer | Benoit Goby <benoit@android.com> | 2011-06-21 00:14:10 -0700 |
| commit | 5da9e51ab161246785f52ec6758d9e21c2191d21 (patch) | |
| tree | 030ec328a6625f883ea986343228a4c73f02c2bc | |
| parent | 744c1561fc028be58230147cc8543e2a694bdbce (diff) | |
| download | kernel_samsung_tuna-5da9e51ab161246785f52ec6758d9e21c2191d21.tar.gz kernel_samsung_tuna-5da9e51ab161246785f52ec6758d9e21c2191d21.tar.bz2 kernel_samsung_tuna-5da9e51ab161246785f52ec6758d9e21c2191d21.zip | |
misc: modem_if: Add modem interface driver
1. Update modem interface driver based on TI HSI patch.
- TI HSI patch
: Change I01715849: omap4: hsi driver initial commit
: Change I14d257ae: ARM: omap: tuna: add HSI support
: Change Icebb37db: omap4: enable hsi hwmod
: Change Iec8eef73: tuna: defcoinfig: enable HSI
2. Checked Modem BootUp and MIPI communication working.
Change-Id: I75a2d960f3a66004fa81743fae931f04bd4f0c07
Signed-off-by: wonhee <wonhee48.seo@samsung.com>
| -rw-r--r-- | drivers/misc/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/misc/Makefile | 1 | ||||
| -rwxr-xr-x | drivers/misc/modem_if/Kconfig | 40 | ||||
| -rwxr-xr-x | drivers/misc/modem_if/Makefile | 8 | ||||
| -rwxr-xr-x | drivers/misc/modem_if/modem.c | 219 | ||||
| -rwxr-xr-x | drivers/misc/modem_if/modem_io_device.c | 758 | ||||
| -rwxr-xr-x | drivers/misc/modem_if/modem_link_device_mipi.c | 1293 | ||||
| -rwxr-xr-x | drivers/misc/modem_if/modem_link_device_mipi.h | 157 | ||||
| -rwxr-xr-x | drivers/misc/modem_if/modem_modemctl_device_xmm6260.c | 195 | ||||
| -rwxr-xr-x | drivers/misc/modem_if/modem_prj.h | 202 | ||||
| -rwxr-xr-x | drivers/misc/modem_if/modem_variation.h | 118 | ||||
| -rwxr-xr-x | include/linux/platform_data/modem.h | 90 |
12 files changed, 3082 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 0c460ac3939..340261670b8 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -544,5 +544,6 @@ source "drivers/misc/iwmc3200top/Kconfig" source "drivers/misc/ti-st/Kconfig" source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" +source "drivers/misc/modem_if/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index cbc540c5c03..00bac0c75c3 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -53,3 +53,4 @@ obj-$(CONFIG_WL127X_RFKILL) += wl127x-rfkill.o obj-$(CONFIG_APANIC) += apanic.o obj-$(CONFIG_SENSORS_AK8975) += akm8975.o obj-y += inv_mpu/ +obj-$(CONFIG_SEC_MODEM) += modem_if/ diff --git a/drivers/misc/modem_if/Kconfig b/drivers/misc/modem_if/Kconfig new file mode 100755 index 00000000000..223a2750455 --- /dev/null +++ b/drivers/misc/modem_if/Kconfig @@ -0,0 +1,40 @@ +menuconfig SEC_MODEM + bool "Modem Interface Driver" + default n + ---help--- + Samsung Modem Interface Driver. + +config UMTS_LINK_MIPI + bool "modem driver link device MIPI-HSI" + depends on SEC_MODEM + default n + +config UMTS_LINK_HSIC + bool "modem driver link device HSIC" + depends on SEC_MODEM + default n + +config UMTS_MODEM_XMM6260 + bool "modem chip : IMC xmm6260" + depends on SEC_MODEM + default n + +config CDMA_LINK_DPRAM + bool "modem driver link device DPRAM" + depends on SEC_MODEM + default n + +config CDMA_MODEM_CBP71 + bool "modem chip : cbp71" + depends on SEC_MODEM + default n + +config LTE_LINK_USB + bool "modem driver link device US" + depends on SEC_MODEM + default n + +config LTE_MODEM_CMC221 + bool "modem chip : cmc221" + depends on SEC_MODEM + default n diff --git a/drivers/misc/modem_if/Makefile b/drivers/misc/modem_if/Makefile new file mode 100755 index 00000000000..3baca23bc4c --- /dev/null +++ b/drivers/misc/modem_if/Makefile @@ -0,0 +1,8 @@ +obj-y += modem.o modem_io_device.o +obj-$(CONFIG_UMTS_MODEM_XMM6260) += modem_modemctl_device_xmm6260.o +obj-$(CONFIG_UMTS_LINK_MIPI) += modem_link_device_mipi.o +obj-$(CONFIG_UMTS_LINK_HSIC) += modem_link_device_hsic.o +obj-$(CONFIG_CDMA_MODEM_CBP71) += modem_modemctl_device_cbp71.o +obj-$(CONFIG_CDMA_LINK_DPRAM) += modem_link_device_dpram.o +obj-$(CONFIG_LTE_MODEM_CMC221) += modem_modemctl_device_cmc221.o lte_modem_bootloader.o +obj-$(CONFIG_LTE_LINK_USB) += modem_link_device_usb.o
\ No newline at end of file diff --git a/drivers/misc/modem_if/modem.c b/drivers/misc/modem_if/modem.c new file mode 100755 index 00000000000..20eb5e23e59 --- /dev/null +++ b/drivers/misc/modem_if/modem.c @@ -0,0 +1,219 @@ +/* linux/drivers/modem/modem.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> + +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/wakelock.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_variation.h" + + +static struct modem_ctl *create_modemctl_device(struct platform_device *pdev) +{ + int ret = 0; + struct modem_data *pdata; + struct modem_ctl *modemctl; + struct device *dev = &pdev->dev; + + /* create modem control device */ + modemctl = kzalloc(sizeof(struct modem_ctl), GFP_KERNEL); + if (!modemctl) + return NULL; + + modemctl->dev = dev; + modemctl->phone_state = STATE_OFFLINE; + + /* get platform data */ + pdata = pdev->dev.platform_data; + + modemctl->name = pdata->name; + + modemctl->gpio_cp_on = pdata->gpio_cp_on; + modemctl->gpio_reset_req_n = pdata->gpio_reset_req_n; + modemctl->gpio_cp_reset = pdata->gpio_cp_reset; + modemctl->gpio_pda_active = pdata->gpio_pda_active; + modemctl->gpio_phone_active = pdata->gpio_phone_active; + modemctl->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + modemctl->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + modemctl->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + + modemctl->irq_phone_active = platform_get_irq(pdev, 0); + +#ifdef CONFIG_LTE_MODEM_CMC221 + modemctl->gpio_cp_off = pdata->gpio_cp_off; + modemctl->gpio_slave_wakeup = pdata->gpio_slave_wakeup; + modemctl->gpio_host_active = pdata->gpio_host_active; + modemctl->gpio_host_wakeup = pdata->gpio_host_wakeup; + modemctl->irq_host_wakeup = platform_get_irq(pdev, 1); +#endif + /* init modemctl device for getting modemctl operations */ + ret = call_modem_init_func(modemctl, pdata); + if (ret) { + kfree(modemctl); + return NULL; + } + + pr_info("[MODEM_IF] %s:create_modemctl_device DONE\n", modemctl->name); + return modemctl; +} + +static struct io_device *create_io_device(struct modem_io_t *io_t, + struct modem_ctl *modemctl) +{ + int ret = 0; + struct io_device *iod = NULL; + + iod = kzalloc(sizeof(struct io_device), GFP_KERNEL); + if (!iod) { + pr_err("[MODEM_IF] io device memory alloc fail\n"); + return NULL; + } + + iod->name = io_t->name; + iod->id = io_t->id; + iod->format = io_t->format; + iod->io_typ = io_t->io_type; + + /* link between io device and modem control */ + iod->mc = modemctl; + modemctl->iod = iod; + + /* register misc device or net device */ + ret = init_io_device(iod); + if (ret) { + kfree(iod); + return NULL; + } + + pr_info("[MODEM_IF] %s : create_io_device DONE\n", io_t->name); + return iod; +} + +static int __devinit modem_probe(struct platform_device *pdev) +{ + int i; + struct modem_data *pdata; + struct modem_ctl *modemctl; + struct io_device *iod[MAX_NUM_IO_DEV]; + struct link_device *ld; + struct io_raw_devices *io_raw_devs = NULL; + + pdata = pdev->dev.platform_data; + + modemctl = create_modemctl_device(pdev); + if (!modemctl) + return -ENOMEM; + + /* create link device */ + ld = call_link_init_func(pdev, pdata->link_type); + if (!ld) + goto err_free_modemctl; + + io_raw_devs = kzalloc(sizeof(struct io_raw_devices), GFP_KERNEL); + if (!io_raw_devs) + return -ENOMEM; + + /* create io deivces and connect to modemctl device */ + for (i = 0; i < pdata->num_iodevs; i++) { + iod[i] = create_io_device(&pdata->iodevs[i], modemctl); + if (!iod[i]) + goto err_free_modemctl; + + if (iod[i]->format == IPC_RAW) { + int ch = iod[i]->id & 0x1F; + io_raw_devs->raw_devices[ch] = iod[i]; + io_raw_devs->num_of_raw_devs++; + iod[i]->link = ld; + } else { + /* connect io devices to one link device */ + ld->attach(ld, iod[i]); + } + + if (iod[i]->format == IPC_MULTI_RAW) + iod[i]->private_data = (void *)io_raw_devs; + } + + platform_set_drvdata(pdev, modemctl); + + pr_info("[MODEM_IF] modem_probe DONE\n"); + return 0; + +err_free_modemctl: + for (i = 0; i < pdata->num_iodevs; i++) + if (iod[i] != NULL) + kfree(iod[i]); + + if (io_raw_devs != NULL) + kfree(io_raw_devs); + + if (modemctl != NULL) + kfree(modemctl); + + return -ENOMEM; +} + +static int modem_suspend(struct device *pdev) +{ + struct modem_ctl *mc = dev_get_drvdata(pdev); + gpio_set_value(mc->gpio_pda_active, 0); + return 0; +} + +static int modem_resume(struct device *pdev) +{ + struct modem_ctl *mc = dev_get_drvdata(pdev); + gpio_set_value(mc->gpio_pda_active, 1); + return 0; +} + +static const struct dev_pm_ops modem_pm_ops = { + .suspend = modem_suspend, + .resume = modem_resume, +}; + +static struct platform_driver modem_driver = { + .probe = modem_probe, + .driver = { + .name = "modem_if", + .pm = &modem_pm_ops, + }, +}; + +static int __init modem_init(void) +{ + return platform_driver_register(&modem_driver); +} + +module_init(modem_init); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung Modem Interface Driver"); diff --git a/drivers/misc/modem_if/modem_io_device.c b/drivers/misc/modem_if/modem_io_device.c new file mode 100755 index 00000000000..05bbb47c836 --- /dev/null +++ b/drivers/misc/modem_if/modem_io_device.c @@ -0,0 +1,758 @@ +/* /linux/drivers/misc/modem_if/modem_io_device.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" + + +#define HDLC_START 0x7F +#define HDLC_END 0x7E +#define SIZE_OF_HDLC_START 1 +#define SIZE_OF_HDLC_END 1 +#define MAX_RXDATA_SIZE 0x1000 /* 4*1024 */ + +static const char hdlc_start[1] = { HDLC_START }; +static const char hdlc_end[1] = { HDLC_END }; + +struct fmt_hdr { + u16 len; + u8 control; +} __attribute__ ((packed)); + +struct raw_hdr { + u32 len; + u8 channel; + u8 control; +} __attribute__ ((packed)); + +struct rfs_hdr { + u32 len; + u8 cmd; + u8 id; +} __attribute__ ((packed)); + +static int rx_iodev_skb(struct io_device *iod); + +static int get_header_size(struct io_device *iod) +{ + switch (iod->format) { + case IPC_FMT: + return sizeof(struct fmt_hdr); + + case IPC_RAW: + case IPC_MULTI_RAW: + return sizeof(struct raw_hdr); + + case IPC_RFS: + return sizeof(struct rfs_hdr); + + case IPC_BOOT: + /* minimum size for transaction align */ + return 4; + + default: + return 0; + } +} + +static int get_hdlc_size(struct io_device *iod, char *buf) +{ + struct fmt_hdr *fmt_header; + struct raw_hdr *raw_header; + struct rfs_hdr *rfs_header; + + pr_debug("[MODEM_IF] buf : %02x %02x %02x (%d)\n", *buf, *(buf + 1), + *(buf + 2), __LINE__); + + switch (iod->format) { + case IPC_FMT: + fmt_header = (struct fmt_hdr *)buf; + return fmt_header->len; + case IPC_RAW: + case IPC_MULTI_RAW: + raw_header = (struct raw_hdr *)buf; + return raw_header->len; + case IPC_RFS: + rfs_header = (struct rfs_hdr *)buf; + return rfs_header->len; + default: + break; + } + return 0; +} + +static void *get_header(struct io_device *iod, struct misc_data_io *misc_data, + 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 = misc_data->size + 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 = misc_data->size + sizeof(struct raw_hdr); + raw_h->channel = misc_data->id; + raw_h->control = 0; + + return (void *)frame_header_buf; + + case IPC_RFS: + rfs_h = (struct rfs_hdr *)frame_header_buf; + + rfs_h->len = misc_data->size + sizeof(struct raw_hdr); + rfs_h->cmd = misc_data->cmd; + rfs_h->id = misc_data->id; + + return (void *)frame_header_buf; + + default: + return 0; + } +} + +static int rx_hdlc_head_start_check(char *buf) +{ + if (strncmp(buf, hdlc_start, sizeof(hdlc_start))) { + pr_err("[MODEM_IF] Wrong HDLC start: 0x%x\n", *buf); + return -EBADMSG; + } + return sizeof(hdlc_start); +} + +static int rx_hdlc_tail_check(char *buf) +{ + if (strncmp(buf, hdlc_end, sizeof(hdlc_end))) { + pr_err("[MODEM_IF] Wrong HDLC end: 0x%x\n", *buf); + return -EBADMSG; + } + return sizeof(hdlc_end); +} + +/* remove hdlc header and store IPC header */ +static int rx_hdlc_head_check(struct io_device *iod, char *buf, unsigned rest) +{ + struct header_data *hdr = &iod->h_data; + int head_size = get_header_size(iod); + int done_len = 0; + int len = 0; + + /* first frame, remove start header 7F */ + if (!hdr->start) { + len = rx_hdlc_head_start_check(buf); + if (len < 0) + return len; /*Wrong hdlc start*/ + + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, + rest, __LINE__); + + memcpy(&hdr->start, hdlc_start, len); + hdr->len = 0; + + /* debug print */ + switch (iod->format) { + case IPC_FMT: + case IPC_RAW: + case IPC_MULTI_RAW: + case IPC_RFS: + /* TODO: print buf... */ + break; + + case IPC_CMD: + case IPC_BOOT: + default: + break; + } + buf += len; + done_len += len; + rest -= len; /* rest, call by value */ + } + + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + + /* store the IPC header to iod priv */ + if (hdr->len < head_size) { + len = min(rest, head_size - hdr->len); + memcpy(hdr->hdr + hdr->len, buf, len); + hdr->len += len; + done_len += len; + } + + pr_debug("[MODEM_IF] check done_len : %d, rest : %d (%d)\n", done_len, + rest, __LINE__); + return done_len; +} + +/* alloc skb and copy dat to skb */ +static int rx_hdlc_data_check(struct io_device *iod, char *buf, unsigned rest) +{ + struct header_data *hdr = &iod->h_data; + struct sk_buff *skb = iod->skb_recv; + int head_size = get_header_size(iod); + int data_size = get_hdlc_size(iod, hdr->hdr) - head_size; + int alloc_size = min(data_size, MAX_RXDATA_SIZE); + int len; + int done_len = 0; + int rest_len = data_size - hdr->flag_len; + struct sk_buff *skb_new; + + pr_debug("[MODEM_IF] head_size : %d, data_size : %d (%d)\n", head_size, + data_size, __LINE__); + + /* first payload data - alloc skb */ + if (!skb) { + switch (iod->format) { + case IPC_RFS: + alloc_size = min(data_size, (int)rest) + head_size; + alloc_size = min(alloc_size, MAX_RXDATA_SIZE); + skb = alloc_skb(alloc_size, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + /* copy the RFS haeder to skb->data */ + memcpy(skb_put(skb, head_size), hdr->hdr, head_size); + break; + default: + skb = alloc_skb(alloc_size, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + break; + } + iod->skb_recv = skb; + } + + /* if recv packet size is larger than user space */ + while (rest_len > MAX_RXDATA_SIZE) { + len = MAX_RXDATA_SIZE - skb->len; + memcpy(skb_put(skb, len), buf, len); + buf += len; + done_len += len; + rest -= len; + rest_len -= len; + rx_iodev_skb(iod); + + alloc_size = min(rest_len, MAX_RXDATA_SIZE); + skb_new = alloc_skb(alloc_size, GFP_ATOMIC); + if (unlikely(!skb_new)) + return -ENOMEM; + skb = iod->skb_recv = skb_new; + } + + /* copy data to skb */ + len = min(rest, alloc_size - skb->len); + pr_debug("[MODEM_IF] rest : %d, alloc_size : %d , len : %d (%d)\n", + rest, alloc_size, skb->len, __LINE__); + + memcpy(skb_put(skb, len), buf, len); + done_len += len; + hdr->flag_len += done_len; + + return done_len; +} + +static int rx_iodev_skb_raw(struct io_device *iod) +{ + int err; + struct sk_buff *skb = iod->skb_recv; + struct net_device *ndev; + + switch (iod->io_typ) { + case IODEV_MISC: + skb_queue_tail(&iod->sk_rx_q, iod->skb_recv); + wake_up(&iod->wq); + return 0; + + case IODEV_NET: + ndev = iod->ndev; + if (!ndev) + return NET_RX_DROP; + + skb->dev = ndev; + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += skb->len; + skb->protocol = htons(ETH_P_IP); + + err = netif_rx(skb); + if (err != NET_RX_SUCCESS) + dev_err(&ndev->dev, "rx error: %d\n", err); + return err; + + default: + pr_err("[MODEM_IF] wrong io_type : %d\n", iod->io_typ); + return -EINVAL; + } +} + +static int rx_multipdp(struct io_device *iod) +{ + int ret; + u8 ch; + struct raw_hdr *raw_header = (struct raw_hdr *)&iod->h_data.hdr; + struct io_raw_devices *io_raw_devs = + (struct io_raw_devices *)iod->private_data; + struct io_device *real_iod; + + ch = raw_header->channel; + real_iod = io_raw_devs->raw_devices[ch]; + + real_iod->skb_recv = iod->skb_recv; + ret = rx_iodev_skb_raw(real_iod); + if (ret < 0) + pr_err("[MODEM_IF] failed!\n"); + return ret; +} + +/* de-mux function draft */ +static int rx_iodev_skb(struct io_device *iod) +{ + switch (iod->format) { + case IPC_MULTI_RAW: + return rx_multipdp(iod); + + case IPC_FMT: + case IPC_RFS: + default: + skb_queue_tail(&iod->sk_rx_q, iod->skb_recv); + + pr_debug("[MODEM_IF] wake up fmt,rfs skb\n"); + wake_up(&iod->wq); + return 0; + } +} + +static int rx_hdlc_packet(struct io_device *iod, const char *data, + unsigned recv_size) +{ + unsigned rest = recv_size; + char *buf = (char *)data; + int err; + int len; + + if (rest <= 0) + goto exit; + + pr_info("[MODEM_IF] RX_SIZE=%d\n", rest); + +next_frame: + err = len = rx_hdlc_head_check(iod, buf, rest); + if (err < 0) + goto exit; /* buf++; rest--; goto next_frame; */ + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + + buf += len; + rest -= len; + if (rest <= 0) + goto exit; + + err = len = rx_hdlc_data_check(iod, buf, rest); + if (err < 0) + goto exit; + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + + buf += len; + rest -= len; + if (rest <= 0) + goto exit; + + err = len = rx_hdlc_tail_check(buf); + if (err < 0) + goto exit; + pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + + buf += len; + rest -= len; + if (rest < 0) + goto exit; + + err = rx_iodev_skb(iod); + if (err < 0) + goto exit; + + /* initialize header & skb */ + iod->skb_recv = NULL; + memset(&iod->h_data, 0x00, sizeof(struct header_data)); + + if (rest) + goto next_frame; + +exit: + /* free buffers. mipi-hsi re-use recv buf */ + + if (rest < 0) + err = -ERANGE; + + if (err < 0 && iod->skb_recv) { + dev_kfree_skb_any(iod->skb_recv); + iod->skb_recv = NULL; + } + /* clear headers */ + memset(&iod->h_data, 0x00, sizeof(struct header_data)); + return err; +} + +/* called from link device when a packet arrives for this io device */ +static int io_dev_recv_data_from_link_dev(struct io_device *iod, + const char *data, unsigned int len) +{ + struct sk_buff *skb; + int err; + + switch (iod->format) { + case IPC_FMT: + case IPC_RAW: + case IPC_RFS: + case IPC_MULTI_RAW: + err = rx_hdlc_packet(iod, data, len); + if (err < 0) + pr_err("[MODEM_IF] fail process hdlc fram\n"); + return err; + + case IPC_CMD: + /* TODO- handle flow control command from CP */ + return 0; + + case IPC_BOOT: + /* save packet to sk_buff */ + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + pr_err("[MODEM_IF] fail alloc skb (%d)\n", __LINE__); + return -ENOMEM; + } + + pr_debug("[MODEM_IF] boot len : %d\n", len); + + memcpy(skb_put(skb, len), data, len); + skb_queue_tail(&iod->sk_rx_q, skb); + pr_debug("[MODEM_IF] skb len : %d\n", skb->len); + + wake_up(&iod->wq); + return len; + + default: + return -EINVAL; + } +} + +/* inform the IO device that the modem is now online or offline or + * crashing or whatever... + */ +static void io_dev_modem_state_changed(struct io_device *iod, + enum modem_state state) +{ +} + +static int misc_open(struct inode *inode, struct file *filp) +{ + struct io_device *iod = to_io_device(filp->private_data); + filp->private_data = (void *)iod; + 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 + return 0; +} + +static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long _arg) +{ + void __user *arg = (void *) _arg; + struct io_device *iod = (struct io_device *)filp->private_data; + struct misc_data_io misc_data; + int frame_len = 0; + char frame_header_buf[sizeof(struct raw_hdr)]; + struct sk_buff *skb; + + pr_debug("[MODEM_IF] misc_ioctl : 0x%x\n", cmd); + + switch (cmd) { + case IOCTL_MODEM_SEND: + + /* TODO - check here flow control for only raw data */ + + if (copy_from_user(&misc_data, arg, sizeof(misc_data)) != 0) + return -EFAULT; + + frame_len = misc_data.size + SIZE_OF_HDLC_START + + get_header_size(iod) + SIZE_OF_HDLC_END; + skb = alloc_skb(frame_len, GFP_ATOMIC); + if (!skb) { + pr_err("[MODEM_IF] fail alloc skb (%d)\n", __LINE__); + return -ENOMEM; + } + + switch (iod->format) { + case IPC_BOOT: + if (copy_from_user(skb_put(skb, misc_data.size), + misc_data.data, misc_data.size) != 0) + 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, misc_data.size), + misc_data.data, misc_data.size) != 0) + return -EFAULT; + memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end, + SIZE_OF_HDLC_END); + break; + + default: + memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start, + SIZE_OF_HDLC_START); + memcpy(skb_put(skb, get_header_size(iod)), + get_header(iod, &misc_data, frame_header_buf), + get_header_size(iod)); + if (copy_from_user(skb_put(skb, misc_data.size), + misc_data.data, misc_data.size) != 0) + return -EFAULT; + memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end, + SIZE_OF_HDLC_END); + break; + } + + /* send data with sk_buff, link device will put sk_buff + * into the specific sk_buff_q and run work-q to send data + */ + return iod->link->send(iod->link, iod, skb); + + case IOCTL_MODEM_RECV: + if (copy_from_user(&misc_data, arg, sizeof(misc_data)) != 0) + return -EFAULT; + + skb = skb_dequeue(&iod->sk_rx_q); + if (!skb) { + pr_info("[MODEM_IF] no data from sk_rx_q\n"); + return 0; + } + + if (skb->len > MAX_RXDATA_SIZE) { + pr_err("[MODEM_IF] skb len is too big = %d!(%d)", + MAX_RXDATA_SIZE, __LINE__); + dev_kfree_skb_any(skb); + return -EFAULT; + } + misc_data.size = skb->len; + pr_debug("[MODEM_IF] skb len : %d\n", skb->len); + + if (copy_to_user(misc_data.data, skb->data, skb->len) != 0) + return -EFAULT; + dev_kfree_skb_any(skb); + + if (copy_to_user(arg, &misc_data, sizeof(misc_data)) != 0) + return -EFAULT; + + pr_debug("[MODEM_IF] copy to user : %d\n", misc_data.size); + + return misc_data.size; + + case IOCTL_MODEM_ON: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_ON\n"); + return iod->mc->ops.modem_on(iod->mc); + + case IOCTL_MODEM_OFF: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_OFF\n"); + return iod->mc->ops.modem_off(iod->mc); + + case IOCTL_MODEM_RESET: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_RESET\n"); + return iod->mc->ops.modem_reset(iod->mc); + + case IOCTL_MODEM_BOOT_ON: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_BOOT_ON\n"); + return iod->mc->ops.modem_boot_on(iod->mc); + + case IOCTL_MODEM_BOOT_OFF: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_BOOT_OFF\n"); + return iod->mc->ops.modem_boot_off(iod->mc); + + case IOCTL_MODEM_START: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_START\n"); + return iod->link->init_comm(iod->link, iod); + + case IOCTL_MODEM_STATUS: + pr_info("[MODEM_IF] misc_ioctl : IOCTL_MODEM_START\n"); + return iod->mc->phone_state; + + default: + return -EINVAL; + } +} + +static const struct file_operations misc_io_fops = { + .owner = THIS_MODULE, + .open = misc_open, + .poll = misc_poll, + .unlocked_ioctl = misc_ioctl, +}; + +static int vnet_open(struct net_device *ndev) +{ + netif_start_queue(ndev); + return 0; +} + +static int vnet_stop(struct net_device *ndev) +{ + netif_stop_queue(ndev); + return 0; +} + +static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + int ret; + struct raw_hdr hd; + struct sk_buff *skb_new; + struct vnet *vnet = netdev_priv(ndev); + struct io_device *iod = vnet->iod; + + hd.len = skb->len + sizeof(hd); + hd.control = 0; + hd.channel = iod->id & 0x1F; + + skb_new = skb_copy_expand(skb, sizeof(hd) + sizeof(hdlc_start), + sizeof(hdlc_end), GFP_ATOMIC); + if (!skb_new) { + dev_kfree_skb_any(skb); + return -ENOMEM; + } + + memcpy(skb_push(skb_new, sizeof(hd)), &hd, sizeof(hd)); + memcpy(skb_push(skb_new, sizeof(hdlc_start)), hdlc_start, + sizeof(hdlc_start)); + memcpy(skb_put(skb_new, sizeof(hdlc_end)), hdlc_end, sizeof(hdlc_end)); + + ret = iod->link->send(iod->link, iod, skb_new); + if (ret < 0) { + netif_stop_queue(ndev); + dev_kfree_skb_any(skb); + return NETDEV_TX_BUSY; + } + + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += skb->len; + dev_kfree_skb_any(skb); + + return NETDEV_TX_OK; +} + +static struct net_device_ops vnet_ops = { + .ndo_open = vnet_open, + .ndo_stop = vnet_stop, + .ndo_start_xmit = vnet_xmit, +}; + +static void vnet_setup(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_PPP; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + ndev->hard_header_len = 0; + ndev->addr_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +int init_io_device(struct io_device *iod) +{ + int ret = 0; + struct vnet *vnet; + + /* get modem state from modem control device */ + iod->modem_state_changed = io_dev_modem_state_changed; + /* get data from link device */ + iod->recv = io_dev_recv_data_from_link_dev; + + INIT_LIST_HEAD(&iod->list); + + /* register misc or net drv */ + switch (iod->io_typ) { + case IODEV_MISC: + init_waitqueue_head(&iod->wq); + skb_queue_head_init(&iod->sk_rx_q); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + + ret = misc_register(&iod->miscdev); + if (ret) + pr_err("failed to register misc io device : %s\n", + iod->name); + + break; + + case IODEV_NET: + iod->ndev = alloc_netdev(0, iod->name, vnet_setup); + if (!iod->ndev) { + pr_err("failed to alloc netdev\n"); + return -ENOMEM; + } + + ret = register_netdev(iod->ndev); + if (ret) + free_netdev(iod->ndev); + + pr_err("%s: %d(iod:0x%p)", __func__, __LINE__, iod); + vnet = netdev_priv(iod->ndev); + pr_err("%s: %d(vnet:0x%p)", __func__, __LINE__, vnet); + vnet->iod = iod; + + break; + + case IODEV_DUMMY: + break; + + default: + pr_err("wrong io_type : %d\n", iod->io_typ); + return -EINVAL; + } + + pr_info("[MODEM_IF] %s(%d) : init_io_device() done : %d\n", + iod->name, iod->io_typ, ret); + return ret; +} + diff --git a/drivers/misc/modem_if/modem_link_device_mipi.c b/drivers/misc/modem_if/modem_link_device_mipi.c new file mode 100755 index 00000000000..84ab0c3d521 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_mipi.c @@ -0,0 +1,1293 @@ +/* /linux/drivers/new_modem_if/link_dev_mipi.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/irq.h> +#include <linux/poll.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> + +#include <linux/hsi_driver_if.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_mipi.h" + + +static int mipi_hsi_attach_io_dev(struct link_device *ld, + struct io_device *iod) +{ + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + iod->link = ld; + + /* list up io devices */ + list_add_tail(&iod->list, &mipi_ld->list_of_io_devices); + return 0; +} + +static int mipi_hsi_init_communication(struct link_device *ld, + struct io_device *iod) +{ + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + switch (iod->format) { + case IPC_FMT: + return hsi_init_handshake(mipi_ld, HSI_INIT_MODE_NORMAL); + + case IPC_BOOT: + return hsi_init_handshake(mipi_ld, + HSI_INIT_MODE_FLASHLESS_BOOT); + + case IPC_RFS: + case IPC_RAW: + default: + return 0; + } +} + +static int mipi_hsi_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + int ret; + u32 mipi_boot_len = 0; + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + struct sk_buff_head *txq; + + switch (iod->format) { + case IPC_RAW: + txq = &ld->sk_raw_tx_q; + break; + + case IPC_BOOT: /* sync operation */ + ret = if_hsi_write(&mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL], + (u32 *)skb->data, skb->len); + if (ret < 0) { + pr_err("[MIPI-HSI] write fail : %d\n", ret); + dev_kfree_skb_any(skb); + return ret; + } else + pr_debug("[MIPI-HSI] write Done\n"); + dev_kfree_skb_any(skb); + + ret = if_hsi_read(&mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL], + (const char *)&mipi_boot_len, 4); + if (ret < 0) { + pr_err("[MIPI-HSI] read len fail : %d\n", ret); + return ret; + } else + pr_debug("[MIPI-HSI] read len Done : %d\n", + mipi_boot_len); + + ret = if_hsi_read(&mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL], + (const char *)mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL].rx_data, + mipi_boot_len); + if (ret < 0) { + pr_err("[MIPI-HSI] read len fail : %d\n", ret); + return ret; + } else + pr_debug("[MIPI-HSI] read len Done\n"); + + ret = iod->recv(iod, (char *)mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL].rx_data, mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL].rx_count); + if (ret < 0) { + pr_err("[MIPI-HSI] iod recv fail : %d\n", ret); + return ret; + } else + pr_debug("[MIPI-HSI] iod recv Done : %d\n", ret); + + return mipi_boot_len; + + case IPC_FMT: + case IPC_RFS: + default: + txq = &ld->sk_fmt_tx_q; + break; + } + + /* save io device into cb area */ + *((struct io_device **)skb->cb) = iod; + /* en queue skb data */ + skb_queue_tail(txq, skb); + + if (!work_pending(&ld->tx_work)) + queue_work(ld->tx_wq, &ld->tx_work); + + return skb->len; +} + +static void mipi_hsi_tx_work(struct work_struct *work) +{ + int ret; + struct link_device *ld = container_of(work, struct link_device, + tx_work); + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + struct io_device *iod; + struct sk_buff *fmt_skb; + struct sk_buff *raw_skb; + int send_channel = 0; + + while (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) { + pr_debug("[MIPI-HSI] fmt qlen : %d, raw qlen:%d\n", + ld->sk_fmt_tx_q.qlen, ld->sk_raw_tx_q.qlen); + + fmt_skb = skb_dequeue(&ld->sk_fmt_tx_q); + if (fmt_skb) { + iod = *((struct io_device **)fmt_skb->cb); + + pr_debug("[MIPI-HSI] dequeue. fmt qlen : %d\n", + ld->sk_fmt_tx_q.qlen); + + if (ld->com_state != COM_ONLINE) { + pr_info("[MIPI-HSI] CP not ready\n"); + skb_queue_head(&ld->sk_fmt_tx_q, fmt_skb); + return; + } + + switch (iod->format) { + case IPC_FMT: + send_channel = HSI_FMT_CHANNEL; + break; + + case IPC_RFS: + send_channel = HSI_RFS_CHANNEL; + break; + + case IPC_BOOT: + send_channel = HSI_FLASHLESS_CHANNEL; + break; + default: + break; + } + ret = if_hsi_protocol_send(mipi_ld, send_channel, + (u32 *)fmt_skb->data, fmt_skb->len); + if (ret < 0) { + /* TODO: Re Enqueue */ + pr_err("[MIPI-HSI] write fail : %d\n", ret); + } else + pr_debug("[MIPI-HSI] write Done\n"); + + dev_kfree_skb_any(fmt_skb); + } + + raw_skb = skb_dequeue(&ld->sk_raw_tx_q); + if (raw_skb) { + if (ld->com_state != COM_ONLINE) { + pr_info("[MIPI-HSI] RAW CP not ready\n"); + skb_queue_head(&ld->sk_raw_tx_q, raw_skb); + return; + } + + pr_debug("[MIPI-HSI] dequeue. raw qlen:%d\n", + ld->sk_raw_tx_q.qlen); + + ret = if_hsi_protocol_send(mipi_ld, HSI_RAW_CHANNEL, + (u32 *)raw_skb->data, raw_skb->len); + if (ret < 0) { + /* TODO: Re Enqueue */ + pr_err("[MIPI-HSI] write fail : %d\n", ret); + } else + pr_debug("[MIPI-HSI] write Done\n"); + + dev_kfree_skb_any(raw_skb); + } + } +} + +static int __devinit if_hsi_probe(struct hsi_device *dev); +static struct hsi_device_driver if_hsi_driver = { + .ctrl_mask = ANY_HSI_CONTROLLER, + .probe = if_hsi_probe, + .driver = { + .name = "if_hsi_driver" + }, +}; + +static int if_hsi_set_wakeline(struct if_hsi_channel *channel, + unsigned int state) +{ + int ret; + + ret = hsi_ioctl(channel->dev, state ? + HSI_IOCTL_ACWAKE_UP : HSI_IOCTL_ACWAKE_DOWN, NULL); + if (ret) { + pr_debug("[MIPI-HSI] ACWAKE(%d) setting fail : %d\n", state, + ret); + return ret; + } + + pr_info("[MIPI-HSI] ACWAKE_%d(%d)\n", channel->channel_id, state); + return 0; +} + +static void if_hsi_acwake_down_func(unsigned long data) +{ + int i; + struct if_hsi_channel *channel; + struct mipi_link_device *mipi_ld = (struct mipi_link_device *)data; + + pr_info("[MIPI-HSI] %s\n", __func__); + + for (i = 1; i < HSI_NUM_OF_USE_CHANNELS; i++) { + channel = &mipi_ld->hsi_channles[i]; + + if (channel->send_step == STEP_IDLE) { + if_hsi_set_wakeline(channel, 0); + } else { + mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies + + HSI_ACWAKE_DOWN_TIMEOUT); + pr_info("[MIPI-HSI] mod_timer done(%d)\n", + HSI_ACWAKE_DOWN_TIMEOUT); + return; + } + } +} + +static int if_hsi_open_channel(struct if_hsi_channel *channel) +{ + int ret; + + if (channel->opened) { + pr_debug("[MIPI-HSI] channel %d is already opened\n", + channel->channel_id); + return 0; + } + + ret = hsi_open(channel->dev); + if (ret) { + pr_err("[MIPI-HSI] hsi_open fail : %d\n", ret); + return ret; + } + channel->opened = 1; + + channel->send_step = STEP_IDLE; + channel->recv_step = STEP_IDLE; + + pr_info("[MIPI-HSI] hsi_open Done : %d\n", channel->channel_id); + return 0; +} + +static int if_hsi_close_channel(struct if_hsi_channel *channel) +{ + unsigned long int flags; + + if (!channel->opened) { + pr_debug("[MIPI-HSI] channel %d is already closed\n", + channel->channel_id); + return 0; + } + + if_hsi_set_wakeline(channel, 0); + hsi_write_cancel(channel->dev); + hsi_read_cancel(channel->dev); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + spin_lock_irqsave(&channel->rx_state_lock, flags); + channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + hsi_ioctl(channel->dev, HSI_IOCTL_SW_RESET, NULL); + + hsi_close(channel->dev); + channel->opened = 0; + + channel->send_step = STEP_CLOSED; + channel->recv_step = STEP_CLOSED; + + pr_info("[MIPI-HSI] hsi_close Done : %d\n", channel->channel_id); + return 0; +} + +static void mipi_hsi_start_work(struct work_struct *work) +{ + int ret; + u32 start_cmd = 0xC2; + struct mipi_link_device *mipi_ld = + container_of(work, struct mipi_link_device, + start_work.work); + + ret = if_hsi_protocol_send(mipi_ld, HSI_CMD_CHANNEL, &start_cmd, 1); + if (ret < 0) { + /* TODO: Re Enqueue */ + pr_err("[MIPI-HSI] First write fail : %d\n", ret); + } else { + pr_info("[MIPI-HSI] First write Done : %d\n", ret); + mipi_ld->ld.com_state = COM_ONLINE; + } +} + +static int hsi_init_handshake(struct mipi_link_device *mipi_ld, int mode) +{ + int ret; + int i; + struct hst_ctx tx_config; + struct hsr_ctx rx_config; + + switch (mode) { + case HSI_INIT_MODE_NORMAL: + if (timer_pending(&mipi_ld->hsi_acwake_down_timer)) + del_timer(&mipi_ld->hsi_acwake_down_timer); + + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) { + if_hsi_close_channel(&mipi_ld->hsi_channles[i]); + ret = if_hsi_open_channel(&mipi_ld->hsi_channles[i]); + if (ret) + return ret; + + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 0; /* Speed : 96MHz */ + tx_config.channels = HSI_MAX_CHANNELS; + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_SET_TX, &tx_config); + + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 0; /* Speed : 96MHz */ + rx_config.channels = HSI_MAX_CHANNELS; + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_SET_RX, &rx_config); + pr_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n"); + + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_SET_ACREADY_NORMAL, NULL); + pr_debug("[MIPI-HSI] ACREADY_NORMAL\n"); + } + + 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) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + schedule_delayed_work(&mipi_ld->start_work, 3 * HZ); + + pr_info("[MIPI-HSI] hsi_init_handshake Done : MODE_NORMAL\n"); + return 0; + + case HSI_INIT_MODE_FLASHLESS_BOOT: + case HSI_INIT_MODE_CP_RAMDUMP: + mipi_ld->ld.com_state = COM_BOOT; + + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) + if_hsi_close_channel(&mipi_ld->hsi_channles[i]); + ret = if_hsi_open_channel( + &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL]); + if (ret) + return ret; + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 0; /* Speed : 96MHz */ + tx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_TX, &tx_config); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 0; /* Speed : 96MHz */ + rx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_RX, &rx_config); + pr_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n"); + + if_hsi_set_wakeline( + &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL], 1); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_ACREADY_NORMAL, NULL); + pr_debug("[MIPI-HSI] ACREADY_NORMAL\n"); + + pr_info("[MIPI-HSI] hsi_init_handshake Done : FLASHLESS_BOOT\n"); + return 0; + + default: + return -EINVAL; + } +} + +static u32 if_hsi_create_cmd(u32 cmd_type, int ch, void *arg) +{ + u32 cmd = 0; + unsigned int size = 0; + + switch (cmd_type) { + case HSI_LL_MSG_BREAK: + return 0; + + case HSI_LL_MSG_CONN_CLOSED: + cmd = ((HSI_LL_MSG_CONN_CLOSED & 0x0000000F) << 28) + |((ch & 0x000000FF) << 24); + return cmd; + + case HSI_LL_MSG_ACK: + size = *(unsigned int *)arg; + + cmd = ((HSI_LL_MSG_ACK & 0x0000000F) << 28) + |((ch & 0x000000FF) << 24) | ((size & 0x00FFFFFF)); + return cmd; + + case HSI_LL_MSG_NAK: + cmd = ((HSI_LL_MSG_NAK & 0x0000000F) << 28) + |((ch & 0x000000FF) << 24); + return cmd; + + case HSI_LL_MSG_OPEN_CONN_OCTET: + size = *(unsigned int *)arg; + + cmd = ((HSI_LL_MSG_OPEN_CONN_OCTET & 0x0000000F) + << 28) | ((ch & 0x000000FF) << 24) + | ((size & 0x00FFFFFF)); + return cmd; + + case HSI_LL_MSG_OPEN_CONN: + case HSI_LL_MSG_CONF_RATE: + case HSI_LL_MSG_CANCEL_CONN: + case HSI_LL_MSG_CONN_READY: + case HSI_LL_MSG_ECHO: + case HSI_LL_MSG_INFO_REQ: + case HSI_LL_MSG_INFO: + case HSI_LL_MSG_CONFIGURE: + case HSI_LL_MSG_ALLOCATE_CH: + case HSI_LL_MSG_RELEASE_CH: + case HSI_LL_MSG_INVALID: + default: + pr_err("[MIPI-HSI] ERROR... CMD Not supported : %08x\n", + cmd_type); + return -EINVAL; + } +} + +static void if_hsi_cmd_work(struct work_struct *work) +{ + int ret; + struct mipi_link_device *mipi_ld = + container_of(work, struct mipi_link_device, cmd_work); + struct if_hsi_channel *channel = + &mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL]; + struct if_hsi_command *hsi_cmd; + + pr_debug("[MIPI-HSI] cmd_work\n"); + + if (!list_empty(&mipi_ld->list_of_hsi_cmd)) + if_hsi_set_wakeline(channel, 1); + else + return; + + do { + hsi_cmd = list_entry(mipi_ld->list_of_hsi_cmd.next, + struct if_hsi_command, list); + pr_debug("[MIPI-HSI] take command : %08x\n", hsi_cmd->command); + + ret = if_hsi_write(channel, &hsi_cmd->command, 4); + if (ret < 0) { + pr_err("[MIPI-HSI] write command fail : %d\n", ret); + if_hsi_set_wakeline(channel, 0); + return; + } + pr_info("[MIPI-HSI] SEND CMD : %08x\n", hsi_cmd->command); + + list_del(&hsi_cmd->list); + kfree(hsi_cmd); + } while (!list_empty(&mipi_ld->list_of_hsi_cmd)); + + if_hsi_set_wakeline(channel, 0); +} + +static int if_hsi_send_command(struct mipi_link_device *mipi_ld, + u32 cmd_type, int ch, u32 param) +{ + struct if_hsi_command *hsi_cmd; + + hsi_cmd = kmalloc(sizeof(struct if_hsi_command), GFP_ATOMIC); + if (!hsi_cmd) { + pr_err("[MIPI-HSI] hsi_cmd kmalloc fail\n"); + return -ENOMEM; + } + INIT_LIST_HEAD(&hsi_cmd->list); + + hsi_cmd->command = if_hsi_create_cmd(cmd_type, ch, ¶m); + pr_debug("[MIPI-HSI] made command : %08x\n", hsi_cmd->command); + + list_add_tail(&hsi_cmd->list, &mipi_ld->list_of_hsi_cmd); + + pr_debug("[MIPI-HSI] queue_work : cmd_work\n"); + if (!work_pending(&mipi_ld->cmd_work)) + queue_work(mipi_ld->mipi_wq, &mipi_ld->cmd_work); + + return 0; +} + +static int if_hsi_decode_cmd(u32 *cmd_data, u32 *cmd, u32 *ch, + u32 *param) +{ + u32 data = *cmd_data; + u8 lrc_cal, lrc_act; + u8 val1, val2, val3; + + *cmd = ((data & 0xF0000000) >> 28); + switch (*cmd) { + case HSI_LL_MSG_BREAK: + pr_err("[MIPI-HSI] Command MSG_BREAK Received\n"); + return -1; + + case HSI_LL_MSG_OPEN_CONN: + *ch = ((data & 0x0F000000) >> 24); + *param = ((data & 0x00FFFF00) >> 8); + val1 = ((data & 0xFF000000) >> 24); + val2 = ((data & 0x00FF0000) >> 16); + val3 = ((data & 0x0000FF00) >> 8); + lrc_act = (data & 0x000000FF); + lrc_cal = val1 ^ val2 ^ val3; + + if (lrc_cal != lrc_act) { + pr_err("[MIPI-HSI] CAL is broken\n"); + return -1; + } + return 0; + + case HSI_LL_MSG_CONN_READY: + case HSI_LL_MSG_CONN_CLOSED: + case HSI_LL_MSG_CANCEL_CONN: + case HSI_LL_MSG_NAK: + *ch = ((data & 0x0F000000) >> 24); + return 0; + + case HSI_LL_MSG_ACK: + *ch = ((data & 0x0F000000) >> 24); + *param = (data & 0x00FFFFFF); + return 0; + + case HSI_LL_MSG_CONF_RATE: + *ch = ((data & 0x0F000000) >> 24); + *param = ((data & 0x0F000000) >> 24); + return 0; + + case HSI_LL_MSG_OPEN_CONN_OCTET: + *ch = ((data & 0x0F000000) >> 24); + *param = (data & 0x00FFFFFF); + return 0; + + case HSI_LL_MSG_ECHO: + case HSI_LL_MSG_INFO_REQ: + case HSI_LL_MSG_INFO: + case HSI_LL_MSG_CONFIGURE: + case HSI_LL_MSG_ALLOCATE_CH: + case HSI_LL_MSG_RELEASE_CH: + case HSI_LL_MSG_INVALID: + default: + pr_err("[MIPI-HSI] Invalid command received : %08x\n", *cmd); + *cmd = HSI_LL_MSG_INVALID; + *ch = HSI_LL_INVALID_CHANNEL; + return -1; + } + return 0; +} + +static int if_hsi_rx_cmd_handle(struct mipi_link_device *mipi_ld, u32 cmd, + u32 ch, u32 param) +{ + int ret; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[ch]; + + pr_debug("[MIPI-HSI] if_hsi_rx_cmd_handle cmd=0x%x, ch=%d, param=%d\n", + cmd, ch, param); + + switch (cmd) { + case HSI_LL_MSG_OPEN_CONN_OCTET: + switch (channel->recv_step) { + case STEP_IDLE: + channel->recv_step = STEP_TO_ACK; + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_ACK, ch, + param); + if (ret) { + pr_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", + ret); + return ret; + } + + channel->packet_size = param; + channel->recv_step = STEP_RX; + if (param % 4) + param += (4 - (param % 4)); + channel->rx_count = param; + ret = hsi_read(channel->dev, channel->rx_data, + channel->rx_count / 4); + if (ret) { + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + return ret; + } + return 0; + + default: + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_NAK, ch, + param); + if (ret) { + pr_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", + ret); + return ret; + } + return 0; + } + + 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; + pr_debug("[MIPI-HSI] got ack\n"); + } else { + channel->send_step = STEP_WAIT_FOR_ACK; + channel->got_nack = 1; + pr_debug("[MIPI-HSI] got nack\n"); + } + + up(&channel->ack_done_sem); + return 0; + + default: + pr_err("[MIPI-HSI] wrong state : %08x\n", cmd); + return -1; + } + + case HSI_LL_MSG_CONN_CLOSED: + switch (channel->send_step) { + case STEP_TX: + case STEP_WAIT_FOR_CONN_CLOSED: + pr_debug("[MIPI-HSI] got close\n"); + + channel->send_step = STEP_IDLE; + up(&channel->close_conn_done_sem); + return 0; + + default: + pr_err("[MIPI-HSI] wrong state : %08x\n", cmd); + return -1; + } + + case HSI_LL_MSG_OPEN_CONN: + case HSI_LL_MSG_ECHO: + case HSI_LL_MSG_CANCEL_CONN: + case HSI_LL_MSG_CONF_RATE: + default: + pr_err("[MIPI-HSI] ERROR... CMD Not supported : %08x\n", cmd); + return -EINVAL; + } +} + +static int if_hsi_protocol_send(struct mipi_link_device *mipi_ld, int ch, + u32 *data, unsigned int len) +{ + int ret; + int retry_count = 0; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[ch]; + + if (channel->send_step != STEP_IDLE) { + pr_err("[MIPI-HSI] send step is not IDLE : %d\n", + channel->send_step); + return -EBUSY; + } + channel->send_step = STEP_SEND_OPEN_CONN; + + if_hsi_set_wakeline(channel, 1); + mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies + + HSI_ACWAKE_DOWN_TIMEOUT); + pr_info("[MIPI-HSI] mod_timer done(%d)\n", + HSI_ACWAKE_DOWN_TIMEOUT); + +retry_send: + + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_OPEN_CONN_OCTET, ch, + len); + if (ret) { + pr_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", ret); + 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) { + pr_err("[MIPI-HSI] ch=%d, ack_done timeout\n", + channel->channel_id); + + channel->send_step = STEP_IDLE; + return -ETIMEDOUT; + } + pr_debug("[MIPI-HSI] ch=%d, got ack_done=%d\n", channel->channel_id, + channel->got_nack); + + if (channel->got_nack && (retry_count < 10)) { + pr_info("[MIPI-HSI] ch=%d, got nack=%d retry=%d\n", + channel->channel_id, channel->got_nack, + retry_count); + retry_count++; + msleep(1); + goto retry_send; + } + retry_count = 0; + + channel->send_step = STEP_TX; + + ret = if_hsi_write(channel, data, len); + if (ret < 0) { + pr_err("[MIPI-HSI] if_hsi_write fail : %d\n", ret); + channel->send_step = STEP_IDLE; + return ret; + } + pr_info("[MIPI-HSI] SEND DATA : %08x(%d)\n", *data, len); + + pr_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n", + *channel->tx_data, *(channel->tx_data + 1), + *(channel->tx_data + 2), *(channel->tx_data + 3), + *(channel->tx_data + 4), *(channel->tx_data + 5), + *(channel->tx_data + 6), *(channel->tx_data + 7)); + + channel->send_step = STEP_WAIT_FOR_CONN_CLOSED; + if (down_timeout(&channel->close_conn_done_sem, + HSI_CLOSE_CONN_DONE_TIMEOUT) < 0) { + pr_err("[MIPI-HSI] ch=%d, close conn timeout\n", + channel->channel_id); + channel->send_step = STEP_IDLE; + return -ETIMEDOUT; + } + pr_debug("[MIPI-HSI] ch=%d, got close_conn_done\n", + channel->channel_id); + + channel->send_step = STEP_IDLE; + + pr_debug("[MIPI-HSI] write protocol Done : %d\n", channel->tx_count); + return channel->tx_count; +} + +static int if_hsi_write(struct if_hsi_channel *channel, u32 *data, + unsigned int size) +{ + int ret; + unsigned long int flags; + + spin_lock_irqsave(&channel->tx_state_lock, flags); + if (channel->tx_state & HSI_CHANNEL_TX_STATE_WRITING) { + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + return -EBUSY; + } + channel->tx_state |= HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + channel->tx_data = data; + if (size % 4) + size += (4 - (size % 4)); + channel->tx_count = size; + + pr_debug("[MIPI-HSI] submit write data : 0x%x(%d)\n", + *(u32 *)channel->tx_data, channel->tx_count); + ret = hsi_write(channel->dev, channel->tx_data, channel->tx_count / 4); + if (ret) { + pr_err("[MIPI-HSI] ch=%d, hsi_write fail : %d\n", + channel->channel_id, ret); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + return ret; + } + + if (down_timeout(&channel->write_done_sem, + HSI_WRITE_DONE_TIMEOUT) < 0) { + pr_err("[MIPI-HSI] ch=%d, hsi_write_done timeout : %d\n", + channel->channel_id, size); + + pr_err("[MIPI-HSI] data : %08x %08x %08x %08x %08x ...\n", + *channel->tx_data, *(channel->tx_data + 1), + *(channel->tx_data + 2), *(channel->tx_data + 3), + *(channel->tx_data + 4)); + + hsi_write_cancel(channel->dev); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + return -ETIMEDOUT; + } + + if (channel->tx_count != size) + pr_err("[MIPI-HSI] ch:%d,write_done fail,write_size:%d,origin_size:%d\n", + channel->channel_id, channel->tx_count, size); + + pr_debug("[MIPI-HSI] len:%d, id:%d, data : %08x %08x %08x %08x %08x ...\n", + channel->tx_count, channel->channel_id, *channel->tx_data, + *(channel->tx_data + 1), *(channel->tx_data + 2), + *(channel->tx_data + 3), *(channel->tx_data + 4)); + + return channel->tx_count; +} + +static void if_hsi_write_done(struct hsi_device *dev, unsigned int size) +{ + unsigned long int flags; + struct mipi_link_device *mipi_ld = + (struct mipi_link_device *)if_hsi_driver.priv_data; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[dev->n_ch]; + + pr_debug("[MIPI-HSI] got write data : 0x%x(%d)\n", + *(u32 *)channel->tx_data, size); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + pr_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n", + *channel->tx_data, *(channel->tx_data + 1), + *(channel->tx_data + 2), *(channel->tx_data + 3), + *(channel->tx_data + 4), *(channel->tx_data + 5), + *(channel->tx_data + 6), *(channel->tx_data + 7)); + + channel->tx_count = 4 * size; + up(&channel->write_done_sem); +} + +static int if_hsi_read(struct if_hsi_channel *channel, const char *data, + unsigned int size) +{ + int ret; + unsigned long int flags; + + spin_lock_irqsave(&channel->rx_state_lock, flags); + if (channel->rx_state & HSI_CHANNEL_RX_STATE_READING) { + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + return -EBUSY; + } + channel->rx_state |= HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + if (size % 4) + size += (4 - (size % 4)); + channel->rx_count = size; + + ret = hsi_read(channel->dev, (u32 *)data, channel->rx_count / 4); + if (ret) { + pr_err("[MIPI-HSI] ch=%d, hsi_read fail : %d\n", + channel->channel_id, ret); + + spin_lock_irqsave(&channel->rx_state_lock, flags); + channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + return ret; + } + + pr_debug("[MIPI-HSI] submit read data : 0x%x(%d)\n", + *(u32 *)channel->rx_data, channel->rx_count); + + if (down_timeout(&channel->read_done_sem, + HSI_READ_DONE_TIMEOUT) < 0) { + pr_err("[MIPI-HSI] ch=%d, hsi_read_done timeout : %d\n", + channel->channel_id, channel->rx_count); + + hsi_read_cancel(channel->dev); + + spin_lock_irqsave(&channel->rx_state_lock, flags); + channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + return -ETIMEDOUT; + } + + if (channel->rx_count != size) + pr_err("[MIPI-HSI] ch:%d,read_done fail,read_size:%d,origin_size:%d\n", + channel->channel_id, channel->rx_count, size); + + return channel->rx_count; +} + +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; + + pr_debug("[MIPI-HSI] got read data : 0x%x(%d)\n", + *(u32 *)channel->rx_data, size); + + spin_lock_irqsave(&channel->rx_state_lock, flags); + channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + channel->rx_count = 4 * size; + + switch (channel->channel_id) { + case HSI_CONTROL_CHANNEL: + switch (mipi_ld->ld.com_state) { + case COM_HANDSHAKE: + case COM_ONLINE: + pr_info("[MIPI-HSI] RECV CMD : %08x\n", + *channel->rx_data); + + if (channel->rx_count != 4) { + pr_err("[MIPI-HSI] wrong command len : %d\n", + channel->rx_count); + return; + } + + ret = if_hsi_decode_cmd(channel->rx_data, &cmd, &ch, + ¶m); + if (ret) + pr_err("[MIPI-HSI] decode_cmd fail=%d, cmd=%x\n", + ret, cmd); + else { + pr_debug("[MIPI-HSI] decode_cmd : %08x\n", cmd); + ret = if_hsi_rx_cmd_handle(mipi_ld, cmd, ch, + param); + if (ret) + pr_err("[MIPI-HSI] handle cmd cmd=%x\n", + cmd); + } + + ret = hsi_read(channel->dev, channel->rx_data, 1); + if (ret) + pr_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + return; + + case COM_BOOT: + pr_debug("[MIPI-HSI] receive data : 0x%x(%d)\n", + *channel->rx_data, channel->rx_count); + + up(&channel->read_done_sem); + return; + + case COM_CRASH: + case COM_NONE: + default: + pr_info("[MIPI-HSI] receive data in wrong state : 0x%x(%d)\n", + *channel->rx_data, channel->rx_count); + up(&channel->read_done_sem); + return; + } + break; + + case HSI_FMT_CHANNEL: + pr_debug("[MIPI-HSI] iodevice format : IPC_FMT\n"); + format_type = IPC_FMT; + break; + case HSI_RAW_CHANNEL: + pr_debug("[MIPI-HSI] iodevice format : IPC_MULTI_RAW\n"); + format_type = IPC_MULTI_RAW; + break; + case HSI_RFS_CHANNEL: + pr_debug("[MIPI-HSI] iodevice format : IPC_RFS\n"); + format_type = IPC_RFS; + break; + + case HSI_CMD_CHANNEL: + pr_info("[MIPI-HSI] receive command data : 0x%x\n", + *channel->rx_data); + return; + + default: + up(&channel->read_done_sem); + return; + } + + list_for_each_entry(iod, &mipi_ld->list_of_io_devices, list) { + pr_debug("[MIPI-HSI] iodevice format : %d\n", iod->format); + + if (iod->format == format_type) { + pr_info("[MIPI-HSI] RECV DATA : %08x(%d)-%d\n", + *channel->rx_data, channel->packet_size, + iod->format); + + pr_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n", + *channel->rx_data, *(channel->rx_data + 1), + *(channel->rx_data + 2), *(channel->rx_data + 3), + *(channel->rx_data + 4), *(channel->rx_data + 5), + *(channel->rx_data + 6), *(channel->rx_data + 7)); + + ret = iod->recv(iod, (char *)channel->rx_data, + channel->packet_size); + if (ret < 0) + pr_err("[MIPI-HSI] recv call fail : %d\n", ret); + + if (channel->recv_step == STEP_RX) { + ch = channel->channel_id; + param = 0; + ret = if_hsi_send_command(mipi_ld, + HSI_LL_MSG_CONN_CLOSED, ch, param); + if (ret) + pr_err("[MIPI-HSI] send_cmd fail=%d\n", + ret); + + channel->recv_step = STEP_IDLE; + } + return; + } + } +} + +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: + pr_err("[MIPI-HSI] HSI_EVENT_BREAK_DETECTED\n"); + return; + + case HSI_EVENT_HSR_DATAAVAILABLE: + pr_err("[MIPI-HSI] HSI_EVENT_HSR_DATAAVAILABLE\n"); + return; + + case HSI_EVENT_CAWAKE_UP: + if (dev->n_ch == HSI_CONTROL_CHANNEL) + pr_info("[MIPI-HSI] CAWAKE_%d(1)\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_SET_ACREADY_NORMAL, NULL); + + pr_debug("[MIPI-HSI] ACREADY_NORMAL. Ch : %d\n", + dev->n_ch); + } + + return; + + case HSI_EVENT_CAWAKE_DOWN: + if (dev->n_ch == HSI_CONTROL_CHANNEL) + pr_info("[MIPI-HSI] CAWAKE_%d(0)\n", dev->n_ch); + + if ((dev->n_ch == HSI_CONTROL_CHANNEL) && + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].opened) { + hsi_ioctl( + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev, + HSI_IOCTL_GET_ACWAKE, &acwake_level); + + pr_debug("[MIPI-HSI] GET_ACWAKE. Ch : %d, level : %d\n", + dev->n_ch, acwake_level); + } + + if ((dev->n_ch == HSI_CONTROL_CHANNEL) && + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].opened && + !acwake_level) { + hsi_ioctl( + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev, + HSI_IOCTL_SET_ACREADY_SAFEMODE, NULL); + + pr_debug("[MIPI-HSI] ACREADY_SAFEMODE. Ch : %d\n", + dev->n_ch); + } + + return; + + case HSI_EVENT_ERROR: + pr_err("[MIPI-HSI] HSI_EVENT_ERROR\n"); + return; + + default: + pr_err("[MIPI-HSI] Unknown Event : %d\n", event); + return; + } +} + +static int __devinit if_hsi_probe(struct hsi_device *dev) +{ + int port = 0; + unsigned long *address; + + struct mipi_link_device *mipi_ld = + (struct mipi_link_device *)if_hsi_driver.priv_data; + + for (port = 0; port < HSI_MAX_PORTS; port++) { + if (if_hsi_driver.ch_mask[port]) + break; + } + address = (unsigned long *)&if_hsi_driver.ch_mask[port]; + + if (test_bit(dev->n_ch, address) && (dev->n_p == port)) { + /* Register callback func */ + hsi_set_write_cb(dev, if_hsi_write_done); + hsi_set_read_cb(dev, if_hsi_read_done); + hsi_set_port_event_cb(dev, if_hsi_port_event); + + /* Init device data */ + mipi_ld->hsi_channles[dev->n_ch].dev = dev; + mipi_ld->hsi_channles[dev->n_ch].tx_count = 0; + mipi_ld->hsi_channles[dev->n_ch].rx_count = 0; + mipi_ld->hsi_channles[dev->n_ch].tx_state = 0; + mipi_ld->hsi_channles[dev->n_ch].rx_state = 0; + mipi_ld->hsi_channles[dev->n_ch].packet_size = 0; + mipi_ld->hsi_channles[dev->n_ch].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); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].write_done_sem, 0); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].read_done_sem, 0); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].ack_done_sem, 0); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].close_conn_done_sem, + 0); + } + + pr_info("[MIPI-HSI] if_hsi_probe() done. ch : %d\n", dev->n_ch); + return 0; +} + +static int if_hsi_init(struct link_device *ld) +{ + int ret; + int i = 0; + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + for (i = 0; i < HSI_MAX_PORTS; i++) + if_hsi_driver.ch_mask[i] = 0; + + for (i = 0; i < HSI_MAX_CHANNELS; i++) { + mipi_ld->hsi_channles[i].dev = NULL; + mipi_ld->hsi_channles[i].opened = 0; + mipi_ld->hsi_channles[i].channel_id = i; + } + if_hsi_driver.ch_mask[0] = CHANNEL_MASK; + + /* TODO - need to get priv data (request to TI) */ + if_hsi_driver.priv_data = (void *)mipi_ld; + ret = hsi_register_driver(&if_hsi_driver); + if (ret) { + pr_err("[MIPI-HSI] hsi_register_driver() fail : %d\n", ret); + return ret; + } + + mipi_ld->mipi_wq = create_singlethread_workqueue("mipi_cmd_wq"); + if (!mipi_ld->mipi_wq) { + pr_err("[MIPI-HSI] fail to create work Q.\n"); + return -ENOMEM; + } + INIT_WORK(&mipi_ld->cmd_work, if_hsi_cmd_work); + INIT_DELAYED_WORK(&mipi_ld->start_work, mipi_hsi_start_work); + + setup_timer(&mipi_ld->hsi_acwake_down_timer, if_hsi_acwake_down_func, + (unsigned long)mipi_ld); + pr_info("[MIPI-HSI] setup_timer done\n"); + + /* TODO - allocate rx buff */ + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data = + kmalloc(64 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data) { + pr_err("[MIPI-HSI] alloc HSI_CONTROL_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_FMT_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_FMT_CHANNEL].rx_data) { + pr_err("[MIPI-HSI] alloc HSI_FMT_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_RAW_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_RAW_CHANNEL].rx_data) { + pr_err("[MIPI-HSI] alloc HSI_RAW_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_RFS_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_RFS_CHANNEL].rx_data) { + pr_err("[MIPI-HSI] alloc HSI_RFS_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_CMD_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_CMD_CHANNEL].rx_data) { + pr_err("[MIPI-HSI] alloc HSI_CMD_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + + pr_info("[MIPI-HSI] if_hsi_init() done : %d\n", ret); + return 0; +} + +struct link_device *mipi_create_link_device(struct platform_device *pdev) +{ + int ret; + struct mipi_link_device *mipi_ld; + struct link_device *ld; + + /* for dpram int */ + /* struct modem_data *pdata = pdev->dev.platform_data; */ + + mipi_ld = kzalloc(sizeof(struct mipi_link_device), GFP_KERNEL); + if (!mipi_ld) + return NULL; + + INIT_LIST_HEAD(&mipi_ld->list_of_io_devices); + INIT_LIST_HEAD(&mipi_ld->list_of_hsi_cmd); + skb_queue_head_init(&mipi_ld->ld.sk_fmt_tx_q); + skb_queue_head_init(&mipi_ld->ld.sk_raw_tx_q); + + ld = &mipi_ld->ld; + + ld->name = "mipi_hsi"; + ld->attach = mipi_hsi_attach_io_dev; + ld->init_comm = mipi_hsi_init_communication; + ld->send = mipi_hsi_send; + ld->com_state = COM_NONE; + + /* for dpram int */ + /* ld->irq = gpio_to_irq(pdata->gpio); s*/ + + ld->tx_wq = create_singlethread_workqueue("mipi_tx_wq"); + if (!ld->tx_wq) { + pr_err("[MIPI-HSI] fail to create work Q.\n"); + return NULL; + } + INIT_WORK(&ld->tx_work, mipi_hsi_tx_work); + + ret = if_hsi_init(ld); + if (ret) + return NULL; + + pr_info("[MODEM_IF] %s : create_io_device DONE\n", mipi_ld->ld.name); + return ld; +} + diff --git a/drivers/misc/modem_if/modem_link_device_mipi.h b/drivers/misc/modem_if/modem_link_device_mipi.h new file mode 100755 index 00000000000..0f3b3ea73ee --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_mipi.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_LINK_DEVICE_MIPI_H__ +#define __MODEM_LINK_DEVICE_MIPI_H__ + + +#define HSI_MAX_CHANNELS 16 +#define CHANNEL_MASK 0xFF + +#define HSI_CHANNEL_TX_STATE_UNAVAIL (1 << 0) +#define HSI_CHANNEL_TX_STATE_WRITING (1 << 1) +#define HSI_CHANNEL_RX_STATE_UNAVAIL (1 << 0) +#define HSI_CHANNEL_RX_STATE_READING (1 << 1) + +#define HSI_WRITE_DONE_TIMEOUT (HZ) +#define HSI_READ_DONE_TIMEOUT (HZ) +#define HSI_ACK_DONE_TIMEOUT (HZ) +#define HSI_CLOSE_CONN_DONE_TIMEOUT (HZ) +#define HSI_ACWAKE_DOWN_TIMEOUT (HZ / 2) + +#define HSI_CONTROL_CHANNEL 0 +#define HSI_FLASHLESS_CHANNEL 0 +#define HSI_CP_RAMDUMP_CHANNEL 0 +#define HSI_FMT_CHANNEL 1 +#define HSI_RAW_CHANNEL 2 +#define HSI_RFS_CHANNEL 3 +#define HSI_CMD_CHANNEL 4 +#define HSI_NUM_OF_USE_CHANNELS 5 + +#define HSI_LL_INVALID_CHANNEL 0xFF + + +enum { + HSI_LL_MSG_BREAK, /* 0x0 */ + HSI_LL_MSG_ECHO, + HSI_LL_MSG_INFO_REQ, + HSI_LL_MSG_INFO, + HSI_LL_MSG_CONFIGURE, + HSI_LL_MSG_ALLOCATE_CH, + HSI_LL_MSG_RELEASE_CH, + HSI_LL_MSG_OPEN_CONN, + HSI_LL_MSG_CONN_READY, + HSI_LL_MSG_CONN_CLOSED, /* 0x9 */ + HSI_LL_MSG_CANCEL_CONN, + HSI_LL_MSG_ACK, /* 0xB */ + HSI_LL_MSG_NAK, /* 0xC */ + HSI_LL_MSG_CONF_RATE, + HSI_LL_MSG_OPEN_CONN_OCTET, /* 0xE */ + HSI_LL_MSG_INVALID = 0xFF, +}; + +enum { + STEP_UNDEF, + STEP_CLOSED, + STEP_NOT_READY, + STEP_IDLE, + STEP_ERROR, + STEP_SEND_OPEN_CONN, + STEP_SEND_ACK, + STEP_WAIT_FOR_ACK, + STEP_TO_ACK, + STEP_SEND_NACK, + STEP_GET_NACK, + STEP_SEND_CONN_READY, + STEP_WAIT_FOR_CONN_READY, + STEP_SEND_CONF_RATE, + STEP_WAIT_FOR_CONF_ACK, + STEP_TX, + STEP_RX, + STEP_SEND_CONN_CLOSED, + STEP_WAIT_FOR_CONN_CLOSED, + STEP_SEND_BREAK, +}; + + +struct if_hsi_channel { + struct hsi_device *dev; + unsigned int channel_id; + + u32 *tx_data; + unsigned int tx_count; + u32 *rx_data; + unsigned int rx_count; + unsigned int packet_size; + + unsigned int tx_state; + unsigned int rx_state; + spinlock_t tx_state_lock; + spinlock_t rx_state_lock; + + unsigned int send_step; + unsigned int recv_step; + + unsigned int got_nack; + + struct semaphore write_done_sem; + struct semaphore read_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; + + struct workqueue_struct *mipi_wq; + struct work_struct cmd_work; + struct delayed_work start_work; + + 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; +}; +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_mipi_link_device(linkdev) \ + container_of(linkdev, struct mipi_link_device, ld) + + +enum { + HSI_INIT_MODE_NORMAL, + HSI_INIT_MODE_FLASHLESS_BOOT, + HSI_INIT_MODE_CP_RAMDUMP, +}; +static int hsi_init_handshake(struct mipi_link_device *mipi_ld, int mode); +static int if_hsi_write(struct if_hsi_channel *channel, u32 *data, + unsigned int size); +static int if_hsi_read(struct if_hsi_channel *channel, const char *data, + unsigned int size); +static int if_hsi_protocol_send(struct mipi_link_device *mipi_ld, int ch, + u32 *data, unsigned int len); + +#endif diff --git a/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c b/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c new file mode 100755 index 00000000000..d91098822f6 --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c @@ -0,0 +1,195 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> + +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" + +static int xmm6260_on(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] xmm6260_on()\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_on || !mc->gpio_reset_req_n) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_reset, 0); + udelay(160); + gpio_set_value(mc->gpio_pda_active, 0); + msleep(100); + gpio_set_value(mc->gpio_cp_reset, 1); + udelay(160); + gpio_set_value(mc->gpio_reset_req_n, 1); + udelay(160); + gpio_set_value(mc->gpio_cp_on, 1); + msleep(20); + gpio_set_value(mc->gpio_pda_active, 1); + + mc->phone_state = STATE_BOOTING; + + return 0; +} + +static int xmm6260_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] xmm6260_off()\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_on) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_reset, 0); + + mc->phone_state = STATE_OFFLINE; + + return 0; +} + + +static int xmm6260_reset(struct modem_ctl *mc) +{ + int ret; + + pr_info("[MODEM_IF] xmm6260_reset()\n"); + + ret = xmm6260_off(mc); + if (ret) + return ret; + + msleep(100); + + ret = xmm6260_on(mc); + if (ret) + return ret; + + return 0; +} + +static int xmm6260_boot_on(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] xmm6260_boot_on()\n"); + + if (!mc->gpio_flm_uart_sel) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_flm_uart_sel, 0); + + return 0; +} + +static int xmm6260_boot_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] xmm6260_boot_off()\n"); + + if (!mc->gpio_flm_uart_sel) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_flm_uart_sel, 1); + + return 0; +} + +static irqreturn_t phone_active_irq_handler(int irq, void *_mc) +{ + int phone_reset = 0; + int phone_active_value = 0; + int cp_dump_value = 0; + int phone_state = 0; + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + disable_irq_nosync(mc->irq_phone_active); + + if (!mc->gpio_cp_reset || !mc->gpio_phone_active || + !mc->gpio_cp_dump_int) { + pr_err("[MODEM_IF] no gpio data\n"); + return IRQ_HANDLED; + } + + phone_reset = gpio_get_value(mc->gpio_cp_reset); + phone_active_value = gpio_get_value(mc->gpio_phone_active); + cp_dump_value = gpio_get_value(mc->gpio_cp_dump_int); + + pr_info("[MODEM_IF] PA EVENT : reset =%d, pa=%d, cp_dump=%d\n", + phone_reset, phone_active_value, cp_dump_value); + + if (phone_reset && phone_active_value) + phone_state = STATE_ONLINE; + 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 (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 void xmm6260_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = xmm6260_on; + mc->ops.modem_off = xmm6260_off; + mc->ops.modem_reset = xmm6260_reset; + mc->ops.modem_boot_on = xmm6260_boot_on; + mc->ops.modem_boot_off = xmm6260_boot_off; + + pr_info("[MODEM_IF] xmm6260_get_ops() done\n"); +} + +int xmm6260_init_modemctl_device(struct modem_ctl *mc) +{ + int ret; + xmm6260_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) + pr_err("[MODEM_IF] failed to irq_phone_active request_irq:%d\n" + , ret); + + ret = enable_irq_wake(mc->irq_phone_active); + if (ret) + pr_err("[MODEM_IF] failed to irq_phone_active enable_irq_wake : %d\n", + ret); + + pr_info("[MODEM_IF] init_modemctl_device() done\n"); + return 0; +} + diff --git a/drivers/misc/modem_if/modem_prj.h b/drivers/misc/modem_if/modem_prj.h new file mode 100755 index 00000000000..e6f24956b45 --- /dev/null +++ b/drivers/misc/modem_if/modem_prj.h @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_PRJ_H__ +#define __MODEM_PRJ_H__ + +#include <linux/wait.h> +#include <linux/miscdevice.h> +#include <linux/skbuff.h> + + +#define MAX_LINK_DEVTYPE 3 +#define MAX_RAW_DEVS 32 +#define MAX_NUM_IO_DEV (MAX_RAW_DEVS + 4) + +#define IOCTL_MODEM_ON _IO('o', 0x19) +#define IOCTL_MODEM_OFF _IO('o', 0x20) +#define IOCTL_MODEM_RESET _IO('o', 0x21) +#define IOCTL_MODEM_BOOT_ON _IO('o', 0x22) +#define IOCTL_MODEM_BOOT_OFF _IO('o', 0x23) +#define IOCTL_MODEM_START _IO('o', 0x24) + +#define IOCTL_MODEM_SEND _IO('o', 0x25) +#define IOCTL_MODEM_RECV _IO('o', 0x26) + +#define IOCTL_MODEM_STATUS _IO('o', 0x27) + +/* modem status */ +#define MODEM_OFF 0 +#define MODEM_CRASHED 1 +#define MODEM_RAMDUMP 2 +#define MODEM_POWER_ON 3 +#define MODEM_BOOTING_NORMAL 4 +#define MODEM_BOOTING_RAMDUMP 5 +#define MODEM_DUMPING 6 +#define MODEM_RUNNING 7 + +#define IPC_HEADER_MAX_SIZE 6 /* fmt 3, raw 6, rfs 6 */ + +/* Does modem ctl structure will use state ? or status defined below ?*/ +enum modem_state { + STATE_OFFLINE, + STATE_CRASH_RESET, /* silent reset */ + STATE_CRASH_EXIT, /* cp ramdump */ + STATE_BOOTING, + STATE_ONLINE, +}; + +enum { + COM_NONE, + COM_ONLINE, + COM_HANDSHAKE, + COM_BOOT, + COM_CRASH, +}; + +struct header_data { + char hdr[IPC_HEADER_MAX_SIZE]; + unsigned len; + unsigned flag_len; + char start; /*hdlc start header 0x7F*/ +}; + +struct misc_data_io { + uint32_t size; + uint32_t id; + uint32_t cmd; + void *data; +}; + +struct vnet { + struct io_device *iod; +}; + +struct io_device { + struct list_head list; + char *name; + + wait_queue_head_t wq; + + struct miscdevice miscdev; + struct net_device *ndev; + + /* ID and Format for channel on the link */ + unsigned id; + enum dev_format format; + enum modem_io io_typ; + + struct sk_buff_head sk_rx_q; + + /* for fragmentation data from link device */ + struct sk_buff *skb_recv; + struct header_data h_data; + + /* called from linkdevice when a packet arrives for this iodevice */ + int (*recv)(struct io_device *iod, const char *data, unsigned int len); + + /* inform the IO device that the modem is now online or offline or + * crashing or whatever... + */ + void (*modem_state_changed)(struct io_device *iod, enum modem_state); + + struct link_device *link; + struct modem_ctl *mc; + + void *private_data; +}; +#define to_io_device(misc) container_of(misc, struct io_device, miscdev) + +struct io_raw_devices { + struct io_device *raw_devices[MAX_RAW_DEVS]; + int num_of_raw_devs; +}; + +struct link_device { + char *name; + + struct sk_buff_head sk_fmt_tx_q; + struct sk_buff_head sk_raw_tx_q; + + struct workqueue_struct *tx_wq; + struct work_struct tx_work; + struct delayed_work tx_delayed_work; + + int irq; /* for dpram int */ + unsigned com_state; + + /* called during init to associate an io device with this link */ + int (*attach)(struct link_device *ld, struct io_device *iod); + + /* init communication - setting link driver */ + int (*init_comm)(struct link_device *ld, struct io_device *iod); + + /* 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); +}; + +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 *); +}; + +struct modem_ctl { + struct device *dev; + char *name; + + int phone_state; + + unsigned gpio_cp_on; + unsigned gpio_reset_req_n; + unsigned gpio_cp_reset; + unsigned gpio_pda_active; + unsigned gpio_phone_active; + unsigned gpio_cp_dump_int; + unsigned gpio_flm_uart_sel; + unsigned gpio_cp_warm_reset; + + int irq_phone_active; + + struct work_struct work; + +#ifdef CONFIG_LTE_MODEM_CMC221 + const struct attribute_group *group; + unsigned gpio_cp_off; + unsigned gpio_slave_wakeup; + unsigned gpio_host_wakeup; + unsigned gpio_host_active; + int irq_host_wakeup; + struct delayed_work dwork; + struct work_struct resume_work; + int wakeup_flag; /*flag for CP boot GPIO sync flag*/ + int cpcrash_flag; + struct completion *l2_done; + int irq[3]; +#endif /*CONFIG_LTE_MODEM_CMC221*/ + + struct modemctl_ops ops; + struct io_device *iod; +}; + +int init_io_device(struct io_device *iod); + +#endif diff --git a/drivers/misc/modem_if/modem_variation.h b/drivers/misc/modem_if/modem_variation.h new file mode 100755 index 00000000000..937add3ffef --- /dev/null +++ b/drivers/misc/modem_if/modem_variation.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_VARIATION_H__ +#define __MODEM_VARIATION_H__ + +#define DECLARE_MODEM_INIT(type) \ + int type ## _init_modemctl_device(struct modem_ctl *mc) +#define DECLARE_MODEM_INIT_DUMMY(type) \ + DECLARE_MODEM_INIT(type) { return 0; } + +#define DECLARE_LINK_INIT(type) \ + struct link_device *type ## _create_link_device( \ + struct platform_device *pdev) +#define DECLARE_LINK_INIT_DUMMY(type) \ + DECLARE_LINK_INIT(type) { return NULL; } + +#define MODEM_INIT_CALL(type) type ## _init_modemctl_device +#define LINK_INIT_CALL(type) type ## _create_link_device + +/* add declaration of modem & link type */ +/* modem device support */ +#ifdef CONFIG_UMTS_MODEM_XMM6260 +DECLARE_MODEM_INIT(xmm6260); +#else +DECLARE_MODEM_INIT_DUMMY(xmm6260) +#endif + +#ifdef CONFIG_LTE_MODEM_CMC221 +DECLARE_MODEM_INIT(cmc221); +#else +DECLARE_MODEM_INIT_DUMMY(cmc221) +#endif + +#ifdef CONFIG_CDMA_MODEM_CBP71 +DECLARE_MODEM_INIT(cbp71); +#else +DECLARE_MODEM_INIT_DUMMY(cbp71) +#endif + +/* link device support */ +#ifdef CONFIG_UMTS_LINK_MIPI +DECLARE_LINK_INIT(mipi); +#else +DECLARE_LINK_INIT_DUMMY(mipi) +#endif + +#ifdef CONFIG_CDMA_LINK_DPRAM +DECLARE_LINK_INIT(dpram); +#else +DECLARE_LINK_INIT_DUMMY(dpram) +#endif + +#ifdef CONFIG_LTE_LINK_USB +DECLARE_LINK_INIT(usb); +#else +DECLARE_LINK_INIT_DUMMY(usb) +#endif + +#ifdef CONFIG_UMTS_LINK_HSIC +DECLARE_LINK_INIT(hsic); +#else +DECLARE_LINK_INIT_DUMMY(hsic) +#endif + +#ifdef CONFIG_UMTS_LINK_SPI +DECLARE_LINK_INIT(spi); +#else +DECLARE_LINK_INIT_DUMMY(spi) +#endif + +typedef int (*modem_init_call)(struct modem_ctl *); +modem_init_call modem_init_func[] = { + MODEM_INIT_CALL(xmm6260), + MODEM_INIT_CALL(cbp71), + MODEM_INIT_CALL(cmc221), +}; + +typedef struct link_device *(*link_init_call)(struct platform_device *); +link_init_call link_init_func[] = { + LINK_INIT_CALL(mipi), + LINK_INIT_CALL(dpram), + LINK_INIT_CALL(spi), + LINK_INIT_CALL(usb), + LINK_INIT_CALL(hsic), +}; + +static int call_modem_init_func(struct modem_ctl *mc, struct modem_data *pdata) +{ + if (modem_init_func[pdata->modem_type]) + return modem_init_func[pdata->modem_type](mc); + 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/include/linux/platform_data/modem.h b/include/linux/platform_data/modem.h new file mode 100755 index 00000000000..a1a404a0405 --- /dev/null +++ b/include/linux/platform_data/modem.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_IF_H__ +#define __MODEM_IF_H__ + +enum modem_t { + IMC_XMM6260, + VIA_CBP71, + SEC_CMC221, +}; + +enum dev_format { + IPC_FMT, + IPC_RAW, + IPC_RFS, + IPC_CMD, + IPC_BOOT, + IPC_MULTI_RAW, +}; + +enum modem_io { + IODEV_MISC, + IODEV_NET, + IODEV_DUMMY, +}; + +enum modem_link { + LINKDEV_MIPI, + LINKDEV_DPRAM, + LINKDEV_SPI, + LINKDEV_USB, + LINKDEV_MAX, +}; + +enum modem_network { + UMTS_NETWORK, + CDMA_NETWORK, + LTE_NETWORK, +}; + +/* This structure is used in board-tuna-modem.c */ +struct modem_io_t { + char *name; + int id; + enum dev_format format; + enum modem_io io_type; + enum modem_link link; +}; + +/* platform data */ +struct modem_data { + char *name; + + unsigned gpio_cp_on; + unsigned gpio_reset_req_n; + unsigned gpio_cp_reset; + unsigned gpio_pda_active; + unsigned gpio_phone_active; + unsigned gpio_cp_dump_int; + unsigned gpio_flm_uart_sel; + unsigned gpio_cp_warm_reset; +#ifdef CONFIG_LTE_MODEM_CMC221 + unsigned gpio_cp_off; + unsigned gpio_slave_wakeup; + unsigned gpio_host_wakeup; + unsigned gpio_host_active; +#endif /*CONFIG_LTE_MODEM_CMC221*/ + + /* modem component */ + enum modem_t modem_type; + enum modem_link link_type; + enum modem_network modem_net; + unsigned num_iodevs; + struct modem_io_t *iodevs; +}; + +#endif |
