On Tue, Aug 7, 2018 at 2:47 PM, Quentin Schulz <quentin.sch...@bootlin.com> wrote: > This adds support for the ARM PL022 SPI controller for the standard > variant (0x00041022) which has a 16bit wide and 8 locations deep TX/RX > FIFO. > > A few parts were borrowed from the Linux kernel driver. > > Cc: Armando Visconti <armando.visco...@st.com> > Cc: Vipin Kumar <vipin.ku...@st.com> > Signed-off-by: Quentin Schulz <quentin.sch...@bootlin.com> > --- > > Based on "[PATCH v6] spi: pl022_spi: Add support for ARM PL022 spi controller" > from Armando Visconti sent about 5 years ago. > > Modifications: > - use readw/writew instead of readl/writel functions since the registers > are 16bit wide and not 32, > - flush the RX FIFO before claiming and releasing the bus (taken from the > Linux kernel driver), > - migrate to DM, > - rework the register access (defines instead of a structure), > - fix the set_speed function (taken from Linux kernel driver), > > drivers/spi/Kconfig | 8 +- > drivers/spi/Makefile | 1 +- > drivers/spi/pl022_spi.c | 337 +++++++++++++++++++++++++++++++++++++++++- > include/pl022_spi.h | 28 +++- > 4 files changed, 374 insertions(+) > create mode 100644 drivers/spi/pl022_spi.c > create mode 100644 include/pl022_spi.h > > diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig > index dcd719f..7d4d47d 100644 > --- a/drivers/spi/Kconfig > +++ b/drivers/spi/Kconfig > @@ -125,6 +125,14 @@ config PIC32_SPI > to access the SPI NOR flash, MMC-over-SPI on platforms based on > Microchip PIC32 family devices. > > +config PL022_SPI > + bool "ARM AMBA PL022 SSP controller driver" > + depends on ARM > + help > + This selects the ARM(R) AMBA(R) PrimeCell PL022 SSP > + controller. If you have an embedded system with an AMBA(R) > + bus and a PL022 controller, say Y or M here. > + > config RENESAS_RPC_SPI > bool "Renesas RPC SPI driver" > depends on RCAR_GEN3 > diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile > index 728e30c..6679987 100644 > --- a/drivers/spi/Makefile > +++ b/drivers/spi/Makefile > @@ -38,6 +38,7 @@ obj-$(CONFIG_MXS_SPI) += mxs_spi.o > obj-$(CONFIG_ATCSPI200_SPI) += atcspi200_spi.o > obj-$(CONFIG_OMAP3_SPI) += omap3_spi.o > obj-$(CONFIG_PIC32_SPI) += pic32_spi.o > +obj-$(CONFIG_PL022_SPI) += pl022_spi.o > obj-$(CONFIG_RENESAS_RPC_SPI) += renesas_rpc_spi.o > obj-$(CONFIG_ROCKCHIP_SPI) += rk_spi.o > obj-$(CONFIG_SANDBOX_SPI) += sandbox_spi.o > diff --git a/drivers/spi/pl022_spi.c b/drivers/spi/pl022_spi.c > new file mode 100644 > index 0000000..dc494a5 > --- /dev/null > +++ b/drivers/spi/pl022_spi.c > @@ -0,0 +1,337 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * (C) Copyright 2012 > + * Armando Visconti, ST Microelectronics, armando.visco...@st.com. > + * > + * (C) Copyright 2018 > + * Quentin Schulz, Bootlin, quentin.sch...@bootlin.com > + * > + * Driver for ARM PL022 SPI Controller. > + */ > + > +#include <asm/io.h> > +#include <clk.h> > +#include <common.h> > +#include <dm.h> > +#include <fdtdec.h> > +#include <linux/bitops.h> > +#include <linux/bug.h> > +#include <linux/io.h> > +#include <pl022_spi.h> > +#include <spi.h> > + > +#define SSP_CR0(r) (r + 0x000) > +#define SSP_CR1(r) (r + 0x004) > +#define SSP_DR(r) (r + 0x008) > +#define SSP_SR(r) (r + 0x00C) > +#define SSP_CPSR(r) (r + 0x010) > +#define SSP_IMSC(r) (r + 0x014) > +#define SSP_RIS(r) (r + 0x018) > +#define SSP_MIS(r) (r + 0x01C) > +#define SSP_ICR(r) (r + 0x020) > +#define SSP_DMACR(r) (r + 0x024) > +#define SSP_CSR(r) (r + 0x030) /* vendor extension */ > +#define SSP_ITCR(r) (r + 0x080) > +#define SSP_ITIP(r) (r + 0x084) > +#define SSP_ITOP(r) (r + 0x088) > +#define SSP_TDR(r) (r + 0x08C) > + > +#define SSP_PID0(r) (r + 0xFE0) > +#define SSP_PID1(r) (r + 0xFE4) > +#define SSP_PID2(r) (r + 0xFE8) > +#define SSP_PID3(r) (r + 0xFEC) > + > +#define SSP_CID0(r) (r + 0xFF0) > +#define SSP_CID1(r) (r + 0xFF4) > +#define SSP_CID2(r) (r + 0xFF8) > +#define SSP_CID3(r) (r + 0xFFC)
This make confusing to add exctra calculation on reg base, better to use struct or use base + MACRO diretctly > + > +/* SSP Control Register 0 - SSP_CR0 */ > +#define SSP_CR0_SPO (0x1 << 6) > +#define SSP_CR0_SPH (0x1 << 7) > +#define SSP_CR0_BIT_MODE(x) ((x) - 1) > +#define SSP_SCR_MIN (0x00) > +#define SSP_SCR_MAX (0xFF) > +#define SSP_SCR_SHFT 8 > +#define DFLT_CLKRATE 2 > + > +/* SSP Control Register 1 - SSP_CR1 */ > +#define SSP_CR1_MASK_SSE (0x1 << 1) > + > +#define SSP_CPSR_MIN (0x02) > +#define SSP_CPSR_MAX (0xFE) > +#define DFLT_PRESCALE (0x40) > + > +/* SSP Status Register - SSP_SR */ > +#define SSP_SR_MASK_TFE (0x1 << 0) /* Transmit FIFO empty */ > +#define SSP_SR_MASK_TNF (0x1 << 1) /* Transmit FIFO not full > */ > +#define SSP_SR_MASK_RNE (0x1 << 2) /* Receive FIFO not empty > */ > +#define SSP_SR_MASK_RFF (0x1 << 3) /* Receive FIFO full */ > +#define SSP_SR_MASK_BSY (0x1 << 4) /* Busy Flag */ > + > +struct pl022_spi_slave { > + void *base; > +#if !CONFIG_IS_ENABLED(OF_PLATDATA) Are you planning to support this driver for platdata as well? > + struct clk clk; > +#else > + unsigned int freq; > +#endif > +}; > + > +/* > + * ARM PL022 exists in different 'flavors'. > + * This drivers currently support the standard variant (0x00041022), that > has a > + * 16bit wide and 8 locations deep TX/RX FIFO. > + */ > +static int pl022_is_supported(struct pl022_spi_slave *ps) > +{ > + /* PL022 version is 0x00041022 */ > + if ((readw(SSP_PID0(ps->base)) == 0x22) && > + (readw(SSP_PID1(ps->base)) == 0x10) && > + ((readw(SSP_PID2(ps->base)) & 0xf) == 0x04) && > + (readw(SSP_PID3(ps->base)) == 0x00)) > + return 1; > + > + return 0; > +} > + > +static int pl022_spi_ofdata_to_platdata(struct udevice *bus) > +{ > +#if !CONFIG_IS_ENABLED(OF_PLATDATA) > + struct pl022_spi_pdata *plat = bus->platdata; > + const void *fdt = gd->fdt_blob; > + int node = dev_of_offset(bus); > + > + plat->addr = fdtdec_get_addr_size(fdt, node, "reg", &plat->size); > + > + return clk_get_by_index(bus, 0, &plat->clk); > +#else > + return 0; > +#endif Move DT code at once instead of using seprately in each function, see [1] > +} > + > +static int pl022_spi_probe(struct udevice *bus) > +{ > + struct pl022_spi_pdata *plat = dev_get_platdata(bus); > + struct pl022_spi_slave *ps = dev_get_priv(bus); > + > + ps->base = ioremap(plat->addr, plat->size); > +#if !CONFIG_IS_ENABLED(OF_PLATDATA) > + ps->clk = plat->clk; > +#else > + ps->freq = plat->freq; > +#endif > + > + /* Check the PL022 version */ > + if (!pl022_is_supported(ps)) > + return -ENOTSUPP; > + > + /* 12 bits per word, high polarity and default clock rate */ > + writew(SSP_CR0_BIT_MODE(8) | SSP_CR0_SPH | > + (DFLT_CLKRATE << SSP_SCR_SHFT), SSP_CR0(ps->base)); > + writew(DFLT_PRESCALE, SSP_CPSR(ps->base)); > + > + return 0; > +} > + > +static void flush(struct pl022_spi_slave *ps) > +{ > + do { > + while (readw(SSP_SR(ps->base)) & SSP_SR_MASK_RNE) > + readw(SSP_DR(ps->base)); > + } while (readw(SSP_SR(ps->base)) & SSP_SR_MASK_BSY); > +} > + > +static int pl022_spi_claim_bus(struct udevice *dev) > +{ > + struct udevice *bus = dev->parent; > + struct pl022_spi_slave *ps = dev_get_priv(bus); > + u16 reg; > + > + flush(ps); > + > + /* Enable the SPI hardware */ > + reg = readw(SSP_CR1(ps->base)); > + reg |= SSP_CR1_MASK_SSE; > + writew(reg, SSP_CR1(ps->base)); > + > + return 0; > +} > + > +static int pl022_spi_release_bus(struct udevice *dev) > +{ > + struct udevice *bus = dev->parent; > + struct pl022_spi_slave *ps = dev_get_priv(bus); > + u16 reg; > + > + /* Disable the SPI hardware */ > + reg = readw(SSP_CR1(ps->base)); > + reg &= ~SSP_CR1_MASK_SSE; > + writew(reg, SSP_CR1(ps->base)); > + > + flush(ps); > + > + return 0; > +} > + > +static int pl022_spi_xfer(struct udevice *dev, unsigned int bitlen, > + const void *dout, void *din, unsigned long flags) > +{ > + struct udevice *bus = dev->parent; > + struct pl022_spi_slave *ps = dev_get_priv(bus); > + u32 len_tx = 0, len_rx = 0, len; > + u32 ret = 0; > + const u8 *txp = dout; > + u8 *rxp = din, value; > + > + if (bitlen == 0) > + /* Finish any previously submitted transfers */ > + return 0; > + > + /* > + * TODO: The controller can do non-multiple-of-8 bit > + * transfers, but this driver currently doesn't support it. > + * > + * It's also not clear how such transfers are supposed to be > + * represented as a stream of bytes...this is a limitation of > + * the current SPI interface. > + */ > + if (bitlen % 8) { > + /* Errors always terminate an ongoing transfer */ > + flags |= SPI_XFER_END; > + return -1; > + } > + > + len = bitlen / 8; > + > + while (len_tx < len) { > + if (readw(SSP_SR(ps->base)) & SSP_SR_MASK_TNF) { > + value = (txp) ? *txp++ : 0; > + writew(value, SSP_DR(ps->base)); > + len_tx++; > + } > + > + if (readw(SSP_SR(ps->base)) & SSP_SR_MASK_RNE) { > + value = readw(SSP_DR(ps->base)); > + if (rxp) > + *rxp++ = value; > + len_rx++; > + } > + } > + > + while (len_rx < len_tx) { > + if (readw(SSP_SR(ps->base)) & SSP_SR_MASK_RNE) { > + value = readw(SSP_DR(ps->base)); > + if (rxp) > + *rxp++ = value; > + len_rx++; > + } > + } > + > + return ret; > +} > + > +static inline u32 spi_rate(u32 rate, u16 cpsdvsr, u16 scr) > +{ > + return rate / (cpsdvsr * (1 + scr)); > +} > + > +static int pl022_spi_set_speed(struct udevice *bus, uint speed) > +{ > + struct pl022_spi_slave *ps = dev_get_priv(bus); > + u16 scr = SSP_SCR_MIN, tmp, cr0 = 0, cpsr = SSP_CPSR_MIN, best_scr, > + best_cpsr; > + u32 min, max, best_freq = 0; > +#if !CONFIG_IS_ENABLED(OF_PLATDATA) > + u32 rate = clk_get_rate(&ps->clk); > +#else > + u32 rate = ps->freq; > +#endif > + bool found = false; > + > + max = spi_rate(rate, SSP_CPSR_MIN, SSP_SCR_MIN); > + min = spi_rate(rate, SSP_CPSR_MAX, SSP_SCR_MAX); > + > + if (speed > max || speed < min) { > + pr_err("Tried to set speed to %dHz but min=%d and max=%d\n", > + speed, min, max); > + return -EINVAL; > + } > + > + while (cpsr <= SSP_CPSR_MAX && !found) { > + while (scr <= SSP_SCR_MAX) { > + tmp = spi_rate(rate, cpsr, scr); > + > + if (tmp > speed) { > + scr++; > + continue; > + } > + > + if (tmp > best_freq) { > + best_freq = tmp; > + best_cpsr = cpsr; > + best_scr = scr; > + > + if (tmp == speed) > + found = true; > + } > + > + break; > + } > + cpsr += 2; > + scr = SSP_SCR_MIN; > + } > + > + writew(best_cpsr, SSP_CPSR(ps->base)); > + cr0 = readw(SSP_CR0(ps->base)); > + writew(cr0 | (best_scr << SSP_SCR_SHFT), SSP_CR0(ps->base)); > + > + return 0; > +} > + > +static int pl022_spi_set_mode(struct udevice *bus, uint mode) > +{ > + struct pl022_spi_slave *ps = dev_get_priv(bus); > + u16 reg; > + > + reg = readw(SSP_CR0(ps->base)); > + reg &= ~(SSP_CR0_SPH | SSP_CR0_SPO); > + if (mode & SPI_CPHA) > + reg |= SSP_CR0_SPH; > + if (mode & SPI_CPOL) > + reg |= SSP_CR0_SPO; > + writew(reg, SSP_CR0(ps->base)); > + > + return 0; > +} > + > +static int pl022_cs_info(struct udevice *bus, uint cs, > + struct spi_cs_info *info) > +{ > + return 0; > +} > + > +static const struct dm_spi_ops pl022_spi_ops = { > + .claim_bus = pl022_spi_claim_bus, > + .release_bus = pl022_spi_release_bus, > + .xfer = pl022_spi_xfer, > + .set_speed = pl022_spi_set_speed, > + .set_mode = pl022_spi_set_mode, > + .cs_info = pl022_cs_info, > +}; > + > +static const struct udevice_id pl022_spi_ids[] = { > + { .compatible = "arm,pl022-spi" }, > + { } > +}; This can't use it on PLATDATA. [1] https://patchwork.ozlabs.org/patch/954373/ > + > +U_BOOT_DRIVER(pl022_spi) = { > + .name = "pl022_spi", > + .id = UCLASS_SPI, > + .of_match = pl022_spi_ids, > + .ops = &pl022_spi_ops, > + .ofdata_to_platdata = pl022_spi_ofdata_to_platdata, > + .platdata_auto_alloc_size = sizeof(struct pl022_spi_pdata), > + .priv_auto_alloc_size = sizeof(struct pl022_spi_slave), > + .probe = pl022_spi_probe, > +}; > diff --git a/include/pl022_spi.h b/include/pl022_spi.h > new file mode 100644 > index 0000000..e707b23 > --- /dev/null > +++ b/include/pl022_spi.h > @@ -0,0 +1,28 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * (C) Copyright 2018 > + * Quentin Schulz, Bootlin, quentin.sch...@bootlin.com > + * > + * Structure for use with U_BOOT_DEVICE for pl022 SPI devices or to use > + * in ofdata_to_platdata. > + */ > + > +#ifndef __PL022_SPI_H__ > +#define __PL022_SPI_H__ > + > +#if !CONFIG_IS_ENABLED(OF_PLATDATA) > +#include <clk.h> > +#endif > +#include <fdtdec.h> > + > +struct pl022_spi_pdata { > + fdt_addr_t addr; > + fdt_size_t size; > +#if !CONFIG_IS_ENABLED(OF_PLATDATA) > + struct clk clk; > +#else > + unsigned int freq; > +#endif > +}; > + > +#endif If you use this for PLATDATA, add it in include/dm/platform_data/ _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de https://lists.denx.de/listinfo/u-boot