diff options
author | Tom Rini <trini@konsulko.com> | 2016-09-27 11:40:56 -0400 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2016-09-27 11:40:56 -0400 |
commit | 6d5565608f385b89f528ecf5680410cdc6cf63e9 (patch) | |
tree | 9403a25d40b8546e1e8a77fbe70da67c5ed4ade0 /drivers/spi/mvebu_a3700_spi.c | |
parent | e120c848bac77cfcc88183541c2e966e625a7840 (diff) | |
parent | b28d29f784f5cc33c92e291d35eda603ea4e58e3 (diff) | |
download | u-boot-imx-6d5565608f385b89f528ecf5680410cdc6cf63e9.zip u-boot-imx-6d5565608f385b89f528ecf5680410cdc6cf63e9.tar.gz u-boot-imx-6d5565608f385b89f528ecf5680410cdc6cf63e9.tar.bz2 |
Merge git://www.denx.de/git/u-boot-marvell
Diffstat (limited to 'drivers/spi/mvebu_a3700_spi.c')
-rw-r--r-- | drivers/spi/mvebu_a3700_spi.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/drivers/spi/mvebu_a3700_spi.c b/drivers/spi/mvebu_a3700_spi.c new file mode 100644 index 0000000..7c58c36 --- /dev/null +++ b/drivers/spi/mvebu_a3700_spi.c @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2015 Marvell International Ltd. + * + * Copyright (C) 2016 Stefan Roese <sr@denx.de> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <malloc.h> +#include <spi.h> +#include <wait_bit.h> +#include <asm/io.h> + +DECLARE_GLOBAL_DATA_PTR; + +#define MVEBU_SPI_A3700_XFER_RDY BIT(1) +#define MVEBU_SPI_A3700_FIFO_FLUSH BIT(9) +#define MVEBU_SPI_A3700_BYTE_LEN BIT(5) +#define MVEBU_SPI_A3700_CLK_PHA BIT(6) +#define MVEBU_SPI_A3700_CLK_POL BIT(7) +#define MVEBU_SPI_A3700_FIFO_EN BIT(17) +#define MVEBU_SPI_A3700_SPI_EN_0 BIT(16) +#define MVEBU_SPI_A3700_CLK_PRESCALE_BIT 0 +#define MVEBU_SPI_A3700_CLK_PRESCALE_MASK \ + (0x1f << MVEBU_SPI_A3700_CLK_PRESCALE_BIT) + +/* SPI registers */ +struct spi_reg { + u32 ctrl; /* 0x10600 */ + u32 cfg; /* 0x10604 */ + u32 dout; /* 0x10608 */ + u32 din; /* 0x1060c */ +}; + +struct mvebu_spi_platdata { + struct spi_reg *spireg; + unsigned int frequency; + unsigned int clock; +}; + +static void spi_cs_activate(struct spi_reg *reg, int cs) +{ + setbits_le32(®->ctrl, MVEBU_SPI_A3700_SPI_EN_0 << cs); +} + +static void spi_cs_deactivate(struct spi_reg *reg, int cs) +{ + clrbits_le32(®->ctrl, MVEBU_SPI_A3700_SPI_EN_0 << cs); +} + +/** + * spi_legacy_shift_byte() - triggers the real SPI transfer + * @bytelen: Indicate how many bytes to transfer. + * @dout: Buffer address of what to send. + * @din: Buffer address of where to receive. + * + * This function triggers the real SPI transfer in legacy mode. It + * will shift out char buffer from @dout, and shift in char buffer to + * @din, if necessary. + * + * This function assumes that only one byte is shifted at one time. + * However, it is not its responisbility to set the transfer type to + * one-byte. Also, it does not guarantee that it will work if transfer + * type becomes two-byte. See spi_set_legacy() for details. + * + * In legacy mode, simply write to the SPI_DOUT register will trigger + * the transfer. + * + * If @dout == NULL, which means no actual data needs to be sent out, + * then the function will shift out 0x00 in order to shift in data. + * The XFER_RDY flag is checked every time before accessing SPI_DOUT + * and SPI_DIN register. + * + * The number of transfers to be triggerred is decided by @bytelen. + * + * Return: 0 - cool + * -ETIMEDOUT - XFER_RDY flag timeout + */ +static int spi_legacy_shift_byte(struct spi_reg *reg, unsigned int bytelen, + const void *dout, void *din) +{ + const u8 *dout_8; + u8 *din_8; + int ret; + + /* Use 0x00 as dummy dout */ + const u8 dummy_dout = 0x0; + u32 pending_dout = 0x0; + + /* dout_8: pointer of current dout */ + dout_8 = dout; + /* din_8: pointer of current din */ + din_8 = din; + + while (bytelen) { + ret = wait_for_bit(__func__, ®->ctrl, + MVEBU_SPI_A3700_XFER_RDY, true, 100, false); + if (ret) + return ret; + + if (dout) + pending_dout = (u32)*dout_8; + else + pending_dout = (u32)dummy_dout; + + /* Trigger the xfer */ + writel(pending_dout, ®->dout); + + if (din) { + ret = wait_for_bit(__func__, ®->ctrl, + MVEBU_SPI_A3700_XFER_RDY, + true, 100, false); + if (ret) + return ret; + + /* Read what is transferred in */ + *din_8 = (u8)readl(®->din); + } + + /* Don't increment the current pointer if NULL */ + if (dout) + dout_8++; + if (din) + din_8++; + + bytelen--; + } + + return 0; +} + +static int mvebu_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev->parent; + struct mvebu_spi_platdata *plat = dev_get_platdata(bus); + struct spi_reg *reg = plat->spireg; + unsigned int bytelen; + int ret; + + bytelen = bitlen / 8; + + if (dout && din) + debug("This is a duplex transfer.\n"); + + /* Activate CS */ + if (flags & SPI_XFER_BEGIN) { + debug("SPI: activate cs.\n"); + spi_cs_activate(reg, spi_chip_select(dev)); + } + + /* Send and/or receive */ + if (dout || din) { + ret = spi_legacy_shift_byte(reg, bytelen, dout, din); + if (ret) + return ret; + } + + /* Deactivate CS */ + if (flags & SPI_XFER_END) { + ret = wait_for_bit(__func__, ®->ctrl, + MVEBU_SPI_A3700_XFER_RDY, true, 100, false); + if (ret) + return ret; + + debug("SPI: deactivate cs.\n"); + spi_cs_deactivate(reg, spi_chip_select(dev)); + } + + return 0; +} + +static int mvebu_spi_set_speed(struct udevice *bus, uint hz) +{ + struct mvebu_spi_platdata *plat = dev_get_platdata(bus); + struct spi_reg *reg = plat->spireg; + u32 data; + + data = readl(®->cfg); + + /* Set Prescaler */ + data &= ~MVEBU_SPI_A3700_CLK_PRESCALE_MASK; + + /* Calculate Prescaler = (spi_input_freq / spi_max_freq) */ + if (hz > plat->frequency) + hz = plat->frequency; + data |= plat->clock / hz; + + writel(data, ®->cfg); + + return 0; +} + +static int mvebu_spi_set_mode(struct udevice *bus, uint mode) +{ + struct mvebu_spi_platdata *plat = dev_get_platdata(bus); + struct spi_reg *reg = plat->spireg; + + /* + * Set SPI polarity + * 0: Serial interface clock is low when inactive + * 1: Serial interface clock is high when inactive + */ + if (mode & SPI_CPOL) + setbits_le32(®->cfg, MVEBU_SPI_A3700_CLK_POL); + else + clrbits_le32(®->cfg, MVEBU_SPI_A3700_CLK_POL); + if (mode & SPI_CPHA) + setbits_le32(®->cfg, MVEBU_SPI_A3700_CLK_PHA); + else + clrbits_le32(®->cfg, MVEBU_SPI_A3700_CLK_PHA); + + return 0; +} + +static int mvebu_spi_probe(struct udevice *bus) +{ + struct mvebu_spi_platdata *plat = dev_get_platdata(bus); + struct spi_reg *reg = plat->spireg; + u32 data; + int ret; + + /* + * Settings SPI controller to be working in legacy mode, which + * means use only DO pin (I/O 1) for Data Out, and DI pin (I/O 0) + * for Data In. + */ + + /* Flush read/write FIFO */ + data = readl(®->cfg); + writel(data | MVEBU_SPI_A3700_FIFO_FLUSH, ®->cfg); + ret = wait_for_bit(__func__, ®->cfg, MVEBU_SPI_A3700_FIFO_FLUSH, + false, 1000, false); + if (ret) + return ret; + + /* Disable FIFO mode */ + data &= ~MVEBU_SPI_A3700_FIFO_EN; + + /* Always shift 1 byte at a time */ + data &= ~MVEBU_SPI_A3700_BYTE_LEN; + + writel(data, ®->cfg); + + return 0; +} + +static int mvebu_spi_ofdata_to_platdata(struct udevice *bus) +{ + struct mvebu_spi_platdata *plat = dev_get_platdata(bus); + + plat->spireg = (struct spi_reg *)dev_get_addr(bus); + + /* + * FIXME + * Right now, mvebu does not have a clock infrastructure in U-Boot + * which should be used to query the input clock to the SPI + * controller. Once this clock driver is integrated into U-Boot + * it should be used to read the input clock and the DT property + * can be removed. + */ + plat->clock = fdtdec_get_int(gd->fdt_blob, bus->of_offset, + "clock-frequency", 160000); + plat->frequency = fdtdec_get_int(gd->fdt_blob, bus->of_offset, + "spi-max-frequency", 40000); + + return 0; +} + +static const struct dm_spi_ops mvebu_spi_ops = { + .xfer = mvebu_spi_xfer, + .set_speed = mvebu_spi_set_speed, + .set_mode = mvebu_spi_set_mode, + /* + * cs_info is not needed, since we require all chip selects to be + * in the device tree explicitly + */ +}; + +static const struct udevice_id mvebu_spi_ids[] = { + { .compatible = "marvell,armada-3700-spi" }, + { } +}; + +U_BOOT_DRIVER(mvebu_spi) = { + .name = "mvebu_spi", + .id = UCLASS_SPI, + .of_match = mvebu_spi_ids, + .ops = &mvebu_spi_ops, + .ofdata_to_platdata = mvebu_spi_ofdata_to_platdata, + .platdata_auto_alloc_size = sizeof(struct mvebu_spi_platdata), + .probe = mvebu_spi_probe, +}; |