/* * (C) Copyright 2017 Whitebox Systems / Northend Systems B.V. * S.J.R. van Schaik * M.B.W. Wajer * * (C) Copyright 2017 Olimex Ltd.. * Stefan Mavrodiev * * Based on linux spi driver. Original copyright follows: * linux/drivers/spi/spi-sun4i.c * * Copyright (C) 2012 - 2014 Allwinner Tech * Pan Nan * * Copyright (C) 2014 Maxime Ripard * Maxime Ripard * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include #include #include #include #include #define SUN4I_RXDATA_REG 0x00 #define SUN4I_TXDATA_REG 0x04 #define SUN4I_CTL_REG 0x08 #define SUN4I_CTL_ENABLE BIT(0) #define SUN4I_CTL_MASTER BIT(1) #define SUN4I_CTL_CPHA BIT(2) #define SUN4I_CTL_CPOL BIT(3) #define SUN4I_CTL_CS_ACTIVE_LOW BIT(4) #define SUN4I_CTL_LMTF BIT(6) #define SUN4I_CTL_TF_RST BIT(8) #define SUN4I_CTL_RF_RST BIT(9) #define SUN4I_CTL_XCH BIT(10) #define SUN4I_CTL_CS_MASK 0x3000 #define SUN4I_CTL_CS(cs) (((cs) << 12) & SUN4I_CTL_CS_MASK) #define SUN4I_CTL_DHB BIT(15) #define SUN4I_CTL_CS_MANUAL BIT(16) #define SUN4I_CTL_CS_LEVEL BIT(17) #define SUN4I_CTL_TP BIT(18) #define SUN4I_INT_CTL_REG 0x0c #define SUN4I_INT_CTL_RF_F34 BIT(4) #define SUN4I_INT_CTL_TF_E34 BIT(12) #define SUN4I_INT_CTL_TC BIT(16) #define SUN4I_INT_STA_REG 0x10 #define SUN4I_DMA_CTL_REG 0x14 #define SUN4I_WAIT_REG 0x18 #define SUN4I_CLK_CTL_REG 0x1c #define SUN4I_CLK_CTL_CDR2_MASK 0xff #define SUN4I_CLK_CTL_CDR2(div) ((div) & SUN4I_CLK_CTL_CDR2_MASK) #define SUN4I_CLK_CTL_CDR1_MASK 0xf #define SUN4I_CLK_CTL_CDR1(div) (((div) & SUN4I_CLK_CTL_CDR1_MASK) << 8) #define SUN4I_CLK_CTL_DRS BIT(12) #define SUN4I_MAX_XFER_SIZE 0xffffff #define SUN4I_BURST_CNT_REG 0x20 #define SUN4I_BURST_CNT(cnt) ((cnt) & SUN4I_MAX_XFER_SIZE) #define SUN4I_XMIT_CNT_REG 0x24 #define SUN4I_XMIT_CNT(cnt) ((cnt) & SUN4I_MAX_XFER_SIZE) #define SUN4I_FIFO_STA_REG 0x28 #define SUN4I_FIFO_STA_RF_CNT_BITS 0 #define SUN4I_FIFO_STA_TF_CNT_MASK 0x7f #define SUN4I_FIFO_STA_TF_CNT_BITS 16 #define SUN4I_SPI_MAX_RATE 24000000 #define SUN4I_SPI_MIN_RATE 3000 #define SUN4I_SPI_DEFAULT_RATE 1000000 #define SUN4I_SPI_TIMEOUT_US 1000000 #define SPI_REG(priv, reg) ((priv)->base_addr + \ (priv)->variant->regs[reg]) #define SPI_BIT(priv, bit) ((priv)->variant->bits[bit]) #define SPI_CS(priv, cs) (((cs) << SPI_BIT(priv, SPI_TCR_CS_SEL)) & \ SPI_BIT(priv, SPI_TCR_CS_MASK)) /* sun spi register set */ enum sun4i_spi_regs { SPI_GCR, SPI_TCR, SPI_FCR, SPI_FSR, SPI_CCR, SPI_BC, SPI_TC, SPI_BCTL, SPI_TXD, SPI_RXD, }; /* sun spi register bits */ enum sun4i_spi_bits { SPI_GCR_TP, SPI_TCR_CPHA, SPI_TCR_CPOL, SPI_TCR_CS_ACTIVE_LOW, SPI_TCR_CS_SEL, SPI_TCR_CS_MASK, SPI_TCR_XCH, SPI_TCR_CS_MANUAL, SPI_TCR_CS_LEVEL, SPI_FCR_TF_RST, SPI_FCR_RF_RST, SPI_FSR_RF_CNT_MASK, }; struct sun4i_spi_variant { const unsigned long *regs; const u32 *bits; u32 fifo_depth; }; struct sun4i_spi_platdata { struct sun4i_spi_variant *variant; u32 base_addr; u32 max_hz; }; struct sun4i_spi_priv { struct sun4i_spi_variant *variant; u32 base_addr; u32 freq; u32 mode; const u8 *tx_buf; u8 *rx_buf; }; DECLARE_GLOBAL_DATA_PTR; static inline void sun4i_spi_drain_fifo(struct sun4i_spi_priv *priv, int len) { u8 byte; while (len--) { byte = readb(SPI_REG(priv, SPI_RXD)); if (priv->rx_buf) *priv->rx_buf++ = byte; } } static inline void sun4i_spi_fill_fifo(struct sun4i_spi_priv *priv, int len) { u8 byte; while (len--) { byte = priv->tx_buf ? *priv->tx_buf++ : 0; writeb(byte, SPI_REG(priv, SPI_TXD)); } } static void sun4i_spi_set_cs(struct udevice *bus, u8 cs, bool enable) { struct sun4i_spi_priv *priv = dev_get_priv(bus); u32 reg; reg = readl(SPI_REG(priv, SPI_TCR)); reg &= ~SPI_BIT(priv, SPI_TCR_CS_MASK); reg |= SPI_CS(priv, cs); if (enable) reg &= ~SPI_BIT(priv, SPI_TCR_CS_LEVEL); else reg |= SPI_BIT(priv, SPI_TCR_CS_LEVEL); writel(reg, SPI_REG(priv, SPI_TCR)); } static int sun4i_spi_parse_pins(struct udevice *dev) { const void *fdt = gd->fdt_blob; const char *pin_name; const fdt32_t *list; u32 phandle; int drive, pull = 0, pin, i; int offset; int size; list = fdt_getprop(fdt, dev_of_offset(dev), "pinctrl-0", &size); if (!list) { printf("WARNING: sun4i_spi: cannot find pinctrl-0 node\n"); return -EINVAL; } while (size) { phandle = fdt32_to_cpu(*list++); size -= sizeof(*list); offset = fdt_node_offset_by_phandle(fdt, phandle); if (offset < 0) return offset; drive = fdt_getprop_u32_default_node(fdt, offset, 0, "drive-strength", 0); if (drive) { if (drive <= 10) drive = 0; else if (drive <= 20) drive = 1; else if (drive <= 30) drive = 2; else drive = 3; } else { drive = fdt_getprop_u32_default_node(fdt, offset, 0, "allwinner,drive", 0); drive = min(drive, 3); } if (fdt_get_property(fdt, offset, "bias-disable", NULL)) pull = 0; else if (fdt_get_property(fdt, offset, "bias-pull-up", NULL)) pull = 1; else if (fdt_get_property(fdt, offset, "bias-pull-down", NULL)) pull = 2; else pull = fdt_getprop_u32_default_node(fdt, offset, 0, "allwinner,pull", 0); pull = min(pull, 2); for (i = 0; ; i++) { pin_name = fdt_stringlist_get(fdt, offset, "pins", i, NULL); if (!pin_name) { pin_name = fdt_stringlist_get(fdt, offset, "allwinner,pins", i, NULL); if (!pin_name) break; } pin = name_to_gpio(pin_name); if (pin < 0) break; sunxi_gpio_set_cfgpin(pin, SUNXI_GPC_SPI0); sunxi_gpio_set_drv(pin, drive); sunxi_gpio_set_pull(pin, pull); } } return 0; } static inline void sun4i_spi_enable_clock(void) { struct sunxi_ccm_reg *const ccm = (struct sunxi_ccm_reg *const)SUNXI_CCM_BASE; setbits_le32(&ccm->ahb_gate0, (1 << AHB_GATE_OFFSET_SPI0)); writel((1 << 31), &ccm->spi0_clk_cfg); } static int sun4i_spi_ofdata_to_platdata(struct udevice *bus) { struct sun4i_spi_platdata *plat = dev_get_platdata(bus); int node = dev_of_offset(bus); plat->base_addr = devfdt_get_addr(bus); plat->variant = (struct sun4i_spi_variant *)dev_get_driver_data(bus); plat->max_hz = fdtdec_get_int(gd->fdt_blob, node, "spi-max-frequency", SUN4I_SPI_DEFAULT_RATE); if (plat->max_hz > SUN4I_SPI_MAX_RATE) plat->max_hz = SUN4I_SPI_MAX_RATE; return 0; } static int sun4i_spi_probe(struct udevice *bus) { struct sun4i_spi_platdata *plat = dev_get_platdata(bus); struct sun4i_spi_priv *priv = dev_get_priv(bus); sun4i_spi_enable_clock(); sun4i_spi_parse_pins(bus); priv->variant = plat->variant; priv->base_addr = plat->base_addr; priv->freq = plat->max_hz; return 0; } static int sun4i_spi_claim_bus(struct udevice *dev) { struct sun4i_spi_priv *priv = dev_get_priv(dev->parent); setbits_le32(SPI_REG(priv, SPI_GCR), SUN4I_CTL_ENABLE | SUN4I_CTL_MASTER | SPI_BIT(priv, SPI_GCR_TP)); setbits_le32(SPI_REG(priv, SPI_TCR), SPI_BIT(priv, SPI_TCR_CS_MANUAL) | SPI_BIT(priv, SPI_TCR_CS_ACTIVE_LOW)); return 0; } static int sun4i_spi_release_bus(struct udevice *dev) { struct sun4i_spi_priv *priv = dev_get_priv(dev->parent); clrbits_le32(SPI_REG(priv, SPI_GCR), SUN4I_CTL_ENABLE); return 0; } static int sun4i_spi_xfer(struct udevice *dev, unsigned int bitlen, const void *dout, void *din, unsigned long flags) { struct udevice *bus = dev->parent; struct sun4i_spi_priv *priv = dev_get_priv(bus); struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev); u32 len = bitlen / 8; u32 rx_fifocnt; u8 nbytes; int ret; priv->tx_buf = dout; priv->rx_buf = din; if (bitlen % 8) { debug("%s: non byte-aligned SPI transfer.\n", __func__); return -ENAVAIL; } if (flags & SPI_XFER_BEGIN) sun4i_spi_set_cs(bus, slave_plat->cs, true); /* Reset FIFOs */ setbits_le32(SPI_REG(priv, SPI_FCR), SPI_BIT(priv, SPI_FCR_RF_RST) | SPI_BIT(priv, SPI_FCR_TF_RST)); while (len) { /* Setup the transfer now... */ nbytes = min(len, (priv->variant->fifo_depth - 1)); /* Setup the counters */ writel(SUN4I_BURST_CNT(nbytes), SPI_REG(priv, SPI_BC)); writel(SUN4I_XMIT_CNT(nbytes), SPI_REG(priv, SPI_TC)); /* Fill the TX FIFO */ sun4i_spi_fill_fifo(priv, nbytes); /* Start the transfer */ setbits_le32(SPI_REG(priv, SPI_TCR), SPI_BIT(priv, SPI_TCR_XCH)); /* Wait till RX FIFO to be empty */ ret = readl_poll_timeout(SPI_REG(priv, SPI_FSR), rx_fifocnt, (((rx_fifocnt & SPI_BIT(priv, SPI_FSR_RF_CNT_MASK)) >> SUN4I_FIFO_STA_RF_CNT_BITS) >= nbytes), SUN4I_SPI_TIMEOUT_US); if (ret < 0) { printf("ERROR: sun4i_spi: Timeout transferring data\n"); sun4i_spi_set_cs(bus, slave_plat->cs, false); return ret; } /* Drain the RX FIFO */ sun4i_spi_drain_fifo(priv, nbytes); len -= nbytes; } if (flags & SPI_XFER_END) sun4i_spi_set_cs(bus, slave_plat->cs, false); return 0; } static int sun4i_spi_set_speed(struct udevice *dev, uint speed) { struct sun4i_spi_platdata *plat = dev_get_platdata(dev); struct sun4i_spi_priv *priv = dev_get_priv(dev); unsigned int div; u32 reg; if (speed > plat->max_hz) speed = plat->max_hz; if (speed < SUN4I_SPI_MIN_RATE) speed = SUN4I_SPI_MIN_RATE; /* * Setup clock divider. * * We have two choices there. Either we can use the clock * divide rate 1, which is calculated thanks to this formula: * SPI_CLK = MOD_CLK / (2 ^ (cdr + 1)) * Or we can use CDR2, which is calculated with the formula: * SPI_CLK = MOD_CLK / (2 * (cdr + 1)) * Whether we use the former or the latter is set through the * DRS bit. * * First try CDR2, and if we can't reach the expected * frequency, fall back to CDR1. */ div = SUN4I_SPI_MAX_RATE / (2 * speed); reg = readl(SPI_REG(priv, SPI_CCR)); if (div <= (SUN4I_CLK_CTL_CDR2_MASK + 1)) { if (div > 0) div--; reg &= ~(SUN4I_CLK_CTL_CDR2_MASK | SUN4I_CLK_CTL_DRS); reg |= SUN4I_CLK_CTL_CDR2(div) | SUN4I_CLK_CTL_DRS; } else { div = __ilog2(SUN4I_SPI_MAX_RATE) - __ilog2(speed); reg &= ~((SUN4I_CLK_CTL_CDR1_MASK << 8) | SUN4I_CLK_CTL_DRS); reg |= SUN4I_CLK_CTL_CDR1(div); } priv->freq = speed; writel(reg, SPI_REG(priv, SPI_CCR)); return 0; } static int sun4i_spi_set_mode(struct udevice *dev, uint mode) { struct sun4i_spi_priv *priv = dev_get_priv(dev); u32 reg; reg = readl(SPI_REG(priv, SPI_TCR)); reg &= ~(SPI_BIT(priv, SPI_TCR_CPOL) | SPI_BIT(priv, SPI_TCR_CPHA)); if (mode & SPI_CPOL) reg |= SPI_BIT(priv, SPI_TCR_CPOL); if (mode & SPI_CPHA) reg |= SPI_BIT(priv, SPI_TCR_CPHA); priv->mode = mode; writel(reg, SPI_REG(priv, SPI_TCR)); return 0; } static const struct dm_spi_ops sun4i_spi_ops = { .claim_bus = sun4i_spi_claim_bus, .release_bus = sun4i_spi_release_bus, .xfer = sun4i_spi_xfer, .set_speed = sun4i_spi_set_speed, .set_mode = sun4i_spi_set_mode, }; static const unsigned long sun4i_spi_regs[] = { [SPI_GCR] = SUN4I_CTL_REG, [SPI_TCR] = SUN4I_CTL_REG, [SPI_FCR] = SUN4I_CTL_REG, [SPI_FSR] = SUN4I_FIFO_STA_REG, [SPI_CCR] = SUN4I_CLK_CTL_REG, [SPI_BC] = SUN4I_BURST_CNT_REG, [SPI_TC] = SUN4I_XMIT_CNT_REG, [SPI_TXD] = SUN4I_TXDATA_REG, [SPI_RXD] = SUN4I_RXDATA_REG, }; static const u32 sun4i_spi_bits[] = { [SPI_GCR_TP] = BIT(18), [SPI_TCR_CPHA] = BIT(2), [SPI_TCR_CPOL] = BIT(3), [SPI_TCR_CS_ACTIVE_LOW] = BIT(4), [SPI_TCR_XCH] = BIT(10), [SPI_TCR_CS_SEL] = 12, [SPI_TCR_CS_MASK] = 0x3000, [SPI_TCR_CS_MANUAL] = BIT(16), [SPI_TCR_CS_LEVEL] = BIT(17), [SPI_FCR_TF_RST] = BIT(8), [SPI_FCR_RF_RST] = BIT(9), [SPI_FSR_RF_CNT_MASK] = GENMASK(6, 0), }; static const struct sun4i_spi_variant sun4i_a10_spi_variant = { .regs = sun4i_spi_regs, .bits = sun4i_spi_bits, .fifo_depth = 64, }; static const struct udevice_id sun4i_spi_ids[] = { { .compatible = "allwinner,sun4i-a10-spi", .data = (ulong)&sun4i_a10_spi_variant, }, { } }; U_BOOT_DRIVER(sun4i_spi) = { .name = "sun4i_spi", .id = UCLASS_SPI, .of_match = sun4i_spi_ids, .ops = &sun4i_spi_ops, .ofdata_to_platdata = sun4i_spi_ofdata_to_platdata, .platdata_auto_alloc_size = sizeof(struct sun4i_spi_platdata), .priv_auto_alloc_size = sizeof(struct sun4i_spi_priv), .probe = sun4i_spi_probe, };