[PATCH v2] mtd/spi-nor: Add SPI memory controllers for Aspeed SoCs
Cédric Le Goater
clg at kaod.org
Thu Dec 8 08:36:06 PST 2016
Hello Marek,
On 11/20/2016 10:43 PM, Marek Vasut wrote:
> On 11/09/2016 11:42 AM, Cédric Le Goater wrote:
>> This driver adds mtd support for spi-nor attached to either or both of
>> the Firmware Memory Controller or the SPI Flash Controller (AST2400
>> only).
>>
>> The SMC controllers on the Aspeed AST2500 SoC are very similar to the
>> ones found on the AST2400. The differences are on the number of
>> supported flash modules and their default mappings in the SoC address
>> space.
>>
>> The Aspeed AST2500 has one SPI controller for the BMC firmware and two
>> for the host firmware. All controllers have now the same set of
>> registers compatible with the AST2400 FMC controller and the legacy
>> 'SMC' controller is fully gone.
>>
>> Each controller has a memory range on which it maps its flash module
>> slaves. Each slave is assigned a memory window for its mapping that
>> can be changed at bootime with the Segment Address Register.
>>
>> Each SPI flash slave can then be accessed in two modes: Command and
>> User. When in User mode, accesses to the memory segment of the slaves
>> are translated in SPI transfers. When in Command mode, the HW
>> generates the SPI commands automatically and the memory segment is
>> accessed as if doing a MMIO.
>>
>> Currently, only the User mode is supported. Command mode needs a
>> little more work to check that the memory window on the AHB bus fits
>> the module size.
>>
>> Based on previous work from Milton D. Miller II <miltonm at us.ibm.com>
>>
>> Signed-off-by: Cédric Le Goater <clg at kaod.org>
>> ---
>> Tested on:
>>
>> * OpenPOWER Palmetto (AST2400) with
>> FMC controller : n25q256a
>> SPI controller : mx25l25635e and n25q512ax3
>>
>> * Evaluation board (AST2500) with
>> FMC controller : w25q256
>> SPI controller : w25q256
>>
>> * OpenPOWER Witherspoon (AST2500) with
>> FMC controller : mx25l25635e * 2
>> SPI controller : mx66l1g45g
>>
>> Changes since v2:
>>
>> - added a set4b ops to handle difference in the controllers
>> - simplified the IO routines
>> - prepared for fast read using dummy cycles
>>
>> Work in progress:
>>
>> - read optimization using higher SPI clock frequencies
>> - command mode to direct reads from AHB
>> - DMA support
>>
>> .../devicetree/bindings/mtd/aspeed-smc.txt | 72 ++
>> drivers/mtd/spi-nor/Kconfig | 12 +
>> drivers/mtd/spi-nor/Makefile | 1 +
>> drivers/mtd/spi-nor/aspeed-smc.c | 783 +++++++++++++++++++++
>> 4 files changed, 868 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/mtd/aspeed-smc.txt
>> create mode 100644 drivers/mtd/spi-nor/aspeed-smc.c
>>
>> diff --git a/Documentation/devicetree/bindings/mtd/aspeed-smc.txt b/Documentation/devicetree/bindings/mtd/aspeed-smc.txt
>> new file mode 100644
>> index 000000000000..7516b0c01fcf
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mtd/aspeed-smc.txt
>> @@ -0,0 +1,72 @@
>> +* Aspeed Static Memory controller
>> +* Aspeed SPI Flash Controller
>> +
>> +The Static memory controller in the ast2400 supports 5 chip selects
>> +each can be attached to NAND, parallel NOR, or SPI NOR attached flash.
>
> So this controller is supported by this driver, which behaves like a SPI
> controller driver, yet the block can also do NAND and parallel NOR ?
I think that was answered in a previous email.
>> +The Firmware Memory Controller in the ast2500 supports 3 chip selects,
>> +two of which are always in SPI-NOR mode and the third can be SPI-NOR
>> +or parallel flash. The SPI flash controller in the ast2400 supports
>> +one of 2 chip selects selected by pinmux. The two SPI flash
>> +controllers in the ast2500 each support two chip selects.
>
> This paragraph is confusing, it's hard to grok down how many different
> controllers does this driver support and what are their properties from
> it. It is all there, it's just hard to read.
I will start with the AST2500 controllers only, which are consistent.
> Also, please split the DT bindings into separate patch and send them to
> DT list for review.
ok.
>> +Required properties:
>> + - compatible : Should be one of
>> + "aspeed,ast2400-fmc" for the AST2400 Static Memory Controller
>> + "aspeed,ast2400-smc" for the AST2400 SPI Flash Controller
>> + "aspeed,ast2500-fmc" for the AST2500 Firmware SPI Memory Controller
>> + "aspeed,ast2500-smc" for the AST2500 SPI Flash Controllers
>> + - reg : the first contains the control register location and length,
>> + the second contains the memory window mapping address and length
>> + - #address-cells : must be 1 corresponding to chip select child binding
>> + - #size-cells : must be 0 corresponding to chip select child binding
>> +
>> +Optional properties:
>> + - interrupts : Should contain the interrupt for the dma device if an fmc
>> +
>> +The child nodes are the SPI Flash modules which must have a compatible
>> +property as specified in bindings/mtd/jedec,spi-nor.txt
>> +
>> +Optionally, the child node can contain properties for SPI mode (may be
>> +ignored):
>> + - spi-max-frequency - (optional) max frequency of spi bus
>
> You don't need to add the (optional) here again.
>
>> +Example:
>> +fmc: fmc at 1e620000 {
>
> I'd suggest to keep the example minimal -- drop the partitions etc.
ok
>> + compatible = "aspeed,ast2400-fmc";
>> + reg = < 0x1e620000 0x94
>> + 0x20000000 0x02000000
>> + 0x22000000 0x02000000 >;
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> + interrupts = <19>;
>> + flash at 0 {
>> + reg = < 0 >;
>> + compatible = "jedec,spi-nor" ;
>> + /* spi-max-frequency = <>; */
>> + /* m25p,fast-read; */
>> + #address-cells = <1>;
>> + #size-cells = <1>;
>> + partitions {
>> + compatible = "fixed-partitions";
>> + #address-cells = <1>;
>> + #size-cells = <1>;
>> + boot at 0 {
>> + label = "boot-loader";
>> + reg = < 0 0x8000 >;
>> + };
>> + image at 8000 {
>> + label = "kernel-image";
>> + reg = < 0x8000 0x1f8000 >;
>> + };
>> + };
>> + };
>> + flash at 1 {
>> + reg = < 1 >;
>> + compatible = "jedec,spi-nor" ;
>> + label = "alt";
>> + /* spi-max-frequency = <>; */
>> + status = "fail";
>> + /* m25p,fast-read; */
>> + };
>> +};
>> diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
>> index 4a682ee0f632..96148600fdab 100644
>> --- a/drivers/mtd/spi-nor/Kconfig
>> +++ b/drivers/mtd/spi-nor/Kconfig
>> @@ -76,4 +76,16 @@ config SPI_NXP_SPIFI
>> Flash. Enable this option if you have a device with a SPIFI
>> controller and want to access the Flash as a mtd device.
>>
>> +config ASPEED_FLASH_SPI
>
> Should be SPI_ASPEED , see the other controllers and keep the list sorted.
ok
>> + tristate "Aspeed flash controllers in SPI mode"
>> + depends on HAS_IOMEM && OF
>> + depends on ARCH_ASPEED || COMPILE_TEST
>> + # IO_SPACE_LIMIT must be equivalent to (~0UL)
>> + depends on !NEED_MACH_IO_H
>
> Why?
Some left over from the patch we have been keeping for too long (+1year)
in our tree.
>> + help
>> + This enables support for the New Static Memory Controller
>> + (FMC) in the Aspeed SoCs (AST2400 and AST2500) when attached
>> + to SPI nor chips, and support for the SPI Memory controller
>> + (SPI) for the BIOS.
>
> I think there is a naming chaos between FMC, SMC (as in Static MC) and
> SMC (as in SPI MC).
I agree, I will try to clarify the naming in the next version. I will keep
the smc suffix for the driver name though.
>> endif # MTD_SPI_NOR
>> diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
>> index 121695e83542..c3174ebc45c2 100644
>> --- a/drivers/mtd/spi-nor/Makefile
>> +++ b/drivers/mtd/spi-nor/Makefile
>> @@ -4,4 +4,5 @@ obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o
>> obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
>> obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o
>> obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
>> +obj-$(CONFIG_ASPEED_FLASH_SPI) += aspeed-smc.o
>> obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
>> diff --git a/drivers/mtd/spi-nor/aspeed-smc.c b/drivers/mtd/spi-nor/aspeed-smc.c
>> new file mode 100644
>> index 000000000000..30662daf89ca
>> --- /dev/null
>> +++ b/drivers/mtd/spi-nor/aspeed-smc.c
>> @@ -0,0 +1,783 @@
>> +/*
>> + * ASPEED Static Memory Controller driver
>> + *
>> + * Copyright (c) 2015-2016, IBM Corporation.
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License
>> + * as published by the Free Software Foundation; either version
>> + * 2 of the License, or (at your option) any later version.
>> + */
>> +
>> +#include <linux/bug.h>
>> +#include <linux/device.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/mtd/partitions.h>
>> +#include <linux/mtd/spi-nor.h>
>> +#include <linux/of.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/sysfs.h>
>> +
>> +#define DEVICE_NAME "aspeed-smc"
>> +
>> +/*
>> + * In user mode all data bytes read or written to the chip decode address
>> + * range are transferred to or from the SPI bus. The range is treated as a
>> + * fifo of arbitratry 1, 2, or 4 byte width but each write has to be aligned
>> + * to its size. The address within the multiple 8kB range is ignored when
>> + * sending bytes to the SPI bus.
>> + *
>> + * On the arm architecture, as of Linux version 4.3, memcpy_fromio and
>> + * memcpy_toio on little endian targets use the optimized memcpy routines
>> + * that were designed for well behavied memory storage. These routines
>> + * have a stutter if the source and destination are not both word aligned,
>> + * once with a duplicate access to the source after aligning to the
>> + * destination to a word boundary, and again with a duplicate access to
>> + * the source when the final byte count is not word aligned.
>> + *
>> + * When writing or reading the fifo this stutter discards data or sends
>> + * too much data to the fifo and can not be used by this driver.
>> + *
>> + * While the low level io string routines that implement the insl family do
>> + * the desired accesses and memory increments, the cross architecture io
>> + * macros make them essentially impossible to use on a memory mapped address
>> + * instead of a a token from the call to iomap of an io port.
>> + *
>> + * These fifo routines use readl and friends to a constant io port and update
>> + * the memory buffer pointer and count via explicit code. The final updates
>> + * to len are optimistically suppressed.
>> + */
>> +static int aspeed_smc_read_from_ahb(void *buf, const void __iomem *src,
>> + size_t len)
>> +{
>
> What if start of buf is unaligned ?
>
>> + if ((((unsigned long)src | (unsigned long)buf | len) & 3) == 0) {
>
> Uh, should use boolean OR, not bitwise or. Also, if you're testing
> pointer for NULL, do if (!ptr) .
>
> if (!src || !buf || !len)
> return;
>
> while (...)
I have added a bunch of IS_ALIGNED() in the next version to clarify what
these tests are for.
>> + while (len > 3) {
>> + *(u32 *)buf = readl(src);
>> + buf += 4;
>> + src += 4;
>> + len -= 4;
>> + }
>> + }
>> +
>> + while (len--) {
>> + *(u8 *)buf = readb(src);
>> + buf += 1;
>> + src += 1;
>> + }
>> + return 0;
>> +}
>> +
>> +static int aspeed_smc_write_to_ahb(void __iomem *dst, const void *buf,
>> + size_t len)
>> +{
>> + if ((((unsigned long)dst | (unsigned long)buf | len) & 3) == 0) {
>
> DTTO
>
>> + while (len > 3) {
>> + u32 val = *(u32 *)buf;
>> +
>> + writel(val, dst);
>> + buf += 4;
>> + dst += 4;
>> + len -= 4;
>> + }
>> + }
>> +
>> + while (len--) {
>> + u8 val = *(u8 *)buf;
>> +
>> + writeb(val, dst);
>> + buf += 1;
>> + dst += 1;
>> + }
>> + return 0;
>> +}
>> +
>> +enum smc_flash_type {
>> + smc_type_nor = 0, /* controller connected to nor flash */
>> + smc_type_nand = 1, /* controller connected to nand flash */
>> + smc_type_spi = 2, /* controller connected to spi flash */
>> +};
>> +
>> +struct aspeed_smc_chip;
>> +
>> +struct aspeed_smc_info {
>> + u32 maxsize; /* maximum size of 1 chip window */
>> + u8 nce; /* number of chip enables */
>> + u8 maxwidth; /* max width of spi bus */
>> + bool hastype; /* flash type field exists in cfg reg */
>> + u8 we0; /* shift for write enable bit for ce 0 */
>> + u8 ctl0; /* offset in regs of ctl for ce 0 */
>> + u8 time; /* offset in regs of timing */
>> + u8 misc; /* offset in regs of misc settings */
>> +
>> + void (*set_4b)(struct aspeed_smc_chip *chip);
>> +};
>> +
>> +static void aspeed_smc_chip_set_4b_smc_2400(struct aspeed_smc_chip *chip);
>> +static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip);
>> +
>> +static const struct aspeed_smc_info fmc_2400_info = {
>> + .maxsize = 64 * 1024 * 1024,
>> + .nce = 5,
>> + .maxwidth = 4,
>> + .hastype = true,
>
> Shouldn't all this be specified in DT ?
I am not sure, these values are not configurable. I will remove a few
of them in the next version as they are unused.
>> + .we0 = 16,
>> + .ctl0 = 0x10,
>> + .time = 0x94,
>> + .misc = 0x54,
>> + .set_4b = aspeed_smc_chip_set_4b,
>> +};
>> +
>> +static const struct aspeed_smc_info smc_2400_info = {
>> + .maxsize = 64 * 1024 * 1024,
>> + .nce = 1,
>> + .maxwidth = 2,
>> + .hastype = false,
>> + .we0 = 0,
>> + .ctl0 = 0x04,
>> + .time = 0x14,
>> + .misc = 0x10,
>> + .set_4b = aspeed_smc_chip_set_4b_smc_2400,
>> +};
>> +
>> +static const struct aspeed_smc_info fmc_2500_info = {
>> + .maxsize = 256 * 1024 * 1024,
>> + .nce = 3,
>> + .maxwidth = 2,
>> + .hastype = true,
>> + .we0 = 16,
>> + .ctl0 = 0x10,
>> + .time = 0x94,
>> + .misc = 0x54,
>> + .set_4b = aspeed_smc_chip_set_4b,
>> +};
>> +
>> +static const struct aspeed_smc_info smc_2500_info = {
>> + .maxsize = 128 * 1024 * 1024,
>> + .nce = 2,
>> + .maxwidth = 2,
>> + .hastype = false,
>> + .we0 = 16,
>> + .ctl0 = 0x10,
>> + .time = 0x94,
>> + .misc = 0x54,
>> + .set_4b = aspeed_smc_chip_set_4b,
>> +};
>> +
>> +enum smc_ctl_reg_value {
>> + smc_base, /* base value without mode for other commands */
>> + smc_read, /* command reg for (maybe fast) reads */
>> + smc_write, /* command reg for writes with timings */
>> + smc_num_ctl_reg_values /* last value to get count of commands */
>> +};
>> +
>> +struct aspeed_smc_controller;
>> +
>> +struct aspeed_smc_chip {
>> + int cs;
>> + struct aspeed_smc_controller *controller;
>> + __le32 __iomem *ctl; /* control register */
>
> Why do you use __le32* here and void* below ?
arg. this is left over rust.
>
>> + void __iomem *base; /* base of chip window */
>> + __le32 ctl_val[smc_num_ctl_reg_values]; /* controls with timing */
>> + enum smc_flash_type type; /* what type of flash */
>> + struct spi_nor nor;
>> +};
>> +
>> +struct aspeed_smc_controller {
>> + struct device *dev;
>> +
>> + struct mutex mutex; /* controller access mutex */
>> + const struct aspeed_smc_info *info; /* type info of controller */
>> + void __iomem *regs; /* controller registers */
>> + void __iomem *windows; /* per-chip windows resource */
>> +
>> + struct aspeed_smc_chip *chips[0]; /* pointers to attached chips */
>> +};
>> +
>> +/*
>> + * SPI Flash Configuration Register (AST2400 SPI)
>> + */
>> +#define CONFIG_REG 0x0
>> +#define CONFIG_ENABLE_CE_INACTIVE BIT(1)
>> +#define CONFIG_WRITE BIT(0)
>
> #define[space]FOO[tab]BIT(bar)
ok. I don't have a strong preference for the indent.
>
>> +/*
>> + * SPI Flash Configuration Register (AST2500 SPI)
>> + * Type setting Register (AST2500 FMC and AST2400 FMC)
>> + */
>> +#define TYPE_SETTING_REG 0x0
>> +#define CONFIG_DISABLE_LEGACY BIT(31) /* 1 on AST2500 FMC */
>> +
>> +#define CONFIG_CE2_WRITE BIT(18)
>> +#define CONFIG_CE1_WRITE BIT(17)
>> +#define CONFIG_CE0_WRITE BIT(16)
>> +
>> +#define CONFIG_CE2_TYPE BIT(4) /* FMC only */
>> +#define CONFIG_CE1_TYPE BIT(2) /* FMC only */
>> +#define CONFIG_CE0_TYPE BIT(0) /* FMC only */
>> +
>> +/*
>> + * CE Control Register (AST2500 SPI,FMC and AST2400 FMC)
>> + */
>> +#define CE_CONTROL_REG 0x4
>> +#define CE2_ENABLE_CE_INACTIVE BIT(10)
>> +#define CE1_ENABLE_CE_INACTIVE BIT(9)
>> +#define CE0_ENABLE_CE_INACTIVE BIT(8)
>> +#define CE2_CONTROL_EXTENDED BIT(2)
>> +#define CE1_CONTROL_EXTENDED BIT(1)
>> +#define CE0_CONTROL_EXTENDED BIT(0)
>> +
>> +/* CE0 Control Register (depends on the controller type) */
>> +#define CONTROL_SPI_AAF_MODE BIT(31)
>> +#define CONTROL_SPI_IO_MODE_MASK GENMASK(30, 28)
>> +#define CONTROL_SPI_IO_DUAL_DATA BIT(29)
>> +#define CONTROL_SPI_IO_DUAL_ADDR_DATA (BIT(29) | BIT(28))
>> +#define CONTROL_SPI_IO_QUAD_DATA BIT(30)
>> +#define CONTROL_SPI_IO_QUAD_ADDR_DATA (BIT(30) | BIT(28))
>> +#define CONTROL_SPI_CE_INACTIVE_SHIFT 24
>> +#define CONTROL_SPI_CE_INACTIVE_MASK GENMASK(27, CONTROL_SPI_CE_INACTIVE_SHIFT)
>> +/* 0 = 16T ... 15 = 1T T=HCLK */
>> +#define CONTROL_SPI_COMMAND_SHIFT 16
>> +#define CONTROL_SPI_DUMMY_CYCLE_COMMAND_OUTPUT BIT(15)
>> +#define CONTROL_SPI_IO_DUMMY_CYCLES_HI BIT(14)
>> +#define CONTROL_SPI_IO_DUMMY_CYCLES_HI_SHIFT 14
>> +#define CONTROL_SPI_IO_ADDRESS_4B BIT(13) /* AST2400 SPI */
>> +#define CONTROL_SPI_CLK_DIV4 BIT(13) /* others */
>> +#define CONTROL_SPI_RW_MERGE BIT(12)
>> +#define CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT 6
>> +#define CONTROL_SPI_IO_DUMMY_CYCLES_LO GENMASK(7, \
>> + CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT)
>> +#define CONTROL_SPI_IO_DUMMY_CYCLES_MASK (CONTROL_SPI_IO_DUMMY_CYCLES_HI | \
>> + CONTROL_SPI_IO_DUMMY_CYCLES_LO)
>> +#define CONTROL_SPI_IO_DUMMY_CYCLES_SET(dummy) \
>> + (((((dummy) >> 2) & 0x1) << CONTROL_SPI_IO_DUMMY_CYCLES_HI_SHIFT) | \
>> + (((dummy) & 0x3) << CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT))
>> +
>> +#define CONTROL_SPI_CLOCK_FREQ_SEL_SHIFT 8
>> +#define CONTROL_SPI_CLOCK_FREQ_SEL_MASK GENMASK(11, \
>> + CONTROL_SPI_CLOCK_FREQ_SEL_SHIFT)
>> +#define CONTROL_SPI_LSB_FIRST BIT(5)
>> +#define CONTROL_SPI_CLOCK_MODE_3 BIT(4)
>> +#define CONTROL_SPI_IN_DUAL_DATA BIT(3)
>> +#define CONTROL_SPI_CE_STOP_ACTIVE_CONTROL BIT(2)
>> +#define CONTROL_SPI_COMMAND_MODE_MASK GENMASK(1, 0)
>> +#define CONTROL_SPI_COMMAND_MODE_NORMAL (0)
>> +#define CONTROL_SPI_COMMAND_MODE_FREAD (1)
>> +#define CONTROL_SPI_COMMAND_MODE_WRITE (2)
>> +#define CONTROL_SPI_COMMAND_MODE_USER (3)
>> +
>> +#define CONTROL_SPI_KEEP_MASK (CONTROL_SPI_AAF_MODE | \
>> + CONTROL_SPI_CE_INACTIVE_MASK | CONTROL_SPI_CLK_DIV4 | \
>> + CONTROL_SPI_IO_DUMMY_CYCLES_MASK | CONTROL_SPI_CLOCK_FREQ_SEL_MASK | \
>> + CONTROL_SPI_LSB_FIRST | CONTROL_SPI_CLOCK_MODE_3)
>> +
>> +/* Segment Address Registers */
>> +#define SEGMENT_ADDR_REG0 0x30
>> +#define SEGMENT_ADDR_START(_r) ((((_r) >> 16) & 0xFF) << 23)
>> +#define SEGMENT_ADDR_END(_r) ((((_r) >> 24) & 0xFF) << 23)
>> +
>> +static u32 spi_control_fill_opcode(u8 opcode)
>> +{
>> + return ((u32)(opcode)) << CONTROL_SPI_COMMAND_SHIFT;
>
> return opcode << CONTROL... , fix these horrible casts and parenthesis
> globally.
I killed the helper.
>> +}
>> +
>> +static inline u32 aspeed_smc_chip_write_bit(struct aspeed_smc_chip *chip)
>> +{
>> + return ((u32)1 << (chip->controller->info->we0 + chip->cs));
>
> return BIT(...)
>
> I'm not sure these microfunctions are even needed.
well, this one is used in a couple of places.
>> +}
>> +
>> +static void aspeed_smc_chip_check_config(struct aspeed_smc_chip *chip)
>> +{
>> + struct aspeed_smc_controller *controller = chip->controller;
>> + u32 reg;
>> +
>> + reg = readl(controller->regs + CONFIG_REG);
>> +
>> + if (!(reg & aspeed_smc_chip_write_bit(chip))) {
>
> Invert the logic and return here, ie if (reg & BIT()) return , to trim
> the indent.
ok.
>> + dev_dbg(controller->dev,
>> + "config write is not set ! @%p: 0x%08x\n",
>> + controller->regs + CONFIG_REG, reg);
>> + reg |= aspeed_smc_chip_write_bit(chip);
>> + writel(reg, controller->regs + CONFIG_REG);
>> + }
>> +}
>> +
>> +static void aspeed_smc_start_user(struct spi_nor *nor)
>> +{
>> + struct aspeed_smc_chip *chip = nor->priv;
>> + u32 ctl = chip->ctl_val[smc_base];
>> +
>> + /*
>> + * When the chip is controlled in user mode, we need write
>> + * access to send the opcodes to it. So check the config.
>> + */
>> + aspeed_smc_chip_check_config(chip);
>> +
>> + ctl |= CONTROL_SPI_COMMAND_MODE_USER |
>> + CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
>> + writel(ctl, chip->ctl);
>> +
>> + ctl &= ~CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
>> + writel(ctl, chip->ctl);
>> +}
>> +
>> +static void aspeed_smc_stop_user(struct spi_nor *nor)
>> +{
>> + struct aspeed_smc_chip *chip = nor->priv;
>> +
>> + u32 ctl = chip->ctl_val[smc_read];
>> + u32 ctl2 = ctl | CONTROL_SPI_COMMAND_MODE_USER |
>> + CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
>> +
>> + writel(ctl2, chip->ctl); /* stop user CE control */
>> + writel(ctl, chip->ctl); /* default to fread or read */
>> +}
>> +
>> +static int aspeed_smc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
>> +{
>> + struct aspeed_smc_chip *chip = nor->priv;
>> +
>> + mutex_lock(&chip->controller->mutex);
>
> Won't this have a horrid overhead ?
Shall I use the prepare() and unprepare() ops instead ?
>> + aspeed_smc_start_user(nor);
>> + aspeed_smc_write_to_ahb(chip->base, &opcode, 1);
>> + aspeed_smc_read_from_ahb(buf, chip->base, len);
>> + aspeed_smc_stop_user(nor);
>> +
>> + mutex_unlock(&chip->controller->mutex);
>> +
>> + return 0;
>> +}
>> +
>> +static int aspeed_smc_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
>> + int len)
>> +{
>> + struct aspeed_smc_chip *chip = nor->priv;
>> +
>> + mutex_lock(&chip->controller->mutex);
>> +
>> + aspeed_smc_start_user(nor);
>> + aspeed_smc_write_to_ahb(chip->base, &opcode, 1);
>> + aspeed_smc_write_to_ahb(chip->base, buf, len);
>> + aspeed_smc_stop_user(nor);
>> +
>> + mutex_unlock(&chip->controller->mutex);
>> +
>> + return 0;
>> +}
>> +
>> +static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr)
>> +{
>> + struct aspeed_smc_chip *chip = nor->priv;
>> + __be32 temp;
>> + u32 cmdaddr;
>> +
>> + switch (nor->addr_width) {
>> + default:
>> + WARN_ONCE(1, "Unexpected address width %u, defaulting to 3\n",
>> + nor->addr_width);
>> + /* FALLTHROUGH */
>> + case 3:
>> + cmdaddr = addr & 0xFFFFFF;
>> +
>> + cmdaddr |= (u32)cmd << 24;
>
> Drop the cast.
sure.
>
>> + temp = cpu_to_be32(cmdaddr);
>> + aspeed_smc_write_to_ahb(chip->base, &temp, 4);
>> + break;
>> + case 4:
>> + temp = cpu_to_be32(addr);
>> + aspeed_smc_write_to_ahb(chip->base, &cmd, 1);
>> + aspeed_smc_write_to_ahb(chip->base, &temp, 4);
>> + break;
>> + }
>> +}
>> +
>> +static ssize_t aspeed_smc_read_user(struct spi_nor *nor, loff_t from,
>> + size_t len, u_char *read_buf)
>> +{
>> + struct aspeed_smc_chip *chip = nor->priv;
>> +
>> + mutex_lock(&chip->controller->mutex);
>> +
>> + aspeed_smc_start_user(nor);
>> + aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from);
>> + aspeed_smc_read_from_ahb(read_buf, chip->base, len);
>> + aspeed_smc_stop_user(nor);
>> +
>> + mutex_unlock(&chip->controller->mutex);
>> +
>> + return len;
>> +}
>> +
>> +static ssize_t aspeed_smc_write_user(struct spi_nor *nor, loff_t to, size_t len,
>> + const u_char *write_buf)
>> +{
>> + struct aspeed_smc_chip *chip = nor->priv;
>> +
>> + mutex_lock(&chip->controller->mutex);
>> +
>> + aspeed_smc_start_user(nor);
>> + aspeed_smc_send_cmd_addr(nor, nor->program_opcode, to);
>> + aspeed_smc_write_to_ahb(chip->base, write_buf, len);
>> + aspeed_smc_stop_user(nor);
>> +
>> + mutex_unlock(&chip->controller->mutex);
>> +
>> + return len;
>> +}
>> +
>> +static int aspeed_smc_remove(struct platform_device *dev)
>> +{
>> + struct aspeed_smc_chip *chip;
>> + struct aspeed_smc_controller *controller = platform_get_drvdata(dev);
>> + int n;
>> +
>> + for (n = 0; n < controller->info->nce; n++) {
>> + chip = controller->chips[n];
>> + if (chip)
>> + mtd_device_unregister(&chip->nor.mtd);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static const struct of_device_id aspeed_smc_matches[] = {
>> + { .compatible = "aspeed,ast2400-fmc", .data = &fmc_2400_info },
>> + { .compatible = "aspeed,ast2400-smc", .data = &smc_2400_info },
>> + { .compatible = "aspeed,ast2500-fmc", .data = &fmc_2500_info },
>> + { .compatible = "aspeed,ast2500-smc", .data = &smc_2500_info },
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(of, aspeed_smc_matches);
>> +
>> +static struct platform_device *
>> +of_platform_device_create_or_find(struct device_node *child,
>> + struct device *parent)
>> +{
>> + struct platform_device *cdev;
>> +
>> + cdev = of_platform_device_create(child, NULL, parent);
>> + if (!cdev)
>> + cdev = of_find_device_by_node(child);
>> + return cdev;
>> +}
>> +
>> +static void __iomem *window_start(struct aspeed_smc_controller *controller,
>> + struct resource *r, unsigned int n)
>> +{
>> + u32 offset = 0;
>> + u32 reg;
>> +
>> + if (controller->info->nce > 1) {
>> + reg = readl(controller->regs + SEGMENT_ADDR_REG0 + n * 4);
>> +
>> + if (SEGMENT_ADDR_START(reg) >= SEGMENT_ADDR_END(reg))
>> + return NULL;
>> +
>> + offset = SEGMENT_ADDR_START(reg) - r->start;
>> + }
>> +
>> + return controller->windows + offset;
>> +}
>> +
>> +static void aspeed_smc_chip_enable_write(struct aspeed_smc_chip *chip)
>> +{
>> + struct aspeed_smc_controller *controller = chip->controller;
>> + u32 reg;
>> +
>> + reg = readl(controller->regs + CONFIG_REG);
>> +
>> + reg |= aspeed_smc_chip_write_bit(chip);
>> + writel(reg, controller->regs + CONFIG_REG);
>> +}
>> +
>> +static void aspeed_smc_chip_set_type(struct aspeed_smc_chip *chip, int type)
>> +{
>> + struct aspeed_smc_controller *controller = chip->controller;
>> + u32 reg;
>> +
>> + reg = readl(controller->regs + CONFIG_REG);
>> +
>> + chip->type = type;
>
> You can move this above the readl() to make the RMW block consistent.
ok
>> + reg &= ~(3 << (chip->cs * 2));
>> + reg |= chip->type << (chip->cs * 2);
>> + writel(reg, controller->regs + CONFIG_REG);
>> +}
>> +
>> +/*
>> + * The AST2500 FMC and AST2400 FMC flash controllers should be
>> + * strapped by hardware, or autodetected, but the AST2500 SPI flash
>> + * needs to be set.
>> + */
>> +static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip)
>> +{
>> + struct aspeed_smc_controller *controller = chip->controller;
>> + u32 reg;
>> +
>> + if (chip->controller->info == &smc_2500_info) {
>> + reg = readl(controller->regs + CE_CONTROL_REG);
>> + reg |= 1 << chip->cs;
>> + writel(reg, controller->regs + CE_CONTROL_REG);
>> + }
>> +}
>> +
>> +/*
>> + * The AST2400 SPI flash controller does not have a CE Control
>> + * register. It uses the CE0 control register to set 4Byte mode at the
>> + * controller level.
>> + */
>> +static void aspeed_smc_chip_set_4b_smc_2400(struct aspeed_smc_chip *chip)
>> +{
>> + chip->ctl_val[smc_base] |= CONTROL_SPI_IO_ADDRESS_4B;
>> + chip->ctl_val[smc_read] |= CONTROL_SPI_IO_ADDRESS_4B;
>> +}
>> +
>> +static int aspeed_smc_chip_setup_init(struct aspeed_smc_chip *chip,
>> + struct resource *r)
>> +{
>> + struct aspeed_smc_controller *controller = chip->controller;
>> + const struct aspeed_smc_info *info = controller->info;
>> + u32 reg, base_reg;
>> +
>> + /*
>> + * Always turn on the write enable bit to allow opcodes to be
>> + * sent in user mode.
>> + */
>> + aspeed_smc_chip_enable_write(chip);
>> +
>> + /* The driver only supports SPI type flash for the moment */
>> + if (info->hastype)
>> + aspeed_smc_chip_set_type(chip, smc_type_spi);
>> +
>> + /*
>> + * Configure chip base address in memory
>> + */
>> + chip->base = window_start(controller, r, chip->cs);
>> + if (!chip->base) {
>> + dev_warn(chip->nor.dev, "CE segment window closed.\n");
>> + return -1;
>> + }
>> +
>> + /*
>> + * Read the existing control register to get basic values.
>> + *
>> + * XXX This register probably needs more sanitation.
>
> What's this comment about ?
This is an initial comment about settings being done by U-Boot
before the kernel is loaded, and some optimisations should be
nice to keep, for the FMC controller. I will rephrase.
>> + * Do we need support for mode 3 vs mode 0 clock phasing?
>> + */
>> + reg = readl(chip->ctl);
>> + dev_dbg(controller->dev, "control register: %08x\n", reg);
>> +
>> + base_reg = reg & CONTROL_SPI_KEEP_MASK;
>> + if (base_reg != reg) {
>> + dev_info(controller->dev,
>> + "control register changed to: %08x\n",
>> + base_reg);
>> + }
>> + chip->ctl_val[smc_base] = base_reg;
>> +
>> + /*
>> + * Retain the prior value of the control register as the
>> + * default if it was normal access mode. Otherwise start with
>> + * the sanitized base value set to read mode.
>> + */
>> + if ((reg & CONTROL_SPI_COMMAND_MODE_MASK) ==
>> + CONTROL_SPI_COMMAND_MODE_NORMAL)
>> + chip->ctl_val[smc_read] = reg;
>> + else
>> + chip->ctl_val[smc_read] = chip->ctl_val[smc_base] |
>> + CONTROL_SPI_COMMAND_MODE_NORMAL;
>> +
>> + dev_dbg(controller->dev, "default control register: %08x\n",
>> + chip->ctl_val[smc_read]);
>> + return 0;
>> +}
>> +
>> +static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
>> +{
>> + struct aspeed_smc_controller *controller = chip->controller;
>> + const struct aspeed_smc_info *info = controller->info;
>> + u32 cmd;
>> +
>> + if (chip->nor.addr_width == 4 && info->set_4b)
>> + info->set_4b(chip);
>> +
>> + /*
>> + * base mode has not been optimized yet. use it for writes.
>> + */
>> + chip->ctl_val[smc_write] = chip->ctl_val[smc_base] |
>> + spi_control_fill_opcode(chip->nor.program_opcode) |
>> + CONTROL_SPI_COMMAND_MODE_WRITE;
>> +
>> + dev_dbg(controller->dev, "write control register: %08x\n",
>> + chip->ctl_val[smc_write]);
>> +
>> + /*
>> + * XXX TODO
>> + * Adjust clocks if fast read and write are supported.
>> + * Interpret spi-nor flags to adjust controller settings.
>> + * Check if resource size big enough for detected chip and
>> + * add support assisted (normal or fast-) read and dma.
>> + */
>> + switch (chip->nor.flash_read) {
>> + case SPI_NOR_NORMAL:
>> + cmd = CONTROL_SPI_COMMAND_MODE_NORMAL;
>> + break;
>> + case SPI_NOR_FAST:
>> + cmd = CONTROL_SPI_COMMAND_MODE_FREAD;
>> + break;
>> + default:
>> + dev_err(chip->nor.dev, "unsupported SPI read mode\n");
>> + return -EINVAL;
>> + }
>> +
>> + chip->ctl_val[smc_read] |= cmd |
>> + CONTROL_SPI_IO_DUMMY_CYCLES_SET(chip->nor.read_dummy / 8);
>> +
>> + dev_dbg(controller->dev, "base control register: %08x\n",
>> + chip->ctl_val[smc_read]);
>> + return 0;
>> +}
>> +
>> +static int aspeed_smc_probe(struct platform_device *pdev)
>> +{
>> + struct aspeed_smc_controller *controller;
>> + const struct of_device_id *match;
>> + const struct aspeed_smc_info *info;
>> + struct resource *r;
>> + struct device_node *child;
>> + int err = 0;
>> + unsigned int n;
>> +
>> + match = of_match_device(aspeed_smc_matches, &pdev->dev);
>> + if (!match || !match->data)
>> + return -ENODEV;
>> + info = match->data;
>> +
>> + controller = devm_kzalloc(&pdev->dev, sizeof(*controller) +
>> + info->nce * sizeof(controller->chips[0]), GFP_KERNEL);
>> + if (!controller)
>> + return -ENOMEM;
>> + controller->info = info;
>> +
>> + mutex_init(&controller->mutex);
>> + platform_set_drvdata(pdev, controller);
>> +
>> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + controller->regs = devm_ioremap_resource(&pdev->dev, r);
>> + if (IS_ERR(controller->regs))
>> + return PTR_ERR(controller->regs);
>> +
>> + r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>> + controller->windows = devm_ioremap_resource(&pdev->dev, r);
>> + if (IS_ERR(controller->windows))
>> + return PTR_ERR(controller->windows);
>> +
>> + controller->dev = &pdev->dev;
>> +
>> + /* The pinmux or bootloader will disable the legacy mode controller */
>> +
>> + /*
>> + * XXX Need to add arbitration to the SMC (BIOS) controller if access
>> + * is shared by the host.
>> + */
>> + for_each_available_child_of_node(controller->dev->of_node, child) {
>> + struct platform_device *cdev;
>> + struct aspeed_smc_chip *chip;
>
> Pull this into separate function, ie. like cadence_qspi.c , so we can
> identify the developing boilerplate easily.
yes. I have reworked the code to follow the same pattern.
>
>> + /* This version does not support nand or nor flash devices. */
>> + if (!of_device_is_compatible(child, "jedec,spi-nor"))
>> + continue;
>> +
>> + /*
>> + * create a platform device from the of node. If the device
>> + * already was created (eg from a prior bind/unbind cycle)
>> + * reuse it.
>> + *
>> + * The creating the device node for the child here allows its
>> + * use for error reporting via dev_err below.
>> + */
>> + cdev = of_platform_device_create_or_find(child,
>> + controller->dev);
>> + if (!cdev)
>> + continue;
>> +
>> + err = of_property_read_u32(child, "reg", &n);
>> + if (err == -EINVAL && info->nce == 1)
>> + n = 0;
>> + else if (err || n >= info->nce)
>> + continue;
>> + if (controller->chips[n]) {
>> + dev_err(&cdev->dev,
>> + "chip-id %u already in use in use by %s\n",
>> + n, dev_name(controller->chips[n]->nor.dev));
>> + continue;
>> + }
>> +
>> + chip = devm_kzalloc(controller->dev, sizeof(*chip), GFP_KERNEL);
>> + if (!chip)
>> + continue;
>> + chip->controller = controller;
>> + chip->ctl = controller->regs + info->ctl0 + n * 4;
>> + chip->cs = n;
>> +
>> + chip->nor.dev = &cdev->dev;
>> + chip->nor.priv = chip;
>> + spi_nor_set_flash_node(&chip->nor, child);
>> + chip->nor.mtd.name = of_get_property(child, "label", NULL);
>> + chip->nor.read = aspeed_smc_read_user;
>> + chip->nor.write = aspeed_smc_write_user;
>> + chip->nor.read_reg = aspeed_smc_read_reg;
>> + chip->nor.write_reg = aspeed_smc_write_reg;
>> +
>> + err = aspeed_smc_chip_setup_init(chip, r);
>> + if (err)
>> + continue;
>> +
>> + /*
>> + * XXX Add support for SPI_NOR_QUAD and SPI_NOR_DUAL attach
>> + * when board support is present as determined by of property.
>> + */
>> + err = spi_nor_scan(&chip->nor, NULL, SPI_NOR_NORMAL);
>> + if (err)
>> + continue;
>> +
>> + err = aspeed_smc_chip_setup_finish(chip);
>> + if (err)
>> + continue;
>> +
>> + err = mtd_device_register(&chip->nor.mtd, NULL, 0);
>> + if (err)
>> + continue;
>
> What happens if some chip fails to register ?
That's not correctly handled ... I have a fix for it.
Thanks,
C.
>
>> + controller->chips[n] = chip;
>> + }
>> +
>> + /* Were any children registered? */
>> + for (n = 0; n < info->nce; n++)
>> + if (controller->chips[n])
>> + break;
>> +
>> + if (n == info->nce)
>> + return -ENODEV;
>> +
>> + return 0;
>> +}
>> +
>> +static struct platform_driver aspeed_smc_driver = {
>> + .probe = aspeed_smc_probe,
>> + .remove = aspeed_smc_remove,
>> + .driver = {
>> + .name = DEVICE_NAME,
>> + .of_match_table = aspeed_smc_matches,
>> + }
>> +};
>> +
>> +module_platform_driver(aspeed_smc_driver);
>> +
>> +MODULE_DESCRIPTION("ASPEED Static Memory Controller Driver");
>> +MODULE_AUTHOR("Milton Miller");
>> +MODULE_LICENSE("GPL v2");
>>
>
>
More information about the linux-mtd
mailing list