[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