Re: [PATCH 4/5] mtd: spi-nor: Add driver for Adaptrum Anarion QSPI controller

From: Marek Vasut
Date: Sat Jul 29 2017 - 05:34:40 EST


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 <alex.g@xxxxxxxxxxxx>
> ---
> .../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 <alex.g at adaptrum.com> for Adaptrum, Inc.)
> + * Licensed under the GPLv2 or (at your option) any later version.

The GPL boilerplate should be here.

> + */
> +
> +#include <linux/io.h>
> +#include <linux/delay.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/mtd/spi-nor.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +
> +#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 <mr.nuke.me@xxxxxxxxx>");
> +MODULE_LICENSE("GPL v2");
>


--
Best regards,
Marek Vasut