diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2014-05-05 11:06:17 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2014-05-05 11:06:17 +0200 |
commit | 8fb1d4e0758fbeb91dd82541f7c769e2d1e6900a (patch) | |
tree | f8d8f474a0882dc0d65dafae17541e1c53fb3507 /drivers | |
parent | 4626972921355dd8ed97f4a0d71a00950f2364e0 (diff) | |
parent | b7cc37eedf1663edd62179715ad1deb44421444a (diff) | |
download | barebox-8fb1d4e0758fbeb91dd82541f7c769e2d1e6900a.tar.gz barebox-8fb1d4e0758fbeb91dd82541f7c769e2d1e6900a.tar.xz |
Merge branch 'for-next/mips'
Conflicts:
arch/mips/boards/loongson-ls1b/serial.c
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/spi/Kconfig | 4 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/ath79_spi.c | 299 | ||||
-rw-r--r-- | drivers/spi/spi-bitbang-txrx.h | 95 |
4 files changed, 399 insertions, 0 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index e429ea1a85..8a9bbd7917 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -10,6 +10,10 @@ config DRIVER_SPI_ALTERA bool "Altera SPI Master driver" depends on NIOS2 +config DRIVER_SPI_ATH79 + bool "Atheros AR71XX/AR724X/AR913X/AR933X SPI controller driver" + depends on MACH_MIPS_ATH79 + config DRIVER_SPI_ATMEL bool "Atmel (AT91) SPI Master driver" depends on ARCH_AT91 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 1036f8fbc7..7469479c31 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_SPI) += spi.o +obj-$(CONFIG_DRIVER_SPI_ATH79) += ath79_spi.o obj-$(CONFIG_DRIVER_SPI_IMX) += imx_spi.o obj-$(CONFIG_DRIVER_SPI_MVEBU) += mvebu_spi.o obj-$(CONFIG_DRIVER_SPI_MXS) += mxs_spi.o diff --git a/drivers/spi/ath79_spi.c b/drivers/spi/ath79_spi.c new file mode 100644 index 0000000000..d9ab269da7 --- /dev/null +++ b/drivers/spi/ath79_spi.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2013, 2014 Antony Pavlov <antonynpavlov@gmail.com> + * + * This file is part of barebox. + * 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. + * + */ + +#include <common.h> +#include <init.h> +#include <driver.h> +#include <spi/spi.h> +#include <io.h> +#include <clock.h> + +struct ath79_spi { + struct spi_master master; + void __iomem *regs; + u32 val; + u32 reg_ctrl; +}; + +#define AR71XX_SPI_REG_FS 0x00 /* Function Select */ +#define AR71XX_SPI_REG_CTRL 0x04 /* SPI Control */ +#define AR71XX_SPI_REG_IOC 0x08 /* SPI I/O Control */ +#define AR71XX_SPI_REG_RDS 0x0c /* Read Data Shift */ + +#define AR71XX_SPI_FS_GPIO BIT(0) /* Enable GPIO mode */ + +#define AR71XX_SPI_IOC_DO BIT(0) /* Data Out pin */ +#define AR71XX_SPI_IOC_CLK BIT(8) /* CLK pin */ +#define AR71XX_SPI_IOC_CS(n) BIT(16 + (n)) +#define AR71XX_SPI_IOC_CS0 AR71XX_SPI_IOC_CS(0) +#define AR71XX_SPI_IOC_CS1 AR71XX_SPI_IOC_CS(1) +#define AR71XX_SPI_IOC_CS2 AR71XX_SPI_IOC_CS(2) +#define AR71XX_SPI_IOC_CS_ALL (AR71XX_SPI_IOC_CS0 | AR71XX_SPI_IOC_CS1 | \ + AR71XX_SPI_IOC_CS2) + +static inline u32 ath79_spi_rr(struct ath79_spi *sp, int reg) +{ + return cpu_readl(sp->regs + reg); +} + +static inline void ath79_spi_wr(struct ath79_spi *sp, u32 val, int reg) +{ + cpu_writel(val, sp->regs + reg); +} + +static inline void setbits(struct ath79_spi *sp, int bits, int on) +{ + /* + * We are the only user of SCSPTR so no locking is required. + * Reading bit 2 and 0 in SCSPTR gives pin state as input. + * Writing the same bits sets the output value. + * This makes regular read-modify-write difficult so we + * use sp->val to keep track of the latest register value. + */ + + if (on) + sp->val |= bits; + else + sp->val &= ~bits; + + ath79_spi_wr(sp, sp->val, AR71XX_SPI_REG_IOC); +} + +static inline struct ath79_spi *ath79_spidev_to_sp(struct spi_device *spi) +{ + return container_of(spi->master, struct ath79_spi, master); +} + +static inline void setsck(struct spi_device *spi, int on) +{ + struct ath79_spi *sc = ath79_spidev_to_sp(spi); + + setbits(sc, AR71XX_SPI_IOC_CLK, on); +} + +static inline void setmosi(struct spi_device *spi, int on) +{ + struct ath79_spi *sc = ath79_spidev_to_sp(spi); + + setbits(sc, AR71XX_SPI_IOC_DO, on); +} + +static inline u32 getmiso(struct spi_device *spi) +{ + struct ath79_spi *sc = ath79_spidev_to_sp(spi); + + return !!((ath79_spi_rr(sc, AR71XX_SPI_REG_RDS) & 1)); +} + +#include "spi-bitbang-txrx.h" + +static inline void ath79_spi_chipselect(struct ath79_spi *sp, int chipselect) +{ + int off_bits; + + off_bits = 0xffffffff; + + switch (chipselect) { + case 0: + off_bits &= ~AR71XX_SPI_IOC_CS0; + break; + + case 1: + off_bits &= ~AR71XX_SPI_IOC_CS1; + break; + + case 2: + off_bits &= ~AR71XX_SPI_IOC_CS2; + break; + + case 3: + break; + } + + /* by default inactivate chip selects */ + sp->val |= AR71XX_SPI_IOC_CS_ALL; + sp->val &= off_bits; + + ath79_spi_wr(sp, sp->val, AR71XX_SPI_REG_IOC); +} + +static int ath79_spi_setup(struct spi_device *spi) +{ + struct spi_master *master = spi->master; + struct device_d spi_dev = spi->dev; + + if (spi->bits_per_word != 8) { + dev_err(master->dev, "master doesn't support %d bits per word requested by %s\n", + spi->bits_per_word, spi_dev.name); + return -EINVAL; + } + + if ((spi->mode & (SPI_CPHA | SPI_CPOL)) != SPI_MODE_0) { + dev_err(master->dev, "master doesn't support SPI_MODE%d requested by %s\n", + spi->mode & (SPI_CPHA | SPI_CPOL), spi_dev.name); + return -EINVAL; + } + + return 0; +} + +static int ath79_spi_read(struct spi_device *spi, void *buf, size_t nbyte) +{ + ssize_t cnt = 0; + u8 *rxf_buf = buf; + + while (cnt < nbyte) { + *rxf_buf = bitbang_txrx_be_cpha1(spi, 1000, 1, 0, 8); + rxf_buf++; + cnt++; + } + + return cnt; +} + +static int ath79_spi_write(struct spi_device *spi, + const void *buf, size_t nbyte) +{ + ssize_t cnt = 0; + const u8 *txf_buf = buf; + + while (cnt < nbyte) { + bitbang_txrx_be_cpha1(spi, 1000, 1, (u32)*txf_buf, 8); + txf_buf++; + cnt++; + } + + return 0; +} + +static int ath79_spi_transfer(struct spi_device *spi, struct spi_message *mesg) +{ + struct ath79_spi *sc = ath79_spidev_to_sp(spi); + struct spi_transfer *t; + + mesg->actual_length = 0; + + /* activate chip select signal */ + ath79_spi_chipselect(sc, spi->chip_select); + + list_for_each_entry(t, &mesg->transfers, transfer_list) { + + if (t->tx_buf) + ath79_spi_write(spi, t->tx_buf, t->len); + + if (t->rx_buf) + ath79_spi_read(spi, t->rx_buf, t->len); + + mesg->actual_length += t->len; + } + + /* inactivate chip select signal */ + ath79_spi_chipselect(sc, -1); + + return 0; +} + +static void ath79_spi_enable(struct ath79_spi *sp) +{ + /* enable GPIO mode */ + ath79_spi_wr(sp, AR71XX_SPI_FS_GPIO, AR71XX_SPI_REG_FS); + + /* save CTRL register */ + sp->reg_ctrl = ath79_spi_rr(sp, AR71XX_SPI_REG_CTRL); + sp->val = ath79_spi_rr(sp, AR71XX_SPI_REG_IOC); + + /* TODO: setup speed? */ + ath79_spi_wr(sp, 0x43, AR71XX_SPI_REG_CTRL); +} + +static void ath79_spi_disable(struct ath79_spi *sp) +{ + /* restore CTRL register */ + ath79_spi_wr(sp, sp->reg_ctrl, AR71XX_SPI_REG_CTRL); + /* disable GPIO mode */ + ath79_spi_wr(sp, 0, AR71XX_SPI_REG_FS); +} + +static int ath79_spi_probe(struct device_d *dev) +{ + struct spi_master *master; + struct ath79_spi *ath79_spi; + + ath79_spi = xzalloc(sizeof(*ath79_spi)); + dev->priv = ath79_spi; + + master = &ath79_spi->master; + master->dev = dev; + + master->bus_num = dev->id; + master->setup = ath79_spi_setup; + master->transfer = ath79_spi_transfer; + master->num_chipselect = 3; + + if (IS_ENABLED(CONFIG_OFDEVICE)) { + struct device_node *node = dev->device_node; + u32 num_cs; + int ret; + + ret = of_property_read_u32(node, "num-chipselects", &num_cs); + if (ret) + num_cs = 3; + + if (num_cs > 3) { + dev_err(dev, "master doesn't support num-chipselects > 3\n"); + } + + master->num_chipselect = num_cs; + } + + ath79_spi->regs = dev_request_mem_region(dev, 0); + + /* enable gpio mode */ + ath79_spi_enable(ath79_spi); + + /* inactivate chip select signal */ + ath79_spi_chipselect(ath79_spi, -1); + + spi_register_master(master); + + return 0; +} + +static void ath79_spi_remove(struct device_d *dev) +{ + struct ath79_spi *sp = dev->priv; + + ath79_spi_disable(sp); +} + +static __maybe_unused struct of_device_id ath79_spi_dt_ids[] = { + { + .compatible = "qca,ath79-spi", + }, + { + /* sentinel */ + } +}; + +static struct driver_d ath79_spi_driver = { + .name = "ath79-spi", + .probe = ath79_spi_probe, + .remove = ath79_spi_remove, + .of_compatible = DRV_OF_COMPAT(ath79_spi_dt_ids), +}; +device_platform_driver(ath79_spi_driver); diff --git a/drivers/spi/spi-bitbang-txrx.h b/drivers/spi/spi-bitbang-txrx.h new file mode 100644 index 0000000000..4c74d4e0c5 --- /dev/null +++ b/drivers/spi/spi-bitbang-txrx.h @@ -0,0 +1,95 @@ +/* + * Mix this utility code with some glue code to get one of several types of + * simple SPI master driver. Two do polled word-at-a-time I/O: + * + * - GPIO/parport bitbangers. Provide chipselect() and txrx_word[](), + * expanding the per-word routines from the inline templates below. + * + * - Drivers for controllers resembling bare shift registers. Provide + * chipselect() and txrx_word[](), with custom setup()/cleanup() methods + * that use your controller's clock and chipselect registers. + * + * Some hardware works well with requests at spi_transfer scope: + * + * - Drivers leveraging smarter hardware, with fifos or DMA; or for half + * duplex (MicroWire) controllers. Provide chipselect() and txrx_bufs(), + * and custom setup()/cleanup() methods. + */ + +/* + * The code that knows what GPIO pins do what should have declared four + * functions, ideally as inlines, before including this header: + * + * void setsck(struct spi_device *, int is_on); + * void setmosi(struct spi_device *, int is_on); + * int getmiso(struct spi_device *); + * void spidelay(unsigned); + * + * setsck()'s is_on parameter is a zero/nonzero boolean. + * + * setmosi()'s is_on parameter is a zero/nonzero boolean. + * + * getmiso() is required to return 0 or 1 only. Any other value is invalid + * and will result in improper operation. + * + * A non-inlined routine would call bitbang_txrx_*() routines. The + * main loop could easily compile down to a handful of instructions, + * especially if the delay is a NOP (to run at peak speed). + * + * Since this is software, the timings may not be exactly what your board's + * chips need ... there may be several reasons you'd need to tweak timings + * in these routines, not just to make it faster or slower to match a + * particular CPU clock rate. + */ + +#define spidelay(nsecs) udelay(nsecs/1000) + +static inline u32 +bitbang_txrx_be_cpha0(struct spi_device *spi, + unsigned nsecs, unsigned cpol, + u32 word, u8 bits) +{ + /* if (cpol == 0) this is SPI_MODE_0; else this is SPI_MODE_2 */ + + /* clock starts at inactive polarity */ + for (word <<= (32 - bits); likely(bits); bits--) { + + /* setup MSB (to slave) on trailing edge */ + setmosi(spi, word & (1 << 31)); + spidelay(nsecs); /* T(setup) */ + + setsck(spi, !cpol); + spidelay(nsecs); + + /* sample MSB (from slave) on leading edge */ + word <<= 1; + word |= getmiso(spi); + setsck(spi, cpol); + } + return word; +} + +static inline u32 +bitbang_txrx_be_cpha1(struct spi_device *spi, + unsigned nsecs, unsigned cpol, + u32 word, u8 bits) +{ + /* if (cpol == 0) this is SPI_MODE_1; else this is SPI_MODE_3 */ + + /* clock starts at inactive polarity */ + for (word <<= (32 - bits); likely(bits); bits--) { + + /* setup MSB (to slave) on leading edge */ + setsck(spi, !cpol); + setmosi(spi, word & (1 << 31)); + spidelay(nsecs); /* T(setup) */ + + setsck(spi, cpol); + spidelay(nsecs); + + /* sample MSB (from slave) on trailing edge */ + word <<= 1; + word |= getmiso(spi); + } + return word; +} |