Message ID | 20220325100849.2019209-4-clg@kaod.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | spi: spi-mem: Convert Aspeed SMC driver to spi-mem | expand |
Mark, On 3/25/22 11:08, Cédric Le Goater wrote: > This SPI driver adds support for the Aspeed static memory controllers > of the AST2600, AST2500 and AST2400 SoCs using the spi-mem interface. > > * AST2600 Firmware SPI Memory Controller (FMC) > . BMC firmware > . 3 chip select pins (CE0 ~ CE2) > . Only supports SPI type flash memory > . different segment register interface > . single, dual and quad mode. > > * AST2600 SPI Flash Controller (SPI1 and SPI2) > . host firmware > . 2 chip select pins (CE0 ~ CE1) > . different segment register interface > . single, dual and quad mode. > > * AST2500 Firmware SPI Memory Controller (FMC) > . BMC firmware > . 3 chip select pins (CE0 ~ CE2) > . supports SPI type flash memory (CE0-CE1) > . CE2 can be of NOR type flash but this is not supported by the driver > . single, dual mode. > > * AST2500 SPI Flash Controller (SPI1 and SPI2) > . host firmware > . 2 chip select pins (CE0 ~ CE1) > . single, dual mode. > > * AST2400 New Static Memory Controller (also referred as FMC) > . BMC firmware > . New register set > . 5 chip select pins (CE0 ∼ CE4) > . supports NOR flash, NAND flash and SPI flash memory. > . single, dual and quad mode. > > Each controller has a memory range on which flash devices contents are > mapped. Each device is assigned a window that can be changed at bootime > with the Segment Address Registers. > > Each SPI flash device can then be accessed in two modes: Command and > User. When in User mode, SPI transfers are initiated with accesses to > the memory segment of a device. When in Command mode, memory > operations on the memory segment of a device generate SPI commands > automatically using a Control Register for the settings. > > This initial patch adds support for User mode. Command mode needs a little > more work to check that the memory window on the AHB bus fits the device > size. It will come later when support for direct mapping is added. > > Single and dual mode RX transfers are supported. Other types than SPI > are not supported. > > Reviewed-by: Joel Stanley <joel@jms.id.au> > Tested-by: Joel Stanley <joel@jms.id.au> > Tested-by: Tao Ren <rentao.bupt@gmail.com> > Signed-off-by: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com> > Signed-off-by: Cédric Le Goater <clg@kaod.org> > --- > drivers/mtd/spi-nor/controllers/aspeed-smc.c | 910 ------------------ This patchset was sent a bit early and commit 7f852ec58af6 ("mtd: aspeed-smc: improve probe resilience") was applied after which conflicts with the removal of the aspeed-smc.c file. Would you prefer a rebase and resend ? on -rc2 when the tree is stabilized ? Thanks, C. > drivers/spi/spi-aspeed-smc.c | 709 ++++++++++++++ > .../devicetree/bindings/mtd/aspeed-smc.txt | 51 - > MAINTAINERS | 1 + > drivers/mtd/spi-nor/controllers/Kconfig | 10 - > drivers/mtd/spi-nor/controllers/Makefile | 1 - > drivers/spi/Kconfig | 11 + > drivers/spi/Makefile | 1 + > 8 files changed, 722 insertions(+), 972 deletions(-) > delete mode 100644 drivers/mtd/spi-nor/controllers/aspeed-smc.c > create mode 100644 drivers/spi/spi-aspeed-smc.c > delete mode 100644 Documentation/devicetree/bindings/mtd/aspeed-smc.txt > > diff --git a/drivers/mtd/spi-nor/controllers/aspeed-smc.c b/drivers/mtd/spi-nor/controllers/aspeed-smc.c > deleted file mode 100644 > index 7225870e8b18..000000000000 > --- a/drivers/mtd/spi-nor/controllers/aspeed-smc.c > +++ /dev/null > @@ -1,910 +0,0 @@ > -// SPDX-License-Identifier: GPL-2.0-or-later > -/* > - * ASPEED Static Memory Controller driver > - * > - * Copyright (c) 2015-2016, IBM Corporation. > - */ > - > -#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/sizes.h> > -#include <linux/sysfs.h> > - > -#define DEVICE_NAME "aspeed-smc" > - > -/* > - * The driver only support SPI flash > - */ > -enum aspeed_smc_flash_type { > - smc_type_nor = 0, > - smc_type_nand = 1, > - smc_type_spi = 2, > -}; > - > -struct aspeed_smc_chip; > - > -struct aspeed_smc_info { > - u32 maxsize; /* maximum size of chip window */ > - u8 nce; /* number of chip enables */ > - bool hastype; /* flash type field exists in config reg */ > - u8 we0; /* shift for write enable bit for CE0 */ > - u8 ctl0; /* offset in regs of ctl for CE0 */ > - > - void (*set_4b)(struct aspeed_smc_chip *chip); > -}; > - > -static void aspeed_smc_chip_set_4b_spi_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, > - .hastype = true, > - .we0 = 16, > - .ctl0 = 0x10, > - .set_4b = aspeed_smc_chip_set_4b, > -}; > - > -static const struct aspeed_smc_info spi_2400_info = { > - .maxsize = 64 * 1024 * 1024, > - .nce = 1, > - .hastype = false, > - .we0 = 0, > - .ctl0 = 0x04, > - .set_4b = aspeed_smc_chip_set_4b_spi_2400, > -}; > - > -static const struct aspeed_smc_info fmc_2500_info = { > - .maxsize = 256 * 1024 * 1024, > - .nce = 3, > - .hastype = true, > - .we0 = 16, > - .ctl0 = 0x10, > - .set_4b = aspeed_smc_chip_set_4b, > -}; > - > -static const struct aspeed_smc_info spi_2500_info = { > - .maxsize = 128 * 1024 * 1024, > - .nce = 2, > - .hastype = false, > - .we0 = 16, > - .ctl0 = 0x10, > - .set_4b = aspeed_smc_chip_set_4b, > -}; > - > -enum aspeed_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 */ > - smc_max, > -}; > - > -struct aspeed_smc_controller; > - > -struct aspeed_smc_chip { > - int cs; > - struct aspeed_smc_controller *controller; > - void __iomem *ctl; /* control register */ > - void __iomem *ahb_base; /* base of chip window */ > - u32 ahb_window_size; /* chip mapping window size */ > - u32 ctl_val[smc_max]; /* control settings */ > - enum aspeed_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 *ahb_base; /* per-chip windows resource */ > - u32 ahb_window_size; /* full mapping window size */ > - > - struct aspeed_smc_chip *chips[]; /* pointers to attached chips */ > -}; > - > -/* > - * SPI Flash Configuration Register (AST2500 SPI) > - * or > - * Type setting Register (AST2500 FMC). > - * CE0 and CE1 can only be of type SPI. CE2 can be of type NOR but the > - * driver does not support it. > - */ > -#define CONFIG_REG 0x0 > -#define CONFIG_DISABLE_LEGACY BIT(31) /* 1 */ > - > -#define CONFIG_CE2_WRITE BIT(18) > -#define CONFIG_CE1_WRITE BIT(17) > -#define CONFIG_CE0_WRITE BIT(16) > - > -#define CONFIG_CE2_TYPE BIT(4) /* AST2500 FMC only */ > -#define CONFIG_CE1_TYPE BIT(2) /* AST2500 FMC only */ > -#define CONFIG_CE0_TYPE BIT(0) /* AST2500 FMC only */ > - > -/* > - * CE Control Register > - */ > -#define CE_CONTROL_REG 0x4 > - > -/* > - * CEx Control Register > - */ > -#define CONTROL_AAF_MODE BIT(31) > -#define CONTROL_IO_MODE_MASK GENMASK(30, 28) > -#define CONTROL_IO_DUAL_DATA BIT(29) > -#define CONTROL_IO_DUAL_ADDR_DATA (BIT(29) | BIT(28)) > -#define CONTROL_IO_QUAD_DATA BIT(30) > -#define CONTROL_IO_QUAD_ADDR_DATA (BIT(30) | BIT(28)) > -#define CONTROL_CE_INACTIVE_SHIFT 24 > -#define CONTROL_CE_INACTIVE_MASK GENMASK(27, \ > - CONTROL_CE_INACTIVE_SHIFT) > -/* 0 = 16T ... 15 = 1T T=HCLK */ > -#define CONTROL_COMMAND_SHIFT 16 > -#define CONTROL_DUMMY_COMMAND_OUT BIT(15) > -#define CONTROL_IO_DUMMY_HI BIT(14) > -#define CONTROL_IO_DUMMY_HI_SHIFT 14 > -#define CONTROL_CLK_DIV4 BIT(13) /* others */ > -#define CONTROL_IO_ADDRESS_4B BIT(13) /* AST2400 SPI */ > -#define CONTROL_RW_MERGE BIT(12) > -#define CONTROL_IO_DUMMY_LO_SHIFT 6 > -#define CONTROL_IO_DUMMY_LO GENMASK(7, \ > - CONTROL_IO_DUMMY_LO_SHIFT) > -#define CONTROL_IO_DUMMY_MASK (CONTROL_IO_DUMMY_HI | \ > - CONTROL_IO_DUMMY_LO) > -#define CONTROL_IO_DUMMY_SET(dummy) \ > - (((((dummy) >> 2) & 0x1) << CONTROL_IO_DUMMY_HI_SHIFT) | \ > - (((dummy) & 0x3) << CONTROL_IO_DUMMY_LO_SHIFT)) > - > -#define CONTROL_CLOCK_FREQ_SEL_SHIFT 8 > -#define CONTROL_CLOCK_FREQ_SEL_MASK GENMASK(11, \ > - CONTROL_CLOCK_FREQ_SEL_SHIFT) > -#define CONTROL_LSB_FIRST BIT(5) > -#define CONTROL_CLOCK_MODE_3 BIT(4) > -#define CONTROL_IN_DUAL_DATA BIT(3) > -#define CONTROL_CE_STOP_ACTIVE_CONTROL BIT(2) > -#define CONTROL_COMMAND_MODE_MASK GENMASK(1, 0) > -#define CONTROL_COMMAND_MODE_NORMAL 0 > -#define CONTROL_COMMAND_MODE_FREAD 1 > -#define CONTROL_COMMAND_MODE_WRITE 2 > -#define CONTROL_COMMAND_MODE_USER 3 > - > -#define CONTROL_KEEP_MASK \ > - (CONTROL_AAF_MODE | CONTROL_CE_INACTIVE_MASK | CONTROL_CLK_DIV4 | \ > - CONTROL_CLOCK_FREQ_SEL_MASK | CONTROL_LSB_FIRST | CONTROL_CLOCK_MODE_3) > - > -/* > - * The Segment Register uses a 8MB unit to encode the start address > - * and the end address of the mapping window of a flash SPI slave : > - * > - * | byte 1 | byte 2 | byte 3 | byte 4 | > - * +--------+--------+--------+--------+ > - * | end | start | 0 | 0 | > - */ > -#define SEGMENT_ADDR_REG0 0x30 > -#define SEGMENT_ADDR_START(_r) ((((_r) >> 16) & 0xFF) << 23) > -#define SEGMENT_ADDR_END(_r) ((((_r) >> 24) & 0xFF) << 23) > -#define SEGMENT_ADDR_VALUE(start, end) \ > - (((((start) >> 23) & 0xFF) << 16) | ((((end) >> 23) & 0xFF) << 24)) > -#define SEGMENT_ADDR_REG(controller, cs) \ > - ((controller)->regs + SEGMENT_ADDR_REG0 + (cs) * 4) > - > -/* > - * 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, void __iomem *src, size_t len) > -{ > - size_t offset = 0; > - > - if (IS_ALIGNED((uintptr_t)src, sizeof(uintptr_t)) && > - IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { > - ioread32_rep(src, buf, len >> 2); > - offset = len & ~0x3; > - len -= offset; > - } > - ioread8_rep(src, (u8 *)buf + offset, len); > - return 0; > -} > - > -static int aspeed_smc_write_to_ahb(void __iomem *dst, const void *buf, > - size_t len) > -{ > - size_t offset = 0; > - > - if (IS_ALIGNED((uintptr_t)dst, sizeof(uintptr_t)) && > - IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { > - iowrite32_rep(dst, buf, len >> 2); > - offset = len & ~0x3; > - len -= offset; > - } > - iowrite8_rep(dst, (const u8 *)buf + offset, len); > - return 0; > -} > - > -static inline u32 aspeed_smc_chip_write_bit(struct aspeed_smc_chip *chip) > -{ > - return BIT(chip->controller->info->we0 + chip->cs); > -} > - > -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)) > - return; > - > - 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_COMMAND_MODE_USER | > - CONTROL_CE_STOP_ACTIVE_CONTROL; > - writel(ctl, chip->ctl); > - > - ctl &= ~CONTROL_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_COMMAND_MODE_USER | > - CONTROL_CE_STOP_ACTIVE_CONTROL; > - > - writel(ctl2, chip->ctl); /* stop user CE control */ > - writel(ctl, chip->ctl); /* default to fread or read mode */ > -} > - > -static int aspeed_smc_prep(struct spi_nor *nor) > -{ > - struct aspeed_smc_chip *chip = nor->priv; > - > - mutex_lock(&chip->controller->mutex); > - return 0; > -} > - > -static void aspeed_smc_unprep(struct spi_nor *nor) > -{ > - struct aspeed_smc_chip *chip = nor->priv; > - > - mutex_unlock(&chip->controller->mutex); > -} > - > -static int aspeed_smc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, > - size_t len) > -{ > - struct aspeed_smc_chip *chip = nor->priv; > - > - aspeed_smc_start_user(nor); > - aspeed_smc_write_to_ahb(chip->ahb_base, &opcode, 1); > - aspeed_smc_read_from_ahb(buf, chip->ahb_base, len); > - aspeed_smc_stop_user(nor); > - return 0; > -} > - > -static int aspeed_smc_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf, > - size_t len) > -{ > - struct aspeed_smc_chip *chip = nor->priv; > - > - aspeed_smc_start_user(nor); > - aspeed_smc_write_to_ahb(chip->ahb_base, &opcode, 1); > - aspeed_smc_write_to_ahb(chip->ahb_base, buf, len); > - aspeed_smc_stop_user(nor); > - 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 |= cmd << 24; > - > - temp = cpu_to_be32(cmdaddr); > - aspeed_smc_write_to_ahb(chip->ahb_base, &temp, 4); > - break; > - case 4: > - temp = cpu_to_be32(addr); > - aspeed_smc_write_to_ahb(chip->ahb_base, &cmd, 1); > - aspeed_smc_write_to_ahb(chip->ahb_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; > - int i; > - u8 dummy = 0xFF; > - > - aspeed_smc_start_user(nor); > - aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from); > - for (i = 0; i < chip->nor.read_dummy / 8; i++) > - aspeed_smc_write_to_ahb(chip->ahb_base, &dummy, sizeof(dummy)); > - > - aspeed_smc_read_from_ahb(read_buf, chip->ahb_base, len); > - aspeed_smc_stop_user(nor); > - 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; > - > - aspeed_smc_start_user(nor); > - aspeed_smc_send_cmd_addr(nor, nor->program_opcode, to); > - aspeed_smc_write_to_ahb(chip->ahb_base, write_buf, len); > - aspeed_smc_stop_user(nor); > - return len; > -} > - > -static int aspeed_smc_unregister(struct aspeed_smc_controller *controller) > -{ > - struct aspeed_smc_chip *chip; > - 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 int aspeed_smc_remove(struct platform_device *dev) > -{ > - return aspeed_smc_unregister(platform_get_drvdata(dev)); > -} > - > -static const struct of_device_id aspeed_smc_matches[] = { > - { .compatible = "aspeed,ast2400-fmc", .data = &fmc_2400_info }, > - { .compatible = "aspeed,ast2400-spi", .data = &spi_2400_info }, > - { .compatible = "aspeed,ast2500-fmc", .data = &fmc_2500_info }, > - { .compatible = "aspeed,ast2500-spi", .data = &spi_2500_info }, > - { } > -}; > -MODULE_DEVICE_TABLE(of, aspeed_smc_matches); > - > -/* > - * Each chip has a mapping window defined by a segment address > - * register defining a start and an end address on the AHB bus. These > - * addresses can be configured to fit the chip size and offer a > - * contiguous memory region across chips. For the moment, we only > - * check that each chip segment is valid. > - */ > -static void __iomem *aspeed_smc_chip_base(struct aspeed_smc_chip *chip, > - struct resource *res) > -{ > - struct aspeed_smc_controller *controller = chip->controller; > - u32 offset = 0; > - u32 reg; > - > - if (controller->info->nce > 1) { > - reg = readl(SEGMENT_ADDR_REG(controller, chip->cs)); > - > - if (SEGMENT_ADDR_START(reg) >= SEGMENT_ADDR_END(reg)) > - return NULL; > - > - offset = SEGMENT_ADDR_START(reg) - res->start; > - } > - > - return controller->ahb_base + offset; > -} > - > -static u32 aspeed_smc_ahb_base_phy(struct aspeed_smc_controller *controller) > -{ > - u32 seg0_val = readl(SEGMENT_ADDR_REG(controller, 0)); > - > - return SEGMENT_ADDR_START(seg0_val); > -} > - > -static u32 chip_set_segment(struct aspeed_smc_chip *chip, u32 cs, u32 start, > - u32 size) > -{ > - struct aspeed_smc_controller *controller = chip->controller; > - void __iomem *seg_reg; > - u32 seg_oldval, seg_newval, ahb_base_phy, end; > - > - ahb_base_phy = aspeed_smc_ahb_base_phy(controller); > - > - seg_reg = SEGMENT_ADDR_REG(controller, cs); > - seg_oldval = readl(seg_reg); > - > - /* > - * If the chip size is not specified, use the default segment > - * size, but take into account the possible overlap with the > - * previous segment > - */ > - if (!size) > - size = SEGMENT_ADDR_END(seg_oldval) - start; > - > - /* > - * The segment cannot exceed the maximum window size of the > - * controller. > - */ > - if (start + size > ahb_base_phy + controller->ahb_window_size) { > - size = ahb_base_phy + controller->ahb_window_size - start; > - dev_warn(chip->nor.dev, "CE%d window resized to %dMB", > - cs, size >> 20); > - } > - > - end = start + size; > - seg_newval = SEGMENT_ADDR_VALUE(start, end); > - writel(seg_newval, seg_reg); > - > - /* > - * Restore default value if something goes wrong. The chip > - * might have set some bogus value and we would loose access > - * to the chip. > - */ > - if (seg_newval != readl(seg_reg)) { > - dev_err(chip->nor.dev, "CE%d window invalid", cs); > - writel(seg_oldval, seg_reg); > - start = SEGMENT_ADDR_START(seg_oldval); > - end = SEGMENT_ADDR_END(seg_oldval); > - size = end - start; > - } > - > - dev_info(chip->nor.dev, "CE%d window [ 0x%.8x - 0x%.8x ] %dMB", > - cs, start, end, size >> 20); > - > - return size; > -} > - > -/* > - * The segment register defines the mapping window on the AHB bus and > - * it needs to be configured depending on the chip size. The segment > - * register of the following CE also needs to be tuned in order to > - * provide a contiguous window across multiple chips. > - * > - * This is expected to be called in increasing CE order > - */ > -static u32 aspeed_smc_chip_set_segment(struct aspeed_smc_chip *chip) > -{ > - struct aspeed_smc_controller *controller = chip->controller; > - u32 ahb_base_phy, start; > - u32 size = chip->nor.mtd.size; > - > - /* > - * Each controller has a chip size limit for direct memory > - * access > - */ > - if (size > controller->info->maxsize) > - size = controller->info->maxsize; > - > - /* > - * The AST2400 SPI controller only handles one chip and does > - * not have segment registers. Let's use the chip size for the > - * AHB window. > - */ > - if (controller->info == &spi_2400_info) > - goto out; > - > - /* > - * The AST2500 SPI controller has a HW bug when the CE0 chip > - * size reaches 128MB. Enforce a size limit of 120MB to > - * prevent the controller from using bogus settings in the > - * segment register. > - */ > - if (chip->cs == 0 && controller->info == &spi_2500_info && > - size == SZ_128M) { > - size = 120 << 20; > - dev_info(chip->nor.dev, > - "CE%d window resized to %dMB (AST2500 HW quirk)", > - chip->cs, size >> 20); > - } > - > - ahb_base_phy = aspeed_smc_ahb_base_phy(controller); > - > - /* > - * As a start address for the current segment, use the default > - * start address if we are handling CE0 or use the previous > - * segment ending address > - */ > - if (chip->cs) { > - u32 prev = readl(SEGMENT_ADDR_REG(controller, chip->cs - 1)); > - > - start = SEGMENT_ADDR_END(prev); > - } else { > - start = ahb_base_phy; > - } > - > - size = chip_set_segment(chip, chip->cs, start, size); > - > - /* Update chip base address on the AHB bus */ > - chip->ahb_base = controller->ahb_base + (start - ahb_base_phy); > - > - /* > - * Now, make sure the next segment does not overlap with the > - * current one we just configured, even if there is no > - * available chip. That could break access in Command Mode. > - */ > - if (chip->cs < controller->info->nce - 1) > - chip_set_segment(chip, chip->cs + 1, start + size, 0); > - > -out: > - if (size < chip->nor.mtd.size) > - dev_warn(chip->nor.dev, > - "CE%d window too small for chip %dMB", > - chip->cs, (u32)chip->nor.mtd.size >> 20); > - > - return size; > -} > - > -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; > - > - chip->type = type; > - > - reg = readl(controller->regs + CONFIG_REG); > - reg &= ~(3 << (chip->cs * 2)); > - reg |= chip->type << (chip->cs * 2); > - writel(reg, controller->regs + CONFIG_REG); > -} > - > -/* > - * The first chip of the AST2500 FMC flash controller is strapped by > - * hardware, or autodetected, but other chips need to be set. Enforce > - * the 4B setting for all chips. > - */ > -static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip) > -{ > - struct aspeed_smc_controller *controller = chip->controller; > - u32 reg; > - > - 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_spi_2400(struct aspeed_smc_chip *chip) > -{ > - chip->ctl_val[smc_base] |= CONTROL_IO_ADDRESS_4B; > - chip->ctl_val[smc_read] |= CONTROL_IO_ADDRESS_4B; > -} > - > -static int aspeed_smc_chip_setup_init(struct aspeed_smc_chip *chip, > - struct resource *res) > -{ > - 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 */ > - if (info->hastype) > - aspeed_smc_chip_set_type(chip, smc_type_spi); > - > - /* > - * Configure chip base address in memory > - */ > - chip->ahb_base = aspeed_smc_chip_base(chip, res); > - if (!chip->ahb_base) { > - dev_warn(chip->nor.dev, "CE%d window closed", chip->cs); > - return -EINVAL; > - } > - > - /* > - * Get value of the inherited control register. U-Boot usually > - * does some timing calibration on the FMC chip, so it's good > - * to keep them. In the future, we should handle calibration > - * from Linux. > - */ > - reg = readl(chip->ctl); > - dev_dbg(controller->dev, "control register: %08x\n", reg); > - > - base_reg = reg & CONTROL_KEEP_MASK; > - if (base_reg != reg) { > - dev_dbg(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_COMMAND_MODE_MASK) == > - CONTROL_COMMAND_MODE_NORMAL) > - chip->ctl_val[smc_read] = reg; > - else > - chip->ctl_val[smc_read] = chip->ctl_val[smc_base] | > - CONTROL_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); > - > - /* This is for direct AHB access when using Command Mode. */ > - chip->ahb_window_size = aspeed_smc_chip_set_segment(chip); > - > - /* > - * base mode has not been optimized yet. use it for writes. > - */ > - chip->ctl_val[smc_write] = chip->ctl_val[smc_base] | > - chip->nor.program_opcode << CONTROL_COMMAND_SHIFT | > - CONTROL_COMMAND_MODE_WRITE; > - > - dev_dbg(controller->dev, "write control register: %08x\n", > - chip->ctl_val[smc_write]); > - > - /* > - * TODO: Adjust clocks if fast read is supported and interpret > - * SPI NOR flags to adjust controller settings. > - */ > - if (chip->nor.read_proto == SNOR_PROTO_1_1_1) { > - if (chip->nor.read_dummy == 0) > - cmd = CONTROL_COMMAND_MODE_NORMAL; > - else > - cmd = CONTROL_COMMAND_MODE_FREAD; > - } else { > - dev_err(chip->nor.dev, "unsupported SPI read mode\n"); > - return -EINVAL; > - } > - > - chip->ctl_val[smc_read] |= cmd | > - CONTROL_IO_DUMMY_SET(chip->nor.read_dummy / 8); > - > - dev_dbg(controller->dev, "base control register: %08x\n", > - chip->ctl_val[smc_read]); > - return 0; > -} > - > -static const struct spi_nor_controller_ops aspeed_smc_controller_ops = { > - .prepare = aspeed_smc_prep, > - .unprepare = aspeed_smc_unprep, > - .read_reg = aspeed_smc_read_reg, > - .write_reg = aspeed_smc_write_reg, > - .read = aspeed_smc_read_user, > - .write = aspeed_smc_write_user, > -}; > - > -static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller, > - struct device_node *np, struct resource *r) > -{ > - const struct spi_nor_hwcaps hwcaps = { > - .mask = SNOR_HWCAPS_READ | > - SNOR_HWCAPS_READ_FAST | > - SNOR_HWCAPS_PP, > - }; > - const struct aspeed_smc_info *info = controller->info; > - struct device *dev = controller->dev; > - struct device_node *child; > - unsigned int cs; > - int ret = -ENODEV; > - > - for_each_available_child_of_node(np, child) { > - struct aspeed_smc_chip *chip; > - struct spi_nor *nor; > - struct mtd_info *mtd; > - > - /* This driver does not support NAND or NOR flash devices. */ > - if (!of_device_is_compatible(child, "jedec,spi-nor")) > - continue; > - > - ret = of_property_read_u32(child, "reg", &cs); > - if (ret) { > - dev_err(dev, "Couldn't not read chip select.\n"); > - break; > - } > - > - if (cs >= info->nce) { > - dev_err(dev, "Chip select %d out of range.\n", > - cs); > - ret = -ERANGE; > - break; > - } > - > - if (controller->chips[cs]) { > - dev_err(dev, "Chip select %d already in use by %s\n", > - cs, dev_name(controller->chips[cs]->nor.dev)); > - ret = -EBUSY; > - break; > - } > - > - chip = devm_kzalloc(controller->dev, sizeof(*chip), GFP_KERNEL); > - if (!chip) { > - ret = -ENOMEM; > - break; > - } > - > - chip->controller = controller; > - chip->ctl = controller->regs + info->ctl0 + cs * 4; > - chip->cs = cs; > - > - nor = &chip->nor; > - mtd = &nor->mtd; > - > - nor->dev = dev; > - nor->priv = chip; > - spi_nor_set_flash_node(nor, child); > - nor->controller_ops = &aspeed_smc_controller_ops; > - > - ret = aspeed_smc_chip_setup_init(chip, r); > - if (ret) > - break; > - > - /* > - * TODO: Add support for Dual and Quad SPI protocols > - * attach when board support is present as determined > - * by of property. > - */ > - ret = spi_nor_scan(nor, NULL, &hwcaps); > - if (ret) > - break; > - > - ret = aspeed_smc_chip_setup_finish(chip); > - if (ret) > - break; > - > - ret = mtd_device_register(mtd, NULL, 0); > - if (ret) > - break; > - > - controller->chips[cs] = chip; > - } > - > - if (ret) { > - of_node_put(child); > - aspeed_smc_unregister(controller); > - } > - > - return ret; > -} > - > -static int aspeed_smc_probe(struct platform_device *pdev) > -{ > - struct device_node *np = pdev->dev.of_node; > - struct device *dev = &pdev->dev; > - struct aspeed_smc_controller *controller; > - const struct of_device_id *match; > - const struct aspeed_smc_info *info; > - struct resource *res; > - int ret; > - > - match = of_match_device(aspeed_smc_matches, &pdev->dev); > - if (!match || !match->data) > - return -ENODEV; > - info = match->data; > - > - controller = devm_kzalloc(&pdev->dev, > - struct_size(controller, chips, info->nce), > - GFP_KERNEL); > - if (!controller) > - return -ENOMEM; > - controller->info = info; > - controller->dev = dev; > - > - mutex_init(&controller->mutex); > - platform_set_drvdata(pdev, controller); > - > - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > - controller->regs = devm_ioremap_resource(dev, res); > - if (IS_ERR(controller->regs)) > - return PTR_ERR(controller->regs); > - > - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); > - controller->ahb_base = devm_ioremap_resource(dev, res); > - if (IS_ERR(controller->ahb_base)) > - return PTR_ERR(controller->ahb_base); > - > - controller->ahb_window_size = resource_size(res); > - > - ret = aspeed_smc_setup_flash(controller, np, res); > - if (ret) > - dev_err(dev, "Aspeed SMC probe failed %d\n", ret); > - > - return ret; > -} > - > -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("Cedric Le Goater <clg@kaod.org>"); > -MODULE_LICENSE("GPL v2"); > diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c > new file mode 100644 > index 000000000000..997ec2e45118 > --- /dev/null > +++ b/drivers/spi/spi-aspeed-smc.c > @@ -0,0 +1,709 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * ASPEED FMC/SPI Memory Controller Driver > + * > + * Copyright (c) 2015-2022, IBM Corporation. > + * Copyright (c) 2020, ASPEED Corporation. > + */ > + > +#include <linux/clk.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_platform.h> > +#include <linux/platform_device.h> > +#include <linux/spi/spi.h> > +#include <linux/spi/spi-mem.h> > + > +#define DEVICE_NAME "spi-aspeed-smc" > + > +/* Type setting Register */ > +#define CONFIG_REG 0x0 > +#define CONFIG_TYPE_SPI 0x2 > + > +/* CE Control Register */ > +#define CE_CTRL_REG 0x4 > + > +/* CEx Control Register */ > +#define CE0_CTRL_REG 0x10 > +#define CTRL_IO_MODE_MASK GENMASK(30, 28) > +#define CTRL_IO_SINGLE_DATA 0x0 > +#define CTRL_IO_DUAL_DATA BIT(29) > +#define CTRL_IO_QUAD_DATA BIT(30) > +#define CTRL_COMMAND_SHIFT 16 > +#define CTRL_IO_DUMMY_SET(dummy) \ > + (((((dummy) >> 2) & 0x1) << 14) | (((dummy) & 0x3) << 6)) > +#define CTRL_CE_STOP_ACTIVE BIT(2) > +#define CTRL_IO_MODE_CMD_MASK GENMASK(1, 0) > +#define CTRL_IO_MODE_NORMAL 0x0 > +#define CTRL_IO_MODE_READ 0x1 > +#define CTRL_IO_MODE_WRITE 0x2 > +#define CTRL_IO_MODE_USER 0x3 > + > +#define CTRL_IO_CMD_MASK 0xf0ff40c3 > + > +/* CEx Address Decoding Range Register */ > +#define CE0_SEGMENT_ADDR_REG 0x30 > + > +enum aspeed_spi_ctl_reg_value { > + ASPEED_SPI_BASE, > + ASPEED_SPI_READ, > + ASPEED_SPI_WRITE, > + ASPEED_SPI_MAX, > +}; > + > +struct aspeed_spi; > + > +struct aspeed_spi_chip { > + struct aspeed_spi *aspi; > + u32 cs; > + void __iomem *ctl; > + void __iomem *ahb_base; > + u32 ahb_window_size; > + u32 ctl_val[ASPEED_SPI_MAX]; > + u32 clk_freq; > +}; > + > +struct aspeed_spi_data { > + u32 ctl0; > + u32 max_cs; > + bool hastype; > + u32 mode_bits; > + u32 we0; > + > + u32 (*segment_start)(struct aspeed_spi *aspi, u32 reg); > + u32 (*segment_end)(struct aspeed_spi *aspi, u32 reg); > + u32 (*segment_reg)(struct aspeed_spi *aspi, u32 start, u32 end); > +}; > + > +#define ASPEED_SPI_MAX_NUM_CS 5 > + > +struct aspeed_spi { > + const struct aspeed_spi_data *data; > + > + void __iomem *regs; > + void __iomem *ahb_base; > + u32 ahb_base_phy; > + u32 ahb_window_size; > + struct device *dev; > + > + struct clk *clk; > + u32 clk_freq; > + > + struct aspeed_spi_chip chips[ASPEED_SPI_MAX_NUM_CS]; > +}; > + > +static u32 aspeed_spi_get_io_mode(const struct spi_mem_op *op) > +{ > + switch (op->data.buswidth) { > + 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; > + } > +} > + > +static void aspeed_spi_set_io_mode(struct aspeed_spi_chip *chip, u32 io_mode) > +{ > + u32 ctl; > + > + if (io_mode > 0) { > + ctl = readl(chip->ctl) & ~CTRL_IO_MODE_MASK; > + ctl |= io_mode; > + writel(ctl, chip->ctl); > + } > +} > + > +static void aspeed_spi_start_user(struct aspeed_spi_chip *chip) > +{ > + u32 ctl = chip->ctl_val[ASPEED_SPI_BASE]; > + > + ctl |= CTRL_IO_MODE_USER | CTRL_CE_STOP_ACTIVE; > + writel(ctl, chip->ctl); > + > + ctl &= ~CTRL_CE_STOP_ACTIVE; > + writel(ctl, chip->ctl); > +} > + > +static void aspeed_spi_stop_user(struct aspeed_spi_chip *chip) > +{ > + u32 ctl = chip->ctl_val[ASPEED_SPI_READ] | > + CTRL_IO_MODE_USER | CTRL_CE_STOP_ACTIVE; > + > + writel(ctl, chip->ctl); > + > + /* Restore defaults */ > + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); > +} > + > +static int aspeed_spi_read_from_ahb(void *buf, void __iomem *src, size_t len) > +{ > + size_t offset = 0; > + > + if (IS_ALIGNED((uintptr_t)src, sizeof(uintptr_t)) && > + IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { > + ioread32_rep(src, buf, len >> 2); > + offset = len & ~0x3; > + len -= offset; > + } > + ioread8_rep(src, (u8 *)buf + offset, len); > + return 0; > +} > + > +static int aspeed_spi_write_to_ahb(void __iomem *dst, const void *buf, size_t len) > +{ > + size_t offset = 0; > + > + if (IS_ALIGNED((uintptr_t)dst, sizeof(uintptr_t)) && > + IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { > + iowrite32_rep(dst, buf, len >> 2); > + offset = len & ~0x3; > + len -= offset; > + } > + iowrite8_rep(dst, (const u8 *)buf + offset, len); > + return 0; > +} > + > +static void aspeed_spi_send_cmd_addr(struct aspeed_spi_chip *chip, u8 addr_nbytes, > + u64 offset, u32 opcode) > +{ > + struct aspeed_spi *aspi = chip->aspi; > + __be32 temp; > + u32 cmdaddr; > + > + switch (addr_nbytes) { > + default: > + dev_warn_once(aspi->dev, "Unexpected address width %u, defaulting to 3", > + addr_nbytes); > + fallthrough; > + case 3: > + cmdaddr = offset & 0xFFFFFF; > + cmdaddr |= opcode << 24; > + > + temp = cpu_to_be32(cmdaddr); > + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); > + break; > + case 4: > + temp = cpu_to_be32(offset); > + aspeed_spi_write_to_ahb(chip->ahb_base, &opcode, 1); > + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); > + break; > + } > +} > + > +static int aspeed_spi_read_reg(struct aspeed_spi_chip *chip, > + const struct spi_mem_op *op) > +{ > + aspeed_spi_start_user(chip); > + aspeed_spi_write_to_ahb(chip->ahb_base, &op->cmd.opcode, 1); > + aspeed_spi_read_from_ahb(op->data.buf.in, > + chip->ahb_base, op->data.nbytes); > + aspeed_spi_stop_user(chip); > + return 0; > +} > + > +static int aspeed_spi_write_reg(struct aspeed_spi_chip *chip, > + const struct spi_mem_op *op) > +{ > + aspeed_spi_start_user(chip); > + aspeed_spi_write_to_ahb(chip->ahb_base, &op->cmd.opcode, 1); > + aspeed_spi_write_to_ahb(chip->ahb_base, op->data.buf.out, > + op->data.nbytes); > + aspeed_spi_stop_user(chip); > + return 0; > +} > + > +static ssize_t aspeed_spi_read_user(struct aspeed_spi_chip *chip, > + const struct spi_mem_op *op, > + u64 offset, size_t len, void *buf) > +{ > + int io_mode = aspeed_spi_get_io_mode(op); > + u8 dummy = 0xFF; > + int i; > + > + aspeed_spi_start_user(chip); > + aspeed_spi_send_cmd_addr(chip, op->addr.nbytes, offset, op->cmd.opcode); > + if (op->dummy.buswidth && op->dummy.nbytes) { > + for (i = 0; i < op->dummy.nbytes / op->dummy.buswidth; i++) > + aspeed_spi_write_to_ahb(chip->ahb_base, &dummy, sizeof(dummy)); > + } > + > + aspeed_spi_set_io_mode(chip, io_mode); > + > + aspeed_spi_read_from_ahb(buf, chip->ahb_base, len); > + aspeed_spi_stop_user(chip); > + return 0; > +} > + > +static ssize_t aspeed_spi_write_user(struct aspeed_spi_chip *chip, > + const struct spi_mem_op *op) > +{ > + aspeed_spi_start_user(chip); > + aspeed_spi_send_cmd_addr(chip, op->addr.nbytes, op->addr.val, op->cmd.opcode); > + aspeed_spi_write_to_ahb(chip->ahb_base, op->data.buf.out, op->data.nbytes); > + aspeed_spi_stop_user(chip); > + return 0; > +} > + > +/* support for 1-1-1, 1-1-2 or 1-1-4 */ > +static bool aspeed_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) > +{ > + 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 && op->data.buswidth > 4) > + return false; > + > + return spi_mem_default_supports_op(mem, op); > +} > + > +static int do_aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) > +{ > + struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->master); > + struct aspeed_spi_chip *chip = &aspi->chips[mem->spi->chip_select]; > + u32 addr_mode, addr_mode_backup; > + u32 ctl_val; > + int ret = 0; > + > + dev_dbg(aspi->dev, > + "CE%d %s OP %#x mode:%d.%d.%d.%d naddr:%#x ndummies:%#x len:%#x", > + chip->cs, op->data.dir == SPI_MEM_DATA_IN ? "read" : "write", > + op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth, > + op->dummy.buswidth, op->data.buswidth, > + op->addr.nbytes, op->dummy.nbytes, op->data.nbytes); > + > + addr_mode = readl(aspi->regs + CE_CTRL_REG); > + addr_mode_backup = addr_mode; > + > + ctl_val = chip->ctl_val[ASPEED_SPI_BASE]; > + ctl_val &= ~CTRL_IO_CMD_MASK; > + > + ctl_val |= op->cmd.opcode << CTRL_COMMAND_SHIFT; > + > + /* 4BYTE address mode */ > + if (op->addr.nbytes) { > + if (op->addr.nbytes == 4) > + addr_mode |= (0x11 << chip->cs); > + else > + addr_mode &= ~(0x11 << chip->cs); > + } > + > + if (op->dummy.buswidth && op->dummy.nbytes) > + ctl_val |= CTRL_IO_DUMMY_SET(op->dummy.nbytes / op->dummy.buswidth); > + > + if (op->data.nbytes != 0) { > + if (op->data.buswidth) > + ctl_val |= aspeed_spi_get_io_mode(op); > + } > + > + if (op->data.dir == SPI_MEM_DATA_OUT) > + ctl_val |= CTRL_IO_MODE_WRITE; > + else > + ctl_val |= CTRL_IO_MODE_READ; > + > + if (addr_mode != addr_mode_backup) > + writel(addr_mode, aspi->regs + CE_CTRL_REG); > + writel(ctl_val, chip->ctl); > + > + if (op->data.dir == SPI_MEM_DATA_IN) { > + if (!op->addr.nbytes) > + ret = aspeed_spi_read_reg(chip, op); > + else > + ret = aspeed_spi_read_user(chip, op, op->addr.val, > + op->data.nbytes, op->data.buf.in); > + } else { > + if (!op->addr.nbytes) > + ret = aspeed_spi_write_reg(chip, op); > + else > + ret = aspeed_spi_write_user(chip, op); > + } > + > + /* Restore defaults */ > + if (addr_mode != addr_mode_backup) > + writel(addr_mode_backup, aspi->regs + CE_CTRL_REG); > + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); > + return ret; > +} > + > +static int aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) > +{ > + int ret; > + > + ret = do_aspeed_spi_exec_op(mem, op); > + if (ret) > + dev_err(&mem->spi->dev, "operation failed: %d\n", ret); > + return ret; > +} > + > +static const char *aspeed_spi_get_name(struct spi_mem *mem) > +{ > + struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->master); > + struct device *dev = aspi->dev; > + > + return devm_kasprintf(dev, GFP_KERNEL, "%s.%d", dev_name(dev), mem->spi->chip_select); > +} > + > +struct aspeed_spi_window { > + u32 cs; > + u32 offset; > + u32 size; > +}; > + > +static void aspeed_spi_get_windows(struct aspeed_spi *aspi, > + struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS]) > +{ > + const struct aspeed_spi_data *data = aspi->data; > + u32 reg_val; > + u32 cs; > + > + for (cs = 0; cs < aspi->data->max_cs; cs++) { > + reg_val = readl(aspi->regs + CE0_SEGMENT_ADDR_REG + cs * 4); > + windows[cs].cs = cs; > + windows[cs].size = data->segment_end(aspi, reg_val) - > + data->segment_start(aspi, reg_val); > + windows[cs].offset = cs ? windows[cs - 1].offset + windows[cs - 1].size : 0; > + dev_vdbg(aspi->dev, "CE%d offset=0x%.8x size=0x%x\n", cs, > + windows[cs].offset, windows[cs].size); > + } > +} > + > +/* > + * On the AST2600, some CE windows are closed by default at reset but > + * U-Boot should open all. > + */ > +static int aspeed_spi_chip_set_default_window(struct aspeed_spi_chip *chip) > +{ > + struct aspeed_spi *aspi = chip->aspi; > + struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS] = { 0 }; > + struct aspeed_spi_window *win = &windows[chip->cs]; > + > + aspeed_spi_get_windows(aspi, windows); > + > + chip->ahb_base = aspi->ahb_base + win->offset; > + chip->ahb_window_size = win->size; > + > + dev_dbg(aspi->dev, "CE%d default window [ 0x%.8x - 0x%.8x ] %dMB", > + chip->cs, aspi->ahb_base_phy + win->offset, > + aspi->ahb_base_phy + win->offset + win->size - 1, > + win->size >> 20); > + > + return chip->ahb_window_size ? 0 : -1; > +} > + > +static const struct spi_controller_mem_ops aspeed_spi_mem_ops = { > + .supports_op = aspeed_spi_supports_op, > + .exec_op = aspeed_spi_exec_op, > + .get_name = aspeed_spi_get_name, > +}; > + > +static void aspeed_spi_chip_set_type(struct aspeed_spi *aspi, unsigned int cs, int type) > +{ > + u32 reg; > + > + reg = readl(aspi->regs + CONFIG_REG); > + reg &= ~(0x3 << (cs * 2)); > + reg |= type << (cs * 2); > + writel(reg, aspi->regs + CONFIG_REG); > +} > + > +static void aspeed_spi_chip_enable(struct aspeed_spi *aspi, unsigned int cs, bool enable) > +{ > + u32 we_bit = BIT(aspi->data->we0 + cs); > + u32 reg = readl(aspi->regs + CONFIG_REG); > + > + if (enable) > + reg |= we_bit; > + else > + reg &= ~we_bit; > + writel(reg, aspi->regs + CONFIG_REG); > +} > + > +static int aspeed_spi_setup(struct spi_device *spi) > +{ > + struct aspeed_spi *aspi = spi_controller_get_devdata(spi->master); > + const struct aspeed_spi_data *data = aspi->data; > + unsigned int cs = spi->chip_select; > + struct aspeed_spi_chip *chip = &aspi->chips[cs]; > + > + chip->aspi = aspi; > + chip->cs = cs; > + chip->ctl = aspi->regs + data->ctl0 + cs * 4; > + > + /* The driver only supports SPI type flash */ > + if (data->hastype) > + aspeed_spi_chip_set_type(aspi, cs, CONFIG_TYPE_SPI); > + > + if (aspeed_spi_chip_set_default_window(chip) < 0) { > + dev_warn(aspi->dev, "CE%d window invalid", cs); > + return -EINVAL; > + } > + > + aspeed_spi_chip_enable(aspi, cs, true); > + > + chip->ctl_val[ASPEED_SPI_BASE] = CTRL_CE_STOP_ACTIVE | CTRL_IO_MODE_USER; > + > + dev_dbg(aspi->dev, "CE%d setup done\n", cs); > + return 0; > +} > + > +static void aspeed_spi_cleanup(struct spi_device *spi) > +{ > + struct aspeed_spi *aspi = spi_controller_get_devdata(spi->master); > + unsigned int cs = spi->chip_select; > + > + aspeed_spi_chip_enable(aspi, cs, false); > + > + dev_dbg(aspi->dev, "CE%d cleanup done\n", cs); > +} > + > +static void aspeed_spi_enable(struct aspeed_spi *aspi, bool enable) > +{ > + int cs; > + > + for (cs = 0; cs < aspi->data->max_cs; cs++) > + aspeed_spi_chip_enable(aspi, cs, enable); > +} > + > +static int aspeed_spi_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + const struct aspeed_spi_data *data; > + struct spi_controller *ctlr; > + struct aspeed_spi *aspi; > + struct resource *res; > + int ret; > + > + data = of_device_get_match_data(&pdev->dev); > + if (!data) > + return -ENODEV; > + > + ctlr = devm_spi_alloc_master(dev, sizeof(*aspi)); > + if (!ctlr) > + return -ENOMEM; > + > + aspi = spi_controller_get_devdata(ctlr); > + platform_set_drvdata(pdev, aspi); > + aspi->data = data; > + aspi->dev = dev; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + aspi->regs = devm_ioremap_resource(dev, res); > + if (IS_ERR(aspi->regs)) { > + dev_err(dev, "missing AHB register window\n"); > + return PTR_ERR(aspi->regs); > + } > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); > + aspi->ahb_base = devm_ioremap_resource(dev, res); > + if (IS_ERR(aspi->ahb_base)) { > + dev_err(dev, "missing AHB mapping window\n"); > + return PTR_ERR(aspi->ahb_base); > + } > + > + aspi->ahb_window_size = resource_size(res); > + aspi->ahb_base_phy = res->start; > + > + aspi->clk = devm_clk_get(&pdev->dev, NULL); > + if (IS_ERR(aspi->clk)) { > + dev_err(dev, "missing clock\n"); > + return PTR_ERR(aspi->clk); > + } > + > + aspi->clk_freq = clk_get_rate(aspi->clk); > + if (!aspi->clk_freq) { > + dev_err(dev, "invalid clock\n"); > + return -EINVAL; > + } > + > + ret = clk_prepare_enable(aspi->clk); > + if (ret) { > + dev_err(dev, "can not enable the clock\n"); > + return ret; > + } > + > + /* IRQ is for DMA, which the driver doesn't support yet */ > + > + ctlr->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | data->mode_bits; > + ctlr->bus_num = pdev->id; > + ctlr->mem_ops = &aspeed_spi_mem_ops; > + ctlr->setup = aspeed_spi_setup; > + ctlr->cleanup = aspeed_spi_cleanup; > + ctlr->num_chipselect = data->max_cs; > + ctlr->dev.of_node = dev->of_node; > + > + ret = devm_spi_register_controller(dev, ctlr); > + if (ret) { > + dev_err(&pdev->dev, "spi_register_controller failed\n"); > + goto disable_clk; > + } > + return 0; > + > +disable_clk: > + clk_disable_unprepare(aspi->clk); > + return ret; > +} > + > +static int aspeed_spi_remove(struct platform_device *pdev) > +{ > + struct aspeed_spi *aspi = platform_get_drvdata(pdev); > + > + aspeed_spi_enable(aspi, false); > + clk_disable_unprepare(aspi->clk); > + return 0; > +} > + > +/* > + * AHB mappings > + */ > + > +/* > + * The Segment Registers of the AST2400 and AST2500 use a 8MB unit. > + * The address range is encoded with absolute addresses in the overall > + * mapping window. > + */ > +static u32 aspeed_spi_segment_start(struct aspeed_spi *aspi, u32 reg) > +{ > + return ((reg >> 16) & 0xFF) << 23; > +} > + > +static u32 aspeed_spi_segment_end(struct aspeed_spi *aspi, u32 reg) > +{ > + return ((reg >> 24) & 0xFF) << 23; > +} > + > +static u32 aspeed_spi_segment_reg(struct aspeed_spi *aspi, u32 start, u32 end) > +{ > + return (((start >> 23) & 0xFF) << 16) | (((end >> 23) & 0xFF) << 24); > +} > + > +/* > + * The Segment Registers of the AST2600 use a 1MB unit. The address > + * range is encoded with offsets in the overall mapping window. > + */ > + > +#define AST2600_SEG_ADDR_MASK 0x0ff00000 > + > +static u32 aspeed_spi_segment_ast2600_start(struct aspeed_spi *aspi, > + u32 reg) > +{ > + u32 start_offset = (reg << 16) & AST2600_SEG_ADDR_MASK; > + > + return aspi->ahb_base_phy + start_offset; > +} > + > +static u32 aspeed_spi_segment_ast2600_end(struct aspeed_spi *aspi, > + u32 reg) > +{ > + u32 end_offset = reg & AST2600_SEG_ADDR_MASK; > + > + /* segment is disabled */ > + if (!end_offset) > + return aspi->ahb_base_phy; > + > + return aspi->ahb_base_phy + end_offset + 0x100000; > +} > + > +static u32 aspeed_spi_segment_ast2600_reg(struct aspeed_spi *aspi, > + u32 start, u32 end) > +{ > + /* disable zero size segments */ > + if (start == end) > + return 0; > + > + return ((start & AST2600_SEG_ADDR_MASK) >> 16) | > + ((end - 1) & AST2600_SEG_ADDR_MASK); > +} > + > +/* > + * Platform definitions > + */ > +static const struct aspeed_spi_data ast2400_fmc_data = { > + .max_cs = 5, > + .hastype = true, > + .we0 = 16, > + .ctl0 = CE0_CTRL_REG, > + .segment_start = aspeed_spi_segment_start, > + .segment_end = aspeed_spi_segment_end, > + .segment_reg = aspeed_spi_segment_reg, > +}; > + > +static const struct aspeed_spi_data ast2500_fmc_data = { > + .max_cs = 3, > + .hastype = true, > + .we0 = 16, > + .ctl0 = CE0_CTRL_REG, > + .segment_start = aspeed_spi_segment_start, > + .segment_end = aspeed_spi_segment_end, > + .segment_reg = aspeed_spi_segment_reg, > +}; > + > +static const struct aspeed_spi_data ast2500_spi_data = { > + .max_cs = 2, > + .hastype = false, > + .we0 = 16, > + .ctl0 = CE0_CTRL_REG, > + .segment_start = aspeed_spi_segment_start, > + .segment_end = aspeed_spi_segment_end, > + .segment_reg = aspeed_spi_segment_reg, > +}; > + > +static const struct aspeed_spi_data ast2600_fmc_data = { > + .max_cs = 3, > + .hastype = false, > + .mode_bits = SPI_RX_QUAD | SPI_RX_QUAD, > + .we0 = 16, > + .ctl0 = CE0_CTRL_REG, > + .segment_start = aspeed_spi_segment_ast2600_start, > + .segment_end = aspeed_spi_segment_ast2600_end, > + .segment_reg = aspeed_spi_segment_ast2600_reg, > +}; > + > +static const struct aspeed_spi_data ast2600_spi_data = { > + .max_cs = 2, > + .hastype = false, > + .mode_bits = SPI_RX_QUAD | SPI_RX_QUAD, > + .we0 = 16, > + .ctl0 = CE0_CTRL_REG, > + .segment_start = aspeed_spi_segment_ast2600_start, > + .segment_end = aspeed_spi_segment_ast2600_end, > + .segment_reg = aspeed_spi_segment_ast2600_reg, > +}; > + > +static const struct of_device_id aspeed_spi_matches[] = { > + { .compatible = "aspeed,ast2400-fmc", .data = &ast2400_fmc_data }, > + { .compatible = "aspeed,ast2500-fmc", .data = &ast2500_fmc_data }, > + { .compatible = "aspeed,ast2500-spi", .data = &ast2500_spi_data }, > + { .compatible = "aspeed,ast2600-fmc", .data = &ast2600_fmc_data }, > + { .compatible = "aspeed,ast2600-spi", .data = &ast2600_spi_data }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, aspeed_spi_matches); > + > +static struct platform_driver aspeed_spi_driver = { > + .probe = aspeed_spi_probe, > + .remove = aspeed_spi_remove, > + .driver = { > + .name = DEVICE_NAME, > + .of_match_table = aspeed_spi_matches, > + } > +}; > + > +module_platform_driver(aspeed_spi_driver); > + > +MODULE_DESCRIPTION("ASPEED Static Memory Controller Driver"); > +MODULE_AUTHOR("Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>"); > +MODULE_AUTHOR("Cedric Le Goater <clg@kaod.org>"); > +MODULE_LICENSE("GPL v2"); > diff --git a/Documentation/devicetree/bindings/mtd/aspeed-smc.txt b/Documentation/devicetree/bindings/mtd/aspeed-smc.txt > deleted file mode 100644 > index 49f6528ef547..000000000000 > --- a/Documentation/devicetree/bindings/mtd/aspeed-smc.txt > +++ /dev/null > @@ -1,51 +0,0 @@ > -* Aspeed Firmware Memory controller > -* Aspeed SPI Flash Memory Controller > - > -The Firmware Memory Controller in the Aspeed AST2500 SoC supports > -three chip selects, two of which are always of SPI type and the third > -can be SPI or NOR type flash. These bindings only describe SPI. > - > -The two SPI flash memory controllers in the AST2500 each support two > -chip selects. > - > -Required properties: > - - compatible : Should be one of > - "aspeed,ast2400-fmc" for the AST2400 Firmware Memory Controller > - "aspeed,ast2400-spi" for the AST2400 SPI Flash memory Controller > - "aspeed,ast2500-fmc" for the AST2500 Firmware Memory Controller > - "aspeed,ast2500-spi" for the AST2500 SPI flash memory 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 - max frequency of spi bus > - > - > -Example: > -fmc: fmc@1e620000 { > - compatible = "aspeed,ast2500-fmc"; > - reg = < 0x1e620000 0x94 > - 0x20000000 0x02000000 >; > - #address-cells = <1>; > - #size-cells = <0>; > - interrupts = <19>; > - flash@0 { > - reg = < 0 >; > - compatible = "jedec,spi-nor"; > - /* spi-max-frequency = <>; */ > - /* m25p,fast-read; */ > - #address-cells = <1>; > - #size-cells = <1>; > - }; > -}; > diff --git a/MAINTAINERS b/MAINTAINERS > index 7d5f81dcd837..f4605689f41c 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -3029,6 +3029,7 @@ L: openbmc@lists.ozlabs.org (moderated for non-subscribers) > L: linux-spi@vger.kernel.org > S: Maintained > F: Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml > +F: drivers/spi/spi-aspeed-smc.c > > ASPEED VIDEO ENGINE DRIVER > M: Eddie James <eajames@linux.ibm.com> > diff --git a/drivers/mtd/spi-nor/controllers/Kconfig b/drivers/mtd/spi-nor/controllers/Kconfig > index 50f4f3484d42..ca45dcd3ffe8 100644 > --- a/drivers/mtd/spi-nor/controllers/Kconfig > +++ b/drivers/mtd/spi-nor/controllers/Kconfig > @@ -1,14 +1,4 @@ > # SPDX-License-Identifier: GPL-2.0-only > -config SPI_ASPEED_SMC > - tristate "Aspeed flash controllers in SPI mode" > - depends on ARCH_ASPEED || COMPILE_TEST > - depends on HAS_IOMEM && OF > - help > - This enables support for the Firmware Memory controller (FMC) > - in the Aspeed AST2500/AST2400 SoCs when attached to SPI NOR chips, > - and support for the SPI flash memory controller (SPI) for > - the host firmware. The implementation only supports SPI NOR. > - > config SPI_HISI_SFC > tristate "Hisilicon FMC SPI NOR Flash Controller(SFC)" > depends on ARCH_HISI || COMPILE_TEST > diff --git a/drivers/mtd/spi-nor/controllers/Makefile b/drivers/mtd/spi-nor/controllers/Makefile > index 6e2a1dc68466..0b8e1d530913 100644 > --- a/drivers/mtd/spi-nor/controllers/Makefile > +++ b/drivers/mtd/spi-nor/controllers/Makefile > @@ -1,4 +1,3 @@ > # SPDX-License-Identifier: GPL-2.0 > -obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o > obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o > obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o > diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig > index 31a2cef3790c..9f6679cb0f78 100644 > --- a/drivers/spi/Kconfig > +++ b/drivers/spi/Kconfig > @@ -101,6 +101,17 @@ config SPI_ARMADA_3700 > This enables support for the SPI controller present on the > Marvell Armada 3700 SoCs. > > +config SPI_ASPEED_SMC > + tristate "Aspeed flash controllers in SPI mode" > + depends on ARCH_ASPEED || COMPILE_TEST > + depends on OF > + help > + This enables support for the Firmware Memory controller (FMC) > + in the Aspeed AST2600, AST2500 and AST2400 SoCs when attached > + to SPI NOR chips, and support for the SPI flash memory > + controller (SPI) for the host firmware. The implementation > + only supports SPI NOR. > + > config SPI_ATMEL > tristate "Atmel SPI Controller" > depends on ARCH_AT91 || COMPILE_TEST > diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile > index 3aa28ed3f761..1566388b96ae 100644 > --- a/drivers/spi/Makefile > +++ b/drivers/spi/Makefile > @@ -19,6 +19,7 @@ obj-$(CONFIG_SPI_ALTERA_CORE) += spi-altera-core.o > obj-$(CONFIG_SPI_ALTERA_DFL) += spi-altera-dfl.o > obj-$(CONFIG_SPI_AR934X) += spi-ar934x.o > obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o > +obj-$(CONFIG_SPI_ASPEED_SMC) += spi-aspeed-smc.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
On Tue, Mar 29, 2022 at 10:56:49AM +0200, Cédric Le Goater wrote: > This patchset was sent a bit early and commit 7f852ec58af6 ("mtd: aspeed-smc: > improve probe resilience") was applied after which conflicts with the removal > of the aspeed-smc.c file. > Would you prefer a rebase and resend ? on -rc2 when the tree is stabilized ? A rebase on -rc1 (assuming the change makes it in there) would be good.
Hi Cedric, Thanks for doing the conversion. On 25/03/22 11:08AM, Cédric Le Goater wrote: > This SPI driver adds support for the Aspeed static memory controllers > of the AST2600, AST2500 and AST2400 SoCs using the spi-mem interface. > > * AST2600 Firmware SPI Memory Controller (FMC) > . BMC firmware > . 3 chip select pins (CE0 ~ CE2) > . Only supports SPI type flash memory > . different segment register interface > . single, dual and quad mode. > > * AST2600 SPI Flash Controller (SPI1 and SPI2) > . host firmware > . 2 chip select pins (CE0 ~ CE1) > . different segment register interface > . single, dual and quad mode. > > * AST2500 Firmware SPI Memory Controller (FMC) > . BMC firmware > . 3 chip select pins (CE0 ~ CE2) > . supports SPI type flash memory (CE0-CE1) > . CE2 can be of NOR type flash but this is not supported by the driver > . single, dual mode. > > * AST2500 SPI Flash Controller (SPI1 and SPI2) > . host firmware > . 2 chip select pins (CE0 ~ CE1) > . single, dual mode. > > * AST2400 New Static Memory Controller (also referred as FMC) > . BMC firmware > . New register set > . 5 chip select pins (CE0 ∼ CE4) > . supports NOR flash, NAND flash and SPI flash memory. > . single, dual and quad mode. > > Each controller has a memory range on which flash devices contents are > mapped. Each device is assigned a window that can be changed at bootime > with the Segment Address Registers. > > Each SPI flash device can then be accessed in two modes: Command and > User. When in User mode, SPI transfers are initiated with accesses to > the memory segment of a device. When in Command mode, memory > operations on the memory segment of a device generate SPI commands > automatically using a Control Register for the settings. > > This initial patch adds support for User mode. Command mode needs a little > more work to check that the memory window on the AHB bus fits the device > size. It will come later when support for direct mapping is added. > > Single and dual mode RX transfers are supported. Other types than SPI > are not supported. > > Reviewed-by: Joel Stanley <joel@jms.id.au> > Tested-by: Joel Stanley <joel@jms.id.au> > Tested-by: Tao Ren <rentao.bupt@gmail.com> > Signed-off-by: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com> > Signed-off-by: Cédric Le Goater <clg@kaod.org> > --- > drivers/mtd/spi-nor/controllers/aspeed-smc.c | 910 ------------------ > drivers/spi/spi-aspeed-smc.c | 709 ++++++++++++++ > .../devicetree/bindings/mtd/aspeed-smc.txt | 51 - > MAINTAINERS | 1 + > drivers/mtd/spi-nor/controllers/Kconfig | 10 - > drivers/mtd/spi-nor/controllers/Makefile | 1 - > drivers/spi/Kconfig | 11 + > drivers/spi/Makefile | 1 + > 8 files changed, 722 insertions(+), 972 deletions(-) > delete mode 100644 drivers/mtd/spi-nor/controllers/aspeed-smc.c > create mode 100644 drivers/spi/spi-aspeed-smc.c > delete mode 100644 Documentation/devicetree/bindings/mtd/aspeed-smc.txt > [...] > +static void aspeed_spi_send_cmd_addr(struct aspeed_spi_chip *chip, u8 addr_nbytes, > + u64 offset, u32 opcode) > +{ > + struct aspeed_spi *aspi = chip->aspi; > + __be32 temp; > + u32 cmdaddr; > + > + switch (addr_nbytes) { > + default: > + dev_warn_once(aspi->dev, "Unexpected address width %u, defaulting to 3", > + addr_nbytes); > + fallthrough; I think it is better if you reject ops where addr width is not 3 or 4. This you can drop this. Or if you really want to keep it, you can change it to a WARN_ON() and return an error. > + case 3: > + cmdaddr = offset & 0xFFFFFF; > + cmdaddr |= opcode << 24; > + > + temp = cpu_to_be32(cmdaddr); > + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); > + break; > + case 4: > + temp = cpu_to_be32(offset); > + aspeed_spi_write_to_ahb(chip->ahb_base, &opcode, 1); > + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); > + break; > + } > +} > + [...] > +/* support for 1-1-1, 1-1-2 or 1-1-4 */ > +static bool aspeed_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) > +{ > + if (op->cmd.buswidth > 1) > + return false; > + > + if (op->addr.nbytes != 0) { > + if (op->addr.buswidth > 1 || op->addr.nbytes > 4) As mentioned above, this should reject ops with addr width 1 and 2. > + return false; > + } > + > + if (op->dummy.nbytes != 0) { > + if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) > + return false; > + } > + > + if (op->data.nbytes != 0 && op->data.buswidth > 4) > + return false; > + > + return spi_mem_default_supports_op(mem, op); > +} > + > +static int do_aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) > +{ > + struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->master); > + struct aspeed_spi_chip *chip = &aspi->chips[mem->spi->chip_select]; > + u32 addr_mode, addr_mode_backup; > + u32 ctl_val; > + int ret = 0; > + > + dev_dbg(aspi->dev, > + "CE%d %s OP %#x mode:%d.%d.%d.%d naddr:%#x ndummies:%#x len:%#x", > + chip->cs, op->data.dir == SPI_MEM_DATA_IN ? "read" : "write", > + op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth, > + op->dummy.buswidth, op->data.buswidth, > + op->addr.nbytes, op->dummy.nbytes, op->data.nbytes); > + > + addr_mode = readl(aspi->regs + CE_CTRL_REG); > + addr_mode_backup = addr_mode; > + > + ctl_val = chip->ctl_val[ASPEED_SPI_BASE]; > + ctl_val &= ~CTRL_IO_CMD_MASK; > + > + ctl_val |= op->cmd.opcode << CTRL_COMMAND_SHIFT; > + > + /* 4BYTE address mode */ > + if (op->addr.nbytes) { > + if (op->addr.nbytes == 4) > + addr_mode |= (0x11 << chip->cs); > + else > + addr_mode &= ~(0x11 << chip->cs); > + } > + > + if (op->dummy.buswidth && op->dummy.nbytes) Nitpick: op->dummy.nbytes being set should imply op->dummy.buswidth > 0. > + ctl_val |= CTRL_IO_DUMMY_SET(op->dummy.nbytes / op->dummy.buswidth); > + > + if (op->data.nbytes != 0) { > + if (op->data.buswidth) Nitpick: op->data.nbytes != 0 should imply op->data.buswidth > 0. > + ctl_val |= aspeed_spi_get_io_mode(op); > + } > + > + if (op->data.dir == SPI_MEM_DATA_OUT) > + ctl_val |= CTRL_IO_MODE_WRITE; > + else > + ctl_val |= CTRL_IO_MODE_READ; > + > + if (addr_mode != addr_mode_backup) > + writel(addr_mode, aspi->regs + CE_CTRL_REG); > + writel(ctl_val, chip->ctl); > + > + if (op->data.dir == SPI_MEM_DATA_IN) { > + if (!op->addr.nbytes) > + ret = aspeed_spi_read_reg(chip, op); > + else > + ret = aspeed_spi_read_user(chip, op, op->addr.val, > + op->data.nbytes, op->data.buf.in); > + } else { > + if (!op->addr.nbytes) > + ret = aspeed_spi_write_reg(chip, op); > + else > + ret = aspeed_spi_write_user(chip, op); > + } > + > + /* Restore defaults */ > + if (addr_mode != addr_mode_backup) > + writel(addr_mode_backup, aspi->regs + CE_CTRL_REG); > + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); Why do you need to restore defaults here? Do you expect some other piece of software to use it as well? The patch looks good to me apart from these. > + return ret; > +} > + [...]
On 3/30/22 21:33, Pratyush Yadav wrote: > Hi Cedric, > > Thanks for doing the conversion. > > On 25/03/22 11:08AM, Cédric Le Goater wrote: >> This SPI driver adds support for the Aspeed static memory controllers >> of the AST2600, AST2500 and AST2400 SoCs using the spi-mem interface. >> >> * AST2600 Firmware SPI Memory Controller (FMC) >> . BMC firmware >> . 3 chip select pins (CE0 ~ CE2) >> . Only supports SPI type flash memory >> . different segment register interface >> . single, dual and quad mode. >> >> * AST2600 SPI Flash Controller (SPI1 and SPI2) >> . host firmware >> . 2 chip select pins (CE0 ~ CE1) >> . different segment register interface >> . single, dual and quad mode. >> >> * AST2500 Firmware SPI Memory Controller (FMC) >> . BMC firmware >> . 3 chip select pins (CE0 ~ CE2) >> . supports SPI type flash memory (CE0-CE1) >> . CE2 can be of NOR type flash but this is not supported by the driver >> . single, dual mode. >> >> * AST2500 SPI Flash Controller (SPI1 and SPI2) >> . host firmware >> . 2 chip select pins (CE0 ~ CE1) >> . single, dual mode. >> >> * AST2400 New Static Memory Controller (also referred as FMC) >> . BMC firmware >> . New register set >> . 5 chip select pins (CE0 ∼ CE4) >> . supports NOR flash, NAND flash and SPI flash memory. >> . single, dual and quad mode. >> >> Each controller has a memory range on which flash devices contents are >> mapped. Each device is assigned a window that can be changed at bootime >> with the Segment Address Registers. >> >> Each SPI flash device can then be accessed in two modes: Command and >> User. When in User mode, SPI transfers are initiated with accesses to >> the memory segment of a device. When in Command mode, memory >> operations on the memory segment of a device generate SPI commands >> automatically using a Control Register for the settings. >> >> This initial patch adds support for User mode. Command mode needs a little >> more work to check that the memory window on the AHB bus fits the device >> size. It will come later when support for direct mapping is added. >> >> Single and dual mode RX transfers are supported. Other types than SPI >> are not supported. >> >> Reviewed-by: Joel Stanley <joel@jms.id.au> >> Tested-by: Joel Stanley <joel@jms.id.au> >> Tested-by: Tao Ren <rentao.bupt@gmail.com> >> Signed-off-by: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com> >> Signed-off-by: Cédric Le Goater <clg@kaod.org> >> --- >> drivers/mtd/spi-nor/controllers/aspeed-smc.c | 910 ------------------ >> drivers/spi/spi-aspeed-smc.c | 709 ++++++++++++++ >> .../devicetree/bindings/mtd/aspeed-smc.txt | 51 - >> MAINTAINERS | 1 + >> drivers/mtd/spi-nor/controllers/Kconfig | 10 - >> drivers/mtd/spi-nor/controllers/Makefile | 1 - >> drivers/spi/Kconfig | 11 + >> drivers/spi/Makefile | 1 + >> 8 files changed, 722 insertions(+), 972 deletions(-) >> delete mode 100644 drivers/mtd/spi-nor/controllers/aspeed-smc.c >> create mode 100644 drivers/spi/spi-aspeed-smc.c >> delete mode 100644 Documentation/devicetree/bindings/mtd/aspeed-smc.txt >> > [...] >> +static void aspeed_spi_send_cmd_addr(struct aspeed_spi_chip *chip, u8 addr_nbytes, >> + u64 offset, u32 opcode) >> +{ >> + struct aspeed_spi *aspi = chip->aspi; >> + __be32 temp; >> + u32 cmdaddr; >> + >> + switch (addr_nbytes) { >> + default: >> + dev_warn_once(aspi->dev, "Unexpected address width %u, defaulting to 3", >> + addr_nbytes); >> + fallthrough; > > I think it is better if you reject ops where addr width is not 3 or 4. > This you can drop this. Or if you really want to keep it, you can change > it to a WARN_ON() and return an error. OK. This is a left over from the initial driver. I have added both at WARN_ONCE() and a 'return -EOPNOTSUPP' >> + case 3: >> + cmdaddr = offset & 0xFFFFFF; >> + cmdaddr |= opcode << 24; >> + >> + temp = cpu_to_be32(cmdaddr); >> + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); >> + break; >> + case 4: >> + temp = cpu_to_be32(offset); >> + aspeed_spi_write_to_ahb(chip->ahb_base, &opcode, 1); >> + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); >> + break; >> + } >> +} >> + > [...] >> +/* support for 1-1-1, 1-1-2 or 1-1-4 */ >> +static bool aspeed_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) >> +{ >> + if (op->cmd.buswidth > 1) >> + return false; >> + >> + if (op->addr.nbytes != 0) { >> + if (op->addr.buswidth > 1 || op->addr.nbytes > 4) > > As mentioned above, this should reject ops with addr width 1 and 2. yes >> + return false; >> + } >> + >> + if (op->dummy.nbytes != 0) { >> + if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) >> + return false; >> + } >> + >> + if (op->data.nbytes != 0 && op->data.buswidth > 4) >> + return false; >> + >> + return spi_mem_default_supports_op(mem, op); >> +} >> + >> +static int do_aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) >> +{ >> + struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->master); >> + struct aspeed_spi_chip *chip = &aspi->chips[mem->spi->chip_select]; >> + u32 addr_mode, addr_mode_backup; >> + u32 ctl_val; >> + int ret = 0; >> + >> + dev_dbg(aspi->dev, >> + "CE%d %s OP %#x mode:%d.%d.%d.%d naddr:%#x ndummies:%#x len:%#x", >> + chip->cs, op->data.dir == SPI_MEM_DATA_IN ? "read" : "write", >> + op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth, >> + op->dummy.buswidth, op->data.buswidth, >> + op->addr.nbytes, op->dummy.nbytes, op->data.nbytes); >> + >> + addr_mode = readl(aspi->regs + CE_CTRL_REG); >> + addr_mode_backup = addr_mode; >> + >> + ctl_val = chip->ctl_val[ASPEED_SPI_BASE]; >> + ctl_val &= ~CTRL_IO_CMD_MASK; >> + >> + ctl_val |= op->cmd.opcode << CTRL_COMMAND_SHIFT; >> + >> + /* 4BYTE address mode */ >> + if (op->addr.nbytes) { >> + if (op->addr.nbytes == 4) >> + addr_mode |= (0x11 << chip->cs); >> + else >> + addr_mode &= ~(0x11 << chip->cs); >> + } >> + >> + if (op->dummy.buswidth && op->dummy.nbytes) > > Nitpick: op->dummy.nbytes being set should imply op->dummy.buswidth > 0. > >> + ctl_val |= CTRL_IO_DUMMY_SET(op->dummy.nbytes / op->dummy.buswidth); >> + >> + if (op->data.nbytes != 0) { >> + if (op->data.buswidth) > > Nitpick: op->data.nbytes != 0 should imply op->data.buswidth > 0. Indeed. Fixed both. >> + ctl_val |= aspeed_spi_get_io_mode(op); >> + } >> + >> + if (op->data.dir == SPI_MEM_DATA_OUT) >> + ctl_val |= CTRL_IO_MODE_WRITE; >> + else >> + ctl_val |= CTRL_IO_MODE_READ; >> + >> + if (addr_mode != addr_mode_backup) >> + writel(addr_mode, aspi->regs + CE_CTRL_REG); >> + writel(ctl_val, chip->ctl); >> + >> + if (op->data.dir == SPI_MEM_DATA_IN) { >> + if (!op->addr.nbytes) >> + ret = aspeed_spi_read_reg(chip, op); >> + else >> + ret = aspeed_spi_read_user(chip, op, op->addr.val, >> + op->data.nbytes, op->data.buf.in); >> + } else { >> + if (!op->addr.nbytes) >> + ret = aspeed_spi_write_reg(chip, op); >> + else >> + ret = aspeed_spi_write_user(chip, op); >> + } >> + >> + /* Restore defaults */ >> + if (addr_mode != addr_mode_backup) >> + writel(addr_mode_backup, aspi->regs + CE_CTRL_REG); >> + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); > > Why do you need to restore defaults here? Do you expect some other piece > of software to use it as well? We expect the controller to be correctly set when dirmap_read() is called. But it is only required in the next patch. > > The patch looks good to me apart from these. Thanks, C. > >> + return ret; >> +} >> + > [...] >
On 04/04/22 09:06AM, Cédric Le Goater wrote: > On 3/30/22 21:33, Pratyush Yadav wrote: > > Hi Cedric, > > > > Thanks for doing the conversion. > > > > On 25/03/22 11:08AM, Cédric Le Goater wrote: > > > This SPI driver adds support for the Aspeed static memory controllers > > > of the AST2600, AST2500 and AST2400 SoCs using the spi-mem interface. > > > > > > * AST2600 Firmware SPI Memory Controller (FMC) > > > . BMC firmware > > > . 3 chip select pins (CE0 ~ CE2) > > > . Only supports SPI type flash memory > > > . different segment register interface > > > . single, dual and quad mode. > > > > > > * AST2600 SPI Flash Controller (SPI1 and SPI2) > > > . host firmware > > > . 2 chip select pins (CE0 ~ CE1) > > > . different segment register interface > > > . single, dual and quad mode. > > > > > > * AST2500 Firmware SPI Memory Controller (FMC) > > > . BMC firmware > > > . 3 chip select pins (CE0 ~ CE2) > > > . supports SPI type flash memory (CE0-CE1) > > > . CE2 can be of NOR type flash but this is not supported by the driver > > > . single, dual mode. > > > > > > * AST2500 SPI Flash Controller (SPI1 and SPI2) > > > . host firmware > > > . 2 chip select pins (CE0 ~ CE1) > > > . single, dual mode. > > > > > > * AST2400 New Static Memory Controller (also referred as FMC) > > > . BMC firmware > > > . New register set > > > . 5 chip select pins (CE0 ∼ CE4) > > > . supports NOR flash, NAND flash and SPI flash memory. > > > . single, dual and quad mode. > > > > > > Each controller has a memory range on which flash devices contents are > > > mapped. Each device is assigned a window that can be changed at bootime > > > with the Segment Address Registers. > > > > > > Each SPI flash device can then be accessed in two modes: Command and > > > User. When in User mode, SPI transfers are initiated with accesses to > > > the memory segment of a device. When in Command mode, memory > > > operations on the memory segment of a device generate SPI commands > > > automatically using a Control Register for the settings. > > > > > > This initial patch adds support for User mode. Command mode needs a little > > > more work to check that the memory window on the AHB bus fits the device > > > size. It will come later when support for direct mapping is added. > > > > > > Single and dual mode RX transfers are supported. Other types than SPI > > > are not supported. > > > > > > Reviewed-by: Joel Stanley <joel@jms.id.au> > > > Tested-by: Joel Stanley <joel@jms.id.au> > > > Tested-by: Tao Ren <rentao.bupt@gmail.com> > > > Signed-off-by: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com> > > > Signed-off-by: Cédric Le Goater <clg@kaod.org> > > > --- > > > drivers/mtd/spi-nor/controllers/aspeed-smc.c | 910 ------------------ > > > drivers/spi/spi-aspeed-smc.c | 709 ++++++++++++++ > > > .../devicetree/bindings/mtd/aspeed-smc.txt | 51 - > > > MAINTAINERS | 1 + > > > drivers/mtd/spi-nor/controllers/Kconfig | 10 - > > > drivers/mtd/spi-nor/controllers/Makefile | 1 - > > > drivers/spi/Kconfig | 11 + > > > drivers/spi/Makefile | 1 + > > > 8 files changed, 722 insertions(+), 972 deletions(-) > > > delete mode 100644 drivers/mtd/spi-nor/controllers/aspeed-smc.c > > > create mode 100644 drivers/spi/spi-aspeed-smc.c > > > delete mode 100644 Documentation/devicetree/bindings/mtd/aspeed-smc.txt > > > > > [...] > > > +static void aspeed_spi_send_cmd_addr(struct aspeed_spi_chip *chip, u8 addr_nbytes, > > > + u64 offset, u32 opcode) > > > +{ > > > + struct aspeed_spi *aspi = chip->aspi; > > > + __be32 temp; > > > + u32 cmdaddr; > > > + > > > + switch (addr_nbytes) { > > > + default: > > > + dev_warn_once(aspi->dev, "Unexpected address width %u, defaulting to 3", > > > + addr_nbytes); > > > + fallthrough; > > > > I think it is better if you reject ops where addr width is not 3 or 4. > > This you can drop this. Or if you really want to keep it, you can change > > it to a WARN_ON() and return an error. > > OK. This is a left over from the initial driver. > > I have added both at WARN_ONCE() and a 'return -EOPNOTSUPP' > > > > + case 3: > > > + cmdaddr = offset & 0xFFFFFF; > > > + cmdaddr |= opcode << 24; > > > + > > > + temp = cpu_to_be32(cmdaddr); > > > + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); > > > + break; > > > + case 4: > > > + temp = cpu_to_be32(offset); > > > + aspeed_spi_write_to_ahb(chip->ahb_base, &opcode, 1); > > > + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); > > > + break; > > > + } > > > +} > > > + > > [...] > > > +/* support for 1-1-1, 1-1-2 or 1-1-4 */ > > > +static bool aspeed_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) > > > +{ > > > + if (op->cmd.buswidth > 1) > > > + return false; > > > + > > > + if (op->addr.nbytes != 0) { > > > + if (op->addr.buswidth > 1 || op->addr.nbytes > 4) > > > > As mentioned above, this should reject ops with addr width 1 and 2. > > yes > > > > + return false; > > > + } > > > + > > > + if (op->dummy.nbytes != 0) { > > > + if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) > > > + return false; > > > + } > > > + > > > + if (op->data.nbytes != 0 && op->data.buswidth > 4) > > > + return false; > > > + > > > + return spi_mem_default_supports_op(mem, op); > > > +} > > > + > > > +static int do_aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) > > > +{ > > > + struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->master); > > > + struct aspeed_spi_chip *chip = &aspi->chips[mem->spi->chip_select]; > > > + u32 addr_mode, addr_mode_backup; > > > + u32 ctl_val; > > > + int ret = 0; > > > + > > > + dev_dbg(aspi->dev, > > > + "CE%d %s OP %#x mode:%d.%d.%d.%d naddr:%#x ndummies:%#x len:%#x", > > > + chip->cs, op->data.dir == SPI_MEM_DATA_IN ? "read" : "write", > > > + op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth, > > > + op->dummy.buswidth, op->data.buswidth, > > > + op->addr.nbytes, op->dummy.nbytes, op->data.nbytes); > > > + > > > + addr_mode = readl(aspi->regs + CE_CTRL_REG); > > > + addr_mode_backup = addr_mode; > > > + > > > + ctl_val = chip->ctl_val[ASPEED_SPI_BASE]; > > > + ctl_val &= ~CTRL_IO_CMD_MASK; > > > + > > > + ctl_val |= op->cmd.opcode << CTRL_COMMAND_SHIFT; > > > + > > > + /* 4BYTE address mode */ > > > + if (op->addr.nbytes) { > > > + if (op->addr.nbytes == 4) > > > + addr_mode |= (0x11 << chip->cs); > > > + else > > > + addr_mode &= ~(0x11 << chip->cs); > > > + } > > > + > > > + if (op->dummy.buswidth && op->dummy.nbytes) > > > > Nitpick: op->dummy.nbytes being set should imply op->dummy.buswidth > 0. > > > > > + ctl_val |= CTRL_IO_DUMMY_SET(op->dummy.nbytes / op->dummy.buswidth); > > > + > > > + if (op->data.nbytes != 0) { > > > + if (op->data.buswidth) > > > > Nitpick: op->data.nbytes != 0 should imply op->data.buswidth > 0. > > Indeed. Fixed both. > > > > + ctl_val |= aspeed_spi_get_io_mode(op); > > > + } > > > + > > > + if (op->data.dir == SPI_MEM_DATA_OUT) > > > + ctl_val |= CTRL_IO_MODE_WRITE; > > > + else > > > + ctl_val |= CTRL_IO_MODE_READ; > > > + > > > + if (addr_mode != addr_mode_backup) > > > + writel(addr_mode, aspi->regs + CE_CTRL_REG); > > > + writel(ctl_val, chip->ctl); > > > + > > > + if (op->data.dir == SPI_MEM_DATA_IN) { > > > + if (!op->addr.nbytes) > > > + ret = aspeed_spi_read_reg(chip, op); > > > + else > > > + ret = aspeed_spi_read_user(chip, op, op->addr.val, > > > + op->data.nbytes, op->data.buf.in); > > > + } else { > > > + if (!op->addr.nbytes) > > > + ret = aspeed_spi_write_reg(chip, op); > > > + else > > > + ret = aspeed_spi_write_user(chip, op); > > > + } > > > + > > > + /* Restore defaults */ > > > + if (addr_mode != addr_mode_backup) > > > + writel(addr_mode_backup, aspi->regs + CE_CTRL_REG); > > > + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); > > > > Why do you need to restore defaults here? Do you expect some other piece > > of software to use it as well? > > We expect the controller to be correctly set when dirmap_read() is called. > But it is only required in the next patch. Okay. This should be fine then.
diff --git a/drivers/mtd/spi-nor/controllers/aspeed-smc.c b/drivers/mtd/spi-nor/controllers/aspeed-smc.c deleted file mode 100644 index 7225870e8b18..000000000000 --- a/drivers/mtd/spi-nor/controllers/aspeed-smc.c +++ /dev/null @@ -1,910 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * ASPEED Static Memory Controller driver - * - * Copyright (c) 2015-2016, IBM Corporation. - */ - -#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/sizes.h> -#include <linux/sysfs.h> - -#define DEVICE_NAME "aspeed-smc" - -/* - * The driver only support SPI flash - */ -enum aspeed_smc_flash_type { - smc_type_nor = 0, - smc_type_nand = 1, - smc_type_spi = 2, -}; - -struct aspeed_smc_chip; - -struct aspeed_smc_info { - u32 maxsize; /* maximum size of chip window */ - u8 nce; /* number of chip enables */ - bool hastype; /* flash type field exists in config reg */ - u8 we0; /* shift for write enable bit for CE0 */ - u8 ctl0; /* offset in regs of ctl for CE0 */ - - void (*set_4b)(struct aspeed_smc_chip *chip); -}; - -static void aspeed_smc_chip_set_4b_spi_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, - .hastype = true, - .we0 = 16, - .ctl0 = 0x10, - .set_4b = aspeed_smc_chip_set_4b, -}; - -static const struct aspeed_smc_info spi_2400_info = { - .maxsize = 64 * 1024 * 1024, - .nce = 1, - .hastype = false, - .we0 = 0, - .ctl0 = 0x04, - .set_4b = aspeed_smc_chip_set_4b_spi_2400, -}; - -static const struct aspeed_smc_info fmc_2500_info = { - .maxsize = 256 * 1024 * 1024, - .nce = 3, - .hastype = true, - .we0 = 16, - .ctl0 = 0x10, - .set_4b = aspeed_smc_chip_set_4b, -}; - -static const struct aspeed_smc_info spi_2500_info = { - .maxsize = 128 * 1024 * 1024, - .nce = 2, - .hastype = false, - .we0 = 16, - .ctl0 = 0x10, - .set_4b = aspeed_smc_chip_set_4b, -}; - -enum aspeed_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 */ - smc_max, -}; - -struct aspeed_smc_controller; - -struct aspeed_smc_chip { - int cs; - struct aspeed_smc_controller *controller; - void __iomem *ctl; /* control register */ - void __iomem *ahb_base; /* base of chip window */ - u32 ahb_window_size; /* chip mapping window size */ - u32 ctl_val[smc_max]; /* control settings */ - enum aspeed_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 *ahb_base; /* per-chip windows resource */ - u32 ahb_window_size; /* full mapping window size */ - - struct aspeed_smc_chip *chips[]; /* pointers to attached chips */ -}; - -/* - * SPI Flash Configuration Register (AST2500 SPI) - * or - * Type setting Register (AST2500 FMC). - * CE0 and CE1 can only be of type SPI. CE2 can be of type NOR but the - * driver does not support it. - */ -#define CONFIG_REG 0x0 -#define CONFIG_DISABLE_LEGACY BIT(31) /* 1 */ - -#define CONFIG_CE2_WRITE BIT(18) -#define CONFIG_CE1_WRITE BIT(17) -#define CONFIG_CE0_WRITE BIT(16) - -#define CONFIG_CE2_TYPE BIT(4) /* AST2500 FMC only */ -#define CONFIG_CE1_TYPE BIT(2) /* AST2500 FMC only */ -#define CONFIG_CE0_TYPE BIT(0) /* AST2500 FMC only */ - -/* - * CE Control Register - */ -#define CE_CONTROL_REG 0x4 - -/* - * CEx Control Register - */ -#define CONTROL_AAF_MODE BIT(31) -#define CONTROL_IO_MODE_MASK GENMASK(30, 28) -#define CONTROL_IO_DUAL_DATA BIT(29) -#define CONTROL_IO_DUAL_ADDR_DATA (BIT(29) | BIT(28)) -#define CONTROL_IO_QUAD_DATA BIT(30) -#define CONTROL_IO_QUAD_ADDR_DATA (BIT(30) | BIT(28)) -#define CONTROL_CE_INACTIVE_SHIFT 24 -#define CONTROL_CE_INACTIVE_MASK GENMASK(27, \ - CONTROL_CE_INACTIVE_SHIFT) -/* 0 = 16T ... 15 = 1T T=HCLK */ -#define CONTROL_COMMAND_SHIFT 16 -#define CONTROL_DUMMY_COMMAND_OUT BIT(15) -#define CONTROL_IO_DUMMY_HI BIT(14) -#define CONTROL_IO_DUMMY_HI_SHIFT 14 -#define CONTROL_CLK_DIV4 BIT(13) /* others */ -#define CONTROL_IO_ADDRESS_4B BIT(13) /* AST2400 SPI */ -#define CONTROL_RW_MERGE BIT(12) -#define CONTROL_IO_DUMMY_LO_SHIFT 6 -#define CONTROL_IO_DUMMY_LO GENMASK(7, \ - CONTROL_IO_DUMMY_LO_SHIFT) -#define CONTROL_IO_DUMMY_MASK (CONTROL_IO_DUMMY_HI | \ - CONTROL_IO_DUMMY_LO) -#define CONTROL_IO_DUMMY_SET(dummy) \ - (((((dummy) >> 2) & 0x1) << CONTROL_IO_DUMMY_HI_SHIFT) | \ - (((dummy) & 0x3) << CONTROL_IO_DUMMY_LO_SHIFT)) - -#define CONTROL_CLOCK_FREQ_SEL_SHIFT 8 -#define CONTROL_CLOCK_FREQ_SEL_MASK GENMASK(11, \ - CONTROL_CLOCK_FREQ_SEL_SHIFT) -#define CONTROL_LSB_FIRST BIT(5) -#define CONTROL_CLOCK_MODE_3 BIT(4) -#define CONTROL_IN_DUAL_DATA BIT(3) -#define CONTROL_CE_STOP_ACTIVE_CONTROL BIT(2) -#define CONTROL_COMMAND_MODE_MASK GENMASK(1, 0) -#define CONTROL_COMMAND_MODE_NORMAL 0 -#define CONTROL_COMMAND_MODE_FREAD 1 -#define CONTROL_COMMAND_MODE_WRITE 2 -#define CONTROL_COMMAND_MODE_USER 3 - -#define CONTROL_KEEP_MASK \ - (CONTROL_AAF_MODE | CONTROL_CE_INACTIVE_MASK | CONTROL_CLK_DIV4 | \ - CONTROL_CLOCK_FREQ_SEL_MASK | CONTROL_LSB_FIRST | CONTROL_CLOCK_MODE_3) - -/* - * The Segment Register uses a 8MB unit to encode the start address - * and the end address of the mapping window of a flash SPI slave : - * - * | byte 1 | byte 2 | byte 3 | byte 4 | - * +--------+--------+--------+--------+ - * | end | start | 0 | 0 | - */ -#define SEGMENT_ADDR_REG0 0x30 -#define SEGMENT_ADDR_START(_r) ((((_r) >> 16) & 0xFF) << 23) -#define SEGMENT_ADDR_END(_r) ((((_r) >> 24) & 0xFF) << 23) -#define SEGMENT_ADDR_VALUE(start, end) \ - (((((start) >> 23) & 0xFF) << 16) | ((((end) >> 23) & 0xFF) << 24)) -#define SEGMENT_ADDR_REG(controller, cs) \ - ((controller)->regs + SEGMENT_ADDR_REG0 + (cs) * 4) - -/* - * 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, void __iomem *src, size_t len) -{ - size_t offset = 0; - - if (IS_ALIGNED((uintptr_t)src, sizeof(uintptr_t)) && - IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { - ioread32_rep(src, buf, len >> 2); - offset = len & ~0x3; - len -= offset; - } - ioread8_rep(src, (u8 *)buf + offset, len); - return 0; -} - -static int aspeed_smc_write_to_ahb(void __iomem *dst, const void *buf, - size_t len) -{ - size_t offset = 0; - - if (IS_ALIGNED((uintptr_t)dst, sizeof(uintptr_t)) && - IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { - iowrite32_rep(dst, buf, len >> 2); - offset = len & ~0x3; - len -= offset; - } - iowrite8_rep(dst, (const u8 *)buf + offset, len); - return 0; -} - -static inline u32 aspeed_smc_chip_write_bit(struct aspeed_smc_chip *chip) -{ - return BIT(chip->controller->info->we0 + chip->cs); -} - -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)) - return; - - 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_COMMAND_MODE_USER | - CONTROL_CE_STOP_ACTIVE_CONTROL; - writel(ctl, chip->ctl); - - ctl &= ~CONTROL_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_COMMAND_MODE_USER | - CONTROL_CE_STOP_ACTIVE_CONTROL; - - writel(ctl2, chip->ctl); /* stop user CE control */ - writel(ctl, chip->ctl); /* default to fread or read mode */ -} - -static int aspeed_smc_prep(struct spi_nor *nor) -{ - struct aspeed_smc_chip *chip = nor->priv; - - mutex_lock(&chip->controller->mutex); - return 0; -} - -static void aspeed_smc_unprep(struct spi_nor *nor) -{ - struct aspeed_smc_chip *chip = nor->priv; - - mutex_unlock(&chip->controller->mutex); -} - -static int aspeed_smc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, - size_t len) -{ - struct aspeed_smc_chip *chip = nor->priv; - - aspeed_smc_start_user(nor); - aspeed_smc_write_to_ahb(chip->ahb_base, &opcode, 1); - aspeed_smc_read_from_ahb(buf, chip->ahb_base, len); - aspeed_smc_stop_user(nor); - return 0; -} - -static int aspeed_smc_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf, - size_t len) -{ - struct aspeed_smc_chip *chip = nor->priv; - - aspeed_smc_start_user(nor); - aspeed_smc_write_to_ahb(chip->ahb_base, &opcode, 1); - aspeed_smc_write_to_ahb(chip->ahb_base, buf, len); - aspeed_smc_stop_user(nor); - 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 |= cmd << 24; - - temp = cpu_to_be32(cmdaddr); - aspeed_smc_write_to_ahb(chip->ahb_base, &temp, 4); - break; - case 4: - temp = cpu_to_be32(addr); - aspeed_smc_write_to_ahb(chip->ahb_base, &cmd, 1); - aspeed_smc_write_to_ahb(chip->ahb_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; - int i; - u8 dummy = 0xFF; - - aspeed_smc_start_user(nor); - aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from); - for (i = 0; i < chip->nor.read_dummy / 8; i++) - aspeed_smc_write_to_ahb(chip->ahb_base, &dummy, sizeof(dummy)); - - aspeed_smc_read_from_ahb(read_buf, chip->ahb_base, len); - aspeed_smc_stop_user(nor); - 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; - - aspeed_smc_start_user(nor); - aspeed_smc_send_cmd_addr(nor, nor->program_opcode, to); - aspeed_smc_write_to_ahb(chip->ahb_base, write_buf, len); - aspeed_smc_stop_user(nor); - return len; -} - -static int aspeed_smc_unregister(struct aspeed_smc_controller *controller) -{ - struct aspeed_smc_chip *chip; - 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 int aspeed_smc_remove(struct platform_device *dev) -{ - return aspeed_smc_unregister(platform_get_drvdata(dev)); -} - -static const struct of_device_id aspeed_smc_matches[] = { - { .compatible = "aspeed,ast2400-fmc", .data = &fmc_2400_info }, - { .compatible = "aspeed,ast2400-spi", .data = &spi_2400_info }, - { .compatible = "aspeed,ast2500-fmc", .data = &fmc_2500_info }, - { .compatible = "aspeed,ast2500-spi", .data = &spi_2500_info }, - { } -}; -MODULE_DEVICE_TABLE(of, aspeed_smc_matches); - -/* - * Each chip has a mapping window defined by a segment address - * register defining a start and an end address on the AHB bus. These - * addresses can be configured to fit the chip size and offer a - * contiguous memory region across chips. For the moment, we only - * check that each chip segment is valid. - */ -static void __iomem *aspeed_smc_chip_base(struct aspeed_smc_chip *chip, - struct resource *res) -{ - struct aspeed_smc_controller *controller = chip->controller; - u32 offset = 0; - u32 reg; - - if (controller->info->nce > 1) { - reg = readl(SEGMENT_ADDR_REG(controller, chip->cs)); - - if (SEGMENT_ADDR_START(reg) >= SEGMENT_ADDR_END(reg)) - return NULL; - - offset = SEGMENT_ADDR_START(reg) - res->start; - } - - return controller->ahb_base + offset; -} - -static u32 aspeed_smc_ahb_base_phy(struct aspeed_smc_controller *controller) -{ - u32 seg0_val = readl(SEGMENT_ADDR_REG(controller, 0)); - - return SEGMENT_ADDR_START(seg0_val); -} - -static u32 chip_set_segment(struct aspeed_smc_chip *chip, u32 cs, u32 start, - u32 size) -{ - struct aspeed_smc_controller *controller = chip->controller; - void __iomem *seg_reg; - u32 seg_oldval, seg_newval, ahb_base_phy, end; - - ahb_base_phy = aspeed_smc_ahb_base_phy(controller); - - seg_reg = SEGMENT_ADDR_REG(controller, cs); - seg_oldval = readl(seg_reg); - - /* - * If the chip size is not specified, use the default segment - * size, but take into account the possible overlap with the - * previous segment - */ - if (!size) - size = SEGMENT_ADDR_END(seg_oldval) - start; - - /* - * The segment cannot exceed the maximum window size of the - * controller. - */ - if (start + size > ahb_base_phy + controller->ahb_window_size) { - size = ahb_base_phy + controller->ahb_window_size - start; - dev_warn(chip->nor.dev, "CE%d window resized to %dMB", - cs, size >> 20); - } - - end = start + size; - seg_newval = SEGMENT_ADDR_VALUE(start, end); - writel(seg_newval, seg_reg); - - /* - * Restore default value if something goes wrong. The chip - * might have set some bogus value and we would loose access - * to the chip. - */ - if (seg_newval != readl(seg_reg)) { - dev_err(chip->nor.dev, "CE%d window invalid", cs); - writel(seg_oldval, seg_reg); - start = SEGMENT_ADDR_START(seg_oldval); - end = SEGMENT_ADDR_END(seg_oldval); - size = end - start; - } - - dev_info(chip->nor.dev, "CE%d window [ 0x%.8x - 0x%.8x ] %dMB", - cs, start, end, size >> 20); - - return size; -} - -/* - * The segment register defines the mapping window on the AHB bus and - * it needs to be configured depending on the chip size. The segment - * register of the following CE also needs to be tuned in order to - * provide a contiguous window across multiple chips. - * - * This is expected to be called in increasing CE order - */ -static u32 aspeed_smc_chip_set_segment(struct aspeed_smc_chip *chip) -{ - struct aspeed_smc_controller *controller = chip->controller; - u32 ahb_base_phy, start; - u32 size = chip->nor.mtd.size; - - /* - * Each controller has a chip size limit for direct memory - * access - */ - if (size > controller->info->maxsize) - size = controller->info->maxsize; - - /* - * The AST2400 SPI controller only handles one chip and does - * not have segment registers. Let's use the chip size for the - * AHB window. - */ - if (controller->info == &spi_2400_info) - goto out; - - /* - * The AST2500 SPI controller has a HW bug when the CE0 chip - * size reaches 128MB. Enforce a size limit of 120MB to - * prevent the controller from using bogus settings in the - * segment register. - */ - if (chip->cs == 0 && controller->info == &spi_2500_info && - size == SZ_128M) { - size = 120 << 20; - dev_info(chip->nor.dev, - "CE%d window resized to %dMB (AST2500 HW quirk)", - chip->cs, size >> 20); - } - - ahb_base_phy = aspeed_smc_ahb_base_phy(controller); - - /* - * As a start address for the current segment, use the default - * start address if we are handling CE0 or use the previous - * segment ending address - */ - if (chip->cs) { - u32 prev = readl(SEGMENT_ADDR_REG(controller, chip->cs - 1)); - - start = SEGMENT_ADDR_END(prev); - } else { - start = ahb_base_phy; - } - - size = chip_set_segment(chip, chip->cs, start, size); - - /* Update chip base address on the AHB bus */ - chip->ahb_base = controller->ahb_base + (start - ahb_base_phy); - - /* - * Now, make sure the next segment does not overlap with the - * current one we just configured, even if there is no - * available chip. That could break access in Command Mode. - */ - if (chip->cs < controller->info->nce - 1) - chip_set_segment(chip, chip->cs + 1, start + size, 0); - -out: - if (size < chip->nor.mtd.size) - dev_warn(chip->nor.dev, - "CE%d window too small for chip %dMB", - chip->cs, (u32)chip->nor.mtd.size >> 20); - - return size; -} - -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; - - chip->type = type; - - reg = readl(controller->regs + CONFIG_REG); - reg &= ~(3 << (chip->cs * 2)); - reg |= chip->type << (chip->cs * 2); - writel(reg, controller->regs + CONFIG_REG); -} - -/* - * The first chip of the AST2500 FMC flash controller is strapped by - * hardware, or autodetected, but other chips need to be set. Enforce - * the 4B setting for all chips. - */ -static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip) -{ - struct aspeed_smc_controller *controller = chip->controller; - u32 reg; - - 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_spi_2400(struct aspeed_smc_chip *chip) -{ - chip->ctl_val[smc_base] |= CONTROL_IO_ADDRESS_4B; - chip->ctl_val[smc_read] |= CONTROL_IO_ADDRESS_4B; -} - -static int aspeed_smc_chip_setup_init(struct aspeed_smc_chip *chip, - struct resource *res) -{ - 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 */ - if (info->hastype) - aspeed_smc_chip_set_type(chip, smc_type_spi); - - /* - * Configure chip base address in memory - */ - chip->ahb_base = aspeed_smc_chip_base(chip, res); - if (!chip->ahb_base) { - dev_warn(chip->nor.dev, "CE%d window closed", chip->cs); - return -EINVAL; - } - - /* - * Get value of the inherited control register. U-Boot usually - * does some timing calibration on the FMC chip, so it's good - * to keep them. In the future, we should handle calibration - * from Linux. - */ - reg = readl(chip->ctl); - dev_dbg(controller->dev, "control register: %08x\n", reg); - - base_reg = reg & CONTROL_KEEP_MASK; - if (base_reg != reg) { - dev_dbg(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_COMMAND_MODE_MASK) == - CONTROL_COMMAND_MODE_NORMAL) - chip->ctl_val[smc_read] = reg; - else - chip->ctl_val[smc_read] = chip->ctl_val[smc_base] | - CONTROL_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); - - /* This is for direct AHB access when using Command Mode. */ - chip->ahb_window_size = aspeed_smc_chip_set_segment(chip); - - /* - * base mode has not been optimized yet. use it for writes. - */ - chip->ctl_val[smc_write] = chip->ctl_val[smc_base] | - chip->nor.program_opcode << CONTROL_COMMAND_SHIFT | - CONTROL_COMMAND_MODE_WRITE; - - dev_dbg(controller->dev, "write control register: %08x\n", - chip->ctl_val[smc_write]); - - /* - * TODO: Adjust clocks if fast read is supported and interpret - * SPI NOR flags to adjust controller settings. - */ - if (chip->nor.read_proto == SNOR_PROTO_1_1_1) { - if (chip->nor.read_dummy == 0) - cmd = CONTROL_COMMAND_MODE_NORMAL; - else - cmd = CONTROL_COMMAND_MODE_FREAD; - } else { - dev_err(chip->nor.dev, "unsupported SPI read mode\n"); - return -EINVAL; - } - - chip->ctl_val[smc_read] |= cmd | - CONTROL_IO_DUMMY_SET(chip->nor.read_dummy / 8); - - dev_dbg(controller->dev, "base control register: %08x\n", - chip->ctl_val[smc_read]); - return 0; -} - -static const struct spi_nor_controller_ops aspeed_smc_controller_ops = { - .prepare = aspeed_smc_prep, - .unprepare = aspeed_smc_unprep, - .read_reg = aspeed_smc_read_reg, - .write_reg = aspeed_smc_write_reg, - .read = aspeed_smc_read_user, - .write = aspeed_smc_write_user, -}; - -static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller, - struct device_node *np, struct resource *r) -{ - const struct spi_nor_hwcaps hwcaps = { - .mask = SNOR_HWCAPS_READ | - SNOR_HWCAPS_READ_FAST | - SNOR_HWCAPS_PP, - }; - const struct aspeed_smc_info *info = controller->info; - struct device *dev = controller->dev; - struct device_node *child; - unsigned int cs; - int ret = -ENODEV; - - for_each_available_child_of_node(np, child) { - struct aspeed_smc_chip *chip; - struct spi_nor *nor; - struct mtd_info *mtd; - - /* This driver does not support NAND or NOR flash devices. */ - if (!of_device_is_compatible(child, "jedec,spi-nor")) - continue; - - ret = of_property_read_u32(child, "reg", &cs); - if (ret) { - dev_err(dev, "Couldn't not read chip select.\n"); - break; - } - - if (cs >= info->nce) { - dev_err(dev, "Chip select %d out of range.\n", - cs); - ret = -ERANGE; - break; - } - - if (controller->chips[cs]) { - dev_err(dev, "Chip select %d already in use by %s\n", - cs, dev_name(controller->chips[cs]->nor.dev)); - ret = -EBUSY; - break; - } - - chip = devm_kzalloc(controller->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) { - ret = -ENOMEM; - break; - } - - chip->controller = controller; - chip->ctl = controller->regs + info->ctl0 + cs * 4; - chip->cs = cs; - - nor = &chip->nor; - mtd = &nor->mtd; - - nor->dev = dev; - nor->priv = chip; - spi_nor_set_flash_node(nor, child); - nor->controller_ops = &aspeed_smc_controller_ops; - - ret = aspeed_smc_chip_setup_init(chip, r); - if (ret) - break; - - /* - * TODO: Add support for Dual and Quad SPI protocols - * attach when board support is present as determined - * by of property. - */ - ret = spi_nor_scan(nor, NULL, &hwcaps); - if (ret) - break; - - ret = aspeed_smc_chip_setup_finish(chip); - if (ret) - break; - - ret = mtd_device_register(mtd, NULL, 0); - if (ret) - break; - - controller->chips[cs] = chip; - } - - if (ret) { - of_node_put(child); - aspeed_smc_unregister(controller); - } - - return ret; -} - -static int aspeed_smc_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct device *dev = &pdev->dev; - struct aspeed_smc_controller *controller; - const struct of_device_id *match; - const struct aspeed_smc_info *info; - struct resource *res; - int ret; - - match = of_match_device(aspeed_smc_matches, &pdev->dev); - if (!match || !match->data) - return -ENODEV; - info = match->data; - - controller = devm_kzalloc(&pdev->dev, - struct_size(controller, chips, info->nce), - GFP_KERNEL); - if (!controller) - return -ENOMEM; - controller->info = info; - controller->dev = dev; - - mutex_init(&controller->mutex); - platform_set_drvdata(pdev, controller); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - controller->regs = devm_ioremap_resource(dev, res); - if (IS_ERR(controller->regs)) - return PTR_ERR(controller->regs); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - controller->ahb_base = devm_ioremap_resource(dev, res); - if (IS_ERR(controller->ahb_base)) - return PTR_ERR(controller->ahb_base); - - controller->ahb_window_size = resource_size(res); - - ret = aspeed_smc_setup_flash(controller, np, res); - if (ret) - dev_err(dev, "Aspeed SMC probe failed %d\n", ret); - - return ret; -} - -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("Cedric Le Goater <clg@kaod.org>"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c new file mode 100644 index 000000000000..997ec2e45118 --- /dev/null +++ b/drivers/spi/spi-aspeed-smc.c @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ASPEED FMC/SPI Memory Controller Driver + * + * Copyright (c) 2015-2022, IBM Corporation. + * Copyright (c) 2020, ASPEED Corporation. + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi-mem.h> + +#define DEVICE_NAME "spi-aspeed-smc" + +/* Type setting Register */ +#define CONFIG_REG 0x0 +#define CONFIG_TYPE_SPI 0x2 + +/* CE Control Register */ +#define CE_CTRL_REG 0x4 + +/* CEx Control Register */ +#define CE0_CTRL_REG 0x10 +#define CTRL_IO_MODE_MASK GENMASK(30, 28) +#define CTRL_IO_SINGLE_DATA 0x0 +#define CTRL_IO_DUAL_DATA BIT(29) +#define CTRL_IO_QUAD_DATA BIT(30) +#define CTRL_COMMAND_SHIFT 16 +#define CTRL_IO_DUMMY_SET(dummy) \ + (((((dummy) >> 2) & 0x1) << 14) | (((dummy) & 0x3) << 6)) +#define CTRL_CE_STOP_ACTIVE BIT(2) +#define CTRL_IO_MODE_CMD_MASK GENMASK(1, 0) +#define CTRL_IO_MODE_NORMAL 0x0 +#define CTRL_IO_MODE_READ 0x1 +#define CTRL_IO_MODE_WRITE 0x2 +#define CTRL_IO_MODE_USER 0x3 + +#define CTRL_IO_CMD_MASK 0xf0ff40c3 + +/* CEx Address Decoding Range Register */ +#define CE0_SEGMENT_ADDR_REG 0x30 + +enum aspeed_spi_ctl_reg_value { + ASPEED_SPI_BASE, + ASPEED_SPI_READ, + ASPEED_SPI_WRITE, + ASPEED_SPI_MAX, +}; + +struct aspeed_spi; + +struct aspeed_spi_chip { + struct aspeed_spi *aspi; + u32 cs; + void __iomem *ctl; + void __iomem *ahb_base; + u32 ahb_window_size; + u32 ctl_val[ASPEED_SPI_MAX]; + u32 clk_freq; +}; + +struct aspeed_spi_data { + u32 ctl0; + u32 max_cs; + bool hastype; + u32 mode_bits; + u32 we0; + + u32 (*segment_start)(struct aspeed_spi *aspi, u32 reg); + u32 (*segment_end)(struct aspeed_spi *aspi, u32 reg); + u32 (*segment_reg)(struct aspeed_spi *aspi, u32 start, u32 end); +}; + +#define ASPEED_SPI_MAX_NUM_CS 5 + +struct aspeed_spi { + const struct aspeed_spi_data *data; + + void __iomem *regs; + void __iomem *ahb_base; + u32 ahb_base_phy; + u32 ahb_window_size; + struct device *dev; + + struct clk *clk; + u32 clk_freq; + + struct aspeed_spi_chip chips[ASPEED_SPI_MAX_NUM_CS]; +}; + +static u32 aspeed_spi_get_io_mode(const struct spi_mem_op *op) +{ + switch (op->data.buswidth) { + 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; + } +} + +static void aspeed_spi_set_io_mode(struct aspeed_spi_chip *chip, u32 io_mode) +{ + u32 ctl; + + if (io_mode > 0) { + ctl = readl(chip->ctl) & ~CTRL_IO_MODE_MASK; + ctl |= io_mode; + writel(ctl, chip->ctl); + } +} + +static void aspeed_spi_start_user(struct aspeed_spi_chip *chip) +{ + u32 ctl = chip->ctl_val[ASPEED_SPI_BASE]; + + ctl |= CTRL_IO_MODE_USER | CTRL_CE_STOP_ACTIVE; + writel(ctl, chip->ctl); + + ctl &= ~CTRL_CE_STOP_ACTIVE; + writel(ctl, chip->ctl); +} + +static void aspeed_spi_stop_user(struct aspeed_spi_chip *chip) +{ + u32 ctl = chip->ctl_val[ASPEED_SPI_READ] | + CTRL_IO_MODE_USER | CTRL_CE_STOP_ACTIVE; + + writel(ctl, chip->ctl); + + /* Restore defaults */ + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); +} + +static int aspeed_spi_read_from_ahb(void *buf, void __iomem *src, size_t len) +{ + size_t offset = 0; + + if (IS_ALIGNED((uintptr_t)src, sizeof(uintptr_t)) && + IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { + ioread32_rep(src, buf, len >> 2); + offset = len & ~0x3; + len -= offset; + } + ioread8_rep(src, (u8 *)buf + offset, len); + return 0; +} + +static int aspeed_spi_write_to_ahb(void __iomem *dst, const void *buf, size_t len) +{ + size_t offset = 0; + + if (IS_ALIGNED((uintptr_t)dst, sizeof(uintptr_t)) && + IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) { + iowrite32_rep(dst, buf, len >> 2); + offset = len & ~0x3; + len -= offset; + } + iowrite8_rep(dst, (const u8 *)buf + offset, len); + return 0; +} + +static void aspeed_spi_send_cmd_addr(struct aspeed_spi_chip *chip, u8 addr_nbytes, + u64 offset, u32 opcode) +{ + struct aspeed_spi *aspi = chip->aspi; + __be32 temp; + u32 cmdaddr; + + switch (addr_nbytes) { + default: + dev_warn_once(aspi->dev, "Unexpected address width %u, defaulting to 3", + addr_nbytes); + fallthrough; + case 3: + cmdaddr = offset & 0xFFFFFF; + cmdaddr |= opcode << 24; + + temp = cpu_to_be32(cmdaddr); + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); + break; + case 4: + temp = cpu_to_be32(offset); + aspeed_spi_write_to_ahb(chip->ahb_base, &opcode, 1); + aspeed_spi_write_to_ahb(chip->ahb_base, &temp, 4); + break; + } +} + +static int aspeed_spi_read_reg(struct aspeed_spi_chip *chip, + const struct spi_mem_op *op) +{ + aspeed_spi_start_user(chip); + aspeed_spi_write_to_ahb(chip->ahb_base, &op->cmd.opcode, 1); + aspeed_spi_read_from_ahb(op->data.buf.in, + chip->ahb_base, op->data.nbytes); + aspeed_spi_stop_user(chip); + return 0; +} + +static int aspeed_spi_write_reg(struct aspeed_spi_chip *chip, + const struct spi_mem_op *op) +{ + aspeed_spi_start_user(chip); + aspeed_spi_write_to_ahb(chip->ahb_base, &op->cmd.opcode, 1); + aspeed_spi_write_to_ahb(chip->ahb_base, op->data.buf.out, + op->data.nbytes); + aspeed_spi_stop_user(chip); + return 0; +} + +static ssize_t aspeed_spi_read_user(struct aspeed_spi_chip *chip, + const struct spi_mem_op *op, + u64 offset, size_t len, void *buf) +{ + int io_mode = aspeed_spi_get_io_mode(op); + u8 dummy = 0xFF; + int i; + + aspeed_spi_start_user(chip); + aspeed_spi_send_cmd_addr(chip, op->addr.nbytes, offset, op->cmd.opcode); + if (op->dummy.buswidth && op->dummy.nbytes) { + for (i = 0; i < op->dummy.nbytes / op->dummy.buswidth; i++) + aspeed_spi_write_to_ahb(chip->ahb_base, &dummy, sizeof(dummy)); + } + + aspeed_spi_set_io_mode(chip, io_mode); + + aspeed_spi_read_from_ahb(buf, chip->ahb_base, len); + aspeed_spi_stop_user(chip); + return 0; +} + +static ssize_t aspeed_spi_write_user(struct aspeed_spi_chip *chip, + const struct spi_mem_op *op) +{ + aspeed_spi_start_user(chip); + aspeed_spi_send_cmd_addr(chip, op->addr.nbytes, op->addr.val, op->cmd.opcode); + aspeed_spi_write_to_ahb(chip->ahb_base, op->data.buf.out, op->data.nbytes); + aspeed_spi_stop_user(chip); + return 0; +} + +/* support for 1-1-1, 1-1-2 or 1-1-4 */ +static bool aspeed_spi_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + 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 && op->data.buswidth > 4) + return false; + + return spi_mem_default_supports_op(mem, op); +} + +static int do_aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->master); + struct aspeed_spi_chip *chip = &aspi->chips[mem->spi->chip_select]; + u32 addr_mode, addr_mode_backup; + u32 ctl_val; + int ret = 0; + + dev_dbg(aspi->dev, + "CE%d %s OP %#x mode:%d.%d.%d.%d naddr:%#x ndummies:%#x len:%#x", + chip->cs, op->data.dir == SPI_MEM_DATA_IN ? "read" : "write", + op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth, + op->dummy.buswidth, op->data.buswidth, + op->addr.nbytes, op->dummy.nbytes, op->data.nbytes); + + addr_mode = readl(aspi->regs + CE_CTRL_REG); + addr_mode_backup = addr_mode; + + ctl_val = chip->ctl_val[ASPEED_SPI_BASE]; + ctl_val &= ~CTRL_IO_CMD_MASK; + + ctl_val |= op->cmd.opcode << CTRL_COMMAND_SHIFT; + + /* 4BYTE address mode */ + if (op->addr.nbytes) { + if (op->addr.nbytes == 4) + addr_mode |= (0x11 << chip->cs); + else + addr_mode &= ~(0x11 << chip->cs); + } + + if (op->dummy.buswidth && op->dummy.nbytes) + ctl_val |= CTRL_IO_DUMMY_SET(op->dummy.nbytes / op->dummy.buswidth); + + if (op->data.nbytes != 0) { + if (op->data.buswidth) + ctl_val |= aspeed_spi_get_io_mode(op); + } + + if (op->data.dir == SPI_MEM_DATA_OUT) + ctl_val |= CTRL_IO_MODE_WRITE; + else + ctl_val |= CTRL_IO_MODE_READ; + + if (addr_mode != addr_mode_backup) + writel(addr_mode, aspi->regs + CE_CTRL_REG); + writel(ctl_val, chip->ctl); + + if (op->data.dir == SPI_MEM_DATA_IN) { + if (!op->addr.nbytes) + ret = aspeed_spi_read_reg(chip, op); + else + ret = aspeed_spi_read_user(chip, op, op->addr.val, + op->data.nbytes, op->data.buf.in); + } else { + if (!op->addr.nbytes) + ret = aspeed_spi_write_reg(chip, op); + else + ret = aspeed_spi_write_user(chip, op); + } + + /* Restore defaults */ + if (addr_mode != addr_mode_backup) + writel(addr_mode_backup, aspi->regs + CE_CTRL_REG); + writel(chip->ctl_val[ASPEED_SPI_READ], chip->ctl); + return ret; +} + +static int aspeed_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + int ret; + + ret = do_aspeed_spi_exec_op(mem, op); + if (ret) + dev_err(&mem->spi->dev, "operation failed: %d\n", ret); + return ret; +} + +static const char *aspeed_spi_get_name(struct spi_mem *mem) +{ + struct aspeed_spi *aspi = spi_controller_get_devdata(mem->spi->master); + struct device *dev = aspi->dev; + + return devm_kasprintf(dev, GFP_KERNEL, "%s.%d", dev_name(dev), mem->spi->chip_select); +} + +struct aspeed_spi_window { + u32 cs; + u32 offset; + u32 size; +}; + +static void aspeed_spi_get_windows(struct aspeed_spi *aspi, + struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS]) +{ + const struct aspeed_spi_data *data = aspi->data; + u32 reg_val; + u32 cs; + + for (cs = 0; cs < aspi->data->max_cs; cs++) { + reg_val = readl(aspi->regs + CE0_SEGMENT_ADDR_REG + cs * 4); + windows[cs].cs = cs; + windows[cs].size = data->segment_end(aspi, reg_val) - + data->segment_start(aspi, reg_val); + windows[cs].offset = cs ? windows[cs - 1].offset + windows[cs - 1].size : 0; + dev_vdbg(aspi->dev, "CE%d offset=0x%.8x size=0x%x\n", cs, + windows[cs].offset, windows[cs].size); + } +} + +/* + * On the AST2600, some CE windows are closed by default at reset but + * U-Boot should open all. + */ +static int aspeed_spi_chip_set_default_window(struct aspeed_spi_chip *chip) +{ + struct aspeed_spi *aspi = chip->aspi; + struct aspeed_spi_window windows[ASPEED_SPI_MAX_NUM_CS] = { 0 }; + struct aspeed_spi_window *win = &windows[chip->cs]; + + aspeed_spi_get_windows(aspi, windows); + + chip->ahb_base = aspi->ahb_base + win->offset; + chip->ahb_window_size = win->size; + + dev_dbg(aspi->dev, "CE%d default window [ 0x%.8x - 0x%.8x ] %dMB", + chip->cs, aspi->ahb_base_phy + win->offset, + aspi->ahb_base_phy + win->offset + win->size - 1, + win->size >> 20); + + return chip->ahb_window_size ? 0 : -1; +} + +static const struct spi_controller_mem_ops aspeed_spi_mem_ops = { + .supports_op = aspeed_spi_supports_op, + .exec_op = aspeed_spi_exec_op, + .get_name = aspeed_spi_get_name, +}; + +static void aspeed_spi_chip_set_type(struct aspeed_spi *aspi, unsigned int cs, int type) +{ + u32 reg; + + reg = readl(aspi->regs + CONFIG_REG); + reg &= ~(0x3 << (cs * 2)); + reg |= type << (cs * 2); + writel(reg, aspi->regs + CONFIG_REG); +} + +static void aspeed_spi_chip_enable(struct aspeed_spi *aspi, unsigned int cs, bool enable) +{ + u32 we_bit = BIT(aspi->data->we0 + cs); + u32 reg = readl(aspi->regs + CONFIG_REG); + + if (enable) + reg |= we_bit; + else + reg &= ~we_bit; + writel(reg, aspi->regs + CONFIG_REG); +} + +static int aspeed_spi_setup(struct spi_device *spi) +{ + struct aspeed_spi *aspi = spi_controller_get_devdata(spi->master); + const struct aspeed_spi_data *data = aspi->data; + unsigned int cs = spi->chip_select; + struct aspeed_spi_chip *chip = &aspi->chips[cs]; + + chip->aspi = aspi; + chip->cs = cs; + chip->ctl = aspi->regs + data->ctl0 + cs * 4; + + /* The driver only supports SPI type flash */ + if (data->hastype) + aspeed_spi_chip_set_type(aspi, cs, CONFIG_TYPE_SPI); + + if (aspeed_spi_chip_set_default_window(chip) < 0) { + dev_warn(aspi->dev, "CE%d window invalid", cs); + return -EINVAL; + } + + aspeed_spi_chip_enable(aspi, cs, true); + + chip->ctl_val[ASPEED_SPI_BASE] = CTRL_CE_STOP_ACTIVE | CTRL_IO_MODE_USER; + + dev_dbg(aspi->dev, "CE%d setup done\n", cs); + return 0; +} + +static void aspeed_spi_cleanup(struct spi_device *spi) +{ + struct aspeed_spi *aspi = spi_controller_get_devdata(spi->master); + unsigned int cs = spi->chip_select; + + aspeed_spi_chip_enable(aspi, cs, false); + + dev_dbg(aspi->dev, "CE%d cleanup done\n", cs); +} + +static void aspeed_spi_enable(struct aspeed_spi *aspi, bool enable) +{ + int cs; + + for (cs = 0; cs < aspi->data->max_cs; cs++) + aspeed_spi_chip_enable(aspi, cs, enable); +} + +static int aspeed_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct aspeed_spi_data *data; + struct spi_controller *ctlr; + struct aspeed_spi *aspi; + struct resource *res; + int ret; + + data = of_device_get_match_data(&pdev->dev); + if (!data) + return -ENODEV; + + ctlr = devm_spi_alloc_master(dev, sizeof(*aspi)); + if (!ctlr) + return -ENOMEM; + + aspi = spi_controller_get_devdata(ctlr); + platform_set_drvdata(pdev, aspi); + aspi->data = data; + aspi->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + aspi->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(aspi->regs)) { + dev_err(dev, "missing AHB register window\n"); + return PTR_ERR(aspi->regs); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + aspi->ahb_base = devm_ioremap_resource(dev, res); + if (IS_ERR(aspi->ahb_base)) { + dev_err(dev, "missing AHB mapping window\n"); + return PTR_ERR(aspi->ahb_base); + } + + aspi->ahb_window_size = resource_size(res); + aspi->ahb_base_phy = res->start; + + aspi->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(aspi->clk)) { + dev_err(dev, "missing clock\n"); + return PTR_ERR(aspi->clk); + } + + aspi->clk_freq = clk_get_rate(aspi->clk); + if (!aspi->clk_freq) { + dev_err(dev, "invalid clock\n"); + return -EINVAL; + } + + ret = clk_prepare_enable(aspi->clk); + if (ret) { + dev_err(dev, "can not enable the clock\n"); + return ret; + } + + /* IRQ is for DMA, which the driver doesn't support yet */ + + ctlr->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | data->mode_bits; + ctlr->bus_num = pdev->id; + ctlr->mem_ops = &aspeed_spi_mem_ops; + ctlr->setup = aspeed_spi_setup; + ctlr->cleanup = aspeed_spi_cleanup; + ctlr->num_chipselect = data->max_cs; + ctlr->dev.of_node = dev->of_node; + + ret = devm_spi_register_controller(dev, ctlr); + if (ret) { + dev_err(&pdev->dev, "spi_register_controller failed\n"); + goto disable_clk; + } + return 0; + +disable_clk: + clk_disable_unprepare(aspi->clk); + return ret; +} + +static int aspeed_spi_remove(struct platform_device *pdev) +{ + struct aspeed_spi *aspi = platform_get_drvdata(pdev); + + aspeed_spi_enable(aspi, false); + clk_disable_unprepare(aspi->clk); + return 0; +} + +/* + * AHB mappings + */ + +/* + * The Segment Registers of the AST2400 and AST2500 use a 8MB unit. + * The address range is encoded with absolute addresses in the overall + * mapping window. + */ +static u32 aspeed_spi_segment_start(struct aspeed_spi *aspi, u32 reg) +{ + return ((reg >> 16) & 0xFF) << 23; +} + +static u32 aspeed_spi_segment_end(struct aspeed_spi *aspi, u32 reg) +{ + return ((reg >> 24) & 0xFF) << 23; +} + +static u32 aspeed_spi_segment_reg(struct aspeed_spi *aspi, u32 start, u32 end) +{ + return (((start >> 23) & 0xFF) << 16) | (((end >> 23) & 0xFF) << 24); +} + +/* + * The Segment Registers of the AST2600 use a 1MB unit. The address + * range is encoded with offsets in the overall mapping window. + */ + +#define AST2600_SEG_ADDR_MASK 0x0ff00000 + +static u32 aspeed_spi_segment_ast2600_start(struct aspeed_spi *aspi, + u32 reg) +{ + u32 start_offset = (reg << 16) & AST2600_SEG_ADDR_MASK; + + return aspi->ahb_base_phy + start_offset; +} + +static u32 aspeed_spi_segment_ast2600_end(struct aspeed_spi *aspi, + u32 reg) +{ + u32 end_offset = reg & AST2600_SEG_ADDR_MASK; + + /* segment is disabled */ + if (!end_offset) + return aspi->ahb_base_phy; + + return aspi->ahb_base_phy + end_offset + 0x100000; +} + +static u32 aspeed_spi_segment_ast2600_reg(struct aspeed_spi *aspi, + u32 start, u32 end) +{ + /* disable zero size segments */ + if (start == end) + return 0; + + return ((start & AST2600_SEG_ADDR_MASK) >> 16) | + ((end - 1) & AST2600_SEG_ADDR_MASK); +} + +/* + * Platform definitions + */ +static const struct aspeed_spi_data ast2400_fmc_data = { + .max_cs = 5, + .hastype = true, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .segment_start = aspeed_spi_segment_start, + .segment_end = aspeed_spi_segment_end, + .segment_reg = aspeed_spi_segment_reg, +}; + +static const struct aspeed_spi_data ast2500_fmc_data = { + .max_cs = 3, + .hastype = true, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .segment_start = aspeed_spi_segment_start, + .segment_end = aspeed_spi_segment_end, + .segment_reg = aspeed_spi_segment_reg, +}; + +static const struct aspeed_spi_data ast2500_spi_data = { + .max_cs = 2, + .hastype = false, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .segment_start = aspeed_spi_segment_start, + .segment_end = aspeed_spi_segment_end, + .segment_reg = aspeed_spi_segment_reg, +}; + +static const struct aspeed_spi_data ast2600_fmc_data = { + .max_cs = 3, + .hastype = false, + .mode_bits = SPI_RX_QUAD | SPI_RX_QUAD, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .segment_start = aspeed_spi_segment_ast2600_start, + .segment_end = aspeed_spi_segment_ast2600_end, + .segment_reg = aspeed_spi_segment_ast2600_reg, +}; + +static const struct aspeed_spi_data ast2600_spi_data = { + .max_cs = 2, + .hastype = false, + .mode_bits = SPI_RX_QUAD | SPI_RX_QUAD, + .we0 = 16, + .ctl0 = CE0_CTRL_REG, + .segment_start = aspeed_spi_segment_ast2600_start, + .segment_end = aspeed_spi_segment_ast2600_end, + .segment_reg = aspeed_spi_segment_ast2600_reg, +}; + +static const struct of_device_id aspeed_spi_matches[] = { + { .compatible = "aspeed,ast2400-fmc", .data = &ast2400_fmc_data }, + { .compatible = "aspeed,ast2500-fmc", .data = &ast2500_fmc_data }, + { .compatible = "aspeed,ast2500-spi", .data = &ast2500_spi_data }, + { .compatible = "aspeed,ast2600-fmc", .data = &ast2600_fmc_data }, + { .compatible = "aspeed,ast2600-spi", .data = &ast2600_spi_data }, + { } +}; +MODULE_DEVICE_TABLE(of, aspeed_spi_matches); + +static struct platform_driver aspeed_spi_driver = { + .probe = aspeed_spi_probe, + .remove = aspeed_spi_remove, + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_spi_matches, + } +}; + +module_platform_driver(aspeed_spi_driver); + +MODULE_DESCRIPTION("ASPEED Static Memory Controller Driver"); +MODULE_AUTHOR("Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>"); +MODULE_AUTHOR("Cedric Le Goater <clg@kaod.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/Documentation/devicetree/bindings/mtd/aspeed-smc.txt b/Documentation/devicetree/bindings/mtd/aspeed-smc.txt deleted file mode 100644 index 49f6528ef547..000000000000 --- a/Documentation/devicetree/bindings/mtd/aspeed-smc.txt +++ /dev/null @@ -1,51 +0,0 @@ -* Aspeed Firmware Memory controller -* Aspeed SPI Flash Memory Controller - -The Firmware Memory Controller in the Aspeed AST2500 SoC supports -three chip selects, two of which are always of SPI type and the third -can be SPI or NOR type flash. These bindings only describe SPI. - -The two SPI flash memory controllers in the AST2500 each support two -chip selects. - -Required properties: - - compatible : Should be one of - "aspeed,ast2400-fmc" for the AST2400 Firmware Memory Controller - "aspeed,ast2400-spi" for the AST2400 SPI Flash memory Controller - "aspeed,ast2500-fmc" for the AST2500 Firmware Memory Controller - "aspeed,ast2500-spi" for the AST2500 SPI flash memory 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 - max frequency of spi bus - - -Example: -fmc: fmc@1e620000 { - compatible = "aspeed,ast2500-fmc"; - reg = < 0x1e620000 0x94 - 0x20000000 0x02000000 >; - #address-cells = <1>; - #size-cells = <0>; - interrupts = <19>; - flash@0 { - reg = < 0 >; - compatible = "jedec,spi-nor"; - /* spi-max-frequency = <>; */ - /* m25p,fast-read; */ - #address-cells = <1>; - #size-cells = <1>; - }; -}; diff --git a/MAINTAINERS b/MAINTAINERS index 7d5f81dcd837..f4605689f41c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3029,6 +3029,7 @@ L: openbmc@lists.ozlabs.org (moderated for non-subscribers) L: linux-spi@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/spi/aspeed,ast2600-fmc.yaml +F: drivers/spi/spi-aspeed-smc.c ASPEED VIDEO ENGINE DRIVER M: Eddie James <eajames@linux.ibm.com> diff --git a/drivers/mtd/spi-nor/controllers/Kconfig b/drivers/mtd/spi-nor/controllers/Kconfig index 50f4f3484d42..ca45dcd3ffe8 100644 --- a/drivers/mtd/spi-nor/controllers/Kconfig +++ b/drivers/mtd/spi-nor/controllers/Kconfig @@ -1,14 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -config SPI_ASPEED_SMC - tristate "Aspeed flash controllers in SPI mode" - depends on ARCH_ASPEED || COMPILE_TEST - depends on HAS_IOMEM && OF - help - This enables support for the Firmware Memory controller (FMC) - in the Aspeed AST2500/AST2400 SoCs when attached to SPI NOR chips, - and support for the SPI flash memory controller (SPI) for - the host firmware. The implementation only supports SPI NOR. - config SPI_HISI_SFC tristate "Hisilicon FMC SPI NOR Flash Controller(SFC)" depends on ARCH_HISI || COMPILE_TEST diff --git a/drivers/mtd/spi-nor/controllers/Makefile b/drivers/mtd/spi-nor/controllers/Makefile index 6e2a1dc68466..0b8e1d530913 100644 --- a/drivers/mtd/spi-nor/controllers/Makefile +++ b/drivers/mtd/spi-nor/controllers/Makefile @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 31a2cef3790c..9f6679cb0f78 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -101,6 +101,17 @@ config SPI_ARMADA_3700 This enables support for the SPI controller present on the Marvell Armada 3700 SoCs. +config SPI_ASPEED_SMC + tristate "Aspeed flash controllers in SPI mode" + depends on ARCH_ASPEED || COMPILE_TEST + depends on OF + help + This enables support for the Firmware Memory controller (FMC) + in the Aspeed AST2600, AST2500 and AST2400 SoCs when attached + to SPI NOR chips, and support for the SPI flash memory + controller (SPI) for the host firmware. The implementation + only supports SPI NOR. + config SPI_ATMEL tristate "Atmel SPI Controller" depends on ARCH_AT91 || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 3aa28ed3f761..1566388b96ae 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_SPI_ALTERA_CORE) += spi-altera-core.o obj-$(CONFIG_SPI_ALTERA_DFL) += spi-altera-dfl.o obj-$(CONFIG_SPI_AR934X) += spi-ar934x.o obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o +obj-$(CONFIG_SPI_ASPEED_SMC) += spi-aspeed-smc.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