[v3 4/4] spi: aspeed: Add ASPEED FMC/SPI memory controller driver
Cédric Le Goater
clg at kaod.org
Fri Nov 6 01:09:11 AEDT 2020
Hello Chin-Ting,
Thanks for this driver. It's much cleaner than the previous and we should
try adding support for the AST2500 SoC also. I guess we can keep the old
driver for the AST2400 which has a different register layout.
On the patchset, I think we should split this patch in three :
- basic support
- AHB window calculation depending on the flash size
- read training support
We should avoid magic values when setting registers. This is confusing
and defines are much better.
AST2500 support will be a bit challenging because HW does not allow
to configure a 128MB AHB window, max is 120MB This is a bug and the work
around is to use user mode for the remaining 8MB. Something to keep in
mind.
I gave it a try on QEMU. It looks good. When I can revive my EVB, I will
do the same.
More comments below,
Thanks,
C.
On 11/5/20 1:03 PM, Chin-Ting Kuo wrote:
> Add driver for ASPEED BMC FMC/SPI memory controller which
> supports spi-mem interface.
>
> There are three SPI memory controllers embedded in an ASPEED SoC.
> Each of them can connect to two or three SPI NOR flashes. The first
> SPI memory controller is also named as Firmware Memory Controller (FMC),
> which is similar to SPI memory controller. After device AC on, MCU ROM
> can fetch device boot code from FMC CS 0. Thus, there exists additional
> registers for boot process control in FMC.
>
> ASPEED SPI memory controller supports single, dual and quad mode for
> SPI NOR flash. It also supports two types of command mode, user mode
> and command read/write mode. User mode is traditional pure SPI operations
> where all transmission is controlled by CPU. Contrarily, with command
> read/write mode, SPI controller can send command and address automatically
> when CPU read/write related remapped address.
>
> Besides, different wafer processes of SPI NOR flash result in different
> signal response time. This phenomenon will be enlarged when SPI clock
> frequency increases. ASPEED SPI memory controller provides a mechanism
> for timing compensation in order to satisfy various SPI NOR flash parts
> and PCB layout.
>
> Signed-off-by: Chin-Ting Kuo <chin-ting_kuo at aspeedtech.com>
> ---
> v2: Fix sparse warnings reported by kernel test robot <lkp at intel.com>.
> v3: Fix build warnings with x86 allmodconfig.
>
> drivers/spi/Kconfig | 10 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi-aspeed.c | 969 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 980 insertions(+)
> create mode 100644 drivers/spi/spi-aspeed.c
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 5cff60de8e83..c848f2f7b694 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -70,6 +70,16 @@ config SPI_AR934X
> This enables support for the SPI controller present on the
> Qualcomm Atheros AR934X/QCA95XX SoCs.
>
> +config SPI_ASPEED
> + tristate "ASPEED FMC/SPI Memory Controller"
> + depends on OF && HAS_IOMEM && (ARCH_ASPEED || COMPILE_TEST)
We will need to do something about the other driver. For the moment,
we can select both but that won't be the case anymore when we add
AST2500 support.
> + help
> + Enable driver for ASPEED FMC/SPI Memory Controller.
> +
> + This driver is not a generic pure SPI driver, which
> + is especially designed for spi-mem framework with
> + SPI NOR flash direct read and write features.
> +
> config SPI_ATH79
> tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver"
> depends on ATH79 || COMPILE_TEST
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 6fea5821662e..9e62c650fca0 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -17,6 +17,7 @@ obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
> obj-$(CONFIG_SPI_ALTERA) += spi-altera.o
> obj-$(CONFIG_SPI_AR934X) += spi-ar934x.o
> obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o
> +obj-$(CONFIG_SPI_ASPEED) += spi-aspeed.o
> obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o
> obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o
> obj-$(CONFIG_SPI_AT91_USART) += spi-at91-usart.o
> diff --git a/drivers/spi/spi-aspeed.c b/drivers/spi/spi-aspeed.c
> new file mode 100644
> index 000000000000..cfaaa0d5bac6
> --- /dev/null
> +++ b/drivers/spi/spi-aspeed.c
> @@ -0,0 +1,969 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +
> +/*
> + * ASPEED FMC/SPI Memory Controller Driver
> + *
> + * Copyright (c) 2020, ASPEED Corporation.
> + * Copyright (c) 2015-2016, IBM Corporation.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/sizes.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/spi-mem.h>
> +
> +/* ASPEED FMC/SPI memory control register related */
> +#define OFFSET_CE_TYPE_SETTING 0x00
> +#define OFFSET_CE_ADDR_MODE_CTRL 0x04
> +#define OFFSET_INTR_CTRL_STATUS 0x08
> +#define OFFSET_ADDR_DATA_MASK 0x0c
> +#define OFFSET_CE0_CTRL_REG 0x10
> +#define OFFSET_CE0_DECODE_RANGE_REG 0x30
> +#define OFFSET_DMA_CTRL 0x80
> +#define OFFSET_DMA_FLASH_ADDR_REG 0x84
> +#define OFFSET_DMA_RAM_ADDR_REG 0x88
> +#define OFFSET_DMA_LEN_REG 0x8c
> +#define OFFSET_DMA_CHECKSUM_RESULT 0x90
> +#define OFFSET_CE0_TIMING_COMPENSATION 0x94
> +
> +#define CTRL_IO_SINGLE_DATA 0
> +#define CTRL_IO_DUAL_DATA BIT(29)
> +#define CTRL_IO_QUAD_DATA BIT(30)
> +
> +#define CTRL_IO_MODE_USER GENMASK(1, 0)
> +#define CTRL_IO_MODE_CMD_READ BIT(0)
> +#define CTRL_IO_MODE_CMD_WRITE BIT(1)
> +#define CTRL_STOP_ACTIVE BIT(2)
> +
> +#define CALIBRATION_LEN 0x400
> +#define SPI_DAM_REQUEST BIT(31)
> +#define SPI_DAM_GRANT BIT(30)
What are these bits ? There is no documentation for them.
> +#define SPI_DMA_CALIB_MODE BIT(3)
> +#define SPI_DMA_CALC_CKSUM BIT(2)
> +#define SPI_DMA_ENABLE BIT(0)
> +#define SPI_DMA_STATUS BIT(11)
> +
> +enum aspeed_spi_ctl_reg_value {
> + ASPEED_SPI_BASE,
> + ASPEED_SPI_READ,
> + ASPEED_SPI_WRITE,
> + ASPEED_SPI_MAX,
> +};
> +
> +#define ASPEED_SPI_MAX_CS 5
> +
> +struct aspeed_spi_controller;
> +struct aspeed_spi_chip;
> +
> +struct aspeed_spi_info {
> + uint32_t cmd_io_ctrl_mask;
> + uint32_t max_data_bus_width;
> + uint32_t min_decode_sz;
> + void (*set_4byte)(struct aspeed_spi_controller *ast_ctrl, uint32_t cs);
> + int (*calibrate)(struct aspeed_spi_controller *ast_ctrl, uint32_t cs);
> + void (*adjust_decode_sz)(uint32_t decode_sz_arr[], int len);
> + uint32_t (*segment_start)(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t reg);
> + uint32_t (*segment_end)(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t reg);
> + uint32_t (*segment_reg)(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t start, uint32_t end);
> +};
> +
> +struct aspeed_spi_chip {
> + void __iomem *ahb_base;
> + phys_addr_t ahb_base_phy;
> + uint32_t ahb_window_sz;
> + uint32_t ctrl_val[ASPEED_SPI_MAX];
> + uint32_t max_clk_freq;
> +};
> +
> +struct aspeed_spi_controller {
> + struct device *dev;
> + const struct aspeed_spi_info *info; /* controller info */
> + void __iomem *regs; /* controller registers */
> + void __iomem *ahb_base;
> + phys_addr_t ahb_base_phy; /* physical addr of AHB window */
> + uint32_t ahb_window_sz; /* AHB window size */
> + uint32_t num_cs;
> + uint64_t ahb_clk;
> + struct aspeed_spi_chip *chips; /* pointers to attached chips */
> +};
> +
> +static uint32_t
> +aspeed_2600_spi_segment_start(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t reg)
> +{
> + uint32_t start_offset = (reg << 16) & 0x0ff00000;
> +
> + return (uint32_t)(ast_ctrl->ahb_base_phy + start_offset);
> +}
> +
> +static uint32_t
> +aspeed_2600_spi_segment_end(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t reg)
> +{
> + uint32_t end_offset = reg & 0x0ff00000;
> +
> + /* no decode range, set to physical ahb base */
> + if (end_offset == 0)
> + return ast_ctrl->ahb_base_phy;
> +
> + return (uint32_t)(ast_ctrl->ahb_base_phy + end_offset + 0x100000);
> +}
> +
> +static uint32_t
> +aspeed_2600_spi_segment_reg(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t start, uint32_t end)
> +{
> + /* no decode range, assign zero value */
> + if (start == end)
> + return 0;
> +
> + return ((start & 0x0ff00000) >> 16) | ((end - 0x100000) & 0x0ff00000);
> +}
> +
> +static void aspeed_spi_chip_set_4byte(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t cs)
> +{
> + uint32_t reg_val;
> +
> + reg_val = readl(ast_ctrl->regs + OFFSET_CE_ADDR_MODE_CTRL);
> + reg_val |= 0x11 << cs;
> + writel(reg_val, ast_ctrl->regs + OFFSET_CE_ADDR_MODE_CTRL);
> +}
> +
> +static uint32_t aspeed_spi_get_io_mode(uint32_t bus_width)
> +{
> + switch (bus_width) {
> + case 1:
> + return CTRL_IO_SINGLE_DATA;
> + case 2:
> + return CTRL_IO_DUAL_DATA;
> + case 4:
> + return CTRL_IO_QUAD_DATA;
> + default:
> + return CTRL_IO_SINGLE_DATA;
> + }
> +}
> +
> +/*
> + * Check whether the data is not all 0 or 1 in order to
> + * avoid calibriate umount spi-flash.
> + */
> +static bool aspeed_spi_calibriation_enable(const uint8_t *buf, uint32_t sz)
> +{
> + const uint32_t *buf_32 = (const uint32_t *)buf;
> + uint32_t i;
> + uint32_t valid_count = 0;
> +
> + for (i = 0; i < (sz / 4); i++) {
> + if (buf_32[i] != 0 && buf_32[i] != 0xffffffff)
> + valid_count++;
> + if (valid_count > 100)
> + return true;
> + }
> + return false;
> +}
> +
> +static uint32_t
> +aspeed_2600_spi_dma_checksum(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t cs, uint32_t div, uint32_t delay)
> +{
> + uint32_t ctrl_val;
> + uint32_t checksum;
> +
> + writel(0xaeed0000, ast_ctrl->regs + OFFSET_DMA_CTRL);
You should use define for the magic value above ^
> + if (readl(ast_ctrl->regs + OFFSET_DMA_CTRL) & SPI_DAM_REQUEST) {
> + while (!(readl(ast_ctrl->regs + OFFSET_DMA_CTRL) &
> + SPI_DAM_GRANT))
> + ;
> + }
What are these DAM bits for ?
I don't think the AST2500 SPI controllers supports DMA. It would be better to
use a common method.
> +
> + writel((uint32_t)ast_ctrl->chips[cs].ahb_base_phy,
> + ast_ctrl->regs + OFFSET_DMA_FLASH_ADDR_REG);
> + writel(CALIBRATION_LEN, ast_ctrl->regs + OFFSET_DMA_LEN_REG);
> +
> + ctrl_val = SPI_DMA_ENABLE | SPI_DMA_CALC_CKSUM | SPI_DMA_CALIB_MODE |
> + (delay << 8) | ((div & 0xf) << 16);
> + writel(ctrl_val, ast_ctrl->regs + OFFSET_DMA_CTRL);
> + while (!(readl(ast_ctrl->regs + OFFSET_INTR_CTRL_STATUS) &
> + SPI_DMA_STATUS))
> + ;
> +
> + checksum = readl(ast_ctrl->regs + OFFSET_DMA_CHECKSUM_RESULT);
> +
> + writel(0xdeea0000, ast_ctrl->regs + OFFSET_DMA_CTRL);
> + writel(0x0, ast_ctrl->regs + OFFSET_DMA_CTRL);
> +
> + return checksum;
> +}
> +
> +static int get_mid_point_of_longest_one(uint8_t *buf, uint32_t len)
> +{
> + int i;
> + int start = 0, mid_point = 0;
> + int max_cnt = 0, cnt = 0;
> +
> + for (i = 0; i < len; i++) {
> + if (buf[i] == 1) {
> + cnt++;
> + } else {
> + cnt = 0;
> + start = i;
> + }
> +
> + if (max_cnt < cnt) {
> + max_cnt = cnt;
> + mid_point = start + (cnt / 2);
> + }
> + }
> +
> + /*
> + * In order to get a stable SPI read timing,
> + * abandon the result if the length of longest
> + * consecutive good points is too short.
> + */
> + if (max_cnt < 4)
> + return -1;
> +
> + return mid_point;
> +}
> +
> +/* Transfer maximum clock frequency to register setting */
> +static uint32_t
> +aspeed_2600_spi_clk_basic_setting(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t *max_clk)
> +{
> + struct device *dev = ast_ctrl->dev;
> + uint32_t hclk_clk = ast_ctrl->ahb_clk;
> + uint32_t hclk_div = 0x400; /* default value */
> + uint32_t i, j = 0;
> + bool found = false;
> + /* HCLK/1 .. HCLK/16 */
> + uint32_t hclk_masks[] = { 15, 7, 14, 6, 13, 5, 12, 4,
> + 11, 3, 10, 2, 9, 1, 8, 0 };
> +
> + /* FMC/SPIR10[27:24] */
> + for (j = 0; j < 0xf; i++) {
> + /* FMC/SPIR10[11:8] */
> + for (i = 0; i < ARRAY_SIZE(hclk_masks); i++) {
> + if (i == 0 && j == 0)
> + continue;
> +
> + if (hclk_clk / (i + 1 + (j * 16)) <= *max_clk) {
> + found = 1;
> + *max_clk = hclk_clk / (i + 1 + (j * 16));
> + break;
> + }
> + }
> +
> + if (found) {
> + hclk_div = ((j << 24) | hclk_masks[i] << 8);
> + break;
> + }
> + }
> +
> + dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", found ? "yes" : "no",
> + hclk_clk, *max_clk);
> + dev_dbg(dev, "base_clk: %d, h_div: %d (mask %x), speed: %d\n", j, i + 1,
> + hclk_masks[i], hclk_clk / (i + 1 + j * 16));
> +
> + return hclk_div;
> +}
> +
> +/*
> + * If SPI frequency is too high, timing compensation is needed,
> + * otherwise, SPI controller will sample unready data. For AST2600
> + * SPI memory controller, only the first four frequency levels
> + * (HCLK/2, HCLK/3,..., HCKL/5) may need timing compensation.
> + * Here, for each frequency, we will get a sequence of reading
> + * result (pass or fail) compared to golden data. Then, getting the
> + * middle point of the maximum pass widow. Besides, if the flash's
> + * content is too monotonous, the frequency recorded in the device
> + * tree will be adopted.
> + */
> +static int
> +aspeed_2600_spi_timing_calibration(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t cs)
> +{
> + int ret = 0;
> + struct device *dev = ast_ctrl->dev;
> + struct aspeed_spi_chip *chip = &ast_ctrl->chips[cs];
> + uint32_t max_freq = chip->max_clk_freq;
> + /* HCLK/2, ..., HCKL/5 */
> + uint32_t hclk_masks[] = { 7, 14, 6, 13 };
> + uint8_t *calib_res = NULL;
> + uint8_t *check_buf = NULL;
> + uint32_t reg_val;
> + uint32_t checksum, gold_checksum;
> + uint32_t i, hcycle, delay_ns, final_delay = 0;
> + uint32_t hclk_div;
> + bool pass;
> + int calib_point;
> +
> + reg_val =
> + readl(ast_ctrl->regs + OFFSET_CE0_TIMING_COMPENSATION + cs * 4);
> + if (reg_val != 0) {
> + dev_dbg(dev, "has executed calibration.\n");
> + goto no_calib;
> + }
> +
> + dev_dbg(dev, "calculate timing compensation :\n");
> + /*
> + * use the related low frequency to get check calibration data
> + * and get golden data.
> + */
> + reg_val = chip->ctrl_val[ASPEED_SPI_READ] & 0xf0fff0ff;
> + writel(reg_val, ast_ctrl->regs + OFFSET_CE0_CTRL_REG + cs * 4);
> +
> + check_buf = kzalloc(CALIBRATION_LEN, GFP_KERNEL);
> + if (!check_buf)
> + return -ENOMEM;
> +
> + memcpy_fromio(check_buf, chip->ahb_base, CALIBRATION_LEN);
> + if (!aspeed_spi_calibriation_enable(check_buf, CALIBRATION_LEN)) {
> + dev_info(dev, "flash data is monotonous, skip calibration.");
> + goto no_calib;
> + }
> +
> + gold_checksum = aspeed_2600_spi_dma_checksum(ast_ctrl, cs, 0, 0);
> +
> + /*
> + * allocate a space to record calibration result for
> + * different timing compensation with fixed
> + * HCLK division.
> + */
> + calib_res = kzalloc(6 * 17, GFP_KERNEL);
> + if (!calib_res) {
> + ret = -ENOMEM;
> + goto no_calib;
> + }
> +
> + /* From HCLK/2 to HCLK/5 */
> + for (i = 0; i < ARRAY_SIZE(hclk_masks); i++) {
> + if (max_freq < (uint32_t)ast_ctrl->ahb_clk / (i + 2)) {
> + dev_dbg(dev, "skipping freq %d\n",
> + (uint32_t)ast_ctrl->ahb_clk / (i + 2));
> + continue;
> + }
> + max_freq = (uint32_t)ast_ctrl->ahb_clk / (i + 2);
> +
> + checksum = aspeed_2600_spi_dma_checksum(ast_ctrl, cs,
> + hclk_masks[i], 0);
> + pass = (checksum == gold_checksum);
> + dev_dbg(dev, "HCLK/%d, no timing compensation: %s\n", i + 2,
> + pass ? "PASS" : "FAIL");
> +
> + if (pass)
> + break;
> +
> + memset(calib_res, 0x0, 6 * 17);
> +
> + for (hcycle = 0; hcycle <= 5; hcycle++) {
> + /* increase DI delay by the step of 0.5ns */
> + dev_dbg(dev, "Delay Enable : hcycle %x\n", hcycle);
> + for (delay_ns = 0; delay_ns <= 0xf; delay_ns++) {
> + checksum = aspeed_2600_spi_dma_checksum(
> + ast_ctrl, cs, hclk_masks[i],
> + BIT(3) | hcycle | (delay_ns << 4));
> + pass = (checksum == gold_checksum);
> + calib_res[hcycle * 17 + delay_ns] = pass;
> + dev_dbg(dev,
> + "HCLK/%d, %d HCLK cycle, %d delay_ns : %s\n",
> + i + 2, hcycle, delay_ns,
> + pass ? "PASS" : "FAIL");
> + }
> + }
> +
> + calib_point = get_mid_point_of_longest_one(calib_res, 6 * 17);
> + if (calib_point < 0) {
> + dev_info(dev, "cannot get good calibration point.\n");
> + continue;
> + }
> +
> + hcycle = calib_point / 17;
> + delay_ns = calib_point % 17;
> + dev_dbg(dev, "final hcycle: %d, delay_ns: %d\n", hcycle,
> + delay_ns);
> +
> + final_delay = (BIT(3) | hcycle | (delay_ns << 4)) << (i * 8);
> + writel(final_delay, ast_ctrl->regs +
> + OFFSET_CE0_TIMING_COMPENSATION +
> + cs * 4);
> + break;
> + }
> +
> +no_calib:
> +
> + hclk_div = aspeed_2600_spi_clk_basic_setting(ast_ctrl, &max_freq);
> +
> + /* configure SPI clock frequency */
> + reg_val = readl(ast_ctrl->regs + OFFSET_CE0_CTRL_REG + cs * 4);
> + reg_val = (reg_val & 0xf0fff0ff) | hclk_div;
> + writel(reg_val, ast_ctrl->regs + OFFSET_CE0_CTRL_REG + cs * 4);
> +
> + /* add clock setting info for CE ctrl setting */
> + for (i = 0; i < ASPEED_SPI_MAX; i++)
> + chip->ctrl_val[i] = (chip->ctrl_val[i] & 0xf0fff0ff) | hclk_div;
> +
> + dev_info(dev, "freq: %dMHz\n", max_freq / 1000000);
> +
> + kfree(check_buf);
> + kfree(calib_res);
> +
> + return ret;
> +}
> +
> +/*
> + * AST2600 SPI memory controllers support multiple chip selects.
> + * The start address of a decode range should be multiple
> + * of its related flash size. Namely, the total decoded size
> + * from flash 0 to flash N should be multiple of flash (N + 1).
> + */
> +static void aspeed_2600_adjust_decode_sz(uint32_t decode_sz_arr[], int len)
> +{
> + int cs, j;
> + uint32_t sz;
> +
> + for (cs = len - 1; cs >= 0; cs--) {
> + sz = 0;
> + for (j = 0; j < cs; j++)
> + sz += decode_sz_arr[j];
> +
> + if (sz % decode_sz_arr[cs] != 0)
> + decode_sz_arr[0] += (sz % decode_sz_arr[cs]);
> + }
> +}
> +
> +static int
> +aspeed_spi_decode_range_config(struct aspeed_spi_controller *ast_ctrl,
> + uint32_t decode_sz_arr[])
> +{
> + struct aspeed_spi_chip *chip = ast_ctrl->chips;
> + uint32_t i;
> + uint32_t cs;
> + uint32_t decode_reg_val;
> + phys_addr_t start_addr_phy, end_addr_phy, pre_end_addr_phy = 0;
> + uint32_t total_decode_sz = 0;
> +
> + /* decode range sanity */
> + for (cs = 0; cs < ast_ctrl->num_cs; cs++) {
> + total_decode_sz += decode_sz_arr[cs];
> + if (ast_ctrl->ahb_window_sz < total_decode_sz) {
> + dev_err(ast_ctrl->dev, "insufficient decode size\n");
> + for (i = 0; i <= cs; i++)
> + dev_err(ast_ctrl->dev, "cs:%d %x\n", i,
> + decode_sz_arr[i]);
> + return -ENOSPC;
> + }
> + }
> +
> + for (cs = 0; cs < ast_ctrl->num_cs; cs++) {
> + if (chip[cs].ahb_base)
> + devm_iounmap(ast_ctrl->dev, chip[cs].ahb_base);
> + }
> +
> + /* configure each CE's decode range */
> + for (cs = 0; cs < ast_ctrl->num_cs; cs++) {
> + if (cs == 0)
> + start_addr_phy = ast_ctrl->ahb_base_phy;
> + else
> + start_addr_phy = pre_end_addr_phy;
> +
> + chip[cs].ahb_base = devm_ioremap(ast_ctrl->dev, start_addr_phy,
> + decode_sz_arr[cs]);
> + chip[cs].ahb_base_phy = start_addr_phy;
> +
> + chip[cs].ahb_window_sz = decode_sz_arr[cs];
> + end_addr_phy = start_addr_phy + decode_sz_arr[cs];
> +
> + decode_reg_val = ast_ctrl->info->segment_reg(
> + ast_ctrl, start_addr_phy, end_addr_phy);
> +
> + writel(decode_reg_val,
> + ast_ctrl->regs + OFFSET_CE0_DECODE_RANGE_REG + cs * 4);
> +
> + pre_end_addr_phy = end_addr_phy;
> +
> + dev_dbg(ast_ctrl->dev, "cs: %d, decode_reg: 0x%x\n", cs,
> + decode_reg_val);
> + }
> +
> + return 0;
> +}
> +
> +static const struct aspeed_spi_info ast2600_spi_info = {
> + .max_data_bus_width = 4,
> + .cmd_io_ctrl_mask = 0xf0ff40c3,
> + /* for ast2600, the minimum decode size for each CE is 2MB */
> + .min_decode_sz = 0x200000,
> + .set_4byte = aspeed_spi_chip_set_4byte,
> + .calibrate = aspeed_2600_spi_timing_calibration,
> + .adjust_decode_sz = aspeed_2600_adjust_decode_sz,
> + .segment_start = aspeed_2600_spi_segment_start,
> + .segment_end = aspeed_2600_spi_segment_end,
> + .segment_reg = aspeed_2600_spi_segment_reg,
> +};
> +
> +/*
> + * If the slave device is SPI NOR flash, there are two types
> + * of command mode for ASPEED SPI memory controller used to
> + * transfer data. The first one is user mode and the other is
> + * command read/write mode. With user mode, SPI NOR flash
> + * command, address and data processes are all handled by CPU.
> + * But, when address filter is enabled to protect some flash
> + * regions from being written, user mode will be disabled.
> + * Thus, here, we use command read/write mode to issue SPI
> + * operations. After remapping flash space correctly, we can
> + * easily read/write data to flash by reading or writing
> + * related remapped address, then, SPI NOR flash command and
> + * address will be transferred to flash by controller
> + * automatically. Besides, ASPEED SPI memory controller can
> + * also block address or data bytes by configure FMC0C/SPIR0C
> + * address and data mask register in order to satisfy the
> + * following SPI flash operation sequences: (command) only,
> + * (command and address) only or (coommand and data) only.
> + */
> +static int aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
> +{
> + struct aspeed_spi_controller *ast_ctrl =
> + spi_controller_get_devdata(mem->spi->master);
> + struct device *dev = ast_ctrl->dev;
> + uint32_t cs = mem->spi->chip_select;
> + struct aspeed_spi_chip *chip = &ast_ctrl->chips[cs];
> + uint32_t ctrl_val;
> + uint32_t addr_mode_reg, addr_mode_reg_backup;
> + uint32_t addr_data_mask = 0;
> + void __iomem *op_addr;
> + const void *data_buf;
> + uint32_t data_byte = 0;
> + uint32_t dummy_data = 0;
> +
> + dev_dbg(dev, "cmd:%x(%d),addr:%llx(%d),dummy:%d(%d),data_len:%x(%d)\n",
> + op->cmd.opcode, op->cmd.buswidth, op->addr.val,
> + op->addr.buswidth, op->dummy.nbytes, op->dummy.buswidth,
> + op->data.nbytes, op->data.buswidth);
> +
> + addr_mode_reg = addr_mode_reg_backup =
> + readl(ast_ctrl->regs + OFFSET_CE_ADDR_MODE_CTRL);
> + addr_data_mask = readl(ast_ctrl->regs + OFFSET_ADDR_DATA_MASK);
> +
> + ctrl_val = chip->ctrl_val[ASPEED_SPI_BASE];
> + ctrl_val &= ~ast_ctrl->info->cmd_io_ctrl_mask;
> +
> + /* configure opcode */
> + ctrl_val |= op->cmd.opcode << 16;
> +
> + /* configure operation address, address length and address mask */
> + if (op->addr.nbytes != 0) {
> + if (op->addr.nbytes == 3)
> + addr_mode_reg &= ~(0x11 << cs);
> + else
> + addr_mode_reg |= (0x11 << cs);
please use define.
> +
> + addr_data_mask &= 0x0f;
> + op_addr = chip->ahb_base + op->addr.val;
> + } else {
> + addr_data_mask |= 0xf0;
Disabling the address lanes needs an explanation.
> + op_addr = chip->ahb_base;
> + }
> +
> + if (op->dummy.nbytes != 0) {
> + ctrl_val |= ((op->dummy.nbytes & 0x3) << 6 |
> + (op->dummy.nbytes & 0x4) << 14);
> + }
> +
> + /* configure data io mode and data mask */
> + if (op->data.nbytes != 0) {
> + addr_data_mask &= 0xF0;
> + data_byte = op->data.nbytes;
> + if (op->data.dir == SPI_MEM_DATA_OUT)
> + data_buf = op->data.buf.out;
> + else
> + data_buf = op->data.buf.in;
> +
> + if (op->data.buswidth)
> + ctrl_val |= aspeed_spi_get_io_mode(op->data.buswidth);
> +
> + } else {
> + addr_data_mask |= 0x0f;
> + data_byte = 1;
> + data_buf = &dummy_data;
> + }
> +
> + /* configure command mode */
> + if (op->data.dir == SPI_MEM_DATA_OUT)
> + ctrl_val |= CTRL_IO_MODE_CMD_WRITE;
> + else
> + ctrl_val |= CTRL_IO_MODE_CMD_READ;
> +
> + /* set controller registers */
> + writel(ctrl_val, ast_ctrl->regs + OFFSET_CE0_CTRL_REG + cs * 4);
> + writel(addr_mode_reg, ast_ctrl->regs + OFFSET_CE_ADDR_MODE_CTRL);
> + writel(addr_data_mask, ast_ctrl->regs + OFFSET_ADDR_DATA_MASK);
> +
> + dev_dbg(dev, "ctrl: 0x%08x, addr_mode: 0x%x, mask: 0x%x, addr:%px\n",
> + ctrl_val, addr_mode_reg, addr_data_mask, op_addr);
> +
> + /* trigger spi transmission or reception sequence */
> + if (op->data.dir == SPI_MEM_DATA_OUT)
> + memcpy_toio(op_addr, data_buf, data_byte);
> + else
> + memcpy_fromio((void *)data_buf, op_addr, data_byte);
> +
> + /* restore controller setting */
> + writel(chip->ctrl_val[ASPEED_SPI_READ],
> + ast_ctrl->regs + OFFSET_CE0_CTRL_REG + cs * 4);
> + writel(addr_mode_reg_backup, ast_ctrl->regs + OFFSET_CE_ADDR_MODE_CTRL);
> + writel(0x0, ast_ctrl->regs + OFFSET_ADDR_DATA_MASK);
> +
> + return 0;
> +}
> +
> +static ssize_t aspeed_spi_dirmap_read(struct spi_mem_dirmap_desc *desc,
> + uint64_t offs, size_t len, void *buf)
> +{
> + struct aspeed_spi_controller *ast_ctrl =
> + spi_controller_get_devdata(desc->mem->spi->master);
> + struct aspeed_spi_chip *chip =
> + &ast_ctrl->chips[desc->mem->spi->chip_select];
> + struct spi_mem_op op_tmpl = desc->info.op_tmpl;
> +
> + if (chip->ahb_window_sz < offs + len) {
> + dev_info(ast_ctrl->dev,
> + "read range exceeds flash remapping size\n");
> + return 0;
> + }
> +
> + dev_dbg(ast_ctrl->dev, "read op:0x%x, addr:0x%llx, len:0x%zx\n",
> + op_tmpl.cmd.opcode, offs, len);
> +
> + memcpy_fromio(buf, chip->ahb_base + offs, len);
> +
> + return len;
> +}
> +
> +static ssize_t aspeed_spi_dirmap_write(struct spi_mem_dirmap_desc *desc,
> + uint64_t offs, size_t len, const void *buf)
> +{
> + struct aspeed_spi_controller *ast_ctrl =
> + spi_controller_get_devdata(desc->mem->spi->master);
> + struct aspeed_spi_chip *chip =
> + &ast_ctrl->chips[desc->mem->spi->chip_select];
> + uint32_t reg_val;
> + uint32_t target_cs = desc->mem->spi->chip_select;
> + struct spi_mem_op op_tmpl = desc->info.op_tmpl;
> +
> + if (chip->ahb_window_sz < offs + len) {
> + dev_info(ast_ctrl->dev,
> + "write range exceeds flash remapping size\n");
> + return 0;
> + }
> +
> + dev_dbg(ast_ctrl->dev, "write op:0x%x, addr:0x%llx, len:0x%zx\n",
> + op_tmpl.cmd.opcode, offs, len);
> +
> + reg_val = ast_ctrl->chips[target_cs].ctrl_val[ASPEED_SPI_WRITE];
> + writel(reg_val, ast_ctrl->regs + OFFSET_CE0_CTRL_REG + target_cs * 4);
> +
> + memcpy_toio(chip->ahb_base + offs, buf, len);
> +
> + reg_val = ast_ctrl->chips[target_cs].ctrl_val[ASPEED_SPI_READ];
> + writel(reg_val, ast_ctrl->regs + OFFSET_CE0_CTRL_REG + target_cs * 4);
> +
> + return len;
> +}
> +
> +static int aspeed_spi_dirmap_create(struct spi_mem_dirmap_desc *desc)
> +{
> + int ret = 0;
> + struct aspeed_spi_controller *ast_ctrl =
> + spi_controller_get_devdata(desc->mem->spi->master);
> + struct device *dev = ast_ctrl->dev;
> + const struct aspeed_spi_info *info = ast_ctrl->info;
> + struct spi_mem_op op_tmpl = desc->info.op_tmpl;
> + uint32_t decode_sz_arr[5];
> + uint32_t cs, target_cs = desc->mem->spi->chip_select;
> + uint32_t reg_val;
> +
> + if (desc->info.op_tmpl.data.dir == SPI_MEM_DATA_IN) {
> + /* record original decode size */
> + for (cs = 0; cs < ast_ctrl->num_cs; cs++) {
> + reg_val = readl(ast_ctrl->regs +
> + OFFSET_CE0_DECODE_RANGE_REG + cs * 4);
> + decode_sz_arr[cs] =
> + info->segment_end(ast_ctrl, reg_val) -
> + info->segment_start(ast_ctrl, reg_val);
> + }
> +
> + decode_sz_arr[target_cs] = desc->info.length;
> +
> + if (info->adjust_decode_sz)
> + info->adjust_decode_sz(decode_sz_arr, ast_ctrl->num_cs);
> +
> + for (cs = 0; cs < ast_ctrl->num_cs; cs++) {
> + dev_dbg(dev, "cs: %d, sz: 0x%x\n", cs,
> + decode_sz_arr[cs]);
> + }
> +
> + ret = aspeed_spi_decode_range_config(ast_ctrl, decode_sz_arr);
> + if (ret)
> + return ret;
> +
> + reg_val = readl(ast_ctrl->regs + OFFSET_CE0_CTRL_REG +
> + target_cs * 4) &
> + (~info->cmd_io_ctrl_mask);
> + reg_val |= aspeed_spi_get_io_mode(op_tmpl.data.buswidth) |
> + op_tmpl.cmd.opcode << 16 |
> + ((op_tmpl.dummy.nbytes) & 0x3) << 6 |
> + ((op_tmpl.dummy.nbytes) & 0x4) << 14 |
> + CTRL_IO_MODE_CMD_READ;
> +
> + writel(reg_val,
> + ast_ctrl->regs + OFFSET_CE0_CTRL_REG + target_cs * 4);
> + ast_ctrl->chips[target_cs].ctrl_val[ASPEED_SPI_READ] = reg_val;
> + ast_ctrl->chips[target_cs].max_clk_freq =
> + desc->mem->spi->max_speed_hz;
> +
> + ret = info->calibrate(ast_ctrl, target_cs);
> +
> + dev_info(dev, "read bus width: %d [0x%08x]\n",
> + op_tmpl.data.buswidth,
> + ast_ctrl->chips[target_cs].ctrl_val[ASPEED_SPI_READ]);
> +
> + } else if (desc->info.op_tmpl.data.dir == SPI_MEM_DATA_OUT) {
> + reg_val = readl(ast_ctrl->regs + OFFSET_CE0_CTRL_REG +
> + target_cs * 4) &
> + (~info->cmd_io_ctrl_mask);
> + reg_val |= aspeed_spi_get_io_mode(op_tmpl.data.buswidth) |
> + op_tmpl.cmd.opcode << 16 | CTRL_IO_MODE_CMD_WRITE;
> + ast_ctrl->chips[target_cs].ctrl_val[ASPEED_SPI_WRITE] = reg_val;
> +
> + dev_info(dev, "write bus width: %d [0x%08x]\n",
> + op_tmpl.data.buswidth,
> + ast_ctrl->chips[target_cs].ctrl_val[ASPEED_SPI_WRITE]);
> + }
> +
> + return ret;
> +}
> +
> +static const char *aspeed_spi_get_name(struct spi_mem *mem)
> +{
> + struct device *dev = &mem->spi->master->dev;
> + const char *name;
> +
> + name = devm_kasprintf(dev, GFP_KERNEL, "%s-%d", dev_name(dev),
> + mem->spi->chip_select);
> +
> + if (!name) {
> + dev_err(dev, "cannot get spi name\n");
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + return name;
> +}
> +
> +/*
> + * Currently, only support 1-1-1, 1-1-2 or 1-1-4
> + * SPI NOR flash operation format.
> + */
> +static bool aspeed_spi_support_op(struct spi_mem *mem,
> + const struct spi_mem_op *op)
> +{
> + struct aspeed_spi_controller *ast_ctrl =
> + spi_controller_get_devdata(mem->spi->master);
> +
> + if (op->cmd.buswidth > 1)
> + return false;
> +
> + if (op->addr.nbytes != 0) {
> + if (op->addr.buswidth > 1 || op->addr.nbytes > 4)
> + return false;
> + }
> +
> + if (op->dummy.nbytes != 0) {
> + if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7)
> + return false;
> + }
> +
> + if (op->data.nbytes != 0 &&
> + ast_ctrl->info->max_data_bus_width < op->data.buswidth)
> + return false;
> +
> + if (!spi_mem_default_supports_op(mem, op))
> + return false;
> +
> + if (op->addr.nbytes == 4)
> + ast_ctrl->info->set_4byte(ast_ctrl, mem->spi->chip_select);
> +
> + return true;
> +}
> +
> +static const struct spi_controller_mem_ops aspeed_spi_mem_ops = {
> + .exec_op = aspeed_spi_exec_op,
> + .get_name = aspeed_spi_get_name,
> + .supports_op = aspeed_spi_support_op,
> + .dirmap_create = aspeed_spi_dirmap_create,
> + .dirmap_read = aspeed_spi_dirmap_read,
> + .dirmap_write = aspeed_spi_dirmap_write,
> +};
> +
> +/*
> + * Initialize SPI controller for each chip select.
> + * Here, only the minimum decode range is configured
> + * in order to get device (SPI NOR flash) information
> + * at the early stage.
> + */
> +static int aspeed_spi_ctrl_init(struct aspeed_spi_controller *ast_ctrl)
> +{
> + int ret;
> + uint32_t cs;
> + uint32_t val;
> + uint32_t decode_sz_arr[ASPEED_SPI_MAX_CS];
> +
> + /* enable write capability for all CEs */
> + val = readl(ast_ctrl->regs + OFFSET_CE_TYPE_SETTING);
> + writel(val | (GENMASK(ast_ctrl->num_cs, 0) << 16),
> + ast_ctrl->regs + OFFSET_CE_TYPE_SETTING);
> +
> + /* initial each CE's controller register */
> + for (cs = 0; cs < ast_ctrl->num_cs; cs++) {
> + val = CTRL_STOP_ACTIVE | CTRL_IO_MODE_USER;
> + writel(val, ast_ctrl->regs + OFFSET_CE0_CTRL_REG + cs * 4);
> + ast_ctrl->chips[cs].ctrl_val[ASPEED_SPI_BASE] = val;
> + }
> +
> + for (cs = 0; cs < ast_ctrl->num_cs && cs < ASPEED_SPI_MAX_CS; cs++)
> + decode_sz_arr[cs] = ast_ctrl->info->min_decode_sz;
> +
> + ret = aspeed_spi_decode_range_config(ast_ctrl, decode_sz_arr);
> +
> + return ret;
> +}
> +
> +static const struct of_device_id aspeed_spi_matches[] = {
> + { .compatible = "aspeed,ast2600-fmc", .data = &ast2600_spi_info },
> + { .compatible = "aspeed,ast2600-spi", .data = &ast2600_spi_info },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, aspeed_spi_matches);
> +
> +static int aspeed_spi_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct device *dev = &pdev->dev;
> + struct spi_controller *spi_ctrl;
> + struct aspeed_spi_controller *ast_ctrl;
> + const struct of_device_id *match;
> + struct clk *clk;
> + struct resource *res;
> +
> + spi_ctrl = spi_alloc_master(dev, sizeof(struct aspeed_spi_controller));
> + if (!spi_ctrl)
> + return -ENOMEM;
> +
> + ast_ctrl = spi_controller_get_devdata(spi_ctrl);
> +
> + match = of_match_device(aspeed_spi_matches, dev);
> + if (!match || !match->data) {
> + dev_err(dev, "no compatible OF match\n");
> + return -ENODEV;
> + }
> +
> + ast_ctrl->info = match->data;
> +
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> + "spi_ctrl_reg");
> + ast_ctrl->regs = devm_ioremap_resource(dev, res);
> + if (IS_ERR(ast_ctrl->regs))
> + return PTR_ERR(ast_ctrl->regs);
> +
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "spi_mmap");
> + ast_ctrl->ahb_base_phy = res->start;
> + ast_ctrl->ahb_window_sz = resource_size(res);
> +
> + ast_ctrl->dev = dev;
> +
> + clk = devm_clk_get(&pdev->dev, NULL);
> + if (IS_ERR(clk))
> + return PTR_ERR(clk);
> + ast_ctrl->ahb_clk = clk_get_rate(clk);
> + devm_clk_put(&pdev->dev, clk);
> +
> + if (of_property_read_u32(dev->of_node, "num-cs", &ast_ctrl->num_cs)) {
> + dev_err(dev, "fail to get chip number.\n");
> + goto end;
> + }
> +
> + if (ast_ctrl->num_cs > ASPEED_SPI_MAX_CS) {
> + dev_err(dev, "chip number, %d, exceeds %d.\n", ast_ctrl->num_cs,
> + ASPEED_SPI_MAX_CS);
> + goto end;
> + }
> +
> + ast_ctrl->chips =
> + devm_kzalloc(dev,
> + sizeof(struct aspeed_spi_chip) * ast_ctrl->num_cs,
> + GFP_KERNEL);
> +
> + platform_set_drvdata(pdev, ast_ctrl);
> +
> + spi_ctrl->mode_bits =
> + SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
> +
> + spi_ctrl->bus_num = -1;
> + spi_ctrl->mem_ops = &aspeed_spi_mem_ops;
> + spi_ctrl->dev.of_node = dev->of_node;
> + spi_ctrl->num_chipselect = ast_ctrl->num_cs;
> +
> + ret = aspeed_spi_ctrl_init(ast_ctrl);
> + if (ret)
> + goto end;
> +
> + ret = devm_spi_register_master(dev, spi_ctrl);
> +
> +end:
> + return ret;
> +}
> +
> +static int aspeed_spi_remove(struct platform_device *pdev)
> +{
> + struct aspeed_spi_controller *ast_ctrl = platform_get_drvdata(pdev);
> + uint32_t val;
> +
> + /* disable write capability for all CEs */
> + val = readl(ast_ctrl->regs + OFFSET_CE_TYPE_SETTING);
> + writel(val & ~(GENMASK(ast_ctrl->num_cs, 0) << 16),
> + ast_ctrl->regs + OFFSET_CE_TYPE_SETTING);
> +
> + return 0;
> +}
> +
> +static struct platform_driver aspeed_spi_driver = {
> + .driver = {
> + .name = "ASPEED_FMC_SPI",
> + .bus = &platform_bus_type,
> + .of_match_table = aspeed_spi_matches,
> + },
> + .probe = aspeed_spi_probe,
> + .remove = aspeed_spi_remove,
> +};
> +module_platform_driver(aspeed_spi_driver);
> +
> +MODULE_DESCRIPTION("ASPEED FMC/SPI Memory Controller Driver");
> +MODULE_AUTHOR("Chin-Ting Kuo <chin-ting_kuo at aspeedtech.com>");
> +MODULE_AUTHOR("Cedric Le Goater <clg at kaod.org>");
> +MODULE_LICENSE("GPL v2");
>
More information about the Linux-aspeed
mailing list