| /* Copyright (c) 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-1]; |
| 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"); |