604 lines
13 KiB
C
604 lines
13 KiB
C
/* Copyright 2014 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "adc.h"
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "dma.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "hwtimer.h"
|
|
#include "injector.h"
|
|
#include "registers.h"
|
|
#include "system.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "usb_pd.h"
|
|
#include "usb_pd_config.h"
|
|
#include "util.h"
|
|
#include "watchdog.h"
|
|
|
|
/* FSM command/data buffer */
|
|
static uint32_t inj_cmds[INJ_CMD_COUNT];
|
|
|
|
/* Current polarity for sending operations */
|
|
static enum inj_pol inj_polarity = INJ_POL_CC1;
|
|
|
|
/*
|
|
* CCx Resistors control definition
|
|
*
|
|
* Resistor control GPIOs :
|
|
* CC1_RA A8
|
|
* CC1_RPUSB A13
|
|
* CC1_RP1A5 A14
|
|
* CC1_RP3A0 A15
|
|
* CC2_RPUSB B0
|
|
* CC1_RD B5
|
|
* CC2_RD B8
|
|
* CC2_RA B15
|
|
* CC2_RP1A5 C14
|
|
* CC2_RP3A0 C15
|
|
*/
|
|
static const struct res_cfg {
|
|
const char *name;
|
|
struct config {
|
|
enum gpio_signal signal;
|
|
uint32_t flags;
|
|
} cfgs[2];
|
|
} res_cfg[] = {
|
|
[INJ_RES_NONE] = {"NONE"},
|
|
[INJ_RES_RA] = {"RA", {{GPIO_CC1_RA, GPIO_ODR_LOW},
|
|
{GPIO_CC2_RA, GPIO_ODR_LOW} } },
|
|
[INJ_RES_RD] = {"RD", {{GPIO_CC1_RD, GPIO_ODR_LOW},
|
|
{GPIO_CC2_RD, GPIO_ODR_LOW} } },
|
|
[INJ_RES_RPUSB] = {"RPUSB", {{GPIO_CC1_RPUSB, GPIO_OUT_HIGH},
|
|
{GPIO_CC2_RPUSB, GPIO_OUT_HIGH} } },
|
|
[INJ_RES_RP1A5] = {"RP1A5", {{GPIO_CC1_RP1A5, GPIO_OUT_HIGH},
|
|
{GPIO_CC2_RP1A5, GPIO_OUT_HIGH} } },
|
|
[INJ_RES_RP3A0] = {"RP3A0", {{GPIO_CC1_RP3A0, GPIO_OUT_HIGH},
|
|
{GPIO_CC2_RP3A0, GPIO_OUT_HIGH} } },
|
|
};
|
|
|
|
#define CC_RA(cc) (cc < PD_SRC_RD_THRESHOLD)
|
|
#define CC_RD(cc) ((cc > PD_SRC_RD_THRESHOLD) && (cc < PD_SRC_VNC))
|
|
#define GET_POLARITY(cc1, cc2) (CC_RD(cc2) || CC_RA(cc1))
|
|
|
|
#ifdef HAS_TASK_SNIFFER
|
|
/* we don't have the default DMA handlers */
|
|
void dma_event_interrupt_channel_3(void)
|
|
{
|
|
if (STM32_DMA1_REGS->isr & STM32_DMA_ISR_TCIF(STM32_DMAC_CH3)) {
|
|
dma_clear_isr(STM32_DMAC_CH3);
|
|
task_wake(TASK_ID_CONSOLE);
|
|
}
|
|
}
|
|
DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_2_3, dma_event_interrupt_channel_3, 3);
|
|
#endif
|
|
|
|
static void twinkie_init(void)
|
|
{
|
|
/* configure TX clock pins */
|
|
gpio_config_module(MODULE_USB_PD, 1);
|
|
/* Initialize physical layer */
|
|
pd_hw_init(0, PD_ROLE_SINK);
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, twinkie_init, HOOK_PRIO_DEFAULT);
|
|
|
|
/* ------ Helper functions ------ */
|
|
|
|
static inline int disable_tracing_save(void)
|
|
{
|
|
int tr_enabled = STM32_EXTI_IMR & EXTI_COMP_MASK(0);
|
|
|
|
if (tr_enabled)
|
|
pd_rx_disable_monitoring(0);
|
|
return tr_enabled;
|
|
}
|
|
|
|
static inline void enable_tracing_ifneeded(int flag)
|
|
{
|
|
if (flag)
|
|
pd_rx_enable_monitoring(0);
|
|
}
|
|
|
|
static int send_message(int polarity, uint16_t header,
|
|
uint8_t cnt, const uint32_t *data)
|
|
{
|
|
int bit_len;
|
|
|
|
/* Don't get preempted by the tracing */
|
|
int flag = disable_tracing_save();
|
|
|
|
bit_len = prepare_message(0, header, cnt, data);
|
|
/* Transmit the packet */
|
|
pd_start_tx(0, polarity, bit_len);
|
|
pd_tx_done(0, polarity);
|
|
|
|
enable_tracing_ifneeded(flag);
|
|
|
|
return bit_len;
|
|
}
|
|
|
|
static int send_hrst(int polarity)
|
|
{
|
|
int off;
|
|
int flag = disable_tracing_save();
|
|
/* 64-bit preamble */
|
|
off = pd_write_preamble(0);
|
|
/* Hard-Reset: 3x RST-1 + 1x RST-2 */
|
|
off = pd_write_sym(0, off, 0b0011010101); /* RST-1 = 00111 */
|
|
off = pd_write_sym(0, off, 0b0011010101); /* RST-1 = 00111 */
|
|
off = pd_write_sym(0, off, 0b0011010101); /* RST-1 = 00111 */
|
|
off = pd_write_sym(0, off, 0b0101001101); /* RST-2 = 11001 */
|
|
/* Ensure that we have a final edge */
|
|
off = pd_write_last_edge(0, off);
|
|
/* Transmit the packet */
|
|
pd_start_tx(0, polarity, off);
|
|
pd_tx_done(0, polarity);
|
|
enable_tracing_ifneeded(flag);
|
|
|
|
return off;
|
|
}
|
|
|
|
static void set_resistor(int pol, enum inj_res res)
|
|
{
|
|
/* reset everything on one CC to high impedance */
|
|
gpio_set_flags(res_cfg[INJ_RES_RA].cfgs[pol].signal, GPIO_ODR_HIGH);
|
|
gpio_set_flags(res_cfg[INJ_RES_RD].cfgs[pol].signal, GPIO_ODR_HIGH);
|
|
gpio_set_flags(res_cfg[INJ_RES_RPUSB].cfgs[pol].signal, GPIO_ODR_HIGH);
|
|
gpio_set_flags(res_cfg[INJ_RES_RP1A5].cfgs[pol].signal, GPIO_ODR_HIGH);
|
|
gpio_set_flags(res_cfg[INJ_RES_RP3A0].cfgs[pol].signal, GPIO_ODR_HIGH);
|
|
|
|
/* connect the resistor if needed */
|
|
if (res != INJ_RES_NONE)
|
|
gpio_set_flags(res_cfg[res].cfgs[pol].signal,
|
|
res_cfg[res].cfgs[pol].flags);
|
|
}
|
|
|
|
static enum inj_pol guess_polarity(enum inj_pol pol)
|
|
{
|
|
int cc1_volt, cc2_volt;
|
|
/* polarity forced by the user */
|
|
if (pol == INJ_POL_CC1 || pol == INJ_POL_CC2)
|
|
return pol;
|
|
/* Auto-detection */
|
|
cc1_volt = pd_adc_read(0, 0);
|
|
cc2_volt = pd_adc_read(0, 1);
|
|
return GET_POLARITY(cc1_volt, cc2_volt);
|
|
}
|
|
|
|
/* ------ FSM commands ------ */
|
|
|
|
static void fsm_send(uint32_t w)
|
|
{
|
|
uint16_t header = INJ_ARG0(w);
|
|
int idx = INJ_ARG1(w);
|
|
uint8_t cnt = INJ_ARG2(w);
|
|
|
|
/* Buffer overflow */
|
|
if (idx > INJ_CMD_COUNT)
|
|
return;
|
|
|
|
send_message(inj_polarity, header, cnt, inj_cmds + idx);
|
|
}
|
|
|
|
static void fsm_wave(uint32_t w)
|
|
{
|
|
uint16_t bit_len = INJ_ARG0(w);
|
|
int idx = INJ_ARG1(w);
|
|
int off = 0;
|
|
int nbwords = DIV_ROUND_UP(bit_len, 32);
|
|
int i;
|
|
int flag;
|
|
|
|
/* Buffer overflow */
|
|
if (idx + nbwords > INJ_CMD_COUNT)
|
|
return;
|
|
|
|
flag = disable_tracing_save();
|
|
|
|
for (i = idx; i < idx + nbwords; i++)
|
|
off = encode_word(0, off, inj_cmds[i]);
|
|
/* Ensure that we have a final edge */
|
|
off = pd_write_last_edge(0, bit_len);
|
|
/* Transmit the packet */
|
|
pd_start_tx(0, inj_polarity, off);
|
|
pd_tx_done(0, inj_polarity);
|
|
enable_tracing_ifneeded(flag);
|
|
}
|
|
|
|
static void fsm_wait(uint32_t w)
|
|
{
|
|
#ifdef HAS_TASK_SNIFFER
|
|
uint32_t timeout_ms = INJ_ARG0(w);
|
|
uint32_t min_edges = INJ_ARG12(w);
|
|
|
|
wait_packet(inj_polarity, min_edges, timeout_ms * 1000);
|
|
#endif
|
|
}
|
|
|
|
static void fsm_expect(uint32_t w)
|
|
{
|
|
uint32_t timeout_ms = INJ_ARG0(w);
|
|
uint8_t cmd = INJ_ARG2(w);
|
|
|
|
expect_packet(inj_polarity, cmd, timeout_ms * 1000);
|
|
}
|
|
static void fsm_get(uint32_t w)
|
|
{
|
|
int store_idx = INJ_ARG0(w);
|
|
int param_idx = INJ_ARG1(w);
|
|
uint32_t *store_ptr = inj_cmds + store_idx;
|
|
|
|
/* Buffer overflow */
|
|
if (store_idx > INJ_CMD_COUNT)
|
|
return;
|
|
|
|
switch (param_idx) {
|
|
case INJ_GET_CC:
|
|
*store_ptr = pd_adc_read(0, 0) | (pd_adc_read(0, 1) << 16);
|
|
break;
|
|
case INJ_GET_VBUS:
|
|
*store_ptr = (ina2xx_get_voltage(0) & 0xffff) |
|
|
((ina2xx_get_current(0) & 0xffff) << 16);
|
|
break;
|
|
case INJ_GET_VCONN:
|
|
*store_ptr = (ina2xx_get_voltage(1) & 0xffff) |
|
|
((ina2xx_get_current(1) & 0xffff) << 16);
|
|
break;
|
|
case INJ_GET_POLARITY:
|
|
*store_ptr = inj_polarity;
|
|
break;
|
|
default:
|
|
/* Do nothing */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void fsm_set(uint32_t w)
|
|
{
|
|
int val = INJ_ARG0(w);
|
|
int idx = INJ_ARG1(w);
|
|
|
|
switch (idx) {
|
|
case INJ_SET_RESISTOR1:
|
|
case INJ_SET_RESISTOR2:
|
|
set_resistor(idx - INJ_SET_RESISTOR1, val);
|
|
break;
|
|
case INJ_SET_RECORD:
|
|
#ifdef HAS_TASK_SNIFFER
|
|
recording_enable(val);
|
|
#endif
|
|
break;
|
|
case INJ_SET_TX_SPEED:
|
|
pd_set_clock(0, val * 1000);
|
|
break;
|
|
case INJ_SET_RX_THRESH:
|
|
/* set DAC voltage (Vref = 3.3V) */
|
|
STM32_DAC_DHR12RD = val * 4096 / 3300;
|
|
break;
|
|
case INJ_SET_POLARITY:
|
|
inj_polarity = guess_polarity(val);
|
|
break;
|
|
case INJ_SET_TRACE:
|
|
set_trace_mode(val);
|
|
break;
|
|
default:
|
|
/* Do nothing */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int fsm_run(int index)
|
|
{
|
|
while (index < INJ_CMD_COUNT) {
|
|
uint32_t w = inj_cmds[index];
|
|
int cmd = INJ_CMD(w);
|
|
switch (cmd) {
|
|
case INJ_CMD_END:
|
|
return index;
|
|
case INJ_CMD_SEND:
|
|
fsm_send(w);
|
|
break;
|
|
case INJ_CMD_WAVE:
|
|
fsm_wave(w);
|
|
break;
|
|
case INJ_CMD_HRST:
|
|
send_hrst(inj_polarity);
|
|
break;
|
|
case INJ_CMD_WAIT:
|
|
fsm_wait(w);
|
|
break;
|
|
case INJ_CMD_GET:
|
|
fsm_get(w);
|
|
break;
|
|
case INJ_CMD_SET:
|
|
fsm_set(w);
|
|
break;
|
|
case INJ_CMD_JUMP:
|
|
index = INJ_ARG0(w);
|
|
continue; /* do not increment index */
|
|
case INJ_CMD_EXPCT:
|
|
fsm_expect(w);
|
|
break;
|
|
case INJ_CMD_NOP:
|
|
default:
|
|
/* Do nothing */
|
|
break;
|
|
}
|
|
index += 1;
|
|
watchdog_reload();
|
|
}
|
|
return index;
|
|
}
|
|
|
|
/* ------ Console commands ------ */
|
|
|
|
static int hex8tou32(char *str, uint32_t *val)
|
|
{
|
|
char *ptr = str;
|
|
uint32_t tmp = 0;
|
|
|
|
while (*ptr) {
|
|
char c = *ptr++;
|
|
if (c >= '0' && c <= '9')
|
|
tmp = (tmp << 4) + (c - '0');
|
|
else if (c >= 'A' && c <= 'F')
|
|
tmp = (tmp << 4) + (c - 'A' + 10);
|
|
else if (c >= 'a' && c <= 'f')
|
|
tmp = (tmp << 4) + (c - 'a' + 10);
|
|
else
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
if (ptr != str + 8)
|
|
return EC_ERROR_INVAL;
|
|
*val = tmp;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int cmd_fsm(int argc, char **argv)
|
|
{
|
|
int index;
|
|
char *e;
|
|
|
|
if (argc < 1)
|
|
return EC_ERROR_PARAM2;
|
|
|
|
index = strtoi(argv[0], &e, 10);
|
|
if (*e)
|
|
return EC_ERROR_PARAM2;
|
|
index = fsm_run(index);
|
|
ccprintf("FSM Done %d\n", index);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
static int cmd_send(int argc, char **argv)
|
|
{
|
|
int pol, cnt, i;
|
|
uint16_t header;
|
|
uint32_t data[VDO_MAX_SIZE];
|
|
char *e;
|
|
int bit_len;
|
|
|
|
cnt = argc - 2;
|
|
if (argc < 2 || cnt > VDO_MAX_SIZE)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
pol = strtoi(argv[0], &e, 10) - 1;
|
|
if (*e || pol > 1 || pol < 0)
|
|
return EC_ERROR_PARAM2;
|
|
header = strtoi(argv[1], &e, 16);
|
|
if (*e)
|
|
return EC_ERROR_PARAM3;
|
|
|
|
for (i = 0; i < cnt; i++)
|
|
if (hex8tou32(argv[i+2], data + i))
|
|
return EC_ERROR_INVAL;
|
|
|
|
bit_len = send_message(pol, header, cnt, data);
|
|
ccprintf("Sent CC%d %04x + %d = %d\n", pol + 1, header, cnt, bit_len);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int cmd_cc_level(int argc, char **argv)
|
|
{
|
|
ccprintf("CC1 = %d mV ; CC2 = %d mV\n",
|
|
pd_adc_read(0, 0), pd_adc_read(0, 1));
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int cmd_resistor(int argc, char **argv)
|
|
{
|
|
int p, r;
|
|
|
|
if (argc < 2)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
for (p = 0; p < 2; p++) {
|
|
int is_set = 0;
|
|
for (r = 0; r < ARRAY_SIZE(res_cfg); r++)
|
|
if (strcasecmp(res_cfg[r].name, argv[p]) == 0) {
|
|
set_resistor(p, r);
|
|
is_set = 1;
|
|
break;
|
|
}
|
|
/* Unknown name : set to No resistor */
|
|
if (!is_set)
|
|
set_resistor(p, INJ_RES_NONE);
|
|
}
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int cmd_tx_clock(int argc, char **argv)
|
|
{
|
|
int freq;
|
|
char *e;
|
|
|
|
if (argc < 1)
|
|
return EC_ERROR_PARAM2;
|
|
|
|
freq = strtoi(argv[0], &e, 10);
|
|
if (*e)
|
|
return EC_ERROR_PARAM2;
|
|
pd_set_clock(0, freq);
|
|
ccprintf("TX frequency = %d Hz\n", freq);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int cmd_rx_threshold(int argc, char **argv)
|
|
{
|
|
int mv;
|
|
char *e;
|
|
|
|
if (argc < 1)
|
|
return EC_ERROR_PARAM2;
|
|
|
|
mv = strtoi(argv[0], &e, 10);
|
|
if (*e)
|
|
return EC_ERROR_PARAM2;
|
|
|
|
/* set DAC voltage (Vref = 3.3V) */
|
|
STM32_DAC_DHR12RD = mv * 4096 / 3300;
|
|
ccprintf("RX threshold = %d mV\n", mv);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int cmd_ina_dump(int argc, char **argv, int index)
|
|
{
|
|
if (index == 1) { /* VCONN INA is off by default, switch it on */
|
|
ina2xx_write(index, INA2XX_REG_CONFIG, 0x4123);
|
|
/*
|
|
* wait for the end of conversion : 2x 1.1ms as defined
|
|
* by the Vb and Vsh CT bits in the CONFIG register above.
|
|
*/
|
|
udelay(2200);
|
|
}
|
|
|
|
ccprintf("%s = %d mV ; %d mA\n", index == 0 ? "VBUS" : "VCONN",
|
|
ina2xx_get_voltage(index), ina2xx_get_current(index));
|
|
|
|
if (index == 1) /* power off VCONN INA */
|
|
ina2xx_write(index, INA2XX_REG_CONFIG, 0);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int cmd_bufwr(int argc, char **argv)
|
|
{
|
|
int idx, cnt, i;
|
|
char *e;
|
|
|
|
cnt = argc - 1;
|
|
if (argc < 2 || cnt > INJ_CMD_COUNT)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
idx = strtoi(argv[0], &e, 10);
|
|
if (*e || idx + cnt > INJ_CMD_COUNT)
|
|
return EC_ERROR_PARAM2;
|
|
|
|
for (i = 0; i < cnt; i++)
|
|
if (hex8tou32(argv[i+1], inj_cmds + idx + i))
|
|
return EC_ERROR_INVAL;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int cmd_bufrd(int argc, char **argv)
|
|
{
|
|
int idx, i;
|
|
int cnt = 1;
|
|
char *e;
|
|
|
|
if (argc < 1)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
idx = strtoi(argv[0], &e, 10);
|
|
if (*e || idx > INJ_CMD_COUNT)
|
|
return EC_ERROR_PARAM2;
|
|
|
|
if (argc >= 2)
|
|
cnt = strtoi(argv[1], &e, 10);
|
|
|
|
if (*e || idx + cnt > INJ_CMD_COUNT)
|
|
return EC_ERROR_PARAM3;
|
|
|
|
for (i = idx; i < idx + cnt; i++)
|
|
ccprintf("%08x ", inj_cmds[i]);
|
|
ccprintf("\n");
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int cmd_sink(int argc, char **argv)
|
|
{
|
|
/*
|
|
* Jump to the RW section which should contain a firmware acting
|
|
* as a USB PD sink
|
|
*/
|
|
system_run_image_copy(SYSTEM_IMAGE_RW);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int cmd_trace(int argc, char **argv)
|
|
{
|
|
if (argc < 1)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
if (!strcasecmp(argv[0], "on") ||
|
|
!strcasecmp(argv[0], "1"))
|
|
set_trace_mode(TRACE_MODE_ON);
|
|
else if (!strcasecmp(argv[0], "raw"))
|
|
set_trace_mode(TRACE_MODE_RAW);
|
|
else if (!strcasecmp(argv[0], "off") ||
|
|
!strcasecmp(argv[0], "0"))
|
|
set_trace_mode(TRACE_MODE_OFF);
|
|
else
|
|
return EC_ERROR_PARAM2;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int command_tw(int argc, char **argv)
|
|
{
|
|
if (!strcasecmp(argv[1], "send"))
|
|
return cmd_send(argc - 2, argv + 2);
|
|
else if (!strcasecmp(argv[1], "fsm"))
|
|
return cmd_fsm(argc - 2, argv + 2);
|
|
else if (!strcasecmp(argv[1], "bufwr"))
|
|
return cmd_bufwr(argc - 2, argv + 2);
|
|
else if (!strcasecmp(argv[1], "bufrd"))
|
|
return cmd_bufrd(argc - 2, argv + 2);
|
|
else if (!strcasecmp(argv[1], "cc"))
|
|
return cmd_cc_level(argc - 2, argv + 2);
|
|
else if (!strncasecmp(argv[1], "resistor", 3))
|
|
return cmd_resistor(argc - 2, argv + 2);
|
|
else if (!strcasecmp(argv[1], "sink"))
|
|
return cmd_sink(argc - 2, argv + 2);
|
|
else if (!strcasecmp(argv[1], "trace"))
|
|
return cmd_trace(argc - 2, argv + 2);
|
|
else if (!strcasecmp(argv[1], "txclock"))
|
|
return cmd_tx_clock(argc - 2, argv + 2);
|
|
else if (!strncasecmp(argv[1], "rxthresh", 8))
|
|
return cmd_rx_threshold(argc - 2, argv + 2);
|
|
else if (!strcasecmp(argv[1], "vbus"))
|
|
return cmd_ina_dump(argc - 2, argv + 2, 0);
|
|
else if (!strcasecmp(argv[1], "vconn"))
|
|
return cmd_ina_dump(argc - 2, argv + 2, 1);
|
|
else
|
|
return EC_ERROR_PARAM1;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(twinkie, command_tw,
|
|
"[send|fsm|cc|resistor|txclock|rxthresh|vbus|vconn]",
|
|
"Manual Twinkie tweaking");
|