diff --git a/arch/mips/boards/loongson-ls1b/Makefile b/arch/mips/boards/loongson-ls1b/Makefile index a21952b73..31c062987 100644 --- a/arch/mips/boards/loongson-ls1b/Makefile +++ b/arch/mips/boards/loongson-ls1b/Makefile @@ -1,2 +1 @@ -obj-y += ram.o obj-y += serial.o diff --git a/arch/mips/boards/loongson-ls1b/ram.c b/arch/mips/boards/loongson-ls1b/ram.c deleted file mode 100644 index 9e655aba5..000000000 --- a/arch/mips/boards/loongson-ls1b/ram.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include -#include -#include - -static int mem_init(void) -{ - mips_add_ram0(SZ_64M); - - return 0; -} -mem_initcall(mem_init); diff --git a/arch/mips/boards/loongson-ls1b/serial.c b/arch/mips/boards/loongson-ls1b/serial.c index 7159ab7a5..a9453ede2 100644 --- a/arch/mips/boards/loongson-ls1b/serial.c +++ b/arch/mips/boards/loongson-ls1b/serial.c @@ -1,22 +1,10 @@ #include #include -#include - -#include - -static struct NS16550_plat serial_plat = { - .clock = 83000000, - .shift = 0, -}; static int console_init(void) { - barebox_set_model("Loongson Tech LS1B Demo Board"); barebox_set_hostname("ls1b"); - add_ns16550_device(DEVICE_ID_DYNAMIC, KSEG1ADDR(LS1X_UART2_BASE), - 8, IORESOURCE_MEM | IORESOURCE_MEM_8BIT, &serial_plat); - return 0; } console_initcall(console_init); diff --git a/arch/mips/configs/loongson-ls1b_defconfig b/arch/mips/configs/loongson-ls1b_defconfig index 0322052a3..bc48abad7 100644 --- a/arch/mips/configs/loongson-ls1b_defconfig +++ b/arch/mips/configs/loongson-ls1b_defconfig @@ -1,3 +1,5 @@ +CONFIG_BUILTIN_DTB=y +CONFIG_BUILTIN_DTB_NAME="loongson-ls1b" CONFIG_MACH_MIPS_LOONGSON=y CONFIG_PBL_IMAGE=y CONFIG_STACK_SIZE=0x7000 @@ -36,7 +38,11 @@ CONFIG_CMD_IOMEM=y CONFIG_FLEXIBLE_BOOTARGS=y CONFIG_CMD_RESET=y CONFIG_CMD_GO=y +CONFIG_CMD_OFTREE=y +CONFIG_CMD_OF_PROPERTY=y +CONFIG_CMD_OF_NODE=y CONFIG_CMD_TIMEOUT=y +CONFIG_OFDEVICE=y # CONFIG_SPI is not set CONFIG_ZLIB=y CONFIG_BZLIB=y diff --git a/arch/mips/configs/tplink-mr3020_defconfig b/arch/mips/configs/tplink-mr3020_defconfig index 2e925d9e3..d249919be 100644 --- a/arch/mips/configs/tplink-mr3020_defconfig +++ b/arch/mips/configs/tplink-mr3020_defconfig @@ -14,16 +14,21 @@ CONFIG_CMD_MEMINFO=y CONFIG_CMD_IOMEM=y CONFIG_CMD_MM=y CONFIG_CMD_SHA1SUM=y +CONFIG_CMD_FLASH=y # CONFIG_CMD_BOOTM is not set CONFIG_CMD_RESET=y CONFIG_CMD_GO=y CONFIG_CMD_OFTREE=y CONFIG_CMD_OF_PROPERTY=y CONFIG_CMD_OF_NODE=y +CONFIG_CMD_SPI=y CONFIG_CMD_CLK=y CONFIG_OFDEVICE=y CONFIG_DRIVER_SERIAL_AR933X=y -# CONFIG_SPI is not set +CONFIG_DRIVER_SPI_ATH79=y +CONFIG_MTD=y +# CONFIG_MTD_OOB_DEVICE is not set +CONFIG_MTD_M25P80=y CONFIG_MD5=y CONFIG_SHA224=y CONFIG_SHA256=y diff --git a/arch/mips/dts/ar9331.dtsi b/arch/mips/dts/ar9331.dtsi index 890fda8ab..9485fbff5 100644 --- a/arch/mips/dts/ar9331.dtsi +++ b/arch/mips/dts/ar9331.dtsi @@ -22,5 +22,11 @@ reg = <0xb8050000 0x48>; #clock-cells = <1>; }; + + spi: spi@bf000000{ + compatible = "qca,ath79-spi"; + reg = <0xbf000000 0x01000000>; + status = "disabled"; + }; }; }; diff --git a/arch/mips/dts/loongson-ls1b.dts b/arch/mips/dts/loongson-ls1b.dts new file mode 100644 index 000000000..b81a951dc --- /dev/null +++ b/arch/mips/dts/loongson-ls1b.dts @@ -0,0 +1,16 @@ +/dts-v1/; + +#include "ls1b.dtsi" + +/ { + model = "Loongson Tech LS1B Demo Board"; + compatible = "loongson,ls1b"; + + memory { + reg = <0x00000000 0x4000000>; + }; +}; + +&serial2 { + status = "okay"; +}; diff --git a/arch/mips/dts/ls1b.dtsi b/arch/mips/dts/ls1b.dtsi new file mode 100644 index 000000000..f4ff8b6ed --- /dev/null +++ b/arch/mips/dts/ls1b.dtsi @@ -0,0 +1,43 @@ +#include "skeleton.dtsi" + +/ { + soc { + #address-cells = <1>; + #size-cells = <1>; + compatible = "simple-bus"; + device_type = "soc"; + ranges; + + serial0: serial@bfe40000 { + compatible = "ns16550a"; + reg = <0xbfe40000 0x8>; + reg-shift = <0>; + clock-frequency = <83000000>; + status = "disabled"; + }; + + serial1: serial@bfe44000 { + compatible = "ns16550a"; + reg = <0xbfe44000 0x8>; + reg-shift = <0>; + clock-frequency = <83000000>; + status = "disabled"; + }; + + serial2: serial@bfe48000 { + compatible = "ns16550a"; + reg = <0xbfe48000 0x8>; + reg-shift = <0>; + clock-frequency = <83000000>; + status = "disabled"; + }; + + serial3: serial@bfe4c000 { + compatible = "ns16550a"; + reg = <0xbfe4c000 0x8>; + reg-shift = <0>; + clock-frequency = <83000000>; + status = "disabled"; + }; + }; +}; diff --git a/arch/mips/dts/tplink-mr3020.dts b/arch/mips/dts/tplink-mr3020.dts index 9845bced7..41be352e5 100644 --- a/arch/mips/dts/tplink-mr3020.dts +++ b/arch/mips/dts/tplink-mr3020.dts @@ -14,3 +14,23 @@ &serial0 { status = "okay"; }; + +&spi { + num-chipselects = <1>; + status = "okay"; + + /* Spansion S25FL032PIF SPI flash */ + spiflash: m25p80@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "m25p80"; + spi-max-frequency = <104000000>; + reg = <0>; + }; +}; + +/ { + aliases { + spiflash = &spiflash; + }; +}; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index e429ea1a8..8a9bbd791 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 1036f8fbc..7469479c3 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 000000000..d9ab269da --- /dev/null +++ b/drivers/spi/ath79_spi.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2013, 2014 Antony Pavlov + * + * 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 +#include +#include +#include +#include +#include + +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 000000000..4c74d4e0c --- /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; +}