/* * Copyright (C) 2015-2016 Wills Wang * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include #include #include #include /* CLOCK_DIVIDER = 3 (SPI clock = 200 / 8 ~ 25 MHz) */ #define ATH79_SPI_CLK_DIV(x) (((x) >> 1) - 1) #define ATH79_SPI_RRW_DELAY_FACTOR 12000 #define ATH79_SPI_MHZ (1000 * 1000) struct ath79_spi_priv { void __iomem *regs; u32 rrw_delay; }; static void spi_cs_activate(struct udevice *dev) { struct udevice *bus = dev_get_parent(dev); struct ath79_spi_priv *priv = dev_get_priv(bus); writel(AR71XX_SPI_FS_GPIO, priv->regs + AR71XX_SPI_REG_FS); writel(AR71XX_SPI_IOC_CS_ALL, priv->regs + AR71XX_SPI_REG_IOC); } static void spi_cs_deactivate(struct udevice *dev) { struct udevice *bus = dev_get_parent(dev); struct ath79_spi_priv *priv = dev_get_priv(bus); writel(AR71XX_SPI_IOC_CS_ALL, priv->regs + AR71XX_SPI_REG_IOC); writel(0, priv->regs + AR71XX_SPI_REG_FS); } static int ath79_spi_claim_bus(struct udevice *dev) { return 0; } static int ath79_spi_release_bus(struct udevice *dev) { return 0; } static int ath79_spi_xfer(struct udevice *dev, unsigned int bitlen, const void *dout, void *din, unsigned long flags) { struct udevice *bus = dev_get_parent(dev); struct ath79_spi_priv *priv = dev_get_priv(bus); struct dm_spi_slave_platdata *slave = dev_get_parent_platdata(dev); u8 *rx = din; const u8 *tx = dout; u8 curbyte, curbitlen, restbits; u32 bytes = bitlen / 8; u32 out, in; u64 tick; if (flags & SPI_XFER_BEGIN) spi_cs_activate(dev); restbits = (bitlen % 8); if (restbits) bytes++; out = AR71XX_SPI_IOC_CS_ALL & ~(AR71XX_SPI_IOC_CS(slave->cs)); while (bytes > 0) { bytes--; curbyte = 0; if (tx) curbyte = *tx++; if (restbits && !bytes) { curbitlen = restbits; curbyte <<= 8 - restbits; } else { curbitlen = 8; } for (curbyte <<= (8 - curbitlen); curbitlen; curbitlen--) { if (curbyte & 0x80) out |= AR71XX_SPI_IOC_DO; else out &= ~(AR71XX_SPI_IOC_DO); writel(out, priv->regs + AR71XX_SPI_REG_IOC); /* delay for low level */ if (priv->rrw_delay) { tick = get_ticks() + priv->rrw_delay; while (get_ticks() < tick) /*NOP*/; } writel(out | AR71XX_SPI_IOC_CLK, priv->regs + AR71XX_SPI_REG_IOC); /* delay for high level */ if (priv->rrw_delay) { tick = get_ticks() + priv->rrw_delay; while (get_ticks() < tick) /*NOP*/; } curbyte <<= 1; } if (!bytes) writel(out, priv->regs + AR71XX_SPI_REG_IOC); in = readl(priv->regs + AR71XX_SPI_REG_RDS); if (rx) { if (restbits && !bytes) *rx++ = (in << (8 - restbits)); else *rx++ = in; } } if (flags & SPI_XFER_END) spi_cs_deactivate(dev); return 0; } static int ath79_spi_set_speed(struct udevice *bus, uint speed) { struct ath79_spi_priv *priv = dev_get_priv(bus); u32 val, div = 0; u64 time; if (speed) div = get_bus_freq(0) / speed; if (div > 63) div = 63; if (div < 5) div = 5; /* calculate delay */ time = get_tbclk(); do_div(time, speed / 2); val = get_bus_freq(0) / ATH79_SPI_MHZ; val = ATH79_SPI_RRW_DELAY_FACTOR / val; if (time > val) priv->rrw_delay = time - val + 1; else priv->rrw_delay = 0; writel(AR71XX_SPI_FS_GPIO, priv->regs + AR71XX_SPI_REG_FS); clrsetbits_be32(priv->regs + AR71XX_SPI_REG_CTRL, AR71XX_SPI_CTRL_DIV_MASK, ATH79_SPI_CLK_DIV(div)); writel(0, priv->regs + AR71XX_SPI_REG_FS); return 0; } static int ath79_spi_set_mode(struct udevice *bus, uint mode) { return 0; } static int ath79_spi_probe(struct udevice *bus) { struct ath79_spi_priv *priv = dev_get_priv(bus); fdt_addr_t addr; addr = dev_get_addr(bus); if (addr == FDT_ADDR_T_NONE) return -EINVAL; priv->regs = map_physmem(addr, AR71XX_SPI_SIZE, MAP_NOCACHE); /* Init SPI Hardware, disable remap, set clock */ writel(AR71XX_SPI_FS_GPIO, priv->regs + AR71XX_SPI_REG_FS); writel(AR71XX_SPI_CTRL_RD | ATH79_SPI_CLK_DIV(8), priv->regs + AR71XX_SPI_REG_CTRL); writel(0, priv->regs + AR71XX_SPI_REG_FS); return 0; } static int ath79_cs_info(struct udevice *bus, uint cs, struct spi_cs_info *info) { /* Always allow activity on CS 0/1/2 */ if (cs >= 3) return -ENODEV; return 0; } static const struct dm_spi_ops ath79_spi_ops = { .claim_bus = ath79_spi_claim_bus, .release_bus = ath79_spi_release_bus, .xfer = ath79_spi_xfer, .set_speed = ath79_spi_set_speed, .set_mode = ath79_spi_set_mode, .cs_info = ath79_cs_info, }; static const struct udevice_id ath79_spi_ids[] = { { .compatible = "qca,ar7100-spi" }, {} }; U_BOOT_DRIVER(ath79_spi) = { .name = "ath79_spi", .id = UCLASS_SPI, .of_match = ath79_spi_ids, .ops = &ath79_spi_ops, .priv_auto_alloc_size = sizeof(struct ath79_spi_priv), .probe = ath79_spi_probe, };