diff options
author | Kamil Debski <k.debski@samsung.com> | 2013-05-22 14:34:06 +0200 |
---|---|---|
committer | Chanho Park <chanho61.park@samsung.com> | 2014-11-18 11:43:20 +0900 |
commit | 174781e29962c90e81b032860a732c8b04e31a26 (patch) | |
tree | ec06b90c00829370bb2d7da4efb12c5d9d7b4de9 /drivers | |
parent | eddf3808185f64b7474d0a85c903f599e613e673 (diff) | |
download | linux-3.10-174781e29962c90e81b032860a732c8b04e31a26.tar.gz linux-3.10-174781e29962c90e81b032860a732c8b04e31a26.tar.bz2 linux-3.10-174781e29962c90e81b032860a732c8b04e31a26.zip |
modem_if: Add modem_if driver files
All files were taken from exynos3.4 kernel.
Signed-off-by: Kamil Debski <k.debski@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Diffstat (limited to 'drivers')
37 files changed, 21527 insertions, 0 deletions
diff --git a/drivers/misc/modem_if/Kconfig b/drivers/misc/modem_if/Kconfig new file mode 100644 index 00000000000..234de2fad2b --- /dev/null +++ b/drivers/misc/modem_if/Kconfig @@ -0,0 +1,89 @@ +menuconfig SEC_MODEM + bool "Samsung Mobile Modem Interface" + default n + ---help--- + Samsung Modem Interface Driver. + +config UMTS_MODEM_XMM6260 + bool "modem chip : IMC XMM6260" + depends on SEC_MODEM + default n + +config UMTS_MODEM_XMM6262 + bool "modem chip : IMC XMM6262" + depends on SEC_MODEM + default n + +config CDMA_MODEM_CBP71 + bool "modem chip : VIA CBP7.1" + depends on SEC_MODEM + default n + +config CDMA_MODEM_CBP72 + bool "modem chip : VIA CBP7.2" + depends on SEC_MODEM + default n + +config LTE_MODEM_CMC221 + bool "modem chip : SEC CMC221" + depends on SEC_MODEM + default n + +config CDMA_MODEM_MDM6600 + bool "modem chip : QC MDM6600" + depends on SEC_MODEM + default n + +config GSM_MODEM_ESC6270 + bool "modem chip : QC ESC6270" + depends on SEC_MODEM + default n + +config LINK_DEVICE_MIPI + bool "modem driver link device MIPI-HSI" + depends on SEC_MODEM + default n + +config LINK_DEVICE_DPRAM + bool "modem driver link device DPRAM" + depends on SEC_MODEM + default n + +config LINK_DEVICE_PLD + bool "modem driver link device PLD" + depends on SEC_MODEM + default n +config LINK_DEVICE_USB + bool "modem driver link device USB" + depends on SEC_MODEM + default n + +config LINK_DEVICE_HSIC + bool "modem driver link device HSIC" + depends on SEC_MODEM + default n + +config LINK_DEVICE_C2C + bool "modem driver link device C2C" + depends on SEC_MODEM + default n + +config IPC_CMC22x_OLD_RFS + bool "IPC: CMC22x ancient RFS" + depends on SEC_MODEM + default n + +config SIPC_VER_5 + bool "IPC: Samsung IPC 5.0" + depends on SEC_MODEM + default n + +config SIM_DETECT + bool "SIM_DETECT pin" + depends on SEC_MODEM + default n + +config SIM_SLOT_SWITCH + bool "SIM_SLOT_SWITCH" + depends on SEC_MODEM + default n diff --git a/drivers/misc/modem_if/Makefile b/drivers/misc/modem_if/Makefile new file mode 100644 index 00000000000..9bedbe1c139 --- /dev/null +++ b/drivers/misc/modem_if/Makefile @@ -0,0 +1,24 @@ +# Makefile of modem_if + +EXTRA_CFLAGS += -Idrivers/misc/modem_if + +obj-y += sipc5_modem.o sipc5_io_device.o +obj-y += sipc4_modem.o sipc4_io_device.o +obj-y += modem_net_flowcontrol_device.o modem_utils.o + +obj-$(CONFIG_UMTS_MODEM_XMM6260) += modem_modemctl_device_xmm6260.o +obj-$(CONFIG_UMTS_MODEM_XMM6262) += modem_modemctl_device_xmm6262.o +obj-$(CONFIG_CDMA_MODEM_CBP71) += modem_modemctl_device_cbp71.o +obj-$(CONFIG_CDMA_MODEM_CBP72) += modem_modemctl_device_cbp72.o +obj-$(CONFIG_LTE_MODEM_CMC221) += modem_modemctl_device_cmc221.o lte_modem_bootloader.o +obj-$(CONFIG_CDMA_MODEM_MDM6600) += modem_modemctl_device_mdm6600.o +obj-$(CONFIG_GSM_MODEM_ESC6270) += modem_modemctl_device_esc6270.o + +obj-$(CONFIG_LINK_DEVICE_MIPI) += modem_link_device_mipi.o +obj-$(CONFIG_LINK_DEVICE_DPRAM) += modem_link_device_dpram.o modem_link_device_dpram_ext_op.o +obj-$(CONFIG_LINK_DEVICE_PLD) += modem_link_device_pld.o modem_link_device_pld_ext_op.o +obj-$(CONFIG_LINK_DEVICE_USB) += modem_link_device_usb.o modem_link_pm_usb.o +obj-$(CONFIG_LINK_DEVICE_HSIC) += modem_link_device_hsic.o +obj-$(CONFIG_LINK_DEVICE_C2C) += modem_link_device_c2c.o + +obj-$(CONFIG_SIM_SLOT_SWITCH) += modem_sim_slot_switch.o diff --git a/drivers/misc/modem_if/lte_modem_bootloader.c b/drivers/misc/modem_if/lte_modem_bootloader.c new file mode 100644 index 00000000000..f259aaeb214 --- /dev/null +++ b/drivers/misc/modem_if/lte_modem_bootloader.c @@ -0,0 +1,313 @@ +/* Lte modem bootloader support for Samsung Tuna Board. + * + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> + +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/wakelock.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> + +#include <linux/platform_data/modem.h> +#include <linux/platform_data/lte_modem_bootloader.h> + +#define LEN_XMIT_DELEY 100 + +#ifdef AIRPLAIN_MODE_TEST +int lte_airplain_mode; +#endif + +enum xmit_bootloader_status { + XMIT_BOOT_READY = 0, + XMIT_LOADER_READY, +}; + +struct lte_modem_bootloader { + struct spi_device *spi_dev; + struct miscdevice dev; + + struct mutex lock; + + unsigned int gpio_lte2ap_status; + enum xmit_bootloader_status xmit_status; +}; +#define to_loader(misc) container_of(misc, struct lte_modem_bootloader, dev); + +static inline +int spi_xmit(struct lte_modem_bootloader *loader, + const unsigned char val) +{ + unsigned char buf[1]; + int ret; + struct spi_message msg; + + struct spi_transfer xfer = { + .len = 1, + .tx_buf = buf, + }; + + buf[0] = val; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + ret = spi_sync(loader->spi_dev, &msg); + + if (ret < 0) + mif_err("error %d\n", ret); + + return ret; +} + +static +int bootloader_write(struct lte_modem_bootloader *loader, + const char *addr, const int len) +{ + int i; + int ret = 0; + unsigned char lenbuf[4]; + + if (loader->xmit_status == XMIT_LOADER_READY) { + memcpy(lenbuf, &len, ARRAY_SIZE(lenbuf)); + for (i = 0 ; i < ARRAY_SIZE(lenbuf) ; i++) { + ret = spi_xmit(loader, lenbuf[i]); + if (ret < 0) + return ret; + } + msleep(LEN_XMIT_DELEY); + } + + for (i = 0 ; i < len ; i++) { + ret = spi_xmit(loader, addr[i]); + if (ret < 0) + return ret; + } + + return 0; +} + + +static +int bootloader_open(struct inode *inode, struct file *flip) +{ + struct lte_modem_bootloader *loader = to_loader(flip->private_data); + flip->private_data = loader; + + return 0; +} + +static +long bootloader_ioctl(struct file *flip, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + int status; + struct lte_modem_bootloader_param param; + struct lte_modem_bootloader *loader = flip->private_data; + + mutex_lock(&loader->lock); + switch (cmd) { + case IOCTL_LTE_MODEM_XMIT_BOOT: + + ret = copy_from_user(¶m, (const void __user *)arg, + sizeof(param)); + if (ret) { + mif_err("can not copy userdata\n"); + ret = -EFAULT; + goto exit_err; + } + + dev_info(&loader->spi_dev->dev, + "IOCTL_LTE_MODEM_XMIT_BOOT - bin size: %d\n", + param.len); + + ret = bootloader_write(loader, param.buf, param.len); + if (ret < 0) + mif_err("failed to xmit boot bin\n"); + else { + if (loader->xmit_status == XMIT_BOOT_READY) + loader->xmit_status = XMIT_LOADER_READY; + else + loader->xmit_status = XMIT_BOOT_READY; + } + + break; + case IOCTL_LTE_MODEM_LTE2AP_STATUS: + status = gpio_get_value(loader->gpio_lte2ap_status); + mif_debug("LTE2AP status :%d\n", status); + ret = copy_to_user((unsigned int *)arg, &status, + sizeof(status)); + + break; +#ifdef AIRPLAIN_MODE_TEST + case IOCTL_LTE_MODEM_AIRPLAIN_ON: + lte_airplain_mode = 1; + mif_info("IOCTL_LTE_MODEM LPM_ON\n"); + break; + case IOCTL_LTE_MODEM_AIRPLAIN_OFF: + mif_info("IOCTL_LTE_MODEM LPM_OFF\n"); + lte_airplain_mode = 0; + break; +#endif + default: + mif_err("ioctl cmd error\n"); + ret = -ENOIOCTLCMD; + + break; + } + mutex_unlock(&loader->lock); + +exit_err: + return ret; +} + +static const struct file_operations lte_modem_bootloader_fops = { + .owner = THIS_MODULE, + .open = bootloader_open, + .unlocked_ioctl = bootloader_ioctl, +}; + +static +int bootloader_gpio_setup(struct lte_modem_bootloader *loader) +{ + if (!loader->gpio_lte2ap_status) + return -EINVAL; + + gpio_request(loader->gpio_lte2ap_status, "GPIO_LTE2AP_STATUS"); + gpio_direction_input(loader->gpio_lte2ap_status); + + return 0; +} + +static +int __devinit lte_modem_bootloader_probe(struct spi_device *spi) +{ + int ret; + + struct lte_modem_bootloader *loader; + struct lte_modem_bootloader_platform_data *pdata; + + loader = kzalloc(sizeof(*loader), GFP_KERNEL); + if (!loader) { + mif_err("failed to allocate for lte_modem_bootloader\n"); + ret = -ENOMEM; + goto err_alloc; + } + mutex_init(&loader->lock); + + spi->bits_per_word = 8; + + if (spi_setup(spi)) { + mif_err("failed to setup spi for lte_modem_bootloader\n"); + ret = -EINVAL; + goto err_setup; + } + + loader->spi_dev = spi; + + if (!spi->dev.platform_data) { + mif_err("failed to get platform data for lte_modem_bootloader\n"); + ret = -EINVAL; + goto err_setup; + } + pdata = (struct lte_modem_bootloader_platform_data *) + spi->dev.platform_data; + loader->gpio_lte2ap_status = pdata->gpio_lte2ap_status; + + ret = bootloader_gpio_setup(loader); + if (ret) { + mif_err("failed to set gpio for lte_modem_boot_loader\n"); + goto err_setup; + } + + loader->gpio_lte2ap_status = pdata->gpio_lte2ap_status; + loader->xmit_status = XMIT_BOOT_READY; + + spi_set_drvdata(spi, loader); + + loader->dev.minor = MISC_DYNAMIC_MINOR; + loader->dev.name = "lte_spi"; + loader->dev.fops = <e_modem_bootloader_fops; + ret = misc_register(&loader->dev); + if (ret) { + mif_err("failed to register misc dev for lte_modem_bootloader\n"); + goto err_setup; + } + mif_info("lte_modem_bootloader successfully probed\n"); +#ifdef AIRPLAIN_MODE_TEST + lte_airplain_mode = 0; +#endif + return 0; + +err_setup: + mutex_destroy(&loader->lock); + kfree(loader); + +err_alloc: + + return ret; +} + +static +int __devexit lte_modem_bootloader_remove(struct spi_device *spi) +{ + struct lte_modem_bootloader *loader = spi_get_drvdata(spi); + + misc_deregister(&loader->dev); + mutex_destroy(&loader->lock); + kfree(loader); + + return 0; +} + +static +struct spi_driver lte_modem_bootloader_driver = { + .driver = { + .name = "lte_modem_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = lte_modem_bootloader_probe, + .remove = __devexit_p(lte_modem_bootloader_remove), +}; + +static +int __init lte_modem_bootloader_init(void) +{ + return spi_register_driver(<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/modem_link_device_c2c.c b/drivers/misc/modem_if/modem_link_device_c2c.c new file mode 100644 index 00000000000..acbaadf0f93 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_c2c.c @@ -0,0 +1,61 @@ +/* /linux/drivers/new_modem_if/link_dev_c2c.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/wakelock.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/proc_fs.h> +#include <linux/if_arp.h> +#include <linux/platform_device.h> + +#include <linux/platform_data/modem.h> +#include <linux/platform_data/c2c.h> +#include "modem_prj.h" +#include "modem_link_device_c2c.h" + +struct link_device *c2c_create_link_device(struct platform_device *pdev) +{ + struct c2c_link_device *dpld; + struct link_device *ld; + struct modem_data *pdata; + + pdata = pdev->dev.platform_data; + + dpld = kzalloc(sizeof(struct c2c_link_device), GFP_KERNEL); + if (!dpld) { + mif_err("dpld == NULL\n"); + return NULL; + } + + wake_lock_init(&dpld->c2c_wake_lock, WAKE_LOCK_SUSPEND, "c2c_wakelock"); + wake_lock(&dpld->c2c_wake_lock); + + ld = &dpld->ld; + dpld->pdata = pdata; + + ld->name = "c2c"; + + mif_info("%s is created!!!\n", dpld->ld.name); + + return ld; +} diff --git a/drivers/misc/modem_if/modem_link_device_c2c.h b/drivers/misc/modem_if/modem_link_device_c2c.h new file mode 100644 index 00000000000..7ec9aa635aa --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_c2c.h @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/wakelock.h> + +#ifndef __MODEM_LINK_DEVICE_C2C_H__ +#define __MODEM_LINK_DEVICE_C2C_H__ + +#define DPRAM_ERR_MSG_LEN 128 +#define DPRAM_ERR_DEVICE "c2cerr" + +#define MAX_IDX 2 + +#define DPRAM_BASE_PTR 0x4000000 + +#define DPRAM_START_ADDRESS 0 +#define DPRAM_MAGIC_CODE_ADDRESS DPRAM_START_ADDRESS +#define DPRAM_GOTA_MAGIC_CODE_SIZE 0x4 +#define DPRAM_PDA2PHONE_FORMATTED_START_ADDRESS \ + (DPRAM_START_ADDRESS + DPRAM_GOTA_MAGIC_CODE_SIZE) +#define BSP_DPRAM_BASE_SIZE 0x1ff8 +#define DPRAM_END_OF_ADDRESS (BSP_DPRAM_BASE_SIZE - 1) +#define DPRAM_INTERRUPT_SIZE 0x2 +#define DPRAM_PDA2PHONE_INTERRUPT_ADDRESS \ + (DPRAM_START_ADDRESS + BSP_DPRAM_BASE_SIZE - DPRAM_INTERRUPT_SIZE*2) +#define DPRAM_PHONE2PDA_INTERRUPT_ADDRESS \ + (DPRAM_START_ADDRESS + BSP_DPRAM_BASE_SIZE) +#define DPRAM_BUFFER_SIZE \ + (DPRAM_PHONE2PDA_INTERRUPT_ADDRESS -\ + DPRAM_PDA2PHONE_FORMATTED_START_ADDRESS) +#define DPRAM_INDEX_SIZE 0x2 + +#define MAGIC_DMDL 0x4445444C +#define MAGIC_UMDL 0x4445444D + +#define DPRAM_PACKET_DATA_SIZE 0x3f00 +#define DPRAM_PACKET_HEADER_SIZE 0x7 + +#define INT_GOTA_MASK_VALID 0xA000 +#define INT_DPRAM_DUMP_MASK_VALID 0xA000 +#define MASK_CMD_RECEIVE_READY_NOTIFICATION 0xA100 +#define MASK_CMD_DOWNLOAD_START_REQUEST 0xA200 +#define MASK_CMD_DOWNLOAD_START_RESPONSE 0xA301 +#define MASK_CMD_IMAGE_SEND_REQUEST 0xA400 +#define MASK_CMD_IMAGE_SEND_RESPONSE 0xA500 +#define MASK_CMD_SEND_DONE_REQUEST 0xA600 +#define MASK_CMD_SEND_DONE_RESPONSE 0xA701 +#define MASK_CMD_STATUS_UPDATE_NOTIFICATION 0xA800 +#define MASK_CMD_UPDATE_DONE_NOTIFICATION 0xA900 +#define MASK_CMD_EFS_CLEAR_RESPONSE 0xAB00 +#define MASK_CMD_ALARM_BOOT_OK 0xAC00 +#define MASK_CMD_ALARM_BOOT_FAIL 0xAD00 + +#define WRITEIMG_HEADER_SIZE 8 +#define WRITEIMG_TAIL_SIZE 4 +#define WRITEIMG_BODY_SIZE \ + (DPRAM_BUFFER_SIZE - WRITEIMG_HEADER_SIZE - WRITEIMG_TAIL_SIZE) + +#define DPDN_DEFAULT_WRITE_LEN WRITEIMG_BODY_SIZE +#define CMD_DL_START_REQ 0x9200 +#define CMD_IMG_SEND_REQ 0x9400 +#define CMD_DL_SEND_DONE_REQ 0x9600 + +#define CMD_UL_START_REQ 0x9200 +#define CMD_UL_START_READY 0x9400 +#define CMD_UL_SEND_RESP 0x9601 +#define CMD_UL_SEND_DONE_RESP 0x9801 +#define CMD_UL_SEND_REQ 0xA500 +#define CMD_UL_START_RESPONSE 0xA301 +#define CMD_UL_SEND_DONE_REQ 0xA700 +#define CMD_RECEIVE_READY_NOTIFICATION 0xA100 + +#define MASK_CMD_RESULT_FAIL 0x0002 +#define MASK_CMD_RESULT_SUCCESS 0x0001 + +#define START_INDEX 0x007F +#define END_INDEX 0x007E + +#define CMD_IMG_SEND_REQ 0x9400 + +#define CRC_TAB_SIZE 256 +#define CRC_16_L_SEED 0xFFFF + +struct c2c_device { + /* DPRAM memory addresses */ + u16 *in_head_addr; + u16 *in_tail_addr; + u8 *in_buff_addr; + unsigned long in_buff_size; + + u16 *out_head_addr; + u16 *out_tail_addr; + u8 *out_buff_addr; + unsigned long out_buff_size; + + unsigned long in_head_saved; + unsigned long in_tail_saved; + unsigned long out_head_saved; + unsigned long out_tail_saved; + + u16 mask_req_ack; + u16 mask_res_ack; + u16 mask_send; +}; + +struct memory_region { + u8 *control; + u8 *fmt_out; + u8 *raw_out; + u8 *fmt_in; + u8 *raw_in; + u8 *mbx; +}; + +struct UldDataHeader { + u8 bop; + u16 total_frame; + u16 curr_frame; + u16 len; +}; + +struct c2c_link_device { + struct link_device ld; + + struct modem_data *pdata; + + /*only c2c*/ + struct wake_lock c2c_wake_lock; + atomic_t raw_txq_req_ack_rcvd; + atomic_t fmt_txq_req_ack_rcvd; + u8 net_stop_flag; + int phone_sync; + u8 phone_status; + + struct work_struct xmit_work_struct; + + struct workqueue_struct *gota_wq; + struct work_struct gota_cmd_work; + + struct c2c_device dev_map[MAX_IDX]; + + struct wake_lock dumplock; + + u8 c2c_read_data[131072]; + + int c2c_init_cmd_wait_condition; + wait_queue_head_t c2c_init_cmd_wait_q; + + int modem_pif_init_wait_condition; + wait_queue_head_t modem_pif_init_done_wait_q; + + struct completion gota_download_start_complete; + + int gota_send_done_cmd_wait_condition; + wait_queue_head_t gota_send_done_cmd_wait_q; + + int gota_update_done_cmd_wait_condition; + wait_queue_head_t gota_update_done_cmd_wait_q; + + int upload_send_req_wait_condition; + wait_queue_head_t upload_send_req_wait_q; + + int upload_send_done_wait_condition; + wait_queue_head_t upload_send_done_wait_q; + + int upload_start_req_wait_condition; + wait_queue_head_t upload_start_req_wait_q; + + int upload_packet_start_condition; + wait_queue_head_t upload_packet_start_wait_q; + + u16 gota_irq_handler_cmd; + + u16 c2c_dump_handler_cmd; + + int dump_region_number; + + unsigned int is_c2c_err ; + + int c2c_dump_start; + int gota_start; + + char c2c_err_buf[DPRAM_ERR_MSG_LEN]; + + struct fasync_struct *c2c_err_async_q; + + void (*clear_interrupt)(struct c2c_link_device *); + + struct memory_region m_region; + + unsigned long fmt_out_buff_size; + unsigned long raw_out_buff_size; + unsigned long fmt_in_buff_size; + unsigned long raw_in_buff_size; + + struct delayed_work delayed_tx; + struct sk_buff *delayed_skb; + u8 delayed_count; +}; + +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_c2c_link_device(linkdev) \ + container_of(linkdev, struct c2c_link_device, ld) + +#endif diff --git a/drivers/misc/modem_if/modem_link_device_dpram.c b/drivers/misc/modem_if/modem_link_device_dpram.c new file mode 100644 index 00000000000..ea0b21e33bd --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_dpram.c @@ -0,0 +1,2111 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/time.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/wakelock.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/if_arp.h> +#include <linux/platform_device.h> +#include <linux/kallsyms.h> +#include <linux/platform_data/modem.h> + +#include "modem_prj.h" +#include "modem_link_device_dpram.h" +#include "modem_utils.h" + +/* +** Function prototypes for basic DPRAM operations +*/ +static inline void clear_intr(struct dpram_link_device *dpld); +static inline u16 recv_intr(struct dpram_link_device *dpld); +static inline void send_intr(struct dpram_link_device *dpld, u16 mask); + +static inline u16 get_magic(struct dpram_link_device *dpld); +static inline void set_magic(struct dpram_link_device *dpld, u16 val); +static inline u16 get_access(struct dpram_link_device *dpld); +static inline void set_access(struct dpram_link_device *dpld, u16 val); + +static inline u32 get_tx_head(struct dpram_link_device *dpld, int id); +static inline u32 get_tx_tail(struct dpram_link_device *dpld, int id); +static inline void set_tx_head(struct dpram_link_device *dpld, int id, u32 in); +static inline void set_tx_tail(struct dpram_link_device *dpld, int id, u32 out); +static inline u8 *get_tx_buff(struct dpram_link_device *dpld, int id); +static inline u32 get_tx_buff_size(struct dpram_link_device *dpld, int id); + +static inline u32 get_rx_head(struct dpram_link_device *dpld, int id); +static inline u32 get_rx_tail(struct dpram_link_device *dpld, int id); +static inline void set_rx_head(struct dpram_link_device *dpld, int id, u32 in); +static inline void set_rx_tail(struct dpram_link_device *dpld, int id, u32 out); +static inline u8 *get_rx_buff(struct dpram_link_device *dpld, int id); +static inline u32 get_rx_buff_size(struct dpram_link_device *dpld, int id); + +static inline u16 get_mask_req_ack(struct dpram_link_device *dpld, int id); +static inline u16 get_mask_res_ack(struct dpram_link_device *dpld, int id); +static inline u16 get_mask_send(struct dpram_link_device *dpld, int id); + +static inline void reset_tx_circ(struct dpram_link_device *dpld, int dev); +static inline void reset_rx_circ(struct dpram_link_device *dpld, int dev); + +static int trigger_force_cp_crash(struct dpram_link_device *dpld); + +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP +static inline void log_dpram_status(struct dpram_link_device *dpld, char *str) +{ + struct utc_time utc; + + get_utc_time(&utc); + + pr_info("%s%s: %s: [%02d:%02d:%02d.%03d] " + "ACC{%X %d} FMT{TI:%u TO:%u RI:%u RO:%u} " + "RAW{TI:%u TO:%u RI:%u RO:%u} INTR{0x%X}\n", + LOG_TAG, dpld->ld.mc->name, str, + utc.hour, utc.min, utc.sec, utc.msec, + get_magic(dpld), get_access(dpld), + get_tx_head(dpld, IPC_FMT), get_tx_tail(dpld, IPC_FMT), + get_rx_head(dpld, IPC_FMT), get_rx_tail(dpld, IPC_FMT), + get_tx_head(dpld, IPC_RAW), get_tx_tail(dpld, IPC_RAW), + get_rx_head(dpld, IPC_RAW), get_rx_tail(dpld, IPC_RAW), + recv_intr(dpld)); +} + +static void pr_trace(struct dpram_link_device *dpld, int dev, + struct timespec *ts, u8 *buff, u32 rcvd) +{ + struct link_device *ld = &dpld->ld; + struct utc_time utc; + + ts2utc(ts, &utc); + + pr_info("%s[%d-%02d-%02d %02d:%02d:%02d.%03d] %s trace (%s)\n", + LOG_TAG, utc.year, utc.mon, utc.day, utc.hour, utc.min, utc.sec, + utc.msec, get_dev_name(dev), ld->name); + + mif_print_dump(buff, rcvd, 4); + + return; +} + +static void save_dpram_dump_work(struct work_struct *work) +{ + struct dpram_link_device *dpld; + struct link_device *ld; + struct trace_queue *trq; + struct trace_data *trd; + struct file *fp; + struct timespec *ts; + u8 *dump; + int rcvd; + char *path; + struct utc_time utc; + + dpld = container_of(work, struct dpram_link_device, dump_dwork.work); + ld = &dpld->ld; + trq = &dpld->dump_list; + path = dpld->dump_path; + + while (1) { + trd = trq_get_data_slot(trq); + if (!trd) + break; + + ts = &trd->ts; + dump = trd->data; + rcvd = trd->size; + + ts2utc(ts, &utc); + snprintf(path, MIF_MAX_PATH_LEN, + "%s/%s_dump_%d%02d%02d-%02d%02d%02d", + MIF_LOG_DIR, ld->name, utc.year, utc.mon, utc.day, + utc.hour, utc.min, utc.sec); + + fp = mif_open_file(path); + if (fp) { + mif_save_file(fp, dump, rcvd); + mif_close_file(fp); + } else { + mif_err("%s: ERR! %s open fail\n", ld->name, path); + mif_print_dump(dump, rcvd, 16); + } + + kfree(dump); + } +} + +static void save_dpram_dump(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + struct trace_data *trd; + u8 *buff; + struct timespec ts; + + buff = kzalloc(dpld->size, GFP_ATOMIC); + if (!buff) { + mif_err("%s: ERR! kzalloc fail\n", ld->name); + return; + } + + getnstimeofday(&ts); + + memcpy(buff, dpld->base, dpld->size); + + trd = trq_get_free_slot(&dpld->dump_list); + if (!trd) { + mif_err("%s: ERR! trq_get_free_slot fail\n", ld->name); + mif_print_dump(buff, dpld->size, 16); + kfree(buff); + return; + } + + memcpy(&trd->ts, &ts, sizeof(struct timespec)); + trd->data = buff; + trd->size = dpld->size; + + queue_delayed_work(system_nrt_wq, &dpld->dump_dwork, 0); +} + +static void save_ipc_trace_work(struct work_struct *work) +{ + struct dpram_link_device *dpld; + struct link_device *ld; + struct trace_queue *trq; + struct trace_data *trd; + struct file *fp; + struct timespec *ts; + int dev; + u8 *dump; + int rcvd; + u8 *buff; + char *path; + struct utc_time utc; + + dpld = container_of(work, struct dpram_link_device, trace_dwork.work); + ld = &dpld->ld; + trq = &dpld->trace_list; + path = dpld->trace_path; + + buff = kzalloc(dpld->size << 3, GFP_KERNEL); + if (!buff) { + while (1) { + trd = trq_get_data_slot(trq); + if (!trd) + break; + + ts = &trd->ts; + dev = trd->dev; + dump = trd->data; + rcvd = trd->size; + pr_trace(dpld, dev, ts, dump, rcvd); + + kfree(dump); + } + return; + } + + while (1) { + trd = trq_get_data_slot(trq); + if (!trd) + break; + + ts = &trd->ts; + dev = trd->dev; + dump = trd->data; + rcvd = trd->size; + + ts2utc(ts, &utc); + snprintf(path, MIF_MAX_PATH_LEN, + "%s/%s_%s_%d%02d%02d-%02d%02d%02d", + MIF_LOG_DIR, ld->name, get_dev_name(dev), + utc.year, utc.mon, utc.day, utc.hour, utc.min, utc.sec); + + fp = mif_open_file(path); + if (fp) { + int len; + + snprintf(buff, MIF_MAX_PATH_LEN, + "[%d-%02d-%02d %02d:%02d:%02d.%03d]\n", + utc.year, utc.mon, utc.day, utc.hour, utc.min, + utc.sec, utc.msec); + len = strlen(buff); + mif_dump2format4(dump, rcvd, (buff + len), NULL); + strcat(buff, "\n"); + len = strlen(buff); + + mif_save_file(fp, buff, len); + + memset(buff, 0, len); + mif_close_file(fp); + } else { + mif_err("%s: %s open fail\n", ld->name, path); + pr_trace(dpld, dev, ts, dump, rcvd); + } + + kfree(dump); + } + + kfree(buff); +} + +static void print_ipc_trace(struct dpram_link_device *dpld, int dev, + u8 __iomem *src, u32 qsize, u32 in, u32 out, u32 rcvd) +{ + u8 *buff = dpld->buff; + struct timespec ts; + + getnstimeofday(&ts); + + memset(buff, 0, dpld->size); + circ_read(buff, src, qsize, out, rcvd); + + pr_trace(dpld, dev, &ts, buff, rcvd); +} + +static void save_ipc_trace(struct dpram_link_device *dpld, int dev, + u8 __iomem *src, u32 qsize, u32 in, u32 out, u32 rcvd) +{ + struct link_device *ld = &dpld->ld; + struct trace_data *trd; + u8 *buff; + struct timespec ts; + + buff = kzalloc(rcvd, GFP_ATOMIC); + if (!buff) { + mif_err("%s: %s: ERR! kzalloc fail\n", + ld->name, get_dev_name(dev)); + print_ipc_trace(dpld, dev, src, qsize, in, out, rcvd); + return; + } + + getnstimeofday(&ts); + + circ_read(buff, src, qsize, out, rcvd); + + trd = trq_get_free_slot(&dpld->trace_list); + if (!trd) { + mif_err("%s: %s: ERR! trq_get_free_slot fail\n", + ld->name, get_dev_name(dev)); + pr_trace(dpld, dev, &ts, buff, rcvd); + kfree(buff); + return; + } + + memcpy(&trd->ts, &ts, sizeof(struct timespec)); + trd->dev = dev; + trd->data = buff; + trd->size = rcvd; + + queue_delayed_work(system_nrt_wq, &dpld->trace_dwork, 0); +} +#endif + +static void set_dpram_map(struct dpram_link_device *dpld, + struct mif_irq_map *map) +{ + map->magic = get_magic(dpld); + map->access = get_access(dpld); + + map->fmt_tx_in = get_tx_head(dpld, IPC_FMT); + map->fmt_tx_out = get_tx_tail(dpld, IPC_FMT); + map->fmt_rx_in = get_rx_head(dpld, IPC_FMT); + map->fmt_rx_out = get_rx_tail(dpld, IPC_FMT); + map->raw_tx_in = get_tx_head(dpld, IPC_RAW); + map->raw_tx_out = get_tx_tail(dpld, IPC_RAW); + map->raw_rx_in = get_rx_head(dpld, IPC_RAW); + map->raw_rx_out = get_rx_tail(dpld, IPC_RAW); + + map->cp2ap = recv_intr(dpld); +} + +/* +** DPRAM operations +*/ +static int register_isr(unsigned irq, irqreturn_t (*isr)(int, void*), + unsigned long flag, const char *name, + struct dpram_link_device *dpld) +{ + int ret; + + ret = request_irq(irq, isr, flag, name, dpld); + if (ret) { + mif_info("%s: ERR! request_irq fail (err %d)\n", name, ret); + return ret; + } + + ret = enable_irq_wake(irq); + if (ret) + mif_info("%s: ERR! enable_irq_wake fail (err %d)\n", name, ret); + + mif_info("%s (#%d) handler registered\n", name, irq); + + return 0; +} + +static inline void clear_intr(struct dpram_link_device *dpld) +{ + if (likely(dpld->need_intr_clear)) + dpld->ext_op->clear_intr(dpld); +} + +static inline u16 recv_intr(struct dpram_link_device *dpld) +{ + return ioread16(dpld->mbx2ap); +} + +static inline void send_intr(struct dpram_link_device *dpld, u16 mask) +{ + iowrite16(mask, dpld->mbx2cp); +} + +static inline u16 get_magic(struct dpram_link_device *dpld) +{ + return ioread16(dpld->magic); +} + +static inline void set_magic(struct dpram_link_device *dpld, u16 val) +{ + iowrite16(val, dpld->magic); +} + +static inline u16 get_access(struct dpram_link_device *dpld) +{ + return ioread16(dpld->access); +} + +static inline void set_access(struct dpram_link_device *dpld, u16 val) +{ + iowrite16(val, dpld->access); +} + +static inline u32 get_tx_head(struct dpram_link_device *dpld, int id) +{ + return ioread16(dpld->dev[id]->txq.head); +} + +static inline u32 get_tx_tail(struct dpram_link_device *dpld, int id) +{ + return ioread16(dpld->dev[id]->txq.tail); +} + +static inline void set_tx_head(struct dpram_link_device *dpld, int id, u32 in) +{ + int cnt = 0; + u32 val = 0; + + iowrite16((u16)in, dpld->dev[id]->txq.head); + + while (1) { + /* Check head value written */ + val = ioread16(dpld->dev[id]->txq.head); + if (likely(val == in)) + return; + + cnt++; + mif_err("ERR: %s txq.head(%d) != in(%d), count %d\n", + get_dev_name(id), val, in, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + /* Write head value again */ + udelay(100); + iowrite16((u16)in, dpld->dev[id]->txq.head); + } + + trigger_force_cp_crash(dpld); +} + +static inline void set_tx_tail(struct dpram_link_device *dpld, int id, u32 out) +{ + int cnt = 0; + u32 val = 0; + + iowrite16((u16)out, dpld->dev[id]->txq.tail); + + while (1) { + /* Check tail value written */ + val = ioread16(dpld->dev[id]->txq.tail); + if (likely(val == out)) + return; + + cnt++; + mif_err("ERR: %s txq.tail(%d) != out(%d), count %d\n", + get_dev_name(id), val, out, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + /* Write tail value again */ + udelay(100); + iowrite16((u16)out, dpld->dev[id]->txq.tail); + } + + trigger_force_cp_crash(dpld); +} + +static inline u8 *get_tx_buff(struct dpram_link_device *dpld, int id) +{ + return dpld->dev[id]->txq.buff; +} + +static inline u32 get_tx_buff_size(struct dpram_link_device *dpld, int id) +{ + return dpld->dev[id]->txq.size; +} + +static inline u32 get_rx_head(struct dpram_link_device *dpld, int id) +{ + return ioread16(dpld->dev[id]->rxq.head); +} + +static inline u32 get_rx_tail(struct dpram_link_device *dpld, int id) +{ + return ioread16(dpld->dev[id]->rxq.tail); +} + +static inline void set_rx_head(struct dpram_link_device *dpld, int id, u32 in) +{ + int cnt = 0; + u32 val = 0; + + iowrite16((u16)in, dpld->dev[id]->rxq.head); + + while (1) { + /* Check head value written */ + val = ioread16(dpld->dev[id]->rxq.head); + if (val == in) + return; + + cnt++; + mif_err("ERR: %s rxq.head(%d) != in(%d), count %d\n", + get_dev_name(id), val, in, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + /* Write head value again */ + udelay(100); + iowrite16((u16)in, dpld->dev[id]->rxq.head); + } + + trigger_force_cp_crash(dpld); +} + +static inline void set_rx_tail(struct dpram_link_device *dpld, int id, u32 out) +{ + int cnt = 0; + u32 val = 0; + + iowrite16((u16)out, dpld->dev[id]->rxq.tail); + + while (1) { + /* Check tail value written */ + val = ioread16(dpld->dev[id]->rxq.tail); + if (val == out) + return; + + cnt++; + mif_err("ERR: %s rxq.tail(%d) != out(%d), count %d\n", + get_dev_name(id), val, out, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + /* Write tail value again */ + udelay(100); + iowrite16((u16)out, dpld->dev[id]->rxq.tail); + } + + trigger_force_cp_crash(dpld); +} + +static inline u8 *get_rx_buff(struct dpram_link_device *dpld, int id) +{ + return dpld->dev[id]->rxq.buff; +} + +static inline u32 get_rx_buff_size(struct dpram_link_device *dpld, int id) +{ + return dpld->dev[id]->rxq.size; +} + +static inline u16 get_mask_req_ack(struct dpram_link_device *dpld, int id) +{ + return dpld->dev[id]->mask_req_ack; +} + +static inline u16 get_mask_res_ack(struct dpram_link_device *dpld, int id) +{ + return dpld->dev[id]->mask_res_ack; +} + +static inline u16 get_mask_send(struct dpram_link_device *dpld, int id) +{ + return dpld->dev[id]->mask_send; +} + +/* Get free space in the TXQ as well as in & out pointers */ +static int get_txq_space(struct dpram_link_device *dpld, int dev, u32 qsize, + u32 *in, u32 *out) +{ + struct link_device *ld = &dpld->ld; + int cnt = 0; + u32 head; + u32 tail; + int space; + + while (1) { + head = get_tx_head(dpld, dev); + tail = get_tx_tail(dpld, dev); + + space = circ_get_space(qsize, head, tail); + mif_debug("%s: %s_TXQ qsize[%u] in[%u] out[%u] space[%u]\n", + ld->name, get_dev_name(dev), qsize, head, tail, space); + + if (circ_valid(qsize, head, tail)) { + *in = head; + *out = tail; + return space; + } + + cnt++; + mif_err("%s: ERR! <%pf> " + "%s_TXQ invalid (size:%d in:%d out:%d), count %d\n", + ld->name, __builtin_return_address(0), + get_dev_name(dev), qsize, head, tail, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + udelay(100); + } + + *in = 0; + *out = 0; + return -EINVAL; +} + +/* Get data size in the RXQ as well as in & out pointers */ +static int get_rxq_rcvd(struct dpram_link_device *dpld, int dev, u32 qsize, + u32 *in, u32 *out) +{ + struct link_device *ld = &dpld->ld; + int cnt = 0; + u32 head; + u32 tail; + u32 rcvd; + + while (1) { + head = get_rx_head(dpld, dev); + tail = get_rx_tail(dpld, dev); + if (head == tail) { + *in = head; + *out = tail; + return 0; + } + + rcvd = circ_get_usage(qsize, head, tail); + mif_debug("%s: %s_RXQ qsize[%u] in[%u] out[%u] rcvd[%u]\n", + ld->name, get_dev_name(dev), qsize, head, tail, rcvd); + + if (circ_valid(qsize, head, tail)) { + *in = head; + *out = tail; + return rcvd; + } + + cnt++; + mif_err("%s: ERR! <%pf> " + "%s_RXQ invalid (size:%d in:%d out:%d), count %d\n", + ld->name, __builtin_return_address(0), + get_dev_name(dev), qsize, head, tail, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + udelay(100); + } + + *in = 0; + *out = 0; + return -EINVAL; +} + +static inline void reset_tx_circ(struct dpram_link_device *dpld, int dev) +{ + set_tx_head(dpld, dev, 0); + set_tx_tail(dpld, dev, 0); + if (dev == IPC_FMT) + trigger_force_cp_crash(dpld); +} + +static inline void reset_rx_circ(struct dpram_link_device *dpld, int dev) +{ +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + trigger_force_cp_crash(dpld); +#else + set_rx_tail(dpld, dev, get_rx_head(dpld, dev)); + if (dev == IPC_FMT) + trigger_force_cp_crash(dpld); +#endif +} + +/* +** CAUTION : dpram_allow_sleep() MUST be invoked after dpram_wake_up() success +*/ +static inline bool dpram_can_sleep(struct dpram_link_device *dpld) +{ + return dpld->need_wake_up; +} + +static int dpram_wake_up(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + + if (unlikely(!dpram_can_sleep(dpld))) + return 0; + + if (dpld->ext_op->wakeup(dpld) < 0) { + mif_err("%s: ERR! <%pf> DPRAM wakeup fail once\n", + ld->name, __builtin_return_address(0)); + + dpld->ext_op->sleep(dpld); + + udelay(10); + + if (dpld->ext_op->wakeup(dpld) < 0) { + mif_err("%s: ERR! <%pf> DPRAM wakeup fail twice\n", + ld->name, __builtin_return_address(0)); + return -EACCES; + } + } + + atomic_inc(&dpld->accessing); + return 0; +} + +static void dpram_allow_sleep(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + + if (unlikely(!dpram_can_sleep(dpld))) + return; + + if (atomic_dec_return(&dpld->accessing) <= 0) { + dpld->ext_op->sleep(dpld); + atomic_set(&dpld->accessing, 0); + mif_debug("%s: DPRAM sleep possible\n", ld->name); + } +} + +static int check_access(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + int i; + u16 magic = get_magic(dpld); + u16 access = get_access(dpld); + + if (likely(magic == DPRAM_MAGIC_CODE && access == 1)) + return 0; + + for (i = 1; i <= 100; i++) { + mif_info("%s: ERR! magic:%X access:%X -> retry:%d\n", + ld->name, magic, access, i); + udelay(100); + + magic = get_magic(dpld); + access = get_access(dpld); + if (likely(magic == DPRAM_MAGIC_CODE && access == 1)) + return 0; + } + + mif_info("%s: !CRISIS! magic:%X access:%X\n", ld->name, magic, access); + return -EACCES; +} + +static bool ipc_active(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + + /* Check DPRAM mode */ + if (ld->mode != LINK_MODE_IPC) { + mif_info("%s: <%pf> ld->mode != LINK_MODE_IPC\n", + ld->name, __builtin_return_address(0)); + return false; + } + + if (check_access(dpld) < 0) { + mif_info("%s: ERR! <%pf> check_access fail\n", + ld->name, __builtin_return_address(0)); + return false; + } + + return true; +} + +static void dpram_ipc_write(struct dpram_link_device *dpld, int dev, u32 qsize, + u32 in, u32 out, struct sk_buff *skb) +{ + struct link_device *ld = &dpld->ld; + u8 __iomem *buff = get_tx_buff(dpld, dev); + u8 *src = skb->data; + u32 len = skb->len; + u32 inp; + struct mif_irq_map map; + + circ_write(buff, src, qsize, in, len); + + /* update new in pointer */ + inp = in + len; + if (inp >= qsize) + inp -= qsize; + set_tx_head(dpld, dev, inp); + + if (dev == IPC_FMT) { +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + char tag[MIF_MAX_STR_LEN]; + snprintf(tag, MIF_MAX_STR_LEN, "%s: MIF2CP", ld->mc->name); + pr_ipc(tag, src, (len > 20 ? 20 : len)); + log_dpram_status(dpld, "MIF2CP"); +#endif + set_dpram_map(dpld, &map); + mif_irq_log(ld->mc->msd, map, "ipc_write", sizeof("ipc_write")); + mif_ipc_log(MIF_IPC_AP2CP, ld->mc->msd, skb->data, skb->len); + } + +#if 1 + if (ld->aligned && memcmp16_to_io((buff + in), src, 4)) { + mif_err("%s: memcmp16_to_io fail\n", ld->name); + trigger_force_cp_crash(dpld); + } +#endif +} + +static int dpram_ipc_tx(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; + struct sk_buff_head *txq = ld->skb_txq[dev]; + struct sk_buff *skb; + unsigned long int flags; + u32 qsize = get_tx_buff_size(dpld, dev); + u32 in; + u32 out; + int space; + int copied = 0; + + spin_lock_irqsave(&dpld->tx_lock[dev], flags); + + while (1) { + space = get_txq_space(dpld, dev, qsize, &in, &out); + if (unlikely(space < 0)) { + spin_unlock_irqrestore(&dpld->tx_lock[dev], flags); + reset_tx_circ(dpld, dev); + return space; + } + + skb = skb_dequeue(txq); + if (unlikely(!skb)) + break; + + if (unlikely(space < skb->len)) { + atomic_set(&dpld->res_required[dev], 1); + skb_queue_head(txq, skb); + spin_unlock_irqrestore(&dpld->tx_lock[dev], flags); + mif_info("%s: %s " + "qsize[%u] in[%u] out[%u] free[%u] < len[%u]\n", + ld->name, get_dev_name(dev), + qsize, in, out, space, skb->len); + return -ENOSPC; + } + + /* TX if there is enough room in the queue */ + dpram_ipc_write(dpld, dev, qsize, in, out, skb); + copied += skb->len; + dev_kfree_skb_any(skb); + } + + spin_unlock_irqrestore(&dpld->tx_lock[dev], flags); + + return copied; +} + +static int dpram_wait_for_res_ack(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; + struct completion *cmpl = &dpld->res_ack_cmpl[dev]; + unsigned long timeout = RES_ACK_WAIT_TIMEOUT; + int ret; + u16 mask; + + mask = get_mask_req_ack(dpld, dev); + mif_info("%s: Send REQ_ACK 0x%04X\n", ld->name, mask); + send_intr(dpld, INT_NON_CMD(mask)); + + ret = wait_for_completion_interruptible_timeout(cmpl, timeout); + /* ret == 0 on timeout, ret < 0 if interrupted */ + if (ret == 0) { + mif_err("%s: %s: TIMEOUT\n", ld->name, get_dev_name(dev)); + queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], 0); + } else if (ret < 0) { + mif_err("%s: %s: interrupted (ret %d)\n", + ld->name, get_dev_name(dev), ret); + } + + return ret; +} + +static int dpram_process_res_ack(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; + int ret; + u16 mask; + + ret = dpram_ipc_tx(dpld, dev); + if (ret < 0) { + if (ret == -ENOSPC) { + /* dpld->res_required[dev] is set in dpram_ipc_tx() */ + queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], 0); + } else { + mif_err("%s: ERR! ipc_tx fail (%d)\n", ld->name, ret); + } + } else { + if (ret > 0) { + mask = get_mask_send(dpld, dev); + send_intr(dpld, INT_NON_CMD(mask)); + mif_debug("%s: send intr 0x%04X\n", ld->name, mask); + } + atomic_set(&dpld->res_required[dev], 0); + } + + return ret; +} + +static void dpram_fmt_tx_work(struct work_struct *work) +{ + struct link_device *ld; + struct dpram_link_device *dpld; + int ret; + + ld = container_of(work, struct link_device, fmt_tx_dwork.work); + dpld = to_dpram_link_device(ld); + + ret = dpram_wait_for_res_ack(dpld, IPC_FMT); + /* ret == 0 on timeout, ret < 0 if interrupted */ + if (ret <= 0) + return; + + ret = dpram_process_res_ack(dpld, IPC_FMT); +} + +static void dpram_raw_tx_work(struct work_struct *work) +{ + struct link_device *ld; + struct dpram_link_device *dpld; + int ret; + + ld = container_of(work, struct link_device, raw_tx_dwork.work); + dpld = to_dpram_link_device(ld); + + ret = dpram_wait_for_res_ack(dpld, IPC_RAW); + /* ret == 0 on timeout, ret < 0 if interrupted */ + if (ret <= 0) + return; + + ret = dpram_process_res_ack(dpld, IPC_RAW); + if (ret > 0) + mif_netif_wake(ld); +} + +static void dpram_rfs_tx_work(struct work_struct *work) +{ + struct link_device *ld; + struct dpram_link_device *dpld; + int ret; + + ld = container_of(work, struct link_device, rfs_tx_dwork.work); + dpld = to_dpram_link_device(ld); + + ret = dpram_wait_for_res_ack(dpld, IPC_RFS); + /* ret == 0 on timeout, ret < 0 if interrupted */ + if (ret <= 0) + return; + + ret = dpram_process_res_ack(dpld, IPC_RFS); +} + +static void dpram_ipc_rx_task(unsigned long data) +{ + struct dpram_link_device *dpld = (struct dpram_link_device *)data; + struct link_device *ld = &dpld->ld; + struct io_device *iod; + struct mif_rxb *rxb; + unsigned qlen; + int i; + + for (i = 0; i < ld->max_ipc_dev; i++) { + iod = dpld->iod[i]; + qlen = rxbq_size(&dpld->rxbq[i]); + while (qlen > 0) { + rxb = rxbq_get_data_rxb(&dpld->rxbq[i]); + iod->recv(iod, ld, rxb->data, rxb->len); + rxb_clear(rxb); + qlen--; + } + } +} + +/* + ret < 0 : error + ret == 0 : no data + ret > 0 : valid data +*/ +static int dpram_recv_ipc_with_rxb(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; + struct mif_rxb *rxb; + u8 __iomem *src = get_rx_buff(dpld, dev); + u32 qsize = get_rx_buff_size(dpld, dev); + u32 in; + u32 out; + u32 rcvd; + struct mif_irq_map map; + + rcvd = get_rxq_rcvd(dpld, dev, qsize, &in, &out); + if (rcvd <= 0) + return rcvd; + + if (dev == IPC_FMT) { + set_dpram_map(dpld, &map); + mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv")); + } + + /* Allocate an rxb */ + rxb = rxbq_get_free_rxb(&dpld->rxbq[dev]); + if (!rxb) { + mif_info("%s: ERR! %s rxbq_get_free_rxb fail\n", + ld->name, get_dev_name(dev)); + return -ENOMEM; + } + + /* Read data from each DPRAM buffer */ + circ_read(rxb_put(rxb, rcvd), src, qsize, out, rcvd); + + /* Update tail (out) pointer */ + set_rx_tail(dpld, dev, in); + + return rcvd; +} + +/* + ret < 0 : error + ret == 0 : no data + ret > 0 : valid data +*/ +static int dpram_recv_ipc_with_skb(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; + struct io_device *iod = dpld->iod[dev]; + struct sk_buff *skb; + u8 __iomem *src = get_rx_buff(dpld, dev); + u32 qsize = get_rx_buff_size(dpld, dev); + u32 in; + u32 out; + u32 rcvd; + int rest; + u8 *frm; + u8 *dst; + unsigned int len; + unsigned int pad; + unsigned int tot; + struct mif_irq_map map; + + rcvd = get_rxq_rcvd(dpld, dev, qsize, &in, &out); + if (rcvd <= 0) + return rcvd; + + if (dev == IPC_FMT) { +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + log_dpram_status(dpld, "CP2MIF"); +#endif + set_dpram_map(dpld, &map); + mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv")); + } + + rest = rcvd; + while (rest > 0) { + /* Calculate the start of an SIPC5 frame */ + frm = src + out; + + /* Check the SIPC5 frame */ + len = sipc5_check_frame_in_dev(ld, dev, frm, rest); + if (len <= 0) { +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + u32 tail = get_rx_tail(dpld, dev); + log_dpram_status(dpld, "CP2MIF"); + save_ipc_trace(dpld, dev, src, qsize, in, tail, rcvd); + save_dpram_dump(dpld); +#endif + return -EBADMSG; + } + + /* Calculate total length with padding in DPRAM */ + pad = sipc5_calc_padding_size(len); + tot = len + pad; + + /* Allocate an skb */ + skb = dev_alloc_skb(tot); + if (!skb) { + mif_err("%s: ERR! %s dev_alloc_skb fail\n", + ld->name, get_dev_name(dev)); + return -ENOMEM; + } + + /* Read data from each DPRAM buffer */ + dst = skb_put(skb, tot); + circ_read(dst, src, qsize, out, tot); + skb_trim(skb, len); +#if 1 + if (ld->aligned && memcmp16_to_io((src + out), dst, 4)) { + mif_err("%s: memcmp16_to_io fail\n", ld->name); + trigger_force_cp_crash(dpld); + } +#endif +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + if (unlikely(dev == IPC_FMT)) { + char str[MIF_MAX_STR_LEN]; + snprintf(str, MIF_MAX_STR_LEN, "%s: CP2MIF", + ld->mc->name); + pr_ipc(str, skb->data, (skb->len > 20 ? 20 : skb->len)); + } +#endif + + /* Pass the IPC frame to IOD */ + iod->recv_skb(iod, ld, skb); + + /* Calculate new 'out' pointer */ + rest -= tot; + out += tot; + if (out >= qsize) + out -= qsize; + } + + /* Update tail (out) pointer */ + set_rx_tail(dpld, dev, in); + + return rcvd; +} + +static void non_command_handler(struct dpram_link_device *dpld, u16 intr) +{ + struct link_device *ld = &dpld->ld; + int i = 0; + int ret = 0; + u16 mask = 0; + + if (!ipc_active(dpld)) + return; + + /* Read data from DPRAM */ + for (i = 0; i < ld->max_ipc_dev; i++) { + if (dpld->use_skb) + ret = dpram_recv_ipc_with_skb(dpld, i); + else + ret = dpram_recv_ipc_with_rxb(dpld, i); + if (ret < 0) + reset_rx_circ(dpld, i); + + /* Check and process REQ_ACK (at this time, in == out) */ + if (intr & get_mask_req_ack(dpld, i)) { + mif_debug("%s: send %s_RES_ACK\n", + ld->name, get_dev_name(i)); + mask |= get_mask_res_ack(dpld, i); + } + } + + if (!dpld->use_skb) { + /* Schedule soft IRQ for RX */ + tasklet_hi_schedule(&dpld->rx_tsk); + } + + if (mask) { + send_intr(dpld, INT_NON_CMD(mask)); + mif_debug("%s: send intr 0x%04X\n", ld->name, mask); + } + + if (intr && INT_MASK_RES_ACK_SET) { + if (intr && INT_MASK_RES_ACK_R) + complete_all(&dpld->res_ack_cmpl[IPC_RAW]); + else if (intr && INT_MASK_RES_ACK_F) + complete_all(&dpld->res_ack_cmpl[IPC_FMT]); + else + complete_all(&dpld->res_ack_cmpl[IPC_RFS]); + } +} + +static void handle_cp_crash(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + struct io_device *iod; + int i; + + mif_netif_stop(ld); + + for (i = 0; i < ld->max_ipc_dev; i++) { + mif_info("%s: purging %s_skb_txq\n", ld->name, get_dev_name(i)); + skb_queue_purge(ld->skb_txq[i]); + } + + iod = link_get_iod_with_format(ld, IPC_FMT); + iod->modem_state_changed(iod, STATE_CRASH_EXIT); + + iod = link_get_iod_with_format(ld, IPC_BOOT); + iod->modem_state_changed(iod, STATE_CRASH_EXIT); +} + +static void handle_no_crash_ack(unsigned long arg) +{ + struct dpram_link_device *dpld = (struct dpram_link_device *)arg; + struct link_device *ld = &dpld->ld; + + mif_err("%s: ERR! No CRASH_EXIT ACK from CP\n", ld->mc->name); + + if (!wake_lock_active(&dpld->wlock)) + wake_lock(&dpld->wlock); + + handle_cp_crash(dpld); +} + +static int trigger_force_cp_crash(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + + if (ld->mode == LINK_MODE_ULOAD) { + mif_err("%s: CP crash is already in progress\n", ld->mc->name); + return 0; + } + + disable_irq_nosync(dpld->irq); + + ld->mode = LINK_MODE_ULOAD; + mif_err("%s: called by %pf\n", ld->name, __builtin_return_address(0)); + + dpram_wake_up(dpld); +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + save_dpram_dump(dpld); +#endif + + enable_irq(dpld->irq); + + send_intr(dpld, INT_CMD(INT_CMD_CRASH_EXIT)); + + mif_add_timer(&dpld->crash_ack_timer, FORCE_CRASH_ACK_TIMEOUT, + handle_no_crash_ack, (unsigned long)dpld); + + return 0; +} + +static int dpram_init_ipc(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + int i; + + if (ld->mode == LINK_MODE_IPC && + get_magic(dpld) == DPRAM_MAGIC_CODE && + get_access(dpld) == 1) + mif_info("%s: IPC already initialized\n", ld->name); + + /* Clear pointers in every circular queue */ + for (i = 0; i < ld->max_ipc_dev; i++) { + set_tx_head(dpld, i, 0); + set_tx_tail(dpld, i, 0); + set_rx_head(dpld, i, 0); + set_rx_tail(dpld, i, 0); + } + + /* Initialize variables for efficient TX/RX processing */ + for (i = 0; i < ld->max_ipc_dev; i++) + dpld->iod[i] = link_get_iod_with_format(ld, i); + dpld->iod[IPC_RAW] = link_get_iod_with_format(ld, IPC_MULTI_RAW); + + if (dpld->iod[IPC_RAW]->recv_skb) + dpld->use_skb = true; + + for (i = 0; i < ld->max_ipc_dev; i++) { + spin_lock_init(&dpld->tx_lock[i]); + atomic_set(&dpld->res_required[i], 0); + } + + /* Enable IPC */ + atomic_set(&dpld->accessing, 0); + + set_magic(dpld, DPRAM_MAGIC_CODE); + set_access(dpld, 1); + if (get_magic(dpld) != DPRAM_MAGIC_CODE || get_access(dpld) != 1) + return -EACCES; + + ld->mode = LINK_MODE_IPC; + + if (wake_lock_active(&dpld->wlock)) + wake_unlock(&dpld->wlock); + + return 0; +} + +static void cmd_req_active_handler(struct dpram_link_device *dpld) +{ + send_intr(dpld, INT_CMD(INT_CMD_RES_ACTIVE)); +} + +static void cmd_crash_reset_handler(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + struct io_device *iod = NULL; + + ld->mode = LINK_MODE_ULOAD; + + if (!wake_lock_active(&dpld->wlock)) + wake_lock(&dpld->wlock); + + mif_err("%s: Recv 0xC7 (CRASH_RESET)\n", ld->name); + + iod = link_get_iod_with_format(ld, IPC_FMT); + iod->modem_state_changed(iod, STATE_CRASH_RESET); + + iod = link_get_iod_with_format(ld, IPC_BOOT); + iod->modem_state_changed(iod, STATE_CRASH_RESET); +} + +static void cmd_crash_exit_handler(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + + ld->mode = LINK_MODE_ULOAD; + + if (!wake_lock_active(&dpld->wlock)) + wake_lock(&dpld->wlock); + + del_timer(&dpld->crash_ack_timer); + + dpram_wake_up(dpld); +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + save_dpram_dump(dpld); +#endif + + if (dpld->ext_op && dpld->ext_op->crash_log) + dpld->ext_op->crash_log(dpld); + + mif_err("%s: Recv 0xC9 (CRASH_EXIT)\n", ld->name); + + handle_cp_crash(dpld); +} + +static void cmd_phone_start_handler(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + struct io_device *iod = NULL; + + mif_info("%s: Recv 0xC8 (CP_START)\n", ld->name); + + dpram_init_ipc(dpld); + + iod = link_get_iod_with_format(ld, IPC_FMT); + if (!iod) { + mif_info("%s: ERR! no iod\n", ld->name); + return; + } + + if (dpld->ext_op && dpld->ext_op->cp_start_handler) + dpld->ext_op->cp_start_handler(dpld); + + if (ld->mc->phone_state != STATE_ONLINE) { + mif_info("%s: phone_state: %d -> ONLINE\n", + ld->name, ld->mc->phone_state); + iod->modem_state_changed(iod, STATE_ONLINE); + } + + mif_info("%s: Send 0xC2 (INIT_END)\n", ld->name); + send_intr(dpld, INT_CMD(INT_CMD_INIT_END)); +} + +static void command_handler(struct dpram_link_device *dpld, u16 cmd) +{ + struct link_device *ld = &dpld->ld; + + switch (INT_CMD_MASK(cmd)) { + case INT_CMD_REQ_ACTIVE: + cmd_req_active_handler(dpld); + break; + + case INT_CMD_CRASH_RESET: + dpld->init_status = DPRAM_INIT_STATE_NONE; + cmd_crash_reset_handler(dpld); + break; + + case INT_CMD_CRASH_EXIT: + dpld->init_status = DPRAM_INIT_STATE_NONE; + cmd_crash_exit_handler(dpld); + break; + + case INT_CMD_PHONE_START: + dpld->init_status = DPRAM_INIT_STATE_READY; + cmd_phone_start_handler(dpld); + complete_all(&dpld->dpram_init_cmd); + break; + + case INT_CMD_NV_REBUILDING: + mif_info("%s: NV_REBUILDING\n", ld->name); + break; + + case INT_CMD_PIF_INIT_DONE: + complete_all(&dpld->modem_pif_init_done); + break; + + case INT_CMD_SILENT_NV_REBUILDING: + mif_info("%s: SILENT_NV_REBUILDING\n", ld->name); + break; + + case INT_CMD_NORMAL_PWR_OFF: + /*ToDo:*/ + /*kernel_sec_set_cp_ack()*/; + break; + + case INT_CMD_REQ_TIME_SYNC: + case INT_CMD_CP_DEEP_SLEEP: + case INT_CMD_EMER_DOWN: + break; + + default: + mif_info("%s: unknown command 0x%04X\n", ld->name, cmd); + } +} + +static void ext_command_handler(struct dpram_link_device *dpld, u16 cmd) +{ + struct link_device *ld = &dpld->ld; + u16 resp; + + switch (EXT_CMD_MASK(cmd)) { + case EXT_CMD_SET_SPEED_LOW: + if (dpld->dpctl->setup_speed) { + dpld->dpctl->setup_speed(DPRAM_SPEED_LOW); + resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_LOW); + send_intr(dpld, resp); + } + break; + + case EXT_CMD_SET_SPEED_MID: + if (dpld->dpctl->setup_speed) { + dpld->dpctl->setup_speed(DPRAM_SPEED_MID); + resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_MID); + send_intr(dpld, resp); + } + break; + + case EXT_CMD_SET_SPEED_HIGH: + if (dpld->dpctl->setup_speed) { + dpld->dpctl->setup_speed(DPRAM_SPEED_HIGH); + resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_HIGH); + send_intr(dpld, resp); + } + break; + + default: + mif_info("%s: unknown command 0x%04X\n", ld->name, cmd); + break; + } +} + +static void udl_command_handler(struct dpram_link_device *dpld, u16 cmd) +{ + struct link_device *ld = &dpld->ld; + + if (cmd & UDL_RESULT_FAIL) { + mif_info("%s: ERR! Command failed: %04x\n", ld->name, cmd); + return; + } + + switch (UDL_CMD_MASK(cmd)) { + case UDL_CMD_RECV_READY: + mif_debug("%s: Send CP-->AP RECEIVE_READY\n", ld->name); + send_intr(dpld, CMD_IMG_START_REQ); + break; + default: + complete_all(&dpld->udl_cmd_complete); + } +} + +static inline void dpram_ipc_rx(struct dpram_link_device *dpld, u16 intr) +{ + if (unlikely(INT_CMD_VALID(intr))) + command_handler(dpld, intr); + else + non_command_handler(dpld, intr); +} + +static inline void dpram_intr_handler(struct dpram_link_device *dpld, u16 intr) +{ + char *name = dpld->ld.name; + + if (unlikely(intr == INT_POWERSAFE_FAIL)) { + mif_info("%s: intr == INT_POWERSAFE_FAIL\n", name); + return; + } + + if (unlikely(EXT_UDL_CMD(intr))) { + if (likely(EXT_INT_VALID(intr))) { + if (UDL_CMD_VALID(intr)) + udl_command_handler(dpld, intr); + else if (EXT_CMD_VALID(intr)) + ext_command_handler(dpld, intr); + else + mif_info("%s: ERR! invalid intr 0x%04X\n", + name, intr); + } else { + mif_info("%s: ERR! invalid intr 0x%04X\n", name, intr); + } + return; + } + + if (likely(INT_VALID(intr))) + dpram_ipc_rx(dpld, intr); + else + mif_info("%s: ERR! invalid intr 0x%04X\n", name, intr); +} + +static irqreturn_t ap_idpram_irq_handler(int irq, void *data) +{ + struct dpram_link_device *dpld = (struct dpram_link_device *)data; + struct link_device *ld = (struct link_device *)&dpld->ld; + u16 int2ap = recv_intr(dpld); + + if (unlikely(ld->mode == LINK_MODE_OFFLINE)) + return IRQ_HANDLED; + + dpram_intr_handler(dpld, int2ap); + + return IRQ_HANDLED; +} + +static irqreturn_t cp_idpram_irq_handler(int irq, void *data) +{ + struct dpram_link_device *dpld = (struct dpram_link_device *)data; + struct link_device *ld = (struct link_device *)&dpld->ld; + u16 int2ap; + + if (unlikely(ld->mode == LINK_MODE_OFFLINE)) + return IRQ_HANDLED; + + if (dpram_wake_up(dpld) < 0) { + trigger_force_cp_crash(dpld); + return IRQ_HANDLED; + } + + int2ap = recv_intr(dpld); + + dpram_intr_handler(dpld, int2ap); + + clear_intr(dpld); + + dpram_allow_sleep(dpld); + + return IRQ_HANDLED; +} + +static irqreturn_t ext_dpram_irq_handler(int irq, void *data) +{ + struct dpram_link_device *dpld = (struct dpram_link_device *)data; + struct link_device *ld = (struct link_device *)&dpld->ld; + u16 int2ap = recv_intr(dpld); + + if (unlikely(ld->mode == LINK_MODE_OFFLINE)) + return IRQ_HANDLED; + + dpram_intr_handler(dpld, int2ap); + + return IRQ_HANDLED; +} + +static void dpram_send_ipc(struct link_device *ld, int dev, + struct io_device *iod, struct sk_buff *skb) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + struct sk_buff_head *txq = ld->skb_txq[dev]; + int ret; + u16 mask; + + if (unlikely(txq->qlen >= MAX_SKB_TXQ_DEPTH)) { + mif_err("%s: %s txq->qlen %d >= %d\n", ld->name, + get_dev_name(dev), txq->qlen, MAX_SKB_TXQ_DEPTH); + if (iod->io_typ == IODEV_NET || iod->format == IPC_MULTI_RAW) { + dev_kfree_skb_any(skb); + return; + } + } + + skb_queue_tail(txq, skb); + + if (dpram_wake_up(dpld) < 0) { + trigger_force_cp_crash(dpld); + return; + } + + if (!ipc_active(dpld)) { + mif_info("%s: IPC is NOT active\n", ld->name); + goto exit; + } + + if (atomic_read(&dpld->res_required[dev]) > 0) { + mif_info("%s: %s_TXQ is full\n", ld->name, get_dev_name(dev)); + goto exit; + } + + ret = dpram_ipc_tx(dpld, dev); + if (ret > 0) { + mask = get_mask_send(dpld, dev); + send_intr(dpld, INT_NON_CMD(mask)); + } else if (ret == -ENOSPC) { + /* + ** dpld->res_required[dev] is set in dpram_ipc_tx() + */ + if (dev == IPC_RAW) + mif_netif_stop(ld); + queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], 0); + } else { + mif_info("%s: dpram_ipc_tx fail (err %d)\n", ld->name, ret); + } + +exit: + dpram_allow_sleep(dpld); +} + +static int dpram_send_cp_binary(struct link_device *ld, struct sk_buff *skb) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + + if (dpld->ext_op && dpld->ext_op->download_binary) + return dpld->ext_op->download_binary(dpld, skb); + else + return -ENODEV; +} + +static int dpram_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + enum dev_format dev = iod->format; + int len = skb->len; + + switch (dev) { + case IPC_FMT: + case IPC_RAW: + case IPC_RFS: + if (likely(ld->mode == LINK_MODE_IPC)) { + dpram_send_ipc(ld, dev, iod, skb); + } else { + mif_info("%s: ld->mode != LINK_MODE_IPC\n", ld->name); + dev_kfree_skb_any(skb); + } + return len; + + case IPC_BOOT: + return dpram_send_cp_binary(ld, skb); + + default: + mif_info("%s: ERR! no TXQ for %s\n", ld->name, iod->name); + dev_kfree_skb_any(skb); + return -ENODEV; + } +} + +static int dpram_force_dump(struct link_device *ld, struct io_device *iod) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + trigger_force_cp_crash(dpld); + return 0; +} + +static int dpram_dump_start(struct link_device *ld, struct io_device *iod) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + + if (dpld->ext_op && dpld->ext_op->dump_start) + return dpld->ext_op->dump_start(dpld); + else + return -ENODEV; +} + +static int dpram_dump_update(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + + if (dpld->ext_op && dpld->ext_op->dump_update) + return dpld->ext_op->dump_update(dpld, (void *)arg); + else + return -ENODEV; +} + +static int dpram_ioctl(struct link_device *ld, struct io_device *iod, + unsigned int cmd, unsigned long arg) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + int err = 0; + + mif_info("%s: cmd 0x%08X\n", ld->name, cmd); + + switch (cmd) { + case IOCTL_DPRAM_INIT_STATUS: + mif_debug("%s: get dpram init status\n", ld->name); + return dpld->init_status; + + default: + if (dpld->ext_ioctl) { + err = dpld->ext_ioctl(dpld, iod, cmd, arg); + } else { + mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd); + err = -EINVAL; + } + + break; + } + + return err; +} + +static void dpram_dump_memory(struct link_device *ld, char *buff) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + dpram_wake_up(dpld); + memcpy(buff, dpld->base, dpld->size); + dpram_allow_sleep(dpld); +} + +static void dpram_remap_std_16k_region(struct dpram_link_device *dpld) +{ + struct dpram_ipc_16k_map *dpram_map; + struct dpram_ipc_device *dev; + + dpram_map = (struct dpram_ipc_16k_map *)dpld->base; + + /* magic code and access enable fields */ + dpld->ipc_map.magic = (u16 __iomem *)&dpram_map->magic; + dpld->ipc_map.access = (u16 __iomem *)&dpram_map->access; + + /* FMT */ + dev = &dpld->ipc_map.dev[IPC_FMT]; + + strcpy(dev->name, "FMT"); + dev->id = IPC_FMT; + + dev->txq.head = (u16 __iomem *)&dpram_map->fmt_tx_head; + dev->txq.tail = (u16 __iomem *)&dpram_map->fmt_tx_tail; + dev->txq.buff = (u8 __iomem *)&dpram_map->fmt_tx_buff[0]; + dev->txq.size = DP_16K_FMT_TX_BUFF_SZ; + + dev->rxq.head = (u16 __iomem *)&dpram_map->fmt_rx_head; + dev->rxq.tail = (u16 __iomem *)&dpram_map->fmt_rx_tail; + dev->rxq.buff = (u8 __iomem *)&dpram_map->fmt_rx_buff[0]; + dev->rxq.size = DP_16K_FMT_RX_BUFF_SZ; + + dev->mask_req_ack = INT_MASK_REQ_ACK_F; + dev->mask_res_ack = INT_MASK_RES_ACK_F; + dev->mask_send = INT_MASK_SEND_F; + + /* RAW */ + dev = &dpld->ipc_map.dev[IPC_RAW]; + + strcpy(dev->name, "RAW"); + dev->id = IPC_RAW; + + dev->txq.head = (u16 __iomem *)&dpram_map->raw_tx_head; + dev->txq.tail = (u16 __iomem *)&dpram_map->raw_tx_tail; + dev->txq.buff = (u8 __iomem *)&dpram_map->raw_tx_buff[0]; + dev->txq.size = DP_16K_RAW_TX_BUFF_SZ; + + dev->rxq.head = (u16 __iomem *)&dpram_map->raw_rx_head; + dev->rxq.tail = (u16 __iomem *)&dpram_map->raw_rx_tail; + dev->rxq.buff = (u8 __iomem *)&dpram_map->raw_rx_buff[0]; + dev->rxq.size = DP_16K_RAW_RX_BUFF_SZ; + + dev->mask_req_ack = INT_MASK_REQ_ACK_R; + dev->mask_res_ack = INT_MASK_RES_ACK_R; + dev->mask_send = INT_MASK_SEND_R; + + /* interrupt ports */ + dpld->ipc_map.mbx_cp2ap = (u16 __iomem *)&dpram_map->mbx_cp2ap; + dpld->ipc_map.mbx_ap2cp = (u16 __iomem *)&dpram_map->mbx_ap2cp; +} + +static int dpram_table_init(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + u8 __iomem *dp_base; + int i; + + if (!dpld->base) { + mif_info("%s: ERR! dpld->base == NULL\n", ld->name); + return -EINVAL; + } + dp_base = dpld->base; + + /* Map for booting */ + if (dpld->ext_op && dpld->ext_op->init_boot_map) { + dpld->ext_op->init_boot_map(dpld); + } else { + dpld->bt_map.magic = (u32 *)(dp_base); + dpld->bt_map.buff = (u8 *)(dp_base + DP_BOOT_BUFF_OFFSET); + dpld->bt_map.size = dpld->size - 8; + } + + /* Map for download (FOTA, UDL, etc.) */ + if (dpld->ext_op && dpld->ext_op->init_dl_map) { + dpld->ext_op->init_dl_map(dpld); + } else { + dpld->dl_map.magic = (u32 *)(dp_base); + dpld->dl_map.buff = (u8 *)(dp_base + DP_DLOAD_BUFF_OFFSET); + } + + /* Map for upload mode */ + if (dpld->ext_op && dpld->ext_op->init_ul_map) { + dpld->ext_op->init_ul_map(dpld); + } else { + dpld->ul_map.magic = (u32 *)(dp_base); + dpld->ul_map.buff = (u8 *)(dp_base + DP_ULOAD_BUFF_OFFSET); + } + + /* Map for IPC */ + if (dpld->ext_op && dpld->ext_op->init_ipc_map) { + dpld->ext_op->init_ipc_map(dpld); + } else if (dpld->dpctl->ipc_map) { + memcpy(&dpld->ipc_map, dpld->dpctl->ipc_map, + sizeof(struct dpram_ipc_map)); + } else { + if (dpld->size == DPRAM_SIZE_16KB) + dpram_remap_std_16k_region(dpld); + else + return -EINVAL; + } + + dpld->magic = dpld->ipc_map.magic; + dpld->access = dpld->ipc_map.access; + for (i = 0; i < ld->max_ipc_dev; i++) + dpld->dev[i] = &dpld->ipc_map.dev[i]; + dpld->mbx2ap = dpld->ipc_map.mbx_cp2ap; + dpld->mbx2cp = dpld->ipc_map.mbx_ap2cp; + + return 0; +} + +static void dpram_setup_common_op(struct dpram_link_device *dpld) +{ + dpld->recv_intr = recv_intr; + dpld->send_intr = send_intr; + dpld->get_magic = get_magic; + dpld->set_magic = set_magic; + dpld->get_access = get_access; + dpld->set_access = set_access; + dpld->get_tx_head = get_tx_head; + dpld->get_tx_tail = get_tx_tail; + dpld->set_tx_head = set_tx_head; + dpld->set_tx_tail = set_tx_tail; + dpld->get_tx_buff = get_tx_buff; + dpld->get_tx_buff_size = get_tx_buff_size; + dpld->get_rx_head = get_rx_head; + dpld->get_rx_tail = get_rx_tail; + dpld->set_rx_head = set_rx_head; + dpld->set_rx_tail = set_rx_tail; + dpld->get_rx_buff = get_rx_buff; + dpld->get_rx_buff_size = get_rx_buff_size; + dpld->get_mask_req_ack = get_mask_req_ack; + dpld->get_mask_res_ack = get_mask_res_ack; + dpld->get_mask_send = get_mask_send; + dpld->ipc_rx_handler = dpram_intr_handler; +} + +static int dpram_link_init(struct link_device *ld, struct io_device *iod) +{ + return 0; +} + +static void dpram_link_terminate(struct link_device *ld, struct io_device *iod) +{ + if (iod->format == IPC_FMT && ld->mode == LINK_MODE_IPC) { + if (!atomic_read(&iod->opened)) { + ld->mode = LINK_MODE_OFFLINE; + mif_err("%s: %s: link mode is changed: IPC->OFFLINE\n", + iod->name, ld->name); + } + } + + return; +} + +struct link_device *dpram_create_link_device(struct platform_device *pdev) +{ + struct dpram_link_device *dpld = NULL; + struct link_device *ld = NULL; + struct modem_data *modem = NULL; + struct modemlink_dpram_control *dpctl = NULL; + struct resource *res = NULL; + resource_size_t res_size; + unsigned long task_data; + int ret = 0; + int i = 0; + int bsize; + int qsize; + + /* + ** Alloc an instance of the DPRAM link device structure + */ + dpld = kzalloc(sizeof(struct dpram_link_device), GFP_KERNEL); + if (!dpld) { + mif_err("ERR! kzalloc dpld fail\n"); + goto err; + } + ld = &dpld->ld; + + /* + ** Get the modem (platform) data + */ + modem = (struct modem_data *)pdev->dev.platform_data; + if (!modem) { + mif_err("ERR! modem == NULL\n"); + goto err; + } + mif_info("modem = %s\n", modem->name); + mif_info("link device = %s\n", modem->link_name); + + /* + ** Retrieve modem data and DPRAM control data from the modem data + */ + ld->mdm_data = modem; + ld->name = modem->link_name; + ld->ipc_version = modem->ipc_version; + + if (!modem->dpram_ctl) { + mif_err("ERR! modem->dpram_ctl == NULL\n"); + goto err; + } + dpctl = modem->dpram_ctl; + + dpld->dpctl = dpctl; + dpld->type = dpctl->dp_type; + + if (ld->ipc_version < SIPC_VER_50) { + if (!dpctl->max_ipc_dev) { + mif_err("%s: ERR! no max_ipc_dev\n", ld->name); + goto err; + } + + ld->aligned = dpctl->aligned; + ld->max_ipc_dev = dpctl->max_ipc_dev; + } else { + ld->aligned = 1; + ld->max_ipc_dev = MAX_SIPC5_DEV; + } + + /* + ** Set attributes as a link device + */ + ld->init_comm = dpram_link_init; + ld->terminate_comm = dpram_link_terminate; + ld->send = dpram_send; + ld->force_dump = dpram_force_dump; + ld->dump_start = dpram_dump_start; + ld->dump_update = dpram_dump_update; + ld->ioctl = dpram_ioctl; + + INIT_LIST_HEAD(&ld->list); + + skb_queue_head_init(&ld->sk_fmt_tx_q); + skb_queue_head_init(&ld->sk_raw_tx_q); + skb_queue_head_init(&ld->sk_rfs_tx_q); + ld->skb_txq[IPC_FMT] = &ld->sk_fmt_tx_q; + ld->skb_txq[IPC_RAW] = &ld->sk_raw_tx_q; + ld->skb_txq[IPC_RFS] = &ld->sk_rfs_tx_q; + + /* + ** Set up function pointers + */ + dpram_setup_common_op(dpld); + dpld->dpram_dump = dpram_dump_memory; + dpld->ext_op = dpram_get_ext_op(modem->modem_type); + if (dpld->ext_op && dpld->ext_op->ioctl) + dpld->ext_ioctl = dpld->ext_op->ioctl; + if (dpld->ext_op && dpld->ext_op->wakeup && dpld->ext_op->sleep) + dpld->need_wake_up = true; + if (dpld->ext_op && dpld->ext_op->clear_intr) + dpld->need_intr_clear = true; + + /* + ** Retrieve DPRAM resource + */ + if (!dpctl->dp_base) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + STR_DPRAM_BASE); + if (!res) { + mif_err("%s: ERR! no DPRAM resource\n", ld->name); + goto err; + } + res_size = resource_size(res); + + dpctl->dp_base = ioremap_nocache(res->start, res_size); + if (!dpctl->dp_base) { + mif_err("%s: ERR! ioremap_nocache for BASE fail\n", + ld->name); + goto err; + } + dpctl->dp_size = res_size; + } + dpld->base = dpctl->dp_base; + dpld->size = dpctl->dp_size; + + mif_info("%s: type %d, aligned %d, base 0x%08X, size %d\n", + ld->name, dpld->type, ld->aligned, (int)dpld->base, dpld->size); + + /* + ** Retrieve DPRAM SFR resource if exists + */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + STR_DPRAM_SFR_BASE); + if (res) { + res_size = resource_size(res); + dpld->sfr_base = ioremap_nocache(res->start, res_size); + if (!dpld->sfr_base) { + mif_err("%s: ERR! ioremap_nocache for SFR fail\n", + ld->name); + goto err; + } + } + + /* Initialize DPRAM map (physical map -> logical map) */ + ret = dpram_table_init(dpld); + if (ret < 0) { + mif_err("%s: ERR! dpram_table_init fail (err %d)\n", + ld->name, ret); + goto err; + } + + /* Disable IPC */ + set_magic(dpld, 0); + set_access(dpld, 0); + dpld->init_status = DPRAM_INIT_STATE_NONE; + + /* Initialize locks, completions, and bottom halves */ + snprintf(dpld->wlock_name, MIF_MAX_NAME_LEN, "%s_wlock", ld->name); + wake_lock_init(&dpld->wlock, WAKE_LOCK_SUSPEND, dpld->wlock_name); + + init_completion(&dpld->dpram_init_cmd); + init_completion(&dpld->modem_pif_init_done); + init_completion(&dpld->udl_start_complete); + init_completion(&dpld->udl_cmd_complete); + init_completion(&dpld->crash_start_complete); + init_completion(&dpld->crash_recv_done); + for (i = 0; i < ld->max_ipc_dev; i++) + init_completion(&dpld->res_ack_cmpl[i]); + + task_data = (unsigned long)dpld; + tasklet_init(&dpld->rx_tsk, dpram_ipc_rx_task, task_data); + + ld->tx_wq = create_singlethread_workqueue("dpram_tx_wq"); + if (!ld->tx_wq) { + mif_err("%s: ERR! fail to create tx_wq\n", ld->name); + goto err; + } + INIT_DELAYED_WORK(&ld->fmt_tx_dwork, dpram_fmt_tx_work); + INIT_DELAYED_WORK(&ld->raw_tx_dwork, dpram_raw_tx_work); + INIT_DELAYED_WORK(&ld->rfs_tx_dwork, dpram_rfs_tx_work); + ld->tx_dwork[IPC_FMT] = &ld->fmt_tx_dwork; + ld->tx_dwork[IPC_RAW] = &ld->raw_tx_dwork; + ld->tx_dwork[IPC_RFS] = &ld->rfs_tx_dwork; + +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + INIT_DELAYED_WORK(&dpld->dump_dwork, save_dpram_dump_work); + INIT_DELAYED_WORK(&dpld->trace_dwork, save_ipc_trace_work); + spin_lock_init(&dpld->dump_list.lock); + spin_lock_init(&dpld->trace_list.lock); +#endif + + /* Prepare RXB queue */ + qsize = DPRAM_MAX_RXBQ_SIZE; + for (i = 0; i < ld->max_ipc_dev; i++) { + bsize = rxbq_get_page_size(get_rx_buff_size(dpld, i)); + dpld->rxbq[i].size = qsize; + dpld->rxbq[i].in = 0; + dpld->rxbq[i].out = 0; + dpld->rxbq[i].rxb = rxbq_create_pool(bsize, qsize); + if (!dpld->rxbq[i].rxb) { + mif_err("%s: ERR! %s rxbq_create_pool fail\n", + ld->name, get_dev_name(i)); + goto err; + } + mif_info("%s: %s rxbq_pool created (bsize:%d, qsize:%d)\n", + ld->name, get_dev_name(i), bsize, qsize); + } + + /* Prepare a multi-purpose miscellaneous buffer */ + dpld->buff = kzalloc(dpld->size, GFP_KERNEL); + if (!dpld->buff) { + mif_err("%s: ERR! kzalloc dpld->buff fail\n", ld->name); + goto err; + } + + /* + ** Retrieve DPRAM IRQ GPIO#, IRQ#, and IRQ flags + */ + dpld->gpio_dpram_int = modem->gpio_dpram_int; + + if (dpctl->dpram_irq) { + dpld->irq = dpctl->dpram_irq; + } else { + dpld->irq = platform_get_irq_byname(pdev, STR_DPRAM_IRQ); + if (dpld->irq < 0) { + mif_err("%s: ERR! no DPRAM IRQ resource\n", ld->name); + goto err; + } + } + + if (dpctl->dpram_irq_flags) + dpld->irq_flags = dpctl->dpram_irq_flags; + else + dpld->irq_flags = (IRQF_NO_SUSPEND | IRQF_TRIGGER_LOW); + + /* + ** Register DPRAM interrupt handler + */ + snprintf(dpld->irq_name, MIF_MAX_NAME_LEN, "%s_irq", ld->name); + if (dpld->ext_op && dpld->ext_op->irq_handler) + dpld->irq_handler = dpld->ext_op->irq_handler; + else if (dpld->type == CP_IDPRAM) + dpld->irq_handler = cp_idpram_irq_handler; + else if (dpld->type == AP_IDPRAM) + dpld->irq_handler = ap_idpram_irq_handler; + else + dpld->irq_handler = ext_dpram_irq_handler; + + ret = register_isr(dpld->irq, dpld->irq_handler, dpld->irq_flags, + dpld->irq_name, dpld); + if (ret) + goto err; + + return ld; + +err: + if (dpld) { + if (dpld->buff) + kfree(dpld->buff); + kfree(dpld); + } + + return NULL; +} + diff --git a/drivers/misc/modem_if/modem_link_device_dpram.h b/drivers/misc/modem_if/modem_link_device_dpram.h new file mode 100644 index 00000000000..8706fd0f339 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_dpram.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MODEM_LINK_DEVICE_DPRAM_H__ +#define __MODEM_LINK_DEVICE_DPRAM_H__ + +#include "modem_link_device_memory.h" + +/* + magic_code + + access_enable + + fmt_tx_head + fmt_tx_tail + fmt_tx_buff + + raw_tx_head + raw_tx_tail + raw_tx_buff + + fmt_rx_head + fmt_rx_tail + fmt_rx_buff + + raw_rx_head + raw_rx_tail + raw_rx_buff + + mbx_cp2ap + + mbx_ap2cp + = 2 + + 2 + + 2 + 2 + 1336 + + 2 + 2 + 4564 + + 2 + 2 + 1336 + + 2 + 2 + 9124 + + 2 + + 2 + = 16384 +*/ +#define DP_16K_FMT_TX_BUFF_SZ 1336 +#define DP_16K_RAW_TX_BUFF_SZ 4564 +#define DP_16K_FMT_RX_BUFF_SZ 1336 +#define DP_16K_RAW_RX_BUFF_SZ 9124 + +struct dpram_ipc_16k_map { + u16 magic; + u16 access; + + u16 fmt_tx_head; + u16 fmt_tx_tail; + u8 fmt_tx_buff[DP_16K_FMT_TX_BUFF_SZ]; + + u16 raw_tx_head; + u16 raw_tx_tail; + u8 raw_tx_buff[DP_16K_RAW_TX_BUFF_SZ]; + + u16 fmt_rx_head; + u16 fmt_rx_tail; + u8 fmt_rx_buff[DP_16K_FMT_RX_BUFF_SZ]; + + u16 raw_rx_head; + u16 raw_rx_tail; + u8 raw_rx_buff[DP_16K_RAW_RX_BUFF_SZ]; + + u16 mbx_cp2ap; + u16 mbx_ap2cp; +}; + +struct dpram_sfr { + u16 __iomem *int2cp; + u16 __iomem *int2ap; + u16 __iomem *clr_int2ap; + u16 __iomem *reset; + u16 __iomem *msg2cp; + u16 __iomem *msg2ap; +}; + +struct dpram_ext_op; + +struct dpram_link_device { + struct link_device ld; + + /* DPRAM address and size */ + enum dpram_type type; /* DPRAM type */ + u8 __iomem *base; /* Virtual address of DPRAM */ + u32 size; /* DPRAM size */ + + /* Whether or not this DPRAM can go asleep */ + bool need_wake_up; + + /* Whether or not this DPRAM needs interrupt clearing */ + bool need_intr_clear; + + /* DPRAM SFR */ + u8 __iomem *sfr_base; /* Virtual address of SFR */ + struct dpram_sfr sfr; + + /* DPRAM IRQ GPIO# */ + unsigned gpio_dpram_int; + + /* DPRAM IRQ from CP */ + int irq; + unsigned long irq_flags; + char irq_name[MIF_MAX_NAME_LEN]; + + /* Link to DPRAM control functions dependent on each platform */ + struct modemlink_dpram_control *dpctl; + + /* Physical configuration -> logical configuration */ + union { + struct dpram_boot_map bt_map; + struct qc_dpram_boot_map qc_bt_map; + }; + + struct dpram_dload_map dl_map; + struct dpram_uload_map ul_map; + + /* IPC device map */ + struct dpram_ipc_map ipc_map; + + /* Pointers (aliases) to IPC device map */ + u16 __iomem *magic; + u16 __iomem *access; + struct dpram_ipc_device *dev[MAX_IPC_DEV]; + u16 __iomem *mbx2ap; + u16 __iomem *mbx2cp; + + /* Wakelock for DPRAM device */ + struct wake_lock wlock; + char wlock_name[MIF_MAX_NAME_LEN]; + + /* For booting */ + unsigned boot_start_complete; + struct completion dpram_init_cmd; + struct completion modem_pif_init_done; + + /* For UDL */ + struct tasklet_struct ul_tsk; + struct tasklet_struct dl_tsk; + struct completion udl_start_complete; + struct completion udl_cmd_complete; + struct dpram_udl_check udl_check; + struct dpram_udl_param udl_param; + + /* For CP crash dump */ + struct timer_list crash_ack_timer; + struct completion crash_start_complete; + struct completion crash_recv_done; + struct timer_list crash_timer; + int crash_rcvd; /* Count of CP crash dump packets received */ + + /* For locking TX process */ + spinlock_t tx_lock[MAX_IPC_DEV]; + + /* For TX under DPRAM flow control */ + struct completion res_ack_cmpl[MAX_IPC_DEV]; + + /* For efficient RX process */ + struct tasklet_struct rx_tsk; + struct mif_rxb_queue rxbq[MAX_IPC_DEV]; + struct io_device *iod[MAX_IPC_DEV]; + bool use_skb; + + /* For retransmission after buffer full state */ + atomic_t res_required[MAX_IPC_DEV]; + + /* For wake-up/sleep control */ + atomic_t accessing; + + /* Multi-purpose miscellaneous buffer */ + u8 *buff; + + /* DPRAM IPC initialization status */ + int init_status; + + /* Alias to device-specific IOCTL function */ + int (*ext_ioctl)(struct dpram_link_device *dpld, struct io_device *iod, + unsigned int cmd, unsigned long arg); + + /* Alias to DPRAM IRQ handler */ + irqreturn_t (*irq_handler)(int irq, void *data); + + /* For DPRAM dump */ + u8 *dump_buff; + char dump_path[MIF_MAX_PATH_LEN]; + char trace_path[MIF_MAX_PATH_LEN]; + struct trace_queue dump_list; + struct trace_queue trace_list; + struct delayed_work dump_dwork; + struct delayed_work trace_dwork; + void (*dpram_dump)(struct link_device *ld, char *buff); + + /* Common operations for each DPRAM */ + u16 (*recv_intr)(struct dpram_link_device *dpld); + void (*send_intr)(struct dpram_link_device *dpld, u16 mask); + u16 (*get_magic)(struct dpram_link_device *dpld); + void (*set_magic)(struct dpram_link_device *dpld, u16 value); + u16 (*get_access)(struct dpram_link_device *dpld); + void (*set_access)(struct dpram_link_device *dpld, u16 value); + u32 (*get_tx_head)(struct dpram_link_device *dpld, int id); + u32 (*get_tx_tail)(struct dpram_link_device *dpld, int id); + void (*set_tx_head)(struct dpram_link_device *dpld, int id, u32 head); + void (*set_tx_tail)(struct dpram_link_device *dpld, int id, u32 tail); + u8 *(*get_tx_buff)(struct dpram_link_device *dpld, int id); + u32 (*get_tx_buff_size)(struct dpram_link_device *dpld, int id); + u32 (*get_rx_head)(struct dpram_link_device *dpld, int id); + u32 (*get_rx_tail)(struct dpram_link_device *dpld, int id); + void (*set_rx_head)(struct dpram_link_device *dpld, int id, u32 head); + void (*set_rx_tail)(struct dpram_link_device *dpld, int id, u32 tail); + u8 *(*get_rx_buff)(struct dpram_link_device *dpld, int id); + u32 (*get_rx_buff_size)(struct dpram_link_device *dpld, int id); + u16 (*get_mask_req_ack)(struct dpram_link_device *dpld, int id); + u16 (*get_mask_res_ack)(struct dpram_link_device *dpld, int id); + u16 (*get_mask_send)(struct dpram_link_device *dpld, int id); + void (*ipc_rx_handler)(struct dpram_link_device *dpld, u16 int2ap); + + /* Extended operations for various modems */ + struct dpram_ext_op *ext_op; +}; + +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_dpram_link_device(linkdev) \ + container_of(linkdev, struct dpram_link_device, ld) + +struct dpram_ext_op { + int exist; + + void (*init_boot_map)(struct dpram_link_device *dpld); + void (*init_dl_map)(struct dpram_link_device *dpld); + void (*init_ul_map)(struct dpram_link_device *dpld); + void (*init_ipc_map)(struct dpram_link_device *dpld); + + int (*download_binary)(struct dpram_link_device *dpld, + struct sk_buff *skb); + + void (*cp_start_handler)(struct dpram_link_device *dpld); + + void (*crash_log)(struct dpram_link_device *dpld); + int (*dump_start)(struct dpram_link_device *dpld); + int (*dump_update)(struct dpram_link_device *dpld, void *arg); + + int (*ioctl)(struct dpram_link_device *dpld, struct io_device *iod, + unsigned int cmd, unsigned long arg); + + irqreturn_t (*irq_handler)(int irq, void *data); + void (*clear_intr)(struct dpram_link_device *dpld); + + int (*wakeup)(struct dpram_link_device *dpld); + void (*sleep)(struct dpram_link_device *dpld); +}; + +struct dpram_ext_op *dpram_get_ext_op(enum modem_t modem); + +#endif diff --git a/drivers/misc/modem_if/modem_link_device_dpram_ext_op.c b/drivers/misc/modem_if/modem_link_device_dpram_ext_op.c new file mode 100644 index 00000000000..83f2cc302d8 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_dpram_ext_op.c @@ -0,0 +1,1306 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/time.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/wakelock.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/if_arp.h> +#include <linux/platform_device.h> +#include <linux/kallsyms.h> +#include <linux/platform_data/modem.h> + +#include "modem_prj.h" +#include "modem_link_device_dpram.h" +#include "modem_utils.h" + +#if defined(CONFIG_LTE_MODEM_CMC221) +/* +** For host (flashless) booting via DPRAM +*/ +#define CMC22x_AP_BOOT_DOWN_DONE 0x54329876 +#define CMC22x_CP_REQ_MAIN_BIN 0xA5A5A5A5 +#define CMC22x_CP_REQ_NV_DATA 0x5A5A5A5A +#define CMC22x_CP_DUMP_MAGIC 0xDEADDEAD + +#define CMC22x_HOST_DOWN_START 0x1234 +#define CMC22x_HOST_DOWN_END 0x4321 +#define CMC22x_REG_NV_DOWN_END 0xABCD +#define CMC22x_CAL_NV_DOWN_END 0xDCBA + +#define CMC22x_1ST_BUFF_READY 0xAAAA +#define CMC22x_2ND_BUFF_READY 0xBBBB +#define CMC22x_1ST_BUFF_FULL 0x1111 +#define CMC22x_2ND_BUFF_FULL 0x2222 + +#define CMC22x_CP_RECV_NV_END 0x8888 +#define CMC22x_CP_CAL_OK 0x4F4B +#define CMC22x_CP_CAL_BAD 0x4552 +#define CMC22x_CP_DUMP_END 0xFADE + +#define CMC22x_DUMP_BUFF_SIZE 8192 /* 8 KB */ +#endif + +#if defined(CONFIG_CDMA_MODEM_CBP72) +static void cbp72_init_boot_map(struct dpram_link_device *dpld) +{ + struct dpram_boot_map *bt_map = &dpld->bt_map; + + bt_map->magic = (u32 *)dpld->base; + bt_map->buff = (u8 *)(dpld->base + DP_BOOT_BUFF_OFFSET); + bt_map->size = dpld->size - 4; +} + +static void cbp72_init_dl_map(struct dpram_link_device *dpld) +{ + dpld->dl_map.magic = (u32 *)dpld->base; + dpld->dl_map.buff = (u8 *)(dpld->base + DP_DLOAD_BUFF_OFFSET); +} + +static int _cbp72_edpram_wait_resp(struct dpram_link_device *dpld, u32 resp) +{ + struct link_device *ld = &dpld->ld; + int ret; + int int2cp; + + ret = wait_for_completion_interruptible_timeout( + &dpld->udl_cmd_complete, UDL_TIMEOUT); + if (!ret) { + mif_info("%s: ERR! No UDL_CMD_RESP!!!\n", ld->name); + return -ENXIO; + } + + int2cp = dpld->recv_intr(dpld); + mif_debug("%s: int2cp = 0x%x\n", ld->name, int2cp); + if (resp == int2cp || int2cp == 0xA700) + return int2cp; + else + return -EINVAL; +} + +static int _cbp72_edpram_download_bin(struct dpram_link_device *dpld, + struct sk_buff *skb) +{ + struct link_device *ld = &dpld->ld; + struct dpram_boot_frame *bf = (struct dpram_boot_frame *)skb->data; + u8 __iomem *buff = dpld->bt_map.buff; + int err = 0; + + if (bf->len > dpld->bt_map.size) { + mif_info("%s: ERR! Out of DPRAM boundary\n", ld->name); + err = -EINVAL; + goto exit; + } + + if (bf->len) + memcpy(buff, bf->data, bf->len); + + init_completion(&dpld->udl_cmd_complete); + + if (bf->req) + dpld->send_intr(dpld, (u16)bf->req); + + if (bf->resp) { + err = _cbp72_edpram_wait_resp(dpld, bf->resp); + if (err < 0) { + mif_info("%s: ERR! wait_response fail (%d)\n", + ld->name, err); + goto exit; + } else if (err == bf->resp) { + err = skb->len; + } + } + +exit: + dev_kfree_skb_any(skb); + return err; +} + +static int cbp72_download_binary(struct dpram_link_device *dpld, + struct sk_buff *skb) +{ + if (dpld->type == EXT_DPRAM) + return _cbp72_edpram_download_bin(dpld, skb); + else + return -ENODEV; +} + +static int cbp72_dump_start(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + u8 *dest = dpld->ul_map.buff; + int ret; + + ld->mode = LINK_MODE_ULOAD; + + ret = del_timer(&dpld->crash_timer); + wake_lock(&dpld->wlock); + + iowrite32(DP_MAGIC_UMDL, dpld->ul_map.magic); + + iowrite8((u8)START_FLAG, dest + 0); + iowrite8((u8)0x1, dest + 1); + iowrite8((u8)0x1, dest + 2); + iowrite8((u8)0x0, dest + 3); + iowrite8((u8)END_FLAG, dest + 4); + + init_completion(&dpld->crash_start_complete); + + return 0; +} + +static int _cbp72_edpram_upload(struct dpram_link_device *dpld, + struct dpram_dump_arg *dump, unsigned char __user *target) +{ + struct link_device *ld = &dpld->ld; + struct ul_header header; + u8 *dest = NULL; + u8 *buff = NULL; + u16 plen = 0; + int err = 0; + int ret = 0; + int buff_size = 0; + + mif_debug("\n"); + + init_completion(&dpld->udl_cmd_complete); + + mif_debug("%s: req %x, resp %x", ld->name, dump->req, dump->resp); + + if (dump->req) + dpld->send_intr(dpld, (u16)dump->req); + + if (dump->resp) { + err = _cbp72_edpram_wait_resp(dpld, dump->resp); + if (err < 0) { + mif_info("%s: ERR! wait_response fail (%d)\n", + ld->name, err); + goto exit; + } + } + + if (dump->cmd) + return err; + + dest = (u8 *)dpld->ul_map.buff; + + header.bop = *(u8 *)(dest); + header.total_frame = *(u16 *)(dest + 1); + header.curr_frame = *(u16 *)(dest + 3); + header.len = *(u16 *)(dest + 5); + + mif_debug("%s: total frame:%d, current frame:%d, data len:%d\n", + ld->name, header.total_frame, header.curr_frame, header.len); + + plen = min_t(u16, header.len, DP_DEFAULT_DUMP_LEN); + + buff = vmalloc(DP_DEFAULT_DUMP_LEN); + if (!buff) { + err = -ENOMEM; + goto exit; + } + + memcpy(buff, dest + sizeof(struct ul_header), plen); + ret = copy_to_user(dump->buff, buff, plen); + if (ret < 0) { + mif_info("%s: ERR! dump copy_to_user fail\n", ld->name); + err = -EIO; + goto exit; + } + buff_size = plen; + + ret = copy_to_user(target + 4, &buff_size, sizeof(int)); + if (ret < 0) { + mif_info("%s: ERR! size copy_to_user fail\n", ld->name); + err = -EIO; + goto exit; + } + + vfree(buff); + return err; + +exit: + if (buff) + vfree(buff); + iowrite32(0, dpld->ul_map.magic); + wake_unlock(&dpld->wlock); + return err; +} + +static int cbp72_dump_update(struct dpram_link_device *dpld, void *arg) +{ + struct link_device *ld = &dpld->ld; + struct dpram_dump_arg dump; + int ret; + + ret = copy_from_user(&dump, (void __user *)arg, sizeof(dump)); + if (ret < 0) { + mif_info("%s: ERR! copy_from_user fail\n", ld->name); + return ret; + } + + return _cbp72_edpram_upload(dpld, &dump, (unsigned char __user *)arg); +} + +static int cbp72_set_dl_magic(struct link_device *ld, struct io_device *iod) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + + ld->mode = LINK_MODE_DLOAD; + + iowrite32(DP_MAGIC_DMDL, dpld->dl_map.magic); + + return 0; +} + +static int cbp72_ioctl(struct dpram_link_device *dpld, struct io_device *iod, + unsigned int cmd, unsigned long arg) +{ + struct link_device *ld = &dpld->ld; + int err = 0; + + switch (cmd) { + case IOCTL_MODEM_DL_START: + err = cbp72_set_dl_magic(ld, iod); + if (err < 0) + mif_err("%s: ERR! set_dl_magic fail\n", ld->name); + break; + + default: + mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd); + err = -EINVAL; + break; + } + + return err; +} +#endif + +#if defined(CONFIG_LTE_MODEM_CMC221) +/* For CMC221 SFR for IDPRAM */ +#define CMC_INT2CP_REG 0x10 /* Interrupt to CP */ +#define CMC_INT2AP_REG 0x50 +#define CMC_CLR_INT_REG 0x28 /* Clear Interrupt to AP */ +#define CMC_RESET_REG 0x3C +#define CMC_PUT_REG 0x40 /* AP->CP reg for hostbooting */ +#define CMC_GET_REG 0x50 /* CP->AP reg for hostbooting */ + +static void cmc221_init_boot_map(struct dpram_link_device *dpld) +{ + struct dpram_boot_map *bt_map = &dpld->bt_map; + + bt_map->buff = dpld->base; + bt_map->size = dpld->size; + bt_map->req = (u32 *)(dpld->base + DP_BOOT_REQ_OFFSET); + bt_map->resp = (u32 *)(dpld->base + DP_BOOT_RESP_OFFSET); +} + +static void cmc221_init_dl_map(struct dpram_link_device *dpld) +{ + dpld->dl_map.magic = (u32 *)dpld->base; + dpld->dl_map.buff = (u8 *)dpld->base; +} + +static void cmc221_init_ul_map(struct dpram_link_device *dpld) +{ + dpld->ul_map.magic = (u32 *)dpld->base; + dpld->ul_map.buff = (u8 *)dpld->base; +} + +static void cmc221_init_ipc_map(struct dpram_link_device *dpld) +{ + struct dpram_ipc_16k_map *dpram_map; + struct dpram_ipc_device *dev; + u8 __iomem *sfr_base = dpld->sfr_base; + + dpram_map = (struct dpram_ipc_16k_map *)dpld->base; + + /* Magic code and access enable fields */ + dpld->ipc_map.magic = (u16 __iomem *)&dpram_map->magic; + dpld->ipc_map.access = (u16 __iomem *)&dpram_map->access; + + /* FMT */ + dev = &dpld->ipc_map.dev[IPC_FMT]; + + strcpy(dev->name, "FMT"); + dev->id = IPC_FMT; + + dev->txq.head = (u16 __iomem *)&dpram_map->fmt_tx_head; + dev->txq.tail = (u16 __iomem *)&dpram_map->fmt_tx_tail; + dev->txq.buff = (u8 __iomem *)&dpram_map->fmt_tx_buff[0]; + dev->txq.size = DP_16K_FMT_TX_BUFF_SZ; + + dev->rxq.head = (u16 __iomem *)&dpram_map->fmt_rx_head; + dev->rxq.tail = (u16 __iomem *)&dpram_map->fmt_rx_tail; + dev->rxq.buff = (u8 __iomem *)&dpram_map->fmt_rx_buff[0]; + dev->rxq.size = DP_16K_FMT_RX_BUFF_SZ; + + dev->mask_req_ack = INT_MASK_REQ_ACK_F; + dev->mask_res_ack = INT_MASK_RES_ACK_F; + dev->mask_send = INT_MASK_SEND_F; + + /* RAW */ + dev = &dpld->ipc_map.dev[IPC_RAW]; + + strcpy(dev->name, "RAW"); + dev->id = IPC_RAW; + + dev->txq.head = (u16 __iomem *)&dpram_map->raw_tx_head; + dev->txq.tail = (u16 __iomem *)&dpram_map->raw_tx_tail; + dev->txq.buff = (u8 __iomem *)&dpram_map->raw_tx_buff[0]; + dev->txq.size = DP_16K_RAW_TX_BUFF_SZ; + + dev->rxq.head = (u16 __iomem *)&dpram_map->raw_rx_head; + dev->rxq.tail = (u16 __iomem *)&dpram_map->raw_rx_tail; + dev->rxq.buff = (u8 __iomem *)&dpram_map->raw_rx_buff[0]; + dev->rxq.size = DP_16K_RAW_RX_BUFF_SZ; + + dev->mask_req_ack = INT_MASK_REQ_ACK_R; + dev->mask_res_ack = INT_MASK_RES_ACK_R; + dev->mask_send = INT_MASK_SEND_R; + + /* SFR */ + dpld->sfr.int2cp = (u16 __iomem *)(sfr_base + CMC_INT2CP_REG); + dpld->sfr.int2ap = (u16 __iomem *)(sfr_base + CMC_INT2AP_REG); + dpld->sfr.clr_int2ap = (u16 __iomem *)(sfr_base + CMC_CLR_INT_REG); + dpld->sfr.reset = (u16 __iomem *)(sfr_base + CMC_RESET_REG); + dpld->sfr.msg2cp = (u16 __iomem *)(sfr_base + CMC_PUT_REG); + dpld->sfr.msg2ap = (u16 __iomem *)(sfr_base + CMC_GET_REG); + + /* Interrupt ports */ + dpld->ipc_map.mbx_cp2ap = dpld->sfr.int2ap; + dpld->ipc_map.mbx_ap2cp = dpld->sfr.int2cp; +} + +static inline void cmc221_idpram_reset(struct dpram_link_device *dpld) +{ + iowrite16(1, dpld->sfr.reset); +} + +static inline u16 cmc221_idpram_recv_msg(struct dpram_link_device *dpld) +{ + return ioread16(dpld->sfr.msg2ap); +} + +static inline void cmc221_idpram_send_msg(struct dpram_link_device *dpld, + u16 msg) +{ + iowrite16(msg, dpld->sfr.msg2cp); +} + +static int _cmc221_idpram_wait_resp(struct dpram_link_device *dpld, u32 resp) +{ + struct link_device *ld = &dpld->ld; + int count = 50000; + u32 rcvd = 0; + + if (resp == CMC22x_CP_REQ_NV_DATA) { + while (1) { + rcvd = ioread32(dpld->bt_map.resp); + if (rcvd == resp) + break; + + rcvd = cmc221_idpram_recv_msg(dpld); + if (rcvd == 0x9999) { + mif_info("%s: Invalid resp 0x%04X\n", + ld->name, rcvd); + panic("CP Crash ... BAD CRC in CP"); + } + + if (count-- < 0) { + mif_info("%s: Invalid resp 0x%08X\n", + ld->name, rcvd); + return -EAGAIN; + } + + udelay(100); + } + } else { + while (1) { + rcvd = cmc221_idpram_recv_msg(dpld); + + if (rcvd == resp) + break; + + if (resp == CMC22x_CP_RECV_NV_END && + rcvd == CMC22x_CP_CAL_BAD) { + mif_info("%s: CMC22x_CP_CAL_BAD\n", ld->name); + break; + } + + if (count-- < 0) { + mif_info("%s: Invalid resp 0x%04X\n", + ld->name, rcvd); + return -EAGAIN; + } + + udelay(100); + } + } + + return rcvd; +} + +static int _cmc221_idpram_send_boot(struct dpram_link_device *dpld, void *arg) +{ + struct link_device *ld = &dpld->ld; + u8 __iomem *bt_buff = dpld->bt_map.buff; + struct dpram_boot_img cp_img; + u8 *img_buff = NULL; + int err = 0; + int cnt = 0; + + ld->mode = LINK_MODE_BOOT; + + dpld->dpctl->setup_speed(DPRAM_SPEED_LOW); + + /* Test memory... After testing, memory is cleared. */ + if (mif_test_dpram(ld->name, bt_buff, dpld->bt_map.size) < 0) { + mif_info("%s: ERR! mif_test_dpram fail!\n", ld->name); + ld->mode = LINK_MODE_OFFLINE; + return -EIO; + } + + memset(&cp_img, 0, sizeof(struct dpram_boot_img)); + + /* Get information about the boot image */ + err = copy_from_user(&cp_img, arg, sizeof(cp_img)); + mif_info("%s: CP image addr = 0x%08X, size = %d\n", + ld->name, (int)cp_img.addr, cp_img.size); + + /* Alloc a buffer for the boot image */ + img_buff = kzalloc(dpld->bt_map.size, GFP_KERNEL); + if (!img_buff) { + mif_info("%s: ERR! kzalloc fail\n", ld->name); + ld->mode = LINK_MODE_OFFLINE; + return -ENOMEM; + } + + /* Copy boot image from the user space to the image buffer */ + err = copy_from_user(img_buff, cp_img.addr, cp_img.size); + + /* Copy boot image to DPRAM and verify it */ + memcpy(bt_buff, img_buff, cp_img.size); + if (memcmp16_to_io(bt_buff, img_buff, cp_img.size)) { + mif_info("%s: ERR! Boot may be broken!!!\n", ld->name); + goto err; + } + + cmc221_idpram_reset(dpld); + usleep_range(1000, 2000); + + if (cp_img.mode == HOST_BOOT_MODE_NORMAL) { + mif_info("%s: HOST_BOOT_MODE_NORMAL\n", ld->name); + mif_info("%s: Send req 0x%08X\n", ld->name, cp_img.req); + iowrite32(cp_img.req, dpld->bt_map.req); + + /* Wait for cp_img.resp for up to 2 seconds */ + mif_info("%s: Wait resp 0x%08X\n", ld->name, cp_img.resp); + while (ioread32(dpld->bt_map.resp) != cp_img.resp) { + cnt++; + usleep_range(1000, 2000); + + if (cnt > 1000) { + mif_info("%s: ERR! Invalid resp 0x%08X\n", + ld->name, ioread32(dpld->bt_map.resp)); + goto err; + } + } + } else { + mif_info("%s: HOST_BOOT_MODE_DUMP\n", ld->name); + } + + kfree(img_buff); + + mif_info("%s: Send BOOT done\n", ld->name); + + dpld->dpctl->setup_speed(DPRAM_SPEED_HIGH); + + return 0; + +err: + ld->mode = LINK_MODE_OFFLINE; + kfree(img_buff); + + mif_info("%s: ERR! Boot send fail!!!\n", ld->name); + return -EIO; +} + +static int cmc221_download_boot(struct dpram_link_device *dpld, void *arg) +{ + if (dpld->type == CP_IDPRAM) + return _cmc221_idpram_send_boot(dpld, arg); + else + return -ENODEV; +} + +static int _cmc221_idpram_download_bin(struct dpram_link_device *dpld, + struct sk_buff *skb) +{ + int err = 0; + int ret = 0; + struct link_device *ld = &dpld->ld; + struct dpram_boot_frame *bf = (struct dpram_boot_frame *)skb->data; + u8 __iomem *buff = (dpld->bt_map.buff + bf->offset); + + if ((bf->offset + bf->len) > dpld->bt_map.size) { + mif_info("%s: ERR! Out of DPRAM boundary\n", ld->name); + err = -EINVAL; + goto exit; + } + + if (bf->len) + memcpy(buff, bf->data, bf->len); + + if (bf->req) + cmc221_idpram_send_msg(dpld, (u16)bf->req); + + if (bf->resp) { + err = _cmc221_idpram_wait_resp(dpld, bf->resp); + if (err < 0) + mif_info("%s: ERR! wait_response fail (err %d)\n", + ld->name, err); + } + + if (bf->req == CMC22x_CAL_NV_DOWN_END) + mif_info("%s: CMC22x_CAL_NV_DOWN_END\n", ld->name); + +exit: + if (err < 0) + ret = err; + else + ret = skb->len; + + dev_kfree_skb_any(skb); + + return ret; +} + +static int cmc221_download_binary(struct dpram_link_device *dpld, + struct sk_buff *skb) +{ + if (dpld->type == CP_IDPRAM) + return _cmc221_idpram_download_bin(dpld, skb); + else + return -ENODEV; +} + +static int cmc221_dump_start(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + int ret; + + ld->mode = LINK_MODE_ULOAD; + + ret = del_timer(&dpld->crash_timer); + wake_lock(&dpld->wlock); + + dpld->crash_rcvd = 0; + iowrite32(CMC22x_CP_DUMP_MAGIC, dpld->ul_map.magic); + + init_completion(&dpld->crash_start_complete); + + return 0; +} + +static void _cmc221_idpram_wait_dump(unsigned long arg) +{ + struct dpram_link_device *dpld = (struct dpram_link_device *)arg; + u16 msg; + + msg = cmc221_idpram_recv_msg(dpld); + if (msg == CMC22x_CP_DUMP_END) { + complete_all(&dpld->crash_recv_done); + return; + } + + if (((dpld->crash_rcvd & 0x1) == 0) && (msg == CMC22x_1ST_BUFF_FULL)) { + complete_all(&dpld->crash_recv_done); + return; + } + + if (((dpld->crash_rcvd & 0x1) == 1) && (msg == CMC22x_2ND_BUFF_FULL)) { + complete_all(&dpld->crash_recv_done); + return; + } + + mif_add_timer(&dpld->crash_timer, DUMP_WAIT_TIMEOUT, + _cmc221_idpram_wait_dump, (unsigned long)dpld); +} + +static int _cmc221_idpram_upload(struct dpram_link_device *dpld, + struct dpram_dump_arg *dumparg) +{ + struct link_device *ld = &dpld->ld; + int ret; + u8 __iomem *src; + int buff_size = CMC22x_DUMP_BUFF_SIZE; + + if ((dpld->crash_rcvd & 0x1) == 0) + cmc221_idpram_send_msg(dpld, CMC22x_1ST_BUFF_READY); + else + cmc221_idpram_send_msg(dpld, CMC22x_2ND_BUFF_READY); + + init_completion(&dpld->crash_recv_done); + + mif_add_timer(&dpld->crash_timer, DUMP_WAIT_TIMEOUT, + _cmc221_idpram_wait_dump, (unsigned long)dpld); + + ret = wait_for_completion_interruptible_timeout( + &dpld->crash_recv_done, DUMP_TIMEOUT); + if (!ret) { + mif_info("%s: ERR! CP didn't send dump data!!!\n", ld->name); + goto err_out; + } + + if (cmc221_idpram_recv_msg(dpld) == CMC22x_CP_DUMP_END) { + mif_info("%s: CMC22x_CP_DUMP_END\n", ld->name); + return 0; + } + + if ((dpld->crash_rcvd & 0x1) == 0) + src = dpld->ul_map.buff; + else + src = dpld->ul_map.buff + CMC22x_DUMP_BUFF_SIZE; + + memcpy(dpld->buff, src, buff_size); + + ret = copy_to_user(dumparg->buff, dpld->buff, buff_size); + if (ret < 0) { + mif_info("%s: ERR! copy_to_user fail\n", ld->name); + goto err_out; + } + + dpld->crash_rcvd++; + return buff_size; + +err_out: + return -EIO; +} + +static int cmc221_dump_update(struct dpram_link_device *dpld, void *arg) +{ + struct link_device *ld = &dpld->ld; + struct dpram_dump_arg dump; + int ret; + + ret = copy_from_user(&dump, (void __user *)arg, sizeof(dump)); + if (ret < 0) { + mif_info("%s: ERR! copy_from_user fail\n", ld->name); + return ret; + } + + return _cmc221_idpram_upload(dpld, &dump); +} + +static int cmc221_ioctl(struct dpram_link_device *dpld, struct io_device *iod, + unsigned int cmd, unsigned long arg) +{ + struct link_device *ld = &dpld->ld; + int err = 0; + + switch (cmd) { + case IOCTL_DPRAM_SEND_BOOT: + err = cmc221_download_boot(dpld, (void *)arg); + if (err < 0) + mif_info("%s: ERR! download_boot fail\n", ld->name); + break; + + default: + mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd); + err = -EINVAL; + break; + } + + return err; +} + +static void cmc221_idpram_clr_int2ap(struct dpram_link_device *dpld) +{ + iowrite16(0xFFFF, dpld->sfr.clr_int2ap); +} + +static int cmc221_idpram_wakeup(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + struct modem_data *mdm_data = ld->mdm_data; + int cnt = 0; + + gpio_set_value(mdm_data->gpio_dpram_wakeup, 1); + + while (!gpio_get_value(mdm_data->gpio_dpram_status)) { + if (cnt++ > 10) { + if (in_irq()) + mif_err("ERR! gpio_dpram_status == 0 in IRQ\n"); + else + mif_err("ERR! gpio_dpram_status == 0\n"); + return -EACCES; + } + + mif_info("gpio_dpram_status == 0 (cnt %d)\n", cnt); + if (in_interrupt()) + udelay(1000); + else + usleep_range(1000, 2000); + } + + return 0; +} + +static void cmc221_idpram_sleep(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + gpio_set_value(ld->mdm_data->gpio_dpram_wakeup, 0); +} +#endif + +#if defined(CONFIG_CDMA_MODEM_MDM6600) || defined(CONFIG_GSM_MODEM_ESC6270) +enum qc_dload_tag { + QC_DLOAD_TAG_NONE = 0, + QC_DLOAD_TAG_BIN, + QC_DLOAD_TAG_NV, + QC_DLOAD_TAG_MAX +}; + +static void qc_dload_task(unsigned long data); + +static void qc_init_boot_map(struct dpram_link_device *dpld) +{ + struct qc_dpram_boot_map *qbt_map = &dpld->qc_bt_map; + struct modemlink_dpram_control *dpctl = dpld->dpctl; + + qbt_map->buff = dpld->base; + qbt_map->frame_size = (u16 *)(dpld->base + dpctl->boot_size_offset); + qbt_map->tag = (u16 *)(dpld->base + dpctl->boot_tag_offset); + qbt_map->count = (u16 *)(dpld->base + dpctl->boot_count_offset); + + tasklet_init(&dpld->dl_tsk, qc_dload_task, (unsigned long)dpld); +} + +static int qc_prepare_download(struct dpram_link_device *dpld) +{ + int retval = 0; + int count = 0; + + while (1) { + if (dpld->udl_check.copy_start) { + dpld->udl_check.copy_start = 0; + break; + } + + usleep_range(10000, 11000); + + count++; + if (count > 200) { + mif_err("ERR! count %d\n", count); + return -1; + } + } + + return retval; +} + +static void _qc_do_download(struct dpram_link_device *dpld, + struct dpram_udl_param *param) +{ + struct qc_dpram_boot_map *qbt_map = &dpld->qc_bt_map; + + if (param->size <= dpld->dpctl->max_boot_frame_size) { + memcpy(qbt_map->buff, param->addr, param->size); + iowrite16(param->size, qbt_map->frame_size); + iowrite16(param->tag, qbt_map->tag); + iowrite16(param->count, qbt_map->count); + dpld->send_intr(dpld, 0xDB12); + } else { + mif_info("param->size %d\n", param->size); + } +} + +static int _qc_download(struct dpram_link_device *dpld, void *arg, + enum qc_dload_tag tag) +{ + int retval = 0; + int count = 0; + int cnt_limit; + unsigned char *img; + struct dpram_udl_param param; + + retval = copy_from_user((void *)¶m, (void *)arg, sizeof(param)); + if (retval < 0) { + mif_err("ERR! copy_from_user fail\n"); + return -1; + } + + img = vmalloc(param.size); + if (!img) { + mif_err("ERR! vmalloc fail\n"); + return -1; + } + memset(img, 0, param.size); + memcpy(img, param.addr, param.size); + + dpld->udl_check.total_size = param.size; + dpld->udl_check.rest_size = param.size; + dpld->udl_check.send_size = 0; + dpld->udl_check.copy_complete = 0; + + dpld->udl_param.addr = img; + dpld->udl_param.size = dpld->dpctl->max_boot_frame_size; + if (tag == QC_DLOAD_TAG_NV) + dpld->udl_param.count = 1; + else + dpld->udl_param.count = param.count; + dpld->udl_param.tag = tag; + + if (dpld->udl_check.rest_size < dpld->dpctl->max_boot_frame_size) + dpld->udl_param.size = dpld->udl_check.rest_size; + + /* Download image (binary or NV) */ + _qc_do_download(dpld, &dpld->udl_param); + + /* Wait for completion + */ + if (tag == QC_DLOAD_TAG_NV) + cnt_limit = 200; + else + cnt_limit = 1000; + + while (1) { + if (dpld->udl_check.copy_complete) { + dpld->udl_check.copy_complete = 0; + retval = 0; + break; + } + + usleep_range(10000, 11000); + + count++; + if (count > cnt_limit) { + dpld->udl_check.total_size = 0; + dpld->udl_check.rest_size = 0; + mif_err("ERR! count %d\n", count); + retval = -1; + break; + } + } + + vfree(img); + + return retval; +} + +static int qc_download_binary(struct dpram_link_device *dpld, void *arg) +{ + return _qc_download(dpld, arg, QC_DLOAD_TAG_BIN); +} + +static int qc_download_nv(struct dpram_link_device *dpld, void *arg) +{ + return _qc_download(dpld, arg, QC_DLOAD_TAG_NV); +} + +static void qc_dload_task(unsigned long data) +{ + struct dpram_link_device *dpld = (struct dpram_link_device *)data; + + dpld->udl_check.send_size += dpld->udl_param.size; + dpld->udl_check.rest_size -= dpld->udl_param.size; + + dpld->udl_param.addr += dpld->udl_param.size; + + if (dpld->udl_check.send_size >= dpld->udl_check.total_size) { + dpld->udl_check.copy_complete = 1; + dpld->udl_param.tag = 0; + return; + } + + if (dpld->udl_check.rest_size < dpld->dpctl->max_boot_frame_size) + dpld->udl_param.size = dpld->udl_check.rest_size; + + dpld->udl_param.count += 1; + + _qc_do_download(dpld, &dpld->udl_param); +} + +static void qc_dload_cmd_handler(struct dpram_link_device *dpld, u16 cmd) +{ + switch (cmd) { + case 0x1234: + dpld->udl_check.copy_start = 1; + break; + + case 0xDBAB: + if (dpld->udl_check.total_size) + tasklet_schedule(&dpld->dl_tsk); + break; + + case 0xABCD: + dpld->udl_check.boot_complete = 1; + break; + + default: + mif_err("ERR! unknown command 0x%04X\n", cmd); + } +} + +static int qc_boot_start(struct dpram_link_device *dpld) +{ + u16 mask = 0; + int count = 0; + + /* Send interrupt -> '0x4567' */ + mask = 0x4567; + dpld->send_intr(dpld, mask); + + while (1) { + if (dpld->udl_check.boot_complete) { + dpld->udl_check.boot_complete = 0; + break; + } + + usleep_range(10000, 11000); + + count++; + if (count > 200) { + mif_err("ERR! count %d\n", count); + return -1; + } + } + + return 0; +} + +static int qc_boot_post_process(struct dpram_link_device *dpld) +{ + int count = 0; + + while (1) { + if (dpld->boot_start_complete) { + dpld->boot_start_complete = 0; + break; + } + + usleep_range(10000, 11000); + + count++; + if (count > 200) { + mif_err("ERR! count %d\n", count); + return -1; + } + } + + return 0; +} + +static void qc_start_handler(struct dpram_link_device *dpld) +{ + /* + * INT_MASK_VALID | INT_MASK_CMD | INT_MASK_CP_AIRPLANE_BOOT | + * INT_MASK_CP_AP_ANDROID | INT_MASK_CMD_INIT_END + */ + u16 mask = (0x0080 | 0x0040 | 0x1000 | 0x0100 | 0x0002); + + dpld->boot_start_complete = 1; + + /* Send INIT_END code to CP */ + mif_info("send 0x%04X (INIT_END)\n", mask); + + dpld->send_intr(dpld, mask); +} + +static void qc_crash_log(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + static unsigned char buf[151]; + u8 __iomem *data = NULL; + + data = dpld->get_rx_buff(dpld, IPC_FMT); + memcpy(buf, data, (sizeof(buf) - 1)); + + mif_info("PHONE ERR MSG\t| %s Crash\n", ld->mdm_data->name); + mif_info("PHONE ERR MSG\t| %s\n", buf); +} + +static int _qc_data_upload(struct dpram_link_device *dpld, + struct dpram_udl_param *param) +{ + struct qc_dpram_boot_map *qbt_map = &dpld->qc_bt_map; + int retval = 0; + u16 intval = 0; + int count = 0; + + while (1) { + if (!gpio_get_value(dpld->gpio_dpram_int)) { + intval = dpld->recv_intr(dpld); + if (intval == 0xDBAB) { + break; + } else { + mif_err("intr 0x%08x\n", intval); + return -1; + } + } + + usleep_range(1000, 2000); + + count++; + if (count > 200) { + mif_err("<%s:%d>\n", __func__, __LINE__); + return -1; + } + } + + param->size = ioread16(qbt_map->frame_size); + memcpy(param->addr, qbt_map->buff, param->size); + param->tag = ioread16(qbt_map->tag); + param->count = ioread16(qbt_map->count); + + dpld->send_intr(dpld, 0xDB12); + + return retval; +} + +static int qc_uload_step1(struct dpram_link_device *dpld) +{ + int retval = 0; + int count = 0; + u16 intval = 0; + u16 mask = 0; + + mif_info("+---------------------------------------------+\n"); + mif_info("| UPLOAD PHONE SDRAM |\n"); + mif_info("+---------------------------------------------+\n"); + + while (1) { + if (!gpio_get_value(dpld->gpio_dpram_int)) { + intval = dpld->recv_intr(dpld); + mif_info("intr 0x%04x\n", intval); + if (intval == 0x1234) { + break; + } else { + mif_info("ERR! invalid intr\n"); + return -1; + } + } + + usleep_range(1000, 2000); + + count++; + if (count > 200) { + intval = dpld->recv_intr(dpld); + mif_info("count %d, intr 0x%04x\n", count, intval); + if (intval == 0x1234) + break; + return -1; + } + } + + mask = 0xDEAD; + dpld->send_intr(dpld, mask); + + return retval; +} + +static int qc_uload_step2(struct dpram_link_device *dpld, void *arg) +{ + int retval = 0; + struct dpram_udl_param param; + + retval = copy_from_user((void *)¶m, (void *)arg, sizeof(param)); + if (retval < 0) { + mif_err("ERR! copy_from_user fail (err %d)\n", retval); + return -1; + } + + retval = _qc_data_upload(dpld, ¶m); + if (retval < 0) { + mif_err("ERR! _qc_data_upload fail (err %d)\n", retval); + return -1; + } + + if (!(param.count % 500)) + mif_info("param->count = %d\n", param.count); + + if (param.tag == 4) { + enable_irq(dpld->irq); + mif_info("param->tag = %d\n", param.tag); + } + + retval = copy_to_user((unsigned long *)arg, ¶m, sizeof(param)); + if (retval < 0) { + mif_err("ERR! copy_to_user fail (err %d)\n", retval); + return -1; + } + + return retval; +} + +static int qc_ioctl(struct dpram_link_device *dpld, struct io_device *iod, + unsigned int cmd, unsigned long arg) +{ + struct link_device *ld = &dpld->ld; + int err = 0; + + switch (cmd) { + case IOCTL_DPRAM_PHONE_POWON: + err = qc_prepare_download(dpld); + if (err < 0) + mif_info("%s: ERR! prepare_download fail\n", ld->name); + break; + + case IOCTL_DPRAM_PHONEIMG_LOAD: + err = qc_download_binary(dpld, (void *)arg); + if (err < 0) + mif_info("%s: ERR! download_binary fail\n", ld->name); + break; + + case IOCTL_DPRAM_NVDATA_LOAD: + err = qc_download_nv(dpld, (void *)arg); + if (err < 0) + mif_info("%s: ERR! download_nv fail\n", ld->name); + break; + + case IOCTL_DPRAM_PHONE_BOOTSTART: + err = qc_boot_start(dpld); + if (err < 0) { + mif_info("%s: ERR! boot_start fail\n", ld->name); + break; + } + + err = qc_boot_post_process(dpld); + if (err < 0) + mif_info("%s: ERR! boot_post_process fail\n", ld->name); + + break; + + case IOCTL_DPRAM_PHONE_UPLOAD_STEP1: + disable_irq_nosync(dpld->irq); + err = qc_uload_step1(dpld); + if (err < 0) { + enable_irq(dpld->irq); + mif_info("%s: ERR! upload_step1 fail\n", ld->name); + } + break; + + case IOCTL_DPRAM_PHONE_UPLOAD_STEP2: + err = qc_uload_step2(dpld, (void *)arg); + if (err < 0) { + enable_irq(dpld->irq); + mif_info("%s: ERR! upload_step2 fail\n", ld->name); + } + break; + + default: + mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd); + err = -EINVAL; + break; + } + + return err; +} + +static irqreturn_t qc_dpram_irq_handler(int irq, void *data) +{ + struct dpram_link_device *dpld = (struct dpram_link_device *)data; + struct link_device *ld = (struct link_device *)&dpld->ld; + u16 int2ap = 0; + + if (unlikely(ld->mode == LINK_MODE_OFFLINE)) + return IRQ_HANDLED; + + int2ap = dpld->recv_intr(dpld); + + if (int2ap == INT_POWERSAFE_FAIL) { + mif_info("%s: int2ap == INT_POWERSAFE_FAIL\n", ld->name); + goto exit; + } + + if (int2ap == 0x1234 || int2ap == 0xDBAB || int2ap == 0xABCD) { + qc_dload_cmd_handler(dpld, int2ap); + goto exit; + } + + if (likely(INT_VALID(int2ap))) + dpld->ipc_rx_handler(dpld, int2ap); + else + mif_info("%s: ERR! invalid intr 0x%04X\n", ld->name, int2ap); + +exit: + return IRQ_HANDLED; +} +#endif + +static struct dpram_ext_op ext_op_set[] = { +#ifdef CONFIG_CDMA_MODEM_CBP72 + [VIA_CBP72] = { + .exist = 1, + .init_boot_map = cbp72_init_boot_map, + .init_dl_map = cbp72_init_dl_map, + .download_binary = cbp72_download_binary, + .dump_start = cbp72_dump_start, + .dump_update = cbp72_dump_update, + .ioctl = cbp72_ioctl, + }, +#endif +#ifdef CONFIG_LTE_MODEM_CMC221 + [SEC_CMC221] = { + .exist = 1, + .init_boot_map = cmc221_init_boot_map, + .init_dl_map = cmc221_init_dl_map, + .init_ul_map = cmc221_init_ul_map, + .init_ipc_map = cmc221_init_ipc_map, + .download_binary = cmc221_download_binary, + .dump_start = cmc221_dump_start, + .dump_update = cmc221_dump_update, + .ioctl = cmc221_ioctl, + .clear_intr = cmc221_idpram_clr_int2ap, + .wakeup = cmc221_idpram_wakeup, + .sleep = cmc221_idpram_sleep, + }, +#endif +#if defined(CONFIG_CDMA_MODEM_MDM6600) + [QC_MDM6600] = { + .exist = 1, + .init_boot_map = qc_init_boot_map, + .cp_start_handler = qc_start_handler, + .crash_log = qc_crash_log, + .ioctl = qc_ioctl, + .irq_handler = qc_dpram_irq_handler, + }, +#endif +#if defined(CONFIG_GSM_MODEM_ESC6270) + [QC_ESC6270] = { + .exist = 1, + .init_boot_map = qc_init_boot_map, + .cp_start_handler = qc_start_handler, + .crash_log = qc_crash_log, + .ioctl = qc_ioctl, + .irq_handler = qc_dpram_irq_handler, + }, +#endif +}; + +struct dpram_ext_op *dpram_get_ext_op(enum modem_t modem) +{ + if (ext_op_set[modem].exist) + return &ext_op_set[modem]; + else + return NULL; +} + diff --git a/drivers/misc/modem_if/modem_link_device_hsic.c b/drivers/misc/modem_if/modem_link_device_hsic.c new file mode 100644 index 00000000000..bb8066a3fa0 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_hsic.c @@ -0,0 +1,1654 @@ +/* /linux/drivers/new_modem_if/link_dev_usb.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/irq.h> +#include <linux/poll.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> +#include <linux/usb.h> +#include <linux/usb/cdc.h> +#include <linux/pm_runtime.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> +#ifdef CONFIG_HAS_WAKELOCK +#include <linux/wakelock.h> +#endif +#include <linux/suspend.h> +#include <linux/version.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_hsic.h" +#include "modem_utils.h" + +static struct modem_ctl *if_usb_get_modemctl(struct link_pm_data *pm_data); +static int link_pm_runtime_get_active(struct link_pm_data *pm_data); +static int usb_tx_urb_with_skb(struct usb_device *usbdev, struct sk_buff *skb, + struct if_usb_devdata *pipe_data); +#ifdef FOR_TEGRA +#define ehci_vendor_txfilltuning tegra_ehci_txfilltuning +#else +#define ehci_vendor_txfilltuning() +#endif +static void usb_rx_complete(struct urb *urb); + +static int start_ipc(struct link_device *ld, struct io_device *iod) +{ + struct sk_buff *skb; + char data[1] = {'a'}; + int err; + struct usb_link_device *usb_ld = to_usb_link_device(ld); + struct link_pm_data *pm_data = usb_ld->link_pm_data; + struct device *dev = &usb_ld->usbdev->dev; + struct if_usb_devdata *pipe_data = &usb_ld->devdata[IF_USB_FMT_EP]; + + if (!usb_ld->if_usb_connected) { + mif_err("HSIC not connected, skip start ipc\n"); + err = -ENODEV; + goto exit; + } + +retry: + if (ld->mc->phone_state != STATE_ONLINE) { + mif_err("MODEM is not online, skip start ipc\n"); + err = -ENODEV; + goto exit; + } + + /* check usb runtime pm first */ + if (dev->power.runtime_status != RPM_ACTIVE) { + if (!pm_data->resume_requested) { + mif_debug("QW PM\n"); + INIT_COMPLETION(pm_data->active_done); + queue_delayed_work(pm_data->wq, + &pm_data->link_pm_work, 0); + } + mif_debug("Wait pm\n"); + err = wait_for_completion_timeout(&pm_data->active_done, + msecs_to_jiffies(500)); + /* timeout or -ERESTARTSYS */ + if (err <= 0) + goto retry; + } + + pm_runtime_get_sync(dev); + + mif_err("send 'a'\n"); + + skb = alloc_skb(16, GFP_ATOMIC); + if (unlikely(!skb)) { + pm_runtime_put(dev); + return -ENOMEM; + } + memcpy(skb_put(skb, 1), data, 1); + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + + if (!usb_ld->if_usb_connected || !usb_ld->usbdev) + return -ENODEV; + + usb_mark_last_busy(usb_ld->usbdev); + err = usb_tx_urb_with_skb(usb_ld->usbdev, skb, pipe_data); + if (err < 0) { + mif_err("usb_tx_urb fail\n"); + dev_kfree_skb_any(skb); + } + + pm_runtime_put(dev); +exit: + return err; +} + +static void stop_ipc(struct link_device *ld) +{ + ld->com_state = COM_NONE; +} + +static int usb_init_communication(struct link_device *ld, + struct io_device *iod) +{ + struct task_struct *task = get_current(); + char str[TASK_COMM_LEN]; + + mif_info("%d:%s\n", task->pid, get_task_comm(str, task)); + + /* Send IPC Start ASCII 'a' */ + if (iod->id == 0x1) + return start_ipc(ld, iod); + + return 0; +} + +static void usb_terminate_communication(struct link_device *ld, + struct io_device *iod) +{ + if (iod->id != 0x1 || iod->format != IPC_FMT) + return; + + if (iod->mc->phone_state == STATE_CRASH_RESET || + iod->mc->phone_state == STATE_CRASH_EXIT) + stop_ipc(ld); +} + +static int usb_rx_submit(struct usb_link_device *usb_ld, + struct if_usb_devdata *pipe_data, + gfp_t gfp_flags) +{ + int ret; + struct urb *urb; + + if (pipe_data->disconnected) + return -ENOENT; + + ehci_vendor_txfilltuning(); + + urb = pipe_data->urb; + + urb->transfer_flags = 0; + usb_fill_bulk_urb(urb, pipe_data->usbdev, + pipe_data->rx_pipe, pipe_data->rx_buf, + pipe_data->rx_buf_size, usb_rx_complete, + (void *)pipe_data); + + if (pipe_data->disconnected) + return -ENOENT; + + usb_mark_last_busy(usb_ld->usbdev); + ret = usb_submit_urb(urb, gfp_flags); + if (ret) + mif_err("submit urb fail with ret (%d)\n", ret); + + return ret; +} + +static void usb_rx_retry_work(struct work_struct *work) +{ + int ret = 0; + struct usb_link_device *usb_ld = + container_of(work, struct usb_link_device, rx_retry_work.work); + struct urb *urb = usb_ld->retry_urb; + struct if_usb_devdata *pipe_data = urb->context; + struct io_device *iod; + int iod_format; + + if (!usb_ld->if_usb_connected || !usb_ld->usbdev) + return; + + if (usb_ld->usbdev) + usb_mark_last_busy(usb_ld->usbdev); + switch (pipe_data->format) { + case IF_USB_FMT_EP: + if (usb_ld->if_usb_is_main) { + pr_urb("IPC-RX, retry", urb); + iod_format = IPC_FMT; + } else { + iod_format = IPC_BOOT; + } + break; + case IF_USB_RAW_EP: + iod_format = IPC_MULTI_RAW; + break; + case IF_USB_RFS_EP: + iod_format = IPC_RFS; + pr_urb("RFS-RX, retry", urb); + break; + case IF_USB_CMD_EP: + iod_format = IPC_CMD; + break; + default: + iod_format = -1; + break; + } + + iod = link_get_iod_with_format(&usb_ld->ld, iod_format); + if (iod) { + ret = iod->recv(iod, &usb_ld->ld, (char *)urb->transfer_buffer, + urb->actual_length); + if (ret == -ENOMEM) { + /* TODO: check the retry count */ + /* retry the delay work after 20ms and resubit*/ + mif_err("ENOMEM, +retry 20ms\n"); + if (usb_ld->usbdev) + usb_mark_last_busy(usb_ld->usbdev); + usb_ld->retry_urb = urb; + if (usb_ld->rx_retry_cnt++ < 10) + queue_delayed_work(usb_ld->ld.tx_wq, + &usb_ld->rx_retry_work, 10); + return; + } + if (ret < 0) + mif_err("io device recv error (%d)\n", ret); + usb_ld->rx_retry_cnt = 0; + } + + if (usb_ld->usbdev) + usb_mark_last_busy(usb_ld->usbdev); + usb_rx_submit(usb_ld, pipe_data, GFP_ATOMIC); +} + + +static void usb_rx_complete(struct urb *urb) +{ + struct if_usb_devdata *pipe_data = urb->context; + struct usb_link_device *usb_ld = pipe_data->usb_ld; + struct io_device *iod; + int iod_format; + int ret; + + if (usb_ld->usbdev) + usb_mark_last_busy(usb_ld->usbdev); + + switch (urb->status) { + case -ENOENT: + /* case for 'link pm suspended but rx data had remained' */ + mif_debug("urb->status = -ENOENT\n"); + case 0: + if (!urb->actual_length) { + mif_debug("urb has zero length!\n"); + goto rx_submit; + } + + usb_ld->link_pm_data->rx_cnt++; + /* call iod recv */ + /* how we can distinguish boot ch with fmt ch ?? */ + switch (pipe_data->format) { + case IF_USB_FMT_EP: + if (usb_ld->if_usb_is_main) { + pr_urb("IPC-RX", urb); + iod_format = IPC_FMT; + } else { + iod_format = IPC_BOOT; + } + break; + case IF_USB_RAW_EP: + iod_format = IPC_MULTI_RAW; + break; + case IF_USB_RFS_EP: + iod_format = IPC_RFS; + break; + case IF_USB_CMD_EP: + iod_format = IPC_CMD; + break; + default: + iod_format = -1; + break; + } + + /* flow control CMD by CP, not use io device */ + if (unlikely(iod_format == IPC_CMD)) { + ret = link_rx_flowctl_cmd(&usb_ld->ld, + (char *)urb->transfer_buffer, + urb->actual_length); + if (ret < 0) + mif_err("no multi raw device (%d)\n", ret); + goto rx_submit; + } + + iod = link_get_iod_with_format(&usb_ld->ld, iod_format); + if (iod) { + ret = iod->recv(iod, + &usb_ld->ld, + (char *)urb->transfer_buffer, + urb->actual_length); + if (ret == -ENOMEM) { + /* retry the delay work and resubit*/ + mif_err("ENOMEM, retry\n"); + if (usb_ld->usbdev) + usb_mark_last_busy(usb_ld->usbdev); + usb_ld->retry_urb = urb; + queue_delayed_work(usb_ld->ld.tx_wq, + &usb_ld->rx_retry_work, 0); + return; + } + if (ret < 0) + mif_err("io device recv error (%d)\n", ret); + } +rx_submit: + if (urb->status == 0) { + if (usb_ld->usbdev) + usb_mark_last_busy(usb_ld->usbdev); + usb_rx_submit(usb_ld, pipe_data, GFP_ATOMIC); + } + break; + default: + mif_err("urb err status = %d\n", urb->status); + break; + } +} + +static int usb_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + struct sk_buff_head *txq; + size_t tx_size; + struct usb_link_device *usb_ld = to_usb_link_device(ld); + struct link_pm_data *pm_data = usb_ld->link_pm_data; + + switch (iod->format) { + case IPC_RAW: + txq = &ld->sk_raw_tx_q; + + if (unlikely(ld->raw_tx_suspended)) { + /* Unlike misc_write, vnet_xmit is in interrupt. + * Despite call netif_stop_queue on CMD_SUSPEND, + * packets can be reached here. + */ + if (in_irq()) { + mif_err("raw tx is suspended, " + "drop packet. size=%d", + skb->len); + return -EBUSY; + } + + mif_err("wait RESUME CMD...\n"); + INIT_COMPLETION(ld->raw_tx_resumed_by_cp); + wait_for_completion(&ld->raw_tx_resumed_by_cp); + mif_err("resumed done.\n"); + } + break; + case IPC_BOOT: + case IPC_FMT: + case IPC_RFS: + default: + txq = &ld->sk_fmt_tx_q; + break; + } + /* store the tx size before run the tx_delayed_work*/ + tx_size = skb->len; + + /* drop packet, when link is not online */ + if (ld->com_state == COM_BOOT && iod->format != IPC_BOOT) { + mif_err("%s: drop packet, size=%d, com_state=%d\n", + iod->name, skb->len, ld->com_state); + dev_kfree_skb_any(skb); + return 0; + } + + /* en queue skb data */ + skb_queue_tail(txq, skb); + /* Hold wake_lock for getting schedule the tx_work */ +#ifdef CONFIG_HAS_WAKELOCK + wake_lock(&pm_data->tx_async_wake); +#else + pm_stay_awake(pm_data->miscdev.this_device); +#endif + + if (!work_pending(&ld->tx_delayed_work.work)) + queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0); + + return tx_size; +} + +static void usb_tx_complete(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct io_device *iod = skbpriv(skb)->iod; + struct link_device *ld = skbpriv(skb)->ld; + struct usb_link_device *usb_ld = to_usb_link_device(ld); + + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + default: + if (iod->format != IPC_BOOT) + mif_info("TX error (%d)\n", urb->status); + } + + dev_kfree_skb_any(skb); + if (urb->dev && usb_ld->if_usb_connected) + usb_mark_last_busy(urb->dev); + usb_free_urb(urb); +} + +/* Even if usb_tx_urb_with_skb is failed, does not release the skb to retry */ +static int usb_tx_urb_with_skb(struct usb_device *usbdev, struct sk_buff *skb, + struct if_usb_devdata *pipe_data) +{ + int ret; + struct urb *urb; + + if (pipe_data->disconnected) + return -ENOENT; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + mif_err("alloc urb error\n"); + return -ENOMEM; + } + + urb->transfer_flags = URB_ZERO_PACKET; + usb_fill_bulk_urb(urb, pipe_data->usbdev, pipe_data->tx_pipe, skb->data, + skb->len, usb_tx_complete, (void *)skb); + + usb_mark_last_busy(usbdev); + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) { + mif_err("usb_submit_urb with ret(%d)\n", ret); + usb_free_urb(urb); + return ret; + } + return 0; +} + + +static int _usb_tx_work(struct sk_buff *skb) +{ + struct sk_buff_head *txq; + struct io_device *iod = skbpriv(skb)->iod; + struct link_device *ld = skbpriv(skb)->ld; + struct usb_link_device *usb_ld = to_usb_link_device(ld); + struct if_usb_devdata *pipe_data; + + switch (iod->format) { + case IPC_BOOT: + case IPC_FMT: + /* boot device uses same intf with fmt*/ + pipe_data = &usb_ld->devdata[IF_USB_FMT_EP]; + txq = &ld->sk_fmt_tx_q; + break; + case IPC_RAW: + pipe_data = &usb_ld->devdata[IF_USB_RAW_EP]; + txq = &ld->sk_raw_tx_q; + break; + case IPC_RFS: + pipe_data = &usb_ld->devdata[IF_USB_RFS_EP]; + txq = &ld->sk_fmt_tx_q; + break; + default: + /* wrong packet, drop it */ + pipe_data = NULL; + txq = NULL; + break; + } + + if (!pipe_data) + return -ENOENT; + + if (iod->format == IPC_FMT && usb_ld->if_usb_is_main) + pr_skb("IPC-TX", skb); + + if (iod->format == IPC_RAW) + mif_debug("TX[RAW]\n"); + + return usb_tx_urb_with_skb(usb_ld->usbdev, skb, pipe_data); +} + + +static void usb_tx_work(struct work_struct *work) +{ + int ret = 0; + struct link_device *ld = + container_of(work, struct link_device, tx_delayed_work.work); + struct usb_link_device *usb_ld = to_usb_link_device(ld); + struct sk_buff *skb; + struct link_pm_data *pm_data = usb_ld->link_pm_data; + + if (!usb_ld->usbdev) { + mif_info("usbdev is invalid\n"); + return; + } + + pm_data->tx_cnt++; + + while (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) { + /* request and check usb runtime pm first */ + ret = link_pm_runtime_get_active(pm_data); + if (ret < 0) { + if (ret == -ENODEV) { + mif_err("link not avail, retry reconnect.\n"); + goto exit; + } + goto retry_tx_work; + } + + /* If AP try to tx when interface disconnect->reconnect probe, + * usbdev was created but one of interface channel device are + * probing, _usb_tx_work return to -ENOENT then runtime usage + * count allways positive and never enter to L2 + */ + if (!usb_ld->if_usb_connected) { + mif_info("link is available, but if was not readey\n"); + goto retry_tx_work; + } + pm_runtime_get_sync(&usb_ld->usbdev->dev); + + ret = 0; + /* send skb from fmt_txq and raw_txq,*/ + /* one by one for fair flow control */ + skb = skb_dequeue(&ld->sk_fmt_tx_q); + if (skb) + ret = _usb_tx_work(skb); + + if (ret) { + mif_err("usb_tx_urb_with_skb for fmt_q %d\n", ret); + skb_queue_head(&ld->sk_fmt_tx_q, skb); + + if (ret == -ENODEV || ret == -ENOENT) + goto exit; + + /* tx fail and usbdev alived, retry tx work */ + pm_runtime_put(&usb_ld->usbdev->dev); + goto retry_tx_work; + } + + skb = skb_dequeue(&ld->sk_raw_tx_q); + if (skb) + ret = _usb_tx_work(skb); + + if (ret) { + mif_err("usb_tx_urb_with_skb for raw_q %d\n", ret); + skb_queue_head(&ld->sk_raw_tx_q, skb); + + if (ret == -ENODEV || ret == -ENOENT) + goto exit; + + pm_runtime_put(&usb_ld->usbdev->dev); + goto retry_tx_work; + } + + pm_runtime_put(&usb_ld->usbdev->dev); + } +#ifdef CONFIG_HAS_WAKELOCK + wake_unlock(&pm_data->tx_async_wake); +#else + pm_relax(pm_data->miscdev.this_device); +#endif +exit: + return; + +retry_tx_work: + queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, + msecs_to_jiffies(20)); + return; +} + +/* +#ifdef CONFIG_LINK_PM +*/ + +static int link_pm_runtime_get_active(struct link_pm_data *pm_data) +{ + int ret; + struct usb_link_device *usb_ld = pm_data->usb_ld; + struct device *dev = &usb_ld->usbdev->dev; + + if (!usb_ld->if_usb_connected || usb_ld->ld.com_state == COM_NONE) + return -ENODEV; + + if (pm_data->dpm_suspending) { + mif_err("Kernel in suspending try get_active later\n"); + /* during dpm_suspending.. + * if AP get tx data, wake up. */ +#ifdef CONFIG_HAS_WAKELOCK + wake_lock(&pm_data->l2_wake); +#else + pm_stay_awake(pm_data->miscdev.this_device); +#endif + return -EAGAIN; + } + + if (dev->power.runtime_status == RPM_ACTIVE) { + pm_data->resume_retry_cnt = 0; + return 0; + } + + if (!pm_data->resume_requested) { + mif_debug("QW PM\n"); + queue_delayed_work(pm_data->wq, &pm_data->link_pm_work, 0); + } + mif_debug("Wait pm\n"); + INIT_COMPLETION(pm_data->active_done); + ret = wait_for_completion_timeout(&pm_data->active_done, + msecs_to_jiffies(500)); + + /* If usb link was disconnected while waiting ACTIVE State, usb device + * was removed, usb_ld->usbdev->dev is invalid and below + * dev->power.runtime_status is also invalid address. + * It will be occured LPA L3 -> AP iniated L0 -> disconnect -> link + * timeout + */ + if (!usb_ld->if_usb_connected || usb_ld->ld.com_state == COM_NONE) { + mif_info("link disconnected after timed-out\n"); + return -ENODEV; + } + + if (dev->power.runtime_status != RPM_ACTIVE) { + mif_info("link_active (%d) retry\n", + dev->power.runtime_status); + return -EAGAIN; + } + mif_debug("link_active success(%d)\n", ret); + return 0; +} + +static void link_pm_runtime_start(struct work_struct *work) +{ + struct link_pm_data *pm_data = + container_of(work, struct link_pm_data, link_pm_start.work); + struct usb_device *usbdev = pm_data->usb_ld->usbdev; + struct device *dev, *hdev; + struct link_device *ld = &pm_data->usb_ld->ld; + + if (!pm_data->usb_ld->if_usb_connected + || pm_data->usb_ld->ld.com_state == COM_NONE) { + mif_debug("disconnect status, ignore\n"); + return; + } + + dev = &pm_data->usb_ld->usbdev->dev; + + /* wait interface driver resumming */ + if (dev->power.runtime_status == RPM_SUSPENDED) { + mif_info("suspended yet, delayed work\n"); + queue_delayed_work(pm_data->wq, &pm_data->link_pm_start, + msecs_to_jiffies(20)); + return; + } + + if (pm_data->usb_ld->usbdev && dev->parent) { + mif_info("rpm_status: %d\n", + dev->power.runtime_status); + pm_runtime_set_autosuspend_delay(dev, 200); + hdev = usbdev->bus->root_hub->dev.parent; + mif_info("EHCI runtime %s, %s\n", dev_driver_string(hdev), + dev_name(hdev)); + pm_runtime_allow(dev); + pm_runtime_allow(hdev);/*ehci*/ + pm_data->link_pm_active = true; + pm_data->resume_requested = false; + pm_data->link_reconnect_cnt = 5; + pm_data->resume_retry_cnt = 0; + + /* retry prvious link tx q */ + queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0); + } +} + +static void link_pm_force_cp_dump(struct link_pm_data *pm_data) +{ + struct modem_ctl *mc = if_usb_get_modemctl(pm_data); + + mif_err("Set modem crash ap_dump_int by %pF\n", + __builtin_return_address(0)); + + if (mc->gpio_ap_dump_int) { + if (gpio_get_value(mc->gpio_ap_dump_int)) { + gpio_set_value(mc->gpio_ap_dump_int, 0); + msleep(20); + } + gpio_set_value(mc->gpio_ap_dump_int, 1); + msleep(20); + mif_err("AP_DUMP_INT(%d)\n", + gpio_get_value(mc->gpio_ap_dump_int)); + gpio_set_value(mc->gpio_ap_dump_int, 0); + } +} + +static void link_pm_change_modem_state(struct link_pm_data *pm_data, + enum modem_state state) +{ + struct modem_ctl *mc = if_usb_get_modemctl(pm_data); + + if (!mc->iod || pm_data->usb_ld->ld.com_state != COM_ONLINE) + return; + + mif_err("set modem state %d by %pF\n", state, + __builtin_return_address(0)); + mc->iod->modem_state_changed(mc->iod, state); + mc->bootd->modem_state_changed(mc->bootd, state); +} + +static void link_pm_reconnect_work(struct work_struct *work) +{ + struct link_pm_data *pm_data = + container_of(work, struct link_pm_data, + link_reconnect_work.work); + struct modem_ctl *mc = if_usb_get_modemctl(pm_data); + + if (!mc || pm_data->usb_ld->if_usb_connected) + return; + + if (pm_data->usb_ld->ld.com_state != COM_ONLINE) + return; + + if (pm_data->link_reconnect_cnt--) { + if (mc->phone_state == STATE_ONLINE && + !pm_data->link_reconnect()) + /* try reconnect and check */ + schedule_delayed_work(&pm_data->link_reconnect_work, + msecs_to_jiffies(500)); + else /* under cp crash or reset, just return */ + return; + } else { + /* try to recover cp */ + mif_err("recover connection: silent reset\n"); + link_pm_change_modem_state(pm_data, STATE_CRASH_RESET); + } +} + +static inline int link_pm_slave_wake(struct link_pm_data *pm_data) +{ + int spin = 20; + + /* when slave device is in sleep, wake up slave cpu first */ + if (gpio_get_value(pm_data->gpio_link_hostwake) + != HOSTWAKE_TRIGLEVEL) { + if (gpio_get_value(pm_data->gpio_link_slavewake)) { + gpio_set_value(pm_data->gpio_link_slavewake, 0); + mif_info("gpio [SWK] set [0]\n"); + mdelay(5); + } + gpio_set_value(pm_data->gpio_link_slavewake, 1); + mif_info("gpio [SWK] set [1]\n"); + mdelay(5); + + /* wait host wake signal*/ + while (spin-- && gpio_get_value(pm_data->gpio_link_hostwake) != + HOSTWAKE_TRIGLEVEL) + mdelay(5); + } + return spin; +} + +static void link_pm_runtime_work(struct work_struct *work) +{ + int ret; + struct link_pm_data *pm_data = + container_of(work, struct link_pm_data, link_pm_work.work); + struct device *dev = &pm_data->usb_ld->usbdev->dev; + + if (!pm_data->usb_ld->if_usb_connected || pm_data->dpm_suspending) + return; + + if (pm_data->usb_ld->ld.com_state == COM_NONE) + return; + + mif_debug("for dev 0x%p : current %d\n", dev, + dev->power.runtime_status); + + switch (dev->power.runtime_status) { + case RPM_ACTIVE: + pm_data->resume_retry_cnt = 0; + pm_data->resume_requested = false; + complete(&pm_data->active_done); + + return; + case RPM_SUSPENDED: + if (pm_data->resume_requested) + break; + pm_data->resume_requested = true; +#ifdef CONFIG_HAS_WAKELOCK + wake_lock(&pm_data->rpm_wake); +#else + pm_stay_awake(pm_data->miscdev.this_device); +#endif + ret = link_pm_slave_wake(pm_data); + if (ret < 0) { + mif_err("slave wake fail\n"); +#ifdef CONFIG_HAS_WAKELOCK + wake_unlock(&pm_data->rpm_wake); +#else + pm_relax(pm_data->miscdev.this_device); +#endif + break; + } + + if (!pm_data->usb_ld->if_usb_connected) { +#ifdef CONFIG_HAS_WAKELOCK + wake_unlock(&pm_data->rpm_wake); +#else + pm_relax(pm_data->miscdev.this_device); +#endif + return; + } + + ret = pm_runtime_resume(dev); + if (ret < 0) { + mif_err("resume error(%d)\n", ret); + if (!pm_data->usb_ld->if_usb_connected) { +#ifdef CONFIG_HAS_WAKELOCK + wake_unlock(&pm_data->rpm_wake); +#else + pm_relax(pm_data->miscdev.this_device); +#endif + return; + } + /* force to go runtime idle before retry resume */ + if (dev->power.timer_expires == 0 && + !dev->power.request_pending) { + mif_debug("run time idle\n"); + pm_runtime_idle(dev); + } + } +#ifdef CONFIG_HAS_WAKELOCK + wake_unlock(&pm_data->rpm_wake); +#else + pm_relax(pm_data->miscdev.this_device); +#endif + break; + case RPM_SUSPENDING: + /* Checking the usb_runtime_suspend running time.*/ + mif_info("rpm_states=%d", dev->power.runtime_status); + msleep(20); + break; + default: + break; + } + pm_data->resume_requested = false; + + /* check until runtime_status goes to active */ + /* attemp 10 times, or re-establish modem-link */ + /* if pm_runtime_resume run properly, rpm status must be in ACTIVE */ + if (dev->power.runtime_status == RPM_ACTIVE) { + pm_data->resume_retry_cnt = 0; + complete(&pm_data->active_done); + } else if (pm_data->resume_retry_cnt++ > 10) { + mif_err("runtime_status(%d), retry_cnt(%d)\n", + dev->power.runtime_status, pm_data->resume_retry_cnt); + link_pm_change_modem_state(pm_data, STATE_CRASH_RESET); + } else + queue_delayed_work(pm_data->wq, &pm_data->link_pm_work, + msecs_to_jiffies(20)); +} + +static irqreturn_t link_pm_irq_handler(int irq, void *data) +{ + int value; + struct link_pm_data *pm_data = data; + +#if defined(CONFIG_SLP) + pm_wakeup_event(pm_data->miscdev.this_device, 0); +#endif + + if (!pm_data->link_pm_active) + return IRQ_HANDLED; + + /* host wake up HIGH */ + /* + resume usb runtime pm start + */ + /* host wake up LOW */ + /* + slave usb enumeration end, + host can send usb packet after + runtime pm status changes to ACTIVE + */ + value = gpio_get_value(pm_data->gpio_link_hostwake); + mif_info("gpio [HWK] get [%d]\n", value); + + /* + * igonore host wakeup interrupt at suspending kernel + */ + if (pm_data->dpm_suspending) { + mif_info("ignore request by suspending\n"); + /* Ignore HWK but AP got to L2 by suspending fail */ +#ifdef CONFIG_HAS_WAKELOCK + wake_lock(&pm_data->l2_wake); +#else + pm_stay_awake(pm_data->miscdev.this_device); +#endif + return IRQ_HANDLED; + } + + if (value == HOSTWAKE_TRIGLEVEL) { + /* move to slave wake function */ + /* runtime pm goes to active */ + /* + if (gpio_get_value(pm_data->gpio_link_active)) { + mif_err("gpio [H ACTV : %d] set 1\n", + gpio_get_value(pm_data->gpio_link_active)); + gpio_set_value(pm_data->gpio_link_active, 1); + } + */ + queue_delayed_work(pm_data->wq, &pm_data->link_pm_work, 0); + } else { + /* notification of enumeration process from slave device + * But it does not mean whole connection is in resume, so do not + * notify resume completion here. + + if (pm_data->link_pm_active && !pm_data->active_done.done) + complete(&pm_data->active_done); + */ + /* clear slave cpu wake up pin */ + gpio_set_value(pm_data->gpio_link_slavewake, 0); + mif_debug("gpio [SWK] set [0]\n"); + } + return IRQ_HANDLED; +} + +static long link_pm_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int value; + struct link_pm_data *pm_data = file->private_data; + struct modem_ctl *mc = if_usb_get_modemctl(pm_data); + + mif_info("%x\n", cmd); + + switch (cmd) { + case IOCTL_LINK_CONTROL_ENABLE: + if (copy_from_user(&value, (const void __user *)arg, + sizeof(int))) + return -EFAULT; + if (pm_data->link_ldo_enable) + pm_data->link_ldo_enable(!!value); + if (pm_data->gpio_link_enable) + gpio_set_value(pm_data->gpio_link_enable, value); + break; + case IOCTL_LINK_CONTROL_ACTIVE: + if (copy_from_user(&value, (const void __user *)arg, + sizeof(int))) + return -EFAULT; + gpio_set_value(pm_data->gpio_link_active, value); + break; + case IOCTL_LINK_GET_HOSTWAKE: + return !gpio_get_value(pm_data->gpio_link_hostwake); + case IOCTL_LINK_CONNECTED: + return pm_data->usb_ld->if_usb_connected; + case IOCTL_LINK_SET_BIAS_CLEAR: + if (copy_from_user(&value, (const void __user *)arg, + sizeof(int))) + return -EFAULT; + if (value) { + gpio_direction_output(pm_data->gpio_link_slavewake, 0); + gpio_direction_output(pm_data->gpio_link_hostwake, 0); + } else { + gpio_direction_output(pm_data->gpio_link_slavewake, 0); + gpio_direction_input(pm_data->gpio_link_hostwake); + irq_set_irq_type(pm_data->irq_link_hostwake, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING); + } + case IOCTL_LINK_GET_PHONEACTIVE: + return gpio_get_value(mc->gpio_phone_active); + default: + break; + } + + return 0; +} + +static int link_pm_open(struct inode *inode, struct file *file) +{ + struct link_pm_data *pm_data = + (struct link_pm_data *)file->private_data; + file->private_data = (void *)pm_data; + return 0; +} + +static int link_pm_release(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + return 0; +} + +static const struct file_operations link_pm_fops = { + .owner = THIS_MODULE, + .open = link_pm_open, + .release = link_pm_release, + .unlocked_ioctl = link_pm_ioctl, +}; + +static int link_pm_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct link_pm_data *pm_data = + container_of(this, struct link_pm_data, pm_notifier); +#ifdef CONFIG_UMTS_MODEM_XMM6262 + struct modem_ctl *mc = if_usb_get_modemctl(pm_data); +#endif + + switch (event) { + case PM_SUSPEND_PREPARE: +#ifdef CONFIG_HIBERNATION + case PM_HIBERNATION_PREPARE: + case PM_RESTORE_PREPARE: +#endif + pm_data->dpm_suspending = true; +#ifdef CONFIG_UMTS_MODEM_XMM6262 + /* set PDA Active High if previous state was LPA */ + if (!gpio_get_value(pm_data->gpio_link_active)) { + mif_info("PDA active High to LPA suspend spot\n"); + gpio_set_value(mc->gpio_pda_active, 1); + } +#endif + mif_debug("dpm suspending set to true\n"); + return NOTIFY_OK; + case PM_POST_SUSPEND: +#ifdef CONFIG_HIBERNATION + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: +#endif + pm_data->dpm_suspending = false; + if (gpio_get_value(pm_data->gpio_link_hostwake) + == HOSTWAKE_TRIGLEVEL) { + queue_delayed_work(pm_data->wq, &pm_data->link_pm_work, + 0); + mif_info("post resume\n"); + } +#ifdef CONFIG_UMTS_MODEM_XMM6262 + /* LPA to Kernel suspend and User Freezing task fail resume, + restore to LPA GPIO states. */ + if (!gpio_get_value(pm_data->gpio_link_active)) { + mif_info("PDA active low to LPA GPIO state\n"); + gpio_set_value(mc->gpio_pda_active, 0); + } +#endif + mif_debug("dpm suspending set to false\n"); + return NOTIFY_OK; + } + return NOTIFY_DONE; +} + +static struct modem_ctl *if_usb_get_modemctl(struct link_pm_data *pm_data) +{ + struct io_device *iod; + + iod = link_get_iod_with_format(&pm_data->usb_ld->ld, IPC_FMT); + if (!iod) { + mif_err("no iodevice for modem control\n"); + return NULL; + } + + return iod->mc; +} + +static int if_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct if_usb_devdata *devdata = usb_get_intfdata(intf); + struct link_pm_data *pm_data = devdata->usb_ld->link_pm_data; + if (!devdata->disconnected && devdata->state == STATE_RESUMED) { + usb_kill_urb(devdata->urb); + devdata->state = STATE_SUSPENDED; + } + + devdata->usb_ld->suspended++; + + if (devdata->usb_ld->suspended == LINKPM_DEV_NUM) { + mif_debug("[if_usb_suspended]\n"); +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_timeout(&pm_data->l2_wake, msecs_to_jiffies(50)); +#else + pm_wakeup_event(pm_data->miscdev.this_device, 50); +#endif + /* XMM6262 Host wakeup toggle recovery */ + if (!pm_data->rx_cnt && !pm_data->tx_cnt) { + if (pm_data->ipc_debug_cnt++ > 10) { + mif_err("No TX/RX after resume 10times\n"); + link_pm_change_modem_state(pm_data, + STATE_CRASH_RESET); + } + } else { + pm_data->ipc_debug_cnt = 0; + pm_data->rx_cnt = 0; + pm_data->tx_cnt = 0; + } + } + return 0; +} + +static int if_usb_resume(struct usb_interface *intf) +{ + int ret; + struct if_usb_devdata *devdata = usb_get_intfdata(intf); + struct link_pm_data *pm_data = devdata->usb_ld->link_pm_data; + + if (!devdata->disconnected && devdata->state == STATE_SUSPENDED) { + ret = usb_rx_submit(devdata->usb_ld, devdata, GFP_ATOMIC); + if (ret < 0) { + mif_err("usb_rx_submit error with (%d)\n", ret); + return ret; + } + devdata->state = STATE_RESUMED; + } + + /* For debugging - nomal case, never reach below... */ + if (pm_data->resume_retry_cnt > 5) { + mif_err("retry_cnt=%d, rpm_status=%d", + pm_data->resume_retry_cnt, + devdata->usb_ld->usbdev->dev.power.runtime_status); + pm_data->resume_retry_cnt = 0; + } + + devdata->usb_ld->suspended--; + if (!devdata->usb_ld->suspended) { + mif_debug("[if_usb_resumed]\n"); +#ifdef CONFIG_HAS_WAKELOCK + wake_lock(&pm_data->l2_wake); +#else + pm_stay_awake(pm_data->miscdev.this_device); +#endif + } + + return 0; +} + +static int if_usb_reset_resume(struct usb_interface *intf) +{ + int ret; + struct if_usb_devdata *devdata = usb_get_intfdata(intf); + struct link_pm_data *pm_data = devdata->usb_ld->link_pm_data; + + ret = if_usb_resume(intf); + pm_data->ipc_debug_cnt = 0; + /* + * for runtime suspend, kick runtime pm at L3 -> L0 reset resume + */ + if (!devdata->usb_ld->suspended) + queue_delayed_work(pm_data->wq, &pm_data->link_pm_start, 0); + return ret; +} + +static void if_usb_disconnect(struct usb_interface *intf) +{ + struct if_usb_devdata *devdata = usb_get_intfdata(intf); + struct link_pm_data *pm_data = devdata->usb_ld->link_pm_data; + struct device *dev, *hdev; + struct link_device *ld = &devdata->usb_ld->ld; + + mif_info("\n"); + + if (devdata->disconnected) + return; + + devdata->usb_ld->if_usb_connected = 0; + + usb_driver_release_interface(to_usb_driver(intf->dev.driver), intf); + + usb_kill_urb(devdata->urb); + + hdev = devdata->usbdev->bus->root_hub->dev.parent; + pm_runtime_forbid(hdev); /*ehci*/ + + mif_info("put dev 0x%p\n", devdata->usbdev); + usb_put_dev(devdata->usbdev); + + devdata->data_intf = NULL; + devdata->usbdev = NULL; + /* if possible, merge below 2 variables */ + devdata->disconnected = 1; + devdata->state = STATE_SUSPENDED; + pm_data->ipc_debug_cnt = 0; + + devdata->usb_ld->suspended = 0; +#ifdef CONFIG_HAS_WAKELOCK + wake_lock(&pm_data->boot_wake); +#else + pm_stay_awake(pm_data->miscdev.this_device); +#endif + usb_set_intfdata(intf, NULL); + + /* cancel runtime start delayed works */ + cancel_delayed_work_sync(&pm_data->link_pm_start); + cancel_delayed_work_sync(&ld->tx_delayed_work); + + /* if reconnect function exist , try reconnect without reset modem + * reconnect function checks modem is under crash or not, so we don't + * need check crash state here. reconnect work checks and determine + * further works + */ + if (!pm_data->link_reconnect) + return; + + if (devdata->usb_ld->ld.com_state != COM_ONLINE) { + cancel_delayed_work(&pm_data->link_reconnect_work); + return; + } else { + if (pm_data->ehci_reg_dump) + pm_data->ehci_reg_dump(hdev); + schedule_delayed_work(&pm_data->link_reconnect_work, + msecs_to_jiffies(500)); + } + return; +} + +static int if_usb_set_pipe(struct usb_link_device *usb_ld, + const struct usb_host_interface *desc, int pipe) +{ + if (pipe < 0 || pipe >= IF_USB_DEVNUM_MAX) { + mif_err("undefined endpoint, exceed max\n"); + return -EINVAL; + } + + mif_info("set %d\n", pipe); + + if ((usb_pipein(desc->endpoint[0].desc.bEndpointAddress)) && + (usb_pipeout(desc->endpoint[1].desc.bEndpointAddress))) { + usb_ld->devdata[pipe].rx_pipe = usb_rcvbulkpipe(usb_ld->usbdev, + desc->endpoint[0].desc.bEndpointAddress); + usb_ld->devdata[pipe].tx_pipe = usb_sndbulkpipe(usb_ld->usbdev, + desc->endpoint[1].desc.bEndpointAddress); + } else if ((usb_pipeout(desc->endpoint[0].desc.bEndpointAddress)) && + (usb_pipein(desc->endpoint[1].desc.bEndpointAddress))) { + usb_ld->devdata[pipe].rx_pipe = usb_rcvbulkpipe(usb_ld->usbdev, + desc->endpoint[1].desc.bEndpointAddress); + usb_ld->devdata[pipe].tx_pipe = usb_sndbulkpipe(usb_ld->usbdev, + desc->endpoint[0].desc.bEndpointAddress); + } else { + mif_err("undefined endpoint\n"); + return -EINVAL; + } + + return 0; +} + +static int __devinit if_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int err; + int pipe; + const struct usb_cdc_union_desc *union_hdr; + const struct usb_host_interface *data_desc; + unsigned char *buf = intf->altsetting->extra; + int buflen = intf->altsetting->extralen; + struct usb_interface *data_intf; + struct usb_device *usbdev = interface_to_usbdev(intf); + struct usb_driver *usbdrv = to_usb_driver(intf->dev.driver); + struct usb_id_info *info = (struct usb_id_info *)id->driver_info; + struct usb_link_device *usb_ld = info->usb_ld; + + mif_info("usbdev = 0x%p\n", usbdev); + + pr_debug("%s: Class=%d, SubClass=%d, Protocol=%d\n", __func__, + intf->altsetting->desc.bInterfaceClass, + intf->altsetting->desc.bInterfaceSubClass, + intf->altsetting->desc.bInterfaceProtocol); + + /* if usb disconnected, AP try to reconnect 5 times. + * but because if_sub_connected is configured + * at the end of if_usb_probe, there was a chance + * that swk will be called again during enumeration. + * so.. cancel reconnect work_queue in this case. */ + if (usb_ld->ld.com_state == COM_ONLINE) + cancel_delayed_work(&usb_ld->link_pm_data->link_reconnect_work); + + usb_ld->usbdev = usbdev; + pm_runtime_forbid(&usbdev->dev); + usb_ld->link_pm_data->link_pm_active = false; + usb_ld->link_pm_data->dpm_suspending = false; + usb_ld->link_pm_data->ipc_debug_cnt = 0; + usb_ld->if_usb_is_main = (info->intf_id != BOOT_DOWN); + + union_hdr = NULL; + /* for WMC-ACM compatibility, WMC-ACM use an end-point for control msg*/ + if (intf->altsetting->desc.bInterfaceSubClass != USB_CDC_SUBCLASS_ACM) { + mif_err("ignore Non ACM end-point\n"); + return -EINVAL; + } + + if (!buflen) { + if (intf->cur_altsetting->endpoint->extralen && + intf->cur_altsetting->endpoint->extra) { + buflen = intf->cur_altsetting->endpoint->extralen; + buf = intf->cur_altsetting->endpoint->extra; + } else { + mif_err("Zero len descriptor reference\n"); + return -EINVAL; + } + } + + while (buflen > 0) { + if (buf[1] == USB_DT_CS_INTERFACE) { + switch (buf[2]) { + case USB_CDC_UNION_TYPE: + if (union_hdr) + break; + union_hdr = (struct usb_cdc_union_desc *)buf; + break; + default: + break; + } + } + buf += buf[0]; + buflen -= buf[0]; + } + + if (!union_hdr) { + mif_err("USB CDC is not union type\n"); + return -EINVAL; + } + + data_intf = usb_ifnum_to_if(usbdev, union_hdr->bSlaveInterface0); + if (!data_intf) { + mif_err("data_inferface is NULL\n"); + return -ENODEV; + } + + data_desc = data_intf->altsetting; + if (!data_desc) { + mif_err("data_desc is NULL\n"); + return -ENODEV; + } + + switch (info->intf_id) { + case BOOT_DOWN: + pipe = IF_USB_BOOT_EP; + usb_ld->ld.com_state = COM_BOOT; + /* purge previous boot fmt/raw tx q + clear all tx q*/ + skb_queue_purge(&usb_ld->ld.sk_fmt_tx_q); + skb_queue_purge(&usb_ld->ld.sk_raw_tx_q); + break; + case IPC_CHANNEL: + pipe = intf->altsetting->desc.bInterfaceNumber / 2; + usb_ld->ld.com_state = COM_ONLINE; + break; + default: + pipe = -1; + break; + } + + if (if_usb_set_pipe(usb_ld, data_desc, pipe) < 0) + return -EINVAL; + + usb_ld->devdata[pipe].usbdev = usb_get_dev(usbdev); + mif_info("devdata usbdev = 0x%p\n", + usb_ld->devdata[pipe].usbdev); + usb_ld->devdata[pipe].usb_ld = usb_ld; + usb_ld->devdata[pipe].data_intf = data_intf; + usb_ld->devdata[pipe].format = pipe; + usb_ld->devdata[pipe].disconnected = 0; + usb_ld->devdata[pipe].state = STATE_RESUMED; + + usb_ld->suspended = 0; + + err = usb_driver_claim_interface(usbdrv, data_intf, + (void *)&usb_ld->devdata[pipe]); + if (err < 0) { + mif_err("usb_driver_claim() failed\n"); + return err; + } + + pm_suspend_ignore_children(&usbdev->dev, true); + + usb_set_intfdata(intf, (void *)&usb_ld->devdata[pipe]); + + /* rx start for this endpoint */ + usb_rx_submit(usb_ld, &usb_ld->devdata[pipe], GFP_KERNEL); + + if (info->intf_id == IPC_CHANNEL && + !work_pending(&usb_ld->link_pm_data->link_pm_start.work)) { + queue_delayed_work(usb_ld->link_pm_data->wq, + &usb_ld->link_pm_data->link_pm_start, + msecs_to_jiffies(500)); +#ifdef CONFIG_HAS_WAKELOCK + wake_lock(&usb_ld->link_pm_data->l2_wake); + wake_unlock(&usb_ld->link_pm_data->boot_wake); +#else + pm_stay_awake + (usb_ld->link_pm_data->miscdev.this_device); +#endif + } + + /* HSIC main comm channel has been established */ + if (pipe == IF_USB_CMD_EP) + link_pm_change_modem_state(usb_ld->link_pm_data, STATE_ONLINE); + + if (pipe == IF_USB_CMD_EP || info->intf_id == BOOT_DOWN) + usb_ld->if_usb_connected = 1; + + mif_info("successfully done\n"); + + return 0; +} + +static void if_usb_free_pipe_data(struct usb_link_device *usb_ld) +{ + int i; + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + kfree(usb_ld->devdata[i].rx_buf); + usb_kill_urb(usb_ld->devdata[i].urb); + } +} + +static struct usb_id_info hsic_boot_down_info = { + .intf_id = BOOT_DOWN, +}; +static struct usb_id_info hsic_channel_info = { + .intf_id = IPC_CHANNEL, +}; + +static struct usb_device_id if_usb_ids[] = { + {USB_DEVICE_AND_INTERFACE_INFO(IMC_BOOT_VID, IMC_BOOT_PID, + USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&hsic_boot_down_info,}, + {USB_DEVICE_AND_INTERFACE_INFO(IMC_MAIN_VID, IMC_MAIN_PID, + USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, 1), + .driver_info = (unsigned long)&hsic_channel_info,}, + {USB_DEVICE(STE_BOOT_VID, STE_BOOT_PID), + .driver_info = (unsigned long)&hsic_boot_down_info,}, + {USB_DEVICE(STE_MAIN_VID, STE_MAIN_PID), + .driver_info = (unsigned long)&hsic_channel_info,}, + {} +}; +MODULE_DEVICE_TABLE(usb, if_usb_ids); + +static struct usb_driver if_usb_driver = { + .name = "cdc_modem", + .probe = if_usb_probe, + .disconnect = if_usb_disconnect, + .id_table = if_usb_ids, + .suspend = if_usb_suspend, + .resume = if_usb_resume, + .reset_resume = if_usb_reset_resume, + .supports_autosuspend = 1, +}; + +static int if_usb_init(struct link_device *ld) +{ + int ret; + int i; + struct usb_link_device *usb_ld = to_usb_link_device(ld); + struct if_usb_devdata *pipe_data; + struct usb_id_info *id_info; + + /* to connect usb link device with usb interface driver */ + for (i = 0; i < ARRAY_SIZE(if_usb_ids); i++) { + id_info = (struct usb_id_info *)if_usb_ids[i].driver_info; + if (id_info) + id_info->usb_ld = usb_ld; + } + + ret = usb_register(&if_usb_driver); + if (ret) { + mif_err("usb_register_driver() fail : %d\n", ret); + return ret; + } + + /* allocate rx buffer for usb receive */ + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + pipe_data = &usb_ld->devdata[i]; + pipe_data->format = i; + pipe_data->rx_buf_size = 16 * 1024; + + pipe_data->rx_buf = kmalloc(pipe_data->rx_buf_size, + GFP_DMA | GFP_KERNEL); + if (!pipe_data->rx_buf) { + if_usb_free_pipe_data(usb_ld); + ret = -ENOMEM; + break; + } + + pipe_data->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pipe_data->urb) { + mif_err("alloc urb fail\n"); + if_usb_free_pipe_data(usb_ld); + return -ENOMEM; + } + } + + mif_info("if_usb_init() done : %d, usb_ld (0x%p)\n", + ret, usb_ld); + return ret; +} + +static int usb_link_pm_init(struct usb_link_device *usb_ld, void *data) +{ + int r; + struct platform_device *pdev = (struct platform_device *)data; + struct modem_data *pdata = + (struct modem_data *)pdev->dev.platform_data; + struct modemlink_pm_data *pm_pdata; + struct link_pm_data *pm_data = + kzalloc(sizeof(struct link_pm_data), GFP_KERNEL); + + if (!pdata || !pdata->link_pm_data) { + mif_err("platform data is NULL\n"); + return -EINVAL; + } + pm_pdata = pdata->link_pm_data; + + if (!pm_data) { + mif_err("link_pm_data is NULL\n"); + return -ENOMEM; + } + + /* get link pm data from modemcontrol's platform data */ + pm_data->gpio_link_active = pm_pdata->gpio_link_active; + pm_data->gpio_link_enable = pm_pdata->gpio_link_enable; + pm_data->gpio_link_hostwake = pm_pdata->gpio_link_hostwake; + pm_data->gpio_link_slavewake = pm_pdata->gpio_link_slavewake; + pm_data->irq_link_hostwake = gpio_to_irq(pm_data->gpio_link_hostwake); + pm_data->link_ldo_enable = pm_pdata->link_ldo_enable; + pm_data->link_reconnect = pm_pdata->link_reconnect; + pm_data->ehci_reg_dump = pm_pdata->ehci_reg_dump; + + pm_data->usb_ld = usb_ld; + pm_data->link_pm_active = false; + pm_data->ipc_debug_cnt = 0; + usb_ld->link_pm_data = pm_data; + + pm_data->miscdev.minor = MISC_DYNAMIC_MINOR; + pm_data->miscdev.name = "link_pm"; + pm_data->miscdev.fops = &link_pm_fops; + + r = misc_register(&pm_data->miscdev); + if (r < 0) { + mif_err("fail to register pm device(%d)\n", r); + goto err_misc_register; + } + + r = request_irq(pm_data->irq_link_hostwake, link_pm_irq_handler, + IRQF_NO_SUSPEND | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "hostwake", (void *)pm_data); + if (r) { + mif_err("fail to request irq(%d)\n", r); + goto err_request_irq; + } + + r = enable_irq_wake(pm_data->irq_link_hostwake); + if (r) { + mif_err("failed to enable_irq_wake:%d\n", r); + goto err_set_wake_irq; + } + + /* create work queue & init work for runtime pm */ + pm_data->wq = create_singlethread_workqueue("linkpmd"); + if (!pm_data->wq) { + mif_err("fail to create wq\n"); + goto err_create_wq; + } + + pm_data->pm_notifier.notifier_call = link_pm_notifier_event; + register_pm_notifier(&pm_data->pm_notifier); + + init_completion(&pm_data->active_done); + INIT_DELAYED_WORK(&pm_data->link_pm_work, link_pm_runtime_work); + INIT_DELAYED_WORK(&pm_data->link_pm_start, link_pm_runtime_start); + INIT_DELAYED_WORK(&pm_data->link_reconnect_work, + link_pm_reconnect_work); +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&pm_data->l2_wake, WAKE_LOCK_SUSPEND, "l2_hsic"); + wake_lock_init(&pm_data->boot_wake, WAKE_LOCK_SUSPEND, "boot_hsic"); + wake_lock_init(&pm_data->rpm_wake, WAKE_LOCK_SUSPEND, "rpm_hsic"); + wake_lock_init(&pm_data->tx_async_wake, WAKE_LOCK_SUSPEND, "tx_hsic"); +#else + device_init_wakeup(pm_data->miscdev.this_device, true); +#endif + + return 0; + +err_create_wq: + disable_irq_wake(pm_data->irq_link_hostwake); +err_set_wake_irq: + free_irq(pm_data->irq_link_hostwake, (void *)pm_data); +err_request_irq: + misc_deregister(&pm_data->miscdev); +err_misc_register: + kfree(pm_data); + return r; +} + +struct link_device *hsic_create_link_device(void *data) +{ + int ret; + struct usb_link_device *usb_ld; + struct link_device *ld; + + usb_ld = kzalloc(sizeof(struct usb_link_device), GFP_KERNEL); + if (!usb_ld) + return NULL; + + INIT_LIST_HEAD(&usb_ld->ld.list); + skb_queue_head_init(&usb_ld->ld.sk_fmt_tx_q); + skb_queue_head_init(&usb_ld->ld.sk_raw_tx_q); + + ld = &usb_ld->ld; + + ld->name = "usb"; + ld->init_comm = usb_init_communication; + ld->terminate_comm = usb_terminate_communication; + ld->send = usb_send; + ld->com_state = COM_NONE; + ld->raw_tx_suspended = false; + init_completion(&ld->raw_tx_resumed_by_cp); + + ld->tx_wq = create_singlethread_workqueue("usb_tx_wq"); + if (!ld->tx_wq) { + mif_err("fail to create work Q.\n"); + goto err; + } + + INIT_DELAYED_WORK(&ld->tx_delayed_work, usb_tx_work); + INIT_DELAYED_WORK(&usb_ld->rx_retry_work, usb_rx_retry_work); + usb_ld->rx_retry_cnt = 0; + + /* create link pm device */ + ret = usb_link_pm_init(usb_ld, data); + if (ret) + goto err; + + ret = if_usb_init(ld); + if (ret) + goto err; + + mif_info("%s : create_link_device DONE\n", usb_ld->ld.name); + return (void *)ld; +err: + kfree(usb_ld); + return NULL; +} + +static void __exit if_usb_exit(void) +{ + usb_deregister(&if_usb_driver); +} diff --git a/drivers/misc/modem_if/modem_link_device_hsic.h b/drivers/misc/modem_if/modem_link_device_hsic.h new file mode 100644 index 00000000000..604b06784e2 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_hsic.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_LINK_DEVICE_USB_H__ +#define __MODEM_LINK_DEVICE_USB_H__ + + +enum { + IF_USB_BOOT_EP = 0, + IF_USB_FMT_EP = 0, + IF_USB_RAW_EP, + IF_USB_RFS_EP, + IF_USB_CMD_EP, + IF_USB_DEVNUM_MAX, +}; + +/* each pipe has 2 ep for in/out */ +#define LINKPM_DEV_NUM (IF_USB_DEVNUM_MAX * 2) +/******************/ +/* xmm6260 specific */ + +#define IOCTL_LINK_CONTROL_ENABLE _IO('o', 0x30) +#define IOCTL_LINK_CONTROL_ACTIVE _IO('o', 0x31) +#define IOCTL_LINK_GET_HOSTWAKE _IO('o', 0x32) +#define IOCTL_LINK_CONNECTED _IO('o', 0x33) +#define IOCTL_LINK_SET_BIAS_CLEAR _IO('o', 0x34) +#define IOCTL_LINK_GET_PHONEACTIVE _IO('o', 0x35) + +/* VID,PID for IMC - XMM6260, XMM6262*/ +#define IMC_BOOT_VID 0x058b +#define IMC_BOOT_PID 0x0041 +#define IMC_MAIN_VID 0x1519 +#define IMC_MAIN_PID 0x0020 +/* VID,PID for STE - M7400 */ +#define STE_BOOT_VID 0x04cc +#define STE_BOOT_PID 0x7400 +#define STE_MAIN_VID 0x04cc +#define STE_MAIN_PID 0x2333 + +enum { + BOOT_DOWN = 0, + IPC_CHANNEL +}; + +enum ch_state { + STATE_SUSPENDED, + STATE_RESUMED, +}; + +#define HOSTWAKE_TRIGLEVEL 0 +/******************/ + +struct link_pm_info { + struct usb_link_device *usb_ld; +}; + +struct usb_id_info { + int intf_id; + struct usb_link_device *usb_ld; +}; + +struct link_pm_data { + struct miscdevice miscdev; + struct usb_link_device *usb_ld; + unsigned irq_link_hostwake; + int (*link_ldo_enable)(bool); + unsigned gpio_link_enable; + unsigned gpio_link_active; + unsigned gpio_link_hostwake; + unsigned gpio_link_slavewake; + int (*link_reconnect)(void); + int link_reconnect_cnt; + + struct workqueue_struct *wq; + struct completion active_done; + struct delayed_work link_pm_work; + struct delayed_work link_pm_start; + struct delayed_work link_reconnect_work; + bool resume_requested; + bool link_pm_active; + int resume_retry_cnt; + + struct wake_lock l2_wake; + struct wake_lock boot_wake; + struct wake_lock rpm_wake; + struct wake_lock tx_async_wake; + struct notifier_block pm_notifier; + bool dpm_suspending; + + /* Host wakeup toggle debugging */ + unsigned ipc_debug_cnt; + unsigned long tx_cnt; + unsigned long rx_cnt; + + void (*ehci_reg_dump)(struct device *); +}; + +struct if_usb_devdata { + struct usb_interface *data_intf; + struct usb_link_device *usb_ld; + struct usb_device *usbdev; + unsigned int tx_pipe; + unsigned int rx_pipe; + u8 disconnected; + + int format; + struct urb *urb; + void *rx_buf; + unsigned int rx_buf_size; + enum ch_state state; +}; + +struct usb_link_device { + /*COMMON LINK DEVICE*/ + struct link_device ld; + + /*USB SPECIFIC LINK DEVICE*/ + struct usb_device *usbdev; + struct if_usb_devdata devdata[IF_USB_DEVNUM_MAX]; + unsigned int dev_count; + unsigned int suspended; + int if_usb_connected; + + bool if_usb_is_main; /* boot,down(false) or main(true) */ + + /* LINK PM DEVICE DATA */ + struct link_pm_data *link_pm_data; + + /*RX retry work by -ENOMEM*/ + struct delayed_work rx_retry_work; + struct urb *retry_urb; + unsigned rx_retry_cnt; +}; +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_usb_link_device(linkdev) \ + container_of(linkdev, struct usb_link_device, ld) + + +#ifdef FOR_TEGRA +extern void tegra_ehci_txfilltuning(void); +#endif + +#endif diff --git a/drivers/misc/modem_if/modem_link_device_memory.h b/drivers/misc/modem_if/modem_link_device_memory.h new file mode 100644 index 00000000000..9ea01a74867 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_memory.h @@ -0,0 +1,501 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MODEM_LINK_DEVICE_MEMORY_H__ +#define __MODEM_LINK_DEVICE_MEMORY_H__ + +#include <linux/spinlock.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/timer.h> +#include <linux/platform_data/modem.h> + +#include "modem_prj.h" + +#define DPRAM_MAGIC_CODE 0xAA + +/* interrupt masks.*/ +#define INT_MASK_VALID 0x0080 +#define INT_MASK_CMD 0x0040 +#define INT_VALID(x) ((x) & INT_MASK_VALID) +#define INT_CMD_VALID(x) ((x) & INT_MASK_CMD) +#define INT_NON_CMD(x) (INT_MASK_VALID | (x)) +#define INT_CMD(x) (INT_MASK_VALID | INT_MASK_CMD | (x)) + +#define EXT_UDL_MASK 0xF000 +#define EXT_UDL_CMD(x) ((x) & EXT_UDL_MASK) +#define EXT_INT_VALID_MASK 0x8000 +#define EXT_CMD_VALID_MASK 0x4000 +#define UDL_CMD_VALID_MASK 0x2000 +#define EXT_INT_VALID(x) ((x) & EXT_INT_VALID_MASK) +#define EXT_CMD_VALID(x) ((x) & EXT_CMD_VALID_MASK) +#define UDL_CMD_VALID(x) ((x) & UDL_CMD_VALID_MASK) +#define INT_EXT_CMD(x) (EXT_INT_VALID_MASK | EXT_CMD_VALID_MASK | (x)) + +#define EXT_CMD_MASK(x) ((x) & 0x0FFF) +#define EXT_CMD_SET_SPEED_LOW 0x0011 +#define EXT_CMD_SET_SPEED_MID 0x0012 +#define EXT_CMD_SET_SPEED_HIGH 0x0013 + +#define UDL_RESULT_SUCCESS 0x1 +#define UDL_RESULT_FAIL 0x2 + +#define UDL_CMD_MASK(x) (((x) >> 8) & 0xF) +#define UDL_CMD_RECV_READY 0x1 +#define UDL_CMD_DL_START_REQ 0x2 +#define UDL_CMD_DL_START_RESP 0x3 +#define UDL_CMD_IMAGE_SEND_REQ 0x4 +#define UDL_CMD_SEND_DONE_RESP 0x5 +#define UDL_CMD_SEND_DONE_REQ 0x6 +#define UDL_CMD_UPDATE_DONE 0x7 +#define UDL_CMD_STATUS_UPDATE 0x8 +#define UDL_CMD_IMAGE_SEND_RESP 0x9 +#define UDL_CMD_EFS_CLEAR_RESP 0xB +#define UDL_CMD_ALARM_BOOT_OK 0xC +#define UDL_CMD_ALARM_BOOT_FAIL 0xD + +#define CMD_IMG_START_REQ 0x9200 +#define CMD_IMG_SEND_REQ 0x9400 +#define CMD_DL_SEND_DONE_REQ 0x9600 +#define CMD_UL_RECV_RESP 0x9601 +#define CMD_UL_RECV_DONE_RESP 0x9801 + +/* special interrupt cmd indicating modem boot failure. */ +#define INT_POWERSAFE_FAIL 0xDEAD + +#define INT_MASK_REQ_ACK_F 0x0020 +#define INT_MASK_REQ_ACK_R 0x0010 +#define INT_MASK_RES_ACK_F 0x0008 +#define INT_MASK_RES_ACK_R 0x0004 +#define INT_MASK_SEND_F 0x0002 +#define INT_MASK_SEND_R 0x0001 + +#define INT_MASK_REQ_ACK_RFS 0x0400 /* Request RES_ACK_RFS */ +#define INT_MASK_RES_ACK_RFS 0x0200 /* Response of REQ_ACK_RFS */ +#define INT_MASK_SEND_RFS 0x0100 /* Indicate sending RFS data */ + +#define INT_MASK_RES_ACK_SET \ + (INT_MASK_RES_ACK_F | INT_MASK_RES_ACK_R | INT_MASK_RES_ACK_RFS) + +#define INT_MASK_SEND_SET \ + (INT_MASK_SEND_F | INT_MASK_SEND_R | INT_MASK_SEND_RFS) + +#define INT_CMD_MASK(x) ((x) & 0xF) +#define INT_CMD_INIT_START 0x1 +#define INT_CMD_INIT_END 0x2 +#define INT_CMD_REQ_ACTIVE 0x3 +#define INT_CMD_RES_ACTIVE 0x4 +#define INT_CMD_REQ_TIME_SYNC 0x5 +#define INT_CMD_CRASH_RESET 0x7 +#define INT_CMD_PHONE_START 0x8 +#define INT_CMD_ERR_DISPLAY 0x9 +#define INT_CMD_CRASH_EXIT 0x9 +#define INT_CMD_CP_DEEP_SLEEP 0xA +#define INT_CMD_NV_REBUILDING 0xB +#define INT_CMD_EMER_DOWN 0xC +#define INT_CMD_PIF_INIT_DONE 0xD +#define INT_CMD_SILENT_NV_REBUILDING 0xE +#define INT_CMD_NORMAL_PWR_OFF 0xF + +#define START_FLAG 0x7F +#define END_FLAG 0x7E + +#define DP_MAGIC_DMDL 0x4445444C +#define DP_MAGIC_UMDL 0x4445444D +#define DP_DPRAM_SIZE 0x4000 +#define DP_DEFAULT_WRITE_LEN 8168 +#define DP_DEFAULT_DUMP_LEN 16128 +#define DP_DUMP_HEADER_SIZE 7 + +#define UDL_TIMEOUT (50 * HZ) +#define UDL_SEND_TIMEOUT (200 * HZ) +#define FORCE_CRASH_ACK_TIMEOUT (5 * HZ) +#define DUMP_TIMEOUT (30 * HZ) +#define DUMP_START_TIMEOUT (100 * HZ) +#define DUMP_WAIT_TIMEOUT (HZ >> 10) /* 1/1024 second */ +#define RES_ACK_WAIT_TIMEOUT (HZ >> 8) /* 1/256 second */ + +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP +#define MAX_RETRY_CNT 1 +#else +#define MAX_RETRY_CNT 3 +#endif + +#define MAX_SKB_TXQ_DEPTH 1024 + +enum host_boot_mode { + HOST_BOOT_MODE_NORMAL, + HOST_BOOT_MODE_DUMP, +}; + +enum dpram_init_status { + DPRAM_INIT_STATE_NONE, + DPRAM_INIT_STATE_READY, +}; + +struct dpram_boot_img { + char *addr; + int size; + enum host_boot_mode mode; + unsigned req; + unsigned resp; +}; + +#define MAX_PAYLOAD_SIZE 0x2000 +struct dpram_boot_frame { + unsigned req; /* AP->CP request */ + unsigned resp; /* response expected by AP */ + ssize_t len; /* data size in the buffer */ + unsigned offset; /* offset to write into DPRAM */ + char data[MAX_PAYLOAD_SIZE]; +}; + +/* buffer type for modem image */ +struct dpram_dump_arg { + char *buff; /* pointer to the buffer */ + int buff_size; /* buffer size */ + unsigned req; /* AP->CP request */ + unsigned resp; /* CP->AP response */ + bool cmd; /* AP->CP command */ +}; + +struct dpram_boot_map { + u32 __iomem *magic; + u8 __iomem *buff; + u32 __iomem *req; + u32 __iomem *resp; + u32 size; +}; + +struct qc_dpram_boot_map { + u8 __iomem *buff; + u16 __iomem *frame_size; + u16 __iomem *tag; + u16 __iomem *count; +}; + +struct dpram_dload_map { + u32 __iomem *magic; + u8 __iomem *buff; +}; + +struct dpram_uload_map { + u32 __iomem *magic; + u8 __iomem *buff; +}; + +struct ul_header { + u8 bop; + u16 total_frame; + u16 curr_frame; + u16 len; +} __packed; + +struct dpram_udl_param { + unsigned char *addr; + unsigned int size; + unsigned int count; + unsigned int tag; +}; + +struct dpram_udl_check { + unsigned int total_size; + unsigned int rest_size; + unsigned int send_size; + unsigned int copy_start; + unsigned int copy_complete; + unsigned int boot_complete; +}; + +#define DP_BOOT_BUFF_OFFSET 4 +#define DP_DLOAD_BUFF_OFFSET 4 +#define DP_ULOAD_BUFF_OFFSET 4 +#define DP_BOOT_REQ_OFFSET 0 +#define DP_BOOT_RESP_OFFSET 8 + +static inline bool circ_valid(u32 qsize, u32 in, u32 out) +{ + if (in >= qsize) + return false; + + if (out >= qsize) + return false; + + return true; +} + +static inline int circ_get_space(int qsize, int in, int out) +{ + return (in < out) ? (out - in - 1) : (qsize + out - in - 1); +} + +static inline int circ_get_usage(int qsize, int in, int out) +{ + return (in >= out) ? (in - out) : (qsize - out + in); +} + +/** + * circ_read + * @dst: pointer to the destination buffer + * @src: pointer to the start of the circular queue + * @qsize: size of the circular queue + * @out: offset to read + * @len: length of data to be read + * + * Should be invoked after checking data length + */ +static inline void circ_read(u8 *dst, u8 *src, u32 qsize, u32 out, u32 len) +{ + unsigned len1; + + if ((out + len) <= qsize) { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + memcpy(dst, (src + out), len); + } else { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + + /* 1) data start (out) ~ buffer end */ + len1 = qsize - out; + memcpy(dst, (src + out), len1); + + /* 2) buffer start ~ data end (in?) */ + memcpy((dst + len1), src, (len - len1)); + } +} + +/** + * circ_write + * @dst: pointer to the start of the circular queue + * @src: pointer to the source + * @qsize: size of the circular queue + * @in: offset to write + * @len: length of data to be written + * + * Should be invoked after checking free space + */ +static inline void circ_write(u8 *dst, u8 *src, u32 qsize, u32 in, u32 len) +{ + u32 space; + + if ((in + len) < qsize) { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + memcpy((dst + in), src, len); + } else { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + + /* 1) space start (in) ~ buffer end */ + space = qsize - in; + memcpy((dst + in), src, ((len > space) ? space : len)); + + /* 2) buffer start ~ data end */ + if (len > space) + memcpy(dst, (src + space), (len - space)); + } +} + +#if 1 +#define DPRAM_MAX_RXBQ_SIZE 256 + +struct mif_rxb { + u8 *buff; + unsigned size; + + u8 *data; + unsigned len; +}; + +struct mif_rxb_queue { + int size; + int in; + int out; + struct mif_rxb *rxb; +}; + +/* +** RXB (DPRAM RX buffer) functions +*/ +static inline struct mif_rxb *rxbq_create_pool(unsigned size, int count) +{ + struct mif_rxb *rxb; + u8 *buff; + int i; + + rxb = kzalloc(sizeof(struct mif_rxb) * count, GFP_KERNEL); + if (!rxb) { + mif_info("ERR! kzalloc rxb fail\n"); + return NULL; + } + + buff = kzalloc((size * count), GFP_KERNEL|GFP_DMA); + if (!buff) { + mif_info("ERR! kzalloc buff fail\n"); + kfree(rxb); + return NULL; + } + + for (i = 0; i < count; i++) { + rxb[i].buff = buff; + rxb[i].size = size; + buff += size; + } + + return rxb; +} + +static inline unsigned rxbq_get_page_size(unsigned len) +{ + return ((len + PAGE_SIZE - 1) >> PAGE_SHIFT) << PAGE_SHIFT; +} + +static inline bool rxbq_empty(struct mif_rxb_queue *rxbq) +{ + return (rxbq->in == rxbq->out) ? true : false; +} + +static inline int rxbq_free_size(struct mif_rxb_queue *rxbq) +{ + int in = rxbq->in; + int out = rxbq->out; + int qsize = rxbq->size; + return (in < out) ? (out - in - 1) : (qsize + out - in - 1); +} + +static inline struct mif_rxb *rxbq_get_free_rxb(struct mif_rxb_queue *rxbq) +{ + struct mif_rxb *rxb = NULL; + + if (likely(rxbq_free_size(rxbq) > 0)) { + rxb = &rxbq->rxb[rxbq->in]; + rxbq->in++; + if (rxbq->in >= rxbq->size) + rxbq->in -= rxbq->size; + rxb->data = rxb->buff; + } + + return rxb; +} + +static inline int rxbq_size(struct mif_rxb_queue *rxbq) +{ + int in = rxbq->in; + int out = rxbq->out; + int qsize = rxbq->size; + return (in >= out) ? (in - out) : (qsize - out + in); +} + +static inline struct mif_rxb *rxbq_get_data_rxb(struct mif_rxb_queue *rxbq) +{ + struct mif_rxb *rxb = NULL; + + if (likely(!rxbq_empty(rxbq))) { + rxb = &rxbq->rxb[rxbq->out]; + rxbq->out++; + if (rxbq->out >= rxbq->size) + rxbq->out -= rxbq->size; + } + + return rxb; +} + +static inline u8 *rxb_put(struct mif_rxb *rxb, unsigned len) +{ + rxb->len = len; + return rxb->data; +} + +static inline void rxb_clear(struct mif_rxb *rxb) +{ + rxb->data = NULL; + rxb->len = 0; +} +#endif + +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP +#define MAX_TRACE_SIZE 1024 + +struct trace_data { + struct timespec ts; + enum dev_format dev; + u8 *data; + int size; +}; + +struct trace_queue { + spinlock_t lock; + int in; + int out; + struct trace_data trd[MAX_TRACE_SIZE]; +}; + +static inline struct trace_data *trq_get_free_slot(struct trace_queue *trq) +{ + int in = trq->in; + int out = trq->out; + int qsize = MAX_TRACE_SIZE; + struct trace_data *trd = NULL; + unsigned long int flags; + + spin_lock_irqsave(&trq->lock, flags); + + if (circ_get_space(qsize, in, out) < 1) { + spin_unlock_irqrestore(&trq->lock, flags); + return NULL; + } + + trd = &trq->trd[in]; + + in++; + if (in == qsize) + trq->in = 0; + else + trq->in = in; + + spin_unlock_irqrestore(&trq->lock, flags); + + return trd; +} + +static inline struct trace_data *trq_get_data_slot(struct trace_queue *trq) +{ + int in = trq->in; + int out = trq->out; + int qsize = MAX_TRACE_SIZE; + struct trace_data *trd = NULL; + unsigned long int flags; + + spin_lock_irqsave(&trq->lock, flags); + + if (circ_get_usage(qsize, in, out) < 1) { + spin_unlock_irqrestore(&trq->lock, flags); + return NULL; + } + + trd = &trq->trd[out]; + + out++; + if (out == qsize) + trq->out = 0; + else + trq->out = out; + + spin_unlock_irqrestore(&trq->lock, flags); + + return trd; +} +#endif + +#endif diff --git a/drivers/misc/modem_if/modem_link_device_mipi.c b/drivers/misc/modem_if/modem_link_device_mipi.c new file mode 100644 index 00000000000..f2804e94ee1 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_mipi.c @@ -0,0 +1,1418 @@ +/* /linux/drivers/new_modem_if/link_dev_mipi.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/irq.h> +#include <linux/poll.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> +#include <linux/wakelock.h> +#include <linux/semaphore.h> +#include <linux/hsi_driver_if.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_mipi.h" +#include "modem_utils.h" + +static int mipi_hsi_init_communication(struct link_device *ld, + struct io_device *iod) +{ + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + switch (iod->format) { + case IPC_FMT: + return hsi_init_handshake(mipi_ld, HSI_INIT_MODE_NORMAL); + + case IPC_BOOT: + return hsi_init_handshake(mipi_ld, + HSI_INIT_MODE_FLASHLESS_BOOT); + + case IPC_RAMDUMP: + return hsi_init_handshake(mipi_ld, + HSI_INIT_MODE_CP_RAMDUMP); + + case IPC_RFS: + case IPC_RAW: + default: + return 0; + } +} + +static void mipi_hsi_terminate_communication( + struct link_device *ld, struct io_device *iod) +{ + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + switch (iod->format) { + case IPC_BOOT: + if (&mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened) + if_hsi_close_channel(&mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL]); + break; + + case IPC_RAMDUMP: + if (&mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].opened) + if_hsi_close_channel(&mipi_ld->hsi_channles[ + HSI_CP_RAMDUMP_CHANNEL]); + break; + + case IPC_FMT: + case IPC_RFS: + case IPC_RAW: + default: + break; + } +} + +static int mipi_hsi_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + int ret; + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + struct sk_buff_head *txq; + + switch (iod->format) { + case IPC_RAW: + txq = &ld->sk_raw_tx_q; + break; + + case IPC_RAMDUMP: + ret = if_hsi_write(&mipi_ld->hsi_channles[ + HSI_CP_RAMDUMP_CHANNEL], + (u32 *)skb->data, skb->len); + if (ret < 0) { + mif_err("[MIPI-HSI] write fail : %d\n", ret); + dev_kfree_skb_any(skb); + return ret; + } else + mif_debug("[MIPI-HSI] write Done\n"); + dev_kfree_skb_any(skb); + return ret; + + case IPC_BOOT: + ret = if_hsi_write(&mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL], + (u32 *)skb->data, skb->len); + if (ret < 0) { + mif_err("[MIPI-HSI] write fail : %d\n", ret); + dev_kfree_skb_any(skb); + return ret; + } else + mif_debug("[MIPI-HSI] write Done\n"); + dev_kfree_skb_any(skb); + return ret; + + case IPC_FMT: + case IPC_RFS: + default: + txq = &ld->sk_fmt_tx_q; + break; + } + + /* save io device */ + skbpriv(skb)->iod = iod; + /* en queue skb data */ + skb_queue_tail(txq, skb); + + queue_work(ld->tx_wq, &ld->tx_work); + return skb->len; +} + +static void mipi_hsi_tx_work(struct work_struct *work) +{ + int ret; + struct link_device *ld = container_of(work, struct link_device, + tx_work); + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + struct io_device *iod; + struct sk_buff *fmt_skb; + struct sk_buff *raw_skb; + int send_channel = 0; + + while (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) { + mif_debug("[MIPI-HSI] fmt qlen : %d, raw qlen:%d\n", + ld->sk_fmt_tx_q.qlen, ld->sk_raw_tx_q.qlen); + + fmt_skb = skb_dequeue(&ld->sk_fmt_tx_q); + if (fmt_skb) { + iod = skbpriv(fmt_skb)->iod; + + mif_debug("[MIPI-HSI] dequeue. fmt qlen : %d\n", + ld->sk_fmt_tx_q.qlen); + + if (ld->com_state != COM_ONLINE) { + mif_err("[MIPI-HSI] CP not ready\n"); + skb_queue_head(&ld->sk_fmt_tx_q, fmt_skb); + return; + } + + switch (iod->format) { + case IPC_FMT: + send_channel = HSI_FMT_CHANNEL; + break; + + case IPC_RFS: + send_channel = HSI_RFS_CHANNEL; + break; + + case IPC_BOOT: + send_channel = HSI_FLASHLESS_CHANNEL; + break; + + case IPC_RAMDUMP: + send_channel = HSI_CP_RAMDUMP_CHANNEL; + break; + + default: + break; + } + ret = if_hsi_protocol_send(mipi_ld, send_channel, + (u32 *)fmt_skb->data, fmt_skb->len); + if (ret < 0) { + /* TODO: Re Enqueue */ + mif_err("[MIPI-HSI] write fail : %d\n", ret); + } else + mif_debug("[MIPI-HSI] write Done\n"); + + dev_kfree_skb_any(fmt_skb); + } + + raw_skb = skb_dequeue(&ld->sk_raw_tx_q); + if (raw_skb) { + if (ld->com_state != COM_ONLINE) { + mif_err("[MIPI-HSI] RAW CP not ready\n"); + skb_queue_head(&ld->sk_raw_tx_q, raw_skb); + return; + } + + mif_debug("[MIPI-HSI] dequeue. raw qlen:%d\n", + ld->sk_raw_tx_q.qlen); + + ret = if_hsi_protocol_send(mipi_ld, HSI_RAW_CHANNEL, + (u32 *)raw_skb->data, raw_skb->len); + if (ret < 0) { + /* TODO: Re Enqueue */ + mif_err("[MIPI-HSI] write fail : %d\n", ret); + } else + mif_debug("[MIPI-HSI] write Done\n"); + + dev_kfree_skb_any(raw_skb); + } + } +} + +static int __devinit if_hsi_probe(struct hsi_device *dev); +static struct hsi_device_driver if_hsi_driver = { + .ctrl_mask = ANY_HSI_CONTROLLER, + .probe = if_hsi_probe, + .driver = { + .name = "if_hsi_driver" + }, +}; + +static int if_hsi_set_wakeline(struct if_hsi_channel *channel, + unsigned int state) +{ + int ret; + + spin_lock_bh(&channel->acwake_lock); + if (channel->acwake == state) { + spin_unlock_bh(&channel->acwake_lock); + return 0; + } + + ret = hsi_ioctl(channel->dev, state ? + HSI_IOCTL_ACWAKE_UP : HSI_IOCTL_ACWAKE_DOWN, NULL); + if (ret) { + mif_err("[MIPI-HSI] ACWAKE(%d) setting fail : %d\n", state, + ret); + /* duplicate operation */ + if (ret == -EPERM) + channel->acwake = state; + spin_unlock_bh(&channel->acwake_lock); + return ret; + } + + channel->acwake = state; + spin_unlock_bh(&channel->acwake_lock); + + mif_debug("[MIPI-HSI] ACWAKE_%d(%d)\n", channel->channel_id, state); + return 0; +} + +static void if_hsi_acwake_down_func(unsigned long data) +{ + int i; + struct if_hsi_channel *channel; + struct mipi_link_device *mipi_ld = (struct mipi_link_device *)data; + + mif_debug("[MIPI-HSI]\n"); + + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) { + channel = &mipi_ld->hsi_channles[i]; + + if ((channel->send_step == STEP_IDLE) && + (channel->recv_step == STEP_IDLE)) { + if_hsi_set_wakeline(channel, 0); + } else { + mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies + + HSI_ACWAKE_DOWN_TIMEOUT); + mif_debug("[MIPI-HSI] mod_timer done(%d)\n", + HSI_ACWAKE_DOWN_TIMEOUT); + return; + } + } +} + +static int if_hsi_open_channel(struct if_hsi_channel *channel) +{ + int ret; + + if (channel->opened) { + mif_debug("[MIPI-HSI] channel %d is already opened\n", + channel->channel_id); + return 0; + } + + ret = hsi_open(channel->dev); + if (ret) { + mif_err("[MIPI-HSI] hsi_open fail : %d\n", ret); + return ret; + } + channel->opened = 1; + + channel->send_step = STEP_IDLE; + channel->recv_step = STEP_IDLE; + + mif_debug("[MIPI-HSI] hsi_open Done : %d\n", channel->channel_id); + return 0; +} + +static int if_hsi_close_channel(struct if_hsi_channel *channel) +{ + unsigned long int flags; + + if (!channel->opened) { + mif_debug("[MIPI-HSI] channel %d is already closed\n", + channel->channel_id); + return 0; + } + + if_hsi_set_wakeline(channel, 0); + hsi_write_cancel(channel->dev); + hsi_read_cancel(channel->dev); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + spin_lock_irqsave(&channel->rx_state_lock, flags); + channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + hsi_close(channel->dev); + channel->opened = 0; + + channel->send_step = STEP_CLOSED; + channel->recv_step = STEP_CLOSED; + + mif_debug("[MIPI-HSI] hsi_close Done : %d\n", channel->channel_id); + return 0; +} + +static void mipi_hsi_start_work(struct work_struct *work) +{ + int ret; + u32 start_cmd = 0xC2; + struct mipi_link_device *mipi_ld = + container_of(work, struct mipi_link_device, + start_work.work); + + ret = if_hsi_protocol_send(mipi_ld, HSI_CMD_CHANNEL, &start_cmd, 1); + if (ret < 0) { + /* TODO: Re Enqueue */ + mif_err("[MIPI-HSI] First write fail : %d\n", ret); + } else { + mif_debug("[MIPI-HSI] First write Done : %d\n", ret); + mipi_ld->ld.com_state = COM_ONLINE; + } +} + +static int hsi_init_handshake(struct mipi_link_device *mipi_ld, int mode) +{ + int ret; + int i; + struct hst_ctx tx_config; + struct hsr_ctx rx_config; + + switch (mode) { + case HSI_INIT_MODE_NORMAL: + if (timer_pending(&mipi_ld->hsi_acwake_down_timer)) + del_timer(&mipi_ld->hsi_acwake_down_timer); + + for (i = 0; i < HSI_NUM_OF_USE_CHANNELS; i++) { + if (mipi_ld->hsi_channles[i].opened) { + hsi_write_cancel(mipi_ld->hsi_channles[i].dev); + hsi_read_cancel(mipi_ld->hsi_channles[i].dev); + } else { + ret = if_hsi_open_channel( + &mipi_ld->hsi_channles[i]); + if (ret) + return ret; + } + + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 0; /* Speed : 96MHz */ + tx_config.channels = HSI_MAX_CHANNELS; + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_SET_TX, &tx_config); + + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 0; /* Speed : 96MHz */ + rx_config.channels = HSI_MAX_CHANNELS; + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_SET_RX, &rx_config); + mif_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n"); + + hsi_ioctl(mipi_ld->hsi_channles[i].dev, + HSI_IOCTL_SET_ACREADY_NORMAL, NULL); + mif_debug("[MIPI-HSI] ACREADY_NORMAL\n"); + } + + if (mipi_ld->ld.com_state != COM_ONLINE) + mipi_ld->ld.com_state = COM_HANDSHAKE; + + ret = hsi_read(mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev, + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data, + 1); + if (ret) + mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + if (mipi_ld->ld.com_state != COM_ONLINE) + schedule_delayed_work(&mipi_ld->start_work, 3 * HZ); + + mif_debug("[MIPI-HSI] hsi_init_handshake Done : MODE_NORMAL\n"); + return 0; + + case HSI_INIT_MODE_FLASHLESS_BOOT: + mipi_ld->ld.com_state = COM_BOOT; + + if (mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].opened) { + hsi_write_cancel(mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL].dev); + hsi_read_cancel(mipi_ld->hsi_channles[ + HSI_FLASHLESS_CHANNEL].dev); + } else { + ret = if_hsi_open_channel( + &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL]); + if (ret) + return ret; + } + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 0; /* Speed : 96MHz */ + tx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_TX, &tx_config); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 0; /* Speed : 96MHz */ + rx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_RX, &rx_config); + mif_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n"); + + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + mif_debug("[MIPI-HSI] wake_lock\n"); + } + + if_hsi_set_wakeline( + &mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL], 1); + + ret = hsi_read(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].rx_data, + HSI_FLASHBOOT_ACK_LEN / 4); + if (ret) + mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_FLASHLESS_CHANNEL].dev, + HSI_IOCTL_SET_ACREADY_NORMAL, NULL); + + mif_debug("[MIPI-HSI] hsi_init_handshake Done : FLASHLESS_BOOT\n"); + return 0; + + case HSI_INIT_MODE_CP_RAMDUMP: + mipi_ld->ld.com_state = COM_CRASH; + + if (mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].opened) { + hsi_write_cancel(mipi_ld->hsi_channles[ + HSI_CP_RAMDUMP_CHANNEL].dev); + hsi_read_cancel(mipi_ld->hsi_channles[ + HSI_CP_RAMDUMP_CHANNEL].dev); + } else { + ret = if_hsi_open_channel( + &mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL]); + if (ret) + return ret; + } + + hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + HSI_IOCTL_GET_TX, &tx_config); + tx_config.mode = 2; + tx_config.divisor = 0; /* Speed : 96MHz */ + tx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + HSI_IOCTL_SET_TX, &tx_config); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + HSI_IOCTL_GET_RX, &rx_config); + rx_config.mode = 2; + rx_config.divisor = 0; /* Speed : 96MHz */ + rx_config.channels = 1; + hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + HSI_IOCTL_SET_RX, &rx_config); + mif_debug("[MIPI-HSI] Set TX/RX MIPI-HSI\n"); + + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + mif_debug("[MIPI-HSI] wake_lock\n"); + } + + if_hsi_set_wakeline( + &mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL], 1); + + ret = hsi_read( + mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].rx_data, + DUMP_ERR_INFO_SIZE); + if (ret) + mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + hsi_ioctl(mipi_ld->hsi_channles[HSI_CP_RAMDUMP_CHANNEL].dev, + HSI_IOCTL_SET_ACREADY_NORMAL, NULL); + + mif_debug("[MIPI-HSI] hsi_init_handshake Done : RAMDUMP\n"); + return 0; + + default: + return -EINVAL; + } +} + +static u32 if_hsi_create_cmd(u32 cmd_type, int ch, void *arg) +{ + u32 cmd = 0; + unsigned int size = 0; + + switch (cmd_type) { + case HSI_LL_MSG_BREAK: + return 0; + + case HSI_LL_MSG_CONN_CLOSED: + cmd = ((HSI_LL_MSG_CONN_CLOSED & 0x0000000F) << 28) + |((ch & 0x000000FF) << 24); + return cmd; + + case HSI_LL_MSG_ACK: + size = *(unsigned int *)arg; + + cmd = ((HSI_LL_MSG_ACK & 0x0000000F) << 28) + |((ch & 0x000000FF) << 24) | ((size & 0x00FFFFFF)); + return cmd; + + case HSI_LL_MSG_NAK: + cmd = ((HSI_LL_MSG_NAK & 0x0000000F) << 28) + |((ch & 0x000000FF) << 24); + return cmd; + + case HSI_LL_MSG_OPEN_CONN_OCTET: + size = *(unsigned int *)arg; + + cmd = ((HSI_LL_MSG_OPEN_CONN_OCTET & 0x0000000F) + << 28) | ((ch & 0x000000FF) << 24) + | ((size & 0x00FFFFFF)); + return cmd; + + case HSI_LL_MSG_OPEN_CONN: + case HSI_LL_MSG_CONF_RATE: + case HSI_LL_MSG_CANCEL_CONN: + case HSI_LL_MSG_CONN_READY: + case HSI_LL_MSG_ECHO: + case HSI_LL_MSG_INFO_REQ: + case HSI_LL_MSG_INFO: + case HSI_LL_MSG_CONFIGURE: + case HSI_LL_MSG_ALLOCATE_CH: + case HSI_LL_MSG_RELEASE_CH: + case HSI_LL_MSG_INVALID: + default: + mif_err("[MIPI-HSI] ERROR... CMD Not supported : %08x\n", + cmd_type); + return -EINVAL; + } +} + +static void if_hsi_cmd_work(struct work_struct *work) +{ + int ret; + unsigned long int flags; + struct mipi_link_device *mipi_ld = + container_of(work, struct mipi_link_device, cmd_work); + struct if_hsi_channel *channel = + &mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL]; + struct if_hsi_command *hsi_cmd; + + mif_debug("[MIPI-HSI] cmd_work\n"); + + do { + spin_lock_irqsave(&mipi_ld->list_cmd_lock, flags); + if (!list_empty(&mipi_ld->list_of_hsi_cmd)) { + hsi_cmd = list_entry(mipi_ld->list_of_hsi_cmd.next, + struct if_hsi_command, list); + list_del(&hsi_cmd->list); + spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags); + + channel->send_step = STEP_TX; + if_hsi_set_wakeline(channel, 1); + mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies + + HSI_ACWAKE_DOWN_TIMEOUT); + } else { + spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags); + channel->send_step = STEP_IDLE; + break; + } + mif_debug("[MIPI-HSI] take command : %08x\n", hsi_cmd->command); + + ret = if_hsi_write(channel, &hsi_cmd->command, 4); + if (ret < 0) { + mif_err("[MIPI-HSI] write command fail : %d\n", ret); + if_hsi_set_wakeline(channel, 0); + channel->send_step = STEP_IDLE; + return; + } + mif_debug("[MIPI-HSI] SEND CMD : %08x\n", hsi_cmd->command); + + kfree(hsi_cmd); + } while (true); +} + +static int if_hsi_send_command(struct mipi_link_device *mipi_ld, + u32 cmd_type, int ch, u32 param) +{ + unsigned long int flags; + struct if_hsi_command *hsi_cmd; + + hsi_cmd = kmalloc(sizeof(struct if_hsi_command), GFP_ATOMIC); + if (!hsi_cmd) { + mif_err("[MIPI-HSI] hsi_cmd kmalloc fail\n"); + return -ENOMEM; + } + INIT_LIST_HEAD(&hsi_cmd->list); + + hsi_cmd->command = if_hsi_create_cmd(cmd_type, ch, ¶m); + mif_debug("[MIPI-HSI] made command : %08x\n", hsi_cmd->command); + + spin_lock_irqsave(&mipi_ld->list_cmd_lock, flags); + list_add_tail(&hsi_cmd->list, &mipi_ld->list_of_hsi_cmd); + spin_unlock_irqrestore(&mipi_ld->list_cmd_lock, flags); + + mif_debug("[MIPI-HSI] queue_work : cmd_work\n"); + queue_work(mipi_ld->mipi_wq, &mipi_ld->cmd_work); + + return 0; +} + +static int if_hsi_decode_cmd(u32 *cmd_data, u32 *cmd, u32 *ch, + u32 *param) +{ + u32 data = *cmd_data; + u8 lrc_cal, lrc_act; + u8 val1, val2, val3; + + *cmd = ((data & 0xF0000000) >> 28); + switch (*cmd) { + case HSI_LL_MSG_BREAK: + mif_err("[MIPI-HSI] Command MSG_BREAK Received\n"); + return -1; + + case HSI_LL_MSG_OPEN_CONN: + *ch = ((data & 0x0F000000) >> 24); + *param = ((data & 0x00FFFF00) >> 8); + val1 = ((data & 0xFF000000) >> 24); + val2 = ((data & 0x00FF0000) >> 16); + val3 = ((data & 0x0000FF00) >> 8); + lrc_act = (data & 0x000000FF); + lrc_cal = val1 ^ val2 ^ val3; + + if (lrc_cal != lrc_act) { + mif_err("[MIPI-HSI] CAL is broken\n"); + return -1; + } + return 0; + + case HSI_LL_MSG_CONN_READY: + case HSI_LL_MSG_CONN_CLOSED: + case HSI_LL_MSG_CANCEL_CONN: + case HSI_LL_MSG_NAK: + *ch = ((data & 0x0F000000) >> 24); + return 0; + + case HSI_LL_MSG_ACK: + *ch = ((data & 0x0F000000) >> 24); + *param = (data & 0x00FFFFFF); + return 0; + + case HSI_LL_MSG_CONF_RATE: + *ch = ((data & 0x0F000000) >> 24); + *param = ((data & 0x0F000000) >> 24); + return 0; + + case HSI_LL_MSG_OPEN_CONN_OCTET: + *ch = ((data & 0x0F000000) >> 24); + *param = (data & 0x00FFFFFF); + return 0; + + case HSI_LL_MSG_ECHO: + case HSI_LL_MSG_INFO_REQ: + case HSI_LL_MSG_INFO: + case HSI_LL_MSG_CONFIGURE: + case HSI_LL_MSG_ALLOCATE_CH: + case HSI_LL_MSG_RELEASE_CH: + case HSI_LL_MSG_INVALID: + default: + mif_err("[MIPI-HSI] Invalid command received : %08x\n", *cmd); + *cmd = HSI_LL_MSG_INVALID; + *ch = HSI_LL_INVALID_CHANNEL; + return -1; + } + return 0; +} + +static int if_hsi_rx_cmd_handle(struct mipi_link_device *mipi_ld, u32 cmd, + u32 ch, u32 param) +{ + int ret; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[ch]; + + mif_debug("[MIPI-HSI] if_hsi_rx_cmd_handle cmd=0x%x, ch=%d, param=%d\n", + cmd, ch, param); + + switch (cmd) { + case HSI_LL_MSG_OPEN_CONN_OCTET: + switch (channel->recv_step) { + case STEP_IDLE: + channel->recv_step = STEP_TO_ACK; + + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + mif_debug("[MIPI-HSI] wake_lock\n"); + } + + if_hsi_set_wakeline(channel, 1); + mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies + + HSI_ACWAKE_DOWN_TIMEOUT); + mif_debug("[MIPI-HSI] mod_timer done(%d)\n", + HSI_ACWAKE_DOWN_TIMEOUT); + + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_ACK, ch, + param); + if (ret) { + mif_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", + ret); + return ret; + } + + channel->packet_size = param; + channel->recv_step = STEP_RX; + if (param % 4) + param += (4 - (param % 4)); + channel->rx_count = param; + ret = hsi_read(channel->dev, channel->rx_data, + channel->rx_count / 4); + if (ret) { + mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + return ret; + } + return 0; + + case STEP_NOT_READY: + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_NAK, ch, + param); + if (ret) { + mif_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", + ret); + return ret; + } + return 0; + + default: + mif_err("[MIPI-HSI] wrong state : %08x, recv_step : %d\n", + cmd, channel->recv_step); + return -1; + } + + case HSI_LL_MSG_ACK: + case HSI_LL_MSG_NAK: + switch (channel->send_step) { + case STEP_WAIT_FOR_ACK: + case STEP_SEND_OPEN_CONN: + if (cmd == HSI_LL_MSG_ACK) { + channel->send_step = STEP_TX; + channel->got_nack = 0; + mif_debug("[MIPI-HSI] got ack\n"); + } else { + channel->send_step = STEP_WAIT_FOR_ACK; + channel->got_nack = 1; + mif_debug("[MIPI-HSI] got nack\n"); + } + + up(&channel->ack_done_sem); + return 0; + + default: + mif_err("[MIPI-HSI] wrong state : %08x\n", cmd); + return -1; + } + + case HSI_LL_MSG_CONN_CLOSED: + switch (channel->send_step) { + case STEP_TX: + case STEP_WAIT_FOR_CONN_CLOSED: + mif_debug("[MIPI-HSI] got close\n"); + + channel->send_step = STEP_IDLE; + up(&channel->close_conn_done_sem); + return 0; + + default: + mif_err("[MIPI-HSI] wrong state : %08x\n", cmd); + return -1; + } + + case HSI_LL_MSG_OPEN_CONN: + case HSI_LL_MSG_ECHO: + case HSI_LL_MSG_CANCEL_CONN: + case HSI_LL_MSG_CONF_RATE: + default: + mif_err("[MIPI-HSI] ERROR... CMD Not supported : %08x\n", cmd); + return -EINVAL; + } +} + +static int if_hsi_protocol_send(struct mipi_link_device *mipi_ld, int ch, + u32 *data, unsigned int len) +{ + int ret; + int retry_count = 0; + int ack_timeout_cnt = 0; + struct io_device *iod; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[ch]; + + if (channel->send_step != STEP_IDLE) { + mif_err("[MIPI-HSI] send step is not IDLE : %d\n", + channel->send_step); + return -EBUSY; + } + channel->send_step = STEP_SEND_OPEN_CONN; + + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + mif_debug("[MIPI-HSI] wake_lock\n"); + } + + if_hsi_set_wakeline(channel, 1); + mod_timer(&mipi_ld->hsi_acwake_down_timer, jiffies + + HSI_ACWAKE_DOWN_TIMEOUT); + mif_debug("[MIPI-HSI] mod_timer done(%d)\n", + HSI_ACWAKE_DOWN_TIMEOUT); + +retry_send: + + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_OPEN_CONN_OCTET, ch, + len); + if (ret) { + mif_err("[MIPI-HSI] if_hsi_send_command fail : %d\n", ret); + if_hsi_set_wakeline(channel, 0); + channel->send_step = STEP_IDLE; + return -1; + } + + channel->send_step = STEP_WAIT_FOR_ACK; + + if (down_timeout(&channel->ack_done_sem, HSI_ACK_DONE_TIMEOUT) < 0) { + mif_err("[MIPI-HSI] ch=%d, ack_done timeout\n", + channel->channel_id); + + if_hsi_set_wakeline(channel, 0); + + if (mipi_ld->ld.com_state == COM_ONLINE) { + ack_timeout_cnt++; + if (ack_timeout_cnt < 10) { + if_hsi_set_wakeline(channel, 1); + mif_err("[MIPI-HSI] ch=%d, retry send open. cnt : %d\n", + channel->channel_id, ack_timeout_cnt); + goto retry_send; + } + + /* try to recover cp */ + iod = link_get_iod_with_format(&mipi_ld->ld, IPC_FMT); + if (iod) + iod->modem_state_changed(iod, + STATE_CRASH_RESET); + } + + channel->send_step = STEP_IDLE; + return -ETIMEDOUT; + } + mif_debug("[MIPI-HSI] ch=%d, got ack_done=%d\n", channel->channel_id, + channel->got_nack); + + if (channel->got_nack && (retry_count < 10)) { + mif_debug("[MIPI-HSI] ch=%d, got nack=%d retry=%d\n", + channel->channel_id, channel->got_nack, + retry_count); + retry_count++; + msleep_interruptible(1); + goto retry_send; + } + retry_count = 0; + + channel->send_step = STEP_TX; + + ret = if_hsi_write(channel, data, len); + if (ret < 0) { + mif_err("[MIPI-HSI] if_hsi_write fail : %d\n", ret); + if_hsi_set_wakeline(channel, 0); + channel->send_step = STEP_IDLE; + return ret; + } + mif_debug("[MIPI-HSI] SEND DATA : %08x(%d)\n", *data, len); + + mif_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n", + *channel->tx_data, *(channel->tx_data + 1), + *(channel->tx_data + 2), *(channel->tx_data + 3), + *(channel->tx_data + 4), *(channel->tx_data + 5), + *(channel->tx_data + 6), *(channel->tx_data + 7)); + + channel->send_step = STEP_WAIT_FOR_CONN_CLOSED; + if (down_timeout(&channel->close_conn_done_sem, + HSI_CLOSE_CONN_DONE_TIMEOUT) < 0) { + mif_err("[MIPI-HSI] ch=%d, close conn timeout\n", + channel->channel_id); + if_hsi_set_wakeline(channel, 0); + channel->send_step = STEP_IDLE; + return -ETIMEDOUT; + } + mif_debug("[MIPI-HSI] ch=%d, got close_conn_done\n", + channel->channel_id); + + channel->send_step = STEP_IDLE; + + mif_debug("[MIPI-HSI] write protocol Done : %d\n", channel->tx_count); + return channel->tx_count; +} + +static int if_hsi_write(struct if_hsi_channel *channel, u32 *data, + unsigned int size) +{ + int ret; + unsigned long int flags; + + spin_lock_irqsave(&channel->tx_state_lock, flags); + if (channel->tx_state & HSI_CHANNEL_TX_STATE_WRITING) { + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + return -EBUSY; + } + channel->tx_state |= HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + channel->tx_data = data; + if (size % 4) + size += (4 - (size % 4)); + channel->tx_count = size; + + mif_debug("[MIPI-HSI] submit write data : 0x%x(%d)\n", + *(u32 *)channel->tx_data, channel->tx_count); + ret = hsi_write(channel->dev, channel->tx_data, channel->tx_count / 4); + if (ret) { + mif_err("[MIPI-HSI] ch=%d, hsi_write fail : %d\n", + channel->channel_id, ret); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + return ret; + } + + if (down_timeout(&channel->write_done_sem, + HSI_WRITE_DONE_TIMEOUT) < 0) { + mif_err("[MIPI-HSI] ch=%d, hsi_write_done timeout : %d\n", + channel->channel_id, size); + + mif_err("[MIPI-HSI] data : %08x %08x %08x %08x %08x ...\n", + *channel->tx_data, *(channel->tx_data + 1), + *(channel->tx_data + 2), *(channel->tx_data + 3), + *(channel->tx_data + 4)); + + hsi_write_cancel(channel->dev); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + return -ETIMEDOUT; + } + + if (channel->tx_count != size) + mif_err("[MIPI-HSI] ch:%d,write_done fail,write_size:%d,origin_size:%d\n", + channel->channel_id, channel->tx_count, size); + + mif_debug("[MIPI-HSI] len:%d, id:%d, data : %08x %08x %08x %08x %08x ...\n", + channel->tx_count, channel->channel_id, *channel->tx_data, + *(channel->tx_data + 1), *(channel->tx_data + 2), + *(channel->tx_data + 3), *(channel->tx_data + 4)); + + return channel->tx_count; +} + +static void if_hsi_write_done(struct hsi_device *dev, unsigned int size) +{ + unsigned long int flags; + struct mipi_link_device *mipi_ld = + (struct mipi_link_device *)if_hsi_driver.priv_data; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[dev->n_ch]; + + mif_debug("[MIPI-HSI] got write data : 0x%x(%d)\n", + *(u32 *)channel->tx_data, size); + + spin_lock_irqsave(&channel->tx_state_lock, flags); + channel->tx_state &= ~HSI_CHANNEL_TX_STATE_WRITING; + spin_unlock_irqrestore(&channel->tx_state_lock, flags); + + mif_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n", + *channel->tx_data, *(channel->tx_data + 1), + *(channel->tx_data + 2), *(channel->tx_data + 3), + *(channel->tx_data + 4), *(channel->tx_data + 5), + *(channel->tx_data + 6), *(channel->tx_data + 7)); + + channel->tx_count = 4 * size; + up(&channel->write_done_sem); +} + +static void if_hsi_read_done(struct hsi_device *dev, unsigned int size) +{ + int ret; + unsigned long int flags; + u32 cmd = 0, ch = 0, param = 0; + struct mipi_link_device *mipi_ld = + (struct mipi_link_device *)if_hsi_driver.priv_data; + struct if_hsi_channel *channel = &mipi_ld->hsi_channles[dev->n_ch]; + struct io_device *iod; + enum dev_format format_type = 0; + + mif_debug("[MIPI-HSI] got read data : 0x%x(%d)\n", + *(u32 *)channel->rx_data, size); + + spin_lock_irqsave(&channel->rx_state_lock, flags); + channel->rx_state &= ~HSI_CHANNEL_RX_STATE_READING; + spin_unlock_irqrestore(&channel->rx_state_lock, flags); + + channel->rx_count = 4 * size; + + switch (channel->channel_id) { + case HSI_CONTROL_CHANNEL: + switch (mipi_ld->ld.com_state) { + case COM_HANDSHAKE: + case COM_ONLINE: + mif_debug("[MIPI-HSI] RECV CMD : %08x\n", + *channel->rx_data); + + if (channel->rx_count != 4) { + mif_err("[MIPI-HSI] wrong command len : %d\n", + channel->rx_count); + return; + } + + ret = if_hsi_decode_cmd(channel->rx_data, &cmd, &ch, + ¶m); + if (ret) + mif_err("[MIPI-HSI] decode_cmd fail=%d, " + "cmd=%x\n", ret, cmd); + else { + mif_debug("[MIPI-HSI] decode_cmd : %08x\n", + cmd); + ret = if_hsi_rx_cmd_handle(mipi_ld, cmd, ch, + param); + if (ret) + mif_err("[MIPI-HSI] handle cmd " + "cmd=%x\n", cmd); + } + + ret = hsi_read(channel->dev, channel->rx_data, 1); + if (ret) + mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + + return; + + case COM_BOOT: + mif_debug("[MIPI-HSI] receive data : 0x%x(%d)\n", + *channel->rx_data, channel->rx_count); + + iod = link_get_iod_with_format(&mipi_ld->ld, IPC_BOOT); + if (iod) { + channel->packet_size = *channel->rx_data; + mif_debug("[MIPI-HSI] flashless packet size : " + "%d\n", channel->packet_size); + + ret = iod->recv(iod, + &mipi_ld->ld, + (char *)channel->rx_data + 4, + HSI_FLASHBOOT_ACK_LEN - 4); + if (ret < 0) + mif_err("[MIPI-HSI] recv call " + "fail : %d\n", ret); + } + + ret = hsi_read(channel->dev, channel->rx_data, + HSI_FLASHBOOT_ACK_LEN / 4); + if (ret) + mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + return; + + case COM_CRASH: + mif_debug("[MIPI-HSI] receive data : 0x%x(%d)\n", + *channel->rx_data, channel->rx_count); + + iod = link_get_iod_with_format(&mipi_ld->ld, + IPC_RAMDUMP); + if (iod) { + channel->packet_size = *channel->rx_data; + mif_debug("[MIPI-HSI] ramdump packet size : " + "%d\n", channel->packet_size); + + ret = iod->recv(iod, + &mipi_ld->ld, + (char *)channel->rx_data + 4, + channel->packet_size); + if (ret < 0) + mif_err("[MIPI-HSI] recv call " + "fail : %d\n", ret); + } + + ret = hsi_read(channel->dev, channel->rx_data, + DUMP_PACKET_SIZE); + if (ret) + mif_err("[MIPI-HSI] hsi_read fail : %d\n", ret); + return; + + case COM_NONE: + default: + mif_err("[MIPI-HSI] receive data in wrong state : 0x%x(%d)\n", + *channel->rx_data, channel->rx_count); + return; + } + break; + + case HSI_FMT_CHANNEL: + mif_debug("[MIPI-HSI] iodevice format : IPC_FMT\n"); + format_type = IPC_FMT; + break; + case HSI_RAW_CHANNEL: + mif_debug("[MIPI-HSI] iodevice format : IPC_MULTI_RAW\n"); + format_type = IPC_MULTI_RAW; + break; + case HSI_RFS_CHANNEL: + mif_debug("[MIPI-HSI] iodevice format : IPC_RFS\n"); + format_type = IPC_RFS; + break; + + case HSI_CMD_CHANNEL: + mif_debug("[MIPI-HSI] receive command data : 0x%x\n", + *channel->rx_data); + + ch = channel->channel_id; + param = 0; + ret = if_hsi_send_command(mipi_ld, HSI_LL_MSG_CONN_CLOSED, + ch, param); + if (ret) + mif_err("[MIPI-HSI] send_cmd fail=%d\n", ret); + + channel->recv_step = STEP_IDLE; + return; + + default: + return; + } + + iod = link_get_iod_with_format(&mipi_ld->ld, format_type); + if (iod) { + mif_debug("[MIPI-HSI] iodevice format : %d\n", iod->format); + + channel->recv_step = STEP_NOT_READY; + + mif_debug("[MIPI-HSI] RECV DATA : %08x(%d)-%d\n", + *channel->rx_data, channel->packet_size, + iod->format); + + mif_debug("%08x %08x %08x %08x %08x %08x %08x %08x\n", + *channel->rx_data, *(channel->rx_data + 1), + *(channel->rx_data + 2), *(channel->rx_data + 3), + *(channel->rx_data + 4), *(channel->rx_data + 5), + *(channel->rx_data + 6), *(channel->rx_data + 7)); + + ret = iod->recv(iod, &mipi_ld->ld, + (char *)channel->rx_data, channel->packet_size); + if (ret < 0) + mif_err("[MIPI-HSI] recv call fail : %d\n", ret); + + ch = channel->channel_id; + param = 0; + ret = if_hsi_send_command(mipi_ld, + HSI_LL_MSG_CONN_CLOSED, ch, param); + if (ret) + mif_err("[MIPI-HSI] send_cmd fail=%d\n", ret); + + channel->recv_step = STEP_IDLE; + } +} + +static void if_hsi_port_event(struct hsi_device *dev, unsigned int event, + void *arg) +{ + int acwake_level = 1; + struct mipi_link_device *mipi_ld = + (struct mipi_link_device *)if_hsi_driver.priv_data; + + switch (event) { + case HSI_EVENT_BREAK_DETECTED: + mif_err("[MIPI-HSI] HSI_EVENT_BREAK_DETECTED\n"); + return; + + case HSI_EVENT_HSR_DATAAVAILABLE: + mif_err("[MIPI-HSI] HSI_EVENT_HSR_DATAAVAILABLE\n"); + return; + + case HSI_EVENT_CAWAKE_UP: + if (dev->n_ch == HSI_CONTROL_CHANNEL) { + if (!wake_lock_active(&mipi_ld->wlock)) { + wake_lock(&mipi_ld->wlock); + mif_debug("[MIPI-HSI] wake_lock\n"); + } + mif_debug("[MIPI-HSI] CAWAKE_%d(1)\n", dev->n_ch); + } + return; + + case HSI_EVENT_CAWAKE_DOWN: + if (dev->n_ch == HSI_CONTROL_CHANNEL) + mif_debug("[MIPI-HSI] CAWAKE_%d(0)\n", dev->n_ch); + + if ((dev->n_ch == HSI_CONTROL_CHANNEL) && + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].opened) { + hsi_ioctl( + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].dev, + HSI_IOCTL_GET_ACWAKE, &acwake_level); + + mif_debug("[MIPI-HSI] GET_ACWAKE. Ch : %d, level : %d\n", + dev->n_ch, acwake_level); + + if (!acwake_level) { + wake_unlock(&mipi_ld->wlock); + mif_debug("[MIPI-HSI] wake_unlock\n"); + } + } + return; + + case HSI_EVENT_ERROR: + mif_err("[MIPI-HSI] HSI_EVENT_ERROR\n"); + return; + + default: + mif_err("[MIPI-HSI] Unknown Event : %d\n", event); + return; + } +} + +static int __devinit if_hsi_probe(struct hsi_device *dev) +{ + int port = 0; + unsigned long *address; + struct mipi_link_device *mipi_ld = + (struct mipi_link_device *)if_hsi_driver.priv_data; + + for (port = 0; port < HSI_MAX_PORTS; port++) { + if (if_hsi_driver.ch_mask[port]) + break; + } + address = (unsigned long *)&if_hsi_driver.ch_mask[port]; + + if (test_bit(dev->n_ch, address) && (dev->n_p == port)) { + /* Register callback func */ + hsi_set_write_cb(dev, if_hsi_write_done); + hsi_set_read_cb(dev, if_hsi_read_done); + hsi_set_port_event_cb(dev, if_hsi_port_event); + + /* Init device data */ + mipi_ld->hsi_channles[dev->n_ch].dev = dev; + mipi_ld->hsi_channles[dev->n_ch].tx_count = 0; + mipi_ld->hsi_channles[dev->n_ch].rx_count = 0; + mipi_ld->hsi_channles[dev->n_ch].tx_state = 0; + mipi_ld->hsi_channles[dev->n_ch].rx_state = 0; + mipi_ld->hsi_channles[dev->n_ch].packet_size = 0; + mipi_ld->hsi_channles[dev->n_ch].acwake = 0; + mipi_ld->hsi_channles[dev->n_ch].send_step = STEP_UNDEF; + mipi_ld->hsi_channles[dev->n_ch].recv_step = STEP_UNDEF; + spin_lock_init(&mipi_ld->hsi_channles[dev->n_ch].tx_state_lock); + spin_lock_init(&mipi_ld->hsi_channles[dev->n_ch].rx_state_lock); + spin_lock_init(&mipi_ld->hsi_channles[dev->n_ch].acwake_lock); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].write_done_sem, + 0); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].ack_done_sem, + 0); + sema_init(&mipi_ld->hsi_channles[dev->n_ch].close_conn_done_sem, + 0); + } + + mif_debug("[MIPI-HSI] if_hsi_probe() done. ch : %d\n", dev->n_ch); + return 0; +} + +static int if_hsi_init(struct link_device *ld) +{ + int ret; + int i = 0; + struct mipi_link_device *mipi_ld = to_mipi_link_device(ld); + + for (i = 0; i < HSI_MAX_PORTS; i++) + if_hsi_driver.ch_mask[i] = 0; + + for (i = 0; i < HSI_MAX_CHANNELS; i++) { + mipi_ld->hsi_channles[i].dev = NULL; + mipi_ld->hsi_channles[i].opened = 0; + mipi_ld->hsi_channles[i].channel_id = i; + } + if_hsi_driver.ch_mask[0] = CHANNEL_MASK; + + /* TODO - need to get priv data (request to TI) */ + if_hsi_driver.priv_data = (void *)mipi_ld; + ret = hsi_register_driver(&if_hsi_driver); + if (ret) { + mif_err("[MIPI-HSI] hsi_register_driver() fail : %d\n", ret); + return ret; + } + + mipi_ld->mipi_wq = create_singlethread_workqueue("mipi_cmd_wq"); + if (!mipi_ld->mipi_wq) { + mif_err("[MIPI-HSI] fail to create work Q.\n"); + return -ENOMEM; + } + INIT_WORK(&mipi_ld->cmd_work, if_hsi_cmd_work); + INIT_DELAYED_WORK(&mipi_ld->start_work, mipi_hsi_start_work); + + setup_timer(&mipi_ld->hsi_acwake_down_timer, if_hsi_acwake_down_func, + (unsigned long)mipi_ld); + + /* TODO - allocate rx buff */ + mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data = + kmalloc(64 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_CONTROL_CHANNEL].rx_data) { + mif_err("[MIPI-HSI] alloc HSI_CONTROL_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_FMT_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_FMT_CHANNEL].rx_data) { + mif_err("[MIPI-HSI] alloc HSI_FMT_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_RAW_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_RAW_CHANNEL].rx_data) { + mif_err("[MIPI-HSI] alloc HSI_RAW_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_RFS_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_RFS_CHANNEL].rx_data) { + mif_err("[MIPI-HSI] alloc HSI_RFS_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + mipi_ld->hsi_channles[HSI_CMD_CHANNEL].rx_data = + kmalloc(256 * 1024, GFP_DMA | GFP_ATOMIC); + if (!mipi_ld->hsi_channles[HSI_CMD_CHANNEL].rx_data) { + mif_err("[MIPI-HSI] alloc HSI_CMD_CHANNEL rx_data fail\n"); + return -ENOMEM; + } + + return 0; +} + +struct link_device *mipi_create_link_device(struct platform_device *pdev) +{ + int ret; + struct mipi_link_device *mipi_ld; + struct link_device *ld; + + /* for dpram int */ + /* struct modem_data *pdata = pdev->dev.platform_data; */ + + mipi_ld = kzalloc(sizeof(struct mipi_link_device), GFP_KERNEL); + if (!mipi_ld) + return NULL; + + INIT_LIST_HEAD(&mipi_ld->list_of_hsi_cmd); + spin_lock_init(&mipi_ld->list_cmd_lock); + skb_queue_head_init(&mipi_ld->ld.sk_fmt_tx_q); + skb_queue_head_init(&mipi_ld->ld.sk_raw_tx_q); + + wake_lock_init(&mipi_ld->wlock, WAKE_LOCK_SUSPEND, "mipi_link"); + + ld = &mipi_ld->ld; + + ld->name = "mipi_hsi"; + ld->init_comm = mipi_hsi_init_communication; + ld->terminate_comm = mipi_hsi_terminate_communication; + ld->send = mipi_hsi_send; + ld->com_state = COM_NONE; + + /* for dpram int */ + /* ld->irq = gpio_to_irq(pdata->gpio); s*/ + + ld->tx_wq = create_singlethread_workqueue("mipi_tx_wq"); + if (!ld->tx_wq) { + mif_err("[MIPI-HSI] fail to create work Q.\n"); + return NULL; + } + INIT_WORK(&ld->tx_work, mipi_hsi_tx_work); + + ret = if_hsi_init(ld); + if (ret) + return NULL; + + return ld; +} diff --git a/drivers/misc/modem_if/modem_link_device_mipi.h b/drivers/misc/modem_if/modem_link_device_mipi.h new file mode 100644 index 00000000000..8ca4968b870 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_mipi.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_LINK_DEVICE_MIPI_H__ +#define __MODEM_LINK_DEVICE_MIPI_H__ + + +#define HSI_MAX_CHANNELS 16 +#define CHANNEL_MASK 0xFF + +#define HSI_CHANNEL_TX_STATE_UNAVAIL (1 << 0) +#define HSI_CHANNEL_TX_STATE_WRITING (1 << 1) +#define HSI_CHANNEL_RX_STATE_UNAVAIL (1 << 0) +#define HSI_CHANNEL_RX_STATE_READING (1 << 1) + +#define HSI_WRITE_DONE_TIMEOUT (HZ) +#define HSI_READ_DONE_TIMEOUT (HZ) +#define HSI_ACK_DONE_TIMEOUT (HZ) +#define HSI_CLOSE_CONN_DONE_TIMEOUT (HZ) +#define HSI_ACWAKE_DOWN_TIMEOUT (HZ / 2) + +#define HSI_CONTROL_CHANNEL 0 +#define HSI_FLASHLESS_CHANNEL 0 +#define HSI_CP_RAMDUMP_CHANNEL 0 +#define HSI_FMT_CHANNEL 1 +#define HSI_RAW_CHANNEL 2 +#define HSI_RFS_CHANNEL 3 +#define HSI_CMD_CHANNEL 4 +#define HSI_NUM_OF_USE_CHANNELS 5 + +#define HSI_LL_INVALID_CHANNEL 0xFF + +#define HSI_FLASHBOOT_ACK_LEN 16 +#define DUMP_PACKET_SIZE 12289 /* 48K + 4 length, word unit */ +#define DUMP_ERR_INFO_SIZE 39 /* 150 bytes + 4 length , word unit */ + +enum { + HSI_LL_MSG_BREAK, /* 0x0 */ + HSI_LL_MSG_ECHO, + HSI_LL_MSG_INFO_REQ, + HSI_LL_MSG_INFO, + HSI_LL_MSG_CONFIGURE, + HSI_LL_MSG_ALLOCATE_CH, + HSI_LL_MSG_RELEASE_CH, + HSI_LL_MSG_OPEN_CONN, + HSI_LL_MSG_CONN_READY, + HSI_LL_MSG_CONN_CLOSED, /* 0x9 */ + HSI_LL_MSG_CANCEL_CONN, + HSI_LL_MSG_ACK, /* 0xB */ + HSI_LL_MSG_NAK, /* 0xC */ + HSI_LL_MSG_CONF_RATE, + HSI_LL_MSG_OPEN_CONN_OCTET, /* 0xE */ + HSI_LL_MSG_INVALID = 0xFF, +}; + +enum { + STEP_UNDEF, + STEP_CLOSED, + STEP_NOT_READY, + STEP_IDLE, + STEP_ERROR, + STEP_SEND_OPEN_CONN, + STEP_SEND_ACK, + STEP_WAIT_FOR_ACK, + STEP_TO_ACK, + STEP_SEND_NACK, + STEP_GET_NACK, + STEP_SEND_CONN_READY, + STEP_WAIT_FOR_CONN_READY, + STEP_SEND_CONF_RATE, + STEP_WAIT_FOR_CONF_ACK, + STEP_TX, + STEP_RX, + STEP_SEND_CONN_CLOSED, + STEP_WAIT_FOR_CONN_CLOSED, + STEP_SEND_BREAK, +}; + + +struct if_hsi_channel { + struct hsi_device *dev; + unsigned int channel_id; + + u32 *tx_data; + unsigned int tx_count; + u32 *rx_data; + unsigned int rx_count; + unsigned int packet_size; + + unsigned int tx_state; + unsigned int rx_state; + spinlock_t tx_state_lock; + spinlock_t rx_state_lock; + + unsigned int send_step; + unsigned int recv_step; + + unsigned int got_nack; + unsigned int acwake; + spinlock_t acwake_lock; + + struct semaphore write_done_sem; + struct semaphore ack_done_sem; + struct semaphore close_conn_done_sem; + + unsigned int opened; +}; + +struct if_hsi_command { + u32 command; + struct list_head list; +}; + +struct mipi_link_device { + struct link_device ld; + + /* mipi specific link data */ + struct if_hsi_channel hsi_channles[HSI_MAX_CHANNELS]; + struct list_head list_of_hsi_cmd; + spinlock_t list_cmd_lock; + + struct workqueue_struct *mipi_wq; + struct work_struct cmd_work; + struct delayed_work start_work; + + struct wake_lock wlock; + struct timer_list hsi_acwake_down_timer; +}; +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_mipi_link_device(linkdev) \ + container_of(linkdev, struct mipi_link_device, ld) + + +enum { + HSI_INIT_MODE_NORMAL, + HSI_INIT_MODE_FLASHLESS_BOOT, + HSI_INIT_MODE_CP_RAMDUMP, +}; +static int hsi_init_handshake(struct mipi_link_device *mipi_ld, int mode); +static int if_hsi_write(struct if_hsi_channel *channel, u32 *data, + unsigned int size); +static int if_hsi_protocol_send(struct mipi_link_device *mipi_ld, int ch, + u32 *data, unsigned int len); +static int if_hsi_close_channel(struct if_hsi_channel *channel); + +#endif diff --git a/drivers/misc/modem_if/modem_link_device_pld.c b/drivers/misc/modem_if/modem_link_device_pld.c new file mode 100644 index 00000000000..23d4627b78a --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_pld.c @@ -0,0 +1,1618 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/time.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/wakelock.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/if_arp.h> +#include <linux/platform_device.h> +#include <linux/kallsyms.h> +#include <linux/platform_data/modem.h> + +#include "modem_prj.h" +#include "modem_link_device_pld.h" +#include "modem_utils.h" + + +/* +** Function prototypes for basic DPRAM operations +*/ +static inline void clear_intr(struct pld_link_device *pld); +static inline u16 recv_intr(struct pld_link_device *pld); +static inline void send_intr(struct pld_link_device *pld, u16 mask); + +static inline u16 get_magic(struct pld_link_device *pld); +static inline void set_magic(struct pld_link_device *pld, u16 val); +static inline u16 get_access(struct pld_link_device *pld); +static inline void set_access(struct pld_link_device *pld, u16 val); + +static inline u32 get_tx_head(struct pld_link_device *pld, int id); +static inline u32 get_tx_tail(struct pld_link_device *pld, int id); +static inline void set_tx_head(struct pld_link_device *pld, int id, u32 in); +static inline void set_tx_tail(struct pld_link_device *pld, int id, u32 out); +static inline u8 *get_tx_buff(struct pld_link_device *pld, int id); +static inline u32 get_tx_buff_size(struct pld_link_device *pld, int id); + +static inline u32 get_rx_head(struct pld_link_device *pld, int id); +static inline u32 get_rx_tail(struct pld_link_device *pld, int id); +static inline void set_rx_head(struct pld_link_device *pld, int id, u32 in); +static inline void set_rx_tail(struct pld_link_device *pld, int id, u32 out); +static inline u8 *get_rx_buff(struct pld_link_device *pld, int id); +static inline u32 get_rx_buff_size(struct pld_link_device *pld, int id); + +static inline u16 get_mask_req_ack(struct pld_link_device *pld, int id); +static inline u16 get_mask_res_ack(struct pld_link_device *pld, int id); +static inline u16 get_mask_send(struct pld_link_device *pld, int id); + +static void handle_cp_crash(struct pld_link_device *pld); +static int trigger_force_cp_crash(struct pld_link_device *pld); + +/* +** Functions for debugging +*/ +static void set_dpram_map(struct pld_link_device *pld, + struct mif_irq_map *map) +{ + map->magic = get_magic(pld); + map->access = get_access(pld); + + map->fmt_tx_in = get_tx_head(pld, IPC_FMT); + map->fmt_tx_out = get_tx_tail(pld, IPC_FMT); + map->fmt_rx_in = get_rx_head(pld, IPC_FMT); + map->fmt_rx_out = get_rx_tail(pld, IPC_FMT); + map->raw_tx_in = get_tx_head(pld, IPC_RAW); + map->raw_tx_out = get_tx_tail(pld, IPC_RAW); + map->raw_rx_in = get_rx_head(pld, IPC_RAW); + map->raw_rx_out = get_rx_tail(pld, IPC_RAW); + + map->cp2ap = recv_intr(pld); +} + +/* +** DPRAM operations +*/ +static int pld_register_isr(unsigned irq, irqreturn_t (*isr)(int, void*), + unsigned long flag, const char *name, + struct pld_link_device *pld) +{ + int ret = 0; + + ret = request_irq(irq, isr, flag, name, pld); + if (ret) { + mif_info("%s: ERR! request_irq fail (err %d)\n", name, ret); + return ret; + } + + ret = enable_irq_wake(irq); + if (ret) + mif_info("%s: ERR! enable_irq_wake fail (err %d)\n", name, ret); + + mif_info("%s (#%d) handler registered\n", name, irq); + + return 0; +} + +static inline void clear_intr(struct pld_link_device *pld) +{ + if (pld->ext_op && pld->ext_op->clear_intr) + pld->ext_op->clear_intr(pld); +} + +static inline u16 recv_intr(struct pld_link_device *pld) +{ + u16 val1 = 0, val2 = 0, cnt = 3; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&pld->mbx2ap[0]), + pld->address_buffer); + val1 = ioread16(pld->base); + + iowrite16(PLD_ADDR_MASK(&pld->mbx2ap[0]), + pld->address_buffer); + val2 = ioread16(pld->base); + + if (likely(val1 == val2)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; + } + + mif_err("ERR: intr1(%d) != intr1(%d)\n", val1, val2); + + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + + return val1; +} + +static inline void send_intr(struct pld_link_device *pld, u16 mask) +{ + int cnt = 3; + u32 val = 0; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + iowrite16(PLD_ADDR_MASK(&pld->mbx2cp[0]), + pld->address_buffer); + iowrite16((u16)mask, pld->base); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&pld->mbx2cp[0]), + pld->address_buffer); + val = ioread16(pld->base); + + if (likely(val == mask)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return; + } + + mif_err("ERR: intr1(%d) != intr2(%d)\n", val, mask); + udelay(100); + + /* Write head value again */ + iowrite16(PLD_ADDR_MASK(&pld->mbx2cp[0]), + pld->address_buffer); + iowrite16((u16)mask, pld->base); + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + + return; +} + +static inline u16 get_magic(struct pld_link_device *pld) +{ + u16 val1 = 0, val2 = 0, cnt = 3; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&pld->magic_ap2cp[0]), + pld->address_buffer); + val1 = ioread16(pld->base); + + iowrite16(PLD_ADDR_MASK(&pld->magic_ap2cp[0]), + pld->address_buffer); + val2 = ioread16(pld->base); + + if (likely(val1 == val2)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; + } + + mif_err("ERR: txq.head(%d) != in(%d)\n", val1, val2); + udelay(100); + + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; + +} + +static inline void set_magic(struct pld_link_device *pld, u16 in) +{ + int cnt = 3; + u32 val = 0; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + iowrite16(PLD_ADDR_MASK(&pld->magic_ap2cp[0]), + pld->address_buffer); + iowrite16((u16)in, pld->base); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&pld->magic_ap2cp[0]), + pld->address_buffer); + val = ioread16(pld->base); + + if (likely(val == in)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return; + } + + mif_err("ERR: magic1(%d) != magic2(%d)\n", val, in); + udelay(100); + + /* Write head value again */ + iowrite16(PLD_ADDR_MASK(&pld->magic_ap2cp[0]), + pld->address_buffer); + iowrite16((u16)in, pld->base); + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + return; +} + +static inline u16 get_access(struct pld_link_device *pld) +{ + u16 val1 = 0, val2 = 0, cnt = 3; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&pld->access_ap2cp[0]), + pld->address_buffer); + val1 = ioread16(pld->base); + + iowrite16(PLD_ADDR_MASK(&pld->access_ap2cp[0]), + pld->address_buffer); + val2 = ioread16(pld->base); + + if (likely(val1 == val2)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; + } + + mif_err("ERR: access1(%d) != access2(%d)\n", val1, val2); + udelay(100); + + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; + +} + +static inline void set_access(struct pld_link_device *pld, u16 in) +{ + int cnt = 3; + u32 val = 0; + unsigned long int flags; + + iowrite16(PLD_ADDR_MASK(&pld->access_ap2cp[0]), + pld->address_buffer); + iowrite16((u16)in, pld->base); + + spin_lock_irqsave(&pld->pld_lock, flags); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&pld->access_ap2cp[0]), + pld->address_buffer); + val = ioread16(pld->base); + + if (likely(val == in)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return; + } + + mif_err("ERR: access(%d) != access(%d)\n", val, in); + udelay(100); + + /* Write head value again */ + iowrite16(PLD_ADDR_MASK(&pld->access_ap2cp[0]), + pld->address_buffer); + iowrite16((u16)in, pld->base); + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + return; +} + +static inline u32 get_tx_head(struct pld_link_device *pld, int id) +{ + u16 val1 = 0, val2 = 0, cnt = 3; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.head)[0]), + pld->address_buffer); + val1 = ioread16(pld->base); + + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.head)[0]), + pld->address_buffer); + val2 = ioread16(pld->base); + + if (likely(val1 == val2)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; + } + + mif_err("ERR: %s txq.head(%d) != in(%d)\n", + get_dev_name(id), val1, val2); + udelay(100); + + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; +} + +static inline u32 get_tx_tail(struct pld_link_device *pld, int id) +{ + u16 val1 = 0, val2 = 0, cnt = 3; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.tail)[0]), + pld->address_buffer); + val1 = ioread16(pld->base); + + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.tail)[0]), + pld->address_buffer); + val2 = ioread16(pld->base); + + if (likely(val1 == val2)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; + } + + mif_err("ERR: %s txq.tail(%d) != in(%d)\n", + get_dev_name(id), val1, val2); + udelay(100); + + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; +} + +static inline void set_tx_head(struct pld_link_device *pld, int id, u32 in) +{ + int cnt = 3; + u32 val = 0; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.head)[0]), + pld->address_buffer); + iowrite16((u16)in, pld->base); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.head)[0]), + pld->address_buffer); + val = ioread16(pld->base); + + if (likely(val == in)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return; + } + + mif_err("ERR: %s txq.head(%d) != in(%d)\n", + get_dev_name(id), val, in); + udelay(100); + + /* Write head value again */ + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->txq.head)[0]), + pld->address_buffer); + iowrite16((u16)in, pld->base); + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + return; +} + +static inline void set_tx_tail(struct pld_link_device *pld, int id, u32 out) +{ + return; +} + +static inline u8 *get_tx_buff(struct pld_link_device *pld, int id) +{ + return pld->dev[id]->txq.buff; +} + +static inline u32 get_tx_buff_size(struct pld_link_device *pld, int id) +{ + return pld->dev[id]->txq.size; +} + +static inline u32 get_rx_head(struct pld_link_device *pld, int id) +{ + u16 val1 = 0, val2 = 0, cnt = 3; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.head)[0]), + pld->address_buffer); + val1 = ioread16(pld->base); + + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.head)[0]), + pld->address_buffer); + val2 = ioread16(pld->base); + + if (likely(val1 == val2)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; + } + + mif_err("ERR: %s rxq.head(%d) != in(%d)\n", + get_dev_name(id), val1, val2); + udelay(100); + + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; +} + +static inline u32 get_rx_tail(struct pld_link_device *pld, int id) +{ + u16 val1 = 0, val2 = 0, cnt = 3; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + do { + /* Check head value written */ + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.tail)[0]), + pld->address_buffer); + val1 = ioread16(pld->base); + + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.tail)[0]), + pld->address_buffer); + val2 = ioread16(pld->base); + + if (likely(val1 == val2)) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; + } + + mif_err("ERR: %s rxq.tail(%d) != in(%d)\n", + get_dev_name(id), val1, val2); + udelay(100); + + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + return val1; +} + +static inline void set_rx_head(struct pld_link_device *pld, int id, u32 in) +{ + return; +} + +static inline void set_rx_tail(struct pld_link_device *pld, int id, u32 out) +{ + int cnt = 3; + u32 val = 0; + unsigned long int flags; + + spin_lock_irqsave(&pld->pld_lock, flags); + + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.tail)[0]), + pld->address_buffer); + iowrite16((u16)out, pld->base); + + do { + /* Check tail value written */ + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.tail)[0]), + pld->address_buffer); + val = ioread16(pld->base); + + if (val == out) { + spin_unlock_irqrestore(&pld->pld_lock, flags); + return; + } + + mif_err("ERR: %s rxq.tail(%d) != out(%d)\n", + get_dev_name(id), val, out); + udelay(100); + + /* Write tail value again */ + iowrite16(PLD_ADDR_MASK(&(pld->dev[id]->rxq.tail)[0]), + pld->address_buffer); + iowrite16((u16)out, pld->base); + } while (cnt--); + + spin_unlock_irqrestore(&pld->pld_lock, flags); + return; +} + +static inline u8 *get_rx_buff(struct pld_link_device *pld, int id) +{ + return pld->dev[id]->rxq.buff; +} + +static inline u32 get_rx_buff_size(struct pld_link_device *pld, int id) +{ + return pld->dev[id]->rxq.size; +} + +static inline u16 get_mask_req_ack(struct pld_link_device *pld, int id) +{ + return pld->dev[id]->mask_req_ack; +} + +static inline u16 get_mask_res_ack(struct pld_link_device *pld, int id) +{ + return pld->dev[id]->mask_res_ack; +} + +static inline u16 get_mask_send(struct pld_link_device *pld, int id) +{ + return pld->dev[id]->mask_send; +} + +/* Get free space in the TXQ as well as in & out pointers */ +static inline int get_txq_space(struct pld_link_device *pld, int dev, u32 qsize, + u32 *in, u32 *out) +{ + struct link_device *ld = &pld->ld; + int cnt = 3; + u32 head; + u32 tail; + int space; + + do { + head = get_tx_head(pld, dev); + tail = get_tx_tail(pld, dev); + + space = (head < tail) ? (tail - head - 1) : + (qsize + tail - head - 1); + mif_debug("%s: %s_TXQ qsize[%u] in[%u] out[%u] space[%u]\n", + ld->name, get_dev_name(dev), qsize, head, tail, space); + + if (circ_valid(qsize, head, tail)) { + *in = head; + *out = tail; + return space; + } + + mif_info("%s: CAUTION! <%pf> " + "%s_TXQ invalid (size:%d in:%d out:%d)\n", + ld->name, __builtin_return_address(0), + get_dev_name(dev), qsize, head, tail); + + udelay(100); + } while (cnt--); + + *in = 0; + *out = 0; + return -EINVAL; +} + +static void reset_tx_circ(struct pld_link_device *pld, int dev) +{ + set_tx_head(pld, dev, 0); + set_tx_tail(pld, dev, 0); + if (dev == IPC_FMT) + trigger_force_cp_crash(pld); +} + +/* Get data size in the RXQ as well as in & out pointers */ +static inline int get_rxq_rcvd(struct pld_link_device *pld, int dev, u32 qsize, + u32 *in, u32 *out) +{ + struct link_device *ld = &pld->ld; + int cnt = 3; + u32 head; + u32 tail; + u32 rcvd; + + do { + head = get_rx_head(pld, dev); + tail = get_rx_tail(pld, dev); + if (head == tail) { + *in = head; + *out = tail; + return 0; + } + + rcvd = (head > tail) ? (head - tail) : (qsize - tail + head); + mif_info("%s: %s_RXQ qsize[%u] in[%u] out[%u] rcvd[%u]\n", + ld->name, get_dev_name(dev), qsize, head, tail, rcvd); + + if (circ_valid(qsize, head, tail)) { + *in = head; + *out = tail; + return rcvd; + } + + mif_info("%s: CAUTION! <%pf> " + "%s_RXQ invalid (size:%d in:%d out:%d)\n", + ld->name, __builtin_return_address(0), + get_dev_name(dev), qsize, head, tail); + + udelay(100); + } while (cnt--); + + *in = 0; + *out = 0; + return -EINVAL; +} + +static void reset_rx_circ(struct pld_link_device *pld, int dev) +{ + set_rx_head(pld, dev, 0); + set_rx_tail(pld, dev, 0); + if (dev == IPC_FMT) + trigger_force_cp_crash(pld); +} + +static int check_access(struct pld_link_device *pld) +{ + struct link_device *ld = &pld->ld; + int i; + u16 magic = get_magic(pld); + u16 access = get_access(pld); + + if (likely(magic == DPRAM_MAGIC_CODE && access == 1)) + return 0; + + for (i = 1; i <= 100; i++) { + mif_info("%s: ERR! magic:%X access:%X -> retry:%d\n", + ld->name, magic, access, i); + udelay(100); + + magic = get_magic(pld); + access = get_access(pld); + if (likely(magic == DPRAM_MAGIC_CODE && access == 1)) + return 0; + } + + mif_info("%s: !CRISIS! magic:%X access:%X\n", ld->name, magic, access); + return -EACCES; +} + +static bool ipc_active(struct pld_link_device *pld) +{ + struct link_device *ld = &pld->ld; + + /* Check DPRAM mode */ + if (ld->mode != LINK_MODE_IPC) { + mif_info("%s: <%pf> ld->mode != LINK_MODE_IPC\n", + ld->name, __builtin_return_address(0)); + return false; + } + + if (check_access(pld) < 0) { + mif_info("%s: ERR! <%pf> check_access fail\n", + ld->name, __builtin_return_address(0)); + return false; + } + + return true; +} + +static void pld_ipc_write(struct pld_link_device *pld, int dev, + u32 qsize, u32 in, u32 out, struct sk_buff *skb) +{ + struct link_device *ld = &pld->ld; + u8 __iomem *buff = get_tx_buff(pld, dev); + u8 *src = skb->data; + u32 len = skb->len; + u32 inp; + struct mif_irq_map map; + + if (in < out) { + /* +++++++++ in ---------- out ++++++++++ */ + iowrite16(PLD_ADDR_MASK(&(buff+in)[0]), pld->address_buffer); + memcpy(pld->base, src, len); + } else { + /* ------ out +++++++++++ in ------------ */ + u32 space = qsize - in; + + /* 1) in -> buffer end */ + iowrite16(PLD_ADDR_MASK(&(buff+in)[0]), pld->address_buffer); + memcpy(pld->base, src, ((len > space) ? space : len)); + + if (len > space) { + iowrite16(PLD_ADDR_MASK(&buff[0]), + pld->address_buffer); + memcpy(pld->base, (src+space), (len-space)); + } + } + + /* update new in pointer */ + inp = in + len; + if (inp >= qsize) + inp -= qsize; + set_tx_head(pld, dev, inp); + + if (dev == IPC_FMT) { + set_dpram_map(pld, &map); + mif_irq_log(ld->mc->msd, map, "ipc_write", sizeof("ipc_write")); + mif_ipc_log(MIF_IPC_AP2CP, ld->mc->msd, skb->data, skb->len); + } +} + +static int pld_try_ipc_tx(struct pld_link_device *pld, int dev) +{ + struct link_device *ld = &pld->ld; + struct sk_buff_head *txq = ld->skb_txq[dev]; + struct sk_buff *skb; + unsigned long int flags; + u32 qsize = get_tx_buff_size(pld, dev); + u32 in; + u32 out; + int space; + int copied = 0; + + spin_lock_irqsave(&pld->tx_rx_lock, flags); + + while (1) { + space = get_txq_space(pld, dev, qsize, &in, &out); + if (unlikely(space < 0)) { + spin_unlock_irqrestore(&pld->tx_rx_lock, flags); + reset_tx_circ(pld, dev); + return space; + } + + skb = skb_dequeue(txq); + if (unlikely(!skb)) + break; + + if (unlikely(space < skb->len)) { + atomic_set(&pld->res_required[dev], 1); + skb_queue_head(txq, skb); + spin_unlock_irqrestore(&pld->tx_rx_lock, flags); + mif_info("%s: %s " + "qsize[%u] in[%u] out[%u] free[%u] < len[%u]\n", + ld->name, get_dev_name(dev), + qsize, in, out, space, skb->len); + return -ENOSPC; + } + + /* TX if there is enough room in the queue */ + pld_ipc_write(pld, dev, qsize, in, out, skb); + copied += skb->len; + dev_kfree_skb_any(skb); + } + + spin_unlock_irqrestore(&pld->tx_rx_lock, flags); + + return copied; +} + +static void pld_ipc_rx_task(unsigned long data) +{ + struct pld_link_device *pld = (struct pld_link_device *)data; + struct link_device *ld = &pld->ld; + struct io_device *iod; + struct mif_rxb *rxb; + unsigned qlen; + int i; + + for (i = 0; i < ld->max_ipc_dev; i++) { + iod = pld->iod[i]; + qlen = rxbq_size(&pld->rxbq[i]); + while (qlen > 0) { + rxb = rxbq_get_data_rxb(&pld->rxbq[i]); + iod->recv(iod, ld, rxb->data, rxb->len); + rxb_clear(rxb); + qlen--; + } + } +} + +static void pld_ipc_read(struct pld_link_device *pld, int dev, u8 *dst, + u8 __iomem *src, u32 out, u32 len, u32 qsize) +{ + u8 *ori_det = dst; + + if ((out + len) <= qsize) { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + iowrite16(PLD_ADDR_MASK(&(src+out)[0]), pld->address_buffer); + memcpy(dst, pld->base, len); + } else { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + unsigned len1 = qsize - out; + + /* 1) out -> buffer end */ + iowrite16(PLD_ADDR_MASK(&(src+out)[0]), pld->address_buffer); + memcpy(dst, pld->base, len1); + + /* 2) buffer start -> in */ + dst += len1; + iowrite16(PLD_ADDR_MASK(&src[0]), pld->address_buffer); + memcpy(dst, pld->base, (len - len1)); + } + + if (pld->ld.mode == LINK_MODE_IPC && ori_det[0] != 0x7F) { + mif_info("ipc read error!! in[%d], out[%d]\n", + get_rx_head(pld, dev), + get_rx_tail(pld, dev)); + } + +} + +/* + ret < 0 : error + ret == 0 : no data + ret > 0 : valid data +*/ +static int pld_ipc_recv_data_with_rxb(struct pld_link_device *pld, int dev) +{ + struct link_device *ld = &pld->ld; + struct mif_rxb *rxb; + u8 __iomem *src = get_rx_buff(pld, dev); + u32 qsize = get_rx_buff_size(pld, dev); + u32 in; + u32 out; + u32 rcvd; + struct mif_irq_map map; + unsigned long int flags; + + spin_lock_irqsave(&pld->tx_rx_lock, flags); + + rcvd = get_rxq_rcvd(pld, dev, qsize, &in, &out); + if (rcvd <= 0) { + spin_unlock_irqrestore(&pld->tx_rx_lock, flags); + return rcvd; + } + + if (dev == IPC_FMT) { + set_dpram_map(pld, &map); + mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv")); + } + + /* Allocate an rxb */ + rxb = rxbq_get_free_rxb(&pld->rxbq[dev]); + if (!rxb) { + mif_info("%s: ERR! %s rxbq_get_free_rxb fail\n", + ld->name, get_dev_name(dev)); + spin_unlock_irqrestore(&pld->tx_rx_lock, flags); + return -ENOMEM; + } + + /* Read data from each DPRAM buffer */ + pld_ipc_read(pld, dev, rxb_put(rxb, rcvd), src, out, rcvd, qsize); + + /* Calculate and set new out */ + out += rcvd; + if (out >= qsize) + out -= qsize; + set_rx_tail(pld, dev, out); + + spin_unlock_irqrestore(&pld->tx_rx_lock, flags); + return rcvd; +} + +static void non_command_handler(struct pld_link_device *pld, u16 non_cmd) +{ + struct link_device *ld = &pld->ld; + int i = 0; + int ret = 0; + u16 mask = 0; + + if (!ipc_active(pld)) + return; + + /* Read data from DPRAM */ + for (i = 0; i < ld->max_ipc_dev; i++) { + ret = pld_ipc_recv_data_with_rxb(pld, i); + if (ret < 0) + reset_rx_circ(pld, i); + + /* Check and process REQ_ACK (at this time, in == out) */ + if (non_cmd & get_mask_req_ack(pld, i)) { + mif_debug("%s: send %s_RES_ACK\n", + ld->name, get_dev_name(i)); + mask |= get_mask_res_ack(pld, i); + } + } + + /* Schedule soft IRQ for RX */ + tasklet_hi_schedule(&pld->rx_tsk); + + /* Try TX via DPRAM */ + for (i = 0; i < ld->max_ipc_dev; i++) { + if (atomic_read(&pld->res_required[i]) > 0) { + ret = pld_try_ipc_tx(pld, i); + if (ret > 0) { + atomic_set(&pld->res_required[i], 0); + mask |= get_mask_send(pld, i); + } else if (ret == -ENOSPC) { + mask |= get_mask_req_ack(pld, i); + } + } + } + + if (mask) { + send_intr(pld, INT_NON_CMD(mask)); + mif_debug("%s: send intr 0x%04X\n", ld->name, mask); + } +} + +static void handle_cp_crash(struct pld_link_device *pld) +{ + struct link_device *ld = &pld->ld; + struct io_device *iod; + int i; + + for (i = 0; i < ld->max_ipc_dev; i++) { + mif_info("%s: purging %s_skb_txq\b", ld->name, get_dev_name(i)); + skb_queue_purge(ld->skb_txq[i]); + } + + iod = link_get_iod_with_format(ld, IPC_FMT); + iod->modem_state_changed(iod, STATE_CRASH_EXIT); + + iod = link_get_iod_with_format(ld, IPC_BOOT); + iod->modem_state_changed(iod, STATE_CRASH_EXIT); + + iod = link_get_iod_with_channel(ld, RMNET0_CH_ID); + if (iod) + iodevs_for_each(iod->msd, iodev_netif_stop, 0); +} + +static void handle_no_crash_ack(unsigned long arg) +{ + struct pld_link_device *pld = (struct pld_link_device *)arg; + struct link_device *ld = &pld->ld; + + mif_err("%s: ERR! No CRASH_EXIT ACK from CP\n", ld->mc->name); + + if (!wake_lock_active(&pld->wlock)) + wake_lock(&pld->wlock); + + handle_cp_crash(pld); +} + +static int trigger_force_cp_crash(struct pld_link_device *pld) +{ + struct link_device *ld = &pld->ld; + + if (ld->mode == LINK_MODE_ULOAD) { + mif_err("%s: CP crash is already in progress\n", ld->mc->name); + return 0; + } + + ld->mode = LINK_MODE_ULOAD; + mif_err("%s: called by %pf\n", ld->name, __builtin_return_address(0)); + + send_intr(pld, INT_CMD(INT_CMD_CRASH_EXIT)); + + mif_add_timer(&pld->crash_ack_timer, FORCE_CRASH_ACK_TIMEOUT, + handle_no_crash_ack, (unsigned long)pld); + + return 0; +} + +static int pld_init_ipc(struct pld_link_device *pld) +{ + struct link_device *ld = &pld->ld; + int i; + + if (ld->mode == LINK_MODE_IPC && + get_magic(pld) == DPRAM_MAGIC_CODE && + get_access(pld) == 1) + mif_info("%s: IPC already initialized\n", ld->name); + + /* Clear pointers in every circular queue */ + for (i = 0; i < ld->max_ipc_dev; i++) { + set_tx_head(pld, i, 0); + set_tx_tail(pld, i, 0); + set_rx_head(pld, i, 0); + set_rx_tail(pld, i, 0); + } + + /* Initialize variables for efficient TX/RX processing */ + for (i = 0; i < ld->max_ipc_dev; i++) + pld->iod[i] = link_get_iod_with_format(ld, i); + pld->iod[IPC_RAW] = link_get_iod_with_format(ld, IPC_MULTI_RAW); + + for (i = 0; i < ld->max_ipc_dev; i++) + atomic_set(&pld->res_required[i], 0); + + spin_lock_init(&pld->tx_rx_lock); + + /* Enable IPC */ + atomic_set(&pld->accessing, 0); + + set_magic(pld, DPRAM_MAGIC_CODE); + set_access(pld, 1); + if (get_magic(pld) != DPRAM_MAGIC_CODE || get_access(pld) != 1) + return -EACCES; + + ld->mode = LINK_MODE_IPC; + + if (wake_lock_active(&pld->wlock)) + wake_unlock(&pld->wlock); + + return 0; +} + +static void cmd_req_active_handler(struct pld_link_device *pld) +{ + send_intr(pld, INT_CMD(INT_CMD_RES_ACTIVE)); +} + +static void cmd_crash_reset_handler(struct pld_link_device *pld) +{ + struct link_device *ld = &pld->ld; + struct io_device *iod = NULL; + + ld->mode = LINK_MODE_ULOAD; + + if (!wake_lock_active(&pld->wlock)) + wake_lock(&pld->wlock); + + mif_err("%s: Recv 0xC7 (CRASH_RESET)\n", ld->name); + + iod = link_get_iod_with_format(ld, IPC_FMT); + iod->modem_state_changed(iod, STATE_CRASH_RESET); + + iod = link_get_iod_with_format(ld, IPC_BOOT); + iod->modem_state_changed(iod, STATE_CRASH_RESET); +} + +static void cmd_crash_exit_handler(struct pld_link_device *pld) +{ + struct link_device *ld = &pld->ld; + + ld->mode = LINK_MODE_ULOAD; + + if (!wake_lock_active(&pld->wlock)) + wake_lock(&pld->wlock); + + mif_err("%s: Recv 0xC9 (CRASH_EXIT)\n", ld->name); + + del_timer(&pld->crash_ack_timer); + + if (pld->ext_op && pld->ext_op->crash_log) + pld->ext_op->crash_log(pld); + + handle_cp_crash(pld); +} + +static void cmd_phone_start_handler(struct pld_link_device *pld) +{ + struct link_device *ld = &pld->ld; + struct io_device *iod = NULL; + + mif_info("%s: Recv 0xC8 (CP_START)\n", ld->name); + + pld_init_ipc(pld); + + iod = link_get_iod_with_format(ld, IPC_FMT); + if (!iod) { + mif_info("%s: ERR! no iod\n", ld->name); + return; + } + + if (pld->ext_op && pld->ext_op->cp_start_handler) + pld->ext_op->cp_start_handler(pld); + + if (ld->mc->phone_state != STATE_ONLINE) { + mif_info("%s: phone_state: %d -> ONLINE\n", + ld->name, ld->mc->phone_state); + iod->modem_state_changed(iod, STATE_ONLINE); + } + + mif_info("%s: Send 0xC2 (INIT_END)\n", ld->name); + send_intr(pld, INT_CMD(INT_CMD_INIT_END)); +} + +static void command_handler(struct pld_link_device *pld, u16 cmd) +{ + struct link_device *ld = &pld->ld; + + switch (INT_CMD_MASK(cmd)) { + case INT_CMD_REQ_ACTIVE: + cmd_req_active_handler(pld); + break; + + case INT_CMD_CRASH_RESET: + pld->init_status = DPRAM_INIT_STATE_NONE; + cmd_crash_reset_handler(pld); + break; + + case INT_CMD_CRASH_EXIT: + pld->init_status = DPRAM_INIT_STATE_NONE; + cmd_crash_exit_handler(pld); + break; + + case INT_CMD_PHONE_START: + pld->init_status = DPRAM_INIT_STATE_READY; + cmd_phone_start_handler(pld); + complete_all(&pld->dpram_init_cmd); + break; + + case INT_CMD_NV_REBUILDING: + mif_info("%s: NV_REBUILDING\n", ld->name); + break; + + case INT_CMD_PIF_INIT_DONE: + complete_all(&pld->modem_pif_init_done); + break; + + case INT_CMD_SILENT_NV_REBUILDING: + mif_info("%s: SILENT_NV_REBUILDING\n", ld->name); + break; + + case INT_CMD_NORMAL_PWR_OFF: + /*ToDo:*/ + /*kernel_sec_set_cp_ack()*/; + break; + + case INT_CMD_REQ_TIME_SYNC: + case INT_CMD_CP_DEEP_SLEEP: + case INT_CMD_EMER_DOWN: + break; + + default: + mif_info("%s: unknown command 0x%04X\n", ld->name, cmd); + } +} + +static irqreturn_t pld_irq_handler(int irq, void *data) +{ + struct pld_link_device *pld = (struct pld_link_device *)data; + struct link_device *ld = (struct link_device *)&pld->ld; + u16 int2ap = 0; + + if (unlikely(ld->mode == LINK_MODE_OFFLINE)) + return IRQ_HANDLED; + + int2ap = recv_intr(pld); + + if (unlikely(int2ap == INT_POWERSAFE_FAIL)) { + mif_info("%s: int2ap == INT_POWERSAFE_FAIL\n", ld->name); + goto exit; + } else if (int2ap == 0x1234 || int2ap == 0xDBAB || int2ap == 0xABCD) { + if (pld->ext_op && pld->ext_op->dload_cmd_handler) { + pld->ext_op->dload_cmd_handler(pld, int2ap); + goto exit; + } + } + + if (likely(INT_VALID(int2ap))) { + if (unlikely(INT_CMD_VALID(int2ap))) + command_handler(pld, int2ap); + else + non_command_handler(pld, int2ap); + } else { + mif_info("%s: ERR! invalid intr 0x%04X\n", + ld->name, int2ap); + } + +exit: + clear_intr(pld); + return IRQ_HANDLED; +} + +static void pld_send_ipc(struct link_device *ld, int dev, + struct io_device *iod, struct sk_buff *skb) +{ + struct pld_link_device *pld = to_pld_link_device(ld); + struct sk_buff_head *txq = ld->skb_txq[dev]; + int ret; + u16 mask; + + skb_queue_tail(txq, skb); + if (txq->qlen > 1024) { + mif_debug("%s: %s txq->qlen %d > 1024\n", + ld->name, get_dev_name(dev), txq->qlen); + } + + if (!ipc_active(pld)) + goto exit; + + if (atomic_read(&pld->res_required[dev]) > 0) { + mif_debug("%s: %s_TXQ is full\n", ld->name, get_dev_name(dev)); + goto exit; + } + + ret = pld_try_ipc_tx(pld, dev); + if (ret > 0) { + mask = get_mask_send(pld, dev); + send_intr(pld, INT_NON_CMD(mask)); + } else if (ret == -ENOSPC) { + mask = get_mask_req_ack(pld, dev); + send_intr(pld, INT_NON_CMD(mask)); + mif_info("%s: Send REQ_ACK 0x%04X\n", ld->name, mask); + } else { + mif_info("%s: pld_try_ipc_tx fail (err %d)\n", ld->name, ret); + } + +exit: + return; +} + +static int pld_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + enum dev_format dev = iod->format; + int len = skb->len; + + switch (dev) { + case IPC_FMT: + case IPC_RAW: + case IPC_RFS: + if (likely(ld->mode == LINK_MODE_IPC)) { + pld_send_ipc(ld, dev, iod, skb); + } else { + mif_info("%s: ld->mode != LINK_MODE_IPC\n", ld->name); + dev_kfree_skb_any(skb); + } + return len; + + default: + mif_info("%s: ERR! no TXQ for %s\n", ld->name, iod->name); + dev_kfree_skb_any(skb); + return -ENODEV; + } +} + +static int pld_force_dump(struct link_device *ld, struct io_device *iod) +{ + struct pld_link_device *pld = to_pld_link_device(ld); + trigger_force_cp_crash(pld); + return 0; +} + +static int pld_dump_start(struct link_device *ld, struct io_device *iod) +{ + struct pld_link_device *pld = to_pld_link_device(ld); + + if (pld->ext_op && pld->ext_op->dump_start) + return pld->ext_op->dump_start(pld); + else + return -ENODEV; +} + +static int pld_dump_update(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct pld_link_device *pld = to_pld_link_device(ld); + + if (pld->ext_op && pld->ext_op->dump_update) + return pld->ext_op->dump_update(pld, (void *)arg); + else + return -ENODEV; +} + +static int pld_ioctl(struct link_device *ld, struct io_device *iod, + unsigned int cmd, unsigned long arg) +{ + struct pld_link_device *pld = to_pld_link_device(ld); + int err = 0; + +/* + mif_info("%s: cmd 0x%08X\n", ld->name, cmd); +*/ + + switch (cmd) { + case IOCTL_DPRAM_INIT_STATUS: + mif_debug("%s: get dpram init status\n", ld->name); + return pld->init_status; + + default: + if (pld->ext_ioctl) { + err = pld->ext_ioctl(pld, iod, cmd, arg); + } else { + mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd); + err = -EINVAL; + } + + break; + } + + return err; +} + +static int pld_table_init(struct pld_link_device *pld) +{ + struct link_device *ld = &pld->ld; + u8 __iomem *dp_base; + int i; + + if (!pld->base) { + mif_info("%s: ERR! pld->base == NULL\n", ld->name); + return -EINVAL; + } + dp_base = pld->base; + + /* Map for IPC */ + if (pld->dpctl->ipc_map) { + memcpy(&pld->ipc_map, pld->dpctl->ipc_map, + sizeof(struct dpram_ipc_map)); + } + + pld->magic_ap2cp = pld->ipc_map.magic_ap2cp; + pld->access_ap2cp = pld->ipc_map.access_ap2cp; + + pld->magic_cp2ap = pld->ipc_map.magic_cp2ap; + pld->access_cp2ap = pld->ipc_map.access_cp2ap; + + pld->address_buffer = pld->ipc_map.address_buffer; + + for (i = 0; i < ld->max_ipc_dev; i++) + pld->dev[i] = &pld->ipc_map.dev[i]; + pld->mbx2ap = pld->ipc_map.mbx_cp2ap; + pld->mbx2cp = pld->ipc_map.mbx_ap2cp; + + /* Map for booting */ + if (pld->ext_op && pld->ext_op->init_boot_map) { + pld->ext_op->init_boot_map(pld); + } else { + pld->bt_map.magic = (u32 *)(dp_base); + pld->bt_map.buff = (u8 *)(dp_base + DP_BOOT_BUFF_OFFSET); + pld->bt_map.size = pld->size - 8; + } + + /* Map for download (FOTA, UDL, etc.) */ + if (pld->ext_op && pld->ext_op->init_dl_map) { + pld->ext_op->init_dl_map(pld); + } else { + pld->dl_map.magic = (u32 *)(dp_base); + pld->dl_map.buff = (u8 *)(dp_base + DP_DLOAD_BUFF_OFFSET); + } + + /* Map for upload mode */ + if (pld->ext_op && pld->ext_op->init_ul_map) { + pld->ext_op->init_ul_map(pld); + } else { + pld->ul_map.magic = (u32 *)(dp_base); + pld->ul_map.buff = (u8 *)(dp_base + DP_ULOAD_BUFF_OFFSET); + } + + return 0; +} + +static void pld_setup_common_op(struct pld_link_device *pld) +{ + pld->clear_intr = clear_intr; + pld->recv_intr = recv_intr; + pld->send_intr = send_intr; + pld->get_magic = get_magic; + pld->set_magic = set_magic; + pld->get_access = get_access; + pld->set_access = set_access; + pld->get_tx_head = get_tx_head; + pld->get_tx_tail = get_tx_tail; + pld->set_tx_head = set_tx_head; + pld->set_tx_tail = set_tx_tail; + pld->get_tx_buff = get_tx_buff; + pld->get_tx_buff_size = get_tx_buff_size; + pld->get_rx_head = get_rx_head; + pld->get_rx_tail = get_rx_tail; + pld->set_rx_head = set_rx_head; + pld->set_rx_tail = set_rx_tail; + pld->get_rx_buff = get_rx_buff; + pld->get_rx_buff_size = get_rx_buff_size; + pld->get_mask_req_ack = get_mask_req_ack; + pld->get_mask_res_ack = get_mask_res_ack; + pld->get_mask_send = get_mask_send; +} + +static int pld_link_init(struct link_device *ld, struct io_device *iod) +{ + return 0; +} + +static void pld_link_terminate(struct link_device *ld, struct io_device *iod) +{ + return; +} + +struct link_device *pld_create_link_device(struct platform_device *pdev) +{ + struct modem_data *mdm_data = NULL; + struct pld_link_device *pld = NULL; + struct link_device *ld = NULL; + struct resource *res = NULL; + resource_size_t res_size; + struct modemlink_dpram_control *dpctl = NULL; + unsigned long task_data; + int ret = 0; + int i = 0; + int bsize; + int qsize; + + /* Get the platform data */ + mdm_data = (struct modem_data *)pdev->dev.platform_data; + if (!mdm_data) { + mif_info("ERR! mdm_data == NULL\n"); + goto err; + } + mif_info("modem = %s\n", mdm_data->name); + mif_info("link device = %s\n", mdm_data->link_name); + + if (!mdm_data->dpram_ctl) { + mif_info("ERR! mdm_data->dpram_ctl == NULL\n"); + goto err; + } + dpctl = mdm_data->dpram_ctl; + + /* Alloc DPRAM link device structure */ + pld = kzalloc(sizeof(struct pld_link_device), GFP_KERNEL); + if (!pld) { + mif_info("ERR! kzalloc pld fail\n"); + goto err; + } + ld = &pld->ld; + + /* Retrieve modem data and DPRAM control data from the modem data */ + ld->mdm_data = mdm_data; + ld->name = mdm_data->link_name; + ld->ipc_version = mdm_data->ipc_version; + + /* Retrieve the most basic data for IPC from the modem data */ + pld->dpctl = dpctl; + pld->type = dpctl->dp_type; + + if (mdm_data->ipc_version < SIPC_VER_50) { + if (!dpctl->max_ipc_dev) { + mif_info("ERR! no max_ipc_dev\n"); + goto err; + } + + ld->aligned = dpctl->aligned; + ld->max_ipc_dev = dpctl->max_ipc_dev; + } else { + ld->aligned = 1; + ld->max_ipc_dev = MAX_SIPC5_DEV; + } + + /* Set attributes as a link device */ + ld->init_comm = pld_link_init; + ld->terminate_comm = pld_link_terminate; + ld->send = pld_send; + ld->force_dump = pld_force_dump; + ld->dump_start = pld_dump_start; + ld->dump_update = pld_dump_update; + ld->ioctl = pld_ioctl; + + INIT_LIST_HEAD(&ld->list); + + skb_queue_head_init(&ld->sk_fmt_tx_q); + skb_queue_head_init(&ld->sk_raw_tx_q); + skb_queue_head_init(&ld->sk_rfs_tx_q); + ld->skb_txq[IPC_FMT] = &ld->sk_fmt_tx_q; + ld->skb_txq[IPC_RAW] = &ld->sk_raw_tx_q; + ld->skb_txq[IPC_RFS] = &ld->sk_rfs_tx_q; + + /* Set up function pointers */ + pld_setup_common_op(pld); + pld->ext_op = pld_get_ext_op(mdm_data->modem_type); + if (pld->ext_op && pld->ext_op->ioctl) + pld->ext_ioctl = pld->ext_op->ioctl; + + /* Retrieve DPRAM resource */ + if (!dpctl->dp_base) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + mif_info("%s: ERR! platform_get_resource fail\n", + ld->name); + goto err; + } + res_size = resource_size(res); + + dpctl->dp_base = ioremap_nocache(res->start, res_size); + dpctl->dp_size = res_size; + } + pld->base = dpctl->dp_base; + pld->size = dpctl->dp_size; + + mif_info("%s: type %d, aligned %d, base 0x%08X, size %d\n", + ld->name, pld->type, ld->aligned, (int)pld->base, pld->size); + + /* Initialize DPRAM map (physical map -> logical map) */ + ret = pld_table_init(pld); + if (ret < 0) { + mif_info("%s: ERR! pld_table_init fail (err %d)\n", + ld->name, ret); + goto err; + } + + spin_lock_init(&pld->pld_lock); + + /* Disable IPC */ + set_magic(pld, 0); + set_access(pld, 0); + + pld->init_status = DPRAM_INIT_STATE_NONE; + + /* Initialize locks, completions, and bottom halves */ + snprintf(pld->wlock_name, MIF_MAX_NAME_LEN, "%s_wlock", ld->name); + wake_lock_init(&pld->wlock, WAKE_LOCK_SUSPEND, pld->wlock_name); + + init_completion(&pld->dpram_init_cmd); + init_completion(&pld->modem_pif_init_done); + init_completion(&pld->udl_start_complete); + init_completion(&pld->udl_cmd_complete); + init_completion(&pld->crash_start_complete); + init_completion(&pld->crash_recv_done); + + task_data = (unsigned long)pld; + tasklet_init(&pld->rx_tsk, pld_ipc_rx_task, task_data); + + /* Prepare RXB queue */ + qsize = DPRAM_MAX_RXBQ_SIZE; + for (i = 0; i < ld->max_ipc_dev; i++) { + bsize = rxbq_get_page_size(get_rx_buff_size(pld, i)); + pld->rxbq[i].size = qsize; + pld->rxbq[i].in = 0; + pld->rxbq[i].out = 0; + pld->rxbq[i].rxb = rxbq_create_pool(bsize, qsize); + if (!pld->rxbq[i].rxb) { + mif_info("%s: ERR! %s rxbq_create_pool fail\n", + ld->name, get_dev_name(i)); + goto err; + } + mif_info("%s: %s rxbq_pool created (bsize:%d, qsize:%d)\n", + ld->name, get_dev_name(i), bsize, qsize); + } + + /* Prepare a multi-purpose miscellaneous buffer */ + pld->buff = kzalloc(pld->size, GFP_KERNEL); + if (!pld->buff) { + mif_info("%s: ERR! kzalloc pld->buff fail\n", ld->name); + goto err; + } + + /* Retrieve DPRAM IRQ GPIO# */ + pld->gpio_dpram_int = mdm_data->gpio_dpram_int; + + /* Retrieve DPRAM IRQ# */ + if (!dpctl->dpram_irq) { + dpctl->dpram_irq = platform_get_irq_byname(pdev, "dpram_irq"); + if (dpctl->dpram_irq < 0) { + mif_info("%s: ERR! platform_get_irq_byname fail\n", + ld->name); + goto err; + } + } + pld->irq = dpctl->dpram_irq; + + /* Retrieve DPRAM IRQ flags */ + if (!dpctl->dpram_irq_flags) + dpctl->dpram_irq_flags = (IRQF_NO_SUSPEND | IRQF_TRIGGER_LOW); + pld->irq_flags = dpctl->dpram_irq_flags; + + /* Register DPRAM interrupt handler */ + snprintf(pld->irq_name, MIF_MAX_NAME_LEN, "%s_irq", ld->name); + ret = pld_register_isr(pld->irq, pld_irq_handler, pld->irq_flags, + pld->irq_name, pld); + if (ret) + goto err; + + return ld; + +err: + if (pld) { + kfree(pld->buff); + kfree(pld); + } + + return NULL; +} + diff --git a/drivers/misc/modem_if/modem_link_device_pld.h b/drivers/misc/modem_if/modem_link_device_pld.h new file mode 100644 index 00000000000..89c44b2a86b --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_pld.h @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MODEM_LINK_DEVICE_PLD_H__ +#define __MODEM_LINK_DEVICE_PLD_H__ + +#include "modem_link_device_memory.h" + +#define PLD_ADDR_MASK(x) (0x00003FFF & (unsigned long)(x)) + +/* + mbx_ap2cp + 0x0 + magic_code + + access_enable + + padding + + mbx_cp2ap + 0x1000 + magic_code + + access_enable + + padding + + fmt_tx_head + fmt_tx_tail + fmt_tx_buff + 0x2000 + raw_tx_head + raw_tx_tail + raw_tx_buff + + fmt_rx_head + fmt_rx_tail + fmt_rx_buff + 0x3000 + raw_rx_head + raw_rx_tail + raw_rx_buff + + = 2 + + 4094 + + 2 + + 4094 + + 2 + + 2 + + 2 + 2 + 1020 + + 2 + 2 + 3064 + + 2 + 2 + 1020 + + 2 + 2 + 3064 + */ + +#define PLD_FMT_TX_BUFF_SZ 1024 +#define PLD_RAW_TX_BUFF_SZ 3072 +#define PLD_FMT_RX_BUFF_SZ 1024 +#define PLD_RAW_RX_BUFF_SZ 3072 + +#define MAX_MSM_EDPRAM_IPC_DEV 2 /* FMT, RAW */ + +struct pld_ipc_map { + u16 mbx_ap2cp; + u16 magic_ap2cp; + u16 access_ap2cp; + u16 fmt_tx_head; + u16 raw_tx_head; + u16 fmt_rx_tail; + u16 raw_rx_tail; + u16 temp1; + u8 padding1[4080]; + + u16 mbx_cp2ap; + u16 magic_cp2ap; + u16 access_cp2ap; + u16 fmt_tx_tail; + u16 raw_tx_tail; + u16 fmt_rx_head; + u16 raw_rx_head; + u16 temp2; + u8 padding2[4080]; + + u8 fmt_tx_buff[PLD_FMT_TX_BUFF_SZ]; + u8 raw_tx_buff[PLD_RAW_TX_BUFF_SZ]; + u8 fmt_rx_buff[PLD_RAW_TX_BUFF_SZ]; + u8 raw_rx_buff[PLD_RAW_RX_BUFF_SZ]; + + u8 padding3[16384]; + + u16 address_buffer; +}; + +struct pld_ext_op; + +struct pld_link_device { + struct link_device ld; + + /* DPRAM address and size */ + enum dpram_type type; /* DPRAM type */ + u8 __iomem *base; /* DPRAM base virtual address */ + u32 size; /* DPRAM size */ + + /* DPRAM IRQ GPIO# */ + unsigned gpio_dpram_int; + + /* DPRAM IRQ from CP */ + int irq; + unsigned long irq_flags; + char irq_name[MIF_MAX_NAME_LEN]; + + /* Link to DPRAM control functions dependent on each platform */ + struct modemlink_dpram_control *dpctl; + + /* Physical configuration -> logical configuration */ + union { + struct dpram_boot_map bt_map; + struct qc_dpram_boot_map qc_bt_map; + }; + + struct dpram_dload_map dl_map; + struct dpram_uload_map ul_map; + + /* IPC device map */ + struct dpram_ipc_map ipc_map; + + /* Pointers (aliases) to IPC device map */ + u16 __iomem *magic_ap2cp; + u16 __iomem *access_ap2cp; + u16 __iomem *magic_cp2ap; + u16 __iomem *access_cp2ap; + u16 __iomem *address_buffer; + + struct dpram_ipc_device *dev[MAX_IPC_DEV]; + u16 __iomem *mbx2ap; + u16 __iomem *mbx2cp; + + /* Wakelock for DPRAM device */ + struct wake_lock wlock; + char wlock_name[MIF_MAX_NAME_LEN]; + + /* For booting */ + unsigned boot_start_complete; + struct completion dpram_init_cmd; + struct completion modem_pif_init_done; + + /* For UDL */ + struct tasklet_struct ul_tsk; + struct tasklet_struct dl_tsk; + struct completion udl_start_complete; + struct completion udl_cmd_complete; + struct dpram_udl_check udl_check; + struct dpram_udl_param udl_param; + + /* For CP crash dump */ + struct timer_list crash_ack_timer; + struct completion crash_start_complete; + struct completion crash_recv_done; + struct timer_list crash_timer; + int crash_rcvd; /* Count of CP crash dump packets received */ + + /* For locking TX process */ + spinlock_t tx_rx_lock; + spinlock_t pld_lock; + + /* For efficient RX process */ + struct tasklet_struct rx_tsk; + struct mif_rxb_queue rxbq[MAX_IPC_DEV]; + struct io_device *iod[MAX_IPC_DEV]; + + /* For retransmission after buffer full state */ + atomic_t res_required[MAX_IPC_DEV]; + + /* For wake-up/sleep control */ + atomic_t accessing; + + /* Multi-purpose miscellaneous buffer */ + u8 *buff; + + /* PLD IPC initialization status */ + int init_status; + + /* Alias to device-specific IOCTL function */ + int (*ext_ioctl)(struct pld_link_device *pld, struct io_device *iod, + unsigned int cmd, unsigned long arg); + + /* Common operations for each DPRAM */ + void (*clear_intr)(struct pld_link_device *pld); + u16 (*recv_intr)(struct pld_link_device *pld); + void (*send_intr)(struct pld_link_device *pld, u16 mask); + u16 (*get_magic)(struct pld_link_device *pld); + void (*set_magic)(struct pld_link_device *pld, u16 value); + u16 (*get_access)(struct pld_link_device *pld); + void (*set_access)(struct pld_link_device *pld, u16 value); + u32 (*get_tx_head)(struct pld_link_device *pld, int id); + u32 (*get_tx_tail)(struct pld_link_device *pld, int id); + void (*set_tx_head)(struct pld_link_device *pld, int id, u32 head); + void (*set_tx_tail)(struct pld_link_device *pld, int id, u32 tail); + u8 *(*get_tx_buff)(struct pld_link_device *pld, int id); + u32 (*get_tx_buff_size)(struct pld_link_device *pld, int id); + u32 (*get_rx_head)(struct pld_link_device *pld, int id); + u32 (*get_rx_tail)(struct pld_link_device *pld, int id); + void (*set_rx_head)(struct pld_link_device *pld, int id, u32 head); + void (*set_rx_tail)(struct pld_link_device *pld, int id, u32 tail); + u8 *(*get_rx_buff)(struct pld_link_device *pld, int id); + u32 (*get_rx_buff_size)(struct pld_link_device *pld, int id); + u16 (*get_mask_req_ack)(struct pld_link_device *pld, int id); + u16 (*get_mask_res_ack)(struct pld_link_device *pld, int id); + u16 (*get_mask_send)(struct pld_link_device *pld, int id); + + /* Extended operations for various modems */ + struct pld_ext_op *ext_op; +}; + +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_pld_link_device(linkdev) \ + container_of(linkdev, struct pld_link_device, ld) + +struct pld_ext_op { + int exist; + + void (*init_boot_map)(struct pld_link_device *pld); + void (*init_dl_map)(struct pld_link_device *pld); + void (*init_ul_map)(struct pld_link_device *pld); + + void (*dload_cmd_handler)(struct pld_link_device *pld, u16 cmd); + + void (*cp_start_handler)(struct pld_link_device *pld); + + void (*crash_log)(struct pld_link_device *pld); + int (*dump_start)(struct pld_link_device *pld); + int (*dump_update)(struct pld_link_device *pld, void *arg); + + int (*ioctl)(struct pld_link_device *pld, struct io_device *iod, + unsigned int cmd, unsigned long arg); + + void (*clear_intr)(struct pld_link_device *pld); +}; + +struct pld_ext_op *pld_get_ext_op(enum modem_t modem); +#endif diff --git a/drivers/misc/modem_if/modem_link_device_pld_ext_op.c b/drivers/misc/modem_if/modem_link_device_pld_ext_op.c new file mode 100644 index 00000000000..4f2df8f447a --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_pld_ext_op.c @@ -0,0 +1,556 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/time.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/wakelock.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/if_arp.h> +#include <linux/platform_device.h> +#include <linux/kallsyms.h> +#include <linux/platform_data/modem.h> + +#include "modem_prj.h" +#include "modem_link_device_pld.h" +#include "modem_utils.h" + +#if defined(CONFIG_CDMA_MODEM_MDM6600) || defined(CONFIG_GSM_MODEM_ESC6270) +enum qc_dload_tag { + QC_DLOAD_TAG_NONE = 0, + QC_DLOAD_TAG_BIN, + QC_DLOAD_TAG_NV, + QC_DLOAD_TAG_MAX +}; + +static void qc_dload_task(unsigned long data); + +static void qc_init_boot_map(struct pld_link_device *pld) +{ + struct qc_dpram_boot_map *qbt_map = &pld->qc_bt_map; + struct modemlink_dpram_control *dpctl = pld->dpctl; + + qbt_map->buff = pld->dev[0]->txq.buff; + qbt_map->frame_size = (u16 *)(pld->base + dpctl->boot_size_offset); + qbt_map->tag = (u16 *)(pld->base + dpctl->boot_tag_offset); + qbt_map->count = (u16 *)(pld->base + dpctl->boot_count_offset); + + tasklet_init(&pld->dl_tsk, qc_dload_task, (unsigned long)pld); +} + +static void qc_dload_map(struct pld_link_device *pld, u8 is_upload) +{ + struct qc_dpram_boot_map *qbt_map = &pld->qc_bt_map; + struct modemlink_dpram_control *dpctl = pld->dpctl; + unsigned int upload_offset = 0; + + if (is_upload == 1) { + upload_offset = 0x1000; + qbt_map->buff = pld->dev[0]->rxq.buff; + } else { + upload_offset = 0; + qbt_map->buff = pld->dev[0]->txq.buff; + } + + qbt_map->frame_size = (u16 *)(pld->base + + dpctl->boot_size_offset + upload_offset); + qbt_map->tag = (u16 *)(pld->base + + dpctl->boot_tag_offset + upload_offset); + qbt_map->count = (u16 *)(pld->base + + dpctl->boot_count_offset + upload_offset); + +} + +static int qc_prepare_download(struct pld_link_device *pld) +{ + int retval = 0; + int count = 0; + + qc_dload_map(pld, 0); + + while (1) { + if (pld->udl_check.copy_start) { + pld->udl_check.copy_start = 0; + break; + } + + usleep_range(10000, 11000); + + count++; + if (count > 1000) { + mif_err("ERR! count %d\n", count); + return -1; + } + } + + return retval; +} + +static void _qc_do_download(struct pld_link_device *pld, + struct dpram_udl_param *param) +{ + struct qc_dpram_boot_map *qbt_map = &pld->qc_bt_map; + + if (param->size <= pld->dpctl->max_boot_frame_size) { + iowrite16(PLD_ADDR_MASK(&qbt_map->buff[0]), + pld->address_buffer); + memcpy(pld->base, param->addr, param->size); + + iowrite16(PLD_ADDR_MASK(&qbt_map->frame_size[0]), + pld->address_buffer); + iowrite16(param->size, pld->base); + + iowrite16(PLD_ADDR_MASK(&qbt_map->tag[0]), + pld->address_buffer); + iowrite16(param->tag, pld->base); + + iowrite16(PLD_ADDR_MASK(&qbt_map->count[0]), + pld->address_buffer); + iowrite16(param->count, pld->base); + + pld->send_intr(pld, 0xDB12); + } else { + mif_info("param->size %d\n", param->size); + } +} + +static int _qc_download(struct pld_link_device *pld, void *arg, + enum qc_dload_tag tag) +{ + int retval = 0; + int count = 0; + int cnt_limit; + unsigned char *img; + struct dpram_udl_param param; + + retval = copy_from_user((void *)¶m, (void *)arg, sizeof(param)); + if (retval < 0) { + mif_err("ERR! copy_from_user fail\n"); + return -1; + } + + img = vmalloc(param.size); + if (!img) { + mif_err("ERR! vmalloc fail\n"); + return -1; + } + memset(img, 0, param.size); + memcpy(img, param.addr, param.size); + + pld->udl_check.total_size = param.size; + pld->udl_check.rest_size = param.size; + pld->udl_check.send_size = 0; + pld->udl_check.copy_complete = 0; + + pld->udl_param.addr = img; + pld->udl_param.size = pld->dpctl->max_boot_frame_size; + if (tag == QC_DLOAD_TAG_NV) + pld->udl_param.count = 1; + else + pld->udl_param.count = param.count; + pld->udl_param.tag = tag; + + if (pld->udl_check.rest_size < pld->dpctl->max_boot_frame_size) + pld->udl_param.size = pld->udl_check.rest_size; + + /* Download image (binary or NV) */ + _qc_do_download(pld, &pld->udl_param); + + /* Wait for completion + */ + if (tag == QC_DLOAD_TAG_NV) + cnt_limit = 200; + else + cnt_limit = 1000; + + while (1) { + if (pld->udl_check.copy_complete) { + pld->udl_check.copy_complete = 0; + retval = 0; + break; + } + + usleep_range(10000, 11000); + + count++; + if (count > cnt_limit) { + mif_err("ERR! count %d\n", count); + retval = -1; + break; + } + } + + vfree(img); + + return retval; +} + +static int qc_download_bin(struct pld_link_device *pld, void *arg) +{ + return _qc_download(pld, arg, QC_DLOAD_TAG_BIN); +} + +static int qc_download_nv(struct pld_link_device *pld, void *arg) +{ + return _qc_download(pld, arg, QC_DLOAD_TAG_NV); +} + +static void qc_dload_task(unsigned long data) +{ + struct pld_link_device *pld = (struct pld_link_device *)data; + + pld->udl_check.send_size += pld->udl_param.size; + pld->udl_check.rest_size -= pld->udl_param.size; + + pld->udl_param.addr += pld->udl_param.size; + + if (pld->udl_check.send_size >= pld->udl_check.total_size) { + pld->udl_check.copy_complete = 1; + pld->udl_param.tag = 0; + return; + } + + if (pld->udl_check.rest_size < pld->dpctl->max_boot_frame_size) + pld->udl_param.size = pld->udl_check.rest_size; + + pld->udl_param.count += 1; + + _qc_do_download(pld, &pld->udl_param); +} + +static void qc_dload_cmd_handler(struct pld_link_device *pld, u16 cmd) +{ + switch (cmd) { + case 0x1234: + pld->udl_check.copy_start = 1; + break; + + case 0xDBAB: + tasklet_schedule(&pld->dl_tsk); + break; + + case 0xABCD: + mif_info("[%s] booting Start\n", pld->ld.name); + pld->udl_check.boot_complete = 1; + break; + + default: + mif_err("ERR! unknown command 0x%04X\n", cmd); + } +} + +static int qc_boot_start(struct pld_link_device *pld) +{ + u16 mask = 0; + int count = 0; + + /* Send interrupt -> '0x4567' */ + mask = 0x4567; + pld->send_intr(pld, mask); + + while (1) { + if (pld->udl_check.boot_complete) { + pld->udl_check.boot_complete = 0; + break; + } + + usleep_range(10000, 11000); + + count++; + if (count > 200) { + mif_err("ERR! count %d\n", count); + return -1; + } + } + + return 0; +} + +static int qc_boot_post_process(struct pld_link_device *pld) +{ + int count = 0; + + while (1) { + if (pld->boot_start_complete) { + pld->boot_start_complete = 0; + break; + } + + usleep_range(10000, 11000); + + count++; + if (count > 200) { + mif_err("ERR! count %d\n", count); + return -1; + } + } + + return 0; +} + +static void qc_start_handler(struct pld_link_device *pld) +{ + /* + * INT_MASK_VALID | INT_MASK_CMD | INT_MASK_CP_AIRPLANE_BOOT | + * INT_MASK_CP_AP_ANDROID | INT_MASK_CMD_INIT_END + */ + u16 mask = (0x0080 | 0x0040 | 0x1000 | 0x0100 | 0x0002); + + pld->boot_start_complete = 1; + + /* Send INIT_END code to CP */ + mif_info("send 0x%04X (INIT_END)\n", mask); + + pld->send_intr(pld, mask); +} + +static void qc_crash_log(struct pld_link_device *pld) +{ + struct link_device *ld = &pld->ld; + static unsigned char buf[151]; + u8 __iomem *data = NULL; + + data = pld->get_rx_buff(pld, IPC_FMT); + memcpy(buf, data, (sizeof(buf) - 1)); + + mif_info("PHONE ERR MSG\t| %s Crash\n", ld->mdm_data->name); + mif_info("PHONE ERR MSG\t| %s\n", buf); +} + +static int _qc_data_upload(struct pld_link_device *pld, + struct dpram_udl_param *param) +{ + struct qc_dpram_boot_map *qbt_map = &pld->qc_bt_map; + int retval = 0; + u16 intval = 0; + int count = 0; + + while (1) { + if (!gpio_get_value(pld->gpio_dpram_int)) { + intval = pld->recv_intr(pld); + if (intval == 0xDBAB) { + break; + } else { + mif_err("intr 0x%08x\n", intval); + return -1; + } + } + + usleep_range(1000, 2000); + + count++; + if (count > 200) { + mif_err("<%s:%d>\n", __func__, __LINE__); + return -1; + } + } + + iowrite16(PLD_ADDR_MASK(&qbt_map->frame_size[0]), + pld->address_buffer); + param->size = ioread16(pld->base); + + iowrite16(PLD_ADDR_MASK(&qbt_map->tag[0]), + pld->address_buffer); + param->tag = ioread16(pld->base); + + iowrite16(PLD_ADDR_MASK(&qbt_map->count[0]), + pld->address_buffer); + param->count = ioread16(pld->base); + + iowrite16(PLD_ADDR_MASK(&qbt_map->buff[0]), + pld->address_buffer); + memcpy(param->addr, pld->base, param->size); + + pld->send_intr(pld, 0xDB12); + + return retval; +} + +static int qc_uload_step1(struct pld_link_device *pld) +{ + int retval = 0; + int count = 0; + u16 intval = 0; + u16 mask = 0; + + qc_dload_map(pld, 1); + + mif_info("+---------------------------------------------+\n"); + mif_info("| UPLOAD PHONE SDRAM |\n"); + mif_info("+---------------------------------------------+\n"); + + while (1) { + if (!gpio_get_value(pld->gpio_dpram_int)) { + intval = pld->recv_intr(pld); + mif_info("intr 0x%04x\n", intval); + if (intval == 0x1234) { + break; + } else { + mif_info("ERR! invalid intr\n"); + return -1; + } + } + + usleep_range(1000, 2000); + + count++; + if (count > 200) { + intval = pld->recv_intr(pld); + mif_info("count %d, intr 0x%04x\n", count, intval); + if (intval == 0x1234) + break; + return -1; + } + } + + mask = 0xDEAD; + pld->send_intr(pld, mask); + + return retval; +} + +static int qc_uload_step2(struct pld_link_device *pld, void *arg) +{ + int retval = 0; + struct dpram_udl_param param; + + retval = copy_from_user((void *)¶m, (void *)arg, sizeof(param)); + if (retval < 0) { + mif_err("ERR! copy_from_user fail (err %d)\n", retval); + return -1; + } + + retval = _qc_data_upload(pld, ¶m); + if (retval < 0) { + mif_err("ERR! _qc_data_upload fail (err %d)\n", retval); + return -1; + } + + if (!(param.count % 500)) + mif_info("param->count = %d\n", param.count); + + if (param.tag == 4) { + enable_irq(pld->irq); + mif_info("param->tag = %d\n", param.tag); + } + + retval = copy_to_user((unsigned long *)arg, ¶m, sizeof(param)); + if (retval < 0) { + mif_err("ERR! copy_to_user fail (err %d)\n", retval); + return -1; + } + + return retval; +} + +static int qc_ioctl(struct pld_link_device *pld, struct io_device *iod, + unsigned int cmd, unsigned long arg) +{ + struct link_device *ld = &pld->ld; + int err = 0; + + switch (cmd) { + case IOCTL_DPRAM_PHONE_POWON: + err = qc_prepare_download(pld); + if (err < 0) + mif_info("%s: ERR! prepare_download fail\n", ld->name); + break; + + case IOCTL_DPRAM_PHONEIMG_LOAD: + err = qc_download_bin(pld, (void *)arg); + if (err < 0) + mif_info("%s: ERR! download_bin fail\n", ld->name); + break; + + case IOCTL_DPRAM_NVDATA_LOAD: + err = qc_download_nv(pld, (void *)arg); + if (err < 0) + mif_info("%s: ERR! download_nv fail\n", ld->name); + break; + + case IOCTL_DPRAM_PHONE_BOOTSTART: + err = qc_boot_start(pld); + if (err < 0) { + mif_info("%s: ERR! boot_start fail\n", ld->name); + break; + } + + err = qc_boot_post_process(pld); + if (err < 0) + mif_info("%s: ERR! boot_post_process fail\n", ld->name); + + break; + + case IOCTL_DPRAM_PHONE_UPLOAD_STEP1: + disable_irq_nosync(pld->irq); + err = qc_uload_step1(pld); + if (err < 0) { + enable_irq(pld->irq); + mif_info("%s: ERR! upload_step1 fail\n", ld->name); + } + break; + + case IOCTL_DPRAM_PHONE_UPLOAD_STEP2: + err = qc_uload_step2(pld, (void *)arg); + if (err < 0) { + enable_irq(pld->irq); + mif_info("%s: ERR! upload_step2 fail\n", ld->name); + } + break; + + default: + mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd); + err = -EINVAL; + break; + } + + return err; +} +#endif + +static struct pld_ext_op ext_op_set[] = { +#if defined(CONFIG_CDMA_MODEM_MDM6600) + [QC_MDM6600] = { + .exist = 1, + .init_boot_map = qc_init_boot_map, + .dload_cmd_handler = qc_dload_cmd_handler, + .cp_start_handler = qc_start_handler, + .crash_log = qc_crash_log, + .ioctl = qc_ioctl, + }, +#endif +#if defined(CONFIG_GSM_MODEM_ESC6270) + [QC_ESC6270] = { + .exist = 1, + .init_boot_map = qc_init_boot_map, + .dload_cmd_handler = qc_dload_cmd_handler, + .cp_start_handler = qc_start_handler, + .crash_log = qc_crash_log, + .ioctl = qc_ioctl, + }, +#endif +}; + +struct pld_ext_op *pld_get_ext_op(enum modem_t modem) +{ + if (ext_op_set[modem].exist) + return &ext_op_set[modem]; + else + return NULL; +} diff --git a/drivers/misc/modem_if/modem_link_device_usb.c b/drivers/misc/modem_if/modem_link_device_usb.c new file mode 100644 index 00000000000..5b7c98bebea --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_usb.c @@ -0,0 +1,1027 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define DEBUG + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/irq.h> +#include <linux/poll.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_usb.h" +#include "modem_utils.h" +#include "modem_link_pm_usb.h" + +#include <mach/regs-gpio.h> + +#define URB_COUNT 4 + +static int usb_tx_urb_with_skb(struct usb_link_device *usb_ld, + struct sk_buff *skb, struct if_usb_devdata *pipe_data); + +static void +usb_free_urbs(struct usb_link_device *usb_ld, struct if_usb_devdata *pipe) +{ + struct usb_device *usbdev = usb_ld->usbdev; + struct urb *urb; + + while ((urb = usb_get_from_anchor(&pipe->urbs))) { + usb_poison_urb(urb); + usb_free_coherent(usbdev, pipe->rx_buf_size, + urb->transfer_buffer, urb->transfer_dma); + urb->transfer_buffer = NULL; + usb_put_urb(urb); + usb_free_urb(urb); + } +} + +static int start_ipc(struct link_device *ld, struct io_device *iod) +{ + struct sk_buff *skb; + char data[1] = {'a'}; + int err; + struct usb_link_device *usb_ld = to_usb_link_device(ld); + struct if_usb_devdata *pipe_data = &usb_ld->devdata[IF_USB_FMT_EP]; + + if (has_hub(usb_ld) && usb_ld->link_pm_data->hub_handshake_done) { + mif_err("Already send start ipc, skip start ipc\n"); + err = 0; + goto exit; + } + + if (!usb_ld->if_usb_connected) { + mif_err("HSIC/USB not connected, skip start ipc\n"); + err = -ENODEV; + goto exit; + } + + if (has_hub(usb_ld) && + usb_ld->if_usb_initstates == INIT_IPC_START_DONE) { + mif_debug("Already IPC started\n"); + err = 0; + goto exit; + } + + mif_info("send 'a'\n"); + + skb = alloc_skb(16, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + memcpy(skb_put(skb, 1), data, 1); + + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = &usb_ld->ld; + err = usb_tx_urb_with_skb(usb_ld, skb, pipe_data); + if (err < 0) { + mif_err("usb_tx_urb fail\n"); + goto exit; + } + usb_ld->link_pm_data->hub_handshake_done = true; + usb_ld->if_usb_initstates = INIT_IPC_START_DONE; +exit: + return err; +} + +static int usb_init_communication(struct link_device *ld, + struct io_device *iod) +{ + int err = 0; + switch (iod->format) { + case IPC_BOOT: + ld->com_state = COM_BOOT; + skb_queue_purge(&ld->sk_fmt_tx_q); + break; + + case IPC_RAMDUMP: + ld->com_state = COM_CRASH; + break; + + case IPC_FMT: + err = start_ipc(ld, iod); + break; + + case IPC_RFS: + case IPC_RAW: + + default: + ld->com_state = COM_ONLINE; + break; + } + + mif_debug("com_state = %d\n", ld->com_state); + return err; +} + +static void usb_terminate_communication( + struct link_device *ld, struct io_device *iod) +{ + mif_debug("com_state = %d\n", ld->com_state); +} + +static int usb_rx_submit(struct if_usb_devdata *pipe, struct urb *urb, + gfp_t gfp_flags) +{ + int ret; + + usb_anchor_urb(urb, &pipe->reading); + ret = usb_submit_urb(urb, gfp_flags); + if (ret) { + usb_unanchor_urb(urb); + usb_anchor_urb(urb, &pipe->urbs); + mif_err("submit urb fail with ret (%d)\n", ret); + } + + usb_mark_last_busy(urb->dev); + return ret; +} + +static void usb_rx_complete(struct urb *urb) +{ + struct if_usb_devdata *pipe_data = urb->context; + struct usb_link_device *usb_ld = usb_get_intfdata(pipe_data->data_intf); + struct io_device *iod; + int iod_format = IPC_FMT; + int ret; + + usb_mark_last_busy(urb->dev); + + switch (urb->status) { + case 0: + case -ENOENT: + if (!urb->actual_length) + goto re_submit; + /* call iod recv */ + /* how we can distinguish boot ch with fmt ch ?? */ + switch (pipe_data->format) { + case IF_USB_FMT_EP: + iod_format = IPC_FMT; + pr_buffer("rx", (char *)urb->transfer_buffer, + (size_t)urb->actual_length, 16); + break; + case IF_USB_RAW_EP: + iod_format = IPC_MULTI_RAW; + break; + case IF_USB_RFS_EP: + iod_format = IPC_RFS; + break; + default: + break; + } + + /* during boot stage fmt end point */ + /* shared with boot io device */ + /* when we use fmt device only, at boot and ipc exchange + it can be reduced to 1 device */ + if (iod_format == IPC_FMT && + usb_ld->ld.com_state == COM_BOOT) + iod_format = IPC_BOOT; + if (iod_format == IPC_FMT && + usb_ld->ld.com_state == COM_CRASH) + iod_format = IPC_RAMDUMP; + + iod = link_get_iod_with_format(&usb_ld->ld, iod_format); + if (iod) { + ret = iod->recv(iod, + &usb_ld->ld, + (char *)urb->transfer_buffer, + urb->actual_length); + if (ret < 0) + mif_err("io device recv error :%d\n", ret); + } +re_submit: + if (urb->status || atomic_read(&usb_ld->suspend_count)) + break; + + usb_mark_last_busy(urb->dev); + usb_rx_submit(pipe_data, urb, GFP_ATOMIC); + return; + case -ESHUTDOWN: + case -EPROTO: + break; + case -EOVERFLOW: + mif_err("RX overflow\n"); + break; + default: + mif_err("RX complete Status (%d)\n", urb->status); + break; + } + + usb_anchor_urb(urb, &pipe_data->urbs); +} + +static int usb_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + struct sk_buff_head *txq; + size_t tx_size; + + if (iod->format == IPC_RAW) + txq = &ld->sk_raw_tx_q; + else + txq = &ld->sk_fmt_tx_q; + + /* store the tx size before run the tx_delayed_work*/ + tx_size = skb->len; + + /* en queue skb data */ + skb_queue_tail(txq, skb); + + queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0); + + return tx_size; +} + +static void usb_tx_complete(struct urb *urb) +{ + int ret = 0; + struct sk_buff *skb = urb->context; + + switch (urb->status) { + case 0: + break; + default: + mif_err("TX error (%d)\n", urb->status); + } + + usb_mark_last_busy(urb->dev); + ret = pm_runtime_put_autosuspend(&urb->dev->dev); + if (ret < 0 && ret != -EAGAIN) + mif_debug("pm_runtime_put_autosuspend failed: %d\n", ret); + usb_free_urb(urb); + dev_kfree_skb_any(skb); +} + +static void if_usb_force_disconnect(struct work_struct *work) +{ + struct usb_link_device *usb_ld = + container_of(work, struct usb_link_device, disconnect_work); + struct usb_device *udev = usb_ld->usbdev; + + /* if already disconnected before run this workqueue */ + if (!udev || !(&udev->dev) || !usb_ld->if_usb_connected) + return; + + /* disconnect udev's parent if usb hub used */ + if (has_hub(usb_ld)) + udev = udev->parent; + + pm_runtime_get_sync(&udev->dev); + if (udev->state != USB_STATE_NOTATTACHED) { + usb_force_disconnect(udev); + mif_info("force disconnect\n"); + } + pm_runtime_put_autosuspend(&udev->dev); +} + +static void +usb_change_modem_state(struct usb_link_device *usb_ld, enum modem_state state) +{ + struct io_device *iod; + + iod = link_get_iod_with_format(&usb_ld->ld, IPC_FMT); + if (iod) + iod->modem_state_changed(iod, state); +} + +static int usb_tx_urb_with_skb(struct usb_link_device *usb_ld, + struct sk_buff *skb, struct if_usb_devdata *pipe_data) +{ + int ret, cnt = 0; + struct urb *urb; + struct usb_device *usbdev = usb_ld->usbdev; + unsigned long flags; + + if (!usbdev || (usbdev->state == USB_STATE_NOTATTACHED) || + usb_ld->host_wake_timeout_flag) + return -ENODEV; + + pm_runtime_get_noresume(&usbdev->dev); + + if (usbdev->dev.power.runtime_status == RPM_SUSPENDED || + usbdev->dev.power.runtime_status == RPM_SUSPENDING) { + usb_ld->resume_status = AP_INITIATED_RESUME; + SET_SLAVE_WAKEUP(usb_ld->pdata, 1); + + while (!wait_event_interruptible_timeout(usb_ld->l2_wait, + usbdev->dev.power.runtime_status == RPM_ACTIVE + || pipe_data->disconnected, + HOST_WAKEUP_TIMEOUT_JIFFIES)) { + + if (cnt == MAX_RETRY) { + mif_err("host wakeup timeout !!\n"); + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + pm_runtime_put_autosuspend(&usbdev->dev); + schedule_work(&usb_ld->disconnect_work); + usb_ld->host_wake_timeout_flag = 1; + return -1; + } + mif_err("host wakeup timeout ! retry..\n"); + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + udelay(100); + SET_SLAVE_WAKEUP(usb_ld->pdata, 1); + cnt++; + } + + if (pipe_data->disconnected) { + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + pm_runtime_put_autosuspend(&usbdev->dev); + return -ENODEV; + } + + mif_debug("wait_q done (runtime_status=%d)\n", + usbdev->dev.power.runtime_status); + } + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + mif_err("alloc urb error\n"); + if (pm_runtime_put_autosuspend(&usbdev->dev) < 0) + mif_debug("pm_runtime_put_autosuspend fail\n"); + return -ENOMEM; + } + + urb->transfer_flags = URB_ZERO_PACKET; + usb_fill_bulk_urb(urb, usbdev, pipe_data->tx_pipe, skb->data, + skb->len, usb_tx_complete, (void *)skb); + + spin_lock_irqsave(&usb_ld->lock, flags); + if (atomic_read(&usb_ld->suspend_count)) { + /* transmission will be done in resume */ + usb_anchor_urb(urb, &usb_ld->deferred); + usb_put_urb(urb); + mif_debug("anchor urb (0x%p)\n", urb); + spin_unlock_irqrestore(&usb_ld->lock, flags); + return 0; + } + spin_unlock_irqrestore(&usb_ld->lock, flags); + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) { + mif_err("usb_submit_urb with ret(%d)\n", ret); + if (pm_runtime_put_autosuspend(&usbdev->dev) < 0) + mif_debug("pm_runtime_put_autosuspend fail\n"); + } + return ret; +} + +static void usb_tx_work(struct work_struct *work) +{ + int ret = 0; + struct link_device *ld = + container_of(work, struct link_device, tx_delayed_work.work); + struct usb_link_device *usb_ld = to_usb_link_device(ld); + struct io_device *iod; + struct sk_buff *skb; + struct if_usb_devdata *pipe_data; + struct link_pm_data *pm_data = usb_ld->link_pm_data; + + /*TODO: check the PHONE ACTIVE STATES */ + /* because tx data wait until hub on with wait_for_complettion, it + should queue to single_threaded work queue */ + if (!link_pm_set_active(usb_ld)) + return; + + while (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) { + /* send skb from fmt_txq and raw_txq, + * one by one for fair flow control */ + skb = skb_dequeue(&ld->sk_fmt_tx_q); + if (skb) { + iod = skbpriv(skb)->iod; + switch (iod->format) { + case IPC_BOOT: + case IPC_RAMDUMP: + case IPC_FMT: + /* boot device uses same intf with fmt*/ + pipe_data = &usb_ld->devdata[IF_USB_FMT_EP]; + break; + case IPC_RFS: + pipe_data = &usb_ld->devdata[IF_USB_RFS_EP]; + break; + default: + /* wrong packet for fmt tx q , drop it */ + dev_kfree_skb_any(skb); + continue; + } + + ret = usb_tx_urb_with_skb(usb_ld, skb, pipe_data); + if (ret < 0) { + mif_err("usb_tx_urb_with_skb, ret(%d)\n", + ret); + skb_queue_head(&ld->sk_fmt_tx_q, skb); + return; + } + } + + skb = skb_dequeue(&ld->sk_raw_tx_q); + if (skb) { + pipe_data = &usb_ld->devdata[IF_USB_RAW_EP]; + ret = usb_tx_urb_with_skb(usb_ld, skb, pipe_data); + if (ret < 0) { + mif_err("usb_tx_urb_with_skb " + "for raw, ret(%d)\n", + ret); + skb_queue_head(&ld->sk_raw_tx_q, skb); + return; + } + } + } +} + +static int if_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_link_device *usb_ld = usb_get_intfdata(intf); + struct link_pm_data *pm_data = usb_ld->link_pm_data; + int i; + + if (atomic_inc_return(&usb_ld->suspend_count) == IF_USB_DEVNUM_MAX) { + mif_debug("L2\n"); + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) + usb_kill_anchored_urbs(&usb_ld->devdata[i].reading); + + if (pm_data->freq_unlock) + pm_data->freq_unlock(&usb_ld->usbdev->dev); + + wake_unlock(&usb_ld->susplock); + } + + return 0; +} + +static void runtime_pm_work(struct work_struct *work) +{ + struct usb_link_device *usb_ld = container_of(work, + struct usb_link_device, runtime_pm_work.work); + int ret; + + ret = pm_request_autosuspend(&usb_ld->usbdev->dev); + + if (ret == -EAGAIN || ret == 1) + queue_delayed_work(system_nrt_wq, &usb_ld->runtime_pm_work, + msecs_to_jiffies(50)); +} + +static void post_resume_work(struct work_struct *work) +{ + struct usb_link_device *usb_ld = container_of(work, + struct usb_link_device, post_resume_work.work); + struct link_pm_data *pm_data = usb_ld->link_pm_data; + struct usb_device *udev = usb_ld->usbdev; + + /* if already disconnected before run this workqueue */ + if (!udev || !(&udev->dev) || !usb_ld->if_usb_connected) + return; + + /* lock cpu/bus frequency when L2->L0 */ + if (pm_data->freq_lock) + pm_data->freq_lock(&udev->dev); +} + +static void wait_enumeration_work(struct work_struct *work) +{ + struct usb_link_device *usb_ld = container_of(work, + struct usb_link_device, wait_enumeration.work); + if (usb_ld->if_usb_connected == 0) { + mif_err("USB disconnected and not enumerated for long time\n"); + usb_change_modem_state(usb_ld, STATE_CRASH_EXIT); + } +} + +static int if_usb_resume(struct usb_interface *intf) +{ + int i, ret; + struct sk_buff *skb; + struct usb_link_device *usb_ld = usb_get_intfdata(intf); + struct if_usb_devdata *pipe; + struct urb *urb; + + spin_lock_irq(&usb_ld->lock); + if (!atomic_dec_return(&usb_ld->suspend_count)) { + spin_unlock_irq(&usb_ld->lock); + + mif_debug("\n"); + wake_lock(&usb_ld->susplock); + + /* HACK: Runtime pm does not allow requesting autosuspend from + * resume callback, delayed it after resume */ + queue_delayed_work(system_nrt_wq, &usb_ld->runtime_pm_work, + msecs_to_jiffies(50)); + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + pipe = &usb_ld->devdata[i]; + while ((urb = usb_get_from_anchor(&pipe->urbs))) { + ret = usb_rx_submit(pipe, urb, GFP_KERNEL); + if (ret < 0) { + usb_put_urb(urb); + mif_err( + "usb_rx_submit error with (%d)\n", + ret); + return ret; + } + usb_put_urb(urb); + } + } + + while ((urb = usb_get_from_anchor(&usb_ld->deferred))) { + mif_debug("got urb (0x%p) from anchor & resubmit\n", + urb); + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) { + mif_err("resubmit failed\n"); + skb = urb->context; + dev_kfree_skb_any(skb); + usb_free_urb(urb); + ret = pm_runtime_put_autosuspend( + &usb_ld->usbdev->dev); + if (ret < 0 && ret != -EAGAIN) + mif_debug("pm_runtime_put_autosuspend " + "failed: %d\n", ret); + } + } + SET_SLAVE_WAKEUP(usb_ld->pdata, 1); + udelay(100); + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + + /* if_usb_resume() is atomic. post_resume_work is + * a kind of bottom halves + */ + queue_delayed_work(system_nrt_wq, &usb_ld->post_resume_work, 0); + + return 0; + } + + spin_unlock_irq(&usb_ld->lock); + return 0; +} + +static int if_usb_reset_resume(struct usb_interface *intf) +{ + int ret; + + mif_debug("\n"); + ret = if_usb_resume(intf); + return ret; +} + +static struct usb_device_id if_usb_ids[] = { + { USB_DEVICE(0x04e8, 0x6999), /* CMC221 LTE Modem */ + /*.driver_info = 0,*/ + }, + { } /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, if_usb_ids); + +static struct usb_driver if_usb_driver; +static void if_usb_disconnect(struct usb_interface *intf) +{ + struct usb_link_device *usb_ld = usb_get_intfdata(intf); + struct usb_device *usbdev = usb_ld->usbdev; + struct link_pm_data *pm_data = usb_ld->link_pm_data; + int dev_id = intf->altsetting->desc.bInterfaceNumber; + struct if_usb_devdata *pipe_data = &usb_ld->devdata[dev_id]; + + + usb_set_intfdata(intf, NULL); + + pipe_data->disconnected = 1; + smp_wmb(); + + wake_up(&usb_ld->l2_wait); + + usb_ld->if_usb_connected = 0; + usb_ld->flow_suspend = 1; + + dev_dbg(&usbdev->dev, "%s\n", __func__); + usb_ld->dev_count--; + usb_driver_release_interface(&if_usb_driver, pipe_data->data_intf); + + usb_kill_anchored_urbs(&pipe_data->reading); + usb_free_urbs(usb_ld, pipe_data); + + if (usb_ld->dev_count == 0) { + cancel_delayed_work_sync(&usb_ld->runtime_pm_work); + cancel_delayed_work_sync(&usb_ld->post_resume_work); + cancel_delayed_work_sync(&usb_ld->ld.tx_delayed_work); + usb_put_dev(usbdev); + usb_ld->usbdev = NULL; + pm_runtime_forbid(pm_data->root_hub); + } +} + +static int __devinit if_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_host_interface *data_desc; + struct usb_link_device *usb_ld = + (struct usb_link_device *)id->driver_info; + struct link_device *ld = &usb_ld->ld; + struct usb_interface *data_intf; + struct usb_device *usbdev = interface_to_usbdev(intf); + struct device *dev, *ehci_dev, *root_hub; + struct if_usb_devdata *pipe; + struct urb *urb; + int i; + int j; + int dev_id; + int err; + + /* To detect usb device order probed */ + dev_id = intf->cur_altsetting->desc.bInterfaceNumber; + + if (dev_id >= IF_USB_DEVNUM_MAX) { + dev_err(&intf->dev, "Device id %d cannot support\n", + dev_id); + return -EINVAL; + } + + if (!usb_ld) { + dev_err(&intf->dev, + "if_usb device doesn't be allocated\n"); + err = ENOMEM; + goto out; + } + + mif_info("probe dev_id=%d usb_device_id(0x%p), usb_ld (0x%p)\n", + dev_id, id, usb_ld); + + usb_ld->usbdev = usbdev; + usb_get_dev(usbdev); + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + data_intf = usb_ifnum_to_if(usbdev, i); + + /* remap endpoint of RAW to no.1 for LTE modem */ + if (i == 0) + pipe = &usb_ld->devdata[1]; + else if (i == 1) + pipe = &usb_ld->devdata[0]; + else + pipe = &usb_ld->devdata[i]; + + pipe->disconnected = 0; + pipe->data_intf = data_intf; + data_desc = data_intf->cur_altsetting; + + /* Endpoints */ + if (usb_pipein(data_desc->endpoint[0].desc.bEndpointAddress)) { + pipe->rx_pipe = usb_rcvbulkpipe(usbdev, + data_desc->endpoint[0].desc.bEndpointAddress); + pipe->tx_pipe = usb_sndbulkpipe(usbdev, + data_desc->endpoint[1].desc.bEndpointAddress); + pipe->rx_buf_size = 1024*4; + } else { + pipe->rx_pipe = usb_rcvbulkpipe(usbdev, + data_desc->endpoint[1].desc.bEndpointAddress); + pipe->tx_pipe = usb_sndbulkpipe(usbdev, + data_desc->endpoint[0].desc.bEndpointAddress); + pipe->rx_buf_size = 1024*4; + } + + if (i == 0) { + dev_info(&usbdev->dev, "USB IF USB device found\n"); + } else { + err = usb_driver_claim_interface(&if_usb_driver, + data_intf, usb_ld); + if (err < 0) { + mif_err("failed to cliam usb interface\n"); + goto out; + } + } + + usb_set_intfdata(data_intf, usb_ld); + usb_ld->dev_count++; + pm_suspend_ignore_children(&data_intf->dev, true); + + for (j = 0; j < URB_COUNT; j++) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + mif_err("alloc urb fail\n"); + err = -ENOMEM; + goto out2; + } + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = usb_alloc_coherent(usbdev, + pipe->rx_buf_size, GFP_KERNEL, + &urb->transfer_dma); + if (!urb->transfer_buffer) { + mif_err( + "Failed to allocate transfer buffer\n"); + usb_free_urb(urb); + err = -ENOMEM; + goto out2; + } + + usb_fill_bulk_urb(urb, usbdev, pipe->rx_pipe, + urb->transfer_buffer, pipe->rx_buf_size, + usb_rx_complete, pipe); + usb_anchor_urb(urb, &pipe->urbs); + } + } + + /* temporary call reset_resume */ + atomic_set(&usb_ld->suspend_count, 1); + if_usb_reset_resume(data_intf); + atomic_set(&usb_ld->suspend_count, 0); + + SET_HOST_ACTIVE(usb_ld->pdata, 1); + usb_ld->host_wake_timeout_flag = 0; + + if (gpio_get_value(usb_ld->pdata->gpio_phone_active)) { + struct link_pm_data *pm_data = usb_ld->link_pm_data; + int delay = pm_data->autosuspend_delay_ms ?: + DEFAULT_AUTOSUSPEND_DELAY_MS; + pm_runtime_set_autosuspend_delay(&usbdev->dev, delay); + dev = &usbdev->dev; + if (dev->parent) { + dev_dbg(&usbdev->dev, "if_usb Runtime PM Start!!\n"); + usb_enable_autosuspend(usb_ld->usbdev); + /* s5p-ehci runtime pm allow - usb phy suspend mode */ + root_hub = &usbdev->bus->root_hub->dev; + ehci_dev = root_hub->parent; + mif_debug("ehci device = %s, %s\n", + dev_driver_string(ehci_dev), + dev_name(ehci_dev)); + pm_runtime_allow(ehci_dev); + + if (!pm_data->autosuspend) + pm_runtime_forbid(dev); + + if (has_hub(usb_ld)) + link_pm_preactive(pm_data); + + pm_data->root_hub = root_hub; + } + + usb_ld->flow_suspend = 0; + /* Queue work if skbs were pending before a disconnect/probe */ + if (ld->sk_fmt_tx_q.qlen || ld->sk_raw_tx_q.qlen) + queue_delayed_work(ld->tx_wq, &ld->tx_delayed_work, 0); + + usb_ld->if_usb_connected = 1; + /*USB3503*/ + mif_debug("hub active complete\n"); + + usb_change_modem_state(usb_ld, STATE_ONLINE); + } else { + usb_change_modem_state(usb_ld, STATE_LOADER_DONE); + } + + /* check dynamic switching gpio received + * before usb enumeration is completed + */ + if (ld->mc->need_switch_to_usb) { + ld->mc->need_switch_to_usb = false; + rawdevs_set_tx_link(ld->msd, LINKDEV_USB); + } + + return 0; + +out2: + usb_ld->dev_count--; + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) + usb_free_urbs(usb_ld, &usb_ld->devdata[i]); +out: + usb_set_intfdata(intf, NULL); + return err; +} + +irqreturn_t usb_resume_irq(int irq, void *data) +{ + int ret; + struct usb_link_device *usb_ld = data; + int hwup; + static int wake_status = -1; + struct device *dev; + + hwup = gpio_get_value(usb_ld->pdata->gpio_host_wakeup); + if (hwup == wake_status) { + mif_err("Received spurious wake irq: %d", hwup); + return IRQ_HANDLED; + } + wake_status = hwup; + + irq_set_irq_type(irq, hwup ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); + /* + * exynos BSP has problem when using level interrupt. + * If we change irq type from interrupt handler, + * we can get level interrupt twice. + * this is temporary solution until SYS.LSI resolve this problem. + */ + __raw_writel(eint_irq_to_bit(irq), S5P_EINT_PEND(EINT_REG_NR(irq))); + wake_lock_timeout(&usb_ld->gpiolock, 100); + + mif_err("< H-WUP %d\n", hwup); + + if (!link_pm_is_connected(usb_ld)) + return IRQ_HANDLED; + + if (hwup) { + dev = &usb_ld->usbdev->dev; + mif_info("runtime status=%d\n", + dev->power.runtime_status); + + /* if usb3503 was on, usb_if was resumed by probe */ + if (has_hub(usb_ld) && + (dev->power.runtime_status == RPM_ACTIVE || + dev->power.runtime_status == RPM_RESUMING)) + return IRQ_HANDLED; + + device_lock(dev); + if (dev->power.is_prepared || dev->power.is_suspended) { + pm_runtime_get_noresume(dev); + ret = 0; + } else { + ret = pm_runtime_get_sync(dev); + } + device_unlock(dev); + if (ret < 0) { + mif_err("pm_runtime_get fail (%d)\n", ret); + return IRQ_HANDLED; + } + } else { + if (usb_ld->resume_status == AP_INITIATED_RESUME) + wake_up(&usb_ld->l2_wait); + usb_ld->resume_status = CP_INITIATED_RESUME; + pm_runtime_mark_last_busy(&usb_ld->usbdev->dev); + pm_runtime_put_autosuspend(&usb_ld->usbdev->dev); + } + + return IRQ_HANDLED; +} + +static int if_usb_init(struct usb_link_device *usb_ld) +{ + int ret; + int i; + struct if_usb_devdata *pipe; + + /* give it to probe, or global variable needed */ + if_usb_ids[0].driver_info = (unsigned long)usb_ld; + + for (i = 0; i < IF_USB_DEVNUM_MAX; i++) { + pipe = &usb_ld->devdata[i]; + pipe->format = i; + pipe->disconnected = 1; + init_usb_anchor(&pipe->urbs); + init_usb_anchor(&pipe->reading); + } + + init_waitqueue_head(&usb_ld->l2_wait); + init_usb_anchor(&usb_ld->deferred); + + ret = usb_register(&if_usb_driver); + if (ret) { + mif_err("usb_register_driver() fail : %d\n", ret); + return ret; + } + + return 0; +} + +struct link_device *usb_create_link_device(void *data) +{ + int ret; + struct modem_data *pdata; + struct platform_device *pdev = (struct platform_device *)data; + struct usb_link_device *usb_ld = NULL; + struct link_device *ld = NULL; + + pdata = pdev->dev.platform_data; + + usb_ld = kzalloc(sizeof(struct usb_link_device), GFP_KERNEL); + if (!usb_ld) + goto err; + + INIT_LIST_HEAD(&usb_ld->ld.list); + skb_queue_head_init(&usb_ld->ld.sk_fmt_tx_q); + skb_queue_head_init(&usb_ld->ld.sk_raw_tx_q); + spin_lock_init(&usb_ld->lock); + + ld = &usb_ld->ld; + usb_ld->pdata = pdata; + + ld->name = "usb"; + ld->init_comm = usb_init_communication; + ld->terminate_comm = usb_terminate_communication; + ld->send = usb_send; + ld->com_state = COM_NONE; + + /*ld->tx_wq = create_singlethread_workqueue("usb_tx_wq");*/ + ld->tx_wq = alloc_workqueue("usb_tx_wq", + WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1); + + if (!ld->tx_wq) { + mif_err("fail to create work Q.\n"); + goto err; + } + + usb_ld->pdata->irq_host_wakeup = platform_get_irq(pdev, 1); + wake_lock_init(&usb_ld->gpiolock, WAKE_LOCK_SUSPEND, + "modem_usb_gpio_wake"); + wake_lock_init(&usb_ld->susplock, WAKE_LOCK_SUSPEND, + "modem_usb_suspend_block"); + + INIT_DELAYED_WORK(&ld->tx_delayed_work, usb_tx_work); + INIT_DELAYED_WORK(&usb_ld->runtime_pm_work, runtime_pm_work); + INIT_DELAYED_WORK(&usb_ld->post_resume_work, post_resume_work); + INIT_DELAYED_WORK(&usb_ld->wait_enumeration, wait_enumeration_work); + INIT_WORK(&usb_ld->disconnect_work, if_usb_force_disconnect); + + /* create link pm device */ + ret = link_pm_init(usb_ld, data); + if (ret) + goto err; + + ret = if_usb_init(usb_ld); + if (ret) + goto err; + + return ld; +err: + if (ld && ld->tx_wq) + destroy_workqueue(ld->tx_wq); + + kfree(usb_ld); + + return NULL; +} + +static struct usb_driver if_usb_driver = { + .name = "if_usb_driver", + .probe = if_usb_probe, + .disconnect = if_usb_disconnect, + .id_table = if_usb_ids, + .suspend = if_usb_suspend, + .resume = if_usb_resume, + .reset_resume = if_usb_reset_resume, + .supports_autosuspend = 1, +}; + +static void __exit if_usb_exit(void) +{ + usb_deregister(&if_usb_driver); +} + +bool usb_is_enumerated(struct modem_shared *msd) +{ + struct link_device *ld = find_linkdev(msd, LINKDEV_USB); + if (ld) + return to_usb_link_device(ld)->usbdev != NULL; + else + return false; +} + + +/* lte specific functions */ + +static int lte_wake_resume(struct device *pdev) +{ + struct modem_data *pdata = pdev->platform_data; + int val; + + val = gpio_get_value(pdata->gpio_host_wakeup); + if (!val) { + mif_debug("> S-WUP 1\n"); + gpio_set_value(pdata->gpio_slave_wakeup, 1); + } + + return 0; +} + +static const struct dev_pm_ops lte_wake_pm_ops = { + .resume = lte_wake_resume, +}; + +static struct platform_driver lte_wake_driver = { + .driver = { + .name = "modem_lte_wake", + .pm = <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/modem_link_device_usb.h b/drivers/misc/modem_if/modem_link_device_usb.h new file mode 100644 index 00000000000..8233fd11548 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_usb.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_LINK_DEVICE_USB_H__ +#define __MODEM_LINK_DEVICE_USB_H__ + +#include <linux/usb.h> +#include <linux/wakelock.h> + +#define IF_USB_DEVNUM_MAX 3 + +#define IF_USB_FMT_EP 0 +#define IF_USB_RAW_EP 1 +#define IF_USB_RFS_EP 2 + +#define DEFAULT_AUTOSUSPEND_DELAY_MS 500 +#define HOST_WAKEUP_TIMEOUT_JIFFIES msecs_to_jiffies(500) +#define WAIT_ENUMURATION_TIMEOUT_JIFFIES msecs_to_jiffies(15000) +#define MAX_RETRY 30 + +#define IOCTL_LINK_CONTROL_ENABLE _IO('o', 0x30) +#define IOCTL_LINK_CONTROL_ACTIVE _IO('o', 0x31) +#define IOCTL_LINK_GET_HOSTWAKE _IO('o', 0x32) +#define IOCTL_LINK_CONNECTED _IO('o', 0x33) +#define IOCTL_LINK_SET_BIAS_CLEAR _IO('o', 0x34) + +#define IOCTL_LINK_PORT_ON _IO('o', 0x35) +#define IOCTL_LINK_PORT_OFF _IO('o', 0x36) + +enum RESUME_STATUS { + CP_INITIATED_RESUME, + AP_INITIATED_RESUME, +}; + +enum IPC_INIT_STATUS { + INIT_IPC_NOT_READY, + INIT_IPC_START_DONE, /* send 'a' done */ +}; + +enum hub_status { + HUB_STATE_OFF, /* usb3503 0ff*/ + HUB_STATE_RESUMMING, /* usb3503 on, but enummerattion was not yet*/ + HUB_STATE_ACTIVE, /* hub and CMC221 enumerate */ +}; + +struct if_usb_devdata { + struct usb_interface *data_intf; + unsigned int tx_pipe; + unsigned int rx_pipe; + u8 disconnected; + + int format; + struct usb_anchor urbs; + struct usb_anchor reading; + unsigned int rx_buf_size; +}; + +struct usb_link_device { + /*COMMON LINK DEVICE*/ + struct link_device ld; + + struct modem_data *pdata; + + /*USB SPECIFIC LINK DEVICE*/ + struct usb_device *usbdev; + struct if_usb_devdata devdata[IF_USB_DEVNUM_MAX]; + struct delayed_work runtime_pm_work; + struct delayed_work post_resume_work; + struct delayed_work wait_enumeration; + struct work_struct disconnect_work; + + struct wake_lock gpiolock; + struct wake_lock susplock; + + unsigned int dev_count; + unsigned int suspended; + atomic_t suspend_count; + enum RESUME_STATUS resume_status; + int if_usb_connected; + int if_usb_initstates; + int flow_suspend; + int host_wake_timeout_flag; + + unsigned gpio_slave_wakeup; + unsigned gpio_host_wakeup; + unsigned gpio_host_active; + int irq_host_wakeup; + struct delayed_work dwork; + struct work_struct resume_work; + int cpcrash_flag; + wait_queue_head_t l2_wait; + + spinlock_t lock; + struct usb_anchor deferred; + + /* LINK PM DEVICE DATA */ + struct link_pm_data *link_pm_data; +}; +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_usb_link_device(linkdev) \ + container_of(linkdev, struct usb_link_device, ld) + +#define SET_SLAVE_WAKEUP(_pdata, _value) \ +do { \ + gpio_set_value(_pdata->gpio_slave_wakeup, _value); \ + mif_debug("> S-WUP %s\n", _value ? "1" : "0"); \ +} while (0) + +#define SET_HOST_ACTIVE(_pdata, _value) \ +do { \ + gpio_set_value(_pdata->gpio_host_active, _value); \ + mif_debug("> H-ACT %s\n", _value ? "1" : "0"); \ +} while (0) + +#define has_hub(usb_ld) ((usb_ld)->link_pm_data->has_usbhub) + +irqreturn_t usb_resume_irq(int irq, void *data); +bool usb_is_enumerated(struct modem_shared *msd); + +#endif diff --git a/drivers/misc/modem_if/modem_link_pm_usb.c b/drivers/misc/modem_if/modem_link_pm_usb.c new file mode 100644 index 00000000000..1b2614eb58d --- /dev/null +++ b/drivers/misc/modem_if/modem_link_pm_usb.c @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2012 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define DEBUG + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/irq.h> +#include <linux/poll.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> + +#include "modem_link_pm_usb.h" + +static inline void start_hub_work(struct link_pm_data *pm_data, int delay) +{ + if (pm_data->hub_work_running == false) { + pm_data->hub_work_running = true; + wake_lock(&pm_data->hub_lock); + mif_debug("link_pm_hub_work is started\n"); + } + + schedule_delayed_work(&pm_data->link_pm_hub, msecs_to_jiffies(delay)); +} + +static inline void end_hub_work(struct link_pm_data *pm_data) +{ + wake_unlock(&pm_data->hub_lock); + pm_data->hub_work_running = false; + mif_debug("link_pm_hub_work is done\n"); +} + +bool link_pm_is_connected(struct usb_link_device *usb_ld) +{ + if (has_hub(usb_ld)) { + struct link_pm_data *pm_data = usb_ld->link_pm_data; + if (pm_data->hub_init_lock) + return false; + + if (pm_data->hub_status == HUB_STATE_OFF) { + if (pm_data->hub_work_running == false) + start_hub_work(pm_data, 0); + return false; + } + } + + if (!usb_ld->if_usb_connected) { + mif_err("mif: if not connected\n"); + return false; + } + + return true; +} + +void link_pm_preactive(struct link_pm_data *pm_data) +{ + if (pm_data->root_hub) { + mif_info("pre-active\n"); + pm_data->hub_on_retry_cnt = 0; + complete(&pm_data->hub_active); + pm_runtime_put_sync(pm_data->root_hub); + } + + pm_data->hub_status = HUB_STATE_ACTIVE; +} + +static void link_pm_hub_work(struct work_struct *work) +{ + int err; + struct link_pm_data *pm_data = + container_of(work, struct link_pm_data, link_pm_hub.work); + + if (pm_data->hub_status == HUB_STATE_ACTIVE) { + end_hub_work(pm_data); + return; + } + + if (!pm_data->port_enable) { + mif_err("mif: hub power func not assinged\n"); + end_hub_work(pm_data); + return; + } + + /* If kernel if suspend, wait the ehci resume */ + if (pm_data->dpm_suspending) { + mif_info("dpm_suspending\n"); + start_hub_work(pm_data, 500); + return; + } + + switch (pm_data->hub_status) { + case HUB_STATE_OFF: + pm_data->hub_status = HUB_STATE_RESUMMING; + mif_trace("hub off->on\n"); + + /* skip 1st time before first probe */ + if (pm_data->root_hub) + pm_runtime_get_sync(pm_data->root_hub); + err = pm_data->port_enable(2, 1); + if (err < 0) { + mif_err("hub on fail err=%d\n", err); + err = pm_data->port_enable(2, 0); + if (err < 0) + mif_err("hub off fail err=%d\n", err); + pm_data->hub_status = HUB_STATE_OFF; + if (pm_data->root_hub) + pm_runtime_put_sync(pm_data->root_hub); + end_hub_work(pm_data); + } else { + /* resume root hub */ + start_hub_work(pm_data, 100); + } + break; + case HUB_STATE_RESUMMING: + if (pm_data->hub_on_retry_cnt++ > 50) { + pm_data->hub_on_retry_cnt = 0; + pm_data->hub_status = HUB_STATE_OFF; + if (pm_data->root_hub) + pm_runtime_put_sync(pm_data->root_hub); + end_hub_work(pm_data); + } else { + mif_info("hub resumming: %d\n", + pm_data->hub_on_retry_cnt); + start_hub_work(pm_data, 200); + } + break; + } +exit: + return; +} + +static int link_pm_hub_standby(void *args) +{ + struct link_pm_data *pm_data = args; + struct usb_link_device *usb_ld = pm_data->usb_ld; + int err = 0; + + if (!pm_data->port_enable) { + mif_err("port power func not assinged\n"); + return -ENODEV; + } + + err = pm_data->port_enable(2, 0); + if (err < 0) + mif_err("hub off fail err=%d\n", err); + + pm_data->hub_status = HUB_STATE_OFF; + + /* this function is atomic. + * make force disconnect in workqueue.. + */ + if (pm_data->usb_ld->if_usb_connected) + schedule_work(&usb_ld->disconnect_work); + + return err; +} + +bool link_pm_set_active(struct usb_link_device *usb_ld) +{ + int ret; + struct link_pm_data *pm_data = usb_ld->link_pm_data; + + if (has_hub(usb_ld)) { + if (pm_data->hub_status != HUB_STATE_ACTIVE) { + INIT_COMPLETION(pm_data->hub_active); + SET_SLAVE_WAKEUP(usb_ld->pdata, 1); + ret = wait_for_completion_timeout(&pm_data->hub_active, + msecs_to_jiffies(2000)); + if (!ret) { /*timeout*/ + mif_err("hub on timeout - retry\n"); + SET_SLAVE_WAKEUP(usb_ld->pdata, 0); + queue_delayed_work(usb_ld->ld.tx_wq, + &usb_ld->ld.tx_delayed_work, 0); + return false; + } + } + } else { + /* TODO do something */ + } + return true; +} + +static long link_pm_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int value, err = 0; + struct link_pm_data *pm_data = file->private_data; + struct usb_link_device *usb_ld = pm_data->usb_ld; + + mif_info("cmd: 0x%08x\n", cmd); + + switch (cmd) { + case IOCTL_LINK_CONTROL_ACTIVE: + if (copy_from_user(&value, (const void __user *)arg, + sizeof(int))) + return -EFAULT; + gpio_set_value(pm_data->gpio_link_active, value); + break; + case IOCTL_LINK_GET_HOSTWAKE: + return !gpio_get_value(pm_data->gpio_link_hostwake); + case IOCTL_LINK_CONNECTED: + return usb_ld->if_usb_connected; + case IOCTL_LINK_PORT_ON: + /* ignore cp host wakeup irq, set the hub_init_lock when AP try + CP off and release hub_init_lock when CP boot done */ + pm_data->hub_init_lock = 0; + if (pm_data->root_hub) + pm_runtime_get_sync(pm_data->root_hub); + if (pm_data->port_enable) { + err = pm_data->port_enable(2, 1); + if (err < 0) { + mif_err("hub on fail err=%d\n", err); + goto exit; + } + pm_data->hub_status = HUB_STATE_RESUMMING; + } + break; + case IOCTL_LINK_PORT_OFF: + err = link_pm_hub_standby(pm_data); + if (err < 0) { + mif_err("usb3503 active fail\n"); + goto exit; + } + pm_data->hub_init_lock = 1; + pm_data->hub_handshake_done = 0; + break; + default: + break; + } +exit: + return err; +} + +static ssize_t show_autosuspend(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char *p = buf; + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct link_pm_data *pm_data = container_of(miscdev, + struct link_pm_data, miscdev); + struct usb_link_device *usb_ld = pm_data->usb_ld; + + p += sprintf(buf, "%s\n", pm_data->autosuspend ? "on" : "off"); + + return p - buf; +} + +static ssize_t store_autosuspend(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct link_pm_data *pm_data = container_of(miscdev, + struct link_pm_data, miscdev); + struct usb_link_device *usb_ld = pm_data->usb_ld; + struct task_struct *task = get_current(); + char taskname[TASK_COMM_LEN]; + + mif_info("autosuspend: %s: %s(%d)'\n", + buf, get_task_comm(taskname, task), task->pid); + + if (!strncmp(buf, "on", 2)) { + pm_data->autosuspend = true; + if (usb_ld->usbdev) + pm_runtime_allow(&usb_ld->usbdev->dev); + } else if (!strncmp(buf, "off", 3)) { + pm_data->autosuspend = false; + if (usb_ld->usbdev) + pm_runtime_forbid(&usb_ld->usbdev->dev); + } + + return count; +} + +static struct device_attribute attr_autosuspend = + __ATTR(autosuspend, S_IRUGO | S_IWUSR, + show_autosuspend, store_autosuspend); + +static int link_pm_open(struct inode *inode, struct file *file) +{ + struct link_pm_data *pm_data = + (struct link_pm_data *)file->private_data; + file->private_data = (void *)pm_data; + return 0; +} + +static int link_pm_release(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + return 0; +} + +static const struct file_operations link_pm_fops = { + .owner = THIS_MODULE, + .open = link_pm_open, + .release = link_pm_release, + .unlocked_ioctl = link_pm_ioctl, +}; + +static int link_pm_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct link_pm_data *pm_data = + container_of(this, struct link_pm_data, pm_notifier); + struct usb_link_device *usb_ld = pm_data->usb_ld; + + switch (event) { + case PM_SUSPEND_PREPARE: + pm_data->dpm_suspending = true; + if (has_hub(usb_ld)) + link_pm_hub_standby(pm_data); + return NOTIFY_OK; + case PM_POST_SUSPEND: + pm_data->dpm_suspending = false; + return NOTIFY_OK; + } + return NOTIFY_DONE; +} + +int link_pm_init(struct usb_link_device *usb_ld, void *data) +{ + int err; + int irq; + struct platform_device *pdev = (struct platform_device *)data; + struct modem_data *pdata = + (struct modem_data *)pdev->dev.platform_data; + struct modemlink_pm_data *pm_pdata = pdata->link_pm_data; + struct link_pm_data *pm_data = + kzalloc(sizeof(struct link_pm_data), GFP_KERNEL); + if (!pm_data) { + mif_err("link_pm_data is NULL\n"); + return -ENOMEM; + } + /* get link pm data from modemcontrol's platform data */ + pm_data->gpio_link_active = pm_pdata->gpio_link_active; + pm_data->gpio_link_hostwake = pm_pdata->gpio_link_hostwake; + pm_data->gpio_link_slavewake = pm_pdata->gpio_link_slavewake; + pm_data->link_reconnect = pm_pdata->link_reconnect; + pm_data->port_enable = pm_pdata->port_enable; + pm_data->freq_lock = pm_pdata->freq_lock; + pm_data->freq_unlock = pm_pdata->freq_unlock; + pm_data->autosuspend_delay_ms = pm_pdata->autosuspend_delay_ms; + pm_data->autosuspend = true; + + pm_data->usb_ld = usb_ld; + usb_ld->link_pm_data = pm_data; + + pm_data->miscdev.minor = MISC_DYNAMIC_MINOR; + pm_data->miscdev.name = "link_pm"; + pm_data->miscdev.fops = &link_pm_fops; + + err = misc_register(&pm_data->miscdev); + if (err < 0) { + mif_err("fail to register pm device(%d)\n", err); + goto err_misc_register; + } + + err = device_create_file(pm_data->miscdev.this_device, + &attr_autosuspend); + if (err) { + mif_err("fail to create file: autosuspend: %d\n", err); + goto err_create_file; + } + + pm_data->hub_init_lock = 1; + irq = gpio_to_irq(usb_ld->pdata->gpio_host_wakeup); + err = request_threaded_irq(irq, NULL, usb_resume_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "modem_usb_wake", usb_ld); + if (err) { + mif_err("Failed to allocate an interrupt(%d)\n", irq); + goto err_request_irq; + } + enable_irq_wake(irq); + + pm_data->has_usbhub = pm_pdata->has_usbhub; + + if (has_hub(usb_ld)) { + init_completion(&pm_data->hub_active); + pm_data->hub_status = HUB_STATE_OFF; + pm_data->hub_handshake_done = 0; + pm_data->root_hub = NULL; + + pm_pdata->hub_standby = link_pm_hub_standby; + pm_pdata->hub_pm_data = pm_data; + + wake_lock_init(&pm_data->hub_lock, WAKE_LOCK_SUSPEND, + "modem_hub_enum_lock"); + INIT_DELAYED_WORK(&pm_data->link_pm_hub, link_pm_hub_work); + pm_data->hub_work_running = false; + } + + pm_data->pm_notifier.notifier_call = link_pm_notifier_event; + register_pm_notifier(&pm_data->pm_notifier); + + return 0; + +err_request_irq: +err_create_file: + misc_deregister(&pm_data->miscdev); +err_misc_register: + kfree(pm_data); + return err; +} diff --git a/drivers/misc/modem_if/modem_link_pm_usb.h b/drivers/misc/modem_if/modem_link_pm_usb.h new file mode 100644 index 00000000000..d26af761114 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_pm_usb.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2012 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_LINK_PM_USB_H__ +#define __MODEM_LINK_PM_USB_H__ + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_usb.h" + +struct link_pm_data { + struct miscdevice miscdev; + struct usb_link_device *usb_ld; + unsigned gpio_link_active; + unsigned gpio_link_hostwake; + unsigned gpio_link_slavewake; + int (*link_reconnect)(void); + int link_reconnect_cnt; + + struct workqueue_struct *wq; + struct completion active_done; + +/*USB3503*/ + struct completion hub_active; + int hub_status; + bool has_usbhub; + /* ignore hub on by host wakeup irq before cp power on*/ + int hub_init_lock; + /* C1 stay disconnect status after send 'a', skip 'a' next enumeration*/ + int hub_handshake_done; + struct wake_lock hub_lock; + struct delayed_work link_pm_hub; + bool hub_work_running; + int hub_on_retry_cnt; + struct device *root_hub; + + struct notifier_block pm_notifier; + bool dpm_suspending; + + int (*port_enable)(int, int); + + int (*freq_lock)(struct device *dev); + int (*freq_unlock)(struct device *dev); + + int autosuspend_delay_ms; /* if zero, the default value is used */ + bool autosuspend; +}; + +bool link_pm_set_active(struct usb_link_device *usb_ld); +bool link_pm_is_connected(struct usb_link_device *usb_ld); +void link_pm_preactive(struct link_pm_data *pm_data); +int link_pm_init(struct usb_link_device *usb_ld, void *data); + +#endif diff --git a/drivers/misc/modem_if/modem_modemctl_device_cbp71.c b/drivers/misc/modem_if/modem_modemctl_device_cbp71.c new file mode 100644 index 00000000000..28f2ce7736f --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_cbp71.c @@ -0,0 +1,233 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_cbp7.1.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> + +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_dpram.h" + +#define PIF_TIMEOUT (180 * HZ) +#define DPRAM_INIT_TIMEOUT (15 * HZ) + +static int cbp71_on(struct modem_ctl *mc) +{ + int RetVal = 0; + int dpram_init_RetVal = 0; + struct link_device *ld = get_current_link(mc->iod); + struct dpram_link_device *dpram_ld = to_dpram_link_device(ld); + + mif_info("cbp71_on()\n"); + + if (!mc->gpio_cp_off || !mc->gpio_cp_reset) { + mif_err("no gpio data\n"); + return -ENXIO; + } + gpio_set_value(mc->gpio_cp_reset, 0); + msleep(600); + gpio_set_value(mc->gpio_cp_reset, 1); + msleep(100); + gpio_set_value(mc->gpio_cp_off, 0); + msleep(300); + + gpio_set_value(mc->gpio_pda_active, 1); + + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + + /* Wait here until the PHONE is up. + * Waiting as the this called from IOCTL->UM thread */ + mif_debug("power control waiting for INT_MASK_CMD_PIF_INIT_DONE\n"); + + /* 1HZ = 1 clock tick, 100 default */ + dpram_ld->clear_interrupt(dpram_ld); + + dpram_init_RetVal = + wait_event_interruptible_timeout( + dpram_ld->dpram_init_cmd_wait_q, + dpram_ld->dpram_init_cmd_wait_condition, + DPRAM_INIT_TIMEOUT); + + if (!dpram_init_RetVal) { + /*RetVal will be 0 on timeout, non zero if interrupted */ + mif_err("INIT_START cmd was not arrived.\n"); + mif_err("init_cmd_wait_condition is 0 and wait timeout happend\n"); + return -ENXIO; + } + + RetVal = wait_event_interruptible_timeout( + dpram_ld->modem_pif_init_done_wait_q, + dpram_ld->modem_pif_init_wait_condition, + PIF_TIMEOUT); + + if (!RetVal) { + /*RetVal will be 0 on timeout, non zero if interrupted */ + mif_err("PIF init failed\n"); + mif_err("pif_init_wait_condition is 0 and wait timeout happend\n"); + return -ENXIO; + } + + mif_debug("complete cbp71_on\n"); + + mc->iod->modem_state_changed(mc->iod, STATE_ONLINE); + + return 0; +} + +static int cbp71_off(struct modem_ctl *mc) +{ + mif_debug("cbp71_off()\n"); + + if (!mc->gpio_cp_off || !mc->gpio_cp_reset) { + mif_err("no gpio data\n"); + return -ENXIO; + } + + mif_err("Phone power Off. - do nothing\n"); + + mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE); + + return 0; +} + +static int cbp71_reset(struct modem_ctl *mc) +{ + int ret = 0; + + mif_debug("cbp71_reset()\n"); + + ret = cbp71_off(mc); + if (ret) + return -ENXIO; + + msleep(100); + + ret = cbp71_on(mc); + if (ret) + return -ENXIO; + + return 0; +} + +static int cbp71_boot_on(struct modem_ctl *mc) +{ + mif_debug("cbp71_boot_on()\n"); + + if (!mc->gpio_cp_reset) { + mif_err("no gpio data\n"); + return -ENXIO; + } + gpio_set_value(mc->gpio_cp_reset, 0); + msleep(600); + gpio_set_value(mc->gpio_cp_reset, 1); + + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + + return 0; +} + +static int cbp71_boot_off(struct modem_ctl *mc) +{ + mif_debug("cbp71_boot_off()\n"); + return 0; +} + +static irqreturn_t phone_active_irq_handler(int irq, void *_mc) +{ + int phone_reset = 0; + int phone_active_value = 0; + int phone_state = 0; + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + if (!mc->gpio_cp_reset || !mc->gpio_phone_active) { + mif_err("no gpio data\n"); + return IRQ_HANDLED; + } + + phone_reset = gpio_get_value(mc->gpio_cp_reset); + phone_active_value = gpio_get_value(mc->gpio_phone_active); + + if (phone_reset && phone_active_value) + phone_state = STATE_ONLINE; + else if (phone_reset && !phone_active_value) + phone_state = STATE_CRASH_EXIT; + else + phone_state = STATE_OFFLINE; + + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + + if (phone_active_value) + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_LOW); + else + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_HIGH); + + mif_info("phone_active_irq_handler : phone_state=%d\n", phone_state); + + return IRQ_HANDLED; +} + +static void cbp71_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = cbp71_on; + mc->ops.modem_off = cbp71_off; + mc->ops.modem_reset = cbp71_reset; + mc->ops.modem_boot_on = cbp71_boot_on; + mc->ops.modem_boot_off = cbp71_boot_off; +} + +int cbp71_init_modemctl_device(struct modem_ctl *mc, + struct modem_data *pdata) +{ + int ret = 0; + struct platform_device *pdev; + + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_reset_req_n = pdata->gpio_reset_req_n; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_pda_active = pdata->gpio_pda_active; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + mc->gpio_cp_off = pdata->gpio_cp_off; + + pdev = to_platform_device(mc->dev); + mc->irq_phone_active = platform_get_irq(pdev, 0); + + cbp71_get_ops(mc); + + /*TODO: check*/ + ret = request_irq(mc->irq_phone_active, phone_active_irq_handler, + IRQF_TRIGGER_HIGH, "phone_active", mc); + if (ret) { + mif_err("failed to irq_phone_active request_irq: %d\n" + , ret); + return ret; + } + + ret = enable_irq_wake(mc->irq_phone_active); + if (ret) + mif_err("failed to enable_irq_wake:%d\n", ret); + + return ret; +} diff --git a/drivers/misc/modem_if/modem_modemctl_device_cbp72.c b/drivers/misc/modem_if/modem_modemctl_device_cbp72.c new file mode 100644 index 00000000000..2617be8abaa --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_cbp72.c @@ -0,0 +1,273 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_cbp7.1.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> + +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/platform_device.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_dpram.h" + +#define PIF_TIMEOUT (180 * HZ) +#define DPRAM_INIT_TIMEOUT (30 * HZ) + + +static irqreturn_t phone_active_handler(int irq, void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + int phone_reset = gpio_get_value(mc->gpio_cp_reset); + int phone_active = gpio_get_value(mc->gpio_phone_active); + int phone_state = mc->phone_state; + + mif_info("state = %d, phone_reset = %d, phone_active = %d\n", + phone_state, phone_reset, phone_active); + + if (phone_reset && phone_active) { + if (mc->phone_state == STATE_BOOTING) { + phone_state = STATE_ONLINE; + mc->bootd->modem_state_changed(mc->bootd, phone_state); + } + } else if (phone_reset && !phone_active) { + if (mc->phone_state == STATE_ONLINE) { + phone_state = STATE_CRASH_EXIT; + mc->bootd->modem_state_changed(mc->bootd, phone_state); + } + } else { + phone_state = STATE_OFFLINE; + if (mc->bootd && mc->bootd->modem_state_changed) + mc->bootd->modem_state_changed(mc->bootd, phone_state); + } + + if (phone_active) + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_LOW); + else + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_HIGH); + + mif_info("phone_state = %d\n", phone_state); + + return IRQ_HANDLED; +} + +static int cbp72_on(struct modem_ctl *mc) +{ + mif_info("start!!!\n"); + + /* prevent sleep during bootloader downloading */ + if (!wake_lock_active(&mc->mc_wake_lock)) + wake_lock(&mc->mc_wake_lock); + + gpio_set_value(mc->gpio_cp_on, 0); + if (mc->gpio_cp_off) + gpio_set_value(mc->gpio_cp_off, 1); + gpio_set_value(mc->gpio_cp_reset, 0); + + msleep(500); + + gpio_set_value(mc->gpio_cp_on, 1); + if (mc->gpio_cp_off) + gpio_set_value(mc->gpio_cp_off, 0); + + msleep(100); + + gpio_set_value(mc->gpio_cp_reset, 1); + + msleep(300); + + gpio_set_value(mc->gpio_pda_active, 1); + + mc->bootd->modem_state_changed(mc->bootd, STATE_BOOTING); + + mif_info("complete!!!\n"); + + return 0; +} + +static int cbp72_off(struct modem_ctl *mc) +{ + mif_info("cbp72_off()\n"); + + if (!mc->gpio_cp_off || !mc->gpio_cp_reset) { + mif_err("no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_off, 1); + + mc->bootd->modem_state_changed(mc->bootd, STATE_OFFLINE); + + return 0; +} + +static int cbp72_reset(struct modem_ctl *mc) +{ + int ret = 0; + + mif_debug("cbp72_reset()\n"); + + ret = cbp72_off(mc); + if (ret) + return -ENXIO; + + msleep(100); + + ret = cbp72_on(mc); + if (ret) + return -ENXIO; + + return 0; +} + +static int cbp72_boot_on(struct modem_ctl *mc) +{ + mif_info("\n"); + + if (!mc->gpio_cp_reset) { + mif_err("no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_reset, 0); + + msleep(600); + + gpio_set_value(mc->gpio_cp_reset, 1); + + mc->bootd->modem_state_changed(mc->bootd, STATE_BOOTING); + + return 0; +} + +static int cbp72_boot_off(struct modem_ctl *mc) +{ + int ret; + struct link_device *ld = get_current_link(mc->bootd); + struct dpram_link_device *dpld = to_dpram_link_device(ld); + mif_debug("\n"); + /* Wait here until the PHONE is up. + * Waiting as the this called from IOCTL->UM thread */ + mif_info("Waiting for INT_CMD_PHONE_START\n"); + ret = wait_for_completion_interruptible_timeout( + &dpld->dpram_init_cmd, DPRAM_INIT_TIMEOUT); + if (!ret) { + /* ret == 0 on timeout, ret < 0 if interrupted */ + mif_err("Timeout!!! (PHONE_START was not arrived.)\n"); + return -ENXIO; + } + + mif_info("Waiting for INT_CMD_PIF_INIT_DONE\n"); + ret = wait_for_completion_interruptible_timeout( + &dpld->modem_pif_init_done, PIF_TIMEOUT); + if (!ret) { + mif_err("Timeout!!! (PIF_INIT_DONE was not arrived.)\n"); + return -ENXIO; + } + mc->bootd->modem_state_changed(mc->bootd, STATE_ONLINE); + + wake_unlock(&mc->mc_wake_lock); + + return 0; +} + +static int cbp72_force_crash_exit(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->bootd); + + mif_err("device = %s\n", mc->bootd->name); + + /* Make DUMP start */ + ld->force_dump(ld, mc->bootd); + + msleep_interruptible(1000); + + mc->bootd->modem_state_changed(mc->bootd, STATE_CRASH_EXIT); + + return 0; +} + +static void cbp72_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = cbp72_on; + mc->ops.modem_off = cbp72_off; + mc->ops.modem_reset = cbp72_reset; + mc->ops.modem_boot_on = cbp72_boot_on; + mc->ops.modem_boot_off = cbp72_boot_off; + mc->ops.modem_force_crash_exit = cbp72_force_crash_exit; +} + +int cbp72_init_modemctl_device(struct modem_ctl *mc, struct modem_data *pdata) +{ + int ret = 0; + int irq = 0; + unsigned long flag = 0; + struct platform_device *pdev = NULL; + + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_cp_off = pdata->gpio_cp_off; + mc->gpio_reset_req_n = pdata->gpio_reset_req_n; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_pda_active = pdata->gpio_pda_active; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + + if (!mc->gpio_cp_on || !mc->gpio_cp_reset || !mc->gpio_phone_active) { + mif_err("no GPIO data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_reset, 0); + if (mc->gpio_cp_off) + gpio_set_value(mc->gpio_cp_off, 1); + gpio_set_value(mc->gpio_cp_on, 0); + + cbp72_get_ops(mc); + + pdev = to_platform_device(mc->dev); + mc->irq_phone_active = platform_get_irq_byname(pdev, "cp_active_irq"); + if (!mc->irq_phone_active) { + mif_err("get irq fail\n"); + return -1; + } + + irq = mc->irq_phone_active; + mif_info("PHONE_ACTIVE IRQ# = %d\n", irq); + + flag = IRQF_TRIGGER_HIGH; + ret = request_irq(irq, phone_active_handler, flag, "cbp_active", mc); + if (ret) { + mif_err("request_irq fail (%d)\n", ret); + return ret; + } + + wake_lock_init(&mc->mc_wake_lock, WAKE_LOCK_SUSPEND, "cbp72_wake_lock"); + + ret = enable_irq_wake(irq); + if (ret) + mif_err("enable_irq_wake fail (%d)\n", ret); + + return 0; +} + diff --git a/drivers/misc/modem_if/modem_modemctl_device_cmc221.c b/drivers/misc/modem_if/modem_modemctl_device_cmc221.c new file mode 100644 index 00000000000..eddc91ae15b --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_cmc221.c @@ -0,0 +1,317 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_cmc221.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/init.h> + +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/platform_device.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_link_device_usb.h" +#include "modem_link_device_dpram.h" +#include "modem_utils.h" + +#define PIF_TIMEOUT (180 * HZ) +#define DPRAM_INIT_TIMEOUT (30 * HZ) + +static void mc_state_fsm(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->iod); + int cp_on = gpio_get_value(mc->gpio_cp_on); + int cp_reset = gpio_get_value(mc->gpio_cp_reset); + int cp_active = gpio_get_value(mc->gpio_phone_active); + int old_state = mc->phone_state; + int new_state = mc->phone_state; + + mif_err("%s: old_state:%d cp_on:%d cp_reset:%d cp_active:%d\n", + mc->name, old_state, cp_on, cp_reset, cp_active); + + if (!cp_active) { + if (!cp_on) { + gpio_set_value(mc->gpio_cp_reset, 0); + new_state = STATE_OFFLINE; + ld->mode = LINK_MODE_OFFLINE; + mif_err("%s: new_state = PHONE_PWR_OFF\n", mc->name); + } else if (old_state == STATE_ONLINE) { + new_state = STATE_CRASH_EXIT; + mif_err("%s: new_state = CRASH_EXIT\n", mc->name); + } else { + mif_err("%s: Don't care!!!\n", mc->name); + } + } + + if (old_state != new_state) { + mc->bootd->modem_state_changed(mc->bootd, new_state); + mc->iod->modem_state_changed(mc->iod, new_state); + } +} + +static irqreturn_t phone_active_handler(int irq, void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + int cp_reset = gpio_get_value(mc->gpio_cp_reset); + + if (cp_reset) + mc_state_fsm(mc); + + return IRQ_HANDLED; +} + +/* TX dynamic switching between DPRAM and USB in one modem */ +static irqreturn_t dynamic_switching_handler(int irq, void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + int txpath = gpio_get_value(mc->gpio_dynamic_switching); + bool enumerated = usb_is_enumerated(mc->msd); + + mif_err("txpath=%d, enumeration=%d\n", txpath, enumerated); + + /* do not switch to USB, when USB is not enumerated. */ + if (!enumerated && txpath) { + mc->need_switch_to_usb = true; + return IRQ_HANDLED; + } + + mc->need_switch_to_usb = false; + rawdevs_set_tx_link(mc->msd, txpath ? LINKDEV_USB : LINKDEV_DPRAM); + + return IRQ_HANDLED; +} + +static int cmc221_on(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->iod); + + if (!wake_lock_active(&mc->mc_wake_lock)) + wake_lock(&mc->mc_wake_lock); + set_sromc_access(true); + + mc->phone_state = STATE_OFFLINE; + ld->mode = LINK_MODE_OFFLINE; + + mif_err("%s\n", mc->name); + + disable_irq_nosync(mc->irq_phone_active); + + gpio_set_value(mc->gpio_cp_on, 0); + msleep(100); + + gpio_set_value(mc->gpio_cp_reset, 0); + msleep(500); + + gpio_set_value(mc->gpio_cp_on, 1); + msleep(100); + + gpio_set_value(mc->gpio_cp_reset, 1); + + return 0; +} + +static int cmc221_off(struct modem_ctl *mc) +{ + int cp_on = gpio_get_value(mc->gpio_cp_on); + + mif_err("%s\n", mc->name); + + if (mc->phone_state == STATE_OFFLINE || cp_on == 0) + return 0; + + if (!wake_lock_active(&mc->mc_wake_lock)) + wake_lock(&mc->mc_wake_lock); + set_sromc_access(true); + + gpio_set_value(mc->gpio_cp_on, 0); + + return 0; +} + +static int cmc221_force_crash_exit(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->bootd); + + mif_err("%s\n", mc->name); + + /* Make DUMP start */ + ld->force_dump(ld, mc->bootd); + + return 0; +} + +static int cmc221_dump_reset(struct modem_ctl *mc) +{ + mif_err("%s\n", mc->name); + + if (!wake_lock_active(&mc->mc_wake_lock)) + wake_lock(&mc->mc_wake_lock); + set_sromc_access(true); + + gpio_set_value(mc->gpio_host_active, 0); + gpio_set_value(mc->gpio_cp_reset, 0); + + udelay(200); + + gpio_set_value(mc->gpio_cp_reset, 1); + + msleep(300); + + return 0; +} + +static int cmc221_reset(struct modem_ctl *mc) +{ + mif_err("%s\n", mc->name); + + if (cmc221_off(mc)) + return -ENXIO; + + msleep(100); + + if (cmc221_on(mc)) + return -ENXIO; + + return 0; +} + +static int cmc221_boot_on(struct modem_ctl *mc) +{ + mif_err("%s\n", mc->name); + + gpio_set_value(mc->gpio_pda_active, 1); + + mc->bootd->modem_state_changed(mc->bootd, STATE_BOOTING); + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + + return 0; +} + +static int cmc221_boot_off(struct modem_ctl *mc) +{ + int ret; + struct link_device *ld = get_current_link(mc->bootd); + struct dpram_link_device *dpld = to_dpram_link_device(ld); + + mif_err("%s\n", mc->name); + + ret = wait_for_completion_interruptible_timeout(&dpld->dpram_init_cmd, + DPRAM_INIT_TIMEOUT); + if (!ret) { + /* ret == 0 on timeout, ret < 0 if interrupted */ + mif_err("%s: ERR! timeout (CP_START not arrived)\n", mc->name); + return -ENXIO; + } + + enable_irq(mc->irq_phone_active); + + return 0; +} + +static int cmc221_boot_done(struct modem_ctl *mc) +{ + mif_err("%s\n", mc->name); + + set_sromc_access(false); + if (wake_lock_active(&mc->mc_wake_lock)) + wake_unlock(&mc->mc_wake_lock); + + return 0; +} + +static void cmc221_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = cmc221_on; + mc->ops.modem_off = cmc221_off; + mc->ops.modem_reset = cmc221_reset; + mc->ops.modem_boot_on = cmc221_boot_on; + mc->ops.modem_boot_off = cmc221_boot_off; + mc->ops.modem_boot_done = cmc221_boot_done; + mc->ops.modem_force_crash_exit = cmc221_force_crash_exit; + mc->ops.modem_dump_reset = cmc221_dump_reset; +} + +int cmc221_init_modemctl_device(struct modem_ctl *mc, struct modem_data *pdata) +{ + int ret = 0; + int irq = 0; + unsigned long flag = 0; + struct platform_device *pdev = NULL; + + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_pda_active = pdata->gpio_pda_active; +#if 0 /*TODO: check the GPIO map*/ + mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + mc->gpio_slave_wakeup = pdata->gpio_slave_wakeup; + mc->gpio_host_active = pdata->gpio_host_active; + mc->gpio_host_wakeup = pdata->gpio_host_wakeup; +#endif + mc->gpio_dynamic_switching = pdata->gpio_dynamic_switching; + mc->need_switch_to_usb = false; + + if (!mc->gpio_cp_on || !mc->gpio_cp_reset || !mc->gpio_phone_active) { + mif_err("%s: ERR! no GPIO data\n", mc->name); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_cp_on, 0); + + cmc221_get_ops(mc); + dev_set_drvdata(mc->dev, mc); + + pdev = to_platform_device(mc->dev); + mc->irq_phone_active = platform_get_irq_byname(pdev, STR_CP_ACTIVE_IRQ); + if (!mc->irq_phone_active) { + mif_err("%s: ERR! get cp_active_irq fail\n", mc->name); + return -1; + } + mif_err("%s: PHONE_ACTIVE IRQ# = %d\n", mc->name, mc->irq_phone_active); + + wake_lock_init(&mc->mc_wake_lock, WAKE_LOCK_SUSPEND, "cmc_wake_lock"); + + flag = IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND; + irq = mc->irq_phone_active; + ret = request_irq(irq, phone_active_handler, flag, "cmc_active", mc); + if (ret) { + mif_err("%s: ERR! request_irq(#%d) fail (err %d)\n", + mc->name, irq, ret); + return ret; + } + ret = enable_irq_wake(irq); + if (ret) { + mif_err("%s: WARNING! enable_irq_wake(#%d) fail (err %d)\n", + mc->name, irq, ret); + } + + flag = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND; + if (mc->gpio_dynamic_switching) { + irq = gpio_to_irq(mc->gpio_dynamic_switching); + mif_err("%s: DYNAMIC_SWITCH IRQ# = %d\n", mc->name, irq); + ret = request_irq(irq, dynamic_switching_handler, flag, + "dynamic_switching", mc); + if (ret) { + mif_err("%s: ERR! request_irq(#%d) fail (err %d)\n", + mc->name, irq, ret); + return ret; + } + } + + return 0; +} diff --git a/drivers/misc/modem_if/modem_modemctl_device_esc6270.c b/drivers/misc/modem_if/modem_modemctl_device_esc6270.c new file mode 100644 index 00000000000..f35e4ccd95c --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_esc6270.c @@ -0,0 +1,348 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_esc6270.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> + +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/platform_device.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include <linux/regulator/consumer.h> + +#include <plat/gpio-cfg.h> + +#if defined(CONFIG_LINK_DEVICE_DPRAM) +#include "modem_link_device_dpram.h" +#elif defined(CONFIG_LINK_DEVICE_PLD) +#include "modem_link_device_pld.h" +#endif + +#if defined(CONFIG_LINK_DEVICE_DPRAM) || defined(CONFIG_LINK_DEVICE_PLD) +#include <linux/mfd/max77693.h> + +#define PIF_TIMEOUT (180 * HZ) +#define DPRAM_INIT_TIMEOUT (30 * HZ) + +static int esc6270_on(struct modem_ctl *mc) +{ + int ret; + struct link_device *ld = get_current_link(mc->iod); + + pr_info("[MODEM_IF:ESC] <%s> start!!!\n", __func__); + + if (!mc->gpio_cp_reset) { + pr_err("[MODEM_IF:ESC] no gpio data\n"); + return -ENXIO; + } + + if (mc->gpio_reset_req_n) + gpio_set_value(mc->gpio_reset_req_n, 1); + + gpio_set_value(mc->gpio_cp_reset, 1); + msleep(30); + + gpio_set_value(mc->gpio_cp_on, 1); + msleep(500); + + gpio_set_value(mc->gpio_cp_on, 0); + msleep(500); + + gpio_set_value(mc->gpio_pda_active, 1); + + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + ld->mode = LINK_MODE_BOOT; + + return 0; +} + +static int esc6270_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF:ESC] esc6270_off()\n"); + +#if 1 + if (!mc->gpio_cp_reset) { + pr_err("[MODEM_IF:ESC] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_cp_on, 0); +#endif + + mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE); + + return 0; +} + +static int esc6270_reset(struct modem_ctl *mc) +{ + int ret = 0; + + pr_debug("[MODEM_IF:ESC] esc6270_reset()\n"); + + ret = esc6270_off(mc); + if (ret) + return -ENXIO; + + msleep(100); + + ret = esc6270_on(mc); + if (ret) + return -ENXIO; + + return 0; +} + +int esc6270_boot_on(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->iod); + + pr_info("[MODEM_IF:ESC] <%s>\n", __func__); + + /* Need to init uart byt gpio_flm_uart_sel GPIO */ + if (!mc->gpio_cp_reset || !mc->gpio_flm_uart_sel) { + pr_err("[MODEM_IF:ESC] no gpio data\n"); + return -ENXIO; + } + gpio_set_value(mc->gpio_flm_uart_sel, 1); + + pr_info(" - ESC_PHONE_ON : %d, ESC_RESET_N : %d\n", + gpio_get_value(mc->gpio_cp_on), + gpio_get_value(mc->gpio_cp_reset)); + + gpio_set_value(mc->gpio_cp_on, 0); + gpio_direction_output(mc->gpio_cp_reset, 0); + msleep(100); + + gpio_direction_output(mc->gpio_cp_on, 1); + msleep(44); + + pr_info(" - ESC_PHONE_ON : %d, ESC_RESET_N : %d\n", + gpio_get_value(mc->gpio_cp_on), + gpio_get_value(mc->gpio_cp_reset)); + + gpio_direction_input(mc->gpio_cp_reset); + msleep(600); + gpio_direction_output(mc->gpio_cp_on, 0); + + msleep(20); + pr_info(" - ESC_PHONE_ON : %d, ESC_RESET_N : %d\n", + gpio_get_value(mc->gpio_cp_on), + gpio_get_value(mc->gpio_cp_reset)); + + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + ld->mode = LINK_MODE_BOOT; + + return 0; +} + +static int esc6270_boot_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF:ESC] <%s>\n", __func__); + + if (!mc->gpio_flm_uart_sel) { + pr_err("[MODEM_IF:ESC] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_flm_uart_sel, 0); + + mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE); + + return 0; +} + +static int esc6270_active_count; + +static irqreturn_t phone_active_irq_handler(int irq, void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + int phone_reset = 0; + int phone_active = 0; + int phone_state = 0; + int cp_dump_int = 0; + + if (!mc->gpio_cp_reset || + !mc->gpio_phone_active) { /* || !mc->gpio_cp_dump_int) { */ + pr_err("[MODEM_IF:ESC] no gpio data\n"); + return IRQ_HANDLED; + } + + phone_reset = gpio_get_value(mc->gpio_cp_reset); + phone_active = gpio_get_value(mc->gpio_phone_active); + cp_dump_int = gpio_get_value(mc->gpio_cp_dump_int); + + pr_info("[MODEM_IF:ESC] <%s> phone_reset=%d, phone_active=%d, cp_dump_int=%d\n", + __func__, phone_reset, phone_active, cp_dump_int); + + if (phone_reset && phone_active) { + phone_state = STATE_ONLINE; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + } else if (phone_reset && !phone_active) { + if (mc->phone_state == STATE_ONLINE) { + phone_state = STATE_CRASH_EXIT; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, + phone_state); + } + } else { + phone_state = STATE_OFFLINE; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + } + + if (phone_active) + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_LOW); + else + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_HIGH); + + pr_info("[MODEM_IF::ESC] <%s> phone_state = %d\n", + __func__, phone_state); + + return IRQ_HANDLED; +} + +#if defined(CONFIG_SIM_DETECT) +#if defined(CONFIG_MACH_GRANDE) +static void sim_detect_work(struct work_struct *work) +{ + struct modem_ctl *mc = + container_of(work, struct modem_ctl, sim_det_dwork.work); + + pr_info("[MODEM_IF:ESC] <%s> gpio_sim_detect = %d\n", + __func__, gpio_get_value(mc->gpio_sim_detect)); + + if (mc->iod && mc->iod->sim_state_changed) + mc->iod->sim_state_changed(mc->iod, + !gpio_get_value(mc->gpio_sim_detect)); +} +#endif +static irqreturn_t sim_detect_irq_handler(int irq, void *_mc) +{ + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + pr_info("[MODEM_IF:ESC] <%s> gpio_sim_detect = %d\n", + __func__, gpio_get_value(mc->gpio_sim_detect)); + +#if defined(CONFIG_MACH_GRANDE) + schedule_delayed_work(&mc->sim_det_dwork, msecs_to_jiffies(400)); +#else + if (mc->iod && mc->iod->sim_state_changed) + mc->iod->sim_state_changed(mc->iod, + !gpio_get_value(mc->gpio_sim_detect)); +#endif + return IRQ_HANDLED; +} +#endif + +static void esc6270_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = esc6270_on; + mc->ops.modem_off = esc6270_off; + mc->ops.modem_reset = esc6270_reset; + mc->ops.modem_boot_on = esc6270_boot_on; + mc->ops.modem_boot_off = esc6270_boot_off; +} + +int esc6270_init_modemctl_device(struct modem_ctl *mc, struct modem_data *pdata) +{ + int ret = 0; + struct platform_device *pdev; + + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_reset_req_n = pdata->gpio_reset_req_n; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_pda_active = pdata->gpio_pda_active; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + mc->gpio_sim_detect = pdata->gpio_sim_detect; + + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_cp_on, 0); + + pdev = to_platform_device(mc->dev); + mc->irq_phone_active = platform_get_irq_byname(pdev, "cp_active_irq"); + pr_info("[MODEM_IF:ESC] <%s> PHONE_ACTIVE IRQ# = %d\n", + __func__, mc->irq_phone_active); + + esc6270_get_ops(mc); + + if (mc->irq_phone_active) { + ret = request_irq(mc->irq_phone_active, + phone_active_irq_handler, + IRQF_TRIGGER_HIGH, + "esc_active", + mc); + if (ret) { + pr_err("[MODEM_IF:ESC] <%s> failed to request_irq IRQ# %d (err=%d)\n", + __func__, mc->irq_phone_active, ret); + return ret; + } + + ret = enable_irq_wake(mc->irq_phone_active); + if (ret) { + pr_err("[MODEM_IF:ESC] %s: failed to enable_irq_wake IRQ# %d (err=%d)\n", + __func__, mc->irq_phone_active, ret); + free_irq(mc->irq_phone_active, mc); + return ret; + } + } + +#if defined(CONFIG_SIM_DETECT) +#if defined(CONFIG_MACH_GRANDE) + INIT_DELAYED_WORK(&mc->sim_det_dwork, sim_detect_work); +#endif + mc->irq_sim_detect = platform_get_irq_byname(pdev, "sim_irq"); + pr_info("[MODEM_IF:ESC] <%s> SIM_DECTCT IRQ# = %d\n", + __func__, mc->irq_sim_detect); + + if (mc->irq_sim_detect) { + ret = request_irq(mc->irq_sim_detect, sim_detect_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "esc_sim_detect", mc); + if (ret) { + mif_err("failed to request_irq: %d\n", ret); + mc->sim_state.online = false; + mc->sim_state.changed = false; + return ret; + } + + ret = enable_irq_wake(mc->irq_sim_detect); + if (ret) { + mif_err("failed to enable_irq_wake: %d\n", ret); + free_irq(mc->irq_sim_detect, mc); + mc->sim_state.online = false; + mc->sim_state.changed = false; + return ret; + } + + /* initialize sim_state => insert: gpio=0, remove: gpio=1 */ + mc->sim_state.online = !gpio_get_value(mc->gpio_sim_detect); + } +#endif + + return ret; +} +#endif diff --git a/drivers/misc/modem_if/modem_modemctl_device_mdm6600.c b/drivers/misc/modem_if/modem_modemctl_device_mdm6600.c new file mode 100644 index 00000000000..129f790fdf8 --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_mdm6600.c @@ -0,0 +1,801 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_mdm6600.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> + +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/platform_device.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include <linux/regulator/consumer.h> + +#include <plat/gpio-cfg.h> + +#if defined(CONFIG_MACH_M0_CTC) +#include <linux/mfd/max77693.h> +#endif + +#if defined(CONFIG_MACH_U1_KOR_LGT) +#include <linux/mfd/max8997.h> + +static int mdm6600_on(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] mdm6600_on()\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_reset_msm || !mc->gpio_cp_on) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_pda_active, 0); + gpio_set_value(mc->gpio_cp_reset, 1); + gpio_set_value(mc->gpio_cp_reset_msm, 1); + msleep(30); + gpio_set_value(mc->gpio_cp_on, 1); + msleep(300); + gpio_set_value(mc->gpio_cp_on, 0); + msleep(500); + + gpio_set_value(mc->gpio_pda_active, 1); + + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + + return 0; +} + +static int mdm6600_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] mdm6600_off()\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_reset_msm || !mc->gpio_cp_on) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_cp_reset_msm, 0); + + mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE); + + return 0; +} + +static int mdm6600_reset(struct modem_ctl *mc) +{ + int ret; + + pr_info("[MODEM_IF] mdm6600_reset()\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_reset_msm || !mc->gpio_cp_on) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + if (system_rev >= 0x05) { + dev_err(mc->dev, "[%s] system_rev: %d\n", __func__, system_rev); + + gpio_set_value(mc->gpio_cp_reset_msm, 0); + msleep(100); /* no spec, confirm later exactly how much time + needed to initialize CP with RESET_PMU_N */ + gpio_set_value(mc->gpio_cp_reset_msm, 1); + msleep(40); /* > 37.2 + 2 msec */ + } else { + dev_err(mc->dev, "[%s] system_rev: %d\n", __func__, system_rev); + + gpio_set_value(mc->gpio_cp_reset, 0); + msleep(500); /* no spec, confirm later exactly how much time + needed to initialize CP with RESET_PMU_N */ + gpio_set_value(mc->gpio_cp_reset, 1); + msleep(40); /* > 37.2 + 2 msec */ + } + + return 0; +} + +static int mdm6600_boot_on(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] mdm6600_boot_on()\n"); + + if (!mc->gpio_boot_sw_sel) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + if (mc->vbus_on) + mc->vbus_on(); + + if (mc->gpio_boot_sw_sel) + gpio_set_value(mc->gpio_boot_sw_sel, 0); + mc->usb_boot = true; + + return 0; +} + +static int mdm6600_boot_off(struct modem_ctl *mc) +{ + pr_info("[MODEM_IF] mdm6600_boot_off()\n"); + + if (!mc->gpio_boot_sw_sel) { + pr_err("[MODEM_IF] no gpio data\n"); + return -ENXIO; + } + + if (mc->vbus_off) + mc->vbus_off(); + + if (mc->gpio_boot_sw_sel) + gpio_set_value(mc->gpio_boot_sw_sel, 1); + mc->usb_boot = false; + + return 0; +} + +static int count; + +static irqreturn_t phone_active_irq_handler(int irq, void *_mc) +{ + int phone_reset = 0; + int phone_active_value = 0; + int cp_dump_value = 0; + int phone_state = 0; + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + if (!mc->gpio_cp_reset || !mc->gpio_phone_active +/*|| !mc->gpio_cp_dump_int */) { + pr_err("[MODEM_IF] no gpio data\n"); + return IRQ_HANDLED; + } + + phone_reset = gpio_get_value(mc->gpio_cp_reset); + phone_active_value = gpio_get_value(mc->gpio_phone_active); + + pr_info("[MODEM_IF] PA EVENT : reset =%d, pa=%d, cp_dump=%d\n", + phone_reset, phone_active_value, cp_dump_value); + + if (phone_reset && phone_active_value) { + phone_state = STATE_ONLINE; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + } else if (phone_reset && !phone_active_value) { + if (count == 1) { + phone_state = STATE_CRASH_EXIT; + if (mc->iod) { + ld = get_current_link(mc->iod); + if (ld->terminate_comm) + ld->terminate_comm(ld, mc->iod); + } + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed + (mc->iod, phone_state); + count = 0; + } else { + count++; + } + } else { + phone_state = STATE_OFFLINE; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + } + + pr_info("phone_active_irq_handler : phone_state=%d\n", phone_state); + + return IRQ_HANDLED; +} + +static void mdm6600_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = mdm6600_on; + mc->ops.modem_off = mdm6600_off; + mc->ops.modem_reset = mdm6600_reset; + mc->ops.modem_boot_on = mdm6600_boot_on; + mc->ops.modem_boot_off = mdm6600_boot_off; +} + +int mdm6600_init_modemctl_device(struct modem_ctl *mc, struct modem_data *pdata) +{ + int ret; + struct platform_device *pdev; + + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_pda_active = pdata->gpio_pda_active; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_cp_reset_msm = pdata->gpio_cp_reset_msm; + mc->gpio_boot_sw_sel = pdata->gpio_boot_sw_sel; + + mc->vbus_on = pdata->vbus_on; + mc->vbus_off = pdata->vbus_off; + + pdev = to_platform_device(mc->dev); + mc->irq_phone_active = platform_get_irq_byname(pdev, "cp_active_irq"); + pr_info("[MODEM_IF] <%s> PHONE_ACTIVE IRQ# = %d\n", + __func__, mc->irq_phone_active); + + mdm6600_get_ops(mc); + + ret = request_irq(mc->irq_phone_active, phone_active_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "phone_active", mc); + if (ret) { + pr_err("[MODEM_IF] %s: failed to request_irq:%d\n", + __func__, ret); + goto err_request_irq; + } + + ret = enable_irq_wake(mc->irq_phone_active); + if (ret) { + pr_err("[MODEM_IF] %s: failed to enable_irq_wake:%d\n", + __func__, ret); + goto err_set_wake_irq; + } + + return ret; + + err_set_wake_irq: + free_irq(mc->irq_phone_active, mc); + err_request_irq: + return ret; +} +#endif /* CONFIG_MACH_U1_KOR_LGT */ + +#if defined(CONFIG_MACH_M0_CTC) || defined(CONFIG_MACH_T0_CHN_CTC) + +#if defined(CONFIG_LINK_DEVICE_DPRAM) +#include "modem_link_device_dpram.h" +#elif defined(CONFIG_LINK_DEVICE_PLD) +#include "modem_link_device_pld.h" +#endif + +#define PIF_TIMEOUT (180 * HZ) +#define DPRAM_INIT_TIMEOUT (30 * HZ) + +#if defined(CONFIG_MACH_M0_DUOSCTC) || defined(CONFIG_MACH_M0_GRANDECTC) || \ + defined(CONFIG_MACH_T0_CHN_CTC) +static void mdm6600_vbus_on(void) +{ + struct regulator *regulator; + + pr_info("[MSM] <%s>\n", __func__); + +#if defined(CONFIG_MACH_T0_CHN_CTC) + if (system_rev == 4) + regulator = regulator_get(NULL, "vcc_1.8v_lcd"); + else + regulator = regulator_get(NULL, "vcc_1.8v_usb"); +#else + regulator = regulator_get(NULL, "vusbhub_osc_1.8v"); +#endif + if (IS_ERR(regulator)) { + pr_err("[MSM] error getting regulator_get <%s>\n", __func__); + return ; + } + regulator_enable(regulator); + regulator_put(regulator); + + pr_info("[MSM] <%s> enable\n", __func__); +} + +static void mdm6600_vbus_off(void) +{ + struct regulator *regulator; + + pr_info("[MSM] <%s>\n", __func__); + +#if defined(CONFIG_MACH_T0_CHN_CTC) + if (system_rev == 4) + regulator = regulator_get(NULL, "vcc_1.8v_lcd"); + else + regulator = regulator_get(NULL, "vcc_1.8v_usb"); +#else + regulator = regulator_get(NULL, "vusbhub_osc_1.8v"); +#endif + if (IS_ERR(regulator)) { + pr_err("[MSM] error getting regulator_get <%s>\n", __func__); + return ; + } + regulator_disable(regulator); + regulator_put(regulator); + + pr_info("[MSM] <%s> disable\n", __func__); +} +#endif + +static int mdm6600_on(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->iod); + + pr_info("[MSM] <%s>\n", __func__); + + if (!mc->gpio_reset_req_n || !mc->gpio_cp_reset + || !mc->gpio_cp_on || !mc->gpio_pda_active) { + pr_err("[MSM] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_pda_active, 0); + + gpio_set_value(mc->gpio_cp_on, 1); + msleep(500); + + gpio_set_value(mc->gpio_reset_req_n, 1); + msleep(50); + + gpio_set_value(mc->gpio_cp_reset, 1); + msleep(50); + + gpio_set_value(mc->gpio_cp_on, 0); + msleep(500); + + gpio_set_value(mc->gpio_pda_active, 1); + + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + ld->mode = LINK_MODE_BOOT; + + return 0; +} + +static int mdm6600_off(struct modem_ctl *mc) +{ + pr_info("[MSM] <%s>\n", __func__); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_on) { + pr_err("[MSM] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_reset_req_n, 0); + gpio_set_value(mc->gpio_cp_on, 0); + + msleep(200); + + mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE); + + return 0; +} + +static int mdm6600_reset(struct modem_ctl *mc) +{ + int ret = 0; + struct link_device *ld = get_current_link(mc->iod); + + pr_info("[MSM] <%s>\n", __func__); + + if (!mc->gpio_reset_req_n || !mc->gpio_cp_reset + || !mc->gpio_cp_on || !mc->gpio_pda_active) { + pr_err("[MSM] no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_reset_req_n, 0); + gpio_set_value(mc->gpio_cp_on, 0); + + msleep(100); + + gpio_set_value(mc->gpio_cp_on, 1); + msleep(300); + + gpio_set_value(mc->gpio_reset_req_n, 1); + msleep(50); + + gpio_set_value(mc->gpio_cp_reset, 1); + msleep(50); + + gpio_set_value(mc->gpio_cp_on, 0); + msleep(100); + + gpio_set_value(mc->gpio_pda_active, 1); + + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + ld->mode = LINK_MODE_BOOT; + + return 0; +} + +static int mdm6600_boot_on(struct modem_ctl *mc) +{ + struct regulator *regulator; + + pr_info("[MSM] <%s>\n", __func__); + + if (!mc->gpio_flm_uart_sel) { + pr_err("[MSM] no gpio data\n"); + return -ENXIO; + } + +#if defined(CONFIG_MACH_M0_DUOSCTC) || defined(CONFIG_MACH_T0_CHN_CTC) + mdm6600_vbus_on(); +#elif defined(CONFIG_MACH_M0_GRANDECTC) + if (system_rev >= 14) + mdm6600_vbus_on(); +#endif + + pr_info("[MSM] <%s> %s\n", __func__, "USB_BOOT_EN initializing"); + if (system_rev < 11) { + + gpio_direction_output(GPIO_USB_BOOT_EN, 0); + s3c_gpio_setpull(GPIO_USB_BOOT_EN, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_USB_BOOT_EN, 0); + + gpio_direction_output(GPIO_BOOT_SW_SEL, 0); + s3c_gpio_setpull(GPIO_BOOT_SW_SEL, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_BOOT_SW_SEL, 0); + + msleep(100); + + gpio_direction_output(GPIO_USB_BOOT_EN, 1); + gpio_set_value(GPIO_USB_BOOT_EN, 1); + + pr_info("[MSM] <%s> USB_BOOT_EN:[%d]\n", __func__, + gpio_get_value(GPIO_USB_BOOT_EN)); + + gpio_direction_output(GPIO_BOOT_SW_SEL, 1); + gpio_set_value(GPIO_BOOT_SW_SEL, 1); + + pr_info("[MSM] <%s> BOOT_SW_SEL : [%d]\n", __func__, + gpio_get_value(GPIO_BOOT_SW_SEL)); + } else if (system_rev == 11) { + gpio_direction_output(GPIO_USB_BOOT_EN, 0); + s3c_gpio_setpull(GPIO_USB_BOOT_EN, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_USB_BOOT_EN, 0); + + gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 0); + s3c_gpio_setpull(GPIO_USB_BOOT_EN_REV06, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_USB_BOOT_EN_REV06, 0); + + msleep(100); + + gpio_direction_output(GPIO_USB_BOOT_EN, 1); + gpio_set_value(GPIO_USB_BOOT_EN, 1); + + pr_info("[MSM] <%s> USB_BOOT_EN:[%d]\n", __func__, + gpio_get_value(GPIO_USB_BOOT_EN)); + + gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 1); + gpio_set_value(GPIO_USB_BOOT_EN_REV06, 1); + + pr_info("[MSM(%d)] <%s> USB_BOOT_EN:[%d]\n", system_rev, + __func__, gpio_get_value(GPIO_USB_BOOT_EN_REV06)); + + gpio_direction_output(GPIO_BOOT_SW_SEL, 0); + s3c_gpio_setpull(GPIO_BOOT_SW_SEL, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_BOOT_SW_SEL, 0); + + gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 0); + s3c_gpio_setpull(GPIO_BOOT_SW_SEL_REV06, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 0); + + msleep(100); + + gpio_direction_output(GPIO_BOOT_SW_SEL, 1); + gpio_set_value(GPIO_BOOT_SW_SEL, 1); + + pr_info("[MSM] <%s> BOOT_SW_SEL : [%d]\n", __func__, + gpio_get_value(GPIO_BOOT_SW_SEL)); + + gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 1); + gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 1); + + pr_info("[MSM(%d)] <%s> BOOT_SW_SEL : [%d]\n", system_rev, + __func__, gpio_get_value(GPIO_BOOT_SW_SEL_REV06)); + + } else { /* system_rev>11 */ + gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 0); + s3c_gpio_setpull(GPIO_USB_BOOT_EN_REV06, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_USB_BOOT_EN_REV06, 0); + + gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 0); + s3c_gpio_setpull(GPIO_BOOT_SW_SEL_REV06, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 0); + + msleep(100); + + gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 1); + gpio_set_value(GPIO_USB_BOOT_EN_REV06, 1); + + pr_info("[MSM] <%s> USB_BOOT_EN:[%d]\n", __func__, + gpio_get_value(GPIO_USB_BOOT_EN_REV06)); + + gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 1); + gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 1); + + pr_info("[MSM] <%s> BOOT_SW_SEL : [%d]\n", __func__, + gpio_get_value(GPIO_BOOT_SW_SEL_REV06)); + + } + + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + + return 0; +} + +static int mdm6600_boot_off(struct modem_ctl *mc) +{ + pr_info("[MSM] <%s>\n", __func__); + + if (!mc->gpio_flm_uart_sel +#if defined(CONFIG_MACH_M0_CTC) + || !mc->gpio_flm_uart_sel_rev06 +#endif + ) { + pr_err("[MSM] no gpio data\n"); + return -ENXIO; + } + +#if defined(CONFIG_MACH_M0_DUOSCTC) || defined(CONFIG_MACH_T0_CHN_CTC) + mdm6600_vbus_off(); +#elif defined(CONFIG_MACH_M0_GRANDECTC) + if (system_rev >= 14) + mdm6600_vbus_off(); +#endif + + if (system_rev < 11) { + gpio_direction_output(GPIO_USB_BOOT_EN, 0); + s3c_gpio_setpull(GPIO_USB_BOOT_EN, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_USB_BOOT_EN, 0); + gpio_direction_output(GPIO_BOOT_SW_SEL, 0); + s3c_gpio_setpull(GPIO_BOOT_SW_SEL, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_BOOT_SW_SEL, 0); + + } else if (system_rev == 11) { + gpio_direction_output(GPIO_USB_BOOT_EN, 0); + s3c_gpio_setpull(GPIO_USB_BOOT_EN, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_USB_BOOT_EN, 0); + + gpio_direction_output(GPIO_BOOT_SW_SEL, 0); + s3c_gpio_setpull(GPIO_BOOT_SW_SEL, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_BOOT_SW_SEL, 0); + +#if defined(CONFIG_MACH_M0_CTC) + gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 0); + s3c_gpio_setpull(GPIO_USB_BOOT_EN_REV06, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_USB_BOOT_EN_REV06, 0); + + gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 0); + s3c_gpio_setpull(GPIO_BOOT_SW_SEL_REV06, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 0); +#endif + + } else { /* system_rev>11 */ +#if defined(CONFIG_MACH_M0_CTC) + gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 0); + s3c_gpio_setpull(GPIO_USB_BOOT_EN_REV06, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_USB_BOOT_EN_REV06, 0); + + gpio_direction_output(GPIO_BOOT_SW_SEL_REV06, 0); + s3c_gpio_setpull(GPIO_BOOT_SW_SEL_REV06, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_BOOT_SW_SEL_REV06, 0); +#endif + } + +#if defined(CONFIG_MACH_M0_CTC) + if (max7693_muic_cp_usb_state()) { + msleep(30); + gpio_direction_output(GPIO_USB_BOOT_EN, 1); + s3c_gpio_setpull(GPIO_USB_BOOT_EN, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_USB_BOOT_EN, 1); + gpio_direction_output(GPIO_USB_BOOT_EN_REV06, 1); + s3c_gpio_setpull(GPIO_USB_BOOT_EN_REV06, S3C_GPIO_PULL_NONE); + gpio_set_value(GPIO_USB_BOOT_EN_REV06, 1); + } +#endif + + gpio_set_value(GPIO_BOOT_SW_SEL, 0); + + return 0; +} + + +static int mdm6600_force_crash_exit(struct modem_ctl *mc) +{ + pr_info("[MSM] <%s>\n", __func__); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_on) { + pr_err("[MSM] no gpio data\n"); + return -ENXIO; + } + + s3c_gpio_cfgpin(mc->gpio_cp_dump_int, S3C_GPIO_OUTPUT); + gpio_direction_output(mc->gpio_cp_dump_int, 1); + + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_reset, 1); + + return 0; +} + +static irqreturn_t phone_active_irq_handler(int irq, void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + int phone_reset = 0; + int phone_active = 0; + int phone_state = 0; + int cp_dump_int = 0; + + if (!mc->gpio_cp_reset || + !mc->gpio_phone_active || !mc->gpio_cp_dump_int) { + pr_err("[MSM] no gpio data\n"); + return IRQ_HANDLED; + } + + phone_reset = gpio_get_value(mc->gpio_cp_reset); + phone_active = gpio_get_value(mc->gpio_phone_active); + cp_dump_int = gpio_get_value(mc->gpio_cp_dump_int); + + pr_info("[MSM] <%s> phone_reset=%d, phone_active=%d, cp_dump_int=%d\n", + __func__, phone_reset, phone_active, cp_dump_int); + + if (phone_reset && phone_active) { + phone_state = STATE_ONLINE; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + } else if (phone_reset && !phone_active) { + if (mc->phone_state == STATE_ONLINE) { + phone_state = STATE_CRASH_EXIT; + + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, + phone_state); + } + } else { + phone_state = STATE_OFFLINE; + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + } + + if (phone_active) + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_LOW); + else + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_HIGH); + + pr_info("[MSM] <%s> phone_state = %d\n", __func__, phone_state); + + return IRQ_HANDLED; +} + +#if defined(CONFIG_SIM_DETECT) +#if defined(CONFIG_MACH_GRANDE) +static void sim_detect_work(struct work_struct *work) +{ + struct modem_ctl *mc = + container_of(work, struct modem_ctl, sim_det_dwork.work); + + pr_info("[MSM] <%s> gpio_sim_detect = %d\n", + __func__, gpio_get_value(mc->gpio_sim_detect)); + + if (mc->iod && mc->iod->sim_state_changed) + mc->iod->sim_state_changed(mc->iod, + !gpio_get_value(mc->gpio_sim_detect)); +} +#endif +static irqreturn_t sim_detect_irq_handler(int irq, void *_mc) +{ + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + pr_info("[MSM] <%s> gpio_sim_detect = %d\n", + __func__, gpio_get_value(mc->gpio_sim_detect)); + +#if defined(CONFIG_MACH_GRANDE) + schedule_delayed_work(&mc->sim_det_dwork, msecs_to_jiffies(400)); +#else + if (mc->iod && mc->iod->sim_state_changed) + mc->iod->sim_state_changed(mc->iod, + !gpio_get_value(mc->gpio_sim_detect)); +#endif + return IRQ_HANDLED; +} +#endif + +static void mdm6600_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = mdm6600_on; + mc->ops.modem_off = mdm6600_off; + mc->ops.modem_reset = mdm6600_reset; + mc->ops.modem_boot_on = mdm6600_boot_on; + mc->ops.modem_boot_off = mdm6600_boot_off; + mc->ops.modem_force_crash_exit = mdm6600_force_crash_exit; +} + +int mdm6600_init_modemctl_device(struct modem_ctl *mc, struct modem_data *pdata) +{ + int ret = 0; + struct platform_device *pdev; + + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_reset_req_n = pdata->gpio_reset_req_n; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_pda_active = pdata->gpio_pda_active; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; +#if defined(CONFIG_MACH_M0_CTC) + mc->gpio_flm_uart_sel_rev06 = pdata->gpio_flm_uart_sel_rev06; +#endif + mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + mc->gpio_sim_detect = pdata->gpio_sim_detect; + + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_cp_on, 0); + + pdev = to_platform_device(mc->dev); + mc->irq_phone_active = platform_get_irq_byname(pdev, "cp_active_irq"); + pr_info("[MSM] <%s> PHONE_ACTIVE IRQ# = %d\n", + __func__, mc->irq_phone_active); + + mdm6600_get_ops(mc); + + ret = request_irq(mc->irq_phone_active, + phone_active_irq_handler, + IRQF_TRIGGER_HIGH, "msm_active", mc); + if (ret) { + pr_err("[MSM] <%s> failed to request_irq IRQ# %d (err=%d)\n", + __func__, mc->irq_phone_active, ret); + return ret; + } + + ret = enable_irq_wake(mc->irq_phone_active); + if (ret) { + pr_err("[MSM] %s: failed to enable_irq_wake IRQ# %d (err=%d)\n", + __func__, mc->irq_phone_active, ret); + free_irq(mc->irq_phone_active, mc); + return ret; + } + +#if defined(CONFIG_SIM_DETECT) +#if defined(CONFIG_MACH_GRANDE) + INIT_DELAYED_WORK(&mc->sim_det_dwork, sim_detect_work); +#endif + mc->irq_sim_detect = platform_get_irq_byname(pdev, "sim_irq"); + pr_info("[MSM] <%s> SIM_DECTCT IRQ# = %d\n", + __func__, mc->irq_sim_detect); + + if (mc->irq_sim_detect) { + ret = request_irq(mc->irq_sim_detect, sim_detect_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "msm_sim_detect", mc); + if (ret) { + mif_err("[MSM] failed to request_irq: %d\n", ret); + mc->sim_state.online = false; + mc->sim_state.changed = false; + return ret; + } + + ret = enable_irq_wake(mc->irq_sim_detect); + if (ret) { + mif_err("[MSM] failed to enable_irq_wake: %d\n", ret); + free_irq(mc->irq_sim_detect, mc); + mc->sim_state.online = false; + mc->sim_state.changed = false; + return ret; + } + + /* initialize sim_state => insert: gpio=0, remove: gpio=1 */ + mc->sim_state.online = !gpio_get_value(mc->gpio_sim_detect); + } +#endif + + return ret; +} +#endif diff --git a/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c b/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c new file mode 100644 index 00000000000..c2d50677aa6 --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c @@ -0,0 +1,303 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_xmm6260.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> + +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/platform_device.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" + +static int xmm6260_on(struct modem_ctl *mc) +{ + mif_info("xmm6260_on()\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_on || !mc->gpio_reset_req_n) { + mif_err("no gpio data\n"); + return -ENXIO; + } + + if (mc->gpio_revers_bias_clear) + mc->gpio_revers_bias_clear(); + + + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_reset, 0); + udelay(160); + gpio_set_value(mc->gpio_pda_active, 0); + msleep(500); /* must be >500ms for CP can boot up under -20 degrees */ + gpio_set_value(mc->gpio_cp_reset, 1); + udelay(160); + gpio_set_value(mc->gpio_reset_req_n, 1); + udelay(160); + gpio_set_value(mc->gpio_cp_on, 1); + udelay(60); + gpio_set_value(mc->gpio_cp_on, 0); + msleep(20); + + if (mc->gpio_revers_bias_restore) + mc->gpio_revers_bias_restore(); + + + gpio_set_value(mc->gpio_pda_active, 1); + + mc->phone_state = STATE_BOOTING; + + return 0; + +} + +static int xmm6260_off(struct modem_ctl *mc) +{ + mif_info("xmm6260_off()\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_on) { + mif_err("no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_reset, 0); + + mc->phone_state = STATE_OFFLINE; + + return 0; +} + + +static int xmm6260_reset(struct modem_ctl *mc) +{ + + mif_info("xmm6260_reset()\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_reset_req_n) + return -ENXIO; + + if (mc->gpio_revers_bias_clear) + mc->gpio_revers_bias_clear(); + + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_reset_req_n, 0); + + mc->phone_state = STATE_OFFLINE; + + msleep(20); + + gpio_set_value(mc->gpio_cp_reset, 1); +/* TODO: check the reset timming with C2C connection */ + udelay(160); + + gpio_set_value(mc->gpio_reset_req_n, 1); + udelay(100); + + gpio_set_value(mc->gpio_cp_on, 1); + udelay(60); + gpio_set_value(mc->gpio_cp_on, 0); + msleep(20); + + if (mc->gpio_revers_bias_restore) + mc->gpio_revers_bias_restore(); + + mc->phone_state = STATE_BOOTING; + + return 0; +} + +static int xmm6260_boot_on(struct modem_ctl *mc) +{ + mif_info("xmm6260_boot_on()\n"); + + if (!mc->gpio_flm_uart_sel) { + mif_err("no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_flm_uart_sel, 0); + + return 0; +} + +static int xmm6260_boot_off(struct modem_ctl *mc) +{ + mif_info("xmm6260_boot_off()\n"); + + if (!mc->gpio_flm_uart_sel) { + mif_err("no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_flm_uart_sel, 1); + + return 0; +} + +static irqreturn_t phone_active_irq_handler(int irq, void *_mc) +{ + int phone_reset = 0; + int phone_active_value = 0; + int cp_dump_value = 0; + int phone_state = 0; + struct modem_ctl *mc = (struct modem_ctl *)_mc; + struct link_device *ld; + + disable_irq_nosync(mc->irq_phone_active); + + if (!mc->gpio_cp_reset || !mc->gpio_phone_active || + !mc->gpio_cp_dump_int) { + mif_err("no gpio data\n"); + return IRQ_HANDLED; + } + + phone_reset = gpio_get_value(mc->gpio_cp_reset); + phone_active_value = gpio_get_value(mc->gpio_phone_active); + cp_dump_value = gpio_get_value(mc->gpio_cp_dump_int); + + mif_info("PA EVENT : reset =%d, pa=%d, cp_dump=%d\n", + phone_reset, phone_active_value, cp_dump_value); + + if (phone_reset && phone_active_value) + phone_state = STATE_BOOTING; + else if (phone_reset && !phone_active_value) { + if (mc->phone_state == STATE_BOOTING) + goto set_type; + if (cp_dump_value) + phone_state = STATE_CRASH_EXIT; + else + phone_state = STATE_CRASH_RESET; + if (mc->iod) { + ld = get_current_link(mc->iod); + if (ld->terminate_comm) + ld->terminate_comm(ld, mc->iod); + } + } else + phone_state = STATE_OFFLINE; + + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + + if (mc->bootd && mc->bootd->modem_state_changed) + mc->bootd->modem_state_changed(mc->bootd, phone_state); + +set_type: + if (phone_active_value) + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_LOW); + else + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_HIGH); + enable_irq(mc->irq_phone_active); + + return IRQ_HANDLED; +} + +static irqreturn_t sim_detect_irq_handler(int irq, void *_mc) +{ + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + if (mc->iod && mc->iod->sim_state_changed) + mc->iod->sim_state_changed(mc->iod, + !gpio_get_value(mc->gpio_sim_detect)); + + return IRQ_HANDLED; +} + +static void xmm6260_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = xmm6260_on; + mc->ops.modem_off = xmm6260_off; + mc->ops.modem_reset = xmm6260_reset; + mc->ops.modem_boot_on = xmm6260_boot_on; + mc->ops.modem_boot_off = xmm6260_boot_off; +} + +int xmm6260_init_modemctl_device(struct modem_ctl *mc, + struct modem_data *pdata) +{ + int ret; + struct platform_device *pdev; + + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_reset_req_n = pdata->gpio_reset_req_n; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_pda_active = pdata->gpio_pda_active; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + mc->gpio_revers_bias_clear = pdata->gpio_revers_bias_clear; + mc->gpio_revers_bias_restore = pdata->gpio_revers_bias_restore; + mc->gpio_sim_detect = pdata->gpio_sim_detect; + + pdev = to_platform_device(mc->dev); + /* mc->irq_phone_active = platform_get_irq(pdev, 0); */ + mc->irq_phone_active = gpio_to_irq(mc->gpio_phone_active); + + if (mc->gpio_sim_detect) + mc->irq_sim_detect = gpio_to_irq(mc->gpio_sim_detect); + + xmm6260_get_ops(mc); + + /* initialize phone active */ + ret = request_irq(mc->irq_phone_active, phone_active_irq_handler, + IRQF_NO_SUSPEND | IRQF_TRIGGER_HIGH, + "phone_active", mc); + if (ret) { + mif_err("failed to request_irq:%d\n", ret); + goto err_phone_active_request_irq; + } + + ret = enable_irq_wake(mc->irq_phone_active); + if (ret) { + mif_err("failed to enable_irq_wake:%d\n", ret); + goto err_phone_active_set_wake_irq; + } + + /* initialize sim_state if gpio_sim_detect exists */ + mc->sim_state.online = false; + mc->sim_state.changed = false; + if (mc->gpio_sim_detect) { + ret = request_irq(mc->irq_sim_detect, sim_detect_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "sim_detect", mc); + if (ret) { + mif_err("failed to request_irq: %d\n", ret); + goto err_sim_detect_request_irq; + } + + ret = enable_irq_wake(mc->irq_sim_detect); + if (ret) { + mif_err("failed to enable_irq_wake: %d\n", ret); + goto err_sim_detect_set_wake_irq; + } + + /* initialize sim_state => insert: gpio=0, remove: gpio=1 */ + mc->sim_state.online = !gpio_get_value(mc->gpio_sim_detect); + } + + return ret; + +err_sim_detect_set_wake_irq: + free_irq(mc->irq_sim_detect, mc); +err_sim_detect_request_irq: + mc->sim_state.online = false; + mc->sim_state.changed = false; +err_phone_active_set_wake_irq: + free_irq(mc->irq_phone_active, mc); +err_phone_active_request_irq: + return ret; +} diff --git a/drivers/misc/modem_if/modem_modemctl_device_xmm6262.c b/drivers/misc/modem_if/modem_modemctl_device_xmm6262.c new file mode 100644 index 00000000000..e4000468b2c --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_xmm6262.c @@ -0,0 +1,261 @@ +/* /linux/drivers/misc/modem_if/modem_modemctl_device_xmm6262.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define DEBUG + +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/cma.h> +#include <plat/devs.h> +#include <linux/platform_data/modem.h> +#include "modem_prj.h" + +static int xmm6262_on(struct modem_ctl *mc) +{ + mif_info("\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_on || !mc->gpio_reset_req_n) { + mif_err("no gpio data\n"); + return -ENXIO; + } + + if (mc->gpio_revers_bias_clear) + mc->gpio_revers_bias_clear(); + + /* TODO */ + gpio_set_value(mc->gpio_reset_req_n, 0); + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_reset, 0); + msleep(100); + gpio_set_value(mc->gpio_cp_reset, 1); + /* If XMM6262 was connected with C2C, AP wait 50ms to BB Reset*/ + msleep(50); + gpio_set_value(mc->gpio_reset_req_n, 1); + + gpio_set_value(mc->gpio_cp_on, 1); + udelay(60); + gpio_set_value(mc->gpio_cp_on, 0); + msleep(20); + if (mc->gpio_revers_bias_restore) + mc->gpio_revers_bias_restore(); + gpio_set_value(mc->gpio_pda_active, 1); + + mc->phone_state = STATE_BOOTING; + + return 0; +} + +static int xmm6262_off(struct modem_ctl *mc) +{ + mif_info("\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_cp_on) { + mif_err("no gpio data\n"); + return -ENXIO; + } + + gpio_set_value(mc->gpio_cp_on, 0); + gpio_set_value(mc->gpio_cp_reset, 0); + + return 0; +} + +static int xmm6262_reset(struct modem_ctl *mc) +{ + mif_info("\n"); + + if (!mc->gpio_cp_reset || !mc->gpio_reset_req_n) + return -ENXIO; + + if (mc->gpio_revers_bias_clear) + mc->gpio_revers_bias_clear(); + + gpio_set_value(mc->gpio_cp_reset, 0); + gpio_set_value(mc->gpio_reset_req_n, 0); + + mc->phone_state = STATE_OFFLINE; + + msleep(20); + + gpio_set_value(mc->gpio_cp_reset, 1); + /* TODO: check the reset timming with C2C connection */ + udelay(160); + + gpio_set_value(mc->gpio_reset_req_n, 1); + udelay(100); + + gpio_set_value(mc->gpio_cp_on, 1); + udelay(60); + gpio_set_value(mc->gpio_cp_on, 0); + msleep(20); + + if (mc->gpio_revers_bias_restore) + mc->gpio_revers_bias_restore(); + + mc->phone_state = STATE_BOOTING; + + return 0; +} + +static irqreturn_t phone_active_irq_handler(int irq, void *_mc) +{ + int phone_reset = 0; + int phone_active_value = 0; + int cp_dump_value = 0; + int phone_state = 0; + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + disable_irq_nosync(mc->irq_phone_active); + + if (!mc->gpio_cp_reset || !mc->gpio_phone_active || + !mc->gpio_cp_dump_int) { + mif_err("no gpio data\n"); + return IRQ_HANDLED; + } + + phone_reset = gpio_get_value(mc->gpio_cp_reset); + phone_active_value = gpio_get_value(mc->gpio_phone_active); + cp_dump_value = gpio_get_value(mc->gpio_cp_dump_int); + + mif_info("PA EVENT : reset =%d, pa=%d, cp_dump=%d\n", + phone_reset, phone_active_value, cp_dump_value); + + if (phone_reset && phone_active_value) + phone_state = STATE_BOOTING; + else if (phone_reset && !phone_active_value) { + if (cp_dump_value) + phone_state = STATE_CRASH_EXIT; + else + phone_state = STATE_CRASH_RESET; + } else + phone_state = STATE_OFFLINE; + + if (mc->iod && mc->iod->modem_state_changed) + mc->iod->modem_state_changed(mc->iod, phone_state); + + if (mc->bootd && mc->bootd->modem_state_changed) + mc->bootd->modem_state_changed(mc->bootd, phone_state); + + if (phone_active_value) + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_LOW); + else + irq_set_irq_type(mc->irq_phone_active, IRQ_TYPE_LEVEL_HIGH); + enable_irq(mc->irq_phone_active); + + return IRQ_HANDLED; +} + +static irqreturn_t sim_detect_irq_handler(int irq, void *_mc) +{ + struct modem_ctl *mc = (struct modem_ctl *)_mc; + + if (mc->iod && mc->iod->sim_state_changed) + mc->iod->sim_state_changed(mc->iod, + gpio_get_value(mc->gpio_sim_detect) == mc->sim_polarity + ); + + return IRQ_HANDLED; +} + +static void xmm6262_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = xmm6262_on; + mc->ops.modem_off = xmm6262_off; + mc->ops.modem_reset = xmm6262_reset; +} + +int xmm6262_init_modemctl_device(struct modem_ctl *mc, + struct modem_data *pdata) +{ + int ret = 0; + struct platform_device *pdev; + + mc->gpio_reset_req_n = pdata->gpio_reset_req_n; + mc->gpio_cp_on = pdata->gpio_cp_on; + mc->gpio_cp_reset = pdata->gpio_cp_reset; + mc->gpio_pda_active = pdata->gpio_pda_active; + mc->gpio_phone_active = pdata->gpio_phone_active; + mc->gpio_cp_dump_int = pdata->gpio_cp_dump_int; + mc->gpio_ap_dump_int = pdata->gpio_ap_dump_int; + mc->gpio_flm_uart_sel = pdata->gpio_flm_uart_sel; + mc->gpio_cp_warm_reset = pdata->gpio_cp_warm_reset; + mc->gpio_sim_detect = pdata->gpio_sim_detect; + mc->sim_polarity = pdata->sim_polarity; + + mc->gpio_revers_bias_clear = pdata->gpio_revers_bias_clear; + mc->gpio_revers_bias_restore = pdata->gpio_revers_bias_restore; + + pdev = to_platform_device(mc->dev); + mc->irq_phone_active = gpio_to_irq(mc->gpio_phone_active); + + if (mc->gpio_sim_detect) + mc->irq_sim_detect = gpio_to_irq(mc->gpio_sim_detect); + + xmm6262_get_ops(mc); + + ret = request_irq(mc->irq_phone_active, phone_active_irq_handler, + IRQF_NO_SUSPEND | IRQF_TRIGGER_HIGH, + "phone_active", mc); + if (ret) { + mif_err("failed to request_irq:%d\n", ret); + goto err_phone_active_request_irq; + } + + ret = enable_irq_wake(mc->irq_phone_active); + if (ret) { + mif_err("failed to enable_irq_wake:%d\n", ret); + goto err_phone_active_set_wake_irq; + } + + /* initialize sim_state if gpio_sim_detect exists */ + mc->sim_state.online = false; + mc->sim_state.changed = false; + if (mc->gpio_sim_detect) { + ret = request_irq(mc->irq_sim_detect, sim_detect_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "sim_detect", mc); + if (ret) { + mif_err("failed to request_irq: %d\n", ret); + goto err_sim_detect_request_irq; + } + + ret = enable_irq_wake(mc->irq_sim_detect); + if (ret) { + mif_err("failed to enable_irq_wake: %d\n", ret); + goto err_sim_detect_set_wake_irq; + } + + /* initialize sim_state => insert: gpio=0, remove: gpio=1 */ + mc->sim_state.online = + gpio_get_value(mc->gpio_sim_detect) == mc->sim_polarity; + } + + return ret; + +err_sim_detect_set_wake_irq: + free_irq(mc->irq_sim_detect, mc); +err_sim_detect_request_irq: + mc->sim_state.online = false; + mc->sim_state.changed = false; +err_phone_active_set_wake_irq: + free_irq(mc->irq_phone_active, mc); +err_phone_active_request_irq: + return ret; +} diff --git a/drivers/misc/modem_if/modem_net_flowcontrol_device.c b/drivers/misc/modem_if/modem_net_flowcontrol_device.c new file mode 100644 index 00000000000..164f4711c25 --- /dev/null +++ b/drivers/misc/modem_if/modem_net_flowcontrol_device.c @@ -0,0 +1,117 @@ +/* /linux/drivers/misc/modem_if/modem_net_flowcontrol_device.c + * + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/platform_data/modem.h> +#include <linux/module.h> + +#include "modem_prj.h" + + +#define NET_FLOWCONTROL_DEV_NAME_LEN 8 + +static int modem_net_flowcontrol_device_open( + struct inode *inode, struct file *filp) +{ + return 0; +} + +static int modem_net_flowcontrol_device_release( + struct inode *inode, struct file *filp) +{ + return 0; +} + +static long modem_net_flowcontrol_device_ioctl( + struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct net *this_net; + struct net_device *ndev; + char dev_name[NET_FLOWCONTROL_DEV_NAME_LEN]; + u8 chan; + + if (copy_from_user(&chan, (void __user *)arg, sizeof(char))) + return -EFAULT; + + if (chan > 15) + return -ENODEV; + + snprintf(dev_name, NET_FLOWCONTROL_DEV_NAME_LEN, "rmnet%d", (int)chan); + this_net = get_net_ns_by_pid(current->pid); + ndev = __dev_get_by_name(this_net, dev_name); + if (ndev == NULL) { + mif_err("device = %s not exist\n", dev_name); + return -ENODEV; + } + + switch (cmd) { + case IOCTL_MODEM_NET_SUSPEND: + netif_stop_queue(ndev); + mif_info("NET SUSPEND(%s)\n", dev_name); + break; + case IOCTL_MODEM_NET_RESUME: + netif_wake_queue(ndev); + mif_info("NET RESUME(%s)\n", dev_name); + break; + default: + return -EINVAL; + } + return 0; +} + +static const struct file_operations modem_net_flowcontrol_device_fops = { + .owner = THIS_MODULE, + .open = modem_net_flowcontrol_device_open, + .release = modem_net_flowcontrol_device_release, + .unlocked_ioctl = modem_net_flowcontrol_device_ioctl, +}; + +static int __init modem_net_flowcontrol_device_init(void) +{ + int ret = 0; + struct io_device *net_flowcontrol_dev; + + net_flowcontrol_dev = kzalloc(sizeof(struct io_device), GFP_KERNEL); + if (!net_flowcontrol_dev) { + mif_err("net_flowcontrol_dev io device memory alloc fail\n"); + return -ENOMEM; + } + + net_flowcontrol_dev->miscdev.minor = MISC_DYNAMIC_MINOR; + net_flowcontrol_dev->miscdev.name = "modem_br"; + net_flowcontrol_dev->miscdev.fops = &modem_net_flowcontrol_device_fops; + + ret = misc_register(&net_flowcontrol_dev->miscdev); + if (ret) { + mif_err("failed to register misc br device : %s\n", + net_flowcontrol_dev->miscdev.name); + kfree(net_flowcontrol_dev); + } + + return ret; +} + +module_init(modem_net_flowcontrol_device_init); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung Modem IF Net Flowcontrol Driver"); diff --git a/drivers/misc/modem_if/modem_prj.h b/drivers/misc/modem_if/modem_prj.h new file mode 100644 index 00000000000..4780bef3676 --- /dev/null +++ b/drivers/misc/modem_if/modem_prj.h @@ -0,0 +1,725 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_PRJ_H__ +#define __MODEM_PRJ_H__ + +#include <linux/wait.h> +#include <linux/miscdevice.h> +#include <linux/skbuff.h> +#include <linux/completion.h> +#include <linux/wakelock.h> +#include <linux/rbtree.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#define MAX_CPINFO_SIZE 512 + +#define MAX_LINK_DEVTYPE 3 + +#define MAX_FMT_DEVS 10 +#define MAX_RAW_DEVS 32 +#define MAX_RFS_DEVS 10 +#define MAX_NUM_IO_DEV (MAX_FMT_DEVS + MAX_RAW_DEVS + MAX_RFS_DEVS) + +#define IOCTL_MODEM_ON _IO('o', 0x19) +#define IOCTL_MODEM_OFF _IO('o', 0x20) +#define IOCTL_MODEM_RESET _IO('o', 0x21) +#define IOCTL_MODEM_BOOT_ON _IO('o', 0x22) +#define IOCTL_MODEM_BOOT_OFF _IO('o', 0x23) +#define IOCTL_MODEM_BOOT_DONE _IO('o', 0x24) + +#define IOCTL_MODEM_PROTOCOL_SUSPEND _IO('o', 0x25) +#define IOCTL_MODEM_PROTOCOL_RESUME _IO('o', 0x26) + +#define IOCTL_MODEM_STATUS _IO('o', 0x27) +#define IOCTL_MODEM_DL_START _IO('o', 0x28) +#define IOCTL_MODEM_FW_UPDATE _IO('o', 0x29) + +#define IOCTL_MODEM_NET_SUSPEND _IO('o', 0x30) +#define IOCTL_MODEM_NET_RESUME _IO('o', 0x31) + +#define IOCTL_MODEM_DUMP_START _IO('o', 0x32) +#define IOCTL_MODEM_DUMP_UPDATE _IO('o', 0x33) +#define IOCTL_MODEM_FORCE_CRASH_EXIT _IO('o', 0x34) +#define IOCTL_MODEM_CP_UPLOAD _IO('o', 0x35) +#define IOCTL_MODEM_DUMP_RESET _IO('o', 0x36) + +#define IOCTL_DPRAM_SEND_BOOT _IO('o', 0x40) +#define IOCTL_DPRAM_INIT_STATUS _IO('o', 0x43) + +/* ioctl command definitions. */ +#define IOCTL_DPRAM_PHONE_POWON _IO('o', 0xd0) +#define IOCTL_DPRAM_PHONEIMG_LOAD _IO('o', 0xd1) +#define IOCTL_DPRAM_NVDATA_LOAD _IO('o', 0xd2) +#define IOCTL_DPRAM_PHONE_BOOTSTART _IO('o', 0xd3) + +#define IOCTL_DPRAM_PHONE_UPLOAD_STEP1 _IO('o', 0xde) +#define IOCTL_DPRAM_PHONE_UPLOAD_STEP2 _IO('o', 0xdf) + +/* ioctl command for IPC Logger */ +#define IOCTL_MIF_LOG_DUMP _IO('o', 0x51) +#define IOCTL_MIF_DPRAM_DUMP _IO('o', 0x52) + +/* modem status */ +#define MODEM_OFF 0 +#define MODEM_CRASHED 1 +#define MODEM_RAMDUMP 2 +#define MODEM_POWER_ON 3 +#define MODEM_BOOTING_NORMAL 4 +#define MODEM_BOOTING_RAMDUMP 5 +#define MODEM_DUMPING 6 +#define MODEM_RUNNING 7 + +#define HDLC_HEADER_MAX_SIZE 6 /* fmt 3, raw 6, rfs 6 */ + +#define PSD_DATA_CHID_BEGIN 0x2A +#define PSD_DATA_CHID_END 0x38 + +#define PS_DATA_CH_0 10 +#define PS_DATA_CH_LAST 24 +#define RMNET0_CH_ID PS_DATA_CH_0 + +#define IP6VERSION 6 + +#define SOURCE_MAC_ADDR {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC} + +/* IP loopback */ +#define CP2AP_LOOPBACK_CHANNEL 30 /* CP -> AP -> CP */ +#define DATA_LOOPBACK_CHANNEL 31 /* AP -> CP -> AP */ + +/* Debugging features */ +#define MIF_LOG_DIR "/sdcard/log" +#define MIF_MAX_PATH_LEN 256 +#define MIF_MAX_NAME_LEN 64 +#define MIF_MAX_STR_LEN 32 + +/* Does modem ctl structure will use state ? or status defined below ?*/ +enum modem_state { + STATE_OFFLINE, + STATE_CRASH_RESET, /* silent reset */ + STATE_CRASH_EXIT, /* cp ramdump */ + STATE_BOOTING, + STATE_ONLINE, + STATE_NV_REBUILDING, /* <= rebuilding start */ + STATE_LOADER_DONE, + STATE_SIM_ATTACH, + STATE_SIM_DETACH, +}; + +enum com_state { + COM_NONE, + COM_ONLINE, + COM_HANDSHAKE, + COM_BOOT, + COM_CRASH, +}; + +enum link_mode { + LINK_MODE_OFFLINE = 0, + LINK_MODE_BOOT, + LINK_MODE_IPC, + LINK_MODE_DLOAD, + LINK_MODE_ULOAD, +}; + +struct sim_state { + bool online; /* SIM is online? */ + bool changed; /* online is changed? */ +}; + +#define HDLC_START 0x7F +#define HDLC_END 0x7E +#define SIZE_OF_HDLC_START 1 +#define SIZE_OF_HDLC_END 1 +#define MAX_LINK_PADDING_SIZE 3 + +struct header_data { + char hdr[HDLC_HEADER_MAX_SIZE]; + unsigned len; + unsigned frag_len; + char start; /*hdlc start header 0x7F*/ +}; + +struct fmt_hdr { + u16 len; + u8 control; +} __packed; + +struct raw_hdr { + u32 len; + u8 channel; + u8 control; +} __packed; + +struct rfs_hdr { + u32 len; + u8 cmd; + u8 id; +} __packed; + +struct sipc_fmt_hdr { + u16 len; + u8 msg_seq; + u8 ack_seq; + u8 main_cmd; + u8 sub_cmd; + u8 cmd_type; +} __packed; + +#define SIPC5_START_MASK 0b11111000 +#define SIPC5_CONFIG_MASK 0b00000111 +#define SIPC5_EXT_FIELD_MASK 0b00000011 + +#define SIPC5_PADDING_EXIST 0b00000100 +#define SIPC5_EXT_FIELD_EXIST 0b00000010 +#define SIPC5_CTL_FIELD_EXIST 0b00000001 + +#define SIPC5_MAX_HEADER_SIZE 6 +#define SIPC5_HEADER_SIZE_WITH_EXT_LEN 6 +#define SIPC5_HEADER_SIZE_WITH_CTL_FLD 5 +#define SIPC5_MIN_HEADER_SIZE 4 +#define SIPC5_CONFIG_SIZE 1 +#define SIPC5_CH_ID_SIZE 1 + +#define SIPC5_CONFIG_OFFSET 0 +#define SIPC5_CH_ID_OFFSET 1 +#define SIPC5_LEN_OFFSET 2 +#define SIPC5_CTL_OFFSET 4 + +#define SIPC5_CH_ID_RAW_0 0 +#define SIPC5_CH_ID_PDP_0 10 +#define SIPC5_CH_ID_PDP_LAST 24 +#define SIPC5_CH_ID_FMT_0 235 +#define SIPC5_CH_ID_RFS_0 245 +#define SIPC5_CH_ID_MAX 255 + +/* If iod->id is 0, do not need to store to `iodevs_tree_fmt' in SIPC4 */ +#define sipc4_is_not_reserved_channel(ch) ((ch) != 0) + +/* Channel 0, 5, 6, 27, 255 are reserved in SIPC5. + * see SIPC5 spec: 2.2.2 Channel Identification (Ch ID) Field. + * They do not need to store in `iodevs_tree_fmt' + */ +#define sipc5_is_not_reserved_channel(ch) \ + ((ch) != 0 && (ch) != 5 && (ch) != 6 && (ch) != 27 && (ch) != 255) + +struct sipc5_link_hdr { + u8 cfg; + u8 ch; + u16 len; + union { + u8 ctl; + u16 ext_len; + }; +} __packed; + +struct sipc5_frame_data { + /* Config octet */ + u8 config; + + /* Channel ID */ + u8 ch_id; + + /* Control for multiple FMT frame */ + u8 control; + + /* Frame configuration set by header analysis */ + bool padding; + bool ctl_fld; + bool ext_len; + + /* Frame length calculated from the length fields */ + unsigned len; + + /* The length of link layer header */ + unsigned hdr_len; + + /* The length of received header */ + unsigned hdr_rcvd; + + /* The length of data payload */ + unsigned data_len; + + /* The length of received data */ + unsigned data_rcvd; + + /* Header buffer */ + u8 hdr[SIPC5_MAX_HEADER_SIZE]; +}; + +struct vnet { + struct io_device *iod; +}; + +/* for fragmented data from link devices */ +struct fragmented_data { + struct sk_buff *skb_recv; + struct header_data h_data; + struct sipc5_frame_data f_data; + /* page alloc fail retry*/ + unsigned realloc_offset; +}; +#define fragdata(iod, ld) (&(iod)->fragments[(ld)->link_type]) + +/** struct skbuff_priv - private data of struct sk_buff + * this is matched to char cb[48] of struct sk_buff + */ +struct skbuff_private { + struct io_device *iod; + struct link_device *ld; + struct io_device *real_iod; /* for rx multipdp */ + u8 ch_id; + u8 control; +} __packed; + +static inline struct skbuff_private *skbpriv(struct sk_buff *skb) +{ + BUILD_BUG_ON(sizeof(struct skbuff_private) > sizeof(skb->cb)); + return (struct skbuff_private *)&skb->cb; +} + +struct io_device { + /* rb_tree node for an io device */ + struct rb_node node_chan; + struct rb_node node_fmt; + + /* Name of the IO device */ + char *name; + + atomic_t opened; + + /* Wait queue for the IO device */ + wait_queue_head_t wq; + + /* Misc and net device structures for the IO device */ + struct miscdevice miscdev; + struct net_device *ndev; + + /* ID and Format for channel on the link */ + unsigned id; + enum modem_link link_types; + enum dev_format format; + enum modem_io io_typ; + enum modem_network net_typ; + + bool use_handover; /* handover 2+ link devices */ + + /* SIPC version */ + enum sipc_ver ipc_version; + + /* Rx queue of sk_buff */ + struct sk_buff_head sk_rx_q; + + /* + ** work for each io device, when delayed work needed + ** use this for private io device rx action + */ + struct delayed_work rx_work; + + struct fragmented_data fragments[LINKDEV_MAX]; + + /* for multi-frame */ + struct sk_buff *skb[128]; + + /* called from linkdevice when a packet arrives for this iodevice */ + int (*recv)(struct io_device *iod, struct link_device *ld, + const char *data, unsigned int len); + int (*recv_skb)(struct io_device *iod, struct link_device *ld, + struct sk_buff *skb); + + /* inform the IO device that the modem is now online or offline or + * crashing or whatever... + */ + void (*modem_state_changed)(struct io_device *iod, enum modem_state); + + /* inform the IO device that the SIM is not inserting or removing */ + void (*sim_state_changed)(struct io_device *iod, bool sim_online); + + struct modem_ctl *mc; + struct modem_shared *msd; + + struct wake_lock wakelock; + long waketime; + + /* DO NOT use __current_link directly + * you MUST use skbpriv(skb)->ld in mc, link, etc.. + */ + struct link_device *__current_link; +}; +#define to_io_device(misc) container_of(misc, struct io_device, miscdev) + +/* get_current_link, set_current_link don't need to use locks. + * In ARM, set_current_link and get_current_link are compiled to + * each one instruction (str, ldr) as atomic_set, atomic_read. + * And, the order of set_current_link and get_current_link is not important. + */ +#define get_current_link(iod) ((iod)->__current_link) +#define set_current_link(iod, ld) ((iod)->__current_link = (ld)) + +struct link_device { + struct list_head list; + char *name; + + enum modem_link link_type; + unsigned aligned; + + /* Maximum IPC device = the last IPC device (e.g. IPC_RFS) + 1 */ + int max_ipc_dev; + + /* SIPC version */ + enum sipc_ver ipc_version; + + /* Modem data */ + struct modem_data *mdm_data; + + /* Modem control */ + struct modem_ctl *mc; + + /* Modem shared data */ + struct modem_shared *msd; + + /* Operation mode of the link device */ + enum link_mode mode; + + struct io_device *fmt_iods[4]; + + /* TX queue of socket buffers */ + struct sk_buff_head sk_fmt_tx_q; + struct sk_buff_head sk_raw_tx_q; + struct sk_buff_head sk_rfs_tx_q; + + struct sk_buff_head *skb_txq[MAX_IPC_DEV]; + + bool raw_tx_suspended; /* for misc dev */ + struct completion raw_tx_resumed_by_cp; + + struct workqueue_struct *tx_wq; + struct work_struct tx_work; + struct delayed_work tx_delayed_work; + + struct delayed_work *tx_dwork[MAX_IPC_DEV]; + struct delayed_work fmt_tx_dwork; + struct delayed_work raw_tx_dwork; + struct delayed_work rfs_tx_dwork; + + struct workqueue_struct *rx_wq; + struct work_struct rx_work; + struct delayed_work rx_delayed_work; + + enum com_state com_state; + + /* init communication - setting link driver */ + int (*init_comm)(struct link_device *ld, struct io_device *iod); + + /* terminate communication */ + void (*terminate_comm)(struct link_device *ld, struct io_device *iod); + + /* called by an io_device when it has a packet to send over link + * - the io device is passed so the link device can look at id and + * format fields to determine how to route/format the packet + */ + int (*send)(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb); + + int (*udl_start)(struct link_device *ld, struct io_device *iod); + + int (*force_dump)(struct link_device *ld, struct io_device *iod); + + int (*dump_start)(struct link_device *ld, struct io_device *iod); + + int (*modem_update)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + + int (*dump_update)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + + int (*ioctl)(struct link_device *ld, struct io_device *iod, + unsigned cmd, unsigned long _arg); +}; + +/** rx_alloc_skb - allocate an skbuff and set skb's iod, ld + * @length: length to allocate + * @iod: struct io_device * + * @ld: struct link_device * + * + * %NULL is returned if there is no free memory. + */ +static inline struct sk_buff *rx_alloc_skb(unsigned int length, + struct io_device *iod, struct link_device *ld) +{ + struct sk_buff *skb; + + if (iod->format == IPC_MULTI_RAW || iod->format == IPC_RAW) + skb = dev_alloc_skb(length); + else + skb = alloc_skb(length, GFP_ATOMIC); + + if (likely(skb)) { + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + } + return skb; +} + +struct modemctl_ops { + int (*modem_on) (struct modem_ctl *); + int (*modem_off) (struct modem_ctl *); + int (*modem_reset) (struct modem_ctl *); + int (*modem_boot_on) (struct modem_ctl *); + int (*modem_boot_off) (struct modem_ctl *); + int (*modem_boot_done) (struct modem_ctl *); + int (*modem_force_crash_exit) (struct modem_ctl *); + int (*modem_dump_reset) (struct modem_ctl *); +}; + +/* for IPC Logger */ +struct mif_storage { + char *addr; + unsigned int cnt; +}; + +/* modem_shared - shared data for all io/link devices and a modem ctl + * msd : mc : iod : ld = 1 : 1 : M : N + */ +struct modem_shared { + /* list of link devices */ + struct list_head link_dev_list; + + /* rb_tree root of io devices. */ + struct rb_root iodevs_tree_chan; /* group by channel */ + struct rb_root iodevs_tree_fmt; /* group by dev_format */ + + /* for IPC Logger */ + struct mif_storage storage; + spinlock_t lock; + + /* loopbacked IP address + * default is 0.0.0.0 (disabled) + * after you setted this, you can use IP packet loopback using this IP. + * exam: echo 1.2.3.4 > /sys/devices/virtual/misc/umts_multipdp/loopback + */ + __be32 loopback_ipaddr; +}; + +struct modem_ctl { + struct device *dev; + char *name; + struct modem_data *mdm_data; + + struct modem_shared *msd; + + enum modem_state phone_state; + struct sim_state sim_state; + + unsigned gpio_cp_on; + unsigned gpio_reset_req_n; + unsigned gpio_cp_reset; + unsigned gpio_pda_active; + unsigned gpio_phone_active; + unsigned gpio_cp_dump_int; + unsigned gpio_ap_dump_int; + unsigned gpio_flm_uart_sel; +#if defined(CONFIG_MACH_M0_CTC) + unsigned gpio_flm_uart_sel_rev06; +#endif + unsigned gpio_cp_warm_reset; + unsigned gpio_cp_off; + unsigned gpio_sim_detect; + unsigned gpio_dynamic_switching; + + int irq_phone_active; + int irq_sim_detect; + +#ifdef CONFIG_LTE_MODEM_CMC221 + const struct attribute_group *group; + unsigned gpio_slave_wakeup; + unsigned gpio_host_wakeup; + unsigned gpio_host_active; + int irq_host_wakeup; + + struct delayed_work dwork; +#endif /*CONFIG_LTE_MODEM_CMC221*/ +#if defined(CONFIG_MACH_GRANDE) + struct delayed_work sim_det_dwork; +#endif /* For checking sim detect pin */ + struct work_struct work; + +#if defined(CONFIG_MACH_U1_KOR_LGT) + unsigned gpio_cp_reset_msm; + unsigned gpio_boot_sw_sel; + void (*vbus_on)(void); + void (*vbus_off)(void); + bool usb_boot; +#endif + + struct modemctl_ops ops; + struct io_device *iod; + struct io_device *bootd; + + /* Wakelock for modem_ctl */ + struct wake_lock mc_wake_lock; + + void (*gpio_revers_bias_clear)(void); + void (*gpio_revers_bias_restore)(void); + + bool need_switch_to_usb; + bool sim_polarity; +}; + +int sipc4_init_io_device(struct io_device *iod); +int sipc5_init_io_device(struct io_device *iod); + +/** + * get_dev_name + * @dev: IPC device (enum dev_format) + * + * Returns IPC device name as a string. + * + */ +static const inline char *get_dev_name(int dev) +{ + if (dev == IPC_FMT) + return "FMT"; + else if (dev == IPC_RAW) + return "RAW"; + else if (dev == IPC_RFS) + return "RFS"; + else if (dev == IPC_BOOT) + return "BOOT"; + else if (dev == IPC_RAMDUMP) + return "DUMP"; + else + return "NONE"; +} + +/** + * sipc5_start_valid + * @cfg: configuration field of an SIPC5 link frame + * + * Returns TRUE if the start (configuration field) of an SIPC5 link frame + * is valid or returns FALSE if it is not valid. + * + */ +static inline int sipc5_start_valid(u8 cfg) +{ + return (cfg & SIPC5_START_MASK) == SIPC5_START_MASK; +} + +/** + * sipc5_get_hdr_len + * @cfg: configuration field of an SIPC5 link frame + * + * Returns the length of SIPC5 link layer header in an SIPC5 link frame + * + */ +static inline unsigned sipc5_get_hdr_len(u8 cfg) +{ + if (cfg & SIPC5_EXT_FIELD_EXIST) { + if (cfg & SIPC5_CTL_FIELD_EXIST) + return SIPC5_HEADER_SIZE_WITH_CTL_FLD; + else + return SIPC5_HEADER_SIZE_WITH_EXT_LEN; + } else { + return SIPC5_MIN_HEADER_SIZE; + } +} + +/** + * sipc5_get_ch_id + * @frm: pointer to an SIPC5 frame + * + * Returns the channel ID in an SIPC5 link frame + * + */ +static inline u8 sipc5_get_ch_id(u8 *frm) +{ + return *(frm + SIPC5_CH_ID_OFFSET); +} + +/** + * sipc5_get_frame_sz16 + * @frm: pointer to an SIPC5 link frame + * + * Returns the length of an SIPC5 link frame without the extended length field + * + */ +static inline unsigned sipc5_get_frame_sz16(u8 *frm) +{ + return *((u16 *)(frm + SIPC5_LEN_OFFSET)); +} + +/** + * sipc5_get_frame_sz32 + * @frm: pointer to an SIPC5 frame + * + * Returns the length of an SIPC5 link frame with the extended length field + * + */ +static inline unsigned sipc5_get_frame_sz32(u8 *frm) +{ + return *((u32 *)(frm + SIPC5_LEN_OFFSET)); +} + +/** + * sipc5_calc_padding_size + * @len: length of an SIPC5 link frame + * + * Returns the padding size for an SIPC5 link frame + * + */ +static inline unsigned sipc5_calc_padding_size(unsigned len) +{ + unsigned residue = len & 0x3; + return residue ? (4 - residue) : 0; +} + +/** + * sipc5_check_frame_in_dev + * @ld: pointer to the link device structure + * @dev: IPC device (enum dev_format) + * @frm: pointer to the start of an SIPC5 frame + * @rest: size of the rest data in the device buffer including this frame + * + * Returns + * < 0 : error + * == 0 : no data + * > 0 : valid data + * + */ +static inline int sipc5_check_frame_in_dev(struct link_device *ld, int dev, + u8 *frm, int rest) +{ + unsigned int len; + + if (unlikely(!sipc5_start_valid(frm[0]))) { + mif_err("%s: ERR! %s invalid start 0x%02X\n", + ld->name, get_dev_name(dev), frm[0]); + return -EBADMSG; + } + + len = sipc5_get_frame_sz16(frm); + if (unlikely(len > rest)) { + mif_err("%s: ERR! %s len %d > rest %d\n", + ld->name, get_dev_name(dev), len, rest); + return -EBADMSG; + } + + return len; +} + +extern void set_sromc_access(bool access); + +#endif diff --git a/drivers/misc/modem_if/modem_sim_slot_switch.c b/drivers/misc/modem_if/modem_sim_slot_switch.c new file mode 100644 index 00000000000..1dd4c6764ce --- /dev/null +++ b/drivers/misc/modem_if/modem_sim_slot_switch.c @@ -0,0 +1,92 @@ +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/err.h> + +#include <plat/gpio-cfg.h> + +#include <mach/gpio.h> + +extern struct class *sec_class; +struct device *slot_switch_dev; + +static ssize_t get_slot_switch(struct device *dev, struct device_attribute *attr, char *buf) +{ + int value; + + //return '0' slot path is '||', return '1' slot path is 'X' + value = gpio_get_value(GPIO_UIM_SIM_SEL); + printk("Current Slot is %x\n", value); + + return sprintf(buf, "%d\n", value); +} + +static ssize_t set_slot_switch(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + int value; + + sscanf(buf, "%d", &value); + + switch(value) { + case 0: + gpio_set_value(GPIO_UIM_SIM_SEL, 0); + printk("set slot switch to %x\n", gpio_get_value(GPIO_UIM_SIM_SEL)); + break; + case 1: + gpio_set_value(GPIO_UIM_SIM_SEL, 1); + printk("set slot switch to %x\n", gpio_get_value(GPIO_UIM_SIM_SEL)); + break; + default: + printk("Enter 0 or 1!!\n"); + } + + return size; +} + +static DEVICE_ATTR(slot_sel, S_IRUGO |S_IWUGO | S_IRUSR | S_IWUSR, get_slot_switch, set_slot_switch); + +static int __init slot_switch_manager_init(void) +{ + int ret = 0; + int err = 0; + + printk("slot_switch_manager_init\n"); + + //initailize uim_sim_switch gpio + err = gpio_request(GPIO_UIM_SIM_SEL, "PDA_ACTIVE"); + if (err) { + pr_err("fail to request gpio %s, gpio %d, errno %d\n", + "PDA_ACTIVE", GPIO_UIM_SIM_SEL, err); + } else { + gpio_direction_output(GPIO_UIM_SIM_SEL, 1); + s3c_gpio_setpull(GPIO_UIM_SIM_SEL, S3C_GPIO_PULL_NONE); +#if defined(CONFIG_MACH_T0_CHN_CTC) + gpio_set_value(GPIO_UIM_SIM_SEL, 1); +#else + gpio_set_value(GPIO_UIM_SIM_SEL, 0); +#endif + } + + //initailize slot switch device + slot_switch_dev = device_create(sec_class, + NULL, 0, NULL, "slot_switch"); + if (IS_ERR(slot_switch_dev)) + pr_err("Failed to create device(switch)!\n"); + + if (device_create_file(slot_switch_dev, &dev_attr_slot_sel) < 0) + pr_err("Failed to create device file(%s)!\n", + dev_attr_slot_sel.attr.name); + + return ret; +} + +static void __exit slot_switch_manager_exit(void) +{ +} + +module_init(slot_switch_manager_init); +module_exit(slot_switch_manager_exit); + +MODULE_AUTHOR("SAMSUNG ELECTRONICS CO., LTD"); +MODULE_DESCRIPTION("Slot Switch"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/modem_if/modem_utils.c b/drivers/misc/modem_if/modem_utils.c new file mode 100644 index 00000000000..a2def11dc7e --- /dev/null +++ b/drivers/misc/modem_if/modem_utils.c @@ -0,0 +1,1227 @@ +/* + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/miscdevice.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/ip.h> +#include <net/ip.h> +#include <linux/tcp.h> +#include <linux/udp.h> +#include <linux/rtc.h> +#include <linux/time.h> + +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/wait.h> +#include <linux/time.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/wakelock.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_variation.h" +#include "modem_utils.h" + +#define CMD_SUSPEND ((unsigned short)(0x00CA)) +#define CMD_RESUME ((unsigned short)(0x00CB)) + +#define TX_SEPARATOR "mif: >>>>>>>>>> Outgoing packet " +#define RX_SEPARATOR "mif: Incoming packet <<<<<<<<<<" +#define LINE_SEPARATOR \ + "mif: ------------------------------------------------------------" +#define LINE_BUFF_SIZE 80 + +static const char *hex = "0123456789abcdef"; + +void ts2utc(struct timespec *ts, struct utc_time *utc) +{ + struct tm tm; + + time_to_tm((ts->tv_sec - (sys_tz.tz_minuteswest * 60)), 0, &tm); + utc->year = 1900 + tm.tm_year; + utc->mon = 1 + tm.tm_mon; + utc->day = tm.tm_mday; + utc->hour = tm.tm_hour; + utc->min = tm.tm_min; + utc->sec = tm.tm_sec; + utc->msec = (ts->tv_nsec > 0) ? (ts->tv_nsec / 1000000) : 0; +} + +void get_utc_time(struct utc_time *utc) +{ + struct timespec ts; + getnstimeofday(&ts); + ts2utc(&ts, utc); +} + +#ifdef CONFIG_LINK_DEVICE_DPRAM +#include "modem_link_device_dpram.h" +int mif_dump_dpram(struct io_device *iod) +{ + struct link_device *ld = get_current_link(iod); + struct dpram_link_device *dpld = to_dpram_link_device(ld); + u32 size = dpld->size; + unsigned long read_len = 0; + struct sk_buff *skb; + char *buff; + + buff = kzalloc(size, GFP_ATOMIC); + if (!buff) { + mif_err("ERR! kzalloc fail\n"); + return -ENOMEM; + } else { + dpld->dpram_dump(ld, buff); + } + + while (read_len < size) { + skb = alloc_skb(MAX_IPC_SKB_SIZE, GFP_ATOMIC); + if (!skb) { + mif_err("ERR! alloc_skb fail\n"); + kfree(buff); + return -ENOMEM; + } + memcpy(skb_put(skb, MAX_IPC_SKB_SIZE), + buff + read_len, MAX_IPC_SKB_SIZE); + skb_queue_tail(&iod->sk_rx_q, skb); + read_len += MAX_IPC_SKB_SIZE; + wake_up(&iod->wq); + } + kfree(buff); + return 0; +} +#endif + +int mif_dump_log(struct modem_shared *msd, struct io_device *iod) +{ + struct sk_buff *skb; + unsigned long read_len = 0; + unsigned long int flags; + + spin_lock_irqsave(&msd->lock, flags); + while (read_len < MAX_MIF_BUFF_SIZE) { + skb = alloc_skb(MAX_IPC_SKB_SIZE, GFP_ATOMIC); + if (!skb) { + mif_err("ERR! alloc_skb fail\n"); + spin_unlock_irqrestore(&msd->lock, flags); + return -ENOMEM; + } + memcpy(skb_put(skb, MAX_IPC_SKB_SIZE), + msd->storage.addr + read_len, MAX_IPC_SKB_SIZE); + skb_queue_tail(&iod->sk_rx_q, skb); + read_len += MAX_IPC_SKB_SIZE; + wake_up(&iod->wq); + } + spin_unlock_irqrestore(&msd->lock, flags); + return 0; +} + +static unsigned long long get_kernel_time(void) +{ + int this_cpu; + unsigned long flags; + unsigned long long time; + + preempt_disable(); + raw_local_irq_save(flags); + + this_cpu = smp_processor_id(); + time = cpu_clock(this_cpu); + + preempt_enable(); + raw_local_irq_restore(flags); + + return time; +} + +void mif_ipc_log(enum mif_log_id id, + struct modem_shared *msd, const char *data, size_t len) +{ + struct mif_ipc_block *block; + unsigned long int flags; + + spin_lock_irqsave(&msd->lock, flags); + + block = (struct mif_ipc_block *) + (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt)); + msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ? + msd->storage.cnt + 1 : 0; + + spin_unlock_irqrestore(&msd->lock, flags); + + block->id = id; + block->time = get_kernel_time(); + block->len = (len > MAX_IPC_LOG_SIZE) ? MAX_IPC_LOG_SIZE : len; + memcpy(block->buff, data, block->len); +} + +void _mif_irq_log(enum mif_log_id id, struct modem_shared *msd, + struct mif_irq_map map, const char *data, size_t len) +{ + struct mif_irq_block *block; + unsigned long int flags; + + spin_lock_irqsave(&msd->lock, flags); + + block = (struct mif_irq_block *) + (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt)); + msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ? + msd->storage.cnt + 1 : 0; + + spin_unlock_irqrestore(&msd->lock, flags); + + block->id = id; + block->time = get_kernel_time(); + memcpy(&(block->map), &map, sizeof(struct mif_irq_map)); + if (data) + memcpy(block->buff, data, + (len > MAX_IRQ_LOG_SIZE) ? MAX_IRQ_LOG_SIZE : len); +} + +void _mif_com_log(enum mif_log_id id, + struct modem_shared *msd, const char *format, ...) +{ + struct mif_common_block *block; + unsigned long int flags; + va_list args; + int ret; + + spin_lock_irqsave(&msd->lock, flags); + + block = (struct mif_common_block *) + (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt)); + msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ? + msd->storage.cnt + 1 : 0; + + spin_unlock_irqrestore(&msd->lock, flags); + + block->id = id; + block->time = get_kernel_time(); + + va_start(args, format); + ret = vsnprintf(block->buff, MAX_COM_LOG_SIZE, format, args); + va_end(args); +} + +void _mif_time_log(enum mif_log_id id, struct modem_shared *msd, + struct timespec epoch, const char *data, size_t len) +{ + struct mif_time_block *block; + unsigned long int flags; + + spin_lock_irqsave(&msd->lock, flags); + + block = (struct mif_time_block *) + (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt)); + msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ? + msd->storage.cnt + 1 : 0; + + spin_unlock_irqrestore(&msd->lock, flags); + + block->id = id; + block->time = get_kernel_time(); + memcpy(&block->epoch, &epoch, sizeof(struct timespec)); + + if (data) + memcpy(block->buff, data, + (len > MAX_IRQ_LOG_SIZE) ? MAX_IRQ_LOG_SIZE : len); +} + +/* dump2hex + * dump data to hex as fast as possible. + * the length of @buff must be greater than "@len * 3" + * it need 3 bytes per one data byte to print. + */ +static inline int dump2hex(char *buff, const char *data, size_t len) +{ + char *dest = buff; + int i; + + for (i = 0; i < len; i++) { + *dest++ = hex[(data[i] >> 4) & 0xf]; + *dest++ = hex[data[i] & 0xf]; + *dest++ = ' '; + } + if (likely(len > 0)) + dest--; /* last space will be overwrited with null */ + + *dest = '\0'; + + return dest - buff; +} + +void pr_ipc(const char *tag, const char *data, size_t len) +{ + struct utc_time utc; + unsigned char str[128]; + + get_utc_time(&utc); + dump2hex(str, data, (len > 32 ? 32 : len)); + pr_info("%s: %s: [%02d:%02d:%02d.%03d] %s\n", + MIF_TAG, tag, utc.hour, utc.min, utc.sec, utc.msec, str); +} + +/* print buffer as hex string */ +int pr_buffer(const char *tag, const char *data, size_t data_len, + size_t max_len) +{ + size_t len = min(data_len, max_len); + unsigned char str[len ? len * 3 : 1]; /* 1 <= sizeof <= max_len*3 */ + dump2hex(str, data, len); + + /* don't change this printk to mif_debug for print this as level7 */ + return printk(KERN_INFO "%s: %s(%u): %s%s\n", MIF_TAG, tag, data_len, + str, (len == data_len) ? "" : " ..."); +} + +/* flow control CM from CP, it use in serial devices */ +int link_rx_flowctl_cmd(struct link_device *ld, const char *data, size_t len) +{ + struct modem_shared *msd = ld->msd; + unsigned short *cmd, *end = (unsigned short *)(data + len); + + mif_debug("flow control cmd: size=%d\n", len); + + for (cmd = (unsigned short *)data; cmd < end; cmd++) { + switch (*cmd) { + case CMD_SUSPEND: + iodevs_for_each(msd, iodev_netif_stop, 0); + ld->raw_tx_suspended = true; + mif_info("flowctl CMD_SUSPEND(%04X)\n", *cmd); + break; + + case CMD_RESUME: + iodevs_for_each(msd, iodev_netif_wake, 0); + ld->raw_tx_suspended = false; + complete_all(&ld->raw_tx_resumed_by_cp); + mif_info("flowctl CMD_RESUME(%04X)\n", *cmd); + break; + + default: + mif_err("flowctl BACMD: %04X\n", *cmd); + break; + } + } + + return 0; +} + +struct io_device *get_iod_with_channel(struct modem_shared *msd, + unsigned channel) +{ + struct rb_node *n = msd->iodevs_tree_chan.rb_node; + struct io_device *iodev; + while (n) { + iodev = rb_entry(n, struct io_device, node_chan); + if (channel < iodev->id) + n = n->rb_left; + else if (channel > iodev->id) + n = n->rb_right; + else + return iodev; + } + return NULL; +} + +struct io_device *get_iod_with_format(struct modem_shared *msd, + enum dev_format format) +{ + struct rb_node *n = msd->iodevs_tree_fmt.rb_node; + struct io_device *iodev; + while (n) { + iodev = rb_entry(n, struct io_device, node_fmt); + if (format < iodev->format) + n = n->rb_left; + else if (format > iodev->format) + n = n->rb_right; + else + return iodev; + } + return NULL; +} + +struct io_device *insert_iod_with_channel(struct modem_shared *msd, + unsigned channel, struct io_device *iod) +{ + struct rb_node **p = &msd->iodevs_tree_chan.rb_node; + struct rb_node *parent = NULL; + struct io_device *iodev; + while (*p) { + parent = *p; + iodev = rb_entry(parent, struct io_device, node_chan); + if (channel < iodev->id) + p = &(*p)->rb_left; + else if (channel > iodev->id) + p = &(*p)->rb_right; + else + return iodev; + } + rb_link_node(&iod->node_chan, parent, p); + rb_insert_color(&iod->node_chan, &msd->iodevs_tree_chan); + return NULL; +} + +struct io_device *insert_iod_with_format(struct modem_shared *msd, + enum dev_format format, struct io_device *iod) +{ + struct rb_node **p = &msd->iodevs_tree_fmt.rb_node; + struct rb_node *parent = NULL; + struct io_device *iodev; + while (*p) { + parent = *p; + iodev = rb_entry(parent, struct io_device, node_fmt); + if (format < iodev->format) + p = &(*p)->rb_left; + else if (format > iodev->format) + p = &(*p)->rb_right; + else + return iodev; + } + rb_link_node(&iod->node_fmt, parent, p); + rb_insert_color(&iod->node_fmt, &msd->iodevs_tree_fmt); + return NULL; +} + +void iodevs_for_each(struct modem_shared *msd, action_fn action, void *args) +{ + struct io_device *iod; + struct rb_node *node = rb_first(&msd->iodevs_tree_chan); + for (; node; node = rb_next(node)) { + iod = rb_entry(node, struct io_device, node_chan); + action(iod, args); + } +} + +void iodev_netif_wake(struct io_device *iod, void *args) +{ + if (iod->io_typ == IODEV_NET && iod->ndev) { + netif_wake_queue(iod->ndev); + mif_info("%s\n", iod->name); + } +} + +void iodev_netif_stop(struct io_device *iod, void *args) +{ + if (iod->io_typ == IODEV_NET && iod->ndev) { + netif_stop_queue(iod->ndev); + mif_info("%s\n", iod->name); + } +} + +static void iodev_set_tx_link(struct io_device *iod, void *args) +{ + struct link_device *ld = (struct link_device *)args; + if (iod->format == IPC_RAW && IS_CONNECTED(iod, ld)) { + set_current_link(iod, ld); + mif_err("%s -> %s\n", iod->name, ld->name); + } +} + +void rawdevs_set_tx_link(struct modem_shared *msd, enum modem_link link_type) +{ + struct link_device *ld = find_linkdev(msd, link_type); + if (ld) + iodevs_for_each(msd, iodev_set_tx_link, ld); +} + +void mif_netif_stop(struct link_device *ld) +{ + struct io_device *iod; + + if (ld->ipc_version < SIPC_VER_50) + iod = link_get_iod_with_channel(ld, 0x20 | RMNET0_CH_ID); + else + iod = link_get_iod_with_channel(ld, RMNET0_CH_ID); + + if (iod) + iodevs_for_each(iod->msd, iodev_netif_stop, 0); +} + +void mif_netif_wake(struct link_device *ld) +{ + struct io_device *iod; + + if (ld->ipc_version < SIPC_VER_50) + iod = link_get_iod_with_channel(ld, 0x20 | RMNET0_CH_ID); + else + iod = link_get_iod_with_channel(ld, RMNET0_CH_ID); + + if (iod) + iodevs_for_each(iod->msd, iodev_netif_wake, 0); +} + +/** + * ipv4str_to_be32 - ipv4 string to be32 (big endian 32bits integer) + * @return: return zero when errors occurred + */ +__be32 ipv4str_to_be32(const char *ipv4str, size_t count) +{ + unsigned char ip[4]; + char ipstr[16]; /* == strlen("xxx.xxx.xxx.xxx") + 1 */ + char *next = ipstr; + char *p; + int i; + + strncpy(ipstr, ipv4str, ARRAY_SIZE(ipstr)); + + for (i = 0; i < 4; i++) { + p = strsep(&next, "."); + if (kstrtou8(p, 10, &ip[i]) < 0) + return 0; /* == 0.0.0.0 */ + } + + return *((__be32 *)ip); +} + +void mif_add_timer(struct timer_list *timer, unsigned long expire, + void (*function)(unsigned long), unsigned long data) +{ + if (timer_pending(timer)) + return; + + init_timer(timer); + timer->expires = get_jiffies_64() + expire; + timer->function = function; + timer->data = data; + add_timer(timer); +} + +void mif_print_data(const char *buff, int len) +{ + int words = len >> 4; + int residue = len - (words << 4); + int i; + char *b; + char last[80]; + char tb[8]; + + /* Make the last line, if ((len % 16) > 0) */ + if (residue > 0) { + memset(last, 0, sizeof(last)); + memset(tb, 0, sizeof(tb)); + b = (char *)buff + (words << 4); + + sprintf(last, "%04X: ", (words << 4)); + for (i = 0; i < residue; i++) { + sprintf(tb, "%02x ", b[i]); + strcat(last, tb); + if ((i & 0x3) == 0x3) { + sprintf(tb, " "); + strcat(last, tb); + } + } + } + + for (i = 0; i < words; i++) { + b = (char *)buff + (i << 4); + mif_err("%04X: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x\n", + (i << 4), + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], + b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); + } + + /* Print the last line */ + if (residue > 0) + mif_err("%s\n", last); +} + +void mif_dump2format16(const char *data, int len, char *buff, char *tag) +{ + char *d; + int i; + int words = len >> 4; + int residue = len - (words << 4); + char line[LINE_BUFF_SIZE]; + char tb[8]; + + for (i = 0; i < words; i++) { + memset(line, 0, LINE_BUFF_SIZE); + d = (char *)data + (i << 4); + + if (tag) + sprintf(line, "%s%04X| " + "%02x %02x %02x %02x " + "%02x %02x %02x %02x " + "%02x %02x %02x %02x " + "%02x %02x %02x %02x\n", + tag, (i << 4), + d[0], d[1], d[2], d[3], + d[4], d[5], d[6], d[7], + d[8], d[9], d[10], d[11], + d[12], d[13], d[14], d[15]); + else + sprintf(line, "%04X| " + "%02x %02x %02x %02x " + "%02x %02x %02x %02x " + "%02x %02x %02x %02x " + "%02x %02x %02x %02x\n", + (i << 4), + d[0], d[1], d[2], d[3], + d[4], d[5], d[6], d[7], + d[8], d[9], d[10], d[11], + d[12], d[13], d[14], d[15]); + + strcat(buff, line); + } + + /* Make the last line, if (len % 16) > 0 */ + if (residue > 0) { + memset(line, 0, LINE_BUFF_SIZE); + memset(tb, 0, sizeof(tb)); + d = (char *)data + (words << 4); + + if (tag) + sprintf(line, "%s%04X|", tag, (words << 4)); + else + sprintf(line, "%04X|", (words << 4)); + + for (i = 0; i < residue; i++) { + sprintf(tb, " %02x", d[i]); + strcat(line, tb); + if ((i & 0x3) == 0x3) { + sprintf(tb, " "); + strcat(line, tb); + } + } + strcat(line, "\n"); + + strcat(buff, line); + } +} + +void mif_dump2format4(const char *data, int len, char *buff, char *tag) +{ + char *d; + int i; + int words = len >> 2; + int residue = len - (words << 2); + char line[LINE_BUFF_SIZE]; + char tb[8]; + + for (i = 0; i < words; i++) { + memset(line, 0, LINE_BUFF_SIZE); + d = (char *)data + (i << 2); + + if (tag) + sprintf(line, "%s%04X| %02x %02x %02x %02x\n", + tag, (i << 2), d[0], d[1], d[2], d[3]); + else + sprintf(line, "%04X| %02x %02x %02x %02x\n", + (i << 2), d[0], d[1], d[2], d[3]); + + strcat(buff, line); + } + + /* Make the last line, if (len % 4) > 0 */ + if (residue > 0) { + memset(line, 0, LINE_BUFF_SIZE); + memset(tb, 0, sizeof(tb)); + d = (char *)data + (words << 2); + + if (tag) + sprintf(line, "%s%04X|", tag, (words << 2)); + else + sprintf(line, "%04X|", (words << 2)); + + for (i = 0; i < residue; i++) { + sprintf(tb, " %02x", d[i]); + strcat(line, tb); + } + strcat(line, "\n"); + + strcat(buff, line); + } +} + +void mif_print_dump(const char *data, int len, int width) +{ + char *buff; + + buff = kzalloc(len << 3, GFP_ATOMIC); + if (!buff) { + mif_err("ERR! kzalloc fail\n"); + return; + } + + if (width == 16) + mif_dump2format16(data, len, buff, LOG_TAG); + else + mif_dump2format4(data, len, buff, LOG_TAG); + + pr_info("%s", buff); + + kfree(buff); +} + +void print_sipc4_hdlc_fmt_frame(const u8 *psrc) +{ + u8 *frm; /* HDLC Frame */ + struct fmt_hdr *hh; /* HDLC Header */ + struct sipc_fmt_hdr *fh; /* IPC Header */ + u16 hh_len = sizeof(struct fmt_hdr); + u16 fh_len = sizeof(struct sipc_fmt_hdr); + u8 *data; + int dlen; + + /* Actual HDLC header starts from after START flag (0x7F) */ + frm = (u8 *)(psrc + 1); + + /* Point HDLC header and IPC header */ + hh = (struct fmt_hdr *)(frm); + fh = (struct sipc_fmt_hdr *)(frm + hh_len); + + /* Point IPC data */ + data = frm + (hh_len + fh_len); + dlen = hh->len - (hh_len + fh_len); + + mif_err("--------------------HDLC & FMT HEADER----------------------\n"); + + mif_err("HDLC: length %d, control 0x%02x\n", hh->len, hh->control); + + mif_err("(M)0x%02X, (S)0x%02X, (T)0x%02X, mseq %d, aseq %d, len %d\n", + fh->main_cmd, fh->sub_cmd, fh->cmd_type, + fh->msg_seq, fh->ack_seq, fh->len); + + mif_err("-----------------------IPC FMT DATA------------------------\n"); + + if (dlen > 0) { + if (dlen > 64) + dlen = 64; + mif_print_data(data, dlen); + } + + mif_err("-----------------------------------------------------------\n"); +} + +void print_sipc4_fmt_frame(const u8 *psrc) +{ + struct sipc_fmt_hdr *fh = (struct sipc_fmt_hdr *)psrc; + u16 fh_len = sizeof(struct sipc_fmt_hdr); + u8 *data; + int dlen; + + /* Point IPC data */ + data = (u8 *)(psrc + fh_len); + dlen = fh->len - fh_len; + + mif_err("----------------------IPC FMT HEADER-----------------------\n"); + + mif_err("(M)0x%02X, (S)0x%02X, (T)0x%02X, mseq:%d, aseq:%d, len:%d\n", + fh->main_cmd, fh->sub_cmd, fh->cmd_type, + fh->msg_seq, fh->ack_seq, fh->len); + + mif_err("-----------------------IPC FMT DATA------------------------\n"); + + if (dlen > 0) + mif_print_data(data, dlen); + + mif_err("-----------------------------------------------------------\n"); +} + +void print_sipc5_link_fmt_frame(const u8 *psrc) +{ + u8 *lf; /* Link Frame */ + struct sipc5_link_hdr *lh; /* Link Header */ + struct sipc_fmt_hdr *fh; /* IPC Header */ + u16 lh_len; + u16 fh_len; + u8 *data; + int dlen; + + lf = (u8 *)psrc; + + /* Point HDLC header and IPC header */ + lh = (struct sipc5_link_hdr *)lf; + if (lh->cfg & SIPC5_CTL_FIELD_EXIST) + lh_len = SIPC5_HEADER_SIZE_WITH_CTL_FLD; + else + lh_len = SIPC5_MIN_HEADER_SIZE; + fh = (struct sipc_fmt_hdr *)(lf + lh_len); + fh_len = sizeof(struct sipc_fmt_hdr); + + /* Point IPC data */ + data = lf + (lh_len + fh_len); + dlen = lh->len - (lh_len + fh_len); + + mif_err("--------------------LINK & FMT HEADER----------------------\n"); + + mif_err("LINK: cfg 0x%02X, ch %d, len %d\n", lh->cfg, lh->ch, lh->len); + + mif_err("(M)0x%02X, (S)0x%02X, (T)0x%02X, mseq:%d, aseq:%d, len:%d\n", + fh->main_cmd, fh->sub_cmd, fh->cmd_type, + fh->msg_seq, fh->ack_seq, fh->len); + + mif_err("-----------------------IPC FMT DATA------------------------\n"); + + if (dlen > 0) { + if (dlen > 64) + dlen = 64; + mif_print_data(data, dlen); + } + + mif_err("-----------------------------------------------------------\n"); +} + +static void strcat_tcp_header(char *buff, u8 *pkt) +{ + struct tcphdr *tcph = (struct tcphdr *)pkt; + int eol; + char line[LINE_BUFF_SIZE]; + char flag_str[32]; + +/*------------------------------------------------------------------------- + + TCP Header Format + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source Port | Destination Port | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Sequence Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Acknowledgment Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Data | |C|E|U|A|P|R|S|F| | + | Offset| Rsvd |W|C|R|C|S|S|Y|I| Window | + | | |R|E|G|K|H|T|N|N| | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Checksum | Urgent Pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Options | Padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | data | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-------------------------------------------------------------------------*/ + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, + "%s: TCP:: Src.Port %u, Dst.Port %u\n", + MIF_TAG, ntohs(tcph->source), ntohs(tcph->dest)); + strcat(buff, line); + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, + "%s: TCP:: SEQ 0x%08X(%u), ACK 0x%08X(%u)\n", + MIF_TAG, ntohs(tcph->seq), ntohs(tcph->seq), + ntohs(tcph->ack_seq), ntohs(tcph->ack_seq)); + strcat(buff, line); + + memset(line, 0, LINE_BUFF_SIZE); + memset(flag_str, 0, sizeof(flag_str)); + if (tcph->cwr) + strcat(flag_str, "CWR "); + if (tcph->ece) + strcat(flag_str, "ECE"); + if (tcph->urg) + strcat(flag_str, "URG "); + if (tcph->ack) + strcat(flag_str, "ACK "); + if (tcph->psh) + strcat(flag_str, "PSH "); + if (tcph->rst) + strcat(flag_str, "RST "); + if (tcph->syn) + strcat(flag_str, "SYN "); + if (tcph->fin) + strcat(flag_str, "FIN "); + eol = strlen(flag_str) - 1; + if (eol > 0) + flag_str[eol] = 0; + snprintf(line, LINE_BUFF_SIZE, "%s: TCP:: Flags {%s}\n", + MIF_TAG, flag_str); + strcat(buff, line); + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, + "%s: TCP:: Window %u, Checksum 0x%04X, Urgent %u\n", MIF_TAG, + ntohs(tcph->window), ntohs(tcph->check), ntohs(tcph->urg_ptr)); + strcat(buff, line); +} + +static void strcat_udp_header(char *buff, u8 *pkt) +{ + struct udphdr *udph = (struct udphdr *)pkt; + char line[LINE_BUFF_SIZE]; + +/*------------------------------------------------------------------------- + + UDP Header Format + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source Port | Destination Port | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length | Checksum | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | data | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-------------------------------------------------------------------------*/ + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, + "%s: UDP:: Src.Port %u, Dst.Port %u\n", + MIF_TAG, ntohs(udph->source), ntohs(udph->dest)); + strcat(buff, line); + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, + "%s: UDP:: Length %u, Checksum 0x%04X\n", + MIF_TAG, ntohs(udph->len), ntohs(udph->check)); + strcat(buff, line); + + if (ntohs(udph->dest) == 53) { + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, "%s: UDP:: DNS query!!!\n", + MIF_TAG); + strcat(buff, line); + } + + if (ntohs(udph->source) == 53) { + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, "%s: UDP:: DNS response!!!\n", + MIF_TAG); + strcat(buff, line); + } +} + +void print_ip4_packet(const u8 *ip_pkt, bool tx) +{ + char *buff; + struct iphdr *iph = (struct iphdr *)ip_pkt; + u8 *pkt = (u8 *)ip_pkt + (iph->ihl << 2); + u16 flags = (ntohs(iph->frag_off) & 0xE000); + u16 frag_off = (ntohs(iph->frag_off) & 0x1FFF); + int eol; + char line[LINE_BUFF_SIZE]; + char flag_str[16]; + +/*--------------------------------------------------------------------------- + IPv4 Header Format + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |Version| IHL |Type of Service| Total Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Identification |C|D|M| Fragment Offset | + | |E|F|F| | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Time to Live | Protocol | Header Checksum | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Options | Padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + IHL - Header Length + Flags - Consist of 3 bits + The 1st bit is "Congestion" bit. + The 2nd bit is "Dont Fragment" bit. + The 3rd bit is "More Fragments" bit. + +---------------------------------------------------------------------------*/ + + if (iph->version != 4) + return; + + buff = kzalloc(4096, GFP_ATOMIC); + if (!buff) + return; + + + memset(line, 0, LINE_BUFF_SIZE); + if (tx) + snprintf(line, LINE_BUFF_SIZE, "\n%s\n", TX_SEPARATOR); + else + snprintf(line, LINE_BUFF_SIZE, "\n%s\n", RX_SEPARATOR); + strcat(buff, line); + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, "%s\n", LINE_SEPARATOR); + strcat(buff, line); + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, + "%s: IP4:: Version %u, Header Length %u, TOS %u, Length %u\n", + MIF_TAG, iph->version, (iph->ihl << 2), iph->tos, + ntohs(iph->tot_len)); + strcat(buff, line); + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, "%s: IP4:: ID %u, Fragment Offset %u\n", + MIF_TAG, ntohs(iph->id), frag_off); + strcat(buff, line); + + memset(line, 0, LINE_BUFF_SIZE); + memset(flag_str, 0, sizeof(flag_str)); + if (flags & IP_CE) + strcat(flag_str, "CE "); + if (flags & IP_DF) + strcat(flag_str, "DF "); + if (flags & IP_MF) + strcat(flag_str, "MF "); + eol = strlen(flag_str) - 1; + if (eol > 0) + flag_str[eol] = 0; + snprintf(line, LINE_BUFF_SIZE, "%s: IP4:: Flags {%s}\n", + MIF_TAG, flag_str); + strcat(buff, line); + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, + "%s: IP4:: TTL %u, Protocol %u, Header Checksum 0x%04X\n", + MIF_TAG, iph->ttl, iph->protocol, ntohs(iph->check)); + strcat(buff, line); + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, + "%s: IP4:: Src.IP %u.%u.%u.%u, Dst.IP %u.%u.%u.%u\n", + MIF_TAG, ip_pkt[12], ip_pkt[13], ip_pkt[14], ip_pkt[15], + ip_pkt[16], ip_pkt[17], ip_pkt[18], ip_pkt[19]); + strcat(buff, line); + + switch (iph->protocol) { + case 6: /* TCP */ + strcat_tcp_header(buff, pkt); + break; + + case 17: /* UDP */ + strcat_udp_header(buff, pkt); + break; + + default: + break; + } + + memset(line, 0, LINE_BUFF_SIZE); + snprintf(line, LINE_BUFF_SIZE, "%s\n", LINE_SEPARATOR); + strcat(buff, line); + + pr_info("%s", buff); + + kfree(buff); +} + +bool is_dns_packet(const u8 *ip_pkt) +{ + struct iphdr *iph = (struct iphdr *)ip_pkt; + struct udphdr *udph = (struct udphdr *)(ip_pkt + (iph->ihl << 2)); + + /* If this packet is not a UDP packet, return here. */ + if (iph->protocol != 17) + return false; + + if (ntohs(udph->dest) == 53 || ntohs(udph->source) == 53) + return true; + else + return false; +} + +bool is_syn_packet(const u8 *ip_pkt) +{ + struct iphdr *iph = (struct iphdr *)ip_pkt; + struct tcphdr *tcph = (struct tcphdr *)(ip_pkt + (iph->ihl << 2)); + + /* If this packet is not a TCP packet, return here. */ + if (iph->protocol != 6) + return false; + + if (tcph->syn || tcph->fin) + return true; + else + return false; +} + +int memcmp16_to_io(const void __iomem *to, void *from, int size) +{ + u16 *d = (u16 *)to; + u16 *s = (u16 *)from; + int count = size >> 1; + int diff = 0; + int i; + u16 d1; + u16 s1; + + for (i = 0; i < count; i++) { + d1 = ioread16(d); + s1 = *s; + if (d1 != s1) { + diff++; + mif_err("ERR! [%d] d:0x%04X != s:0x%04X\n", i, d1, s1); + } + d++; + s++; + } + + return diff; +} + +int mif_test_dpram(char *dp_name, u8 __iomem *start, u32 size) +{ + u8 __iomem *dst; + int i; + u16 val; + + mif_info("%s: start = 0x%p, size = %d\n", dp_name, start, size); + + dst = start; + for (i = 0; i < (size >> 1); i++) { + iowrite16((i & 0xFFFF), dst); + dst += 2; + } + + dst = start; + for (i = 0; i < (size >> 1); i++) { + val = ioread16(dst); + if (val != (i & 0xFFFF)) { + mif_info("%s: ERR! dst[%d] 0x%04X != 0x%04X\n", + dp_name, i, val, (i & 0xFFFF)); + return -EINVAL; + } + dst += 2; + } + + dst = start; + for (i = 0; i < (size >> 1); i++) { + iowrite16(0x00FF, dst); + dst += 2; + } + + dst = start; + for (i = 0; i < (size >> 1); i++) { + val = ioread16(dst); + if (val != 0x00FF) { + mif_info("%s: ERR! dst[%d] 0x%04X != 0x00FF\n", + dp_name, i, val); + return -EINVAL; + } + dst += 2; + } + + dst = start; + for (i = 0; i < (size >> 1); i++) { + iowrite16(0x0FF0, dst); + dst += 2; + } + + dst = start; + for (i = 0; i < (size >> 1); i++) { + val = ioread16(dst); + if (val != 0x0FF0) { + mif_info("%s: ERR! dst[%d] 0x%04X != 0x0FF0\n", + dp_name, i, val); + return -EINVAL; + } + dst += 2; + } + + dst = start; + for (i = 0; i < (size >> 1); i++) { + iowrite16(0xFF00, dst); + dst += 2; + } + + dst = start; + for (i = 0; i < (size >> 1); i++) { + val = ioread16(dst); + if (val != 0xFF00) { + mif_info("%s: ERR! dst[%d] 0x%04X != 0xFF00\n", + dp_name, i, val); + return -EINVAL; + } + dst += 2; + } + + dst = start; + for (i = 0; i < (size >> 1); i++) { + iowrite16(0, dst); + dst += 2; + } + + dst = start; + for (i = 0; i < (size >> 1); i++) { + val = ioread16(dst); + if (val != 0) { + mif_info("%s: ERR! dst[%d] 0x%04X != 0\n", + dp_name, i, val); + return -EINVAL; + } + dst += 2; + } + + mif_info("%s: PASS!!!\n", dp_name); + return 0; +} + +struct file *mif_open_file(const char *path) +{ + struct file *fp; + mm_segment_t old_fs; + + old_fs = get_fs(); + set_fs(get_ds()); + + fp = filp_open(path, O_RDWR|O_CREAT|O_APPEND, 0666); + + set_fs(old_fs); + + if (IS_ERR(fp)) + return NULL; + + return fp; +} + +void mif_save_file(struct file *fp, const char *buff, size_t size) +{ + int ret; + mm_segment_t old_fs; + + old_fs = get_fs(); + set_fs(get_ds()); + + ret = fp->f_op->write(fp, buff, size, &fp->f_pos); + if (ret < 0) + mif_err("ERR! write fail\n"); + + set_fs(old_fs); +} + +void mif_close_file(struct file *fp) +{ + mm_segment_t old_fs; + + old_fs = get_fs(); + set_fs(get_ds()); + + filp_close(fp, NULL); + + set_fs(old_fs); +} + diff --git a/drivers/misc/modem_if/modem_utils.h b/drivers/misc/modem_if/modem_utils.h new file mode 100644 index 00000000000..219e0597ec2 --- /dev/null +++ b/drivers/misc/modem_if/modem_utils.h @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_UTILS_H__ +#define __MODEM_UTILS_H__ + +#include <linux/rbtree.h> + +#define IS_CONNECTED(iod, ld) ((iod)->link_types & LINKTYPE((ld)->link_type)) + +#define MAX_MIF_BUFF_SIZE 0x80000 /* 512kb */ +#define MAX_MIF_SEPA_SIZE 32 +#define MIF_SEPARATOR "IPC_LOGGER(VER1.1)" +#define MIF_SEPARATOR_DPRAM "DPRAM_LOGGER(VER1.1)" +#define MAX_IPC_SKB_SIZE 4096 +#define MAX_LOG_SIZE 64 + +#define MIF_TAG "mif" + +#define MAX_LOG_CNT (MAX_MIF_BUFF_SIZE / MAX_LOG_SIZE) +#define MIF_ID_SIZE sizeof(enum mif_log_id) + +#define MAX_IPC_LOG_SIZE \ + (MAX_LOG_SIZE - sizeof(enum mif_log_id) \ + - sizeof(unsigned long long) - sizeof(size_t)) +#define MAX_IRQ_LOG_SIZE \ + (MAX_LOG_SIZE - sizeof(enum mif_log_id) \ + - sizeof(unsigned long long) - sizeof(struct mif_irq_map)) +#define MAX_COM_LOG_SIZE \ + (MAX_LOG_SIZE - sizeof(enum mif_log_id) \ + - sizeof(unsigned long long)) +#define MAX_TIM_LOG_SIZE \ + (MAX_LOG_SIZE - sizeof(enum mif_log_id) \ + - sizeof(unsigned long long) - sizeof(struct timespec)) + +enum mif_log_id { + MIF_IPC_RL2AP = 1, + MIF_IPC_AP2CP, + MIF_IPC_CP2AP, + MIF_IPC_AP2RL, + MIF_IRQ, + MIF_COM, + MIF_TIME +}; + +struct mif_irq_map { + u16 magic; + u16 access; + + u16 fmt_tx_in; + u16 fmt_tx_out; + u16 fmt_rx_in; + u16 fmt_rx_out; + + u16 raw_tx_in; + u16 raw_tx_out; + u16 raw_rx_in; + u16 raw_rx_out; + + u16 cp2ap; +}; + +struct mif_ipc_block { + enum mif_log_id id; + unsigned long long time; + size_t len; + char buff[MAX_IPC_LOG_SIZE]; +}; + +struct mif_irq_block { + enum mif_log_id id; + unsigned long long time; + struct mif_irq_map map; + char buff[MAX_IRQ_LOG_SIZE]; +}; + +struct mif_common_block { + enum mif_log_id id; + unsigned long long time; + char buff[MAX_COM_LOG_SIZE]; +}; + +struct mif_time_block { + enum mif_log_id id; + unsigned long long time; + struct timespec epoch; + char buff[MAX_TIM_LOG_SIZE]; +}; + +struct utc_time { + u16 year; + u8 mon:4, + day:4; + u8 hour; + u8 min; + u8 sec; + u16 msec; +} __packed; + +void ts2utc(struct timespec *ts, struct utc_time *utc); +void get_utc_time(struct utc_time *utc); + +int mif_dump_dpram(struct io_device *); +int mif_dump_log(struct modem_shared *, struct io_device *); + +#define mif_irq_log(msd, map, data, len) \ + _mif_irq_log(MIF_IRQ, msd, map, data, len) +#define mif_com_log(msd, format, ...) \ + _mif_com_log(MIF_COM, msd, pr_fmt(format), ##__VA_ARGS__) +#define mif_time_log(msd, epoch, data, len) \ + _mif_time_log(MIF_TIME, msd, epoch, data, len) + +void mif_ipc_log(enum mif_log_id, + struct modem_shared *, const char *, size_t); +void _mif_irq_log(enum mif_log_id, + struct modem_shared *, struct mif_irq_map, const char *, size_t); +void _mif_com_log(enum mif_log_id, + struct modem_shared *, const char *, ...); +void _mif_time_log(enum mif_log_id, + struct modem_shared *, struct timespec, const char *, size_t); + +/** find_linkdev - find a link device + * @msd: struct modem_shared * + */ +static inline struct link_device *find_linkdev(struct modem_shared *msd, + enum modem_link link_type) +{ + struct link_device *ld; + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (ld->link_type == link_type) + return ld; + } + return NULL; +} + +/** countbits - count number of 1 bits as fastest way + * @n: number + */ +static inline unsigned int countbits(unsigned int n) +{ + unsigned int i; + for (i = 0; n != 0; i++) + n &= (n - 1); + return i; +} + +/* print IPC message as hex string with UTC time */ +void pr_ipc(const char *tag, const char *data, size_t len); + +/* print buffer as hex string */ +int pr_buffer(const char *tag, const char *data, size_t data_len, + size_t max_len); + +/* print a sk_buff as hex string */ +#define pr_skb(tag, skb) \ + pr_buffer(tag, (char *)((skb)->data), (size_t)((skb)->len), (size_t)16) + +/* print a urb as hex string */ +#define pr_urb(tag, urb) \ + pr_buffer(tag, (char *)((urb)->transfer_buffer), \ + (size_t)((urb)->actual_length), (size_t)16) + +/* Stop/wake all TX queues in network interfaces */ +void mif_netif_stop(struct link_device *ld); +void mif_netif_wake(struct link_device *ld); + +/* flow control CMD from CP, it use in serial devices */ +int link_rx_flowctl_cmd(struct link_device *ld, const char *data, size_t len); + +/* get iod from tree functions */ + +struct io_device *get_iod_with_format(struct modem_shared *msd, + enum dev_format format); +struct io_device *get_iod_with_channel(struct modem_shared *msd, + unsigned channel); + +static inline struct io_device *link_get_iod_with_format( + struct link_device *ld, enum dev_format format) +{ + struct io_device *iod = get_iod_with_format(ld->msd, format); + return (iod && IS_CONNECTED(iod, ld)) ? iod : NULL; +} + +static inline struct io_device *link_get_iod_with_channel( + struct link_device *ld, unsigned channel) +{ + struct io_device *iod = get_iod_with_channel(ld->msd, channel); + return (iod && IS_CONNECTED(iod, ld)) ? iod : NULL; +} + +/* insert iod to tree functions */ +struct io_device *insert_iod_with_format(struct modem_shared *msd, + enum dev_format format, struct io_device *iod); +struct io_device *insert_iod_with_channel(struct modem_shared *msd, + unsigned channel, struct io_device *iod); + +/* iodev for each */ +typedef void (*action_fn)(struct io_device *iod, void *args); +void iodevs_for_each(struct modem_shared *msd, action_fn action, void *args); + +/* netif wake/stop queue of iod */ +void iodev_netif_wake(struct io_device *iod, void *args); +void iodev_netif_stop(struct io_device *iod, void *args); + +/* change tx_link of raw devices */ +void rawdevs_set_tx_link(struct modem_shared *msd, enum modem_link link_type); + +__be32 ipv4str_to_be32(const char *ipv4str, size_t count); + +void mif_add_timer(struct timer_list *timer, unsigned long expire, + void (*function)(unsigned long), unsigned long data); + +/* debug helper functions for sipc4, sipc5 */ +void mif_print_data(const char *buff, int len); +void mif_dump2format16(const char *data, int len, char *buff, char *tag); +void mif_dump2format4(const char *data, int len, char *buff, char *tag); +void mif_print_dump(const char *data, int len, int width); +void print_sipc4_hdlc_fmt_frame(const u8 *psrc); +void print_sipc4_fmt_frame(const u8 *psrc); +void print_sipc5_link_fmt_frame(const u8 *psrc); + +/*--------------------------------------------------------------------------- + + IPv4 Header Format + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |Version| IHL |Type of Service| Total Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Identification |C|D|M| Fragment Offset | + | |E|F|F| | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Time to Live | Protocol | Header Checksum | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Options | Padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + IHL - Header Length + Flags - Consist of 3 bits + The 1st bit is "Congestion" bit. + The 2nd bit is "Dont Fragment" bit. + The 3rd bit is "More Fragments" bit. + +---------------------------------------------------------------------------*/ +#define IPV4_HDR_SIZE 20 + +/*------------------------------------------------------------------------- + + TCP Header Format + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source Port | Destination Port | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Sequence Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Acknowledgment Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Data | |C|E|U|A|P|R|S|F| | + | Offset| Rsvd |W|C|R|C|S|S|Y|I| Window | + | | |R|E|G|K|H|T|N|N| | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Checksum | Urgent Pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Options | Padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | data | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-------------------------------------------------------------------------*/ +#define TCP_HDR_SIZE 20 + +/*------------------------------------------------------------------------- + + UDP Header Format + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source Port | Destination Port | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length | Checksum | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | data | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-------------------------------------------------------------------------*/ +#define UDP_HDR_SIZE 8 + +void print_ip4_packet(const u8 *ip_pkt, bool tx); +bool is_dns_packet(const u8 *ip_pkt); +bool is_syn_packet(const u8 *ip_pkt); + +int memcmp16_to_io(const void __iomem *to, void *from, int size); +int mif_test_dpram(char *dp_name, u8 __iomem *start, u32 size); +struct file *mif_open_file(const char *path); +void mif_save_file(struct file *fp, const char *buff, size_t size); +void mif_close_file(struct file *fp); + +#endif/*__MODEM_UTILS_H__*/ diff --git a/drivers/misc/modem_if/modem_variation.h b/drivers/misc/modem_if/modem_variation.h new file mode 100644 index 00000000000..2865f7bc10e --- /dev/null +++ b/drivers/misc/modem_if/modem_variation.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_VARIATION_H__ +#define __MODEM_VARIATION_H__ + +#include <linux/platform_data/modem.h> + +#define DECLARE_MODEM_INIT(type) \ + int type ## _init_modemctl_device(struct modem_ctl *mc, \ + struct modem_data *pdata) +#define DECLARE_MODEM_INIT_DUMMY(type) \ + static DECLARE_MODEM_INIT(type) { return 0; } + +#define DECLARE_LINK_INIT(type) \ + struct link_device *type ## _create_link_device( \ + struct platform_device *pdev) +#define DECLARE_LINK_INIT_DUMMY(type) \ + static DECLARE_LINK_INIT(type) { return NULL; } + +#define MODEM_INIT_CALL(type) type ## _init_modemctl_device +#define LINK_INIT_CALL(type) type ## _create_link_device + +/* add declaration of modem & link type */ +/* modem device support */ +DECLARE_MODEM_INIT_DUMMY(dummy) + +#ifdef CONFIG_UMTS_MODEM_XMM6260 +DECLARE_MODEM_INIT(xmm6260); +#else +DECLARE_MODEM_INIT_DUMMY(xmm6260) +#endif + +#ifdef CONFIG_UMTS_MODEM_XMM6262 +DECLARE_MODEM_INIT(xmm6262); +#else +DECLARE_MODEM_INIT_DUMMY(xmm6262) +#endif + +#ifdef CONFIG_CDMA_MODEM_CBP71 +DECLARE_MODEM_INIT(cbp71); +#else +DECLARE_MODEM_INIT_DUMMY(cbp71) +#endif + +#ifdef CONFIG_CDMA_MODEM_CBP72 +DECLARE_MODEM_INIT(cbp72); +#else +DECLARE_MODEM_INIT_DUMMY(cbp72) +#endif + +#ifdef CONFIG_LTE_MODEM_CMC221 +DECLARE_MODEM_INIT(cmc221); +#else +DECLARE_MODEM_INIT_DUMMY(cmc221) +#endif + +#ifdef CONFIG_CDMA_MODEM_MDM6600 +DECLARE_MODEM_INIT(mdm6600); +#else +DECLARE_MODEM_INIT_DUMMY(mdm6600) +#endif + +#ifdef CONFIG_GSM_MODEM_ESC6270 +DECLARE_MODEM_INIT(esc6270); +#else +DECLARE_MODEM_INIT_DUMMY(esc6270) +#endif + +/* link device support */ +DECLARE_LINK_INIT_DUMMY(undefined) + +#ifdef CONFIG_LINK_DEVICE_MIPI +DECLARE_LINK_INIT(mipi); +#else +DECLARE_LINK_INIT_DUMMY(mipi) +#endif + +#ifdef CONFIG_LINK_DEVICE_DPRAM +DECLARE_LINK_INIT(dpram); +#else +DECLARE_LINK_INIT_DUMMY(dpram) +#endif + +#ifdef CONFIG_LINK_DEVICE_PLD +DECLARE_LINK_INIT(pld); +#else +DECLARE_LINK_INIT_DUMMY(pld) +#endif + +#ifdef CONFIG_LINK_DEVICE_SPI +DECLARE_LINK_INIT(spi); +#else +DECLARE_LINK_INIT_DUMMY(spi) +#endif + +#ifdef CONFIG_LINK_DEVICE_USB +DECLARE_LINK_INIT(usb); +#else +DECLARE_LINK_INIT_DUMMY(usb) +#endif + +#ifdef CONFIG_LINK_DEVICE_HSIC +DECLARE_LINK_INIT(hsic); +#else +DECLARE_LINK_INIT_DUMMY(hsic) +#endif + +#ifdef CONFIG_LINK_DEVICE_C2C +DECLARE_LINK_INIT(c2c); +#else +DECLARE_LINK_INIT_DUMMY(c2c) +#endif + +typedef int (*modem_init_call)(struct modem_ctl *, struct modem_data *); +static modem_init_call modem_init_func[] = { + MODEM_INIT_CALL(xmm6260), + MODEM_INIT_CALL(xmm6262), + MODEM_INIT_CALL(cbp71), + MODEM_INIT_CALL(cbp72), + MODEM_INIT_CALL(cmc221), + MODEM_INIT_CALL(mdm6600), + MODEM_INIT_CALL(esc6270), + MODEM_INIT_CALL(dummy), +}; + +typedef struct link_device *(*link_init_call)(struct platform_device *); +static link_init_call link_init_func[] = { + LINK_INIT_CALL(undefined), + LINK_INIT_CALL(mipi), + LINK_INIT_CALL(dpram), + LINK_INIT_CALL(spi), + LINK_INIT_CALL(usb), + LINK_INIT_CALL(hsic), + LINK_INIT_CALL(c2c), + LINK_INIT_CALL(pld), +}; + +static int call_modem_init_func(struct modem_ctl *mc, struct modem_data *pdata) +{ + if (modem_init_func[pdata->modem_type]) + return modem_init_func[pdata->modem_type](mc, pdata); + else + return -ENOTSUPP; +} + +static struct link_device *call_link_init_func(struct platform_device *pdev, + enum modem_link link_type) +{ + if (link_init_func[link_type]) + return link_init_func[link_type](pdev); + else + return NULL; +} + +#endif diff --git a/drivers/misc/modem_if/sipc4_io_device.c b/drivers/misc/modem_if/sipc4_io_device.c new file mode 100644 index 00000000000..56b9afd4cee --- /dev/null +++ b/drivers/misc/modem_if/sipc4_io_device.c @@ -0,0 +1,1668 @@ +/* /linux/drivers/misc/modem_if/modem_io_device.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> +#include <linux/ip.h> +#include <linux/if_ether.h> +#include <linux/etherdevice.h> +#include <linux/device.h> +#include <linux/module.h> + +#include <linux/platform_data/modem.h> +#ifdef CONFIG_LINK_DEVICE_C2C +#include <linux/platform_data/c2c.h> +#endif +#include "modem_prj.h" +#include "modem_utils.h" + +/* + * MAX_RXDATA_SIZE is used at making skb, when it called with page size + * it need more bytes to allocate itself (Ex, cache byte, shared info, + * padding...) + * So, give restriction to allocation size below 1 page to prevent + * big pages broken. + */ +#define MAX_RXDATA_SIZE 0x0E00 /* 4 * 1024 - 512 */ +#define MAX_MULTI_FMT_SIZE 0x4000 /* 16 * 1024 */ + +static const char hdlc_start[1] = { HDLC_START }; +static const char hdlc_end[1] = { HDLC_END }; + +static int rx_iodev_skb(struct sk_buff *skb); + +static ssize_t show_waketime(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int msec; + char *p = buf; + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct io_device *iod = container_of(miscdev, struct io_device, + miscdev); + + msec = jiffies_to_msecs(iod->waketime); + + p += sprintf(buf, "raw waketime : %ums\n", msec); + + return p - buf; +} + +static ssize_t store_waketime(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long msec; + int ret; + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct io_device *iod = container_of(miscdev, struct io_device, + miscdev); + + ret = strict_strtoul(buf, 10, &msec); + if (ret) + return count; + + iod->waketime = msecs_to_jiffies(msec); + + return count; +} + +static struct device_attribute attr_waketime = + __ATTR(waketime, S_IRUGO | S_IWUSR, show_waketime, store_waketime); + +static ssize_t show_loopback(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + unsigned char *ip = (unsigned char *)&msd->loopback_ipaddr; + char *p = buf; + + p += sprintf(buf, "%u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]); + + return p - buf; +} + +static ssize_t store_loopback(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + + msd->loopback_ipaddr = ipv4str_to_be32(buf, count); + + return count; +} + +static struct device_attribute attr_loopback = + __ATTR(loopback, S_IRUGO | S_IWUSR, show_loopback, store_loopback); + +static int get_header_size(struct io_device *iod) +{ + switch (iod->format) { + case IPC_FMT: + return sizeof(struct fmt_hdr); + + case IPC_RAW: + case IPC_MULTI_RAW: + return sizeof(struct raw_hdr); + + case IPC_RFS: + return sizeof(struct rfs_hdr); + + case IPC_BOOT: + /* minimum size for transaction align */ + return 4; + + case IPC_RAMDUMP: + default: + return 0; + } +} + +static int get_hdlc_size(struct io_device *iod, char *buf) +{ + struct fmt_hdr *fmt_header; + struct raw_hdr *raw_header; + struct rfs_hdr *rfs_header; + + mif_debug("buf : %02x %02x %02x (%d)\n", *buf, *(buf + 1), + *(buf + 2), __LINE__); + + switch (iod->format) { + case IPC_FMT: + fmt_header = (struct fmt_hdr *)buf; + if (iod->mc->mdm_data->ipc_version == SIPC_VER_42) + return fmt_header->len & 0x3FFF; + else + return fmt_header->len; + case IPC_RAW: + case IPC_MULTI_RAW: + raw_header = (struct raw_hdr *)buf; + return raw_header->len; + case IPC_RFS: + rfs_header = (struct rfs_hdr *)buf; + return rfs_header->len; + default: + break; + } + return 0; +} + +static void *get_header(struct io_device *iod, size_t count, + char *frame_header_buf) +{ + struct fmt_hdr *fmt_h; + struct raw_hdr *raw_h; + struct rfs_hdr *rfs_h; + + switch (iod->format) { + case IPC_FMT: + fmt_h = (struct fmt_hdr *)frame_header_buf; + + fmt_h->len = count + sizeof(struct fmt_hdr); + fmt_h->control = 0; + + return (void *)frame_header_buf; + + case IPC_RAW: + case IPC_MULTI_RAW: + raw_h = (struct raw_hdr *)frame_header_buf; + + raw_h->len = count + sizeof(struct raw_hdr); + raw_h->channel = iod->id & 0x1F; + raw_h->control = 0; + + return (void *)frame_header_buf; + + case IPC_RFS: + rfs_h = (struct rfs_hdr *)frame_header_buf; + + rfs_h->len = count + sizeof(struct raw_hdr); + rfs_h->id = iod->id; + + return (void *)frame_header_buf; + + default: + return 0; + } +} + +static inline int calc_padding_size(struct io_device *iod, + struct link_device *ld, unsigned len) +{ + if (ld->aligned) + return (4 - (len & 0x3)) & 0x3; + else + return 0; +} + +static inline int rx_hdlc_head_start_check(char *buf) +{ + /* check hdlc head and return size of start byte */ + return (buf[0] == HDLC_START) ? SIZE_OF_HDLC_START : -EBADMSG; +} + +static inline int rx_hdlc_tail_check(char *buf) +{ + /* check hdlc tail and return size of tail byte */ + return (buf[0] == HDLC_END) ? SIZE_OF_HDLC_END : -EBADMSG; +} + +/* remove hdlc header and store IPC header */ +static int rx_hdlc_head_check(struct io_device *iod, struct link_device *ld, + char *buf, unsigned rest) +{ + struct header_data *hdr = &fragdata(iod, ld)->h_data; + int head_size = get_header_size(iod); + int done_len = 0; + int len = 0; + + /* first frame, remove start header 7F */ + if (!hdr->start) { + len = rx_hdlc_head_start_check(buf); + if (len < 0) { + mif_err("Wrong HDLC start: 0x%x\n", *buf); + return len; /*Wrong hdlc start*/ + } + + mif_debug("check len : %d, rest : %d (%d)\n", len, + rest, __LINE__); + + /* set the start flag of current packet */ + hdr->start = HDLC_START; + hdr->len = 0; + + /* debug print */ + switch (iod->format) { + case IPC_FMT: + case IPC_RAW: + case IPC_MULTI_RAW: + case IPC_RFS: + /* TODO: print buf... */ + break; + + case IPC_CMD: + case IPC_BOOT: + case IPC_RAMDUMP: + default: + break; + } + + buf += len; + done_len += len; + rest -= len; /* rest, call by value */ + } + + mif_debug("check len : %d, rest : %d (%d)\n", + len, rest, __LINE__); + + /* store the HDLC header to iod priv */ + if (hdr->len < head_size) { + len = min(rest, head_size - hdr->len); + memcpy(hdr->hdr + hdr->len, buf, len); + hdr->len += len; + done_len += len; + } + + mif_debug("check done_len : %d, rest : %d (%d)\n", done_len, + rest, __LINE__); + return done_len; +} + +/* alloc skb and copy data to skb */ +static int rx_hdlc_data_check(struct io_device *iod, struct link_device *ld, + char *buf, unsigned rest) +{ + struct header_data *hdr = &fragdata(iod, ld)->h_data; + struct sk_buff *skb = fragdata(iod, ld)->skb_recv; + int head_size = get_header_size(iod); + int data_size = get_hdlc_size(iod, hdr->hdr) - head_size; + int alloc_size; + int len = 0; + int done_len = 0; + int rest_len = data_size - hdr->frag_len; + int continue_len = fragdata(iod, ld)->realloc_offset; + + mif_debug("head_size : %d, data_size : %d (%d)\n", head_size, + data_size, __LINE__); + + if (continue_len) { + /* check the HDLC header*/ + if (rx_hdlc_head_start_check(buf) == SIZE_OF_HDLC_START) { + rest_len -= (head_size + SIZE_OF_HDLC_START); + continue_len += (head_size + SIZE_OF_HDLC_START); + } + + buf += continue_len; + rest -= continue_len; + done_len += continue_len; + fragdata(iod, ld)->realloc_offset = 0; + + mif_debug("realloc_offset = %d\n", continue_len); + } + + /* first payload data - alloc skb */ + if (!skb) { + /* make skb data size under MAX_RXDATA_SIZE */ + alloc_size = min(data_size, MAX_RXDATA_SIZE); + alloc_size = min(alloc_size, rest_len); + + /* exceptional case for RFS channel + * make skb for header info first + */ + if (iod->format == IPC_RFS && !hdr->frag_len) { + skb = rx_alloc_skb(head_size, iod, ld); + if (unlikely(!skb)) + return -ENOMEM; + memcpy(skb_put(skb, head_size), hdr->hdr, head_size); + rx_iodev_skb(skb); + } + + /* allocate first packet for data, when its size exceed + * MAX_RXDATA_SIZE, this packet will split to + * multiple packets + */ + skb = rx_alloc_skb(alloc_size, iod, ld); + if (unlikely(!skb)) { + fragdata(iod, ld)->realloc_offset = continue_len; + return -ENOMEM; + } + fragdata(iod, ld)->skb_recv = skb; + } + + while (rest) { + /* copy length cannot exceed rest_len */ + len = min_t(int, rest_len, rest); + /* copy length should be under skb tailroom size */ + len = min(len, skb_tailroom(skb)); + /* when skb tailroom is bigger than MAX_RXDATA_SIZE + * restrict its size to MAX_RXDATA_SIZE just for convinience */ + len = min(len, MAX_RXDATA_SIZE); + + /* copy bytes to skb */ + memcpy(skb_put(skb, len), buf, len); + + /* adjusting variables */ + buf += len; + rest -= len; + done_len += len; + rest_len -= len; + hdr->frag_len += len; + + /* check if it is final for this packet sequence */ + if (!rest_len || !rest) + break; + + /* more bytes are remain for this packet sequence + * pass fully loaded skb to rx queue + * and allocate another skb for continues data recv chain + */ + rx_iodev_skb(skb); + fragdata(iod, ld)->skb_recv = NULL; + + alloc_size = min(rest_len, MAX_RXDATA_SIZE); + + skb = rx_alloc_skb(alloc_size, iod, ld); + if (unlikely(!skb)) { + fragdata(iod, ld)->realloc_offset = done_len; + return -ENOMEM; + } + fragdata(iod, ld)->skb_recv = skb; + } + + mif_debug("rest : %d, alloc_size : %d , len : %d (%d)\n", + rest, alloc_size, skb->len, __LINE__); + + return done_len; +} + +static int rx_multi_fmt_frame(struct sk_buff *rx_skb) +{ + struct io_device *iod = skbpriv(rx_skb)->iod; + struct link_device *ld = skbpriv(rx_skb)->ld; + struct fmt_hdr *fh = + (struct fmt_hdr *)fragdata(iod, ld)->h_data.hdr; + unsigned int id = fh->control & 0x7F; + struct sk_buff *skb = iod->skb[id]; + unsigned char *data = fragdata(iod, ld)->skb_recv->data; + unsigned int rcvd = fragdata(iod, ld)->skb_recv->len; + + if (!skb) { + /* If there has been no multiple frame with this ID */ + if (!(fh->control & 0x80)) { + /* It is a single frame because the "more" bit is 0. */ +#if 0 + mif_err("\n<%s> Rx FMT frame (len %d)\n", + iod->name, rcvd); + print_sipc4_fmt_frame(data); + mif_err("\n"); +#endif + skb_queue_tail(&iod->sk_rx_q, + fragdata(iod, ld)->skb_recv); + mif_debug("wake up wq of %s\n", iod->name); + wake_up(&iod->wq); + return 0; + } else { + struct fmt_hdr *fh = NULL; + skb = rx_alloc_skb(MAX_MULTI_FMT_SIZE, iod, ld); + if (!skb) { + mif_err("<%d> alloc_skb fail\n", + __LINE__); + return -ENOMEM; + } + iod->skb[id] = skb; + + fh = (struct fmt_hdr *)data; + mif_info("Start multi-frame (ID %d, len %d)", + id, fh->len); + } + } + + /* Start multi-frame processing */ + + memcpy(skb_put(skb, rcvd), data, rcvd); + dev_kfree_skb_any(fragdata(iod, ld)->skb_recv); + + if (fh->control & 0x80) { + /* The last frame has not arrived yet. */ + mif_info("Receiving (ID %d, %d bytes)\n", + id, skb->len); + } else { + /* It is the last frame because the "more" bit is 0. */ + mif_info("The Last (ID %d, %d bytes received)\n", + id, skb->len); +#if 0 + mif_err("\n<%s> Rx FMT frame (len %d)\n", + iod->name, skb->len); + print_sipc4_fmt_frame(skb->data); + mif_err("\n"); +#endif + skb_queue_tail(&iod->sk_rx_q, skb); + iod->skb[id] = NULL; + mif_info("wake up wq of %s\n", iod->name); + wake_up(&iod->wq); + } + + return 0; +} + +static int rx_multi_fmt_frame_sipc42(struct sk_buff *rx_skb) +{ + struct io_device *iod = skbpriv(rx_skb)->iod; + struct link_device *ld = skbpriv(rx_skb)->ld; + struct fmt_hdr *fh = + (struct fmt_hdr *)fragdata(iod, ld)->h_data.hdr; + unsigned int id = fh->control & 0x7F; + struct sk_buff *skb = iod->skb[id]; + unsigned char *data = fragdata(iod, ld)->skb_recv->data; + unsigned int rcvd = fragdata(iod, ld)->skb_recv->len; + + u8 ch; + struct io_device *real_iod = NULL; + + ch = (fh->len & 0xC000) >> 14; + fh->len = fh->len & 0x3FFF; + real_iod = ld->fmt_iods[ch]; + if (!real_iod) { + mif_err("wrong channel %d\n", ch); + return -1; + } + skbpriv(rx_skb)->real_iod = real_iod; + + if (!skb) { + /* If there has been no multiple frame with this ID */ + if (!(fh->control & 0x80)) { + /* It is a single frame because the "more" bit is 0. */ +#if 0 + mif_err("\n<%s> Rx FMT frame (len %d)\n", + iod->name, rcvd); + print_sipc4_fmt_frame(data); + mif_err("\n"); +#endif + skb_queue_tail(&real_iod->sk_rx_q, + fragdata(iod, ld)->skb_recv); + mif_debug("wake up wq of %s\n", iod->name); + wake_up(&real_iod->wq); + return 0; + } else { + struct fmt_hdr *fh = NULL; + skb = rx_alloc_skb(MAX_MULTI_FMT_SIZE, real_iod, ld); + if (!skb) { + mif_err("alloc_skb fail\n"); + return -ENOMEM; + } + real_iod->skb[id] = skb; + + fh = (struct fmt_hdr *)data; + mif_err("Start multi-frame (ID %d, len %d)", + id, fh->len); + } + } + + /* Start multi-frame processing */ + + memcpy(skb_put(skb, rcvd), data, rcvd); + dev_kfree_skb_any(fragdata(real_iod, ld)->skb_recv); + + if (fh->control & 0x80) { + /* The last frame has not arrived yet. */ + mif_err("Receiving (ID %d, %d bytes)\n", + id, skb->len); + } else { + /* It is the last frame because the "more" bit is 0. */ + mif_err("The Last (ID %d, %d bytes received)\n", + id, skb->len); +#if 0 + mif_err("\n<%s> Rx FMT frame (len %d)\n", + iod->name, skb->len); + print_sipc4_fmt_frame(skb->data); + mif_err("\n"); +#endif + skb_queue_tail(&real_iod->sk_rx_q, skb); + real_iod->skb[id] = NULL; + mif_info("wake up wq of %s\n", real_iod->name); + wake_up(&real_iod->wq); + } + + return 0; +} + +static int rx_iodev_skb_raw(struct sk_buff *skb) +{ + int err = 0; + struct io_device *iod = skbpriv(skb)->real_iod; + struct net_device *ndev = NULL; + struct iphdr *ip_header = NULL; + struct ethhdr *ehdr = NULL; + const char source[ETH_ALEN] = SOURCE_MAC_ADDR; + + /* check the real_iod is open? */ + /* + if (atomic_read(&iod->opened) == 0) { + mif_err("<%s> is not opened.\n", + iod->name); + pr_skb("drop packet", skb); + return -ENOENT; + } + */ + + switch (iod->io_typ) { + case IODEV_MISC: + mif_debug("<%s> sk_rx_q.qlen = %d\n", + iod->name, iod->sk_rx_q.qlen); + skb_queue_tail(&iod->sk_rx_q, skb); + wake_up(&iod->wq); + return 0; + + case IODEV_NET: + ndev = iod->ndev; + if (!ndev) { + mif_err("<%s> ndev == NULL", + iod->name); + return -EINVAL; + } + + skb->dev = ndev; + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += skb->len; + + /* check the version of IP */ + ip_header = (struct iphdr *)skb->data; + if (ip_header->version == IP6VERSION) + skb->protocol = htons(ETH_P_IPV6); + else + skb->protocol = htons(ETH_P_IP); + + if (iod->use_handover) { + skb_push(skb, sizeof(struct ethhdr)); + ehdr = (void *)skb->data; + memcpy(ehdr->h_dest, ndev->dev_addr, ETH_ALEN); + memcpy(ehdr->h_source, source, ETH_ALEN); + ehdr->h_proto = skb->protocol; + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb_reset_mac_header(skb); + + skb_pull(skb, sizeof(struct ethhdr)); + } + + if (in_irq()) + err = netif_rx(skb); + else + err = netif_rx_ni(skb); + + if (err != NET_RX_SUCCESS) + dev_err(&ndev->dev, "rx error: %d\n", err); + + return err; + + default: + mif_err("wrong io_type : %d\n", iod->io_typ); + return -EINVAL; + } +} + +static void rx_iodev_work(struct work_struct *work) +{ + int ret = 0; + struct sk_buff *skb = NULL; + struct io_device *iod = container_of(work, struct io_device, + rx_work.work); + + while ((skb = skb_dequeue(&iod->sk_rx_q)) != NULL) { + ret = rx_iodev_skb_raw(skb); + if (ret < 0) { + mif_err("<%s> rx_iodev_skb_raw err = %d", + iod->name, ret); + dev_kfree_skb_any(skb); + } else if (ret == NET_RX_DROP) { + mif_err("<%s> ret == NET_RX_DROP\n", + iod->name); + schedule_delayed_work(&iod->rx_work, + msecs_to_jiffies(100)); + break; + } + } +} + +static int rx_multipdp(struct sk_buff *skb) +{ + u8 ch; + struct io_device *iod = skbpriv(skb)->iod; + struct link_device *ld = skbpriv(skb)->ld; + struct raw_hdr *raw_header = + (struct raw_hdr *)fragdata(iod, ld)->h_data.hdr; + struct io_device *real_iod = NULL; + + ch = raw_header->channel; + if (ch == DATA_LOOPBACK_CHANNEL && ld->msd->loopback_ipaddr) + ch = RMNET0_CH_ID; + + real_iod = link_get_iod_with_channel(ld, 0x20 | ch); + if (!real_iod) { + mif_err("wrong channel %d\n", ch); + return -1; + } + + skbpriv(skb)->real_iod = real_iod; + skb_queue_tail(&iod->sk_rx_q, skb); + mif_debug("sk_rx_qlen:%d\n", iod->sk_rx_q.qlen); + + schedule_delayed_work(&iod->rx_work, 0); + return 0; +} + +/* de-mux function draft */ +static int rx_iodev_skb(struct sk_buff *skb) +{ + struct io_device *iod = skbpriv(skb)->iod; + + switch (iod->format) { + case IPC_MULTI_RAW: + return rx_multipdp(skb); + + case IPC_FMT: + if (iod->mc->mdm_data->ipc_version == SIPC_VER_42) + return rx_multi_fmt_frame_sipc42(skb); + else + return rx_multi_fmt_frame(skb); + + case IPC_RFS: + default: + skb_queue_tail(&iod->sk_rx_q, skb); + mif_debug("wake up wq of %s\n", iod->name); + wake_up(&iod->wq); + return 0; + } +} + +static int rx_hdlc_packet(struct io_device *iod, struct link_device *ld, + const char *data, unsigned recv_size) +{ + int rest = (int)recv_size; + char *buf = (char *)data; + int err = 0; + int len = 0; + unsigned rcvd = 0; + + if (rest <= 0) + goto exit; + + mif_debug("RX_SIZE = %d, ld: %s\n", rest, ld->name); + + if (fragdata(iod, ld)->h_data.frag_len) { + /* + If the fragdata(iod, ld)->h_data.frag_len field is + not zero, there is a HDLC frame that is waiting for more data + or HDLC_END in the skb (fragdata(iod, ld)->skb_recv). + In this case, rx_hdlc_head_check() must be skipped. + */ + goto data_check; + } + +next_frame: + err = len = rx_hdlc_head_check(iod, ld, buf, rest); + if (err < 0) + goto exit; + mif_debug("check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + + buf += len; + rest -= len; + if (rest <= 0) + goto exit; + +data_check: + /* + If the return value of rx_hdlc_data_check() is zero, there remains + only HDLC_END that will be received. + */ + err = len = rx_hdlc_data_check(iod, ld, buf, rest); + if (err < 0) + goto exit; + mif_debug("check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + + buf += len; + rest -= len; + + if (!rest && fragdata(iod, ld)->h_data.frag_len) { + /* + Data is being received and more data or HDLC_END does not + arrive yet, but there is no more data in the buffer. More + data may come within the next frame from the link device. + */ + return 0; + } else if (rest <= 0) + goto exit; + + /* At this point, one HDLC frame except HDLC_END has been received. */ + + err = len = rx_hdlc_tail_check(buf); + if (err < 0) { + mif_err("Wrong HDLC end: 0x%02X\n", *buf); + goto exit; + } + mif_debug("check len : %d, rest : %d (%d)\n", len, rest, + __LINE__); + buf += len; + rest -= len; + + /* At this point, one complete HDLC frame has been received. */ + + /* + The padding size is applied for the next HDLC frame. Zero will be + returned by calc_padding_size() if the link device does not require + 4-byte aligned access. + */ + rcvd = get_hdlc_size(iod, fragdata(iod, ld)->h_data.hdr) + + (SIZE_OF_HDLC_START + SIZE_OF_HDLC_END); + len = calc_padding_size(iod, ld, rcvd); + buf += len; + rest -= len; + if (rest < 0) + goto exit; + + err = rx_iodev_skb(fragdata(iod, ld)->skb_recv); + if (err < 0) + goto exit; + + /* initialize header & skb */ + fragdata(iod, ld)->skb_recv = NULL; + memset(&fragdata(iod, ld)->h_data, 0x00, + sizeof(struct header_data)); + fragdata(iod, ld)->realloc_offset = 0; + + if (rest) + goto next_frame; + +exit: + /* free buffers. mipi-hsi re-use recv buf */ + + if (rest < 0) + err = -ERANGE; + + if (err == -ENOMEM) { + if (!(fragdata(iod, ld)->h_data.frag_len)) + memset(&fragdata(iod, ld)->h_data, 0x00, + sizeof(struct header_data)); + return err; + } + + if (err < 0 && fragdata(iod, ld)->skb_recv) { + dev_kfree_skb_any(fragdata(iod, ld)->skb_recv); + fragdata(iod, ld)->skb_recv = NULL; + + /* clear headers */ + memset(&fragdata(iod, ld)->h_data, 0x00, + sizeof(struct header_data)); + fragdata(iod, ld)->realloc_offset = 0; + } + + return err; +} + +static int rx_rfs_packet(struct io_device *iod, struct link_device *ld, + const char *data, unsigned size) +{ + int err = 0; + int pad = 0; + int rcvd = 0; + struct sk_buff *skb; + + if (data[0] != HDLC_START) { + mif_err("Dropping RFS packet ... " + "size = %d, start = %02X %02X %02X %02X\n", + size, + data[0], data[1], data[2], data[3]); + return -EINVAL; + } + + if (data[size-1] != HDLC_END) { + for (pad = 1; pad < 4; pad++) + if (data[(size-1)-pad] == HDLC_END) + break; + + if (pad >= 4) { + char *b = (char *)data; + unsigned sz = size; + mif_err("size %d, No END_FLAG!!!\n", size); + mif_err("end = %02X %02X %02X %02X\n", + b[sz-4], b[sz-3], b[sz-2], b[sz-1]); + return -EINVAL; + } else { + mif_info("padding = %d\n", pad); + } + } + + skb = rx_alloc_skb(size, iod, ld); + if (unlikely(!skb)) { + mif_err("alloc_skb fail\n"); + return -ENOMEM; + } + + /* copy the RFS haeder to skb->data */ + rcvd = size - sizeof(hdlc_start) - sizeof(hdlc_end) - pad; + memcpy(skb_put(skb, rcvd), ((char *)data + sizeof(hdlc_start)), rcvd); + + fragdata(iod, ld)->skb_recv = skb; + err = rx_iodev_skb(fragdata(iod, ld)->skb_recv); + + return err; +} + +/* called from link device when a packet arrives for this io device */ +static int io_dev_recv_data_from_link_dev(struct io_device *iod, + struct link_device *ld, const char *data, unsigned int len) +{ + struct sk_buff *skb; + int err; + unsigned int alloc_size, rest_len; + char *cur; + + + /* check the iod(except IODEV_DUMMY) is open? + * if the iod is MULTIPDP, check this data on rx_iodev_skb_raw() + * because, we cannot know the channel no in here. + */ + /* + if (iod->io_typ != IODEV_DUMMY && atomic_read(&iod->opened) == 0) { + mif_err("<%s> is not opened.\n", iod->name); + pr_buffer("drop packet", data, len, 16u); + return -ENOENT; + } + */ + + switch (iod->format) { + case IPC_RFS: +#ifdef CONFIG_IPC_CMC22x_OLD_RFS + err = rx_rfs_packet(iod, ld, data, len); + return err; +#endif + + case IPC_FMT: + case IPC_RAW: + case IPC_MULTI_RAW: + if (iod->waketime) +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_timeout(&iod->wakelock, iod->waketime); +#else + pm_wakeup_event(iod->miscdev.this_device, + jiffies_to_msecs(iod->waketime)); +#endif + err = rx_hdlc_packet(iod, ld, data, len); + if (err < 0) + mif_err("fail process HDLC frame\n"); + return err; + + case IPC_CMD: + /* TODO- handle flow control command from CP */ + return 0; + + case IPC_BOOT: + case IPC_RAMDUMP: + /* save packet to sk_buff */ + skb = rx_alloc_skb(len, iod, ld); + if (skb) { + mif_debug("boot len : %d\n", len); + + memcpy(skb_put(skb, len), data, len); + skb_queue_tail(&iod->sk_rx_q, skb); + mif_debug("skb len : %d\n", skb->len); + + wake_up(&iod->wq); + return len; + } + /* 32KB page alloc fail case, alloc 3.5K a page.. */ + mif_info("(%d)page fail, alloc fragment pages\n", len); + + rest_len = len; + cur = (char *)data; + while (rest_len) { + alloc_size = min_t(unsigned int, MAX_RXDATA_SIZE, + rest_len); + skb = rx_alloc_skb(alloc_size, iod, ld); + if (!skb) { + mif_err("fail alloc skb (%d)\n", __LINE__); + return -ENOMEM; + } + mif_debug("boot len : %d\n", alloc_size); + + memcpy(skb_put(skb, alloc_size), cur, alloc_size); + skb_queue_tail(&iod->sk_rx_q, skb); + mif_debug("skb len : %d\n", skb->len); + + rest_len -= alloc_size; + cur += alloc_size; + } + wake_up(&iod->wq); + return len; + + default: + return -EINVAL; + } +} + +/* inform the IO device that the modem is now online or offline or + * crashing or whatever... + */ +static void io_dev_modem_state_changed(struct io_device *iod, + enum modem_state state) +{ + iod->mc->phone_state = state; + mif_err("modem state changed. (iod: %s, state: %d)\n", + iod->name, state); + + if ((state == STATE_CRASH_RESET) || (state == STATE_CRASH_EXIT) + || (state == STATE_NV_REBUILDING)) + wake_up(&iod->wq); +} + +/** + * io_dev_sim_state_changed + * @iod: IPC's io_device + * @sim_online: SIM is online? + */ +static void io_dev_sim_state_changed(struct io_device *iod, bool sim_online) +{ + if (atomic_read(&iod->opened) == 0) { + mif_err("iod is not opened: %s\n", + iod->name); + } else if (iod->mc->sim_state.online == sim_online) { + mif_err("sim state not changed.\n"); + } else { + iod->mc->sim_state.online = sim_online; + iod->mc->sim_state.changed = true; +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_timeout(&iod->mc->bootd->wakelock, + iod->mc->bootd->waketime); +#else + pm_wakeup_event(iod->mc->bootd->miscdev.this_device, + jiffies_to_msecs(iod->mc->bootd->waketime)); +#endif + mif_err("sim state changed. (iod: %s, state: " + "[online=%d, changed=%d])\n", + iod->name, iod->mc->sim_state.online, + iod->mc->sim_state.changed); + wake_up(&iod->wq); + } +} + +static int misc_open(struct inode *inode, struct file *filp) +{ + struct io_device *iod = to_io_device(filp->private_data); + struct modem_shared *msd = iod->msd; + struct link_device *ld; + int ret; + filp->private_data = (void *)iod; + + mif_err("iod = %s\n", iod->name); + atomic_inc(&iod->opened); + + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (IS_CONNECTED(iod, ld) && ld->init_comm) { + ret = ld->init_comm(ld, iod); + if (ret < 0) { + mif_err("%s: init_comm error: %d\n", + ld->name, ret); + return ret; + } + } + } + + return 0; +} + +static int misc_release(struct inode *inode, struct file *filp) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct modem_shared *msd = iod->msd; + struct link_device *ld; + + mif_err("iod = %s\n", iod->name); + atomic_dec(&iod->opened); + skb_queue_purge(&iod->sk_rx_q); + + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (IS_CONNECTED(iod, ld) && ld->terminate_comm) + ld->terminate_comm(ld, iod); + } + + return 0; +} + +static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + + poll_wait(filp, &iod->wq, wait); + + if ((!skb_queue_empty(&iod->sk_rx_q)) && + (iod->mc->phone_state != STATE_OFFLINE)) { + return POLLIN | POLLRDNORM; + } else if ((iod->mc->phone_state == STATE_CRASH_RESET) || + (iod->mc->phone_state == STATE_CRASH_EXIT) || + (iod->mc->phone_state == STATE_NV_REBUILDING) || + (iod->mc->sim_state.changed)) { + if (iod->format == IPC_RAW) { + msleep(20); + return 0; + } + return POLLHUP; + } else { + return 0; + } +} + +static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int p_state; + struct io_device *iod = (struct io_device *)filp->private_data; + struct link_device *ld = get_current_link(iod); + char cpinfo_buf[530] = "CP Crash "; + unsigned long size; + int ret; + char str[TASK_COMM_LEN]; + + mif_debug("cmd = 0x%x\n", cmd); + + switch (cmd) { + case IOCTL_MODEM_ON: + mif_debug("misc_ioctl : IOCTL_MODEM_ON\n"); + return iod->mc->ops.modem_on(iod->mc); + + case IOCTL_MODEM_OFF: + mif_debug("misc_ioctl : IOCTL_MODEM_OFF\n"); + return iod->mc->ops.modem_off(iod->mc); + + case IOCTL_MODEM_RESET: + mif_debug("misc_ioctl : IOCTL_MODEM_RESET\n"); + return iod->mc->ops.modem_reset(iod->mc); + + case IOCTL_MODEM_BOOT_ON: + mif_debug("misc_ioctl : IOCTL_MODEM_BOOT_ON\n"); + return iod->mc->ops.modem_boot_on(iod->mc); + + case IOCTL_MODEM_BOOT_OFF: + mif_debug("misc_ioctl : IOCTL_MODEM_BOOT_OFF\n"); + return iod->mc->ops.modem_boot_off(iod->mc); + + /* TODO - will remove this command after ril updated */ + case IOCTL_MODEM_BOOT_DONE: + mif_debug("misc_ioctl : IOCTL_MODEM_BOOT_DONE\n"); + return 0; + + case IOCTL_MODEM_STATUS: + mif_debug("misc_ioctl : IOCTL_MODEM_STATUS\n"); + + p_state = iod->mc->phone_state; + if ((p_state == STATE_CRASH_RESET) || + (p_state == STATE_CRASH_EXIT)) { + mif_err("<%s> send err state : %d\n", + iod->name, p_state); + } else if (iod->mc->sim_state.changed && + !strcmp(get_task_comm(str, get_current()), "rild")) { + int s_state = iod->mc->sim_state.online ? + STATE_SIM_ATTACH : STATE_SIM_DETACH; + iod->mc->sim_state.changed = false; + + mif_info("SIM states (%d) to %s\n", s_state, str); + return s_state; + } else if (p_state == STATE_NV_REBUILDING) { + mif_info("send nv rebuild state : %d\n", + p_state); + iod->mc->phone_state = STATE_ONLINE; + } + return p_state; + + case IOCTL_MODEM_PROTOCOL_SUSPEND: + mif_info("misc_ioctl : IOCTL_MODEM_PROTOCOL_SUSPEND\n"); + + if (iod->format != IPC_MULTI_RAW) + return -EINVAL; + + iodevs_for_each(iod->msd, iodev_netif_stop, 0); + return 0; + + case IOCTL_MODEM_PROTOCOL_RESUME: + mif_info("misc_ioctl : IOCTL_MODEM_PROTOCOL_RESUME\n"); + + if (iod->format != IPC_MULTI_RAW) + return -EINVAL; + + iodevs_for_each(iod->msd, iodev_netif_wake, 0); + return 0; + + case IOCTL_MODEM_DUMP_START: + mif_err("misc_ioctl : IOCTL_MODEM_DUMP_START\n"); + return ld->dump_start(ld, iod); + + case IOCTL_MODEM_DUMP_UPDATE: + mif_debug("misc_ioctl : IOCTL_MODEM_DUMP_UPDATE\n"); + return ld->dump_update(ld, iod, arg); + + case IOCTL_MODEM_FORCE_CRASH_EXIT: + mif_debug("misc_ioctl : IOCTL_MODEM_FORCE_CRASH_EXIT\n"); + if (iod->mc->ops.modem_force_crash_exit) + return iod->mc->ops.modem_force_crash_exit(iod->mc); + return -EINVAL; + + case IOCTL_MODEM_CP_UPLOAD: + mif_err("misc_ioctl : IOCTL_MODEM_CP_UPLOAD\n"); + if (copy_from_user(cpinfo_buf + strlen(cpinfo_buf), + (void __user *)arg, MAX_CPINFO_SIZE) != 0) + panic("CP Crash"); + else + panic(cpinfo_buf); + return 0; + + case IOCTL_MODEM_DUMP_RESET: + mif_err("misc_ioctl : IOCTL_MODEM_DUMP_RESET\n"); + return iod->mc->ops.modem_dump_reset(iod->mc); + + case IOCTL_MIF_LOG_DUMP: + size = MAX_MIF_BUFF_SIZE; + ret = copy_to_user((void __user *)arg, &size, + sizeof(unsigned long)); + if (ret < 0) + return -EFAULT; + + mif_dump_log(iod->mc->msd, iod); + return 0; + + case IOCTL_MIF_DPRAM_DUMP: +#ifdef CONFIG_LINK_DEVICE_DPRAM + if (iod->mc->mdm_data->link_types & LINKTYPE(LINKDEV_DPRAM)) { + size = iod->mc->mdm_data->dpram_ctl->dp_size; + ret = copy_to_user((void __user *)arg, &size, + sizeof(unsigned long)); + if (ret < 0) + return -EFAULT; + mif_dump_dpram(iod); + return 0; + } +#endif + return -EINVAL; + + default: + /* If you need to handle the ioctl for specific link device, + * then assign the link ioctl handler to ld->ioctl + * It will be call for specific link ioctl */ + if (ld->ioctl) + return ld->ioctl(ld, iod, cmd, arg); + + mif_err("misc_ioctl : ioctl 0x%X is not defined.\n", cmd); + return -EINVAL; + } + return 0; +} + +static ssize_t misc_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct link_device *ld = get_current_link(iod); + int frame_len = 0; + char frame_header_buf[sizeof(struct raw_hdr)]; + struct sk_buff *skb; + int err; + size_t tx_size; + + /* TODO - check here flow control for only raw data */ + + frame_len = SIZE_OF_HDLC_START + + get_header_size(iod) + + count + + SIZE_OF_HDLC_END; + if (ld->aligned) + frame_len += MAX_LINK_PADDING_SIZE; + + skb = alloc_skb(frame_len, GFP_KERNEL); + if (!skb) { + mif_err("fail alloc skb (%d)\n", __LINE__); + return -ENOMEM; + } + + switch (iod->format) { + case IPC_BOOT: + case IPC_RAMDUMP: + if (copy_from_user(skb_put(skb, count), buf, count) != 0) { + dev_kfree_skb_any(skb); + return -EFAULT; + } + break; + + case IPC_RFS: + memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start, + SIZE_OF_HDLC_START); + if (copy_from_user(skb_put(skb, count), buf, count) != 0) { + dev_kfree_skb_any(skb); + return -EFAULT; + } + memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end, + SIZE_OF_HDLC_END); + break; + + default: + memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start, + SIZE_OF_HDLC_START); + memcpy(skb_put(skb, get_header_size(iod)), + get_header(iod, count, frame_header_buf), + get_header_size(iod)); + if (copy_from_user(skb_put(skb, count), buf, count) != 0) { + dev_kfree_skb_any(skb); + return -EFAULT; + } + memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end, + SIZE_OF_HDLC_END); + break; + } + + skb_put(skb, calc_padding_size(iod, ld, skb->len)); + +#if 0 + if (iod->format == IPC_FMT) { + mif_err("\n<%s> Tx HDLC FMT frame (len %d)\n", + iod->name, skb->len); + print_sipc4_hdlc_fmt_frame(skb->data); + mif_err("\n"); + } +#endif +#if 0 + if (iod->format == IPC_RAW) { + mif_err("\n<%s> Tx HDLC RAW frame (len %d)\n", + iod->name, skb->len); + mif_print_data(skb->data, (skb->len < 64 ? skb->len : 64)); + mif_err("\n"); + } +#endif +#if 0 + if (iod->format == IPC_RFS) { + mif_err("\n<%s> Tx HDLC RFS frame (len %d)\n", + iod->name, skb->len); + mif_print_data(skb->data, (skb->len < 64 ? skb->len : 64)); + mif_err("\n"); + } +#endif + + /* send data with sk_buff, link device will put sk_buff + * into the specific sk_buff_q and run work-q to send data + */ + tx_size = skb->len; + + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + + err = ld->send(ld, iod, skb); + if (err < 0) { + dev_kfree_skb_any(skb); + return err; + } + + if (err != tx_size) + mif_err("WARNNING: wrong tx size: %s, format=%d " + "count=%d, tx_size=%d, return_size=%d", + iod->name, iod->format, count, tx_size, err); + + /* Temporaly enable t he RFS log for debugging IPC RX pedding issue */ + if (iod->format == IPC_RFS) + mif_info("write rfs size = %d\n", count); + + return count; +} + +static ssize_t misc_read(struct file *filp, char *buf, size_t count, + loff_t *f_pos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct sk_buff *skb = NULL; + int pktsize = 0; + unsigned int rest_len, copy_len; + char *cur = buf; + + skb = skb_dequeue(&iod->sk_rx_q); + if (!skb) { + mif_err("<%s> no data from sk_rx_q\n", iod->name); + return 0; + } + mif_debug("<%s> skb->len : %d\n", iod->name, skb->len); + + if (iod->format == IPC_BOOT) { + pktsize = rest_len = count; + while (rest_len) { + if (skb->len > rest_len) { + /* BOOT device receviced rx data as serial + stream, return data by User requested size */ + mif_err("skb->len %d > count %d\n", skb->len, + rest_len); + pr_skb("BOOT-wRX", skb); + if (copy_to_user(cur, skb->data, rest_len) + != 0) { + dev_kfree_skb_any(skb); + return -EFAULT; + } + cur += rest_len; + skb_pull(skb, rest_len); + if (skb->len) { + mif_info("queue-head, skb->len = %d\n", + skb->len); + skb_queue_head(&iod->sk_rx_q, skb); + } + mif_debug("return %u\n", rest_len); + return rest_len; + } + + copy_len = min(rest_len, skb->len); + if (copy_to_user(cur, skb->data, copy_len) != 0) { + dev_kfree_skb_any(skb); + return -EFAULT; + } + cur += skb->len; + dev_kfree_skb_any(skb); + rest_len -= copy_len; + + if (!rest_len) + break; + + skb = skb_dequeue(&iod->sk_rx_q); + if (!skb) { + mif_err("<%s> %d / %d sk_rx_q\n", iod->name, + (count - rest_len), count); + return count - rest_len; + } + } + } else { + if (skb->len > count) { + mif_err("<%s> skb->len %d > count %d\n", iod->name, + skb->len, count); + dev_kfree_skb_any(skb); + return -EFAULT; + } + pktsize = skb->len; + if (copy_to_user(buf, skb->data, pktsize) != 0) { + dev_kfree_skb_any(skb); + return -EFAULT; + } + if (iod->format == IPC_FMT) + mif_debug("copied %d bytes to user\n", pktsize); + + dev_kfree_skb_any(skb); + } + return pktsize; +} + +#ifdef CONFIG_LINK_DEVICE_C2C +static int misc_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int r = 0; + unsigned long size = 0; + unsigned long pfn = 0; + unsigned long offset = 0; + struct io_device *iod = (struct io_device *)filp->private_data; + + if (!vma) + return -EFAULT; + + size = vma->vm_end - vma->vm_start; + offset = vma->vm_pgoff << PAGE_SHIFT; + if (offset + size > (C2C_CP_RGN_SIZE + C2C_SH_RGN_SIZE)) { + mif_err("offset + size > C2C_CP_RGN_SIZE\n"); + return -EINVAL; + } + + /* Set the noncacheable property to the region */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + vma->vm_flags |= VM_RESERVED | VM_IO; + + pfn = __phys_to_pfn(C2C_CP_RGN_ADDR + offset); + r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); + if (r) { + mif_err("Failed in remap_pfn_range()!!!\n"); + return -EAGAIN; + } + + mif_err("VA = 0x%08lx, offset = 0x%lx, size = %lu\n", + vma->vm_start, offset, size); + + return 0; +} +#endif + +static const struct file_operations misc_io_fops = { + .owner = THIS_MODULE, + .open = misc_open, + .release = misc_release, + .poll = misc_poll, + .unlocked_ioctl = misc_ioctl, + .write = misc_write, + .read = misc_read, +#ifdef CONFIG_LINK_DEVICE_C2C + .mmap = misc_mmap, +#endif +}; + +static int vnet_open(struct net_device *ndev) +{ + struct vnet *vnet = netdev_priv(ndev); + netif_start_queue(ndev); + atomic_inc(&vnet->iod->opened); + return 0; +} + +static int vnet_stop(struct net_device *ndev) +{ + struct vnet *vnet = netdev_priv(ndev); + atomic_dec(&vnet->iod->opened); + netif_stop_queue(ndev); + return 0; +} + +static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + int ret = 0; + int headroom = 0; + int tailroom = 0; + struct sk_buff *skb_new = NULL; + struct vnet *vnet = netdev_priv(ndev); + struct io_device *iod = vnet->iod; + struct link_device *ld = get_current_link(iod); + struct raw_hdr hd; + struct iphdr *ip_header = NULL; + + /* When use `handover' with Network Bridge, + * user -> TCP/IP(kernel) -> bridge device -> TCP/IP(kernel) -> this. + * + * We remove the one ethernet header of skb before using skb->len, + * because the skb has two ethernet headers. + */ + if (iod->use_handover) { + if (iod->id >= PSD_DATA_CHID_BEGIN && + iod->id <= PSD_DATA_CHID_END) + skb_pull(skb, sizeof(struct ethhdr)); + } + + /* ip loop-back */ + ip_header = (struct iphdr *)skb->data; + if (iod->msd->loopback_ipaddr && + ip_header->daddr == iod->msd->loopback_ipaddr) { + swap(ip_header->saddr, ip_header->daddr); + hd.channel = DATA_LOOPBACK_CHANNEL; + } else { + hd.channel = iod->id & 0x1F; + } + hd.len = skb->len + sizeof(hd); + hd.control = 0; + + headroom = sizeof(hd) + sizeof(hdlc_start); + tailroom = sizeof(hdlc_end); + if (ld->aligned) + tailroom += MAX_LINK_PADDING_SIZE; + if (skb_headroom(skb) < headroom || skb_tailroom(skb) < tailroom) { + skb_new = skb_copy_expand(skb, headroom, tailroom, GFP_ATOMIC); + /* skb_copy_expand success or not, free old skb from caller */ + dev_kfree_skb_any(skb); + if (!skb_new) + return -ENOMEM; + } else + skb_new = skb; + + memcpy(skb_push(skb_new, sizeof(hd)), &hd, sizeof(hd)); + memcpy(skb_push(skb_new, sizeof(hdlc_start)), hdlc_start, + sizeof(hdlc_start)); + memcpy(skb_put(skb_new, sizeof(hdlc_end)), hdlc_end, sizeof(hdlc_end)); + skb_put(skb_new, calc_padding_size(iod, ld, skb_new->len)); + + skbpriv(skb_new)->iod = iod; + skbpriv(skb_new)->ld = ld; + + ret = ld->send(ld, iod, skb_new); + if (ret < 0) { + netif_stop_queue(ndev); + dev_kfree_skb_any(skb_new); + return NETDEV_TX_BUSY; + } + + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += skb->len; + + return NETDEV_TX_OK; +} + +static struct net_device_ops vnet_ops = { + .ndo_open = vnet_open, + .ndo_stop = vnet_stop, + .ndo_start_xmit = vnet_xmit, +}; + +static void vnet_setup(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_PPP; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + ndev->addr_len = 0; + ndev->hard_header_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +static void vnet_setup_ether(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_ETHER; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST | IFF_SLAVE; + ndev->addr_len = ETH_ALEN; + random_ether_addr(ndev->dev_addr); + ndev->hard_header_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +int sipc4_init_io_device(struct io_device *iod) +{ + int ret = 0; + struct vnet *vnet; + + /* Get modem state from modem control device */ + iod->modem_state_changed = io_dev_modem_state_changed; + + iod->sim_state_changed = io_dev_sim_state_changed; + + /* Get data from link device */ + iod->recv = io_dev_recv_data_from_link_dev; + + /* Register misc or net device */ + switch (iod->io_typ) { + case IODEV_MISC: + init_waitqueue_head(&iod->wq); + skb_queue_head_init(&iod->sk_rx_q); + INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + + ret = misc_register(&iod->miscdev); + if (ret) + mif_err("failed to register misc io device : %s\n", + iod->name); + + break; + + case IODEV_NET: + skb_queue_head_init(&iod->sk_rx_q); + if (iod->use_handover) + iod->ndev = alloc_netdev(0, iod->name, + vnet_setup_ether); + else + iod->ndev = alloc_netdev(0, iod->name, vnet_setup); + + if (!iod->ndev) { + mif_err("failed to alloc netdev\n"); + return -ENOMEM; + } + + ret = register_netdev(iod->ndev); + if (ret) + free_netdev(iod->ndev); + + mif_debug("(iod:0x%p)\n", iod); + vnet = netdev_priv(iod->ndev); + mif_debug("(vnet:0x%p)\n", vnet); + vnet->iod = iod; + + break; + + case IODEV_DUMMY: + skb_queue_head_init(&iod->sk_rx_q); + INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + + ret = misc_register(&iod->miscdev); + if (ret) + mif_err("failed to register misc io device : %s\n", + iod->name); + ret = device_create_file(iod->miscdev.this_device, + &attr_waketime); + if (ret) + mif_err("failed to create `waketime' file : %s\n", + iod->name); + ret = device_create_file(iod->miscdev.this_device, + &attr_loopback); + if (ret) + mif_err("failed to create `loopback file' : %s\n", + iod->name); + break; + + default: + mif_err("wrong io_type : %d\n", iod->io_typ); + return -EINVAL; + } + + mif_debug("%s(%d) : init_io_device() done : %d\n", + iod->name, iod->io_typ, ret); + return ret; +} + diff --git a/drivers/misc/modem_if/sipc4_modem.c b/drivers/misc/modem_if/sipc4_modem.c new file mode 100644 index 00000000000..58b3fafaf0c --- /dev/null +++ b/drivers/misc/modem_if/sipc4_modem.c @@ -0,0 +1,371 @@ +/* linux/drivers/modem/modem.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> + +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#ifdef CONFIG_HAS_WAKELOCK +#include <linux/wakelock.h> +#endif + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_variation.h" +#include "modem_utils.h" + +#define FMT_WAKE_TIME (HZ/2) +#define RFS_WAKE_TIME (HZ*3) +#define RAW_WAKE_TIME (HZ*6) + +static struct modem_shared *create_modem_shared_data(void) +{ + struct modem_shared *msd; + + msd = kzalloc(sizeof(struct modem_shared), GFP_KERNEL); + if (!msd) + return NULL; + + /* initialize link device list */ + INIT_LIST_HEAD(&msd->link_dev_list); + + /* initialize tree of io devices */ + msd->iodevs_tree_chan = RB_ROOT; + msd->iodevs_tree_fmt = RB_ROOT; + + msd->storage.cnt = 0; + msd->storage.addr = + kzalloc(MAX_MIF_BUFF_SIZE + MAX_MIF_SEPA_SIZE, GFP_KERNEL); + if (!msd->storage.addr) { + mif_err("IPC logger buff alloc failed!!\n"); + return NULL; + } + memset(msd->storage.addr, 0, MAX_MIF_BUFF_SIZE); + memcpy(msd->storage.addr, MIF_SEPARATOR, MAX_MIF_SEPA_SIZE); + msd->storage.addr += MAX_MIF_SEPA_SIZE; + spin_lock_init(&msd->lock); + + return msd; +} + +static struct modem_ctl *create_modemctl_device(struct platform_device *pdev, + struct modem_shared *msd) +{ + int ret = 0; + struct modem_data *pdata; + struct modem_ctl *modemctl; + struct device *dev = &pdev->dev; + + /* create modem control device */ + modemctl = kzalloc(sizeof(struct modem_ctl), GFP_KERNEL); + if (!modemctl) + return NULL; + + modemctl->msd = msd; + modemctl->dev = dev; + modemctl->phone_state = STATE_OFFLINE; + + pdata = pdev->dev.platform_data; + modemctl->mdm_data = pdata; + modemctl->name = pdata->name; + + /* init modemctl device for getting modemctl operations */ + ret = call_modem_init_func(modemctl, pdata); + if (ret) { + kfree(modemctl); + return NULL; + } + + mif_info("%s is created!!!\n", pdata->name); + + return modemctl; +} + +static struct io_device *create_io_device(struct modem_io_t *io_t, + struct modem_shared *msd, struct modem_ctl *modemctl, + struct modem_data *pdata) +{ + int ret = 0; + struct io_device *iod = NULL; + + iod = kzalloc(sizeof(struct io_device), GFP_KERNEL); + if (!iod) { + mif_err("iod == NULL\n"); + return NULL; + } + + rb_init_node(&iod->node_chan); + rb_init_node(&iod->node_fmt); + + iod->name = io_t->name; + iod->id = io_t->id; + iod->format = io_t->format; + iod->io_typ = io_t->io_type; + iod->link_types = io_t->links; + iod->net_typ = pdata->modem_net; + iod->use_handover = pdata->use_handover; + iod->ipc_version = pdata->ipc_version; + atomic_set(&iod->opened, 0); + + /* link between io device and modem control */ + iod->mc = modemctl; + if (iod->format == IPC_FMT) + modemctl->iod = iod; + if (iod->format == IPC_BOOT) { + modemctl->bootd = iod; + mif_info("Bood device = %s\n", iod->name); + } + + /* link between io device and modem shared */ + iod->msd = msd; + + /* add iod to rb_tree */ + if (iod->format != IPC_RAW) + insert_iod_with_format(msd, iod->format, iod); + + if (sipc4_is_not_reserved_channel(iod->id)) + insert_iod_with_channel(msd, iod->id, iod); + + /* register misc device or net device */ + ret = sipc4_init_io_device(iod); + if (ret) { + kfree(iod); + mif_err("sipc4_init_io_device fail (%d)\n", ret); + return NULL; + } + + mif_debug("%s is created!!!\n", iod->name); + return iod; +} + +static int attach_devices(struct io_device *iod, enum modem_link tx_link) +{ + struct modem_shared *msd = iod->msd; + struct link_device *ld; + + /* find link type for this io device */ + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (IS_CONNECTED(iod, ld)) { + /* The count 1 bits of iod->link_types is count + * of link devices of this iod. + * If use one link device, + * or, 2+ link devices and this link is tx_link, + * set iod's link device with ld + */ + if ((countbits(iod->link_types) <= 1) || + (tx_link == ld->link_type)) { + mif_debug("set %s->%s\n", iod->name, ld->name); + + set_current_link(iod, ld); + + if (iod->ipc_version == SIPC_VER_42) { + if (iod->format == IPC_FMT) { + int ch = iod->id & 0x03; + ld->fmt_iods[ch] = iod; + } + } + } + } + } + + /* if use rx dynamic switch, set tx_link at modem_io_t of + * board-*-modems.c + */ + if (!get_current_link(iod)) { + mif_err("%s->link == NULL\n", iod->name); + BUG(); + } + + switch (iod->format) { + case IPC_FMT: +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); +#else + device_init_wakeup(iod->miscdev.this_device, true); +#endif + iod->waketime = FMT_WAKE_TIME; + break; + + case IPC_RFS: +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); +#else + device_init_wakeup(iod->miscdev.this_device, true); +#endif + iod->waketime = RFS_WAKE_TIME; + break; + + case IPC_MULTI_RAW: +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); +#else + device_init_wakeup(iod->miscdev.this_device, true); +#endif + iod->waketime = RAW_WAKE_TIME; + break; + case IPC_BOOT: +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); +#else + device_init_wakeup(iod->miscdev.this_device, true); +#endif + iod->waketime = 3 * HZ; + default: + break; + } + + return 0; +} + +static int __devinit modem_probe(struct platform_device *pdev) +{ + int i; + struct modem_data *pdata = pdev->dev.platform_data; + struct modem_shared *msd = NULL; + struct modem_ctl *modemctl = NULL; + struct io_device *iod[pdata->num_iodevs]; + struct link_device *ld; + + mif_err("%s\n", pdev->name); + memset(iod, 0, sizeof(iod)); + + msd = create_modem_shared_data(); + if (!msd) { + mif_err("msd == NULL\n"); + goto err_free_modemctl; + } + + modemctl = create_modemctl_device(pdev, msd); + if (!modemctl) { + mif_err("modemctl == NULL\n"); + goto err_free_modemctl; + } + + /* create link device */ + /* support multi-link device */ + for (i = 0; i < LINKDEV_MAX ; i++) { + /* find matching link type */ + if (pdata->link_types & LINKTYPE(i)) { + ld = call_link_init_func(pdev, i); + if (!ld) + goto err_free_modemctl; + + mif_err("link created: %s\n", ld->name); + ld->link_type = i; + ld->mc = modemctl; + ld->msd = msd; + list_add(&ld->list, &msd->link_dev_list); + } + } + + /* create io deivces and connect to modemctl device */ + for (i = 0; i < pdata->num_iodevs; i++) { + iod[i] = create_io_device(&pdata->iodevs[i], msd, modemctl, + pdata); + if (!iod[i]) { + mif_err("iod[%d] == NULL\n", i); + goto err_free_modemctl; + } + + attach_devices(iod[i], pdata->iodevs[i].tx_link); + } + + platform_set_drvdata(pdev, modemctl); + + mif_info("Complete!!!\n"); + return 0; + +err_free_modemctl: + for (i = 0; i < pdata->num_iodevs; i++) + if (iod[i] != NULL) + kfree(iod[i]); + + if (modemctl != NULL) + kfree(modemctl); + + if (msd != NULL) + kfree(msd); + + return -ENOMEM; +} + +static void modem_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct modem_ctl *mc = dev_get_drvdata(dev); + mc->ops.modem_off(mc); + mc->phone_state = STATE_OFFLINE; +} + +static int modem_suspend(struct device *pdev) +{ +#ifndef CONFIG_LINK_DEVICE_HSIC + struct modem_ctl *mc = dev_get_drvdata(pdev); + + if (mc->gpio_pda_active) + gpio_set_value(mc->gpio_pda_active, 0); +#endif + + return 0; +} + +static int modem_resume(struct device *pdev) +{ +#ifndef CONFIG_LINK_DEVICE_HSIC + struct modem_ctl *mc = dev_get_drvdata(pdev); + + if (mc->gpio_pda_active) + gpio_set_value(mc->gpio_pda_active, 1); +#endif + + return 0; +} + +static const struct dev_pm_ops modem_pm_ops = { + .suspend = modem_suspend, + .resume = modem_resume, +}; + +static struct platform_driver modem_driver = { + .probe = modem_probe, + .shutdown = modem_shutdown, + .driver = { + .name = "modem_if", + .pm = &modem_pm_ops, + }, +}; + +static int __init modem_init(void) +{ + return platform_driver_register(&modem_driver); +} + +module_init(modem_init); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung Modem Interface Driver"); diff --git a/drivers/misc/modem_if/sipc5_io_device.c b/drivers/misc/modem_if/sipc5_io_device.c new file mode 100644 index 00000000000..9e22e9cb718 --- /dev/null +++ b/drivers/misc/modem_if/sipc5_io_device.c @@ -0,0 +1,1620 @@ +/* /linux/drivers/misc/modem_if/sipc5_io_device.c + * + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> +#include <linux/ip.h> +#include <linux/if_ether.h> +#include <linux/etherdevice.h> +#include <linux/device.h> +#include <linux/module.h> + +#include <linux/platform_data/modem.h> +#ifdef CONFIG_LINK_DEVICE_C2C +#include <linux/platform_data/c2c.h> +#endif +#include "modem_prj.h" +#include "modem_utils.h" + +static ssize_t show_waketime(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int msec; + char *p = buf; + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct io_device *iod = container_of(miscdev, struct io_device, + miscdev); + + msec = jiffies_to_msecs(iod->waketime); + + p += sprintf(buf, "raw waketime : %ums\n", msec); + + return p - buf; +} + +static ssize_t store_waketime(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long msec; + int ret; + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct io_device *iod = container_of(miscdev, struct io_device, + miscdev); + + ret = strict_strtoul(buf, 10, &msec); + if (ret) + return count; + + iod->waketime = msecs_to_jiffies(msec); + + return count; +} + +static struct device_attribute attr_waketime = + __ATTR(waketime, S_IRUGO | S_IWUSR, show_waketime, store_waketime); + +static ssize_t show_loopback(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + unsigned char *ip = (unsigned char *)&msd->loopback_ipaddr; + char *p = buf; + + p += sprintf(buf, "%u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]); + + return p - buf; +} + +static ssize_t store_loopback(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + + msd->loopback_ipaddr = ipv4str_to_be32(buf, count); + + return count; +} + +static struct device_attribute attr_loopback = + __ATTR(loopback, S_IRUGO | S_IWUSR, show_loopback, store_loopback); + +static void iodev_showtxlink(struct io_device *iod, void *args) +{ + char **p = (char **)args; + struct link_device *ld = get_current_link(iod); + + if (iod->io_typ == IODEV_NET && IS_CONNECTED(iod, ld)) + *p += sprintf(*p, "%s: %s\n", iod->name, ld->name); +} + +static ssize_t show_txlink(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + char *p = buf; + + iodevs_for_each(msd, iodev_showtxlink, &p); + + return p - buf; +} + +static ssize_t store_txlink(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + /* don't change without gpio dynamic switching */ + return -EINVAL; +} + +static struct device_attribute attr_txlink = + __ATTR(txlink, S_IRUGO | S_IWUSR, show_txlink, store_txlink); + +/** + * rx_check_frame_cfg + * @cfg: configuration field of a link layer header + * @frm: pointer to the sipc5_frame_data buffer + * + * 1) Checks whether or not an extended field exists + * 2) Calculates the length of a link layer header + * + * Returns the size of a link layer header + * + * Must be invoked only when the configuration field of the link layer header + * is validated with sipc5_start_valid() function + */ +static int rx_check_frame_cfg(u8 cfg, struct sipc5_frame_data *frm) +{ + frm->config = cfg; + + if (likely(cfg & SIPC5_PADDING_EXIST)) + frm->padding = true; + + if (unlikely(cfg & SIPC5_EXT_FIELD_EXIST)) { + if (cfg & SIPC5_CTL_FIELD_EXIST) { + frm->ctl_fld = true; + frm->hdr_len = SIPC5_HEADER_SIZE_WITH_CTL_FLD; + } else { + frm->ext_len = true; + frm->hdr_len = SIPC5_HEADER_SIZE_WITH_EXT_LEN; + } + } else { + frm->hdr_len = SIPC5_MIN_HEADER_SIZE; + } + + return frm->hdr_len; +} + +/** + * rx_build_meta_data + * @ld: pointer to the link device + * @frm: pointer to the sipc5_frame_data buffer + * + * Fills each field of sipc5_frame_data from a link layer header + * 1) Extracts the channel ID + * 2) Calculates the length of a link layer frame + * 3) Extracts a control field if exists + * 4) Calculates the length of an IPC message packet in the link layer frame + * + */ +static void rx_build_meta_data(struct link_device *ld, + struct sipc5_frame_data *frm) +{ + u16 *sz16 = (u16 *)(frm->hdr + SIPC5_LEN_OFFSET); + u32 *sz32 = (u32 *)(frm->hdr + SIPC5_LEN_OFFSET); + + frm->ch_id = frm->hdr[SIPC5_CH_ID_OFFSET]; + + if (unlikely(frm->ext_len)) + frm->len = *sz32; + else + frm->len = *sz16; + + if (unlikely(frm->ctl_fld)) + frm->control = frm->hdr[SIPC5_CTL_OFFSET]; + + frm->data_len = frm->len - frm->hdr_len; + + mif_debug("%s: FRM ch:%d len:%d ctl:%02X data.len:%d\n", + ld->name, frm->ch_id, frm->len, frm->control, frm->data_len); +} + +/** + * tx_build_link_header + * @frm: pointer to the sipc5_frame_data buffer + * @iod: pointer to the IO device + * @ld: pointer to the link device + * @count: length of the data to be transmitted + * + * Builds the meta data for an SIPC5 frame and the link layer header of it + * Returns the link layer header length for an SIPC5 frame or 0 for other frame + */ +static unsigned tx_build_link_header(struct sipc5_frame_data *frm, + struct io_device *iod, struct link_device *ld, ssize_t count) +{ + u8 *buff = frm->hdr; + u16 *sz16 = (u16 *)(buff + SIPC5_LEN_OFFSET); + u32 *sz32 = (u32 *)(buff + SIPC5_LEN_OFFSET); + + memset(frm, 0, sizeof(struct sipc5_frame_data)); + + if (iod->format == IPC_CMD || + iod->format == IPC_BOOT || + iod->format == IPC_RAMDUMP) { + frm->len = count; + return 0; + } + + frm->config = SIPC5_START_MASK; + + if (iod->format == IPC_FMT && count > 2048) { + frm->ctl_fld = true; + frm->config |= SIPC5_EXT_FIELD_EXIST; + frm->config |= SIPC5_CTL_FIELD_EXIST; + } + + if (iod->id >= SIPC5_CH_ID_RFS_0 && count > 0xFFFF) { + frm->ext_len = true; + frm->config |= SIPC5_EXT_FIELD_EXIST; + } + + if (ld->aligned) + frm->config |= SIPC5_PADDING_EXIST; + + frm->ch_id = iod->id; + + frm->hdr_len = sipc5_get_hdr_len(frm->config); + frm->data_len = count; + frm->len = frm->hdr_len + frm->data_len; + + buff[SIPC5_CONFIG_OFFSET] = frm->config; + buff[SIPC5_CH_ID_OFFSET] = frm->ch_id; + + if (unlikely(frm->ext_len)) + *sz32 = (u32)frm->len; + else + *sz16 = (u16)frm->len; + + if (unlikely(frm->ctl_fld)) + buff[SIPC5_CTL_OFFSET] = frm->control; + + return frm->hdr_len; +} + +static int rx_fmt_frame(struct sk_buff *skb) +{ + struct io_device *iod = skbpriv(skb)->iod; + struct link_device *ld = skbpriv(skb)->ld; + struct sk_buff_head *rxq = &iod->sk_rx_q; + struct sipc_fmt_hdr *fh; + struct sk_buff *rx_skb; + u8 ctrl = skbpriv(skb)->control; + unsigned id = ctrl & 0x7F; + + if (iod->skb[id] == NULL) { + /* + ** There has been no multiple frame with this ID. + */ + if ((ctrl & 0x80) == 0) { + /* + ** It is a single frame because the "more" bit is 0. + */ + skb_queue_tail(rxq, skb); + if (unlikely(rxq->qlen > 2048)) { + struct sk_buff *victim; + mif_info("%s: WARNING! rxq->qlen %d > 2048\n", + iod->name, rxq->qlen); + victim = skb_dequeue(rxq); + dev_kfree_skb_any(victim); + } else { + mif_debug("%s: rxq->qlen = %d\n", + iod->name, rxq->qlen); + } + + wake_up(&iod->wq); + return 0; + } + + /* + ** The start of multiple frames + */ + fh = (struct sipc_fmt_hdr *)skb->data; + mif_debug("%s: start multi-frame (ID:%d len:%d)\n", + iod->name, id, fh->len); + + rx_skb = rx_alloc_skb(fh->len, iod, ld); + if (!rx_skb) { + mif_info("%s: ERR! rx_alloc_skb fail\n", iod->name); + return -ENOMEM; + } + + iod->skb[id] = rx_skb; + } else { + rx_skb = iod->skb[id]; + } + + /* + ** Start multi-frame processing + */ + memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len); + dev_kfree_skb_any(skb); + + if (ctrl & 0x80) { + /* The last frame has not arrived yet. */ + mif_debug("%s: recv multi-frame (ID:%d rcvd:%d)\n", + iod->name, id, rx_skb->len); + } else { + /* It is the last frame because the "more" bit is 0. */ + mif_debug("%s: end multi-frame (ID:%d rcvd:%d)\n", + iod->name, id, rx_skb->len); + skb_queue_tail(rxq, rx_skb); + if (unlikely(rxq->qlen > 2048)) { + struct sk_buff *victim; + mif_info("%s: WARNING! rxq->qlen %d > 2048\n", + iod->name, rxq->qlen); + victim = skb_dequeue(rxq); + dev_kfree_skb_any(victim); + } else { + mif_debug("%s: rxq->qlen = %d\n", iod->name, rxq->qlen); + } + + iod->skb[id] = NULL; + wake_up(&iod->wq); + } + + return 0; +} + +static int rx_rfs_frame(struct sk_buff *skb) +{ + struct io_device *iod = skbpriv(skb)->iod; + struct sk_buff_head *rxq = &iod->sk_rx_q; + + skb_queue_tail(rxq, skb); + if (unlikely(rxq->qlen > 2048)) { + struct sk_buff *victim; + mif_debug("%s: rxq->qlen %d > 2048\n", iod->name, rxq->qlen); + victim = skb_dequeue(rxq); + dev_kfree_skb_any(victim); + } else { + mif_debug("%s: rxq->qlen %d\n", iod->name, rxq->qlen); + } + + wake_up(&iod->wq); + + return 0; +} + +static int rx_loopback(struct sk_buff *skb) +{ + struct io_device *iod = skbpriv(skb)->iod; + struct link_device *ld = get_current_link(iod); + struct sipc5_frame_data frm; + unsigned headroom; + unsigned tailroom = 0; + int ret; + + headroom = tx_build_link_header(&frm, iod, ld, skb->len); + + if (ld->aligned) + tailroom = sipc5_calc_padding_size(headroom + skb->len); + + /* We need not to expand skb in here. dev_alloc_skb (in rx_alloc_skb) + * already alloc 32bytes padding in headroom. 32bytes are enough. + */ + + /* store IPC link header to start of skb + * this is skb_push not skb_put. different with misc_write. + */ + memcpy(skb_push(skb, headroom), frm.hdr, headroom); + + /* store padding */ + if (tailroom) + skb_put(skb, tailroom); + + /* forward */ + ret = ld->send(ld, iod, skb); + if (ret < 0) + mif_err("%s->%s: ld->send fail: %d\n", iod->name, + ld->name, ret); + return ret; +} + +static int rx_raw_misc(struct sk_buff *skb) +{ + struct io_device *iod = skbpriv(skb)->iod; /* same with real_iod */ + struct sk_buff_head *rxq = &iod->sk_rx_q; + + skb_queue_tail(rxq, skb); + if (unlikely(rxq->qlen > 2048)) { + struct sk_buff *victim; + mif_debug("%s: rxq->qlen %d > 2048\n", iod->name, rxq->qlen); + victim = skb_dequeue(rxq); + dev_kfree_skb_any(victim); + } else { + mif_debug("%s: rxq->qlen %d\n", iod->name, rxq->qlen); + } + + wake_up(&iod->wq); + + return 0; +} + +static int rx_multi_pdp(struct sk_buff *skb) +{ + struct io_device *iod = skbpriv(skb)->iod; /* same with real_iod */ + struct net_device *ndev; + struct iphdr *iphdr; + struct ethhdr *ehdr; + int ret; + const char source[ETH_ALEN] = SOURCE_MAC_ADDR; + + ndev = iod->ndev; + if (!ndev) { + mif_info("%s: ERR! no iod->ndev\n", iod->name); + return -ENODEV; + } + + skb->dev = ndev; + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += skb->len; + + /* check the version of IP */ + iphdr = (struct iphdr *)skb->data; + if (iphdr->version == IP6VERSION) + skb->protocol = htons(ETH_P_IPV6); + else + skb->protocol = htons(ETH_P_IP); + + if (iod->use_handover) { + skb_push(skb, sizeof(struct ethhdr)); + ehdr = (void *)skb->data; + memcpy(ehdr->h_dest, ndev->dev_addr, ETH_ALEN); + memcpy(ehdr->h_source, source, ETH_ALEN); + ehdr->h_proto = skb->protocol; + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb_reset_mac_header(skb); + + skb_pull(skb, sizeof(struct ethhdr)); + } + + if (in_interrupt()) + ret = netif_rx(skb); + else + ret = netif_rx_ni(skb); + + if (ret != NET_RX_SUCCESS) + mif_info("%s: ERR! netif_rx fail (err %d)\n", iod->name, ret); + + return ret; +} + +static int rx_demux(struct link_device *ld, struct sk_buff *skb) +{ + struct io_device *iod = NULL; + char *link = ld->name; + u8 ch = skbpriv(skb)->ch_id; + + if (unlikely(ch == SIPC5_CH_ID_MAX || ch == 0)) { + mif_info("%s: ERR! invalid ch# %d\n", link, ch); + return -ENODEV; + } + + /* IP loopback */ + if (ch == DATA_LOOPBACK_CHANNEL && ld->msd->loopback_ipaddr) + ch = RMNET0_CH_ID; + + iod = link_get_iod_with_channel(ld, ch); + if (unlikely(!iod)) { + mif_info("%s: ERR! no iod for ch# %d\n", link, ch); + return -ENODEV; + } + + skbpriv(skb)->ld = ld; + skbpriv(skb)->iod = iod; + skbpriv(skb)->real_iod = iod; + + /* don't care about CP2AP_LOOPBACK_CHANNEL is opened */ + if (unlikely(iod->id == CP2AP_LOOPBACK_CHANNEL)) + return rx_loopback(skb); + + if (atomic_read(&iod->opened) <= 0) { + mif_info("%s: ERR! %s is not opened\n", link, iod->name); + return -ENODEV; + } + + if (ch >= SIPC5_CH_ID_RFS_0) + return rx_rfs_frame(skb); + else if (ch >= SIPC5_CH_ID_FMT_0) + return rx_fmt_frame(skb); + else if (iod->io_typ == IODEV_MISC) + return rx_raw_misc(skb); + else + return rx_multi_pdp(skb); +} + +/* Check and store link layer header, then alloc an skb */ +static int rx_header_from_serial(struct io_device *iod, struct link_device *ld, + u8 *buff, unsigned size, struct sipc5_frame_data *frm) +{ + char *link = ld->name; + struct sk_buff *skb; + int len; + u8 cfg = buff[0]; + + mif_debug("%s: size %d\n", link, size); + + if (!frm->config) { + if (unlikely(!sipc5_start_valid(cfg))) { + mif_info("%s: ERR! wrong start (0x%02x)\n", link, cfg); + return -EBADMSG; + } + rx_check_frame_cfg(cfg, frm); + + /* Copy the link layer header to the header buffer */ + len = min(frm->hdr_len, size); + memcpy(frm->hdr, buff, len); + } else { + /* Copy the link layer header to the header buffer */ + len = min((frm->hdr_len - frm->hdr_rcvd), size); + memcpy((frm->hdr + frm->hdr_rcvd), buff, len); + } + + frm->hdr_rcvd += len; + + mif_debug("%s: FRM hdr_len:%d, hdr_rcvd:%d\n", + link, frm->hdr_len, frm->hdr_rcvd); + + if (frm->hdr_rcvd >= frm->hdr_len) { + rx_build_meta_data(ld, frm); + skb = rx_alloc_skb(frm->data_len, iod, ld); + fragdata(iod, ld)->skb_recv = skb; + skbpriv(skb)->ch_id = frm->ch_id; + skbpriv(skb)->control = frm->control; + } + + return len; +} + +/* copy data to skb */ +static int rx_payload_from_serial(struct io_device *iod, struct link_device *ld, + u8 *buff, unsigned size, struct sipc5_frame_data *frm) +{ + struct sk_buff *skb = fragdata(iod, ld)->skb_recv; + char *link = ld->name; + unsigned rest = frm->data_len - frm->data_rcvd; + unsigned len; + + /* rest == (frm->data_len - frm->data_rcvd) == tailroom of skb */ + rest = frm->data_len - frm->data_rcvd; + mif_debug("%s: FRM data.len:%d data.rcvd:%d rest:%d size:%d\n", + link, frm->data_len, frm->data_rcvd, rest, size); + + /* If there is no skb, data must be dropped. */ + len = min(rest, size); + if (skb) + memcpy(skb_put(skb, len), buff, len); + + frm->data_rcvd += len; + + mif_debug("%s: FRM data_len:%d, data_rcvd:%d\n", + link, frm->data_len, frm->data_rcvd); + + return len; +} + +static int rx_frame_from_serial(struct io_device *iod, struct link_device *ld, + const char *data, unsigned size) +{ + struct sipc5_frame_data *frm = &fragdata(iod, ld)->f_data; + struct sk_buff *skb; + char *link = ld->name; + u8 *buff = (u8 *)data; + int rest = (int)size; + int err = 0; + int done = 0; + + mif_debug("%s: size = %d\n", link, size); + + if (frm->hdr_rcvd >= frm->hdr_len && frm->data_rcvd < frm->data_len) { + /* + ** There is an skb that is waiting for more SIPC5 data. + ** In this case, rx_header_from_serial() must be skipped. + */ + mif_debug("%s: FRM data.len:%d data.rcvd:%d -> recv_data\n", + link, frm->data_len, frm->data_rcvd); + goto recv_data; + } + +next_frame: + /* Receive and analyze header, then prepare an akb */ + err = done = rx_header_from_serial(iod, ld, buff, rest, frm); + if (err < 0) + goto err_exit; + + buff += done; + rest -= done; + mif_debug("%s: rx_header() -> done:%d rest:%d\n", link, done, rest); + if (rest < 0) + goto err_range; + + if (rest == 0) + return size; + +recv_data: + err = 0; + + mif_debug("%s: done:%d rest:%d -> rx_payload()\n", link, done, rest); + + done = rx_payload_from_serial(iod, ld, buff, rest, frm); + buff += done; + rest -= done; + + mif_debug("%s: rx_payload() -> done:%d rest:%d\n", link, done, rest); + + if (rest == 0 && frm->data_rcvd < frm->data_len) { + /* + Data is being received and more data will come within the next + frame from the link device. + */ + return size; + } + + /* At this point, one complete link layer frame has been received. */ + + /* A padding size is applied to access the next IPC frame. */ + if (frm->padding) { + done = sipc5_calc_padding_size(frm->len); + if (done > rest) { + mif_info("%s: ERR! padding %d > rest %d\n", + link, done, rest); + goto err_exit; + } + + buff += done; + rest -= done; + + mif_debug("%s: padding:%d -> rest:%d\n", link, done, rest); + + if (rest < 0) + goto err_range; + + } + + skb = fragdata(iod, ld)->skb_recv; + if (likely(skb)) { + mif_debug("%s: len:%d -> rx_demux()\n", link, skb->len); + err = rx_demux(ld, skb); + if (err < 0) + dev_kfree_skb_any(skb); + } else { + mif_debug("%s: len:%d -> drop\n", link, skb->len); + } + + /* initialize the skb_recv and the frame_data buffer */ + fragdata(iod, ld)->skb_recv = NULL; + memset(frm, 0, sizeof(struct sipc5_frame_data)); + + if (rest > 0) + goto next_frame; + + if (rest <= 0) + return size; + +err_exit: + if (fragdata(iod, ld)->skb_recv && + frm->hdr_rcvd >= frm->hdr_len && frm->data_rcvd >= frm->data_len) { + dev_kfree_skb_any(fragdata(iod, ld)->skb_recv); + memset(frm, 0, sizeof(struct sipc5_frame_data)); + fragdata(iod, ld)->skb_recv = NULL; + mif_info("%s: ERR! clear frag\n", link); + } + return err; + +err_range: + mif_info("%s: ERR! size:%d vs. rest:%d\n", link, size, rest); + return size; +} + +/** + * rx_header_from_mem + * @ld: pointer to the link device + * @buff: pointer to the frame + * @rest: size of the frame + * @frm: pointer to the sipc5_frame_data buffer + * + * 1) Verifies a link layer header configuration of a frame + * 2) Stores the link layer header to the header buffer + * 3) Builds and stores the meta data of the frame into a meta data buffer + * 4) Verifies the length of the frame + * + * Returns SIPC5 header length + */ +static int rx_header_from_mem(struct link_device *ld, u8 *buff, unsigned rest, + struct sipc5_frame_data *frm) +{ + char *link = ld->name; + u8 cfg = buff[0]; + + /* Verify link layer header configuration */ + if (unlikely(!sipc5_start_valid(cfg))) { + mif_info("%s: ERR! wrong start (0x%02x)\n", link, cfg); + return -EBADMSG; + } + rx_check_frame_cfg(cfg, frm); + + /* Store the link layer header to the header buffer */ + memcpy(frm->hdr, buff, frm->hdr_len); + frm->hdr_rcvd = frm->hdr_len; + + /* Build and store the meta data of this frame */ + rx_build_meta_data(ld, frm); + + /* Verify frame length */ + if (unlikely(frm->len > rest)) { + mif_info("%s: ERR! frame length %d > rest %d\n", + link, frm->len, rest); + return -EBADMSG; + } + + return frm->hdr_rcvd; +} + +/* copy data to skb */ +static int rx_payload_from_mem(struct sk_buff *skb, u8 *buff, unsigned len) +{ + /* If there is no skb, data must be dropped. */ + if (skb) + memcpy(skb_put(skb, len), buff, len); + return len; +} + +static int rx_frame_from_mem(struct io_device *iod, struct link_device *ld, + const char *data, unsigned size) +{ + struct sipc5_frame_data *frm = &fragdata(iod, ld)->f_data; + struct sk_buff *skb; + char *link = ld->name; + u8 *buff = (u8 *)data; + int rest = (int)size; + int len; + int done; + + mif_debug("%s: size = %d\n", link, size); + + while (rest > 0) { + /* Initialize the frame data buffer */ + memset(frm, 0, sizeof(struct sipc5_frame_data)); + skb = NULL; + + /* Receive and analyze link layer header */ + done = rx_header_from_mem(ld, buff, rest, frm); + if (unlikely(done < 0)) + return -EBADMSG; + + /* Verify rest size */ + rest -= done; + if (rest < 0) { + mif_info("%s: ERR! rx_header -> rest %d\n", link, rest); + return -ERANGE; + } + + /* Move buff pointer to the payload */ + buff += done; + + /* Prepare an akb */ + len = frm->data_len; + skb = rx_alloc_skb(len, iod, ld); + + /* Store channel ID and control fields to the CB of the skb */ + skbpriv(skb)->ch_id = frm->ch_id; + skbpriv(skb)->control = frm->control; + + /* Receive payload */ + mif_debug("%s: done:%d rest:%d len:%d -> rx_payload()\n", + link, done, rest, len); + done = rx_payload_from_mem(skb, buff, len); + rest -= done; + if (rest < 0) { + mif_info("%s: ERR! rx_payload() -> rest %d\n", + link, rest); + if (skb) + dev_kfree_skb_any(skb); + return -ERANGE; + } + buff += done; + + /* A padding size is applied to access the next IPC frame. */ + if (frm->padding) { + done = sipc5_calc_padding_size(frm->len); + if (done > rest) { + mif_info("%s: ERR! padding %d > rest %d\n", + link, done, rest); + if (skb) + dev_kfree_skb_any(skb); + return -ERANGE; + } + buff += done; + rest -= done; + } + + if (likely(skb)) { + mif_debug("%s: len:%d -> rx_demux()\n", link, skb->len); + if (rx_demux(ld, skb) < 0) + dev_kfree_skb_any(skb); + } else { + mif_debug("%s: len:%d -> drop\n", link, skb->len); + } + } + + return 0; +} + +/* called from link device when a packet arrives for this io device */ +static int io_dev_recv_data_from_link_dev(struct io_device *iod, + struct link_device *ld, const char *data, unsigned int len) +{ + struct sk_buff_head *rxq = &iod->sk_rx_q; + struct sk_buff *skb; + char *link = ld->name; + int err; + + if (!data) { + mif_info("%s: ERR! !data\n", link); + return -EINVAL; + } + + if (len <= 0) { + mif_info("%s: ERR! len %d <= 0\n", link, len); + return -EINVAL; + } + + switch (iod->format) { + case IPC_FMT: + case IPC_RAW: + case IPC_RFS: + case IPC_MULTI_RAW: + if (iod->waketime) + wake_lock_timeout(&iod->wakelock, iod->waketime); + + if (ld->link_type == LINKDEV_DPRAM && ld->aligned) + err = rx_frame_from_mem(iod, ld, data, len); + else + err = rx_frame_from_serial(iod, ld, data, len); + + if (err < 0) + mif_info("%s: ERR! rx_frame_from_link fail (err %d)\n", + link, err); + + return err; + + case IPC_CMD: + case IPC_BOOT: + case IPC_RAMDUMP: + /* save packet to sk_buff */ + skb = rx_alloc_skb(len, iod, ld); + if (!skb) { + mif_info("%s: ERR! rx_alloc_skb fail\n", link); + return -ENOMEM; + } + + mif_debug("%s: len:%d -> iod:%s\n", link, len, iod->name); + + memcpy(skb_put(skb, len), data, len); + skb_queue_tail(rxq, skb); + if (unlikely(rxq->qlen > 2048)) { + struct sk_buff *victim; + mif_info("%s: ERR! rxq->qlen %d > 2048\n", + iod->name, rxq->qlen); + victim = skb_dequeue(rxq); + dev_kfree_skb_any(victim); + } + wake_up(&iod->wq); + + return len; + + default: + mif_info("%s: ERR! unknown format %d\n", link, iod->format); + return -EINVAL; + } +} + +static int rx_frame_from_skb(struct io_device *iod, struct link_device *ld, + struct sk_buff *skb) +{ + struct sipc5_frame_data *frm = &fragdata(iod, ld)->f_data; + u8 cfg = skb->data[0]; + + /* Initialize the frame data buffer */ + memset(frm, 0, sizeof(struct sipc5_frame_data)); + + /* + ** The start of a link layer header has already been checked in the + ** link device. + */ + + /* Analyze the configuration of the link layer header */ + rx_check_frame_cfg(cfg, frm); + + /* Store the link layer header to the header buffer */ + memcpy(frm->hdr, skb->data, frm->hdr_len); + frm->hdr_rcvd = frm->hdr_len; + + /* Build and store the meta data of this frame */ + rx_build_meta_data(ld, frm); + + /* + ** The length of the frame has already been checked in the link device. + */ + + /* Trim the link layer header off the frame */ + skb_pull(skb, frm->hdr_len); + + /* Store channel ID and control fields to the CB of the skb */ + skbpriv(skb)->ch_id = frm->ch_id; + skbpriv(skb)->control = frm->control; + + /* Demux the frame */ + if (rx_demux(ld, skb) < 0) { + mif_err("%s: ERR! rx_demux fail\n", ld->name); + return -EINVAL; + } + + return 0; +} + +/* called from link device when a packet arrives for this io device */ +static int io_dev_recv_skb_from_link_dev(struct io_device *iod, + struct link_device *ld, struct sk_buff *skb) +{ + char *link = ld->name; + enum dev_format dev = iod->format; + int err; + + switch (dev) { + case IPC_FMT: + case IPC_RAW: + case IPC_RFS: + case IPC_MULTI_RAW: + if (iod->waketime) + wake_lock_timeout(&iod->wakelock, iod->waketime); + + err = rx_frame_from_skb(iod, ld, skb); + if (err < 0) { + dev_kfree_skb_any(skb); + mif_info("%s: ERR! rx_frame_from_skb fail (err %d)\n", + link, err); + } + + return err; + + default: + mif_info("%s: ERR! unknown device %d\n", link, dev); + return -EINVAL; + } +} + +/* inform the IO device that the modem is now online or offline or + * crashing or whatever... + */ +static void io_dev_modem_state_changed(struct io_device *iod, + enum modem_state state) +{ + mif_info("%s: %s state changed (state %d)\n", + iod->name, iod->mc->name, state); + + iod->mc->phone_state = state; + + if (state == STATE_CRASH_RESET || state == STATE_CRASH_EXIT || + state == STATE_NV_REBUILDING) + wake_up(&iod->wq); +} + +/** + * io_dev_sim_state_changed + * @iod: IPC's io_device + * @sim_online: SIM is online? + */ +static void io_dev_sim_state_changed(struct io_device *iod, bool sim_online) +{ + if (atomic_read(&iod->opened) == 0) { + mif_info("%s: ERR! not opened\n", iod->name); + } else if (iod->mc->sim_state.online == sim_online) { + mif_info("%s: SIM state not changed\n", iod->name); + } else { + iod->mc->sim_state.online = sim_online; + iod->mc->sim_state.changed = true; + mif_info("%s: SIM state changed {online %d, changed %d}\n", + iod->name, iod->mc->sim_state.online, + iod->mc->sim_state.changed); + wake_up(&iod->wq); + } +} + +static void iodev_dump_status(struct io_device *iod, void *args) +{ + if (iod->format == IPC_RAW && iod->io_typ == IODEV_NET) { + struct link_device *ld = get_current_link(iod); + mif_com_log(iod->mc->msd, "%s: %s\n", iod->name, ld->name); + } +} + +static int misc_open(struct inode *inode, struct file *filp) +{ + struct io_device *iod = to_io_device(filp->private_data); + struct modem_shared *msd = iod->msd; + struct link_device *ld; + int ret; + filp->private_data = (void *)iod; + + atomic_inc(&iod->opened); + + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (IS_CONNECTED(iod, ld) && ld->init_comm) { + ret = ld->init_comm(ld, iod); + if (ret < 0) { + mif_info("%s: init_comm fail(%d)\n", + ld->name, ret); + return ret; + } + } + } + + mif_err("%s (opened %d)\n", iod->name, atomic_read(&iod->opened)); + + return 0; +} + +static int misc_release(struct inode *inode, struct file *filp) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct modem_shared *msd = iod->msd; + struct link_device *ld; + + atomic_dec(&iod->opened); + skb_queue_purge(&iod->sk_rx_q); + + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (IS_CONNECTED(iod, ld) && ld->terminate_comm) + ld->terminate_comm(ld, iod); + } + + mif_err("%s (opened %d)\n", iod->name, atomic_read(&iod->opened)); + + return 0; +} + +static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + + poll_wait(filp, &iod->wq, wait); + + if (!skb_queue_empty(&iod->sk_rx_q) && + iod->mc->phone_state != STATE_OFFLINE) { + return POLLIN | POLLRDNORM; + } else if ((iod->mc->phone_state == STATE_CRASH_RESET) || + (iod->mc->phone_state == STATE_CRASH_EXIT) || + (iod->mc->phone_state == STATE_NV_REBUILDING) || + (iod->mc->sim_state.changed)) { + if (iod->format == IPC_RAW) { + msleep(20); + return 0; + } + return POLLHUP; + } else { + return 0; + } +} + +static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int p_state; + struct io_device *iod = (struct io_device *)filp->private_data; + struct link_device *ld = get_current_link(iod); + char cpinfo_buf[530] = "CP Crash "; + unsigned long size; + int ret; + + switch (cmd) { + case IOCTL_MODEM_ON: + mif_info("%s: IOCTL_MODEM_ON\n", iod->name); + return iod->mc->ops.modem_on(iod->mc); + + case IOCTL_MODEM_OFF: + mif_info("%s: IOCTL_MODEM_OFF\n", iod->name); + return iod->mc->ops.modem_off(iod->mc); + + case IOCTL_MODEM_RESET: + mif_info("%s: IOCTL_MODEM_RESET\n", iod->name); + return iod->mc->ops.modem_reset(iod->mc); + + case IOCTL_MODEM_BOOT_ON: + mif_info("%s: IOCTL_MODEM_BOOT_ON\n", iod->name); + return iod->mc->ops.modem_boot_on(iod->mc); + + case IOCTL_MODEM_BOOT_OFF: + mif_info("%s: IOCTL_MODEM_BOOT_OFF\n", iod->name); + return iod->mc->ops.modem_boot_off(iod->mc); + + case IOCTL_MODEM_BOOT_DONE: + mif_err("%s: IOCTL_MODEM_BOOT_DONE\n", iod->name); + if (iod->mc->ops.modem_boot_done) + return iod->mc->ops.modem_boot_done(iod->mc); + else + return 0; + + case IOCTL_MODEM_STATUS: + mif_debug("%s: IOCTL_MODEM_STATUS\n", iod->name); + + p_state = iod->mc->phone_state; + if ((p_state == STATE_CRASH_RESET) || + (p_state == STATE_CRASH_EXIT)) { + mif_info("%s: IOCTL_MODEM_STATUS (state %d)\n", + iod->name, p_state); + } else if (iod->mc->sim_state.changed) { + int s_state = iod->mc->sim_state.online ? + STATE_SIM_ATTACH : STATE_SIM_DETACH; + iod->mc->sim_state.changed = false; + return s_state; + } else if (p_state == STATE_NV_REBUILDING) { + mif_info("%s: IOCTL_MODEM_STATUS (state %d)\n", + iod->name, p_state); + iod->mc->phone_state = STATE_ONLINE; + } + return p_state; + + case IOCTL_MODEM_PROTOCOL_SUSPEND: + mif_debug("%s: IOCTL_MODEM_PROTOCOL_SUSPEND\n", + iod->name); + + if (iod->format != IPC_MULTI_RAW) + return -EINVAL; + + iodevs_for_each(iod->msd, iodev_netif_stop, 0); + return 0; + + case IOCTL_MODEM_PROTOCOL_RESUME: + mif_info("%s: IOCTL_MODEM_PROTOCOL_RESUME\n", + iod->name); + + if (iod->format != IPC_MULTI_RAW) + return -EINVAL; + + iodevs_for_each(iod->msd, iodev_netif_wake, 0); + return 0; + + case IOCTL_MODEM_DUMP_START: + mif_info("%s: IOCTL_MODEM_DUMP_START\n", iod->name); + return ld->dump_start(ld, iod); + + case IOCTL_MODEM_DUMP_UPDATE: + mif_debug("%s: IOCTL_MODEM_DUMP_UPDATE\n", iod->name); + return ld->dump_update(ld, iod, arg); + + case IOCTL_MODEM_FORCE_CRASH_EXIT: + mif_info("%s: IOCTL_MODEM_FORCE_CRASH_EXIT\n", iod->name); + if (iod->mc->ops.modem_force_crash_exit) + return iod->mc->ops.modem_force_crash_exit(iod->mc); + return -EINVAL; + + case IOCTL_MODEM_CP_UPLOAD: + mif_info("%s: IOCTL_MODEM_CP_UPLOAD\n", iod->name); + if (copy_from_user(cpinfo_buf + strlen(cpinfo_buf), + (void __user *)arg, MAX_CPINFO_SIZE) != 0) + return -EFAULT; + panic(cpinfo_buf); + return 0; + + case IOCTL_MODEM_DUMP_RESET: + mif_info("%s: IOCTL_MODEM_DUMP_RESET\n", iod->name); + return iod->mc->ops.modem_dump_reset(iod->mc); + + case IOCTL_MIF_LOG_DUMP: + iodevs_for_each(iod->msd, iodev_dump_status, 0); + size = MAX_MIF_BUFF_SIZE; + ret = copy_to_user((void __user *)arg, &size, + sizeof(unsigned long)); + if (ret < 0) + return -EFAULT; + + mif_dump_log(iod->mc->msd, iod); + return 0; + + case IOCTL_MIF_DPRAM_DUMP: +#ifdef CONFIG_LINK_DEVICE_DPRAM + if (iod->mc->mdm_data->link_types & LINKTYPE(LINKDEV_DPRAM)) { + size = iod->mc->mdm_data->dpram_ctl->dp_size; + ret = copy_to_user((void __user *)arg, &size, + sizeof(unsigned long)); + if (ret < 0) + return -EFAULT; + mif_dump_dpram(iod); + return 0; + } +#endif + return -EINVAL; + + default: + /* If you need to handle the ioctl for specific link device, + * then assign the link ioctl handler to ld->ioctl + * It will be call for specific link ioctl */ + if (ld->ioctl) + return ld->ioctl(ld, iod, cmd, arg); + + mif_info("%s: ERR! cmd 0x%X not defined.\n", iod->name, cmd); + return -EINVAL; + } + return 0; +} + +static ssize_t misc_write(struct file *filp, const char __user *data, + size_t count, loff_t *fpos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct link_device *ld = get_current_link(iod); + struct sk_buff *skb; + int ret; + unsigned headroom = 0; + unsigned tailroom = 0; + size_t tx_size; + struct sipc5_frame_data frm; + + if (iod->format <= IPC_RFS && iod->id == 0) + return -EINVAL; + + headroom = tx_build_link_header(&frm, iod, ld, count); + + if (ld->aligned) + tailroom = sipc5_calc_padding_size(headroom + count); + + tx_size = headroom + count + tailroom; + + skb = alloc_skb(tx_size, GFP_KERNEL); + if (!skb) { + mif_info("%s: ERR! alloc_skb fail (tx_size:%d)\n", + iod->name, tx_size); + return -ENOMEM; + } + + /* store IPC link header*/ + memcpy(skb_put(skb, headroom), frm.hdr, headroom); + + /* store IPC message */ + if (copy_from_user(skb_put(skb, count), data, count) != 0) { + if (skb) + dev_kfree_skb_any(skb); + return -EFAULT; + } + + if (iod->format == IPC_FMT) { + struct timespec epoch; + u8 *msg = (skb->data + headroom); +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + char str[MIF_MAX_STR_LEN]; + snprintf(str, MIF_MAX_STR_LEN, "%s: RL2MIF", iod->mc->name); + pr_ipc(str, msg, (count > 16 ? 16 : count)); +#endif + getnstimeofday(&epoch); + mif_time_log(iod->mc->msd, epoch, NULL, 0); + mif_ipc_log(MIF_IPC_RL2AP, iod->mc->msd, msg, count); + } + + /* store padding */ + if (tailroom) + skb_put(skb, tailroom); + + /* send data with sk_buff, link device will put sk_buff + * into the specific sk_buff_q and run work-q to send data + */ + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + + ret = ld->send(ld, iod, skb); + if (ret < 0) { + mif_info("%s: ERR! ld->send fail (err %d)\n", iod->name, ret); + return ret; + } + + if (ret != tx_size) + mif_info("%s: wrong tx size (count:%d tx_size:%d ret:%d)\n", + iod->name, count, tx_size, ret); + + return count; +} + +static ssize_t misc_read(struct file *filp, char *buf, size_t count, + loff_t *fpos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct sk_buff_head *rxq = &iod->sk_rx_q; + struct sk_buff *skb; + int copied = 0; + + skb = skb_dequeue(rxq); + if (!skb) { + mif_info("%s: ERR! no data in rxq\n", iod->name); + return 0; + } + + if (iod->format == IPC_FMT) { + struct timespec epoch; +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + char str[MIF_MAX_STR_LEN]; + snprintf(str, MIF_MAX_STR_LEN, "%s: MIF2RL", iod->mc->name); + pr_ipc(str, skb->data, (skb->len > 16 ? 16 : skb->len)); +#endif + getnstimeofday(&epoch); + mif_time_log(iod->mc->msd, epoch, NULL, 0); + mif_ipc_log(MIF_IPC_AP2RL, iod->mc->msd, skb->data, skb->len); + } + + copied = skb->len > count ? count : skb->len; + + if (copy_to_user(buf, skb->data, copied)) { + mif_info("%s: ERR! copy_to_user fail\n", iod->name); + dev_kfree_skb_any(skb); + return -EFAULT; + } + + mif_debug("%s: data:%d copied:%d qlen:%d\n", + iod->name, skb->len, copied, rxq->qlen); + + if (skb->len > count) { + skb_pull(skb, count); + skb_queue_head(rxq, skb); + } else { + dev_kfree_skb_any(skb); + } + + return copied; +} + +#ifdef CONFIG_LINK_DEVICE_C2C +static int misc_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int r = 0; + unsigned long size = 0; + unsigned long pfn = 0; + unsigned long offset = 0; + struct io_device *iod = (struct io_device *)filp->private_data; + + if (!vma) + return -EFAULT; + + size = vma->vm_end - vma->vm_start; + offset = vma->vm_pgoff << PAGE_SHIFT; + if (offset + size > (C2C_CP_RGN_SIZE + C2C_SH_RGN_SIZE)) { + mif_info("ERR: offset + size > C2C_CP_RGN_SIZE\n"); + return -EINVAL; + } + + /* Set the noncacheable property to the region */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + vma->vm_flags |= VM_RESERVED | VM_IO; + + pfn = __phys_to_pfn(C2C_CP_RGN_ADDR + offset); + r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); + if (r) { + mif_info("ERR: Failed in remap_pfn_range()!!!\n"); + return -EAGAIN; + } + + mif_info("%s: VA = 0x%08lx, offset = 0x%lx, size = %lu\n", + iod->name, vma->vm_start, offset, size); + + return 0; +} +#endif + +static const struct file_operations misc_io_fops = { + .owner = THIS_MODULE, + .open = misc_open, + .release = misc_release, + .poll = misc_poll, + .unlocked_ioctl = misc_ioctl, + .write = misc_write, + .read = misc_read, +#ifdef CONFIG_LINK_DEVICE_C2C + .mmap = misc_mmap, +#endif +}; + +static int vnet_open(struct net_device *ndev) +{ + struct vnet *vnet = netdev_priv(ndev); + + mif_err("%s\n", vnet->iod->name); + + netif_start_queue(ndev); + atomic_inc(&vnet->iod->opened); + return 0; +} + +static int vnet_stop(struct net_device *ndev) +{ + struct vnet *vnet = netdev_priv(ndev); + + mif_err("%s\n", vnet->iod->name); + + atomic_dec(&vnet->iod->opened); + netif_stop_queue(ndev); + skb_queue_purge(&vnet->iod->sk_rx_q); + return 0; +} + +static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct vnet *vnet = netdev_priv(ndev); + struct io_device *iod = vnet->iod; + struct link_device *ld = get_current_link(iod); + struct sk_buff *skb_new; + int ret; + unsigned headroom = 0; + unsigned tailroom = 0; + unsigned long tx_bytes = skb->len; + struct iphdr *ip_header = NULL; + struct sipc5_frame_data frm; + + /* When use `handover' with Network Bridge, + * user -> bridge device(rmnet0) -> real rmnet(xxxx_rmnet0) -> here. + * bridge device is ethernet device unlike xxxx_rmnet(net device). + * We remove the an ethernet header of skb before using skb->len, + * because bridge device added an ethernet header to skb. + */ + if (iod->use_handover) { + if (iod->id >= PS_DATA_CH_0 && iod->id <= PS_DATA_CH_LAST) + skb_pull(skb, sizeof(struct ethhdr)); + } + + headroom = tx_build_link_header(&frm, iod, ld, skb->len); + + /* ip loop-back */ + ip_header = (struct iphdr *)skb->data; + if (iod->msd->loopback_ipaddr && + ip_header->daddr == iod->msd->loopback_ipaddr) { + swap(ip_header->saddr, ip_header->daddr); + frm.ch_id = DATA_LOOPBACK_CHANNEL; + frm.hdr[SIPC5_CH_ID_OFFSET] = DATA_LOOPBACK_CHANNEL; + } + + if (ld->aligned) + tailroom = sipc5_calc_padding_size(frm.len); + + if (skb_headroom(skb) < headroom || skb_tailroom(skb) < tailroom) { + mif_debug("%s: skb_copy_expand needed\n", iod->name); + skb_new = skb_copy_expand(skb, headroom, tailroom, GFP_ATOMIC); + /* skb_copy_expand success or not, free old skb from caller */ + dev_kfree_skb_any(skb); + if (!skb_new) { + mif_info("%s: ERR! skb_copy_expand fail\n", iod->name); + return NETDEV_TX_BUSY; + } + } else { + skb_new = skb; + } + + memcpy(skb_push(skb_new, headroom), frm.hdr, headroom); + if (tailroom) + skb_put(skb_new, tailroom); + + skbpriv(skb_new)->iod = iod; + skbpriv(skb_new)->ld = ld; + + ret = ld->send(ld, iod, skb_new); + if (ret < 0) { + netif_stop_queue(ndev); + mif_info("%s: ERR! ld->send fail (err %d)\n", iod->name, ret); + return NETDEV_TX_BUSY; + } + + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += tx_bytes; + + return NETDEV_TX_OK; +} + +static struct net_device_ops vnet_ops = { + .ndo_open = vnet_open, + .ndo_stop = vnet_stop, + .ndo_start_xmit = vnet_xmit, +}; + +static void vnet_setup(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_PPP; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + ndev->addr_len = 0; + ndev->hard_header_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +static void vnet_setup_ether(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_ETHER; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST | IFF_SLAVE; + ndev->addr_len = ETH_ALEN; + random_ether_addr(ndev->dev_addr); + ndev->hard_header_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +int sipc5_init_io_device(struct io_device *iod) +{ + int ret = 0; + struct vnet *vnet; + + /* Get modem state from modem control device */ + iod->modem_state_changed = io_dev_modem_state_changed; + + iod->sim_state_changed = io_dev_sim_state_changed; + + /* Get data from link device */ + mif_debug("%s: SIPC version = %d\n", iod->name, iod->ipc_version); + iod->recv = io_dev_recv_data_from_link_dev; + iod->recv_skb = io_dev_recv_skb_from_link_dev; + + /* Register misc or net device */ + switch (iod->io_typ) { + case IODEV_MISC: + init_waitqueue_head(&iod->wq); + skb_queue_head_init(&iod->sk_rx_q); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + + ret = misc_register(&iod->miscdev); + if (ret) + mif_info("%s: ERR! misc_register failed\n", iod->name); + + break; + + case IODEV_NET: + skb_queue_head_init(&iod->sk_rx_q); + if (iod->use_handover) + iod->ndev = alloc_netdev(0, iod->name, + vnet_setup_ether); + else + iod->ndev = alloc_netdev(0, iod->name, vnet_setup); + + if (!iod->ndev) { + mif_info("%s: ERR! alloc_netdev fail\n", iod->name); + return -ENOMEM; + } + + ret = register_netdev(iod->ndev); + if (ret) { + mif_info("%s: ERR! register_netdev fail\n", iod->name); + free_netdev(iod->ndev); + } + + mif_debug("iod 0x%p\n", iod); + vnet = netdev_priv(iod->ndev); + mif_debug("vnet 0x%p\n", vnet); + vnet->iod = iod; + + break; + + case IODEV_DUMMY: + skb_queue_head_init(&iod->sk_rx_q); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + + ret = misc_register(&iod->miscdev); + if (ret) + mif_info("%s: ERR! misc_register fail\n", iod->name); + ret = device_create_file(iod->miscdev.this_device, + &attr_waketime); + if (ret) + mif_info("%s: ERR! device_create_file fail\n", + iod->name); + ret = device_create_file(iod->miscdev.this_device, + &attr_loopback); + if (ret) + mif_err("failed to create `loopback file' : %s\n", + iod->name); + ret = device_create_file(iod->miscdev.this_device, + &attr_txlink); + if (ret) + mif_err("failed to create `txlink file' : %s\n", + iod->name); + break; + + default: + mif_info("%s: ERR! wrong io_type %d\n", iod->name, iod->io_typ); + return -EINVAL; + } + + return ret; +} + diff --git a/drivers/misc/modem_if/sipc5_modem.c b/drivers/misc/modem_if/sipc5_modem.c new file mode 100644 index 00000000000..98987516d02 --- /dev/null +++ b/drivers/misc/modem_if/sipc5_modem.c @@ -0,0 +1,384 @@ +/* linux/drivers/modem/modem.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/if_arp.h> + +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/wakelock.h> + +#include <linux/platform_data/modem.h> +#include "modem_prj.h" +#include "modem_variation.h" +#include "modem_utils.h" + +#define FMT_WAKE_TIME (HZ/2) +#define RAW_WAKE_TIME (HZ*6) + +static struct modem_shared *create_modem_shared_data(void) +{ + struct modem_shared *msd; + int size = MAX_MIF_BUFF_SIZE; + + msd = kzalloc(sizeof(struct modem_shared), GFP_KERNEL); + if (!msd) + return NULL; + + /* initialize link device list */ + INIT_LIST_HEAD(&msd->link_dev_list); + + /* initialize tree of io devices */ + msd->iodevs_tree_chan = RB_ROOT; + msd->iodevs_tree_fmt = RB_ROOT; + + msd->storage.cnt = 0; + msd->storage.addr = kzalloc(MAX_MIF_BUFF_SIZE + + (MAX_MIF_SEPA_SIZE * 2), GFP_KERNEL); + if (!msd->storage.addr) { + mif_err("IPC logger buff alloc failed!!\n"); + return NULL; + } + memset(msd->storage.addr, 0, size + (MAX_MIF_SEPA_SIZE * 2)); + memcpy(msd->storage.addr, MIF_SEPARATOR, MAX_MIF_SEPA_SIZE); + msd->storage.addr += MAX_MIF_SEPA_SIZE; + memcpy(msd->storage.addr, &size, MAX_MIF_SEPA_SIZE); + msd->storage.addr += MAX_MIF_SEPA_SIZE; + spin_lock_init(&msd->lock); + + return msd; +} + +static struct modem_ctl *create_modemctl_device(struct platform_device *pdev, + struct modem_shared *msd) +{ + int ret = 0; + struct modem_data *pdata; + struct modem_ctl *modemctl; + struct device *dev = &pdev->dev; + + /* create modem control device */ + modemctl = kzalloc(sizeof(struct modem_ctl), GFP_KERNEL); + if (!modemctl) + return NULL; + + modemctl->msd = msd; + modemctl->dev = dev; + modemctl->phone_state = STATE_OFFLINE; + + pdata = pdev->dev.platform_data; + modemctl->mdm_data = pdata; + modemctl->name = pdata->name; + + /* init modemctl device for getting modemctl operations */ + ret = call_modem_init_func(modemctl, pdata); + if (ret) { + kfree(modemctl); + return NULL; + } + + mif_info("%s is created!!!\n", pdata->name); + + return modemctl; +} + +static struct io_device *create_io_device(struct modem_io_t *io_t, + struct modem_shared *msd, struct modem_ctl *modemctl, + struct modem_data *pdata) +{ + int ret = 0; + struct io_device *iod = NULL; + + iod = kzalloc(sizeof(struct io_device), GFP_KERNEL); + if (!iod) { + mif_err("iod == NULL\n"); + return NULL; + } + + rb_init_node(&iod->node_chan); + rb_init_node(&iod->node_fmt); + + iod->name = io_t->name; + iod->id = io_t->id; + iod->format = io_t->format; + iod->io_typ = io_t->io_type; + iod->link_types = io_t->links; + iod->net_typ = pdata->modem_net; + iod->use_handover = pdata->use_handover; + iod->ipc_version = pdata->ipc_version; + atomic_set(&iod->opened, 0); + + /* link between io device and modem control */ + iod->mc = modemctl; + if (iod->format == IPC_FMT) + modemctl->iod = iod; + if (iod->format == IPC_BOOT) { + modemctl->bootd = iod; + mif_info("Bood device = %s\n", iod->name); + } + + /* link between io device and modem shared */ + iod->msd = msd; + + /* add iod to rb_tree */ + if (iod->format != IPC_RAW) + insert_iod_with_format(msd, iod->format, iod); + + if (sipc5_is_not_reserved_channel(iod->id)) + insert_iod_with_channel(msd, iod->id, iod); + + /* register misc device or net device */ + ret = sipc5_init_io_device(iod); + if (ret) { + kfree(iod); + mif_err("sipc5_init_io_device fail (%d)\n", ret); + return NULL; + } + + mif_debug("%s is created!!!\n", iod->name); + return iod; +} + +static int attach_devices(struct io_device *iod, enum modem_link tx_link) +{ + struct modem_shared *msd = iod->msd; + struct link_device *ld; + unsigned ch; + + /* find link type for this io device */ + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (IS_CONNECTED(iod, ld)) { + /* The count 1 bits of iod->link_types is count + * of link devices of this iod. + * If use one link device, + * or, 2+ link devices and this link is tx_link, + * set iod's link device with ld + */ + if ((countbits(iod->link_types) <= 1) || + (tx_link == ld->link_type)) { + mif_debug("set %s->%s\n", iod->name, ld->name); + set_current_link(iod, ld); + } + } + } + + /* if use rx dynamic switch, set tx_link at modem_io_t of + * board-*-modems.c + */ + if (!get_current_link(iod)) { + mif_err("%s->link == NULL\n", iod->name); + BUG(); + } + + switch (iod->format) { + case IPC_FMT: + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + iod->waketime = FMT_WAKE_TIME; + break; + + case IPC_RAW: + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + iod->waketime = RAW_WAKE_TIME; + break; + + case IPC_RFS: + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + iod->waketime = RAW_WAKE_TIME; + break; + + case IPC_MULTI_RAW: + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + iod->waketime = RAW_WAKE_TIME; + break; + + case IPC_BOOT: + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + iod->waketime = RAW_WAKE_TIME; + break; + + default: + break; + } + + return 0; +} + +static int __devinit modem_probe(struct platform_device *pdev) +{ + int i; + struct modem_data *pdata = pdev->dev.platform_data; + struct modem_shared *msd = NULL; + struct modem_ctl *modemctl = NULL; + struct io_device *iod[pdata->num_iodevs]; + struct link_device *ld; + + mif_err("%s\n", pdev->name); + memset(iod, 0, sizeof(iod)); + + msd = create_modem_shared_data(); + if (!msd) { + mif_err("msd == NULL\n"); + goto err_free_modemctl; + } + + modemctl = create_modemctl_device(pdev, msd); + if (!modemctl) { + mif_err("modemctl == NULL\n"); + goto err_free_modemctl; + } + + /* create link device */ + /* support multi-link device */ + for (i = 0; i < LINKDEV_MAX ; i++) { + /* find matching link type */ + if (pdata->link_types & LINKTYPE(i)) { + ld = call_link_init_func(pdev, i); + if (!ld) + goto err_free_modemctl; + + mif_err("link created: %s\n", ld->name); + ld->link_type = i; + ld->mc = modemctl; + ld->msd = msd; + list_add(&ld->list, &msd->link_dev_list); + } + } + + /* create io deivces and connect to modemctl device */ + for (i = 0; i < pdata->num_iodevs; i++) { + iod[i] = create_io_device(&pdata->iodevs[i], msd, modemctl, + pdata); + if (!iod[i]) { + mif_err("iod[%d] == NULL\n", i); + goto err_free_modemctl; + } + + attach_devices(iod[i], pdata->iodevs[i].tx_link); + } + + platform_set_drvdata(pdev, modemctl); + + mif_err("Complete!!!\n"); + + return 0; + +err_free_modemctl: + for (i = 0; i < pdata->num_iodevs; i++) + if (iod[i] != NULL) + kfree(iod[i]); + + if (modemctl != NULL) + kfree(modemctl); + + if (msd != NULL) + kfree(msd); + + return -ENOMEM; +} + +static void modem_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct modem_ctl *mc = dev_get_drvdata(dev); + struct utc_time utc; + + mc->ops.modem_off(mc); + mc->phone_state = STATE_OFFLINE; + + get_utc_time(&utc); + mif_info("%s: at [%02d:%02d:%02d.%03d]\n", + mc->name, utc.hour, utc.min, utc.sec, utc.msec); +} + +static int modem_suspend(struct device *pdev) +{ +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + struct utc_time utc; +#endif + +#ifndef CONFIG_LINK_DEVICE_HSIC + struct modem_ctl *mc = dev_get_drvdata(pdev); + + if (mc->gpio_pda_active) { + gpio_set_value(mc->gpio_pda_active, 0); +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + get_utc_time(&utc); + mif_info("%s: at [%02d:%02d:%02d.%03d]\n", + mc->name, utc.hour, utc.min, utc.sec, utc.msec); +#endif + } +#endif + + return 0; +} + +static int modem_resume(struct device *pdev) +{ +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + struct utc_time utc; +#endif + +#ifndef CONFIG_LINK_DEVICE_HSIC + struct modem_ctl *mc = dev_get_drvdata(pdev); + + if (mc->gpio_pda_active) { + gpio_set_value(mc->gpio_pda_active, 1); +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + get_utc_time(&utc); + mif_info("%s: at [%02d:%02d:%02d.%03d]\n", + mc->name, utc.hour, utc.min, utc.sec, utc.msec); +#endif + } +#endif + + return 0; +} + +static const struct dev_pm_ops modem_pm_ops = { + .suspend = modem_suspend, + .resume = modem_resume, +}; + +static struct platform_driver modem_driver = { + .probe = modem_probe, + .shutdown = modem_shutdown, + .driver = { + .name = "mif_sipc5", + .pm = &modem_pm_ops, + }, +}; + +static int __init modem_init(void) +{ + return platform_driver_register(&modem_driver); +} + +module_init(modem_init); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung Modem Interface Driver"); |