diff options
author | Gabe Black <gabeblack@chromium.org> | 2011-08-09 17:41:57 -0700 |
---|---|---|
committer | Simon Glass <sjg@chromium.org> | 2011-08-29 10:59:25 -0700 |
commit | ec3dd714cc12d8471b68d1cd2d7930a9b67f4c6b (patch) | |
tree | e49d3af48077e24398279e66aa33041213499229 /drivers/spi | |
parent | ff439240ebce0423e107eea71ab31fdaaece25cd (diff) |
Add a new Tegra SPI driver which uses the new spi_xfer interface
This interface is different enough that it warrants a new driver, not a
retrofit of the old one. Fortunately, flashrom had already taken the Tegra SPI
driver from u-boot and modified it to use essentially this new interface. This
change ports it back again and makes the interface selection setting pick which
driver to use.
BUG=chrome-os-partner:4722
TEST=With this and related changes, built for x86-alex, tegra2_kaen,
tegra2_aebl, tegra2_seaboard, and tegra2_asymptote. Flashed the firmware on a
Kaen, verified the checksums matched, and booted to ChromeOS login. Built with
the option turned off on x86-alex and tegra2_kaen.
Change-Id: Ia2ce772a8f8f3194ff8a76e1abe0e07061fa5672
Signed-off-by: Gabe Black <gabeblack@google.com>
Reviewed-on: http://gerrit.chromium.org/gerrit/5618
Reviewed-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Stefan Reinauer <reinauer@google.com>
Diffstat (limited to 'drivers/spi')
-rw-r--r-- | drivers/spi/Makefile | 4 | ||||
-rw-r--r-- | drivers/spi/tegra2_spi_new.c | 275 |
2 files changed, 279 insertions, 0 deletions
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 19f5bb39c7d..0797b78ca41 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -40,7 +40,11 @@ COBJS-$(CONFIG_OMAP3_SPI) += omap3_spi.o COBJS-$(CONFIG_SOFT_SPI) += soft_spi.o COBJS-$(CONFIG_SH_SPI) += sh_spi.o COBJS-$(CONFIG_FSL_ESPI) += fsl_espi.o +ifdef CONFIG_NEW_SPI_XFER +COBJS-$(CONFIG_TEGRA2_SPI) += tegra2_spi_new.o +else COBJS-$(CONFIG_TEGRA2_SPI) += tegra2_spi.o +endif COBJS-$(CONFIG_DUMMY_SPI) += dummy_spi.o COBJS := $(COBJS-y) diff --git a/drivers/spi/tegra2_spi_new.c b/drivers/spi/tegra2_spi_new.c new file mode 100644 index 00000000000..f4d8d1ca6f0 --- /dev/null +++ b/drivers/spi/tegra2_spi_new.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2010-2011 NVIDIA Corporation + * With help from the mpc8xxx SPI driver + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> + +#include <malloc.h> +#include <ns16550.h> /* for NS16550_drain and NS16550_clear */ +#include <spi.h> +#include <asm/clocks.h> +#include <asm/io.h> +#include <asm/arch/bitfield.h> +#include <asm/arch/clk_rst.h> +#include <asm/arch/clock.h> +#include <asm/arch/gpio.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/tegra2_spi.h> +#include "uart-spi-fix.h" + +int spi_cs_is_valid(unsigned int bus, unsigned int cs) +{ + /* Tegra2 SPI-Flash - only 1 device ('bus/cs') */ + if (bus > 0 && cs != 0) + return 0; + else + return 1; +} + + +struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, + unsigned int max_hz, unsigned int mode) +{ + struct spi_slave *slave; + + if (!spi_cs_is_valid(bus, cs)) + return NULL; + + slave = malloc(sizeof(struct spi_slave)); + if (!slave) + return NULL; + + slave->bus = bus; + slave->cs = cs; + + /* + * Currently, Tegra2 SFLASH uses mode 0 & a 24MHz clock. + * Use 'mode' and 'maz_hz' to change that here, if needed. + */ + + return slave; +} + +void spi_free_slave(struct spi_slave *slave) +{ + free(slave); +} + +void spi_init(void) +{ + struct spi_tegra *spi = (struct spi_tegra *)TEGRA2_SPI_BASE; + u32 reg; + + /* Change SPI clock to 24MHz, PLLP_OUT0 source */ + clock_start_periph_pll(PERIPH_ID_SPI1, CLOCK_ID_PERIPH, CLK_24M); + + /* Clear stale status here */ + reg = SPI_STAT_RDY | SPI_STAT_RXF_FLUSH | SPI_STAT_TXF_FLUSH | \ + SPI_STAT_RXF_UNR | SPI_STAT_TXF_OVF; + writel(reg, &spi->status); + debug("spi_init: STATUS = %08x\n", readl(&spi->status)); + + /* + * Use sw-controlled CS, so we can clock in data after ReadID, etc. + */ + + reg = readl(&spi->command); + writel(reg | SPI_CMD_CS_SOFT, &spi->command); + debug("spi_init: COMMAND = %08x\n", readl(&spi->command)); + + /* + * SPI pins on Tegra2 are muxed - change pinmux last due to UART + * issue. + */ + pinmux_set_func(PINGRP_GMD, PMUX_FUNC_SFLASH); + pinmux_tristate_disable(PINGRP_LSPI); + +#ifndef CONFIG_SPI_UART_SWITCH + /* + * NOTE: + * Only set PinMux bits 3:2 to SPI here on boards that don't have the + * SPI UART switch or subsequent UART data won't go out! See + * spi_uart_switch(). + */ + pinmux_set_func(PINGRP_GMC, PMUX_FUNC_SFLASH); +#endif +} + +int spi_claim_bus(struct spi_slave *slave) +{ + return 0; +} + +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 spi_tegra *spi = (struct spi_tegra *)TEGRA2_SPI_BASE; + u32 val; + + spi_enable(); + + /* 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); +} + +void spi_cs_deactivate(struct spi_slave *slave) +{ + struct spi_tegra *spi = (struct spi_tegra *)TEGRA2_SPI_BASE; + u32 val; + + /* 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); +} + +/* Helper function to calculate the clock cycle in this round. + * Also updates the byte count remaining to be used this round. + * + * For example, we want to write 6 bytes to SPI and then read 5 bytes back. + * + * +---+---+---+---+---+---+ + * | W | W | W | W | W | W | + * +---+---+---+---+---+---+---+---+---+---+---+ + * | R | R | R | R | R | + * +---+---+---+---+---+ + * |<-- round 0 -->| + * |<-- round 1 -->| + * |<-- round 2 -->| + * + * So that the continuous calling this function would get: + * + * round| RET| writecnt readcnt bytes to_write to_read + * -----+----+--------------------------------------------- + * INIT | | 6 5 + * 0 | 1 | 2 5 4 4 0 + * 1 | 1 | 0 3 4 2 2 + * 2 | 1 | 0 0 3 0 3 + * 3 | 0 | - - - - - + * + */ +static int next_4_bytes(uint32_t *writecnt, uint32_t *readcnt, + uint32_t *num_bytes, uint32_t *to_write, + uint32_t *to_read) +{ + *to_write = min(*writecnt, 4); + *to_read = min(*readcnt, 4 - *to_write); + + *writecnt -= *to_write; + *readcnt -= *to_read; + + *num_bytes = *to_write + *to_read; + + if (*num_bytes) + return 1; /* need to be called again. */ + else + return 0; /* handled write and read requests. */ +} + +int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int bitsout, + void *din, unsigned int bitsin) +{ + int retval = 0; + char *delayed_msg = NULL; + struct spi_tegra *spi = (struct spi_tegra *)TEGRA2_SPI_BASE; + uint32_t status; + uint32_t writecnt = (bitsout + 7) / 8; + uint32_t readcnt = (bitsin + 7) / 8; + uint32_t to_write, to_read; /* byte counts to fill FIFO. */ + uint32_t bytes; /* byte count to tell SPI controller. */ + uint8_t *writearr = (uint8_t *)dout; + uint8_t *readarr = (uint8_t *)din; + + /* We don't currently handle sending partial bytes. */ + assert(bitsin % 8 == 0); + assert(bitsout % 8 == 0); + + writel(readl(&spi->status), &spi->status); + writel(readl(&spi->command) | SPI_CMD_TXEN | SPI_CMD_RXEN, + &spi->command); + spi_cs_activate(slave); + + while (next_4_bytes(&writecnt, &readcnt, &bytes, &to_write, &to_read)) { + int i; + uint32_t tmp; + uint32_t tm; /* timeout counter */ + + /* prepare Tx FIFO */ + for (tmp = 0, i = 0; i < to_write; ++i) { + tmp |= *writearr++ << ((bytes - i - 1) * 8); + } + writel(tmp, &spi->tx_fifo); + + /* Kick the SCLK running: Shift out TX FIFO, and receive RX. */ + writel(readl(&spi->command) & ~SPI_CMD_BIT_LENGTH_MASK, + &spi->command); + writel(readl(&spi->command) | (bytes * 8 - 1), &spi->command); + writel(readl(&spi->command) | SPI_CMD_GO, &spi->command); + + /* Wait for controller completes the task. */ + for (tm = 0; tm < SPI_TIMEOUT; ++tm) { + if (((status = readl(&spi->status)) & + (SPI_STAT_BSY | SPI_STAT_RDY)) == SPI_STAT_RDY) + break; + udelay(10); + } + writel(readl(&spi->status) | SPI_STAT_RDY, &spi->status); + + /* Since the UART is disabled here, we delay printing the + * message until spi_cs_deactivate() is called. + */ + if (tm >= SPI_TIMEOUT) { + static char err[256]; + retval = -1; + sprintf(err, + "%s():%d BSY&RDY timeout, status = 0x%08x\n", + __func__, __LINE__, status); + delayed_msg = err; + break; + } + + /* read RX FIFO */ + tmp = readl(&spi->rx_fifo); + for (i = 0; i < to_read; ++i) { + *readarr++ = tmp >> ((to_read - 1 - i) * 8); + } + } + + /* Clear write-on-clear status bits */ + writel(readl(&spi->status), &spi->status); + + spi_cs_deactivate(slave); + if (delayed_msg) { + printf("%s\n", delayed_msg); + } + + return retval; +} |