From d1b282db26a73650b51c680a693e707579ea8035 Mon Sep 17 00:00:00 2001 From: Tom Warren Date: Thu, 28 Apr 2011 07:55:13 -0700 Subject: Fix Seaboard UART corruption on SPI activity On Seaboard the UART and SPI interfere with each other. This causes the UART to receive spurious zero bytes after SPI transactions and also means that SPI can corrupt a few output characters when it starts up if they are still in the UART buffer. This hack corrects this by making SPI record that it may have corrupted the UART, and making the UART take evasive action. BUG=chromium-os:13228 TEST=Try developer U-Boot on Seaboard, make sure it auto-boots OK now Review URL: http://codereview.chromium.org/6715017 Change-Id: If2281357f177eeb3a19a170ddea22adbcf5942e9 Reviewed-on: http://gerrit.chromium.org/gerrit/191 Reviewed-by: Simon Glass Tested-by: Simon Glass --- arch/arm/include/asm/arch-tegra2/tegra2_spi.h | 4 +- board/nvidia/seaboard/seaboard.c | 69 +++++++++++++++++++------- drivers/serial/ns16550.c | 44 ++++++++++++++++- drivers/spi/tegra2_spi.c | 71 +++++++++------------------ include/configs/seaboard.h | 25 ++++++++++ include/ns16550.h | 2 + 6 files changed, 147 insertions(+), 68 deletions(-) diff --git a/arch/arm/include/asm/arch-tegra2/tegra2_spi.h b/arch/arm/include/asm/arch-tegra2/tegra2_spi.h index 60dc0c8637b..d9e3caad707 100644 --- a/arch/arm/include/asm/arch-tegra2/tegra2_spi.h +++ b/arch/arm/include/asm/arch-tegra2/tegra2_spi.h @@ -70,8 +70,8 @@ struct spi_tegra { #define SPI_STAT_SEL_TXRX_N (1 << 16) #define SPI_STAT_CUR_BLKCNT (1 << 15) -#define GMD_SEL_SFLASH (3 << 30) -#define GMC_SEL_SFLASH (3 << 2) +#define GMD_SEL_SFLASH_RANGE 31 : 30 +#define GMC_SEL_SFLASH_RANGE 3 : 2 #define SPI_TIMEOUT 1000 diff --git a/board/nvidia/seaboard/seaboard.c b/board/nvidia/seaboard/seaboard.c index 4b9a8f33e89..86655098faa 100644 --- a/board/nvidia/seaboard/seaboard.c +++ b/board/nvidia/seaboard/seaboard.c @@ -23,8 +23,14 @@ #include #include -#include +#include +#include #include +#include +#include +#include + +static enum spi_uart_switch switch_pos; /* * Routine: gpio_config_uart @@ -32,21 +38,48 @@ */ void gpio_config_uart(void) { - int gp = GPIO_PI3; - struct gpio_ctlr *gpio = (struct gpio_ctlr *)NV_PA_GPIO_BASE; - struct gpio_ctlr_bank *bank = &gpio->gpio_bank[GPIO_BANK(gp)]; - u32 val; - - /* Enable UART via GPIO_PI3 (port 8, bit 3) so serial console works */ - val = readl(&bank->gpio_config[GPIO_PORT(gp)]); - val |= 1 << GPIO_BIT(gp); - writel(val, &bank->gpio_config[GPIO_PORT(gp)]); - - val = readl(&bank->gpio_out[GPIO_PORT(gp)]); - val &= ~(1 << GPIO_BIT(gp)); - writel(val, &bank->gpio_out[GPIO_PORT(gp)]); - - val = readl(&bank->gpio_dir_out[GPIO_PORT(gp)]); - val |= 1 << GPIO_BIT(gp); - writel(val, &bank->gpio_dir_out[GPIO_PORT(gp)]); + gpio_direction_output(UART_DISABLE_GPIO, 0); + switch_pos = SWITCH_UART; +} + +#ifdef CONFIG_SPI_CORRUPTS_UART + +void seaboard_switch_spi_uart(enum spi_uart_switch new_pos) +{ + struct pmux_tri_ctlr *pmt = (struct pmux_tri_ctlr *)NV_PA_APB_MISC_BASE; + + if (new_pos == switch_pos) + return; + + /* if the UART was selected, allow it to drain */ + if (switch_pos == SWITCH_UART) + NS16550_drain((NS16550_t)CONFIG_SPI_CORRUPTS_UART, + CONFIG_SPI_CORRUPTS_UART_NR); + + /* We need to dynamically change the pinmux, shared w/UART RXD/CTS */ + bf_writel(GMC_SEL_SFLASH, new_pos == SWITCH_SPI ? 3 : 0, + &pmt->pmt_ctl_b); + + /* + * On Seaboard, MOSI/MISO are shared w/UART. + * Use GPIO I3 (UART_DISABLE) to tristate UART during SPI activity. + * Enable UART later (cs_deactivate) so we can use it for U-Boot comms. + */ + gpio_direction_output(UART_DISABLE_GPIO, new_pos == SWITCH_SPI); + switch_pos = new_pos; + + /* if the SPI was selected, clear any junk bytes in the UART */ + if (switch_pos == SWITCH_UART) { + /* TODO: What if it is part-way through clocking in junk? */ + udelay(100); + NS16550_clear((NS16550_t)CONFIG_SPI_CORRUPTS_UART, + CONFIG_SPI_CORRUPTS_UART_NR); + } +} + +void enable_uart(void) +{ + seaboard_switch_spi_uart(SWITCH_UART); } + +#endif diff --git a/drivers/serial/ns16550.c b/drivers/serial/ns16550.c index ed3428d7d84..71c523dfeba 100644 --- a/drivers/serial/ns16550.c +++ b/drivers/serial/ns16550.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -31,6 +32,16 @@ DECLARE_GLOBAL_DATA_PTR; #define CONFIG_SYS_NS16550_IER 0x00 #endif /* CONFIG_SYS_NS16550_IER */ +/* + * Signal that we are about to use the UART. This unfortunate hack is + * required by Seaboard, which cannot use its console and SPI at the same + * time. If the board file provides this, the board config will declare it. + * Let this be a lesson for others. + */ +#ifndef CONFIG_SPI_CORRUPTS_UART +inline void uart_enable(void) {} +#endif + void NS16550_init (NS16550_t com_port, int baud_divisor) { serial_out(CONFIG_SYS_NS16550_IER, &com_port->ier); @@ -71,10 +82,12 @@ void NS16550_reinit (NS16550_t com_port, int baud_divisor) serial_out((baud_divisor >> 8) & 0xff, &com_port->dlm); serial_out(UART_LCRVAL, &com_port->lcr); } + #endif /* CONFIG_NS16550_MIN_FUNCTIONS */ void NS16550_putc (NS16550_t com_port, char c) { + uart_enable(); while ((serial_in(&com_port->lsr) & UART_LSR_THRE) == 0); serial_out(c, &com_port->thr); @@ -92,6 +105,7 @@ void NS16550_putc (NS16550_t com_port, char c) static char NS16550_raw_getc(NS16550_t regs) { + uart_enable(); while ((serial_in(®s->lsr) & UART_LSR_DR) == 0) { #ifdef CONFIG_USB_TTY extern void usbtty_poll(void); @@ -107,7 +121,6 @@ static int NS16550_raw_tstc(NS16550_t regs) return ((serial_in(®s->lsr) & UART_LSR_DR) != 0); } - #ifdef CONFIG_NS16550_BUFFER_READS #define BUF_SIZE 256 @@ -185,8 +198,37 @@ char NS16550_getc(NS16550_t regs, unsigned int port) int NS16550_tstc(NS16550_t regs, unsigned int port) { + uart_enable(); return NS16550_raw_tstc(regs); } #endif /* CONFIG_NS16550_BUFFER_READS */ + +/* Clear the UART's RX FIFO */ + +void NS16550_clear(NS16550_t regs, unsigned port) +{ + /* Reset RX fifo */ + serial_out(UART_FCR_FIFO_EN | UART_FCR_RXSR, ®s->fcr); + + /* Remove any pending characters */ + while (NS16550_raw_tstc(regs)) + NS16550_raw_getc(regs); +} + +/* Wait for the UART's output buffer and holding register to drain */ + +void NS16550_drain(NS16550_t regs, unsigned port) +{ + u32 mask = UART_LSR_TEMT | UART_LSR_THRE; + + /* Wait for the UART to finish sending */ + while ((serial_in(®s->lsr) & mask) != mask) + udelay(100); +#ifdef CONFIG_NS16550_BUFFER_READS + /* Quickly grab any incoming data while we can */ + fill_rx_buf(regs, port); +#endif +} + #endif /* CONFIG_NS16550_MIN_FUNCTIONS */ diff --git a/drivers/spi/tegra2_spi.c b/drivers/spi/tegra2_spi.c index 417fcc8dc74..e6ef00357e3 100644 --- a/drivers/spi/tegra2_spi.c +++ b/drivers/spi/tegra2_spi.c @@ -24,8 +24,10 @@ #include #include +#include /* for NS16550_drain and NS16550_clear */ #include #include +#include #include #include #include @@ -114,20 +116,17 @@ void spi_init(void) debug("spi_init: COMMAND = %08x\n", readl(&spi->command)); /* - * SPI pins on Tegra2 are muxed - change pinmux last due to UART issue + * SPI pins on Tegra2 are muxed - change pinmux last due to UART + * issue. GMD_SEL [31:30] = (3) SFLASH */ - reg = readl(&pmt->pmt_ctl_c); - reg |= GMD_SEL_SFLASH; /* GMD_SEL [31:30] = (3) SFLASH */ - writel(reg, &pmt->pmt_ctl_c); - debug("spi_init: PinMuxRegC = %08x\n", reg); + bf_writel(GMD_SEL_SFLASH, 3, &pmt->pmt_ctl_c); pinmux_tristate_disable(PIN_LSPI); /* * NOTE: * Don't set PinMux bits 3:2 to SPI here or subsequent UART data - * won't go out! It'll be correctly set in the actual SPI driver - * before/after any transactions (cs_activate/_deactivate). + * won't go out! It'll be correctly set in seaboard_switch_spi_uart(). */ } @@ -138,64 +137,34 @@ int spi_claim_bus(struct spi_slave *slave) void spi_release_bus(struct spi_slave *slave) { + /* + * We can't release UART_DISABLE and set pinmux to UART4 here since + * some code (e,g, spi_flash_probe) uses printf() while the SPI + * bus is held. That is arguably bad, but it has the advantage of + * already being in the source tree. + */ } void spi_cs_activate(struct spi_slave *slave) { - struct pmux_tri_ctlr *pmt = (struct pmux_tri_ctlr *)NV_PA_APB_MISC_BASE; struct spi_tegra *spi = (struct spi_tegra *)TEGRA2_SPI_BASE; u32 val; - /* - * Delay here to clean up comms - spurious chars seen around SPI xfers. - * Fine-tune later. - */ - udelay(1000); - - /* We need to dynamically change the pinmux, shared w/UART RXD/CTS */ - val = readl(&pmt->pmt_ctl_b); - val |= GMC_SEL_SFLASH; /* GMC_SEL [3:2] = (3) SFLASH */ - writel(val, &pmt->pmt_ctl_b); - - /* - * On Seaboard, MOSI/MISO are shared w/UART. - * Use GPIO I3 (UART_DISABLE) to tristate UART during SPI activity. - * Enable UART later (cs_deactivate) so we can use it for U-Boot comms. - */ - gpio_direction_output(UART_DISABLE_GPIO, 1); + seaboard_switch_spi_uart(SWITCH_SPI); /* CS is negated on Tegra, so drive a 1 to get a 0 */ val = readl(&spi->command); - writel((val |= SPI_CMD_CS_VAL), &spi->command); + writel(val | SPI_CMD_CS_VAL, &spi->command); } void spi_cs_deactivate(struct spi_slave *slave) { - struct pmux_tri_ctlr *pmt = (struct pmux_tri_ctlr *)NV_PA_APB_MISC_BASE; struct spi_tegra *spi = (struct spi_tegra *)TEGRA2_SPI_BASE; u32 val; - /* - * Delay here to clean up comms - spurious chars seen around SPI xfers. - * Fine-tune later. - */ - udelay(1000); - - /* We need to dynamically change the pinmux, shared w/UART RXD/CTS */ - val = readl(&pmt->pmt_ctl_b); - val &= ~GMC_SEL_SFLASH; /* GMC_SEL [3:2] = (0) UARTD */ - writel(val, &pmt->pmt_ctl_b); - - /* - * On Seaboard, MOSI/MISO are shared w/UART. - * GPIO I3 (UART_DISABLE) is used to tristate UART in cs_activate. - * Enable UART here by setting that GPIO to 0 so we can do U-Boot comms. - */ - gpio_direction_output(UART_DISABLE_GPIO, 0); - /* CS is negated on Tegra, so drive a 0 to get a 1 */ val = readl(&spi->command); - writel((val &= ~SPI_CMD_CS_VAL), &spi->command); + writel(val & ~SPI_CMD_CS_VAL, &spi->command); } int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, @@ -235,7 +204,8 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, } num_bytes -= bytes; - dout += bytes; + if (dout) + dout += bytes; bitlen -= bits; reg = readl(&spi->command); @@ -281,10 +251,17 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, /* swap bytes read in */ if (din != NULL) { + for (i = bytes - 1; i >= 0; --i) { + ((u8 *)din)[i] = + (tmpdin & 0xff); + tmpdin >>= 8; + } +/* this is not the same! for (i = 0; i < bytes; ++i) { ((u8 *)din)[i] = (tmpdin >> 24); tmpdin <<= 8; } +*/ din += bytes; } } diff --git a/include/configs/seaboard.h b/include/configs/seaboard.h index b50796334ba..b61dc1535b4 100644 --- a/include/configs/seaboard.h +++ b/include/configs/seaboard.h @@ -36,6 +36,11 @@ #define CONFIG_SERIAL_MULTI #define CONFIG_TEGRA2_ENABLE_UARTD #define CONFIG_SYS_NS16550_COM1 NV_PA_APB_UARTD_BASE +#define CONFIG_NS16550_BUFFER_READS + +/* Seaboard SPI activity corrupts the first UART */ +#define CONFIG_SPI_CORRUPTS_UART NV_PA_APB_UARTD_BASE +#define CONFIG_SPI_CORRUPTS_UART_NR 0 #define CONFIG_MACH_TYPE MACH_TYPE_SEABOARD #define CONFIG_SYS_BOARD_ODMDATA 0x300d8011 /* lp1, 1GB */ @@ -67,5 +72,25 @@ CONFIG_EXTRA_ENV_SETTINGS_COMMON \ "board=seaboard\0" \ +/* On Seaboard: GPIO_PI3 = Port I = 8, bit = 3 */ +#define UART_DISABLE_GPIO GPIO_PI3 + +#if defined(CONFIG_SPI_CORRUPTS_UART) && !defined(__ASSEMBLY__) + +/* position of the UART/SPI select switch */ +enum spi_uart_switch { + SWITCH_UNKNOWN, + SWITCH_SPI, + SWITCH_UART +}; + +/* Move the SPI/UART switch - we can only use one at a time! */ +void seaboard_switch_spi_uart(enum spi_uart_switch new_pos); + +static inline void uart_enable(void) { seaboard_switch_spi_uart(SWITCH_UART); } +static inline void spi_enable(void) { seaboard_switch_spi_uart(SWITCH_SPI); } + +#endif + #endif /* __CONFIG_H */ diff --git a/include/ns16550.h b/include/ns16550.h index fa3e62eb1fd..7c253def3a1 100644 --- a/include/ns16550.h +++ b/include/ns16550.h @@ -163,3 +163,5 @@ void NS16550_putc (NS16550_t com_port, char c); char NS16550_getc (NS16550_t regs, unsigned int port); int NS16550_tstc (NS16550_t regs, unsigned int port); void NS16550_reinit (NS16550_t com_port, int baud_divisor); +void NS16550_clear(NS16550_t regs, unsigned port); +void NS16550_drain(NS16550_t regs, unsigned port); -- cgit v1.2.3