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