diff options
Diffstat (limited to 'drivers/misc/modem_if/modem_link_device_dpram_ext_op.c')
-rw-r--r-- | drivers/misc/modem_if/modem_link_device_dpram_ext_op.c | 1175 |
1 files changed, 1175 insertions, 0 deletions
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 0000000..1475a46 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_dpram_ext_op.c @@ -0,0 +1,1175 @@ +/* + * 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->dp_base; + bt_map->buff = (u8 *)(dpld->dp_base + DP_BOOT_BUFF_OFFSET); + bt_map->size = dpld->dp_size - 4; +} + +static void cbp72_init_dl_map(struct dpram_link_device *dpld) +{ + dpld->dl_map.magic = (u32 *)dpld->dp_base; + dpld->dl_map.buff = (u8 *)(dpld->dp_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->dp_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->dump_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->dump_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) +static void cmc221_init_boot_map(struct dpram_link_device *dpld) +{ + struct dpram_boot_map *bt_map = &dpld->bt_map; + + bt_map->buff = dpld->dp_base; + bt_map->size = dpld->dp_size; + bt_map->req = (u32 *)(dpld->dp_base + DP_BOOT_REQ_OFFSET); + bt_map->resp = (u32 *)(dpld->dp_base + DP_BOOT_RESP_OFFSET); +} + +static void cmc221_init_dl_map(struct dpram_link_device *dpld) +{ + dpld->dl_map.magic = (u32 *)dpld->dp_base; + dpld->dl_map.buff = (u8 *)dpld->dp_base; +} + +static void cmc221_init_ul_map(struct dpram_link_device *dpld) +{ + dpld->ul_map.magic = (u32 *)dpld->dp_base; + dpld->ul_map.buff = (u8 *)dpld->dp_base; +} + +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 = dpld->dpctl->recv_msg(); + 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 = dpld->dpctl->recv_msg(); + + 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; + } + + dpld->dpctl->reset(); + 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->dp_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) + dpld->dpctl->send_msg((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->dp_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->dump_timer); + wake_lock(&dpld->wlock); + + dpld->dump_rcvd = 0; + iowrite32(CMC22x_CP_DUMP_MAGIC, dpld->ul_map.magic); + + init_completion(&dpld->dump_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 = dpld->dpctl->recv_msg(); + if (msg == CMC22x_CP_DUMP_END) { + complete_all(&dpld->dump_recv_done); + return; + } + + if (((dpld->dump_rcvd & 0x1) == 0) && (msg == CMC22x_1ST_BUFF_FULL)) { + complete_all(&dpld->dump_recv_done); + return; + } + + if (((dpld->dump_rcvd & 0x1) == 1) && (msg == CMC22x_2ND_BUFF_FULL)) { + complete_all(&dpld->dump_recv_done); + return; + } + + mif_add_timer(&dpld->dump_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->dump_rcvd & 0x1) == 0) + dpld->dpctl->send_msg(CMC22x_1ST_BUFF_READY); + else + dpld->dpctl->send_msg(CMC22x_2ND_BUFF_READY); + + init_completion(&dpld->dump_recv_done); + + mif_add_timer(&dpld->dump_timer, DUMP_WAIT_TIMEOUT, + _cmc221_idpram_wait_dump, (unsigned long)dpld); + + ret = wait_for_completion_interruptible_timeout( + &dpld->dump_recv_done, DUMP_TIMEOUT); + if (!ret) { + mif_info("%s: ERR! CP didn't send dump data!!!\n", ld->name); + goto err_out; + } + + if (dpld->dpctl->recv_msg() == CMC22x_CP_DUMP_END) { + mif_info("%s: CMC22x_CP_DUMP_END\n", ld->name); + return 0; + } + + if ((dpld->dump_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->dump_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; +} +#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 *bt_map = &dpld->qc_bt_map; + struct modemlink_dpram_control *dpctl = dpld->dpctl; + + bt_map->buff = dpld->dp_base; + bt_map->frame_size = (u16 *)(dpld->dp_base + dpctl->boot_size_offset); + bt_map->tag = (u16 *)(dpld->dp_base + dpctl->boot_tag_offset); + bt_map->count = (u16 *)(dpld->dp_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; + } + + msleep_interruptible(10); + + 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 *bt_map = &dpld->qc_bt_map; + + if (param->size <= dpld->dpctl->max_boot_frame_size) { + memcpy(bt_map->buff, param->addr, param->size); + iowrite16(param->size, bt_map->frame_size); + iowrite16(param->tag, bt_map->tag); + iowrite16(param->count, bt_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; + } + + msleep(10); + + 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; + } + + msleep_interruptible(10); + + 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; + } + + msleep_interruptible(10); + + 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 *bt_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; + } + } + + msleep_interruptible(1); + + count++; + if (count > 200) { + mif_err("<%s:%d>\n", __func__, __LINE__); + return -1; + } + } + + param->size = ioread16(bt_map->frame_size); + memcpy(param->addr, bt_map->buff, param->size); + param->tag = ioread16(bt_map->tag); + param->count = ioread16(bt_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; + } + } + + msleep_interruptible(1); + + 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, + .download_binary = cmc221_download_binary, + .dump_start = cmc221_dump_start, + .dump_update = cmc221_dump_update, + .ioctl = cmc221_ioctl, + }, +#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; +} + |