Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753638AbdG2Jef (ORCPT ); Sat, 29 Jul 2017 05:34:35 -0400 Received: from mail-wm0-f66.google.com ([74.125.82.66]:38884 "EHLO mail-wm0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753513AbdG2Jeb (ORCPT ); Sat, 29 Jul 2017 05:34:31 -0400 Subject: Re: [PATCH 4/5] mtd: spi-nor: Add driver for Adaptrum Anarion QSPI controller To: Alexandru Gagniuc , linux-snps-arc@lists.infradead.org, linux-kernel@vger.kernel.org Cc: David Woodhouse , Brian Norris , Boris Brezillon , Richard Weinberger , Cyrille Pitchen , Rob Herring , Mark Rutland , linux-mtd@lists.infradead.org, devicetree@vger.kernel.org References: <20170728220707.13960-1-alex.g@adaptrum.com> <20170728220707.13960-5-alex.g@adaptrum.com> From: Marek Vasut Message-ID: <135fdf95-1029-2d34-2802-1283a73588e5@gmail.com> Date: Sat, 29 Jul 2017 11:34:27 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.2.1 MIME-Version: 1.0 In-Reply-To: <20170728220707.13960-5-alex.g@adaptrum.com> Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 18072 Lines: 619 On 07/29/2017 12:07 AM, Alexandru Gagniuc wrote: > Add support for the QSPI controller found in Adaptrum Anarion SOCs. > This controller is designed specifically to handle SPI NOR chips, and > the driver is modeled as such. > > Because the system is emulated on an FPGA, we don't have a way to test > all the hardware adjustemts, so only basic features are implemented at > this time. > > Signed-off-by: Alexandru Gagniuc > --- > .../devicetree/bindings/mtd/anarion-quadspi.txt | 22 + > drivers/mtd/spi-nor/Kconfig | 7 + > drivers/mtd/spi-nor/Makefile | 1 + > drivers/mtd/spi-nor/anarion-quadspi.c | 490 +++++++++++++++++++++ > 4 files changed, 520 insertions(+) > create mode 100644 Documentation/devicetree/bindings/mtd/anarion-quadspi.txt > create mode 100644 drivers/mtd/spi-nor/anarion-quadspi.c > > diff --git a/Documentation/devicetree/bindings/mtd/anarion-quadspi.txt b/Documentation/devicetree/bindings/mtd/anarion-quadspi.txt > new file mode 100644 > index 0000000..b4971e1 > --- /dev/null > +++ b/Documentation/devicetree/bindings/mtd/anarion-quadspi.txt > @@ -0,0 +1,22 @@ > +* Adaptrum Anarion Quad SPI controller > + > +Required properties: > +- compatible : Should be "adaptrum,anarion-qspi". > +- reg : Contains two entries, each of which is a tuple consisting of a > + physical address and length. The first entry is the address and > + length of the controller register set. The second entry is the > + address and length of the memory-mapped flash. This second region is > + the region where the controller responds to XIP requests, and may be > + larger than the size of the attached flash. You want to split the bindings into separate patch and CC Rob to review them. > +Example: > + > + qspi: qspi@f200f000 { > + compatible = "adaptrum,anarion-qspi"; > + reg = <0xf200f000 0x1000>, > + <0x20000000 0x08000000>; > + > + flash0: w25q128fvn@0 { > + ... > + } > + }; > diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig > index 293c8a4..98dc012 100644 > --- a/drivers/mtd/spi-nor/Kconfig > +++ b/drivers/mtd/spi-nor/Kconfig > @@ -48,6 +48,13 @@ config SPI_ATMEL_QUADSPI > This driver does not support generic SPI. The implementation only > supports SPI NOR. > > +config SPI_ANARION_QUADSPI > + tristate "Adaptrum Anarion Quad SPI Controller" > + depends on OF && HAS_IOMEM > + help > + Enable support for the Adaptrum Anarion Quad SPI controller. > + This driver does not support generic SPI. It only supports SPI NOR. Keep the list sorted. > config SPI_CADENCE_QUADSPI > tristate "Cadence Quad SPI controller" > depends on OF && (ARM || COMPILE_TEST) > diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile > index 285aab8..53635f6 100644 > --- a/drivers/mtd/spi-nor/Makefile > +++ b/drivers/mtd/spi-nor/Makefile > @@ -1,6 +1,7 @@ > obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o > obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o > obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o > +obj-$(CONFIG_SPI_ANARION_QUADSPI) += anarion-quadspi.o DTTO, N is before S and T . > obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o > obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o > obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o > diff --git a/drivers/mtd/spi-nor/anarion-quadspi.c b/drivers/mtd/spi-nor/anarion-quadspi.c > new file mode 100644 > index 0000000..d981356 > --- /dev/null > +++ b/drivers/mtd/spi-nor/anarion-quadspi.c > @@ -0,0 +1,490 @@ > +/* > + * Adaptrum Anarion Quad SPI controller driver > + * > + * Copyright (C) 2017, Adaptrum, Inc. > + * (Written by Alexandru Gagniuc for Adaptrum, Inc.) > + * Licensed under the GPLv2 or (at your option) any later version. The GPL boilerplate should be here. > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#define ASPI_REG_CLOCK 0x00 > +#define ASPI_REG_GO 0x04 > +#define ASPI_REG_CHAIN 0x08 > +#define ASPI_REG_CMD1 0x0c > +#define ASPI_REG_CMD2 0x10 > +#define ASPI_REG_ADDR1 0x14 > +#define ASPI_REG_ADDR2 0x18 > +#define ASPI_REG_PERF1 0x1c > +#define ASPI_REG_PERF2 0x20 > +#define ASPI_REG_HI_Z 0x24 > +#define ASPI_REG_BYTE_COUNT 0x28 > +#define ASPI_REG_DATA1 0x2c > +#define ASPI_REG_DATA2 0x30 > +#define ASPI_REG_FINISH 0x34 > +#define ASPI_REG_XIP 0x38 > +#define ASPI_REG_FIFO_STATUS 0x3c > +#define ASPI_REG_LAT 0x40 > +#define ASPI_REG_OUT_DELAY_0 0x44 > +#define ASPI_REG_OUT_DELAY_1 0x48 > +#define ASPI_REG_IN_DELAY_0 0x4c > +#define ASPI_REG_IN_DELAY_1 0x50 > +#define ASPI_REG_DQS_DELAY 0x54 > +#define ASPI_REG_STATUS 0x58 > +#define ASPI_REG_IRQ_ENABLE 0x5c > +#define ASPI_REG_IRQ_STATUS 0x60 > +#define ASPI_REG_AXI_BAR 0x64 > +#define ASPI_REG_READ_CFG 0x6c > + > +#define ASPI_CLK_SW_RESET (1 << 0) BIT(0) , fix globally > +#define ASPI_CLK_RESET_BUF (1 << 1) > +#define ASPI_CLK_RESET_ALL (ASPI_CLK_SW_RESET | ASPI_CLK_RESET_BUF) > +#define ASPI_CLK_SPI_MODE3 (1 << 2) > +#define ASPI_CLOCK_DIV_MASK (0xff << 8) > +#define ASPI_CLOCK_DIV(d) (((d) << 8) & ASPI_CLOCK_DIV_MASK) > + > +#define ASPI_TIMEOUT_US 100000 > + > +#define ASPI_DATA_LEN_MASK 0x3fff > +#define ASPI_MAX_XFER_LEN (size_t)(ASPI_DATA_LEN_MASK + 1) > + > +#define MODE_IO_X1 (0 << 16) > +#define MODE_IO_X2 (1 << 16) > +#define MODE_IO_X4 (2 << 16) > +#define MODE_IO_SDR_POS_SKEW (0 << 20) > +#define MODE_IO_SDR_NEG_SKEW (1 << 20) > +#define MODE_IO_DDR_34_SKEW (2 << 20) > +#define MODE_IO_DDR_PN_SKEW (3 << 20) > +#define MODE_IO_DDR_DQS (5 << 20) > + > +#define ASPI_STATUS_BUSY (1 << 2) > + > +/* > + * This mask does not match reality. Get over it: What is this about ? > + * DATA2: 0x3fff > + * CMD2: 0x0003 > + * ADDR2: 0x0007 > + * PERF2: 0x0000 > + * HI_Z: 0x003f > + * BCNT: 0x0007 > + */ > +#define CHAIN_LEN(x) ((x - 1) & ASPI_DATA_LEN_MASK) > + > +struct anarion_qspi { > + struct spi_nor nor; > + struct device *dev; > + uintptr_t regbase; Should be void __iomem * I guess ? > + uintptr_t xipbase; > + uint32_t xfer_mode_cmd; u32 etc, fix globally, this is not userspace. > + uint32_t xfer_mode_addr; > + uint32_t xfer_mode_data; > + uint8_t num_hi_z_clocks; > +}; > + > +struct qspi_io_chain { > + uint8_t action; > + uint32_t data; > + uint16_t data_len; > + uint32_t mode; > +}; > + > +enum chain_code { > + CHAIN_NOP = 0, > + CHAIN_CMD = 1, > + CHAIN_ADDR = 2, > + CHAIN_WTFIUM = 3, > + CHAIN_HI_Z = 4, > + CHAIN_DATA_OUT = 5, > + CHAIN_DATA_IN = 6, > + CHAIN_FINISH = 7, > +}; > + > +static const struct chain_to_reg { > + uint8_t data_reg; > + uint8_t ctl_reg; > +} chain_to_reg_map[] = { > + [CHAIN_NOP] = {0, 0}, > + [CHAIN_CMD] = {ASPI_REG_CMD1, ASPI_REG_CMD2}, > + [CHAIN_ADDR] = {ASPI_REG_ADDR1, ASPI_REG_ADDR2}, > + [CHAIN_WTFIUM] = {0, 0}, > + [CHAIN_HI_Z] = {0, ASPI_REG_HI_Z}, > + [CHAIN_DATA_OUT] = {0, ASPI_REG_DATA2}, > + [CHAIN_DATA_IN] = {0, ASPI_REG_DATA2}, > + [CHAIN_FINISH] = {0, ASPI_REG_FINISH}, > +}; > + > +static uint32_t aspi_read_reg(struct anarion_qspi *spi, uint8_t reg) > +{ > + return readl((void *)(spi->regbase + reg)); > +}; > + > +static void aspi_write_reg(struct anarion_qspi *spi, uint8_t reg, uint32_t val) > +{ > + writel(val, (void *)(spi->regbase + reg)); > +}; > + > +static size_t aspi_get_fifo_level(struct anarion_qspi *spi) > +{ > + return aspi_read_reg(spi, ASPI_REG_FIFO_STATUS) & 0xff; > +} > + > +static void aspi_drain_fifo(struct anarion_qspi *aspi, uint8_t *buf, size_t len) > +{ > + uint32_t data; Is this stuff below something like ioread32_rep() ? > + aspi_write_reg(aspi, ASPI_REG_BYTE_COUNT, sizeof(uint32_t)); > + while (len >= 4) { > + data = aspi_read_reg(aspi, ASPI_REG_DATA1); > + memcpy(buf, &data, sizeof(data)); > + buf += 4; > + len -= 4; > + } > + > + if (len) { > + aspi_write_reg(aspi, ASPI_REG_BYTE_COUNT, len); > + data = aspi_read_reg(aspi, ASPI_REG_DATA1); > + memcpy(buf, &data, len); > + } > +} > + > +static void aspi_seed_fifo(struct anarion_qspi *spi, > + const uint8_t *buf, size_t len) > +{ > + uint32_t data; > + > + aspi_write_reg(spi, ASPI_REG_BYTE_COUNT, sizeof(uint32_t)); > + while (len >= 4) { > + memcpy(&data, buf, sizeof(data)); > + aspi_write_reg(spi, ASPI_REG_DATA1, data); iowrite32_rep ? > + buf += 4; > + len -= 4; > + } > + > + if (len) { > + aspi_write_reg(spi, ASPI_REG_BYTE_COUNT, len); > + memcpy(&data, buf, len); > + aspi_write_reg(spi, ASPI_REG_DATA1, data); > + } > +} > + > +static int aspi_wait_idle(struct anarion_qspi *aspi) > +{ > + uint32_t status; > + void *status_reg = (void *)(aspi->regbase + ASPI_REG_STATUS); > + > + return readl_poll_timeout(status_reg, status, > + !(status & ASPI_STATUS_BUSY), > + 1, ASPI_TIMEOUT_US); > +} > + > +static int aspi_poll_and_seed_fifo(struct anarion_qspi *spi, > + const void *src_addr, size_t len) > +{ > + size_t wait_us, fifo_space = 0, xfer_len; > + const uint8_t *src = src_addr; > + > + while (len > 0) { > + wait_us = 0; > + while (wait_us++ < ASPI_TIMEOUT_US) { > + fifo_space = 64 - aspi_get_fifo_level(spi); > + if (fifo_space) > + break; > + udelay(1); > + } > + > + xfer_len = min(len, fifo_space); > + aspi_seed_fifo(spi, src, xfer_len); > + src += xfer_len; > + len -= xfer_len; > + } > + > + return 0; > +} > + > +static void aspi_setup_chain(struct anarion_qspi *aspi, > + const struct qspi_io_chain *chain, > + size_t chain_len) > +{ > + size_t i; > + uint32_t chain_reg = 0; > + const struct qspi_io_chain *link; > + const struct chain_to_reg *regs; > + > + for (link = chain, i = 0; i < chain_len; i++, link++) { > + regs = &chain_to_reg_map[link->action]; > + > + if (link->data_len && regs->data_reg) > + aspi_write_reg(aspi, regs->data_reg, link->data); > + > + if (regs->ctl_reg) > + aspi_write_reg(aspi, regs->ctl_reg, > + CHAIN_LEN(link->data_len) | link->mode); > + > + chain_reg |= link->action << (i * 4); > + } > + > + chain_reg |= CHAIN_FINISH << (i * 4); > + > + aspi_write_reg(aspi, ASPI_REG_CHAIN, chain_reg); > +} > + > +static int aspi_execute_chain(struct anarion_qspi *aspi) > +{ > + /* Go, johnny go */ > + aspi_write_reg(aspi, ASPI_REG_GO, 1); > + return aspi_wait_idle(aspi); > +} > + > +static int anarion_spi_read_nor_reg(struct spi_nor *nor, uint8_t opcode, > + uint8_t *buf, int len) > +{ > + struct anarion_qspi *aspi = nor->priv; > + struct qspi_io_chain chain[] = { > + {CHAIN_CMD, opcode, 1, MODE_IO_X1}, > + {CHAIN_DATA_IN, 0, (uint16_t)len, MODE_IO_X1}, > + }; > + > + if (len >= 8) > + return -EMSGSIZE; > + > + aspi_setup_chain(aspi, chain, ARRAY_SIZE(chain)); > + aspi_execute_chain(aspi); > + > + aspi_drain_fifo(aspi, buf, len); > + > + return 0; > +} > + > +static int anarion_qspi_cmd_addr(struct anarion_qspi *aspi, uint16_t cmd, > + uint32_t addr, int addr_len) > +{ > + size_t chain_size; > + const struct qspi_io_chain chain[] = { > + {CHAIN_CMD, cmd, 1, MODE_IO_X1}, > + {CHAIN_ADDR, addr, addr_len, MODE_IO_X1}, > + }; > + > + chain_size = addr_len ? ARRAY_SIZE(chain) : (ARRAY_SIZE(chain) - 1); > + aspi_setup_chain(aspi, chain, chain_size); > + return aspi_execute_chain(aspi); > +} > + > +static int anarion_spi_write_nor_reg(struct spi_nor *nor, uint8_t opcode, > + uint8_t *buf, int len) > +{ > + uint32_t addr, i; > + struct anarion_qspi *aspi = nor->priv; > + > + if (len > sizeof(uint32_t)) > + return -ENOTSUPP; > + > + for (i = 0, addr = 0; i < len; i++) > + addr |= buf[len - 1 - i] << (i * 8); > + > + return anarion_qspi_cmd_addr(aspi, opcode, addr, len); > +} > + > +/* After every operation, we need to restore the IO chain for XIP to work. */ > +static void aspi_setup_xip_read_chain(struct anarion_qspi *spi, > + struct spi_nor *nor) > +{ > + struct qspi_io_chain chain[] = { > + {CHAIN_CMD, nor->read_opcode, 1, spi->xfer_mode_cmd}, > + {CHAIN_ADDR, 0, nor->addr_width, spi->xfer_mode_addr}, > + {CHAIN_HI_Z, 0, spi->num_hi_z_clocks, spi->xfer_mode_addr}, > + {CHAIN_DATA_IN, 0, ASPI_DATA_LEN_MASK, spi->xfer_mode_data}, > + }; > + > + aspi_setup_chain(spi, chain, ARRAY_SIZE(chain)); > +} > + > +static int aspi_do_write_xfer(struct anarion_qspi *spi, > + struct spi_nor *nor, uint32_t addr, > + const void *buf, size_t len) > +{ > + struct qspi_io_chain chain[] = { > + {CHAIN_CMD, nor->program_opcode, 1, MODE_IO_X1}, > + {CHAIN_ADDR, addr, nor->addr_width, MODE_IO_X1}, > + {CHAIN_DATA_OUT, 0, len, MODE_IO_X1}, > + }; > + > + aspi_setup_chain(spi, chain, ARRAY_SIZE(chain)); > + > + /* Go, johnny go */ > + aspi_write_reg(spi, ASPI_REG_GO, 1); > + > + aspi_poll_and_seed_fifo(spi, buf, len); > + return aspi_wait_idle(spi); > +} > + > +/* While we could send read commands manually to the flash chip, we'd have to > + * get data back through the DATA2 register. That is on the AHB bus, whereas > + * XIP reads go over AXI. Hence, we use the memory-mapped flash space for read. > + * TODO: Look at using DMA instead of memcpy(). > + */ Multiline comment looks like this, /* * foo * bar */ > +static ssize_t anarion_spi_nor_read(struct spi_nor *nor, loff_t from, > + size_t len, uint8_t *read_buf) > +{ > + struct anarion_qspi *aspi = nor->priv; > + void *from_xip = (void *)(aspi->xipbase + from); > + > + aspi_setup_xip_read_chain(aspi, nor); > + memcpy(read_buf, from_xip, len); > + > + return len; > +} > + > +static ssize_t anarion_spi_nor_write(struct spi_nor *nor, loff_t to, > + size_t len, const uint8_t *src) > +{ > + int ret; > + struct anarion_qspi *aspi = nor->priv; > + > + dev_err(aspi->dev, "%s, @0x%llx + %zu\n", __func__, to, len); Drop this. > + if (len > nor->page_size) > + return -EINVAL; > + > + ret = aspi_do_write_xfer(aspi, nor, to, src, len); > + return (ret < 0) ? ret : len; > +} > + > +/* TODO: Revisit this when we get actual HW. Right now max speed is 6 MHz. */ > +static void aspi_configure_clocks(struct anarion_qspi *aspi) > +{ > + uint8_t div = 0; > + uint32_t ck_ctl = aspi_read_reg(aspi, ASPI_REG_CLOCK); > + > + ck_ctl &= ~ASPI_CLOCK_DIV_MASK; > + ck_ctl |= ASPI_CLOCK_DIV(div); > + aspi_write_reg(aspi, ASPI_REG_CLOCK, ck_ctl); > +} > + > +static int anarion_qspi_drv_probe(struct platform_device *pdev) > +{ > + int ret; > + void __iomem *mmiobase; > + struct resource *res; > + struct anarion_qspi *aspi; > + struct device_node *flash_node; > + struct spi_nor *nor; > + > + aspi = devm_kzalloc(&pdev->dev, sizeof(*aspi), GFP_KERNEL); > + if (!aspi) > + return -ENOMEM; > + platform_set_drvdata(pdev, aspi); > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + mmiobase = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(mmiobase)) { > + dev_err(&pdev->dev, "Cannot get base addresses (%ld)!\n", > + PTR_ERR(mmiobase)); > + return PTR_ERR(mmiobase); > + } > + aspi->regbase = (uintptr_t)mmiobase; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); > + mmiobase = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(mmiobase)) { > + dev_err(&pdev->dev, "Cannot get XIP addresses (%ld)!\n", > + PTR_ERR(mmiobase)); > + return PTR_ERR(mmiobase); > + } > + aspi->xipbase = (uintptr_t)mmiobase; > + > + aspi->dev = &pdev->dev; > + > + /* only support one attached flash */ > + flash_node = of_get_next_available_child(pdev->dev.of_node, NULL); > + if (!flash_node) { > + dev_err(&pdev->dev, "no SPI flash device to configure\n"); > + return -ENODEV; > + } > + > + /* Reset the controller */ > + aspi_write_reg(aspi, ASPI_REG_CLOCK, ASPI_CLK_RESET_ALL); > + aspi_write_reg(aspi, ASPI_REG_LAT, 0x010); > + aspi_configure_clocks(aspi); > + > + nor = &aspi->nor; > + nor->priv = aspi; > + nor->dev = aspi->dev; > + nor->read = anarion_spi_nor_read; > + nor->write = anarion_spi_nor_write; > + nor->read_reg = anarion_spi_read_nor_reg; > + nor->write_reg = anarion_spi_write_nor_reg; > + > + spi_nor_set_flash_node(nor, flash_node); > + > + ret = spi_nor_scan(&aspi->nor, NULL, SPI_NOR_DUAL); > + if (ret) > + return ret; > + > + switch (nor->flash_read) { > + default: /* Fall through */ This will break once we add OSPI support ... > + case SPI_NOR_NORMAL: > + aspi->num_hi_z_clocks = nor->read_dummy; > + aspi->xfer_mode_cmd = MODE_IO_X1; > + aspi->xfer_mode_addr = MODE_IO_X1; > + aspi->xfer_mode_data = MODE_IO_X1; > + break; > + case SPI_NOR_FAST: > + aspi->num_hi_z_clocks = nor->read_dummy; > + aspi->xfer_mode_cmd = MODE_IO_X1; > + aspi->xfer_mode_addr = MODE_IO_X1; > + aspi->xfer_mode_data = MODE_IO_X1; > + break; > + case SPI_NOR_DUAL: > + aspi->num_hi_z_clocks = nor->read_dummy; > + aspi->xfer_mode_cmd = MODE_IO_X1; > + aspi->xfer_mode_addr = MODE_IO_X1; > + aspi->xfer_mode_data = MODE_IO_X2; > + break; > + case SPI_NOR_QUAD: > + aspi->num_hi_z_clocks = nor->read_dummy; > + aspi->xfer_mode_cmd = MODE_IO_X1; > + aspi->xfer_mode_addr = MODE_IO_X1; > + aspi->xfer_mode_data = MODE_IO_X4; > + break; > + } > + > + aspi_setup_xip_read_chain(aspi, nor); > + > + mtd_device_register(&aspi->nor.mtd, NULL, 0); > + > + return 0; > +} > + > +static int anarion_qspi_drv_remove(struct platform_device *pdev) > +{ > + struct anarion_qspi *aspi = platform_get_drvdata(pdev); > + > + mtd_device_unregister(&aspi->nor.mtd); > + return 0; > +} > + > +static const struct of_device_id anarion_qspi_of_match[] = { > + { .compatible = "adaptrum,anarion-qspi" }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, anarion_qspi_of_match); > + > +static struct platform_driver anarion_qspi_driver = { > + .driver = { > + .name = "anarion-qspi", > + .of_match_table = anarion_qspi_of_match, > + }, > + .probe = anarion_qspi_drv_probe, > + .remove = anarion_qspi_drv_remove, > +}; > +module_platform_driver(anarion_qspi_driver); > + > +MODULE_DESCRIPTION("Adaptrum Anarion Quad SPI Controller Driver"); > +MODULE_AUTHOR("Alexandru Gagniuc "); > +MODULE_LICENSE("GPL v2"); > -- Best regards, Marek Vasut