uboot-sunxi: add spi flash support
[openwrt/staging/hauke.git] / package / boot / uboot-sunxi / patches / 404-sunxi-add-SPI-driver-for-Allwinner-devices-sunxi.patch
diff --git a/package/boot/uboot-sunxi/patches/404-sunxi-add-SPI-driver-for-Allwinner-devices-sunxi.patch b/package/boot/uboot-sunxi/patches/404-sunxi-add-SPI-driver-for-Allwinner-devices-sunxi.patch
new file mode 100644 (file)
index 0000000..53cfd9f
--- /dev/null
@@ -0,0 +1,397 @@
+From 34e5d3bc4e50233cdd445ef371f06d33ce71e4b0 Mon Sep 17 00:00:00 2001
+From: "S.J.R. van Schaik" <stephan@synkhronix.com>
+Date: Fri, 10 Feb 2017 12:32:49 +0000
+Subject: sunxi: add SPI driver for Allwinner devices (sunxi)
+
+Implements a driver model SPI driver for Allwinner devices (sunxi).
+
+Signed-off-by: S.J.R. van Schaik <stephan@whiteboxsystems.nl>
+---
+ drivers/spi/Kconfig     |   5 +
+ drivers/spi/Makefile    |   1 +
+ drivers/spi/sunxi_spi.c | 355 ++++++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 361 insertions(+)
+ create mode 100644 drivers/spi/sunxi_spi.c
+
+--- a/drivers/spi/Kconfig
++++ b/drivers/spi/Kconfig
+@@ -148,6 +148,11 @@ config STM32_QSPI
+         used to access the SPI NOR flash chips on platforms embedding
+         this ST IP core.
++config SUNXI_SPI
++      bool "Allwinner SPI driver"
++      help
++        Enable the Allwinner SPI driver.
++
+ config TEGRA114_SPI
+       bool "nVidia Tegra114 SPI driver"
+       help
+--- a/drivers/spi/Makefile
++++ b/drivers/spi/Makefile
+@@ -44,6 +44,7 @@ obj-$(CONFIG_SANDBOX_SPI) += sandbox_spi
+ obj-$(CONFIG_SH_SPI) += sh_spi.o
+ obj-$(CONFIG_SH_QSPI) += sh_qspi.o
+ obj-$(CONFIG_STM32_QSPI) += stm32_qspi.o
++obj-$(CONFIG_SUNXI_SPI) += sunxi_spi.o
+ obj-$(CONFIG_TEGRA114_SPI) += tegra114_spi.o
+ obj-$(CONFIG_TEGRA20_SFLASH) += tegra20_sflash.o
+ obj-$(CONFIG_TEGRA20_SLINK) += tegra20_slink.o
+--- /dev/null
++++ b/drivers/spi/sunxi_spi.c
+@@ -0,0 +1,355 @@
++/*
++ * (C) Copyright 2017 Whitebox Systems / Northend Systems B.V.
++ * S.J.R. van Schaik <stephan@whiteboxsystems.nl>
++ * M.B.W. Wajer <merlijn@whiteboxsystems.nl>
++ *
++ * SPDX-License-Identifier:   GPL-2.0+
++ */
++
++#include <common.h>
++#include <dm.h>
++#include <errno.h>
++#include <spi.h>
++
++#include <asm/bitops.h>
++#include <asm/gpio.h>
++#include <asm/io.h>
++
++#include <asm/arch/clock.h>
++#include <asm/arch/spi.h>
++
++#define SUNXI_SPI_MAX_RATE (24 * 1000 * 1000)
++#define SUNXI_SPI_MIN_RATE (3 * 1000)
++
++struct sunxi_spi_platdata {
++      struct sunxi_spi_regs *regs;
++      unsigned int activate_delay_us;
++      unsigned int deactivate_delay_us;
++      uint32_t freq;
++};
++
++struct sunxi_spi_priv {
++      struct sunxi_spi_regs *regs;
++      unsigned int max_freq;
++      unsigned int last_transaction_us;
++};
++
++DECLARE_GLOBAL_DATA_PTR;
++
++static void sunxi_spi_setup_pinmux(unsigned int pin_func)
++{
++      unsigned int pin;
++
++      for (pin = SUNXI_GPC(0); pin <= SUNXI_GPC(2); pin++)
++              sunxi_gpio_set_cfgpin(pin, pin_func);
++
++      if (IS_ENABLED(CONFIG_MACH_SUN4I) || IS_ENABLED(CONFIG_MACH_SUN7I)) {
++              sunxi_gpio_set_cfgpin(SUNXI_GPC(23), pin_func);
++      } else {
++              sunxi_gpio_set_cfgpin(SUNXI_GPC(3), pin_func);
++      }
++}
++
++static void sunxi_spi_enable_clock(struct udevice *bus)
++{
++      struct sunxi_ccm_reg * const ccm =
++              (struct sunxi_ccm_reg * const)SUNXI_CCM_BASE;
++
++#if defined(CONFIG_MACH_SUN6I) || defined(CONFIG_MACH_SUN8I) || \
++      defined(CONFIG_MACH_SUN9I) || defined(CONFIG_MACH_SUN50I)
++      setbits_le32(&ccm->ahb_reset0_cfg,
++              (1 << AHB_GATE_OFFSET_SPI0));
++#endif
++
++      setbits_le32(&ccm->ahb_gate0, (1 << AHB_GATE_OFFSET_SPI0));
++      writel((1 << 31), &ccm->spi0_clk_cfg);
++}
++
++static void sunxi_spi_disable_clock(void)
++{
++      struct sunxi_ccm_reg * const ccm =
++              (struct sunxi_ccm_reg * const)SUNXI_CCM_BASE;
++
++      writel(0, &ccm->spi0_clk_cfg);
++      clrbits_le32(&ccm->ahb_gate0, (1 << AHB_GATE_OFFSET_SPI0));
++
++#if defined(CONFIG_MACH_SUN6I) || defined(CONFIG_MACH_SUN8I) || \
++      defined(CONFIG_MACH_SUN9I) || defined(CONFIG_MACH_SUN50I)
++      clrbits_le32(&ccm->ahb_reset0_cfg,
++              (1 << AHB_GATE_OFFSET_SPI0));
++#endif
++}
++
++static void sunxi_spi_cs_activate(struct udevice *dev, unsigned int cs)
++{
++      struct udevice *bus = dev->parent;
++      struct sunxi_spi_platdata *plat = dev_get_platdata(bus);
++      struct sunxi_spi_priv *priv = dev_get_priv(bus);
++      uint32_t reg;
++
++      /* If it is too soon to perform another transaction, wait. */
++      if (plat->deactivate_delay_us && priv->last_transaction_us) {
++              unsigned int delay_us;
++
++              delay_us = timer_get_us() - priv->last_transaction_us;
++
++              if (delay_us < plat->deactivate_delay_us)
++                      udelay(plat->deactivate_delay_us - delay_us);
++      }
++
++      debug("%s: activate cs: %u, bus: '%s'\n", __func__, cs, bus->name);
++
++      reg = readl(&priv->regs->xfer_ctl);
++      reg &= ~(SUNXI_SPI_CTL_CS_MASK | SUNXI_SPI_CTL_CS_LEVEL);
++      reg |= SUNXI_SPI_CTL_CS(cs);
++      writel(reg, &priv->regs->xfer_ctl);
++
++      if (plat->activate_delay_us)
++              udelay(plat->activate_delay_us);
++}
++
++static void sunxi_spi_cs_deactivate(struct udevice *dev, unsigned int cs)
++{
++      struct udevice *bus = dev->parent;
++      struct sunxi_spi_platdata *plat = dev_get_platdata(bus);
++      struct sunxi_spi_priv *priv = dev_get_priv(bus);
++      uint32_t reg;
++
++      debug("%s: deactivate cs: %u, bus: '%s'\n", __func__, cs, bus->name);
++      
++      reg = readl(&priv->regs->xfer_ctl);
++      reg &= ~SUNXI_SPI_CTL_CS_MASK;
++      reg |= SUNXI_SPI_CTL_CS_LEVEL;
++      writel(reg, &priv->regs->xfer_ctl);
++
++      /* 
++       * Remember the time of this transaction so that we can honour the bus
++       * delay.
++       */
++      if (plat->deactivate_delay_us)
++              priv->last_transaction_us = timer_get_us();
++}
++
++static int sunxi_spi_ofdata_to_platdata(struct udevice *bus)
++{
++      struct sunxi_spi_platdata *plat = dev_get_platdata(bus);
++      const void *blob = gd->fdt_blob;
++      int node = dev_of_offset(bus);
++
++      plat->regs = (struct sunxi_spi_regs *)devfdt_get_addr(bus);
++      plat->activate_delay_us = fdtdec_get_int(
++              blob, node, "spi-activate_delay", 0);
++      plat->deactivate_delay_us = fdtdec_get_int(
++              blob, node, "spi-deactivate-delay", 0);
++
++      debug("%s: regs=%p, activate-delay=%u, deactivate-delay=%u\n",
++              __func__, plat->regs, plat->activate_delay_us,
++              plat->deactivate_delay_us);
++
++      return 0;
++}
++
++static int sunxi_spi_probe(struct udevice *bus)
++{
++      struct sunxi_spi_platdata *plat = dev_get_platdata(bus);
++      struct sunxi_spi_priv *priv = dev_get_priv(bus);
++
++      debug("%s: probe\n", __func__);
++
++      priv->regs = plat->regs;
++      priv->last_transaction_us = timer_get_us();
++
++      return 0;
++}
++
++static int sunxi_spi_claim_bus(struct udevice *dev)
++{
++      struct udevice *bus = dev->parent;
++      struct sunxi_spi_priv *priv = dev_get_priv(bus);
++      unsigned int pin_func = SUNXI_GPC_SPI0;
++
++      debug("%s: claiming bus\n", __func__);
++
++      if (IS_ENABLED(CONFIG_MACH_SUN50I))
++              pin_func = SUN50I_GPC_SPI0;
++
++      sunxi_spi_setup_pinmux(pin_func);
++      sunxi_spi_enable_clock(bus);
++      setbits_le32(&priv->regs->glb_ctl, SUNXI_SPI_CTL_MASTER |
++              SUNXI_SPI_CTL_ENABLE | SUNXI_SPI_CTL_TP | SUNXI_SPI_CTL_SRST);
++
++      if (IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I))
++              while (readl(&priv->regs->glb_ctl) & SUNXI_SPI_CTL_SRST)
++                      ;
++
++      setbits_le32(&priv->regs->xfer_ctl, SUNXI_SPI_CTL_CS_MANUAL |
++              SUNXI_SPI_CTL_CS_LEVEL);
++      setbits_le32(&priv->regs->fifo_ctl, SUNXI_SPI_CTL_RF_RST |
++              SUNXI_SPI_CTL_TF_RST);
++
++      return 0;
++}
++
++static int sunxi_spi_release_bus(struct udevice *dev)
++{
++      struct udevice *bus = dev->parent;
++      struct sunxi_spi_priv *priv = dev_get_priv(bus);
++
++      debug("%s: releasing bus\n", __func__);
++
++      clrbits_le32(&priv->regs->glb_ctl, SUNXI_SPI_CTL_MASTER |
++              SUNXI_SPI_CTL_ENABLE);
++      sunxi_spi_disable_clock();
++
++      return 0;
++}
++
++static void sunxi_spi_write(struct udevice *dev, const char *tx_buf,
++      size_t nbytes)
++{
++      struct udevice *bus = dev->parent;
++      struct sunxi_spi_priv *priv = dev_get_priv(bus);
++      size_t i;
++      char byte;
++
++      if (!tx_buf)
++              nbytes = 0;
++
++      writel(SUNXI_SPI_XMIT_CNT(nbytes), &priv->regs->xmit_cnt);
++
++      if (IS_ENABLED(CONFIG_SUNXI_GEN_SUN6I))
++              writel(SUNXI_SPI_BURST_CNT(nbytes), &priv->regs->burst_ctl);
++
++      for (i = 0; i < nbytes; ++i) {
++              byte = tx_buf ? *tx_buf++ : 0;
++              writeb(byte, &priv->regs->tx_data);
++      }
++}
++
++static int sunxi_spi_xfer(struct udevice *dev, unsigned int bitlen,
++      const void *dout, void *din, unsigned long flags)
++{
++      struct udevice *bus = dev->parent;
++      struct sunxi_spi_priv *priv = dev_get_priv(bus);
++      struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
++      const char *tx_buf = dout;
++      char *rx_buf = din;
++      size_t len = bitlen / 8;
++      size_t i, nbytes;
++      char byte;
++
++      if (bitlen % 8) {
++              debug("%s: non byte-aligned SPI transfer.\n", __func__);
++              return -1;
++      }
++
++      if (flags & SPI_XFER_BEGIN)
++              sunxi_spi_cs_activate(dev, slave_plat->cs);
++
++      while (len) {
++              nbytes = min(len, (size_t)64 - 1);
++
++              writel(SUNXI_SPI_BURST_CNT(nbytes), &priv->regs->burst_cnt);
++              sunxi_spi_write(dev, tx_buf, nbytes);
++              setbits_le32(&priv->regs->xfer_ctl, SUNXI_SPI_CTL_XCH);
++
++              while (((readl(&priv->regs->fifo_sta) &
++                      SUNXI_SPI_FIFO_RF_CNT_MASK) >>
++                      SUNXI_SPI_FIFO_RF_CNT_BITS) < nbytes)
++                      ;
++
++              for (i = 0; i < nbytes; ++i) {
++                      byte = readb(&priv->regs->rx_data);
++
++                      if (rx_buf)
++                              *rx_buf++ = byte;
++              }
++
++              len -= nbytes;
++      }
++
++      if (flags & SPI_XFER_END)
++              sunxi_spi_cs_deactivate(dev, slave_plat->cs);
++
++      return 0;
++}
++
++static int sunxi_spi_set_speed(struct udevice *bus, uint speed)
++{
++      struct sunxi_spi_priv *priv = dev_get_priv(bus);
++      unsigned int div;
++      uint32_t reg;
++
++      speed = min(speed, (unsigned int)SUNXI_SPI_MAX_RATE);
++      speed = max((unsigned int)SUNXI_SPI_MIN_RATE, speed);
++
++      div = SUNXI_SPI_MAX_RATE / (2 * speed);
++
++      if (div <= (SUNXI_SPI_CLK_CTL_CDR2_MASK + 1)) {
++              if (div > 0)
++                      div--;
++
++              reg = SUNXI_SPI_CLK_CTL_CDR2(div) | SUNXI_SPI_CLK_CTL_DRS;
++      } else {
++              div = __ilog2(SUNXI_SPI_MAX_RATE) - __ilog2(speed);
++              reg = SUNXI_SPI_CLK_CTL_CDR1(div);
++      }
++
++      writel(reg, &priv->regs->clk_ctl);
++
++      debug("%s: speed=%u\n", __func__, speed);
++
++      return 0;
++}
++
++static int sunxi_spi_set_mode(struct udevice *bus, uint mode)
++{
++      struct sunxi_spi_priv *priv = dev_get_priv(bus);
++      uint32_t reg;
++
++      reg = readl(&priv->regs->xfer_ctl);
++      reg &= ~(SUNXI_SPI_CTL_CPOL | SUNXI_SPI_CTL_CPHA |
++              SUNXI_SPI_CTL_CS_ACTIVE_LOW);
++
++      if (mode & SPI_CPOL)
++              reg |= SUNXI_SPI_CTL_CPOL;
++
++      if (mode & SPI_CPHA)
++              reg |= SUNXI_SPI_CTL_CPHA;
++
++      if (!(mode & SPI_CS_HIGH))
++              reg |= SUNXI_SPI_CTL_CS_ACTIVE_LOW;
++
++      writel(reg, &priv->regs->xfer_ctl);
++
++      debug("%s: mode=%d\n", __func__, mode);
++
++      return 0;
++}
++
++static const struct dm_spi_ops sunxi_spi_ops = {
++      .claim_bus      = sunxi_spi_claim_bus,
++      .release_bus    = sunxi_spi_release_bus,
++      .xfer           = sunxi_spi_xfer,
++      .set_speed      = sunxi_spi_set_speed,
++      .set_mode       = sunxi_spi_set_mode,
++};
++
++static const struct udevice_id sunxi_spi_ids[] = {
++      { .compatible = "allwinner,sun4i-a10-spi" },
++      { .compatible = "allwinner,sun6i-a31-spi" },
++      { .compatible = "allwinner,sun8i-h3-spi" },
++      { .compatible = "allwinner,sun50i-a64-spi" },
++      { }
++};
++
++U_BOOT_DRIVER(sunxi_spi) = {
++      .name   = "sunxi_spi",
++      .id     = UCLASS_SPI,
++      .of_match = sunxi_spi_ids,
++      .ops    = &sunxi_spi_ops,
++      .ofdata_to_platdata = sunxi_spi_ofdata_to_platdata,
++      .platdata_auto_alloc_size = sizeof(struct sunxi_spi_platdata),
++      .priv_auto_alloc_size = sizeof(struct sunxi_spi_priv),
++      .probe  = sunxi_spi_probe,
++};