Files
OpenCellular/chip/g/uart_bitbang.c
Aseda Aboagye 27a39b44d1 g: uart_bitbang: Keep debug stuff off by default.
There are some useful UART bitbang commands, statistics, and logs and
such.  These shouldn't be enabled by default, and this commit makes it
so.

BUG=b:35648297
BRANCH=cr50
TEST=Build an image that enables UART bit banging with BITBANG_DEBUG set
to 0.  Verify that the associated debug commands and statistics are not
present.

Change-Id: Ic0348a6fb1620229e2ed601e0ff549596d814e1e
Signed-off-by: Aseda Aboagye <aaboagye@google.com>
Reviewed-on: https://chromium-review.googlesource.com/527605
Commit-Ready: Aseda Aboagye <aaboagye@chromium.org>
Tested-by: Aseda Aboagye <aaboagye@chromium.org>
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
2017-06-07 23:45:30 -07:00

505 lines
11 KiB
C

/* 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)
#define BITBANG_DEBUG 0 /* Set to 1 to enable debug counters and logs. */
/* 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 BUF_NEXT(idx) ((idx+1) % RX_BUF_SIZE)
#define TIMEUS_CLK_FREQ 24 /* units: MHz */
/* Flag indicating whether bit banging is enabled or not. */
static uint8_t bitbang_enabled;
static int rx_buf[RX_BUF_SIZE];
/* Current bitbang context */
static int tx_pin;
static int rx_pin;
static uint32_t bit_period_ticks;
static uint8_t set_parity;
#if BITBANG_DEBUG
/* debug counters and log */
#define DISCARD_LOG 8
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;
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;
#endif /* BITBANG_DEBUG */
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();
#if BITBANG_DEBUG
rx_buff_rx_char_cnt++;
#endif /* BITBANG_DEBUG */
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 BITBANG_DEBUG
if (rv != EC_SUCCESS) {
parity_err_cnt++;
parity_err_discard[parity_discard_idx] = rx_char;
parity_discard_idx = (parity_discard_idx + 1) % DISCARD_LOG;
}
#endif /* BITBANG_DEBUG */
/* Check that the stop bit is valid. */
if (stop_bit != 1) {
rv = EC_ERROR_CRC;
#if BITBANG_DEBUG
stop_bit_err_cnt++;
stop_bit_discard[stop_bit_discard_idx] = rx_char;
stop_bit_discard_idx = (stop_bit_discard_idx + 1) % DISCARD_LOG;
#endif /* BITBANG_DEBUG */
}
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);
#if BITBANG_DEBUG
rx_buff_inserted_cnt++;
#endif /* BITBANG_DEBUG */
}
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);
#if BITBANG_DEBUG
read_char_cnt++;
#endif /* BITBANG_DEBUG */
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;
}
#if BITBANG_DEBUG
static int write_test_pattern(int uart, int pattern_idx)
{
if (!uart_bitbang_is_enabled(uart)) {
ccprintf("bit banging mode not enabled for UART%d\n", uart);
return EC_ERROR_INVAL;
}
switch (pattern_idx) {
case 0:
uartn_write_char(uart, 'a');
uartn_write_char(uart, 'b');
uartn_write_char(uart, 'c');
uartn_write_char(uart, '\n');
ccprintf("wrote: 'abc\\n'\n");
break;
case 1:
uartn_write_char(uart, 0xAA);
uartn_write_char(uart, 0xCC);
uartn_write_char(uart, 0x55);
ccprintf("wrote: '0xAA 0xCC 0x55'\n");
break;
default:
ccprintf("unknown test pattern\n");
return EC_ERROR_INVAL;
};
return EC_SUCCESS;
}
#endif /* BITBANG_DEBUG */
static int command_bitbang(int argc, char **argv)
{
int uart;
int baud_rate;
int parity;
if (argc > 1) {
uart = atoi(argv[1]);
if (argc == 3) {
if (!strcasecmp("disable", argv[2]))
return uart_bitbang_disable(uart);
else
return EC_ERROR_PARAM2;
}
if (argc == 4) {
#if BITBANG_DEBUG
if (!strncasecmp("test", argv[2], 4))
return write_test_pattern(uart, atoi(argv[3]));
#endif /* BITBANG_DEBUG */
baud_rate = atoi(argv[2]);
if (!strcasecmp("odd", argv[3]))
parity = 1;
else if (!strcasecmp("even", argv[3]))
parity = 2;
else if (!strcasecmp("none", argv[3]))
parity = 0;
else
return EC_ERROR_PARAM3;
return uart_bitbang_enable(uart, baud_rate, parity);
}
return EC_ERROR_PARAM_COUNT;
}
if (!uart_bitbang_is_enabled(bitbang_config.uart)) {
ccprintf("bit banging mode disabled.\n");
} else {
ccprintf("baud rate - parity\n");
ccprintf(" %6d ", bitbang_config.baud_rate);
switch (bitbang_config.htp.parity) {
case 1:
ccprintf("odd\n");
break;
case 2:
ccprintf("even\n");
break;
case 0:
default:
ccprintf("none\n");
break;
};
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(bitbang, command_bitbang,
"<uart> <baud_rate> <odd,even,none> | <uart> disable "
#if BITBANG_DEBUG
"| <uart> test <0, 1>"
#endif /* BITBANG_DEBUG */
, "set bit bang mode");
#if BITBANG_DEBUG
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");
#endif /* BITBANG_DEBUG */