/* * Copyright (C) 2015 Spreadtrum Communications Inc. * 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 "bufring.h" #include "wcn_glb.h" #include "wcn_log.h" #include "wcn_misc.h" /* units is ms, 2500ms */ #define WCN_DUMP_TIMEOUT 2500 static struct mdbg_ring_t *mdev_ring; gnss_dump_callback gnss_dump_handle; static int mdbg_snap_shoot_iram_data(void *buf, u32 addr, u32 len) { struct regmap *regmap; u32 i; u8 *ptr = NULL; WCN_INFO("start snap_shoot iram data!addr:%x,len:%d", addr, len); if (marlin_get_module_status() == 0) { WCN_ERR("module status off:can not get iram data!\n"); return -1; } if (wcn_platform_chip_type() == WCN_PLATFORM_TYPE_SHARKL3) regmap = wcn_get_btwf_regmap(REGMAP_WCN_REG); else regmap = wcn_get_btwf_regmap(REGMAP_ANLG_WRAP_WCN); wcn_regmap_raw_write_bit(regmap, 0XFF4, addr); for (i = 0; i < len / 4; i++) { ptr = buf + i * 4; wcn_regmap_read(regmap, 0XFFC, (u32 *)ptr); } WCN_INFO("snap_shoot iram data success\n"); return 0; } int mdbg_snap_shoot_iram(void *buf) { u32 ret; ret = mdbg_snap_shoot_iram_data(buf, 0x18000000, 1024 * 32); return ret; } struct wcn_dump_mem_reg { /* some CP regs can't dump */ bool do_dump; u32 addr; /* 4 btyes align */ u32 len; }; /* magic number, not change it */ #define WCN_DUMP_VERSION_NAME "WCN_DUMP_HEAD__" /* SUB_NAME len not more than 15 bytes */ #define WCN_DUMP_VERSION_SUB_NAME "SIPC_23xx" /* CP2 iram start and end */ #define WCN_DUMP_CP2_IRAM_START 1 #define WCN_DUMP_CP2_IRAM_END 2 /* AP regs start and end */ #define WCN_DUMP_AP_REGS_START (WCN_DUMP_CP2_IRAM_END + 1) #define WCN_DUMP_AP_REGS_END 9 /* CP2 regs start and end */ #define WCN_DUMP_CP2_REGS_START (WCN_DUMP_AP_REGS_END + 1) #define WCN_DUMP_CP2_REGS_END (ARRAY_SIZE(s_wcn_dump_regs) - 1) #define WCN_DUMP_ALIGN(x) (((x) + 3) & ~3) /* used for HEAD, so all dump mem in this array. * if new member added, please modify the macor XXX_START XXX_end above. */ static struct wcn_dump_mem_reg s_wcn_dump_regs[] = { /* share mem */ {1, 0, 0x300000}, /* iram mem */ {1, 0x10000000, 0x8000}, /* wcn iram */ {1, 0x18004000, 0x4000}, /* gnss iram */ /* ap regs */ {1, 0x402B00CC, 4}, /* PMU_SLEEP_CTRL */ {1, 0x402B00D4, 4}, /* PMU_SLEEP_STATUS */ {1, 0x402B0100, 4}, /* PMU_PD_WCN_SYS_CFG */ {1, 0x402B0104, 4}, /* PMU_PD_WIFI_WRAP_CFG */ {1, 0x402B0244, 4}, /* PMU_WCN_SYS_DSLP_ENA */ {1, 0x402B0248, 4}, /* PMU_WIFI_WRAP_DSLP_ENA */ {1, 0x402E057C, 4}, /* AON_APB_WCN_SYS_CFG2 */ /* cp regs */ {0, 0x40060000, 0x300}, /* BTWF_CTRL */ {0, 0x60300000, 0x400}, /* BTWF_AHB_CTRL */ {0, 0x40010000, 0x38}, /* BTWF_INTC */ {0, 0x40020000, 0x10}, /* BTWF_SYSTEM_TIMER */ {0, 0x40030000, 0x20}, /* BTWF_TIMER0 */ {0, 0x40030020, 0x20}, /* BTWF_TIMER1 */ {0, 0x40030040, 0x20}, /* BTWF_TIMER2 */ {0, 0x40040000, 0x24}, /* BTWF_WATCHDOG */ {0, 0xd0010000, 0xb4}, /* COM_AHB_CTRL */ {0, 0xd0020800, 0xc}, /* MANU_CLK_CTRL */ {0, 0x70000000, 0x10000}, /* WIFI */ {0, 0x400b0000, 0x850}, /* FM */ {0, 0x60700000, 0x400}, /* BT_CMD */ {0, 0x60740000, 0xa400} /* BT */ }; struct wcn_dump_section_info { /* cp load start addr */ __le32 start; /* cp load end addr */ __le32 end; /* load from file offset */ __le32 off; __le32 reserv; } __packed; struct wcn_dump_head_info { /* WCN_DUMP_VERSION_NAME */ u8 version[16]; /* WCN_DUMP_VERSION_SUB_NAME */ u8 sub_version[16]; /* numbers of wcn_dump_section_info */ __le32 n_sec; /* used to check if dump is full */ __le32 file_size; u8 reserv[8]; struct wcn_dump_section_info section[0]; } __packed; static int wcn_fill_dump_head_info(struct wcn_dump_mem_reg *mem_cfg, int cnt) { int i, len, head_len; struct wcn_dump_mem_reg *mem; struct wcn_dump_head_info *head; struct wcn_dump_section_info *sec; head_len = sizeof(*head) + sizeof(*sec) * cnt; head = kzalloc(head_len, GFP_KERNEL); if (unlikely(!head)) { WCN_ERR("system has no mem for dump mem\n"); return -1; } strncpy(head->version, WCN_DUMP_VERSION_NAME, strlen(WCN_DUMP_VERSION_NAME)+1); strncpy(head->sub_version, WCN_DUMP_VERSION_SUB_NAME, strlen(WCN_DUMP_VERSION_SUB_NAME)+1); head->n_sec = cpu_to_le32(cnt); len = head_len; for (i = 0; i < cnt; i++) { sec = head->section + i; mem = mem_cfg + i; sec->off = cpu_to_le32(WCN_DUMP_ALIGN(len)); sec->start = cpu_to_le32(mem->addr); sec->end = cpu_to_le32(sec->start + mem->len - 1); len += mem->len; WCN_INFO("section[%d] [0x%x 0x%x 0x%x]\n", i, le32_to_cpu(sec->start), le32_to_cpu(sec->end), le32_to_cpu(sec->off)); } head->file_size = cpu_to_le32(len + strlen(WCN_DUMP_END_STRING)); mdbg_ring_write(mdev_ring, head, head_len); wake_up_log_wait(); kfree(head); return 0; } static void mdbg_dump_str(char *str, int str_len) { if (!str) return; mdbg_ring_write_timeout(mdev_ring, str, str_len, WCN_DUMP_TIMEOUT); wake_up_log_wait(); WCN_INFO("dump str finish!"); } static int mdbg_dump_ap_register_data(phys_addr_t addr, u32 len) { u32 value = 0; u8 *ptr = NULL; ptr = (u8 *)&value; wcn_read_data_from_phy_addr(addr, &value, len); mdbg_ring_write_timeout(mdev_ring, ptr, len, WCN_DUMP_TIMEOUT); wake_up_log_wait(); return 0; } static int mdbg_dump_cp_register_data(u32 addr, u32 len) { struct regmap *regmap; u32 i; u32 count, trans_size; u8 *buf = NULL; u8 *ptr = NULL; WCN_INFO("start dump cp register!addr:%x,len:%d", addr, len); if (unlikely(!mdbg_dev->ring_dev)) { WCN_ERR("ring_dev is NULL\n"); return -1; } buf = kzalloc(len, GFP_KERNEL); if (!buf) return -ENOMEM; if (wcn_platform_chip_type() == WCN_PLATFORM_TYPE_SHARKL3) regmap = wcn_get_btwf_regmap(REGMAP_WCN_REG); else regmap = wcn_get_btwf_regmap(REGMAP_ANLG_WRAP_WCN); wcn_regmap_raw_write_bit(regmap, 0XFF4, addr); for (i = 0; i < len / 4; i++) { ptr = buf + i * 4; wcn_regmap_read(regmap, 0XFFC, (u32 *)ptr); } count = 0; while (count < len) { trans_size = (len - count) > DUMP_PACKET_SIZE ? DUMP_PACKET_SIZE : (len - count); mdbg_ring_write_timeout(mdev_ring, buf + count, trans_size, WCN_DUMP_TIMEOUT); count += trans_size; wake_up_log_wait(); } kfree(buf); WCN_INFO("dump cp register finish count %u\n", count); return count; } static void mdbg_dump_ap_register(struct wcn_dump_mem_reg *mem) { int i; for (i = WCN_DUMP_AP_REGS_START; i <= WCN_DUMP_AP_REGS_END; i++) { mdbg_dump_ap_register_data(mem[i].addr, mem[i].len); WCN_INFO("dump ap reg section[%d] ok!\n", i); } } static void mdbg_dump_cp_register(struct wcn_dump_mem_reg *mem) { int i, count; for (i = WCN_DUMP_CP2_REGS_START; i <= WCN_DUMP_CP2_REGS_END; i++) { count = mdbg_dump_cp_register_data(mem[i].addr, mem[i].len); WCN_INFO("dump cp reg section[%d] %d ok!\n", i, count); } } static void mdbg_dump_iram(struct wcn_dump_mem_reg *mem) { int i, count; for (i = WCN_DUMP_CP2_IRAM_START; i <= WCN_DUMP_CP2_IRAM_END; i++) { count = mdbg_dump_cp_register_data(mem[i].addr, mem[i].len); WCN_INFO("dump iram section[%d] %d ok!\n", i, count); } } static int mdbg_dump_share_memory(struct wcn_dump_mem_reg *mem) { u32 count, len, trans_size; void *virt_addr; phys_addr_t base_addr; unsigned int cnt; unsigned long timeout; if (unlikely(!mdbg_dev->ring_dev)) { WCN_ERR("ring_dev is NULL\n"); return -1; } len = mem[0].len; base_addr = wcn_get_btwf_base_addr(); WCN_INFO("dump sharememory start!"); WCN_INFO("ring->pbuff=%p, ring->end=%p.\n", mdev_ring->pbuff, mdev_ring->end); virt_addr = wcn_mem_ram_vmap_nocache(base_addr, len, &cnt); if (!virt_addr) { WCN_ERR("wcn_mem_ram_vmap_nocache fail\n"); return -1; } count = 0; /* 10s timeout */ timeout = jiffies + msecs_to_jiffies(10000); while (count < len) { trans_size = (len - count) > DUMP_PACKET_SIZE ? DUMP_PACKET_SIZE : (len - count); /* copy data from ddr to ring buf */ mdbg_ring_write_timeout(mdev_ring, virt_addr + count, trans_size, WCN_DUMP_TIMEOUT); count += trans_size; wake_up_log_wait(); if (time_after(jiffies, timeout)) { WCN_ERR("Dump share mem timeout:count:%u\n", count); break; } } wcn_mem_ram_unmap(virt_addr, cnt); WCN_INFO("share memory dump finish! total count %u\n", count); return 0; } void mdbg_dump_gnss_register(gnss_dump_callback callback_func, void *para) { gnss_dump_handle = (gnss_dump_callback)callback_func; WCN_INFO("gnss_dump register success!\n"); } void mdbg_dump_gnss_unregister(void) { gnss_dump_handle = NULL; } static int btwf_dump_mem(void) { u32 cp2_status = 0; phys_addr_t sleep_addr; if (wcn_get_btwf_power_status() == WCN_POWER_STATUS_OFF) { WCN_INFO("wcn power status off:can not dump btwf!\n"); return -1; } mdbg_send_atcmd("at+sleep_switch=0\r", strlen("at+sleep_switch=0\r"), WCN_ATCMD_KERNEL); msleep(500); sleep_addr = wcn_get_btwf_sleep_addr(); wcn_read_data_from_phy_addr(sleep_addr, &cp2_status, sizeof(u32)); mdev_ring = mdbg_dev->ring_dev->ring; mdbg_hold_cpu(); msleep(100); mdbg_ring_reset(mdev_ring); mdbg_atcmd_clean(); if (wcn_fill_dump_head_info(s_wcn_dump_regs, ARRAY_SIZE(s_wcn_dump_regs))) return -1; mdbg_dump_share_memory(s_wcn_dump_regs); mdbg_dump_iram(s_wcn_dump_regs); mdbg_dump_ap_register(s_wcn_dump_regs); if (cp2_status == WCN_CP2_STATUS_DUMP_REG) { mdbg_dump_cp_register(s_wcn_dump_regs); WCN_INFO("dump register ok!\n"); } mdbg_dump_str(WCN_DUMP_END_STRING, strlen(WCN_DUMP_END_STRING)); return 0; } void mdbg_dump_mem(void) { /* dump gnss */ if (gnss_dump_handle) { WCN_INFO("need dump gnss\n"); gnss_dump_handle(); } /* dump btwf */ btwf_dump_mem(); } int dump_arm_reg(void) { mdbg_hold_cpu(); return 0; }