/*
|
* Copyright (C) 2019 Spreadtrum Communications Inc.
|
*
|
* Authors : xiaodong.bi
|
*
|
* 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 "wcn_swd_dp.h"
|
|
#ifdef CONFIG_WCN_PCIE
|
static void swd_ext_sel(bool enable)
|
{
|
u32 *reg = (u32 *)(pcie_bar_vmem(4) + 0x1c);
|
u32 *ahb_ctl = (u32 *)(pcie_bar_vmem(0) + 0x001303e8);/* dis sec */
|
|
*ahb_ctl = 0x73e;
|
if (enable)
|
*reg |= (BIT(7) | BIT(6) | BIT(5));
|
else
|
*reg &= ~(BIT(7) | BIT(6) | BIT(5));
|
}
|
|
static void swclk_clr(void)
|
{
|
u32 *reg = (u32 *)(pcie_bar_vmem(4) + 0x1c);
|
|
*reg &= ~BIT(6);
|
}
|
|
static void swclk_set(void)
|
{
|
u32 *reg = (u32 *)(pcie_bar_vmem(4) + 0x1c);
|
|
*reg |= BIT(6);
|
}
|
|
static void sw_dio_out(u32 value)
|
{
|
u32 *reg = (u32 *)(pcie_bar_vmem(4) + 0x1c);
|
|
if (value & BIT(0))
|
*reg |= BIT(5);
|
else
|
*reg &= ~BIT(5);
|
}
|
|
static u32 sw_dio_in(void)
|
{
|
u32 *reg = (u32 *)(pcie_bar_vmem(4) + 0xc);
|
u32 bit;
|
|
bit = (*reg & BIT(7)) >> 7;
|
|
return bit;
|
}
|
#elif defined CONFIG_WCN_USB
|
static void swd_ext_sel(bool enable)
|
{
|
}
|
|
static void swclk_clr(void)
|
{
|
}
|
|
static void swclk_set(void)
|
{
|
}
|
|
static void sw_dio_out(u32 value)
|
{
|
}
|
|
static u32 sw_dio_in(void)
|
{
|
return 0;
|
}
|
#else
|
static void swd_ext_sel(bool enable)
|
{
|
unsigned char reg;
|
|
sdiohal_aon_readb(DAP_ADDR+0x0E, ®);
|
pr_info("reg value is:0x%x\n", reg);
|
|
if (enable)
|
reg |= (BIT(7) | BIT(6) | BIT(5));
|
else
|
reg &= ~(BIT(7) | BIT(6) | BIT(5));
|
sdiohal_aon_writeb(DAP_ADDR+0x0E, reg);
|
sdiohal_aon_readb(DAP_ADDR+0x0E, ®);
|
pr_info("reg value is:0x%x\n", reg);
|
}
|
|
static void swclk_clr(void)
|
{
|
unsigned char reg;
|
|
sdiohal_aon_readb(DAP_ADDR+0x0e, ®);
|
reg &= ~BIT(6);
|
sdiohal_aon_writeb(DAP_ADDR+0x0E, reg);
|
}
|
|
static void swclk_set(void)
|
{
|
unsigned char reg;
|
|
sdiohal_aon_readb(DAP_ADDR+0x0E, ®);
|
reg |= BIT(6);
|
sdiohal_aon_writeb(DAP_ADDR+0x0E, reg);
|
}
|
|
static void sw_dio_out(u32 value)
|
{
|
unsigned char reg;
|
|
sdiohal_aon_readb(DAP_ADDR+0x0E, ®);
|
if (value & BIT(0))
|
reg |= BIT(5);
|
else
|
reg &= ~BIT(5);
|
sdiohal_aon_writeb(DAP_ADDR+0x0E, reg);
|
}
|
|
static u32 sw_dio_in(void)
|
{
|
unsigned char reg;
|
u32 bit;
|
|
sdiohal_aon_readb(DAP_ACK_ADDR, ®);
|
bit = (reg & BIT(7)) >> 7;
|
|
return bit;
|
}
|
#endif
|
|
static void swd_clk_cycle(void)
|
{
|
swclk_set();
|
ndelay(100);
|
swclk_clr();
|
ndelay(100);
|
}
|
|
static void swd_write_bit(u32 bit)
|
{
|
swclk_set();
|
sw_dio_out(bit);
|
ndelay(100);
|
swclk_clr();
|
ndelay(100);
|
}
|
|
static u32 swd_read_bit(void)
|
{
|
u32 bit;
|
|
swclk_set();
|
ndelay(100);
|
bit = sw_dio_in();
|
swclk_clr();
|
ndelay(100);
|
|
return bit;
|
}
|
|
static void swd_insert_cycles(u32 n)
|
{
|
u32 i;
|
|
for (i = 0; i < n; i++)
|
swd_clk_cycle();
|
}
|
|
u32 swd_transfer(u8 cmd, u32 *data)
|
{
|
u32 ack = 0;
|
u32 bit;
|
u32 val;
|
u32 parity;
|
u32 n;
|
|
parity = 0;
|
/* Start Bit */
|
swd_write_bit(1);
|
bit = ((cmd >> 0) & BIT(0));
|
/* APnDP Bit */
|
swd_write_bit(bit);
|
parity += bit;
|
bit = ((cmd >> 1) & BIT(0));
|
/* RnW Bit */
|
swd_write_bit(bit);
|
parity += bit;
|
bit = ((cmd >> 2) & BIT(0));
|
/* A2 Bit */
|
swd_write_bit(bit);
|
parity += bit;
|
bit = ((cmd >> 3) & BIT(0));
|
/* A3 Bit */
|
swd_write_bit(bit);
|
parity += bit;
|
/* Parity Bit */
|
swd_write_bit(parity & BIT(0));
|
/* Stop Bit */
|
swd_write_bit(0);
|
/* Park Bit */
|
swd_write_bit(1);
|
|
/* Turnaround */
|
swd_insert_cycles(1);
|
|
/* Acknowledge response */
|
bit = swd_read_bit();
|
ack |= bit << 0;
|
bit = swd_read_bit();
|
ack |= bit << 1;
|
bit = swd_read_bit();
|
ack |= bit << 2;
|
|
if (ack == DAP_TRANSFER_OK) {
|
/* Data transfer */
|
if (cmd & DAP_TRANSFER_RnW) {
|
val = 0;
|
parity = 0;
|
/* Read DATA[0:31] */
|
for (n = 0; n < 32; n++) {
|
bit = swd_read_bit();
|
parity += bit;
|
val |= (bit << n);
|
}
|
|
/* Read Parity */
|
bit = swd_read_bit();
|
if ((parity ^ bit) & 1)
|
ack = DAP_TRANSFER_ERROR;
|
|
if (data)
|
*data = val;
|
|
/* Turnaround */
|
swd_insert_cycles(1);
|
} else {
|
/* Turnaround */
|
swd_insert_cycles(1);
|
sw_dio_out(0);
|
|
val = *data;
|
parity = 0;
|
/* Write WDATA[0:31] */
|
for (n = 0; n < 32; n++) {
|
bit = val & BIT(0);
|
swd_write_bit(bit);
|
parity += bit;
|
val >>= 1;
|
}
|
/* Write Parity Bit */
|
swd_write_bit(parity & BIT(0));
|
/* Turnaround */
|
swd_insert_cycles(1);
|
}
|
|
/* Idle cycles >= 8 */
|
sw_dio_out(0);
|
swd_insert_cycles(8);
|
|
return ack;
|
}
|
|
if ((ack == DAP_TRANSFER_WAIT) || (ack == DAP_TRANSFER_FAULT)) {
|
if ((cmd & DAP_TRANSFER_RnW) != 0)
|
swd_insert_cycles(32+1);
|
else {
|
/* Turnaround */
|
swd_insert_cycles(1);
|
if ((cmd & DAP_TRANSFER_RnW) == 0)
|
swd_insert_cycles(32 + 1);
|
}
|
|
/* Idle cycles >= 8 */
|
sw_dio_out(0);
|
swd_insert_cycles(8);
|
|
return ack;
|
}
|
|
/* Turnaround */
|
swd_insert_cycles(1);
|
swd_insert_cycles(32+1);
|
|
/* Idle cycles >= 8 */
|
sw_dio_out(0);
|
swd_insert_cycles(8);
|
|
return ack;
|
}
|
|
static void swd_send_nbytes(u8 *buf, int nbytes)
|
{
|
u8 i, j;
|
u8 dat;
|
|
for (i = 0; i < nbytes; i++) {
|
dat = buf[i];
|
for (j = 0; j < 8; j++) {
|
if ((dat & 0x80) == 0x80)
|
swd_write_bit(1);
|
else
|
swd_write_bit(0);
|
dat <<= 1;
|
}
|
}
|
}
|
|
|
static u32 swd_dap_read(u8 reg, u32 *data)
|
{
|
u32 ack;
|
|
reg &= ~DAP_TRANSFER_APnDP;
|
reg |= DAP_TRANSFER_RnW;
|
ack = swd_transfer(reg, data);
|
|
return ack;
|
}
|
|
static u32 swd_dap_write(u8 reg, u32 *data)
|
{
|
u32 ack;
|
|
reg &= ~(DAP_TRANSFER_APnDP | DAP_TRANSFER_RnW);
|
ack = swd_transfer(reg, data);
|
|
return ack;
|
}
|
|
static u32 swd_ap_read(u8 reg, u32 *data)
|
{
|
u32 ack;
|
|
reg |= DAP_TRANSFER_APnDP | DAP_TRANSFER_RnW;
|
ack = swd_transfer(reg, data);
|
|
return ack;
|
}
|
|
static u32 swd_ap_write(u32 reg, u32 *data)
|
{
|
u32 ack;
|
|
reg &= ~(DAP_TRANSFER_RnW);
|
reg |= DAP_TRANSFER_APnDP;
|
ack = swd_transfer(reg, data);
|
|
return ack;
|
}
|
|
static u32 swd_memap_read(u32 addr, u32 *data)
|
{
|
u8 reg;
|
u32 ack;
|
|
reg = DAP_TRANSFER_A2;
|
ack = swd_ap_write(reg, &addr);
|
|
reg = DAP_TRANSFER_A2 | DAP_TRANSFER_A3;
|
ack = swd_ap_read(reg, data);
|
ack = swd_ap_read(reg, data);
|
|
return ack;
|
}
|
|
static u32 swd_memap_write(u32 addr, u32 *data)
|
{
|
u8 reg;
|
u32 ack;
|
|
reg = DAP_TRANSFER_A2;
|
ack = swd_ap_write(reg, &addr);
|
|
reg = DAP_TRANSFER_A2 | DAP_TRANSFER_A3;
|
ack = swd_ap_write(reg, data);
|
|
return ack;
|
}
|
|
static void swd_read_arm_core(void)
|
{
|
u32 value;
|
u32 index;
|
u32 addr;
|
static const char *core_reg_name[19] = {
|
"R0 ", "R1 ", "R2 ", "R3 ", "R4 ", "R5 ", "R6 ", "R7 ", "R8 ",
|
"R9 ", "R10", "R11", "R12", "R13", "R14", "R15", "PSR", "MSP",
|
"PSP",
|
};
|
|
/* reg arm reg */
|
for (index = 0; index < 19; index++) {
|
addr = 0xe000edf4;
|
swd_memap_write(addr, &index);
|
addr = 0xe000edf8;
|
swd_memap_read(addr, &value);
|
pr_info("%s %s:0x%x\n", __func__, core_reg_name[index], value);
|
}
|
}
|
|
/* MSB first */
|
u8 swd_wakeup_seq[16] = {
|
0x49, 0xCF, 0x90, 0x46,
|
0xA9, 0xB4, 0xA1, 0x61,
|
0x97, 0xF5, 0xBB, 0xC7,
|
0x45, 0x70, 0x3D, 0x98,
|
};
|
|
/* LSB first */
|
u8 swd_wakeup_seq2[16] = {
|
0x19, 0xBC, 0x0E, 0xA2,
|
0xE3, 0xDD, 0xAF, 0xE9,
|
0x86, 0x85, 0x2D, 0x95,
|
0x62, 0x09, 0xF3, 0x92,
|
};
|
|
u8 swd_to_ds_seq[2] = {
|
0x3d, 0xc7,
|
};
|
|
static void swd_dormant_to_wake(void)
|
{
|
u8 data;
|
|
/* Send at least eight SWCLKTCK cycles with SWDIOTMS HIGH */
|
sw_dio_out(1);
|
swd_insert_cycles(8);
|
|
/* 128 bit Selection Alert sequence */
|
swd_send_nbytes(swd_wakeup_seq, 16);
|
|
/* four SWCLKTCK cycles with SWDIOTMS LOW */
|
sw_dio_out(0);
|
swd_insert_cycles(4);
|
|
/* Send the activation code */
|
data = 0x58;
|
swd_send_nbytes(&data, 1);
|
|
swd_insert_cycles(1);
|
sw_dio_out(0);
|
}
|
|
static void swd_wake_to_dormant(void)
|
{
|
|
/* Send at least eight SWCLKTCK cycles with SWDIOTMS HIGH */
|
sw_dio_out(1);
|
swd_insert_cycles(8);
|
|
/* 16-bit SWD-to-DS select sequence */
|
swd_send_nbytes(swd_to_ds_seq, 2);
|
swd_insert_cycles(1);
|
}
|
|
static void switch_jtag_to_swd(void)
|
{
|
|
u8 data[2];
|
|
/* Send at least 50 SWCLKTCK cycles with SWDIOTMS HIGH */
|
sw_dio_out(1);
|
swd_insert_cycles(50);
|
|
/* send the 16-bit JTAG-to-SWD select sequence */
|
data[0] = 0x79;
|
data[1] = 0xe7;
|
swd_send_nbytes(data, 2);
|
|
/* Send at least 50 SWCLKTCK cycles with SWDIOTMS HIGH */
|
swd_insert_cycles(50);
|
sw_dio_out(0);
|
}
|
|
static void swd_line_reset(void)
|
{
|
/* Complete SWD reset sequence
|
*(50 cycles high followed by 2 or more idle cycles)
|
*/
|
sw_dio_out(1);
|
swd_insert_cycles(50);
|
sw_dio_out(0);
|
swd_insert_cycles(2);
|
}
|
|
static void swd_read_dpidr(void)
|
{
|
u32 ack, data = 0;
|
|
/* the dp idr is 0x0be12477*/
|
ack = swd_dap_read(DP_IDCODE, &data);
|
|
pr_info("%s idcode:0x%x\n", __func__, data);
|
}
|
|
static void swd_read_apidr(void)
|
{
|
u32 ack, data = 0;
|
|
data = (0xf << 4);
|
ack = swd_dap_write(DP_SELECT, &data);
|
|
/* the dp idr is 0x14770015*/
|
ack = swd_ap_read(AP_IDCODE, &data);
|
ack = swd_ap_read(AP_IDCODE, &data);
|
pr_info("%s idcode:0x%x\n", __func__, data);
|
|
data = (0x0 << 4);
|
ack = swd_dap_write(DP_SELECT, &data);
|
}
|
|
|
u32 swd_sel_target(u8 cmd, u32 *data)
|
{
|
u32 bit, parity;
|
u32 n, val;
|
|
sw_dio_out(1);
|
swd_insert_cycles(50);
|
|
sw_dio_out(0);
|
swd_insert_cycles(2);
|
|
parity = 0;
|
/* Start Bit */
|
swd_write_bit(1);
|
bit = ((cmd >> 0) & BIT(0));
|
/* APnDP Bit */
|
swd_write_bit(bit);
|
parity += bit;
|
bit = ((cmd >> 1) & BIT(0));
|
/* RnW Bit */
|
swd_write_bit(bit);
|
parity += bit;
|
bit = ((cmd >> 2) & BIT(0));
|
/* A2 Bit */
|
swd_write_bit(bit);
|
parity += bit;
|
bit = ((cmd >> 3) & BIT(0));
|
/* A3 Bit */
|
swd_write_bit(bit);
|
parity += bit;
|
/* Parity Bit */
|
swd_write_bit(parity & BIT(0));
|
/* Stop Bit */
|
swd_write_bit(0);
|
/* Park Bit */
|
swd_write_bit(1);
|
|
/* Turnaround */
|
swd_insert_cycles(5);
|
|
val = *data;
|
parity = 0;
|
/* Write WDATA[0:31] */
|
for (n = 0; n < 32; n++) {
|
bit = (val & BIT(0));
|
swd_write_bit(bit);
|
parity += bit;
|
val >>= 1;
|
}
|
/* Write Parity Bit */
|
swd_write_bit(parity & BIT(0));
|
|
sw_dio_out(0);
|
swd_insert_cycles(3);
|
|
return 0;
|
}
|
|
static void btwf_sys_dap_sel(void)
|
{
|
u32 data = 0;
|
|
swd_line_reset();
|
data = TARGETSEL_CP;
|
swd_sel_target(DP_TARGETSEL, &data);
|
swd_read_dpidr();
|
}
|
|
static int swd_power_up(void)
|
{
|
u32 data;
|
|
pr_info("%s entry\n", __func__);
|
|
data = 0x50000000;
|
swd_dap_write(DP_CTRL_STAT, &data);
|
swd_dap_read(DP_CTRL_STAT, &data);
|
|
pr_info("%s read ctrl stat:0x%x\n", __func__, data);
|
|
return 0;
|
}
|
|
static void swd_device_en(void)
|
{
|
u32 data = 0;
|
|
swd_ap_read(AP_CTRL, &data);
|
swd_ap_read(AP_CTRL, &data);
|
data = (data & 0xffffff88) | BIT(1) | BIT(6);
|
swd_ap_write(AP_STAT, &data);
|
}
|
|
/* Debug Exception and Monitor Control Register */
|
/* (0xe000edfC) = 0x010007f1 */
|
void swd_set_debug_mode(void)
|
{
|
int ret;
|
int addr;
|
unsigned int reg_val;
|
|
reg_val = 0x010007f1;
|
addr = 0xe000edfC;
|
ret = swd_memap_write(addr, ®_val);
|
if (ret < 0) {
|
pr_info("%s error:%d\n", __func__, ret);
|
return;
|
}
|
|
/*test */
|
ret = swd_memap_read(addr, ®_val);
|
if (ret < 0) {
|
pr_info("%s error:%d\n", __func__, ret);
|
return;
|
}
|
pr_info("%s arm debug reg value is 0x%x:\n", __func__, reg_val);
|
}
|
|
/*
|
* Debug Halting Control status Register
|
* (0xe000edf0) = 0xa05f0003
|
*/
|
void swd_hold_btwf_core(void)
|
{
|
int ret;
|
int addr;
|
unsigned int reg_val;
|
|
addr = 0xe000edf0;
|
reg_val = 0xa05f0003;
|
|
ret = swd_memap_write(addr, ®_val);
|
if (ret < 0) {
|
pr_info("%s error:%d\n", __func__, ret);
|
return;
|
}
|
/*test */
|
ret = swd_memap_read(addr, ®_val);
|
if (ret < 0) {
|
pr_info("%s error:%d\n", __func__, ret);
|
return;
|
}
|
pr_info("%s arm hold btwf reg value is 0x%x:\n", __func__, reg_val);
|
}
|
|
int swd_dump_arm_reg(void)
|
{
|
pr_info("%s entry\n", __func__);
|
|
swd_ext_sel(true);
|
swd_line_reset();
|
|
switch_jtag_to_swd();
|
swd_dormant_to_wake();
|
btwf_sys_dap_sel();
|
|
swd_power_up();
|
swd_read_apidr();
|
swd_device_en();
|
swd_hold_btwf_core();
|
swd_set_debug_mode();
|
swd_read_arm_core();
|
|
/* release swd */
|
swd_wake_to_dormant();
|
swd_ext_sel(false);
|
|
pr_info("%s end\n", __func__);
|
|
return 0;
|
}
|