/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }