[U-Boot] [PATCH 2/3] spi: Add support for the Aspeed ast2500 SPI controllers
Simon Glass
sjg at chromium.org
Thu Sep 27 13:41:48 UTC 2018
Hi Cedric,
On 10 September 2018 at 07:16, Cédric Le Goater <clg at kaod.org> wrote:
> The Aspeed AST2500 SoC comes with three static memory controllers, all
> with a similar interface :
>
> * Firmware SPI Memory Controller (FMC)
> . BMC firmware
> . 3 chip select pins (CE0 ~ CE2)
> . supports SPI type flash memory (CE0 ~ CE1)
> . CE2 can be of NOR type flash but this is not supported by the
> driver
>
> * SPI Flash Controller (SPI1 and SPI2)
> . host firmware
> . 2 chip select pins (CE0 ~ CE1)
>
> Each controller has a defined AHB window for its registers and another
> AHB window on which all the flash devices are mapped. Each device is
> assigned a memory range through a set of registers called the Segment
> Address Registers.
>
> Devices are controlled using two different modes: the USER command
> mode or the READ/WRITE command mode. When in USER command mode,
> accesses to the AHB window of the SPI flash device are translated into
> SPI command transfers. When in READ/WRITE command mode, the HW
> generates the SPI commands depending on the setting of the CE control
> register.
>
> The following driver supports the FMC and the SPI controllers with the
> attached SPI flash devices. When the controller is probed, the driver
> performs a read timing calibration using specific DMA control
> registers (FMC only). The driver's primary goal is to support the
> first device of the FMC controller on which reside U-Boot but it
> should support the other controllers also.
>
> The Aspeed FMC controller automatically detects at boot time if a
> flash device is in 4BYTE address mode and self configures to use the
> appropriate address width. This can be a problem for the U-Boot SPI
> Flash layer which only supports 3 byte addresses. In such a case, a
> warning is emitted and the address width is fixed when sent on the
> bus.
>
> Signed-off-by: Cédric Le Goater <clg at kaod.org>
> ---
> arch/arm/include/asm/arch-aspeed/spi.h | 114 ++++
> drivers/spi/aspeed_spi.c | 788 +++++++++++++++++++++++++
> drivers/spi/Kconfig | 8 +
> drivers/spi/Makefile | 1 +
> 4 files changed, 911 insertions(+)
> create mode 100644 arch/arm/include/asm/arch-aspeed/spi.h
> create mode 100644 drivers/spi/aspeed_spi.c
>
> diff --git a/arch/arm/include/asm/arch-aspeed/spi.h b/arch/arm/include/asm/arch-aspeed/spi.h
> new file mode 100644
> index 000000000000..9e952933e1f1
> --- /dev/null
> +++ b/arch/arm/include/asm/arch-aspeed/spi.h
> @@ -0,0 +1,114 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (c) 2018, IBM Corporation.
> + */
> +
> +#ifndef _ASM_ARCH_ASPEED_SPI_H
> +#define _ASM_ARCH_ASPEED_SPI_H
> +
> +/* CE Type Setting Register */
> +#define CONF_ENABLE_W2 BIT(18)
> +#define CONF_ENABLE_W1 BIT(17)
> +#define CONF_ENABLE_W0 BIT(16)
> +#define CONF_FLASH_TYPE2 4
> +#define CONF_FLASH_TYPE1 2 /* Hardwired to SPI */
> +#define CONF_FLASH_TYPE0 0 /* Hardwired to SPI */
> +#define CONF_FLASH_TYPE_NOR 0x0
> +#define CONF_FLASH_TYPE_SPI 0x2
> +
> +/* CE Control Register */
> +#define CTRL_EXTENDED2 BIT(2) /* 32 bit addressing for SPI */
> +#define CTRL_EXTENDED1 BIT(1) /* 32 bit addressing for SPI */
> +#define CTRL_EXTENDED0 BIT(0) /* 32 bit addressing for SPI */
> +
> +/* Interrupt Control and Status Register */
> +#define INTR_CTRL_DMA_STATUS BIT(11)
> +#define INTR_CTRL_CMD_ABORT_STATUS BIT(10)
> +#define INTR_CTRL_WRITE_PROTECT_STATUS BIT(9)
> +#define INTR_CTRL_DMA_EN BIT(3)
> +#define INTR_CTRL_CMD_ABORT_EN BIT(2)
> +#define INTR_CTRL_WRITE_PROTECT_EN BIT(1)
> +
> +/* CEx Control Register */
> +#define CE_CTRL_IO_MODE_MASK GENMASK(30, 28)
> +#define CE_CTRL_IO_DUAL_DATA BIT(29)
> +#define CE_CTRL_IO_DUAL_ADDR_DATA (BIT(29) | BIT(28))
> +#define CE_CTRL_CMD_SHIFT 16
> +#define CE_CTRL_CMD_MASK 0xff
> +#define CE_CTRL_CMD(cmd) \
> + (((cmd) & CE_CTRL_CMD_MASK) << CE_CTRL_CMD_SHIFT)
> +#define CE_CTRL_DUMMY_HIGH_SHIFT 14
> +#define CE_CTRL_CLOCK_FREQ_SHIFT 8
> +#define CE_CTRL_CLOCK_FREQ_MASK 0xf
> +#define CE_CTRL_CLOCK_FREQ(div) \
> + (((div) & CE_CTRL_CLOCK_FREQ_MASK) << CE_CTRL_CLOCK_FREQ_SHIFT)
Do you need this, and other macros like it? I suppose you do use them
twice in the code, at least.
> +#define CE_CTRL_DUMMY_LOW_SHIFT 6 /* 2 bits [7:6] */
> +#define CE_CTRL_DUMMY(dummy) \
> + (((((dummy) >> 2) & 0x1) << CE_CTRL_DUMMY_HIGH_SHIFT) | \
> + (((dummy) & 0x3) << CE_CTRL_DUMMY_LOW_SHIFT))
I think this needs MASK values instead of open-coding them.
> +#define CE_CTRL_STOP_ACTIVE BIT(2)
> +#define CE_CTRL_MODE_MASK 0x3
> +#define CE_CTRL_READMODE 0x0
> +#define CE_CTRL_FREADMODE 0x1
> +#define CE_CTRL_WRITEMODE 0x2
> +#define CE_CTRL_USERMODE 0x3
> +
> +/*
> + * The Segment Register uses a 8MB unit to encode the start address
> + * and the end address of the ABH window of a SPI flash device.
AHB?
> + * Default segment addresses are :
> + *
> + * CE0 0x20000000 - 0x2FFFFFFF 128MB
> + * CE1 0x28000000 - 0x29FFFFFF 32MB
> + * CE2 0x2A000000 - 0x2BFFFFFF 32MB
Can you use local-case hex for consistency?
> + *
> + * The full address space of the AHB window of the controller is
> + * covered and CE0 start address and CE2 end addresses are read-only.
> + */
> +#define SEGMENT_ADDR_START(reg) ((((reg) >> 16) & 0xff) << 23)
> +#define SEGMENT_ADDR_END(reg) ((((reg) >> 24) & 0xff) << 23)
> +#define SEGMENT_ADDR_VALUE(start, end) \
> + (((((start) >> 23) & 0xff) << 16) | ((((end) >> 23) & 0xff) << 24))
> +
I think these should be done as MASK and SHIFT values instead. They
are only used once int he code.
> +/* DMA Control/Status Register */
> +#define DMA_CTRL_DELAY_SHIFT 8
> +#define DMA_CTRL_DELAY_MASK 0xf
> +#define DMA_CTRL_FREQ_SHIFT 4
> +#define DMA_CTRL_FREQ_MASK 0xf
> +#define TIMING_MASK(div, delay) \
> + (((delay & DMA_CTRL_DELAY_MASK) << DMA_CTRL_DELAY_SHIFT) | \
> + ((div & DMA_CTRL_FREQ_MASK) << DMA_CTRL_FREQ_SHIFT))
Just move this code down below?
> +#define DMA_CTRL_CALIB BIT(3)
> +#define DMA_CTRL_CKSUM BIT(2)
> +#define DMA_CTRL_WRITE BIT(1)
> +#define DMA_CTRL_ENABLE BIT(0)
> +
> +#define ASPEED_SPI_MAX_CS 3
> +
> +struct aspeed_spi_regs {
> + u32 conf; /* 0x00 CE Type Setting */
> + u32 ctrl; /* 0x04 Control */
> + u32 intr_ctrl; /* 0x08 Interrupt Control and Status */
> + u32 cmd_ctrl; /* 0x0C Command Control */
> + u32 ce_ctrl[ASPEED_SPI_MAX_CS]; /* 0x10 .. 0x18 CEx Control */
> + u32 _reserved0[5]; /* .. */
> + u32 segment_addr[ASPEED_SPI_MAX_CS];
> + /* 0x30 .. 0x38 Segment Address */
> + u32 _reserved1[17]; /* .. */
> + u32 dma_ctrl; /* 0x80 DMA Control/Status */
> + u32 dma_flash_addr; /* 0x84 DMA Flash Side Address */
> + u32 dma_dram_addr; /* 0x88 DMA DRAM Side Address */
> + u32 dma_len; /* 0x8C DMA Length Register */
> + u32 dma_checksum; /* 0x8C Checksum Calculation Result */
> + u32 timings; /* 0x94 Read Timing Compensation */
> +
> + /* not used */
> + u32 soft_strap_status; /* 0x9C Software Strap Status */
> + u32 write_cmd_filter_ctrl; /* 0xA0 Write Command Filter Control */
> + u32 write_addr_filter_ctrl; /* 0xA4 Write Address Filter Control */
> + u32 lock_ctrl_reset; /* 0xA8 Lock Control (SRST#) */
> + u32 lock_ctrl_wdt; /* 0xAC Lock Control (Watchdog) */
> + u32 write_addr_filter[5]; /* 0xB0 Write Address Filter */
Lower-case hex again
> +};
> +
> +#endif /* _ASM_ARCH_ASPEED_SPI_H */
> diff --git a/drivers/spi/aspeed_spi.c b/drivers/spi/aspeed_spi.c
> new file mode 100644
> index 000000000000..7f1a39d8e698
> --- /dev/null
> +++ b/drivers/spi/aspeed_spi.c
> @@ -0,0 +1,788 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * ASPEED AST2500 FMC/SPI Controller driver
> + *
> + * Copyright (c) 2015-2018, IBM Corporation.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <common.h>
> +#include <clk.h>
> +#include <dm.h>
> +#include <asm/io.h>
> +#include <asm/arch/spi.h>
> +#include <linux/ioport.h>
> +#include <spi.h>
> +#include <spi_flash.h>
These last two should go immediately below dm.h
> +
> +DECLARE_GLOBAL_DATA_PTR;
Do you need this?
> +
> +struct aspeed_spi_flash {
struct comment - what is this for?
> + u8 cs;
> + bool init; /* Initialized when the SPI bus is
> + * first claimed
> + */
Please avoid this type of comment - either put it before the line, or
add a struct comment with each member listed.
Also, can you drop this variable and do the init in the probe() method instead?
> + void __iomem *ahb_base; /* AHB Window for this device */
Should that be a struct *?
> + u32 ahb_size; /* AHB Window segment size */
> + u32 ce_ctrl_user; /* CE Control Register for USER mode */
> + u32 ce_ctrl_fread; /* CE Control Register for FREAD mode */
> + u32 iomode;
> +
> + struct spi_flash *spi; /* Associated SPI Flash device */
You should not need this struct here with driver model.
> +};
> +
> +struct aspeed_spi_priv {
> + struct aspeed_spi_regs *regs;
> + void __iomem *ahb_base; /* AHB Window for all flash devices */
> + u32 ahb_size; /* AHB Window segments size */
> +
> + ulong hclk_rate; /* AHB clock rate */
> + u32 max_hz;
> + u8 num_cs;
> + bool is_fmc;
> +
> + struct aspeed_spi_flash flashes[ASPEED_SPI_MAX_CS];
SPI flash should be modelled as UCLASS_SPI_FLASH devices. Much of the
code in here looks like it should move to a separate driver.
> + u32 flash_count;
> +
> + u8 cmd_buf[16]; /* SPI command in progress */
> + size_t cmd_len;
> +};
> +
> +static struct aspeed_spi_flash *aspeed_spi_get_flash(struct udevice *dev)
> +{
> + struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
> + struct aspeed_spi_priv *priv = dev_get_priv(dev->parent);
> + u8 cs = slave_plat->cs;
> +
> + if (cs >= priv->flash_count) {
> + pr_err("invalid CS %u\n", cs);
> + return NULL;
> + }
> +
> + return &priv->flashes[cs];
> +}
> +
> +static u32 aspeed_spi_hclk_divisor(u32 hclk_rate, u32 max_hz)
Function comment
> +{
> + /* HCLK/1 .. HCLK/16 */
> + const u8 hclk_divisors[] = {
> + 15, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 0
> + };
> +
> + u32 i;
int or uint. This does not need to be 32-bit.
> +
> + for (i = 0; i < ARRAY_SIZE(hclk_divisors); i++) {
> + if (max_hz >= (hclk_rate / (i + 1)))
> + break;
> + }
> +
> + debug("hclk=%d required=%d divisor is %d (mask %x) speed=%d\n",
> + hclk_rate, max_hz, i + 1, hclk_divisors[i], hclk_rate / (i + 1));
> +
> + return hclk_divisors[i];
> +}
> +
> +/*
> + * Use some address/size under the first flash device CE0
Please add a function comment with purpose, args and return value.
Same throughout.
> + */
> +static u32 aspeed_spi_fmc_checksum(struct aspeed_spi_priv *priv, u8 div,
> + u8 delay)
> +{
> + u32 flash_addr = (u32)priv->ahb_base + 0x10000;
What is happening here? The 0x10000 offset should be in the device
tree, I think. Should cast a pointer to ulong, not u32. Also, perhaps
ahb_base should be a ulong instead of a pointer?
> + u32 flash_len = 0x200;
> + u32 dma_ctrl;
> + u32 checksum;
> +
> + writel(flash_addr, &priv->regs->dma_flash_addr);
> + writel(flash_len, &priv->regs->dma_len);
> +
> + /*
> + * When doing calibration, the SPI clock rate in the CE0
> + * Control Register and the read delay cycles in the Read
> + * Timing Compensation Register are replaced by bit[11:4].
> + */
> + dma_ctrl = DMA_CTRL_ENABLE | DMA_CTRL_CKSUM | DMA_CTRL_CALIB |
> + TIMING_MASK(div, delay);
> + writel(dma_ctrl, &priv->regs->dma_ctrl);
> +
> + while (!(readl(&priv->regs->intr_ctrl) & INTR_CTRL_DMA_STATUS))
> + ;
I assume this never times out?
> +
> + writel(0x0, &priv->regs->intr_ctrl);
> +
> + checksum = readl(&priv->regs->dma_checksum);
> +
> + writel(0x0, &priv->regs->dma_ctrl);
> +
> + return checksum;
> +}
> +
> +static u32 aspeed_spi_read_checksum(struct aspeed_spi_priv *priv, u8 div,
> + u8 delay)
> +{
> + /* TODO: the SPI controllers do not have the DMA registers.
/*
* TODO(email) :...
* ...
*/
> + * The algorithm is the same.
> + */
> + if (!priv->is_fmc) {
> + pr_warn("No timing calibration support for SPI controllers");
> + return 0xbadc0de;
What is this value? Can it be a #define?
> + }
> +
> + return aspeed_spi_fmc_checksum(priv, div, delay);
> +}
> +
> +static int aspeed_spi_timing_calibration(struct aspeed_spi_priv *priv)
> +{
> + /* HCLK/5 .. HCLK/1 */
> + const u8 hclk_divisors[] = { 13, 6, 14, 7, 15 };
> +
> + u32 timing_reg = 0;
> + u32 checksum, gold_checksum;
> + int i, j;
> +
> + debug("Read timing calibration :\n");
> +
> + /* Compute reference checksum at lowest freq HCLK/16 */
> + gold_checksum = aspeed_spi_read_checksum(priv, 0, 0);
> +
> + /*
> + * Set CE0 Control Register to FAST READ command mode. The
> + * HCLK divisor will be set through the DMA Control Register.
> + */
> + writel(CE_CTRL_CMD(0xB) | CE_CTRL_DUMMY(1) | CE_CTRL_FREADMODE,
> + &priv->regs->ce_ctrl[0]);
> +
> + /* Increase HCLK freq */
> + for (i = 0; i < ARRAY_SIZE(hclk_divisors); i++) {
> + u32 hdiv = 5 - i;
> + u32 hshift = (hdiv - 1) << 2;
> + bool pass = false;
> + u8 delay;
> +
> + if (priv->hclk_rate / hdiv > priv->max_hz) {
> + debug("skipping freq %ld\n", priv->hclk_rate / hdiv);
> + continue;
> + }
> +
> + /* Increase HCLK delays until read succeeds */
> + for (j = 0; j < 6; j++) {
> + /* Try first with a 4ns DI delay */
> + delay = 0x8 | j;
> + checksum = aspeed_spi_read_checksum(
> + priv, hclk_divisors[i], delay);
> + pass = (checksum == gold_checksum);
> + debug(" HCLK/%d, 4ns DI delay, %d HCLK delay : %s\n",
> + hdiv, j, pass ? "PASS" : "FAIL");
> +
> + /* Try again with more HCLK delays */
> + if (!pass)
> + continue;
> +
> + /* Try without the 4ns DI delay */
> + delay = j;
> + checksum = aspeed_spi_read_checksum(
> + priv, hclk_divisors[i], delay);
> + pass = (checksum == gold_checksum);
> + debug(" HCLK/%d, 0ns DI delay, %d HCLK delay : %s\n",
> + hdiv, j, pass ? "PASS" : "FAIL");
> +
> + /* All good for this freq */
> + if (pass)
> + break;
> + }
> +
> + if (pass) {
> + timing_reg &= ~(0xfu << hshift);
> + timing_reg |= delay << hshift;
> + }
> + }
> +
> + debug("Timing Register set to 0x%08x\n", timing_reg);
> + writel(timing_reg, &priv->regs->timings);
> +
> + /* Reset CE0 Control Register */
> + writel(0x0, &priv->regs->ce_ctrl[0]);
> + return 0;
> +}
> +
> +static int aspeed_spi_controller_init(struct aspeed_spi_priv *priv)
> +{
> + int cs, ret;
> +
> + /*
> + * Enable write on all flash devices as USER command mode
> + * requires it.
> + */
> + setbits_le32(&priv->regs->conf,
> + CONF_ENABLE_W2 | CONF_ENABLE_W1 | CONF_ENABLE_W0);
> +
> + /*
> + * Set the Read Timing Compensation Register. This setting
> + * applies to all devices.
> + */
> + ret = aspeed_spi_timing_calibration(priv);
> + if (ret)
> + return ret;
> +
> + /*
> + * Set safe default settings for each device. These will be
> + * tuned after the SPI flash devices are probed.
> + */
> + for (cs = 0; cs < priv->flash_count; cs++) {
> + struct aspeed_spi_flash *flash = &priv->flashes[cs];
> + u32 seg_addr = readl(&priv->regs->segment_addr[cs]);
> +
> + /*
> + * The start address of the AHB window of CE0 is
> + * read-only and is the same as the address of the
> + * overall AHB window of the controller for all flash
> + * devices.
> + */
> + flash->ahb_base = cs ? (void *)SEGMENT_ADDR_START(seg_addr) :
> + priv->ahb_base;
> +
> + flash->cs = cs;
> + flash->ce_ctrl_user = CE_CTRL_USERMODE;
> + flash->ce_ctrl_fread = CE_CTRL_READMODE;
> + }
> +
> + return 0;
> +}
> +
> +static int aspeed_spi_read_from_ahb(void __iomem *ahb_base, void *buf,
> + size_t len)
> +{
> + size_t offset = 0;
> +
> + if (!((uintptr_t)buf % 4)) {
> + readsl(ahb_base, buf, len >> 2);
> + offset = len & ~0x3;
> + len -= offset;
> + }
> + readsb(ahb_base, (u8 *)buf + offset, len);
> +
> + return 0;
> +}
> +
> +static int aspeed_spi_write_to_ahb(void __iomem *ahb_base, const void *buf,
> + size_t len)
> +{
> + size_t offset = 0;
> +
> + if (!((uintptr_t)buf % 4)) {
> + writesl(ahb_base, buf, len >> 2);
> + offset = len & ~0x3;
> + len -= offset;
> + }
> + writesb(ahb_base, (u8 *)buf + offset, len);
> +
> + return 0;
> +}
> +
> +static void aspeed_spi_start_user(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash)
> +{
> + u32 ctrl_reg = flash->ce_ctrl_user | CE_CTRL_STOP_ACTIVE;
> +
> + /* Unselect CS and set USER command mode */
Deselect?
> + writel(ctrl_reg, &priv->regs->ce_ctrl[flash->cs]);
> +
> + /* Select CS */
> + clrbits_le32(&priv->regs->ce_ctrl[flash->cs], CE_CTRL_STOP_ACTIVE);
> +}
> +
> +static void aspeed_spi_stop_user(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash)
> +{
> + /* Unselect CS first */
> + setbits_le32(&priv->regs->ce_ctrl[flash->cs], CE_CTRL_STOP_ACTIVE);
> +
> + /* Restore default command mode */
> + writel(flash->ce_ctrl_fread, &priv->regs->ce_ctrl[flash->cs]);
> +}
> +
> +static int aspeed_spi_read_reg(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash,
> + u8 opcode, u8 *read_buf, int len)
> +{
> + aspeed_spi_start_user(priv, flash);
> + aspeed_spi_write_to_ahb(flash->ahb_base, &opcode, 1);
> + aspeed_spi_read_from_ahb(flash->ahb_base, read_buf, len);
> + aspeed_spi_stop_user(priv, flash);
Please add blank line before return
> + return 0;
> +}
> +
> +static int aspeed_spi_write_reg(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash,
> + u8 opcode, const u8 *write_buf, int len)
> +{
> + aspeed_spi_start_user(priv, flash);
> + aspeed_spi_write_to_ahb(flash->ahb_base, &opcode, 1);
> + aspeed_spi_write_to_ahb(flash->ahb_base, write_buf, len);
> + aspeed_spi_stop_user(priv, flash);
> + return 0;
> +}
> +
> +static void aspeed_spi_send_cmd_addr(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash,
> + const u8 *cmdbuf, unsigned int cmdlen)
> +{
> + int i;
> + u8 byte0 = 0x0;
> + u8 addrlen = cmdlen - 1;
> +
> + /* First, send the opcode */
> + aspeed_spi_write_to_ahb(flash->ahb_base, &cmdbuf[0], 1);
> +
> + /*
> + * The controller is configured for 4BYTE address mode. Fix
> + * the address width and send an extra byte if the SPI Flash
> + * layer is still 3 bytes addresses.
> + */
> + if (addrlen == 3 && readl(&priv->regs->ctrl) & BIT(flash->cs))
> + aspeed_spi_write_to_ahb(flash->ahb_base, &byte0, 1);
> +
> + /* Then the address */
> + for (i = 1 ; i < cmdlen; i++)
> + aspeed_spi_write_to_ahb(flash->ahb_base, &cmdbuf[i], 1);
> +}
> +
> +static ssize_t aspeed_spi_read_user(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash,
> + unsigned int cmdlen, const u8 *cmdbuf,
> + unsigned int len, u8 *read_buf)
> +{
> + u8 dummy = 0xFF;
> + int i;
> +
> + aspeed_spi_start_user(priv, flash);
> +
> + /* cmd buffer = cmd + addr + dummies */
> + aspeed_spi_send_cmd_addr(priv, flash, cmdbuf,
> + cmdlen - flash->spi->dummy_byte);
> +
> + for (i = 0 ; i < flash->spi->dummy_byte; i++)
> + aspeed_spi_write_to_ahb(flash->ahb_base, &dummy, 1);
> +
> + if (flash->iomode) {
> + clrbits_le32(&priv->regs->ce_ctrl[flash->cs],
> + CE_CTRL_IO_MODE_MASK);
> + setbits_le32(&priv->regs->ce_ctrl[flash->cs], flash->iomode);
> + }
> +
> + aspeed_spi_read_from_ahb(flash->ahb_base, read_buf, len);
> + aspeed_spi_stop_user(priv, flash);
> + return 0;
> +}
> +
> +static ssize_t aspeed_spi_write_user(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash,
> + unsigned int cmdlen, const u8 *cmdbuf,
> + unsigned int len, const u8 *write_buf)
> +{
> + aspeed_spi_start_user(priv, flash);
> +
> + /* cmd buffer = cmd + addr */
> + aspeed_spi_send_cmd_addr(priv, flash, cmdbuf, cmdlen);
> + aspeed_spi_write_to_ahb(flash->ahb_base, write_buf, len);
> +
> + aspeed_spi_stop_user(priv, flash);
> + return 0;
> +}
> +
> +static u32 aspeed_spi_flash_to_addr(struct aspeed_spi_flash *flash,
> + const u8 *cmdbuf, unsigned int cmdlen)
> +{
> + /* cmd buffer = cmd + addr + dummies */
> + u8 addrlen = cmdlen - 1;
> + u32 addr = (cmdbuf[1] << 16) | (cmdbuf[2] << 8) | cmdbuf[3];
> +
> + /* U-Boot SPI Flash layer does not support 4BYTE address mode yet */
Are you sure? I did see some BAR stuff.
> + if (addrlen == 4)
> + addr = (addr << 8) | cmdbuf[4];
> +
> + return addr;
> +}
> +
> +/* TODO: add support for XFER_MMAP instead ? */
> +static ssize_t aspeed_spi_read(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash,
> + unsigned int cmdlen, const u8 *cmdbuf,
> + unsigned int len, u8 *read_buf)
> +{
> + u32 offset = aspeed_spi_flash_to_addr(flash, cmdbuf,
> + cmdlen - flash->spi->dummy_byte);
> +
> + /*
> + * Switch to USER command mode if the AHB window configured
> + * for the device is too small for the read operation
> + */
> + if (offset + len >= flash->ahb_size) {
> + return aspeed_spi_read_user(priv, flash, cmdlen, cmdbuf,
> + len, read_buf);
> + }
> +
> + memcpy_fromio(read_buf, flash->ahb_base + offset, len);
> + return 0;
> +}
> +
> +static int aspeed_spi_xfer(struct udevice *dev, unsigned int bitlen,
> + const void *dout, void *din, unsigned long flags)
> +{
> + struct udevice *bus = dev->parent;
> + struct aspeed_spi_priv *priv = dev_get_priv(bus);
> + struct aspeed_spi_flash *flash;
> + u8 *cmd_buf = priv->cmd_buf;
> + size_t data_bytes;
> + int err = 0;
> +
> + flash = aspeed_spi_get_flash(dev);
> + if (!flash)
> + return -ENODEV;
-ENXIO perhaps? There is already a device since struct udevice *dev is
valid. So you cannot return -ENODEV.
> +
> + if (flags & SPI_XFER_BEGIN) {
> + /* save command in progress */
> + priv->cmd_len = bitlen / 8;
> + memcpy(cmd_buf, dout, priv->cmd_len);
> + }
> +
> + if (flags == (SPI_XFER_BEGIN | SPI_XFER_END)) {
> + /* if start and end bit are set, the data bytes is 0. */
> + data_bytes = 0;
> + } else {
> + data_bytes = bitlen / 8;
> + }
> +
> + debug("CS%u: %s cmd %zu bytes data %zu bytes\n", flash->cs,
> + din ? "read" : "write", priv->cmd_len, data_bytes);
> +
> + if ((flags & SPI_XFER_END) || flags == 0) {
> + if (priv->cmd_len == 0) {
> + pr_err("No command is progress !\n");
> + return -1;
> + }
> +
> + if (din && data_bytes) {
> + if (priv->cmd_len == 1)
> + err = aspeed_spi_read_reg(priv, flash,
> + cmd_buf[0],
> + din, data_bytes);
> + else
> + err = aspeed_spi_read(priv, flash,
> + priv->cmd_len,
> + cmd_buf, data_bytes,
> + din);
> + } else if (dout) {
> + if (priv->cmd_len == 1)
> + err = aspeed_spi_write_reg(priv, flash,
> + cmd_buf[0],
> + dout, data_bytes);
> + else
> + err = aspeed_spi_write_user(priv, flash,
> + priv->cmd_len,
> + cmd_buf, data_bytes,
> + dout);
> + }
> +
> + if (flags & SPI_XFER_END) {
> + /* clear command */
> + memset(cmd_buf, 0, sizeof(priv->cmd_buf));
> + priv->cmd_len = 0;
> + }
> + }
> +
> + return err;
> +}
> +
> +static int aspeed_spi_child_pre_probe(struct udevice *dev)
> +{
> + struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
> +
> + debug("pre_probe slave device on CS%u, max_hz %u, mode 0x%x.\n",
> + slave_plat->cs, slave_plat->max_hz, slave_plat->mode);
> +
> + if (!aspeed_spi_get_flash(dev))
> + return -ENODEV;
-ENXIO, same below also
> +
> + return 0;
> +}
> +
> +/*
> + * It is possible to automatically define a contiguous address space
> + * on top of all CEs in the AHB window of the controller but it would
> + * require much more work. Let's start with a simple mapping scheme
> + * which should work fine for a single flash device.
> + *
> + * More complex schemes should probably be defined with the device
> + * tree.
> + */
> +static int aspeed_spi_flash_set_segment(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash)
> +{
> + u32 seg_addr;
> +
> + /* could be configured through the device tree */
> + flash->ahb_size = flash->spi->size;
> +
> + seg_addr = SEGMENT_ADDR_VALUE((u32)flash->ahb_base,
> + (u32)flash->ahb_base + flash->ahb_size);
> +
> + writel(seg_addr, &priv->regs->segment_addr[flash->cs]);
> + return 0;
> +}
> +
> +/*
> + * The Aspeed FMC controller automatically detects at boot time if a
> + * flash device is in 4BYTE address mode and self configures to use
> + * the appropriate address width. This can be a problem for the U-Boot
> + * SPI Flash layer which only supports 3 byte addresses. In such a
> + * case, a warning is emitted and the address width is fixed when sent
> + * on the bus.
> + */
> +static void aspeed_spi_flash_check_4b(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash)
> +{
> + if (readl(&priv->regs->ctrl) & BIT(flash->cs))
> + pr_err("CS%u: 4BYTE address mode is active\n", flash->cs);
> +}
> +
> +static int aspeed_spi_flash_init(struct aspeed_spi_priv *priv,
> + struct aspeed_spi_flash *flash,
> + struct udevice *dev)
> +{
> + struct spi_flash *spi_flash = dev_get_uclass_priv(dev);
> + struct spi_slave *slave = dev_get_parent_priv(dev);
> + u32 user_hclk;
> + u32 read_hclk;
> +
> + /*
> + * The SPI flash device slave should not change, so initialize
> + * it only once.
> + */
> + if (flash->init)
> + return 0;
Why does the init happen here?
> +
> + /*
> + * The flash device has not been probed yet. Initial transfers
> + * to read the JEDEC of the device will use the initial
> + * default settings of the registers.
> + */
> + if (!spi_flash->name)
> + return 0;
> +
> + debug("CS%u: init %s flags:%x size:%d page:%d sector:%d erase:%d "
> + "cmds [ erase:%x read=%x write:%x ] dummy:%d mmap:%p\n",
> + flash->cs,
> + spi_flash->name, spi_flash->flags, spi_flash->size,
> + spi_flash->page_size, spi_flash->sector_size,
> + spi_flash->erase_size, spi_flash->erase_cmd,
> + spi_flash->read_cmd, spi_flash->write_cmd,
> + spi_flash->dummy_byte, spi_flash->memory_map);
> +
> + flash->spi = spi_flash;
> +
> + /*
> + * Tune the CE Control Register values for the modes the
> + * driver will use:
> + * - USER command for specific SPI commands, write and
> + * erase.
> + * - FAST READ command mode for reads. The flash device is
> + * directly accessed through its AHB window.
> + *
> + * TODO: introduce a DT property for writes ?
TODO(email)
> + */
> + user_hclk = 0;
> +
> + flash->ce_ctrl_user = CE_CTRL_CLOCK_FREQ(user_hclk) |
> + CE_CTRL_USERMODE;
> +
> + read_hclk = aspeed_spi_hclk_divisor(priv->hclk_rate, slave->speed);
> +
> + if (slave->mode & (SPI_RX_DUAL | SPI_TX_DUAL)) {
> + debug("CS%u: setting dual data mode\n", flash->cs);
> + flash->iomode = CE_CTRL_IO_DUAL_DATA;
> + }
> +
> + flash->ce_ctrl_fread = CE_CTRL_CLOCK_FREQ(read_hclk) |
> + flash->iomode |
> + CE_CTRL_CMD(flash->spi->read_cmd) |
> + CE_CTRL_DUMMY(flash->spi->dummy_byte) |
> + CE_CTRL_FREADMODE;
> +
> + debug("CS%u: USER mode 0x%08x FREAD mode 0x%08x\n", flash->cs,
> + flash->ce_ctrl_user, flash->ce_ctrl_fread);
> +
> + /* Set the CE Control Register default (FAST READ) */
> + writel(flash->ce_ctrl_fread, &priv->regs->ce_ctrl[flash->cs]);
> +
> + /* Enable 4BYTE addresses */
> + if (flash->spi->size >= 16 << 20)
> + aspeed_spi_flash_check_4b(priv, flash);
> +
> + /* Set Address Segment Register for direct AHB accesses */
> + aspeed_spi_flash_set_segment(priv, flash);
> +
> + /* All done */
> + flash->init = true;
> + return 0;
> +}
> +
> +static int aspeed_spi_claim_bus(struct udevice *dev)
> +{
> + struct udevice *bus = dev->parent;
> + struct aspeed_spi_priv *priv = dev_get_priv(bus);
> + struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
> + struct aspeed_spi_flash *flash;
> +
> + debug("%s: claim bus CS%u\n", bus->name, slave_plat->cs);Casting a pointer to
> +
> + flash = aspeed_spi_get_flash(dev);
> + if (!flash)
> + return -ENODEV;
> +
> + return aspeed_spi_flash_init(priv, flash, dev);
> +}
> +
> +static int aspeed_spi_release_bus(struct udevice *dev)
> +{
> + struct udevice *bus = dev->parent;
> + struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev);
> +
> + debug("%s: release bus CS%u\n", bus->name, slave_plat->cs);
> +
> + if (!aspeed_spi_get_flash(dev))
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static int aspeed_spi_set_mode(struct udevice *bus, uint mode)
> +{
> + debug("%s: setting mode to %x\n", bus->name, mode);
> +
> + if (mode & (SPI_RX_QUAD | SPI_TX_QUAD)) {
> + pr_err("%s invalid QUAD IO mode\n", bus->name);
> + return -EINVAL;
> + }
> +
> + /* The CE Control Register is set in claim_bus() */
> + return 0;
> +}
> +
> +static int aspeed_spi_set_speed(struct udevice *bus, uint hz)
> +{
> + debug("%s: setting speed to %u\n", bus->name, hz);
> +
> + /* The CE Control Register is set in claim_bus() */
> + return 0;
> +}
> +
> +static int aspeed_spi_count_flash_devices(struct udevice *bus)
> +{
> + ofnode node;
> + int count = 0;
> +
> + dev_for_each_subnode(node, bus) {
> + if (ofnode_is_available(node) &&
> + ofnode_device_is_compatible(node, "spi-flash"))
> + count++;
> + }
> +
> + return count;
> +}
> +
> +static int aspeed_spi_bind(struct udevice *bus)
> +{
> + debug("%s assigned req_seq=%d seq=%d\n", bus->name, bus->req_seq,
> + bus->seq);
> + return 0;
> +}
> +
> +static int aspeed_spi_probe(struct udevice *bus)
> +{
> + struct resource res_regs, res_ahb;
> + struct aspeed_spi_priv *priv = dev_get_priv(bus);
> + struct clk hclk;
> + int ret;
> +
> + ret = dev_read_resource(bus, 0, &res_regs);
> + if (ret < 0)
> + return ret;
> +
> + priv->regs = (void __iomem *)res_regs.start;
> +
> + ret = dev_read_resource(bus, 1, &res_ahb);
> + if (ret < 0)
> + return ret;
> +
> + priv->ahb_base = (void __iomem *)res_ahb.start;
> + priv->ahb_size = res_ahb.end - res_ahb.start;
> +
> + ret = clk_get_by_index(bus, 0, &hclk);
> + if (ret < 0) {
> + pr_err("%s could not get clock: %d\n", bus->name, ret);
> + return ret;
> + }
> +
> + priv->hclk_rate = clk_get_rate(&hclk);
> + clk_free(&hclk);
> +
> + priv->max_hz = dev_read_u32_default(bus, "spi-max-frequency",
> + 100000000);
> +
> + priv->num_cs = dev_read_u32_default(bus, "num-cs", ASPEED_SPI_MAX_CS);
> +
> + priv->flash_count = aspeed_spi_count_flash_devices(bus);
> + if (priv->flash_count > priv->num_cs) {
> + pr_err("%s has too many flash devices: %d\n", bus->name,
> + priv->flash_count);
> + return -EINVAL;
> + }
> +
> + if (!priv->flash_count) {
> + pr_err("%s has no flash devices ?!\n", bus->name);
> + return -ENODEV;
> + }
> +
> + /*
> + * There are some slight differences between the FMC and the
> + * SPI controllers
> + */
> + priv->is_fmc = device_is_compatible(bus, "aspeed,ast2500-fmc");
> +
> + ret = aspeed_spi_controller_init(priv);
> + if (ret)
> + return ret;
> +
> + debug("%s probed regs=%p ahb_base=%p max-hz=%d cs=%d seq=%d\n",
> + bus->name, priv->regs, priv->ahb_base, priv->max_hz,
> + priv->flash_count, bus->seq);
> +
> + return 0;
> +}
> +
> +static const struct dm_spi_ops aspeed_spi_ops = {
> + .claim_bus = aspeed_spi_claim_bus,
> + .release_bus = aspeed_spi_release_bus,
> + .set_mode = aspeed_spi_set_mode,
> + .set_speed = aspeed_spi_set_speed,
> + .xfer = aspeed_spi_xfer,
> +};
> +
> +static const struct udevice_id aspeed_spi_ids[] = {
> + { .compatible = "aspeed,ast2500-fmc" },
> + { .compatible = "aspeed,ast2500-spi" },
> + { }
> +};
> +
> +U_BOOT_DRIVER(aspeed_spi) = {
> + .name = "aspeed_spi",
> + .id = UCLASS_SPI,
> + .of_match = aspeed_spi_ids,
> + .ops = &aspeed_spi_ops,
> + .priv_auto_alloc_size = sizeof(struct aspeed_spi_priv),
> + .child_pre_probe = aspeed_spi_child_pre_probe,
> + .bind = aspeed_spi_bind,
> + .probe = aspeed_spi_probe,
> +};
This is a SPI driver so it should implement the SPI interface. It
should not be messing with SPI flash things.
I have a hard time understanding why this driver knows about SPI flash devices?
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index dcd719ff0ac6..fd5e930ec318 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -26,6 +26,14 @@ config ALTERA_SPI
> IP core. Please find details on the "Embedded Peripherals IP
> User Guide" of Altera.
>
> +config ASPEED_SPI
> + bool "Aspeed SPI driver"
> + default y if ARCH_ASPEED
> + help
> + Enable the Aspeed AST2500 FMC/SPI driver. This driver can be
> + used to access the SPI NOR flash on boards using the Aspeed
> + AST2500 SoC, such as the POWER9 OpenPOWER platforms
What is FMC? Can you spell that one out?
> +
> config ATCSPI200_SPI
> bool "Andestech ATCSPI200 SPI driver"
> help
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 728e30c5383c..40d224130ea5 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -14,6 +14,7 @@ obj-$(CONFIG_SOFT_SPI) += soft_spi_legacy.o
> endif
>
> obj-$(CONFIG_ALTERA_SPI) += altera_spi.o
> +obj-$(CONFIG_ASPEED_SPI) += aspeed_spi.o
> obj-$(CONFIG_ATH79_SPI) += ath79_spi.o
> obj-$(CONFIG_ATMEL_SPI) += atmel_spi.o
> obj-$(CONFIG_BCM63XX_HSSPI) += bcm63xx_hsspi.o
> --
> 2.17.1
>
> _______________________________________________
> U-Boot mailing list
> U-Boot at lists.denx.de
> https://lists.denx.de/listinfo/u-boot
Regards,
Simon
More information about the U-Boot
mailing list