diff options
author | Aseda Aboagye <aaboagye@google.com> | 2017-05-11 11:35:54 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2017-06-05 14:49:09 -0700 |
commit | 7dab0e853caf04a3c8d5d1f7b795a23f8b6142d9 (patch) | |
tree | 431eb724321c53d66d4d3de0349a955ebe2d4bb3 | |
parent | 2ac6afd3e68bde5b50ddc9d35f832112c56f22ab (diff) | |
download | chrome-ec-7dab0e853caf04a3c8d5d1f7b795a23f8b6142d9.tar.gz |
chip: g: Add support for UART bit banging.
The UART block on the g chip has no functionality to adjust the parity.
Unfortunately, this feature is needed for certain applications.
This commit adds a UART bit bang driver with support for configuring the
baud rate and parity. It currently only supports 8 data bits.
BUG=b:35648297
BRANCH=cr50
TEST=make -j buildall
TEST=With some other patches, successfully flash rowan EC at 9600 baud.
Change-Id: I86a160c0960e46b3a8bb1057518f625aefb7d81f
Signed-off-by: Aseda Aboagye <aaboagye@google.com>
Reviewed-on: https://chromium-review.googlesource.com/503473
Commit-Ready: Aseda Aboagye <aaboagye@chromium.org>
Tested-by: Aseda Aboagye <aaboagye@chromium.org>
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
-rw-r--r-- | chip/g/build.mk | 3 | ||||
-rw-r--r-- | chip/g/uart_bitbang.c | 390 | ||||
-rw-r--r-- | chip/g/uart_bitbang.h | 112 | ||||
-rw-r--r-- | chip/g/uartn.c | 66 | ||||
-rw-r--r-- | include/config.h | 3 |
5 files changed, 570 insertions, 4 deletions
diff --git a/chip/g/build.mk b/chip/g/build.mk index d425b4c09e..997a3965bb 100644 --- a/chip/g/build.mk +++ b/chip/g/build.mk @@ -26,7 +26,8 @@ chip-y += polling_uart.o else chip-y += uart.o chip-y += uartn.o -endif +chip-$(CONFIG_UART_BITBANG)+= uart_bitbang.o +endif # undef CONFIG_POLLING_UART chip-$(CONFIG_DCRYPTO)+= crypto_api.o diff --git a/chip/g/uart_bitbang.c b/chip/g/uart_bitbang.c new file mode 100644 index 0000000000..54dcf0d7e0 --- /dev/null +++ b/chip/g/uart_bitbang.c @@ -0,0 +1,390 @@ +/* Copyright 2017 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 "common.h" +#include "compile_time_macros.h" +#include "console.h" +#include "gpio.h" +#include "pmu.h" +#include "registers.h" +#include "task.h" +#include "timer.h" +#include "uart_bitbang.h" +#include "uartn.h" + +#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ##args) +#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ##args) + +/* Support the "standard" baud rates. */ +#define IS_BAUD_RATE_SUPPORTED(rate) \ + ((rate == 1200) || (rate == 2400) || (rate == 4800) || (rate == 9600) \ + || (rate == 19200) || (rate == 38400) || (rate == 57600) || \ + (rate == 115200)) + +#define RX_BUF_SIZE 8 +#define DISCARD_LOG 8 + +#define BUF_NEXT(idx) ((idx+1) % RX_BUF_SIZE) + +#define TIMEUS_CLK_FREQ 24 /* units: MHz */ + +/* Bitmask of UARTs with bit banging enabled. */ +static uint8_t bitbang_enabled; +/* TODO(aaboagye): just a hack for now. */ +static int rx_buf[RX_BUF_SIZE]; +static int parity_err_discard[DISCARD_LOG]; +static int parity_discard_idx; +static int stop_bit_discard[DISCARD_LOG]; +static int stop_bit_discard_idx; + +/* debug counters */ +static int read_char_cnt; +static int rx_buff_inserted_cnt; +static int rx_buff_rx_char_cnt; +static int stop_bit_err_cnt; +static int parity_err_cnt; + +/* Current bitbang context */ +static int tx_pin; +static int rx_pin; +static uint32_t bit_period_ticks; +static uint8_t set_parity; + +static int is_uart_allowed(int uart) +{ + return uart == bitbang_config.uart; +} + +int uart_bitbang_is_enabled(int uart) +{ + return (is_uart_allowed(uart) && !!bitbang_enabled); +} + +int uart_bitbang_enable(int uart, int baud_rate, int parity) +{ + /* We only want to bit bang 1 UART at a time. */ + if (bitbang_enabled) + return EC_ERROR_BUSY; + + if (!is_uart_allowed(uart)) { + CPRINTF("bit bang config not found for UART%d", uart); + return EC_ERROR_INVAL; + } + + /* Check desired properties. */ + if (!IS_BAUD_RATE_SUPPORTED(baud_rate)) { + CPRINTF("Err: invalid baud rate (%d)", baud_rate); + return EC_ERROR_INVAL; + } + bitbang_config.baud_rate = baud_rate; + + switch (parity) { + case 0: + case 1: + case 2: + break; + + default: + CPRINTF("Err: invalid parity '%d'. (0:N, 1:O, 2:E)", parity); + return EC_ERROR_INVAL; + }; + bitbang_config.htp.parity = parity; + + /* Select the GPIOs instead of the UART block. */ + uartn_tx_disconnect(bitbang_config.uart); + REG32(bitbang_config.tx_pinmux_reg) = + bitbang_config.tx_pinmux_regval; + gpio_set_flags(bitbang_config.tx_gpio, GPIO_OUT_HIGH); + REG32(bitbang_config.rx_pinmux_reg) = + bitbang_config.rx_pinmux_regval; + gpio_set_flags(bitbang_config.rx_gpio, GPIO_INPUT); + + /* + * Ungate the microsecond timer so that we can use it. This is needed + * for accurate framing if using faster baud rates. + */ + pmu_clock_en(PERIPH_TIMEUS); + GR_TIMEUS_EN(0) = 0; + GR_TIMEUS_MAXVAL(0) = 0xFFFFFFFF; + GR_TIMEUS_EN(0) = 1; + + /* Save context information. */ + tx_pin = bitbang_config.tx_gpio; + rx_pin = bitbang_config.rx_gpio; + bit_period_ticks = TIMEUS_CLK_FREQ * + ((1 * SECOND) / bitbang_config.baud_rate); + set_parity = bitbang_config.htp.parity; + + /* Register the function pointers. */ + uartn_funcs[uart]._rx_available = _uart_bitbang_rx_available; + uartn_funcs[uart]._write_char = _uart_bitbang_write_char; + uartn_funcs[uart]._read_char = _uart_bitbang_read_char; + + bitbang_enabled = 1; + gpio_enable_interrupt(bitbang_config.rx_gpio); + ccprintf("successfully enabled\n"); + return EC_SUCCESS; +} + +int uart_bitbang_disable(int uart) +{ + if (!uart_bitbang_is_enabled(uart)) + return EC_SUCCESS; + + /* + * This is safe because if the UART was not specified in the config, we + * would have already returned. + */ + bitbang_enabled = 0; + gpio_reset(bitbang_config.tx_gpio); + gpio_reset(bitbang_config.rx_gpio); + + /* Unregister the function pointers. */ + uartn_funcs[uart]._rx_available = _uartn_rx_available; + uartn_funcs[uart]._write_char = _uartn_write_char; + uartn_funcs[uart]._read_char = _uartn_read_char; + + /* Gate the microsecond timer since we're done with it. */ + pmu_clock_dis(PERIPH_TIMEUS); + + /* Reconnect the GPIO to the UART block. */ + gpio_disable_interrupt(bitbang_config.rx_gpio); + uartn_tx_connect(uart); + return EC_SUCCESS; +} + +static void wait_ticks(uint32_t ticks) +{ + uint32_t t0 = GR_TIMEUS_CUR_MAJOR(0); + + while ((GR_TIMEUS_CUR_MAJOR(0) - t0) < ticks) + ; +} + +void uart_bitbang_write_char(int uart, char c) +{ + int val; + int ones; + int i; + + if (!uart_bitbang_is_enabled(uart)) + return; + + interrupt_disable(); + + /* Start bit. */ + gpio_set_level(tx_pin, 0); + wait_ticks(bit_period_ticks); + + /* 8 data bits. */ + ones = 0; + for (i = 0; i < 8; i++) { + val = (c & (1 << i)); + /* Count 1's in order to handle parity bit. */ + if (val) + ones++; + gpio_set_level(tx_pin, val); + wait_ticks(bit_period_ticks); + } + + /* Optional parity. */ + switch (set_parity) { + case 1: /* odd parity */ + if (ones & 0x1) + gpio_set_level(tx_pin, 0); + else + gpio_set_level(tx_pin, 1); + wait_ticks(bit_period_ticks); + break; + + case 2: /* even parity */ + if (ones & 0x1) + gpio_set_level(tx_pin, 1); + else + gpio_set_level(tx_pin, 0); + wait_ticks(bit_period_ticks); + break; + + case 0: /* no parity */ + default: + break; + }; + + /* 1 stop bit. */ + gpio_set_level(tx_pin, 1); + wait_ticks(bit_period_ticks); + interrupt_enable(); +} + +int uart_bitbang_receive_char(int uart) +{ + uint8_t rx_char; + int i; + int rv; + int ones; + int parity_bit; + int stop_bit; + uint8_t head; + uint8_t tail; + + /* Disable interrupts so that we aren't interrupted. */ + interrupt_disable(); + rx_buff_rx_char_cnt++; + rv = EC_SUCCESS; + + rx_char = 0; + + /* Wait 1 bit period for the start bit. */ + wait_ticks(bit_period_ticks); + + /* 8 data bits. */ + ones = 0; + for (i = 0; i < 8; i++) { + if (gpio_get_level(rx_pin)) { + ones++; + rx_char |= (1 << i); + } + wait_ticks(bit_period_ticks); + } + + /* optional parity or stop bit. */ + parity_bit = gpio_get_level(rx_pin); + if (set_parity) { + wait_ticks(bit_period_ticks); + stop_bit = gpio_get_level(rx_pin); + } else { + /* If there's no parity, that _was_ the stop bit. */ + stop_bit = parity_bit; + } + + /* Check the parity if necessary. */ + switch (set_parity) { + case 2: /* even parity */ + if (ones & 0x1) + rv = parity_bit ? EC_SUCCESS : EC_ERROR_CRC; + else + rv = parity_bit ? EC_ERROR_CRC : EC_SUCCESS; + break; + + case 1: /* odd parity */ + if (ones & 0x1) + rv = parity_bit ? EC_ERROR_CRC : EC_SUCCESS; + else + rv = parity_bit ? EC_SUCCESS : EC_ERROR_CRC; + break; + + case 0: + default: + break; + } + + if (rv != EC_SUCCESS) { + parity_err_cnt++; + parity_err_discard[parity_discard_idx] = rx_char; + parity_discard_idx = (parity_discard_idx + 1) % DISCARD_LOG; + } + + /* Check that the stop bit is valid. */ + if (stop_bit != 1) { + rv = EC_ERROR_CRC; + stop_bit_err_cnt++; + stop_bit_discard[stop_bit_discard_idx] = rx_char; + stop_bit_discard_idx = (stop_bit_discard_idx + 1) % DISCARD_LOG; + } + + if (rv != EC_SUCCESS) { + interrupt_enable(); + return rv; + } + + /* Place the received char in the RX buffer. */ + head = bitbang_config.htp.head; + tail = bitbang_config.htp.tail; + if (BUF_NEXT(tail) != head) { + rx_buf[tail] = rx_char; + bitbang_config.htp.tail = BUF_NEXT(tail); + rx_buff_inserted_cnt++; + } + + interrupt_enable(); + return EC_SUCCESS; +} + +int uart_bitbang_read_char(int uart) +{ + int c; + uint8_t head; + + if (!is_uart_allowed(uart)) + return 0; + + head = bitbang_config.htp.head; + c = rx_buf[head]; + + if (head != bitbang_config.htp.tail) + bitbang_config.htp.head = BUF_NEXT(head); + + read_char_cnt++; + return c; +} + +int uart_bitbang_is_char_available(int uart) +{ + if (!is_uart_allowed(uart)) + return 0; + + return bitbang_config.htp.head != bitbang_config.htp.tail; +} + +static int command_bitbang_dump_stats(int argc, char **argv) +{ + int i; + + if (argc == 2) { + /* Clear the counters. */ + if (!strncasecmp(argv[1], "clear", 5)) { + parity_err_cnt = 0; + stop_bit_err_cnt = 0; + rx_buff_rx_char_cnt = 0; + read_char_cnt = 0; + rx_buff_inserted_cnt = 0; + return EC_SUCCESS; + } + return EC_ERROR_PARAM1; + } + + ccprintf("Errors:\n"); + ccprintf("%d Parity Errors\n", parity_err_cnt); + ccprintf("%d Stop Bit Errors\n", stop_bit_err_cnt); + ccprintf("Buffer info\n"); + ccprintf("%d received\n", rx_buff_rx_char_cnt); + ccprintf("%d chars inserted\n", rx_buff_inserted_cnt); + ccprintf("%d chars read\n", read_char_cnt); + ccprintf("Contents\n"); + ccprintf("["); + for (i = 0; i < RX_BUF_SIZE; i++) + ccprintf(" %02x ", rx_buf[i] & 0xFF); + ccprintf("]\n"); + ccprintf("head: %d\ntail: %d\n", + bitbang_config.htp.head, + bitbang_config.htp.tail); + ccprintf("Discards\nparity: "); + ccprintf("["); + for (i = 0; i < DISCARD_LOG; i++) + ccprintf(" %02x ", parity_err_discard[i] & 0xFF); + ccprintf("]\n"); + ccprintf("idx: %d\n", parity_discard_idx); + ccprintf("stop bit: "); + ccprintf("["); + for (i = 0; i < DISCARD_LOG; i++) + ccprintf(" %02x ", stop_bit_discard[i] & 0xFF); + ccprintf("]\n"); + ccprintf("idx: %d\n", stop_bit_discard_idx); + cflush(); + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(bbstats, command_bitbang_dump_stats, + "", + "dumps bitbang stats"); diff --git a/chip/g/uart_bitbang.h b/chip/g/uart_bitbang.h new file mode 100644 index 0000000000..f29c35dd34 --- /dev/null +++ b/chip/g/uart_bitbang.h @@ -0,0 +1,112 @@ +/* Copyright 2017 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. + */ + +#ifndef __CROS_EC_CHIP_G_UART_BITBANG_H +#define __CROS_EC_CHIP_G_UART_BITBANG_H + +/* UART Bit Banging */ + +#include "common.h" +#include "gpio.h" + +/* These are functions that we'll have to replace. */ +struct uartn_function_ptrs { + int (*_rx_available)(int uart); + void (*_write_char)(int uart, char c); + int (*_read_char)(int uart); +}; + +/* + * And these are the function definitions. The functions live in + * chip/g/uartn.c. + */ +extern int _uartn_rx_available(int uart); +extern void _uartn_write_char(int uart, char c); +extern int _uartn_read_char(int uart); +extern int _uart_bitbang_rx_available(int uart); +extern void _uart_bitbang_write_char(int uart, char c); +extern int _uart_bitbang_read_char(int uart); +extern struct uartn_function_ptrs uartn_funcs[]; + +struct uart_bitbang_properties { + enum gpio_signal tx_gpio; + enum gpio_signal rx_gpio; + uint32_t tx_pinmux_reg; + uint32_t tx_pinmux_regval; + uint32_t rx_pinmux_reg; + uint32_t rx_pinmux_regval; + int baud_rate; + uint8_t uart; + struct { + unsigned int head : 3; + unsigned int tail : 3; + unsigned int parity : 2; + } htp __packed; +}; + +/* In order to bitbang a UART, a board must define a bitbang_config. */ +extern struct uart_bitbang_properties bitbang_config; + +/** + * Enable bit banging mode for a UART. + * + * @param uart: Index of UART to enable bit banging mode. + * @param baud_rate: desired baud rate. + * @param parity: 0: no parity, 1: odd parity, 2: even parity. + * + * @returns EC_SUCCESS on success, otherwise an error. + */ +int uart_bitbang_enable(int uart, int baud_rate, int parity); + +/** + * Disable bit banging mode for a UART. + * + * @param uart: Index of UART to disable bit banging mode. + */ +int uart_bitbang_disable(int uart); + +/** + * Returns 1 if bit banging mode is enabled for the UART. + * + * @param uart: Index of UART to query. + */ +int uart_bitbang_is_enabled(int uart); + +/** + * TX a character on a UART configured for bit banging mode. + * + * @param uart: Index of UART to use. + * @param c: Character to send out. + */ +void uart_bitbang_write_char(int uart, char c); + +/** + * Sample the RX line on a UART configured for bit banging mode. + * + * This is called when a falling edge is seen on the RX line and will attempt to + * receive a character. Incoming data with framing errors or parity errors will + * be discarded. + * + * @param uart: Index of UART to use. + * @returns EC_SUCCESS if a character was successfully received, EC_ERROR_CRC if + * there was a framing or parity issue. + */ +int uart_bitbang_receive_char(int uart); + +/** + * Returns 1 if there are characters available for consumption, otherwise 0. + * + * @param uart: Index of UART to check. + */ +int uart_bitbang_is_char_available(int uart); + +/** + * Retrieve a character from the bit bang RX buffer. + * + * @param uart: Index of UART to use. + */ +int uart_bitbang_read_char(int uart); + +#endif /* __CROS_EC_CHIP_G_UART_BITBANG_H */ diff --git a/chip/g/uartn.c b/chip/g/uartn.c index 369ec17a59..6140e8cff4 100644 --- a/chip/g/uartn.c +++ b/chip/g/uartn.c @@ -10,6 +10,7 @@ #include "system.h" #include "task.h" #include "uart.h" +#include "uart_bitbang.h" #include "util.h" #define USE_UART_INTERRUPTS (!(defined(CONFIG_CUSTOMIZED_RO) && \ @@ -25,6 +26,26 @@ static struct uartn_interrupts interrupt[] = { {GC_IRQNUM_UART2_TXINT, GC_IRQNUM_UART2_RXINT}, }; +struct uartn_function_ptrs uartn_funcs[3] = { + { + _uartn_rx_available, + _uartn_write_char, + _uartn_read_char, + }, + + { + _uartn_rx_available, + _uartn_write_char, + _uartn_read_char, + }, + + { + _uartn_rx_available, + _uartn_write_char, + _uartn_read_char, + }, +}; + void uartn_tx_start(int uart) { if (!uart_init_done()) @@ -92,13 +113,18 @@ int uartn_tx_ready(int uart) return !(GR_UART_STATE(uart) & GC_UART_STATE_TX_MASK); } -int uartn_rx_available(int uart) +int _uartn_rx_available(int uart) { /* True if the RX buffer is not completely empty. */ return !(GR_UART_STATE(uart) & GC_UART_STATE_RXEMPTY_MASK); } -void uartn_write_char(int uart, char c) +int uartn_rx_available(int uart) +{ + return uartn_funcs[uart]._rx_available(uart); +} + +void _uartn_write_char(int uart, char c) { /* Wait for space in transmit FIFO. */ while (!uartn_tx_ready(uart)) @@ -107,11 +133,45 @@ void uartn_write_char(int uart, char c) GR_UART_WDATA(uart) = c; } -int uartn_read_char(int uart) +void uartn_write_char(int uart, char c) +{ + uartn_funcs[uart]._write_char(uart, c); +} + +int _uartn_read_char(int uart) { return GR_UART_RDATA(uart); } +int uartn_read_char(int uart) +{ + return uartn_funcs[uart]._read_char(uart); +} + +#ifdef CONFIG_UART_BITBANG +int _uart_bitbang_rx_available(int uart) +{ + if (uart_bitbang_is_enabled(uart)) + return uart_bitbang_is_char_available(uart); + + return 0; +} + +void _uart_bitbang_write_char(int uart, char c) +{ + if (uart_bitbang_is_enabled(uart)) + uart_bitbang_write_char(uart, c); +} + +int _uart_bitbang_read_char(int uart) +{ + if (uart_bitbang_is_enabled(uart)) + return uart_bitbang_read_char(uart); + + return 0; +} +#endif /* defined(CONFIG_UART_BITBANG) */ + void uartn_disable_interrupt(int uart) { task_disable_irq(interrupt[uart].tx_int); diff --git a/include/config.h b/include/config.h index f628673bd1..5174c655e4 100644 --- a/include/config.h +++ b/include/config.h @@ -2215,6 +2215,9 @@ /* Baud rate for UARTs */ #define CONFIG_UART_BAUD_RATE 115200 +/* Allow bit banging of a UARTs pins and bypassing the UART block. */ +#undef CONFIG_UART_BITBANG + /* UART index (number) for EC console */ #undef CONFIG_UART_CONSOLE |