root/drivers/spi/spi-atcspi200.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Driver for Andes ATCSPI200 SPI Controller
 *
 * Copyright (C) 2025 Andes Technology Corporation.
 */

#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/dev_printk.h>
#include <linux/dmaengine.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/jiffies.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>

/* Register definitions  */
#define ATCSPI_TRANS_FMT        0x10    /* SPI transfer format register */
#define ATCSPI_TRANS_CTRL       0x20    /* SPI transfer control register */
#define ATCSPI_CMD              0x24    /* SPI command register */
#define ATCSPI_ADDR             0x28    /* SPI address register */
#define ATCSPI_DATA             0x2C    /* SPI data register */
#define ATCSPI_CTRL             0x30    /* SPI control register */
#define ATCSPI_STATUS           0x34    /* SPI status register */
#define ATCSPI_TIMING           0x40    /* SPI interface timing register */
#define ATCSPI_CONFIG           0x7C    /* SPI configuration register */

/* Transfer format register */
#define TRANS_FMT_CPHA          BIT(0)
#define TRANS_FMT_CPOL          BIT(1)
#define TRANS_FMT_DATA_MERGE_EN BIT(7)
#define TRANS_FMT_DATA_LEN_MASK GENMASK(12, 8)
#define TRANS_FMT_ADDR_LEN_MASK GENMASK(17, 16)
#define TRANS_FMT_DATA_LEN(x)   FIELD_PREP(TRANS_FMT_DATA_LEN_MASK, (x) - 1)
#define TRANS_FMT_ADDR_LEN(x)   FIELD_PREP(TRANS_FMT_ADDR_LEN_MASK, (x) - 1)

/* Transfer control register */
#define TRANS_MODE_MASK         GENMASK(27, 24)
#define TRANS_MODE_W_ONLY       FIELD_PREP(TRANS_MODE_MASK, 1)
#define TRANS_MODE_R_ONLY       FIELD_PREP(TRANS_MODE_MASK, 2)
#define TRANS_MODE_NONE_DATA    FIELD_PREP(TRANS_MODE_MASK, 7)
#define TRANS_MODE_DMY_READ     FIELD_PREP(TRANS_MODE_MASK, 9)
#define TRANS_FIELD_DECNZ(m, x) ((x) ? FIELD_PREP(m, (x) - 1) : 0)
#define TRANS_RD_TRANS_CNT(x)   TRANS_FIELD_DECNZ(GENMASK(8, 0), x)
#define TRANS_DUMMY_CNT(x)      TRANS_FIELD_DECNZ(GENMASK(10, 9), x)
#define TRANS_WR_TRANS_CNT(x)   TRANS_FIELD_DECNZ(GENMASK(20, 12), x)
#define TRANS_DUAL_QUAD(x)      FIELD_PREP(GENMASK(23, 22), (x))
#define TRANS_ADDR_FMT          BIT(28)
#define TRANS_ADDR_EN           BIT(29)
#define TRANS_CMD_EN            BIT(30)

/* Control register */
#define CTRL_SPI_RST            BIT(0)
#define CTRL_RX_FIFO_RST        BIT(1)
#define CTRL_TX_FIFO_RST        BIT(2)
#define CTRL_RX_DMA_EN          BIT(3)
#define CTRL_TX_DMA_EN          BIT(4)

/* Status register */
#define ATCSPI_ACTIVE           BIT(0)
#define ATCSPI_RX_EMPTY         BIT(14)
#define ATCSPI_TX_FULL          BIT(23)

/* Interface timing setting */
#define TIMING_SCLK_DIV_MASK    GENMASK(7, 0)
#define TIMING_SCLK_DIV_MAX     0xFE

/* Configuration register */
#define RXFIFO_SIZE(x)          FIELD_GET(GENMASK(3, 0), (x))
#define TXFIFO_SIZE(x)          FIELD_GET(GENMASK(7, 4), (x))

/* driver configurations */
#define ATCSPI_MAX_TRANS_LEN    512
#define ATCSPI_MAX_SPEED_HZ     50000000
#define ATCSPI_RDY_TIMEOUT_US   1000000
#define ATCSPI_XFER_TIMEOUT(n)  ((n) * 10)
#define ATCSPI_MAX_CS_NUM       1
#define ATCSPI_DMA_THRESHOLD    256
#define ATCSPI_BITS_PER_UINT    8
#define ATCSPI_DATA_MERGE_EN    1
#define ATCSPI_DMA_SUPPORT      1

/**
 * struct atcspi_dev - Andes ATCSPI200 SPI controller private data
 * @host:           Pointer to the SPI controller structure.
 * @mutex_lock:     A mutex to protect concurrent access to the controller.
 * @dma_completion: A completion to signal the end of a DMA transfer.
 * @dev:            Pointer to the device structure.
 * @regmap:         Register map for accessing controller registers.
 * @clk:            Pointer to the controller's functional clock.
 * @dma_addr:       The physical address of the SPI data register for DMA.
 * @clk_rate:       The cached frequency of the functional clock.
 * @sclk_rate:      The target frequency for the SPI clock (SCLK).
 * @txfifo_size:    The size of the transmit FIFO in bytes.
 * @rxfifo_size:    The size of the receive FIFO in bytes.
 * @data_merge:     A flag indicating if the data merge mode is enabled for
 *                  the current transfer.
 * @use_dma:        Enable DMA mode if ATCSPI_DMA_SUPPORT is set and DMA is
 *                  successfully configured.
 */
struct atcspi_dev {
        struct spi_controller   *host;
        struct mutex            mutex_lock;
        struct completion       dma_completion;
        struct device           *dev;
        struct regmap           *regmap;
        struct clk              *clk;
        dma_addr_t              dma_addr;
        unsigned int            clk_rate;
        unsigned int            sclk_rate;
        unsigned int            txfifo_size;
        unsigned int            rxfifo_size;
        bool                    data_merge;
        bool                    use_dma;
};

static int atcspi_wait_fifo_ready(struct atcspi_dev *spi,
                                  enum spi_mem_data_dir dir)
{
        unsigned int val;
        unsigned int mask;
        int ret;

        mask = (dir == SPI_MEM_DATA_OUT) ? ATCSPI_TX_FULL : ATCSPI_RX_EMPTY;
        ret = regmap_read_poll_timeout(spi->regmap,
                                       ATCSPI_STATUS,
                                       val,
                                       !(val & mask),
                                       0,
                                       ATCSPI_RDY_TIMEOUT_US);
        if (ret)
                dev_info(spi->dev, "Timed out waiting for FIFO ready\n");

        return ret;
}

static int atcspi_xfer_data_poll(struct atcspi_dev *spi,
                                 const struct spi_mem_op *op)
{
        void *rx_buf = op->data.buf.in;
        const void *tx_buf = op->data.buf.out;
        unsigned int val;
        int trans_bytes = op->data.nbytes;
        int num_byte;
        int ret = 0;

        num_byte = spi->data_merge ? 4 : 1;
        while (trans_bytes) {
                if (op->data.dir == SPI_MEM_DATA_OUT) {
                        ret = atcspi_wait_fifo_ready(spi, SPI_MEM_DATA_OUT);
                        if (ret)
                                return ret;

                        if (spi->data_merge)
                                val = *(unsigned int *)tx_buf;
                        else
                                val = *(unsigned char *)tx_buf;
                        regmap_write(spi->regmap, ATCSPI_DATA, val);
                        tx_buf = (unsigned char *)tx_buf + num_byte;
                } else {
                        ret = atcspi_wait_fifo_ready(spi, SPI_MEM_DATA_IN);
                        if (ret)
                                return ret;

                        regmap_read(spi->regmap, ATCSPI_DATA, &val);
                        if (spi->data_merge)
                                *(unsigned int *)rx_buf = val;
                        else
                                *(unsigned char *)rx_buf = (unsigned char)val;
                        rx_buf = (unsigned char *)rx_buf + num_byte;
                }
                trans_bytes -= num_byte;
        }

        return ret;
}

static void atcspi_set_trans_ctl(struct atcspi_dev *spi,
                                 const struct spi_mem_op *op)
{
        unsigned int tc = 0;

        if (op->cmd.nbytes)
                tc |= TRANS_CMD_EN;
        if (op->addr.nbytes)
                tc |= TRANS_ADDR_EN;
        if (op->addr.buswidth > 1)
                tc |= TRANS_ADDR_FMT;
        if (op->data.nbytes) {
                unsigned int width_code;

                width_code = ffs(op->data.buswidth) - 1;
                if (unlikely(width_code > 3)) {
                        WARN_ON_ONCE(1);
                        width_code = 0;
                }
                tc |= TRANS_DUAL_QUAD(width_code);

                if (op->data.dir == SPI_MEM_DATA_IN) {
                        if (op->dummy.nbytes)
                                tc |= TRANS_MODE_DMY_READ |
                                      TRANS_DUMMY_CNT(op->dummy.nbytes);
                        else
                                tc |= TRANS_MODE_R_ONLY;
                        tc |= TRANS_RD_TRANS_CNT(op->data.nbytes);
                } else {
                        tc |= TRANS_MODE_W_ONLY |
                              TRANS_WR_TRANS_CNT(op->data.nbytes);
                }
        } else {
                tc |= TRANS_MODE_NONE_DATA;
        }
        regmap_write(spi->regmap, ATCSPI_TRANS_CTRL, tc);
}

static void atcspi_set_trans_fmt(struct atcspi_dev *spi,
                                 const struct spi_mem_op *op)
{
        unsigned int val;

        regmap_read(spi->regmap, ATCSPI_TRANS_FMT, &val);
        if (op->data.nbytes) {
                if (ATCSPI_DATA_MERGE_EN && ATCSPI_BITS_PER_UINT == 8 &&
                    !(op->data.nbytes % 4)) {
                        val |= TRANS_FMT_DATA_MERGE_EN;
                        spi->data_merge = true;
                } else {
                        val &= ~TRANS_FMT_DATA_MERGE_EN;
                        spi->data_merge = false;
                }
        }

        val = (val & ~TRANS_FMT_ADDR_LEN_MASK) |
              TRANS_FMT_ADDR_LEN(op->addr.nbytes);
        regmap_write(spi->regmap, ATCSPI_TRANS_FMT, val);
}

static void atcspi_prepare_trans(struct atcspi_dev *spi,
                                 const struct spi_mem_op *op)
{
        atcspi_set_trans_fmt(spi, op);
        atcspi_set_trans_ctl(spi, op);
        if (op->addr.nbytes)
                regmap_write(spi->regmap, ATCSPI_ADDR, op->addr.val);
        regmap_write(spi->regmap, ATCSPI_CMD, op->cmd.opcode);
}

static int atcspi_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
{
        struct atcspi_dev *spi;

        spi = spi_controller_get_devdata(mem->spi->controller);
        op->data.nbytes = min(op->data.nbytes, ATCSPI_MAX_TRANS_LEN);

        /* DMA needs to be aligned to 4 byte */
        if (spi->use_dma && op->data.nbytes >= ATCSPI_DMA_THRESHOLD)
                op->data.nbytes = ALIGN_DOWN(op->data.nbytes, 4);

        return 0;
}

static int atcspi_dma_config(struct atcspi_dev *spi, bool is_rx)
{
        struct dma_slave_config conf = { 0 };
        struct dma_chan *chan;

        if (is_rx) {
                chan = spi->host->dma_rx;
                conf.direction = DMA_DEV_TO_MEM;
                conf.src_addr = spi->dma_addr;
        } else {
                chan = spi->host->dma_tx;
                conf.direction = DMA_MEM_TO_DEV;
                conf.dst_addr = spi->dma_addr;
        }
        conf.dst_maxburst = spi->rxfifo_size / 2;
        conf.src_maxburst = spi->txfifo_size / 2;

        if (spi->data_merge) {
                conf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
                conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
        } else {
                conf.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
                conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
        }

        return dmaengine_slave_config(chan, &conf);
}

static void atcspi_dma_callback(void *arg)
{
        struct completion *dma_completion = arg;

        complete(dma_completion);
}

static int atcspi_dma_trans(struct atcspi_dev *spi,
                            const struct spi_mem_op *op)
{
        struct dma_async_tx_descriptor *desc;
        struct dma_chan *dma_ch;
        struct sg_table sgt;
        enum dma_transfer_direction dma_dir;
        dma_cookie_t cookie;
        unsigned int ctrl;
        int timeout;
        int ret;

        regmap_read(spi->regmap, ATCSPI_CTRL, &ctrl);
        ctrl |= CTRL_TX_DMA_EN | CTRL_RX_DMA_EN;
        regmap_write(spi->regmap, ATCSPI_CTRL, ctrl);
        if (op->data.dir == SPI_MEM_DATA_IN) {
                ret = atcspi_dma_config(spi, TRUE);
                dma_dir = DMA_DEV_TO_MEM;
                dma_ch = spi->host->dma_rx;
        } else {
                ret = atcspi_dma_config(spi, FALSE);
                dma_dir = DMA_MEM_TO_DEV;
                dma_ch = spi->host->dma_tx;
        }
        if (ret)
                return ret;

        ret = spi_controller_dma_map_mem_op_data(spi->host, op, &sgt);
        if (ret)
                return ret;

        desc = dmaengine_prep_slave_sg(dma_ch, sgt.sgl, sgt.nents, dma_dir,
                                       DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
        if (!desc) {
                ret = -ENOMEM;
                goto exit_unmap;
        }

        reinit_completion(&spi->dma_completion);
        desc->callback = atcspi_dma_callback;
        desc->callback_param = &spi->dma_completion;
        cookie = dmaengine_submit(desc);
        ret = dma_submit_error(cookie);
        if (ret)
                goto exit_unmap;

        dma_async_issue_pending(dma_ch);
        timeout = msecs_to_jiffies(ATCSPI_XFER_TIMEOUT(op->data.nbytes));
        if (!wait_for_completion_timeout(&spi->dma_completion, timeout)) {
                ret = -ETIMEDOUT;
                dmaengine_terminate_all(dma_ch);
        }

exit_unmap:
        spi_controller_dma_unmap_mem_op_data(spi->host, op, &sgt);

        return ret;
}

static int atcspi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op)
{
        struct spi_device *spi_dev = mem->spi;
        struct atcspi_dev *spi;
        unsigned int val;
        int ret;

        spi = spi_controller_get_devdata(spi_dev->controller);
        mutex_lock(&spi->mutex_lock);
        atcspi_prepare_trans(spi, op);
        if (op->data.nbytes) {
                if (spi->use_dma && op->data.nbytes >= ATCSPI_DMA_THRESHOLD)
                        ret = atcspi_dma_trans(spi, op);
                else
                        ret = atcspi_xfer_data_poll(spi, op);
                if (ret) {
                        dev_info(spi->dev, "SPI transmission failed\n");
                        goto exec_mem_exit;
                }
        }

        ret = regmap_read_poll_timeout(spi->regmap,
                                       ATCSPI_STATUS,
                                       val,
                                       !(val & ATCSPI_ACTIVE),
                                       0,
                                       ATCSPI_RDY_TIMEOUT_US);
        if (ret)
                dev_info(spi->dev, "Timed out waiting for ATCSPI_ACTIVE\n");

exec_mem_exit:
        mutex_unlock(&spi->mutex_lock);

        return ret;
}

static const struct spi_controller_mem_ops atcspi_mem_ops = {
        .exec_op = atcspi_exec_mem_op,
        .adjust_op_size = atcspi_adjust_op_size,
};

static int atcspi_setup(struct atcspi_dev *spi)
{
        unsigned int ctrl_val;
        unsigned int val;
        int actual_spi_sclk_f;
        int ret;
        unsigned char div;

        ctrl_val = CTRL_TX_FIFO_RST | CTRL_RX_FIFO_RST | CTRL_SPI_RST;
        regmap_write(spi->regmap, ATCSPI_CTRL, ctrl_val);
        ret = regmap_read_poll_timeout(spi->regmap,
                                       ATCSPI_CTRL,
                                       val,
                                       !(val & ctrl_val),
                                       0,
                                       ATCSPI_RDY_TIMEOUT_US);
        if (ret)
                return dev_err_probe(spi->dev, ret,
                                     "Timed out waiting for ATCSPI_CTRL\n");

        val = TRANS_FMT_DATA_LEN(ATCSPI_BITS_PER_UINT) |
              TRANS_FMT_CPHA | TRANS_FMT_CPOL;
        regmap_write(spi->regmap, ATCSPI_TRANS_FMT, val);

        regmap_read(spi->regmap, ATCSPI_CONFIG, &val);
        spi->txfifo_size = BIT(TXFIFO_SIZE(val) + 1);
        spi->rxfifo_size = BIT(RXFIFO_SIZE(val) + 1);

        regmap_read(spi->regmap, ATCSPI_TIMING, &val);
        val &= ~TIMING_SCLK_DIV_MASK;

        /*
         * The SCLK_DIV value 0xFF is special and indicates that the
         * SCLK rate should be the same as the SPI clock rate.
         */
        if (spi->sclk_rate >= spi->clk_rate) {
                div = TIMING_SCLK_DIV_MASK;
        } else {
                /*
                 * The divider value is determined as follows:
                 * 1. If the divider can generate the exact target frequency,
                 *    use that setting.
                 * 2. If an exact match is not possible, select the closest
                 *    available setting that is lower than the target frequency.
                 */
                div = (spi->clk_rate + (spi->sclk_rate * 2 - 1)) /
                      (spi->sclk_rate * 2) - 1;

                /* Check if the actual SPI clock is lower than the target */
                actual_spi_sclk_f = spi->clk_rate / ((div + 1) * 2);
                if (actual_spi_sclk_f < spi->sclk_rate)
                        dev_info(spi->dev,
                                 "Clock adjusted %d to %d due to divider limitation",
                                 spi->sclk_rate, actual_spi_sclk_f);

                if (div > TIMING_SCLK_DIV_MAX)
                        return dev_err_probe(spi->dev, -EINVAL,
                                             "Unsupported SPI clock %d\n",
                                             spi->sclk_rate);
        }
        val |= div;
        regmap_write(spi->regmap, ATCSPI_TIMING, val);

        return ret;
}

static int atcspi_init_resources(struct platform_device *pdev,
                                 struct atcspi_dev *spi,
                                 struct resource **mem_res)
{
        void __iomem *base;
        const struct regmap_config atcspi_regmap_cfg = {
                .name = "atcspi",
                .reg_bits = 32,
                .val_bits = 32,
                .cache_type = REGCACHE_NONE,
                .reg_stride = 4,
                .pad_bits = 0,
                .max_register = ATCSPI_CONFIG
        };

        base = devm_platform_get_and_ioremap_resource(pdev, 0, mem_res);
        if (IS_ERR(base))
                return dev_err_probe(spi->dev, PTR_ERR(base),
                                     "Failed to get ioremap resource\n");

        spi->regmap = devm_regmap_init_mmio(spi->dev, base,
                                            &atcspi_regmap_cfg);
        if (IS_ERR(spi->regmap))
                return dev_err_probe(spi->dev, PTR_ERR(spi->regmap),
                                     "Failed to init regmap\n");

        spi->clk = devm_clk_get(spi->dev, NULL);
        if (IS_ERR(spi->clk))
                return dev_err_probe(spi->dev, PTR_ERR(spi->clk),
                                     "Failed to get SPI clock\n");

        spi->sclk_rate = ATCSPI_MAX_SPEED_HZ;
        return 0;
}

static int atcspi_configure_dma(struct atcspi_dev *spi)
{
        spi->host->dma_rx = devm_dma_request_chan(spi->dev, "rx");
        if (IS_ERR(spi->host->dma_rx))
                return PTR_ERR(spi->host->dma_rx);

        spi->host->dma_tx = devm_dma_request_chan(spi->dev, "tx");
        if (IS_ERR(spi->host->dma_tx))
                return PTR_ERR(spi->host->dma_tx);

        init_completion(&spi->dma_completion);

        return 0;
}

static int atcspi_enable_clk(struct atcspi_dev *spi)
{
        int ret;

        ret = clk_prepare_enable(spi->clk);
        if (ret)
                return dev_err_probe(spi->dev, ret,
                                     "Failed to enable clock\n");

        spi->clk_rate = clk_get_rate(spi->clk);
        if (!spi->clk_rate)
                return dev_err_probe(spi->dev, -EINVAL,
                                     "Failed to get SPI clock rate\n");

        return 0;
}

static void atcspi_init_controller(struct platform_device *pdev,
                                   struct atcspi_dev *spi,
                                   struct spi_controller *host,
                                   struct resource *mem_res)
{
        /* Get the physical address of the data register for DMA transfers. */
        spi->dma_addr = (dma_addr_t)(mem_res->start + ATCSPI_DATA);

        /* Initialize controller properties */
        host->bus_num = pdev->id;
        host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_RX_QUAD | SPI_TX_QUAD;
        host->num_chipselect = ATCSPI_MAX_CS_NUM;
        host->mem_ops = &atcspi_mem_ops;
        host->max_speed_hz = spi->sclk_rate;
}

static int atcspi_probe(struct platform_device *pdev)
{
        struct spi_controller *host;
        struct atcspi_dev *spi;
        struct resource *mem_res;
        int ret;

        host = spi_alloc_host(&pdev->dev, sizeof(*spi));
        if (!host)
                return -ENOMEM;

        spi = spi_controller_get_devdata(host);
        spi->host = host;
        spi->dev = &pdev->dev;
        dev_set_drvdata(&pdev->dev, host);

        ret = atcspi_init_resources(pdev, spi, &mem_res);
        if (ret)
                goto free_controller;

        ret = atcspi_enable_clk(spi);
        if (ret)
                goto free_controller;

        atcspi_init_controller(pdev, spi, host, mem_res);

        ret = atcspi_setup(spi);
        if (ret)
                goto disable_clk;

        ret = devm_spi_register_controller(&pdev->dev, host);
        if (ret) {
                dev_err_probe(spi->dev, ret,
                              "Failed to register SPI controller\n");
                goto disable_clk;
        }

        spi->use_dma = false;
        if (ATCSPI_DMA_SUPPORT) {
                ret = atcspi_configure_dma(spi);
                if (ret)
                        dev_info(spi->dev,
                                 "Failed to init DMA, fallback to PIO mode\n");
                else
                        spi->use_dma = true;
        }
        mutex_init(&spi->mutex_lock);

        return 0;

disable_clk:
        clk_disable_unprepare(spi->clk);

free_controller:
        spi_controller_put(host);
        return ret;
}

static int atcspi_suspend(struct device *dev)
{
        struct spi_controller *host = dev_get_drvdata(dev);
        struct atcspi_dev *spi = spi_controller_get_devdata(host);

        spi_controller_suspend(host);

        clk_disable_unprepare(spi->clk);

        return 0;
}

static int atcspi_resume(struct device *dev)
{
        struct spi_controller *host = dev_get_drvdata(dev);
        struct atcspi_dev *spi = spi_controller_get_devdata(host);
        int ret;

        ret = clk_prepare_enable(spi->clk);
        if (ret)
                return ret;

        ret = atcspi_setup(spi);
        if (ret)
                goto disable_clk;

        ret = spi_controller_resume(host);
        if (ret)
                goto disable_clk;

        return ret;

disable_clk:
        clk_disable_unprepare(spi->clk);

        return ret;
}

static DEFINE_SIMPLE_DEV_PM_OPS(atcspi_pm_ops, atcspi_suspend, atcspi_resume);

static const struct of_device_id atcspi_of_match[] = {
        { .compatible = "andestech,qilai-spi", },
        { .compatible = "andestech,ae350-spi", },
        { /* sentinel */ }
};

MODULE_DEVICE_TABLE(of, atcspi_of_match);

static struct platform_driver atcspi_driver = {
        .probe = atcspi_probe,
        .driver = {
                .name = "atcspi200",
                .owner  = THIS_MODULE,
                .of_match_table = atcspi_of_match,
                .pm = pm_sleep_ptr(&atcspi_pm_ops)
        }
};
module_platform_driver(atcspi_driver);

MODULE_AUTHOR("CL Wang <cl634@andestech.com>");
MODULE_DESCRIPTION("Andes ATCSPI200 SPI controller driver");
MODULE_LICENSE("GPL");