diff options
Diffstat (limited to 'drivers/spi/spi-nxp-fspi.c')
-rw-r--r-- | drivers/spi/spi-nxp-fspi.c | 168 |
1 files changed, 146 insertions, 22 deletions
diff --git a/drivers/spi/spi-nxp-fspi.c b/drivers/spi/spi-nxp-fspi.c index 36a44a837031..e9237d0a9770 100644 --- a/drivers/spi/spi-nxp-fspi.c +++ b/drivers/spi/spi-nxp-fspi.c @@ -52,6 +52,11 @@ #include <linux/spi/spi.h> #include <linux/spi/spi-mem.h> +#include <linux/pm_runtime.h> + +/* runtime pm timeout */ +#define FSPI_RPM_TIMEOUT 50 /* 50ms */ + /* * The driver only uses one single LUT entry, that is updated on * each call of exec_op(). Index 0 is preset at boot with a basic @@ -307,6 +312,10 @@ #define POLL_TOUT 5000 #define NXP_FSPI_MAX_CHIPSELECT 4 +#define NXP_FSPI_MIN_IOMAP SZ_4M + +/* access memory via IPS only due to this errata */ +#define NXP_FSPI_QUIRK_ERR050601 BIT(0) struct nxp_fspi_devtype_data { unsigned int rxfifo; @@ -324,11 +333,37 @@ static const struct nxp_fspi_devtype_data lx2160a_data = { .little_endian = true, /* little-endian */ }; +static const struct nxp_fspi_devtype_data imx8mm_data = { + .rxfifo = SZ_512, /* (64 * 64 bits) */ + .txfifo = SZ_1K, /* (128 * 64 bits) */ + .ahb_buf_size = SZ_2K, /* (256 * 64 bits) */ + .quirks = 0, + .little_endian = true, /* little-endian */ +}; + +static const struct nxp_fspi_devtype_data imx8qxp_data = { + .rxfifo = SZ_512, /* (64 * 64 bits) */ + .txfifo = SZ_1K, /* (128 * 64 bits) */ + .ahb_buf_size = SZ_2K, /* (256 * 64 bits) */ + .quirks = 0, + .little_endian = true, /* little-endian */ +}; + +static const struct nxp_fspi_devtype_data imx8dxl_data = { + .rxfifo = SZ_512, /* (64 * 64 bits) */ + .txfifo = SZ_1K, /* (128 * 64 bits) */ + .ahb_buf_size = SZ_2K, /* (256 * 64 bits) */ + .quirks = NXP_FSPI_QUIRK_ERR050601, + .little_endian = true, /* little-endian */ +}; + struct nxp_fspi { void __iomem *iobase; void __iomem *ahb_addr; u32 memmap_phy; u32 memmap_phy_size; + u32 memmap_start; + u32 memmap_len; struct clk *clk, *clk_en; struct device *dev; struct completion c; @@ -336,8 +371,15 @@ struct nxp_fspi { struct mutex lock; struct pm_qos_request pm_qos_req; int selected; +#define FSPI_INITILIZED (1 << 0) + int flags; }; +static inline int nxp_fspi_ips_access_only(struct nxp_fspi *f) +{ + return f->devtype_data->quirks & NXP_FSPI_QUIRK_ERR050601; +} + /* * R/W functions for big- or little-endian registers: * The FSPI controller's endianness is independent of @@ -641,12 +683,35 @@ static void nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi) f->selected = spi->chip_select; } -static void nxp_fspi_read_ahb(struct nxp_fspi *f, const struct spi_mem_op *op) +static int nxp_fspi_read_ahb(struct nxp_fspi *f, const struct spi_mem_op *op) { + u32 start = op->addr.val; u32 len = op->data.nbytes; + /* if necessary, ioremap before AHB read */ + if ((!f->ahb_addr) || start < f->memmap_start || + start + len > f->memmap_start + f->memmap_len) { + if (f->ahb_addr) + iounmap(f->ahb_addr); + + f->memmap_start = start; + f->memmap_len = len > NXP_FSPI_MIN_IOMAP ? + len : NXP_FSPI_MIN_IOMAP; + + f->ahb_addr = ioremap_wc(f->memmap_phy + f->memmap_start, + f->memmap_len); + + if (!f->ahb_addr) { + dev_err(f->dev, "failed to alloc memory\n"); + return -ENOMEM; + } + } + /* Read out the data directly from the AHB buffer. */ - memcpy_fromio(op->data.buf.in, (f->ahb_addr + op->addr.val), len); + memcpy_fromio(op->data.buf.in, + f->ahb_addr + start - f->memmap_start, len); + + return 0; } static void nxp_fspi_fill_txfifo(struct nxp_fspi *f, @@ -791,6 +856,12 @@ static int nxp_fspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) mutex_lock(&f->lock); + err = pm_runtime_get_sync(f->dev); + if (err < 0) { + dev_err(f->dev, "Failed to enable clock %d\n", __LINE__); + goto err_mutex; + } + /* Wait for controller being ready. */ err = fspi_readl_poll_tout(f, f->iobase + FSPI_STS0, FSPI_STS0_ARB_IDLE, 1, POLL_TOUT, true); @@ -802,11 +873,13 @@ static int nxp_fspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) /* * If we have large chunks of data, we read them through the AHB bus * by accessing the mapped memory. In all other cases we use - * IP commands to access the flash. + * IP commands to access the flash, but ahb read won't be used for dxl + * since IC errta. */ - if (op->data.nbytes > (f->devtype_data->rxfifo - 4) && + if (!nxp_fspi_ips_access_only(f) && + op->data.nbytes > (f->devtype_data->rxfifo - 4) && op->data.dir == SPI_MEM_DATA_IN) { - nxp_fspi_read_ahb(f, op); + err = nxp_fspi_read_ahb(f, op); } else { if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_OUT) nxp_fspi_fill_txfifo(f, op); @@ -817,8 +890,13 @@ static int nxp_fspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) /* Invalidate the data in the AHB buffer. */ nxp_fspi_invalid(f); + pm_runtime_mark_last_busy(f->dev); + pm_runtime_put_autosuspend(f->dev); + mutex_unlock(&f->lock); +err_mutex: + mutex_unlock(&f->lock); return err; } @@ -834,6 +912,10 @@ static int nxp_fspi_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op) op->data.nbytes = f->devtype_data->ahb_buf_size; else if (op->data.nbytes > (f->devtype_data->rxfifo - 4)) op->data.nbytes = ALIGN_DOWN(op->data.nbytes, 8); + /* dxl won't use ahb to access data, limit to rxfifo size */ + if (nxp_fspi_ips_access_only(f) && + op->data.nbytes > f->devtype_data->rxfifo) + op->data.nbytes = f->devtype_data->rxfifo; } return 0; @@ -871,8 +953,9 @@ static int nxp_fspi_default_setup(struct nxp_fspi *f) fspi_writel(f, FSPI_DLLBCR_OVRDEN, base + FSPI_DLLBCR); /* enable module */ - fspi_writel(f, FSPI_MCR0_AHB_TIMEOUT(0xFF) | FSPI_MCR0_IP_TIMEOUT(0xFF), - base + FSPI_MCR0); + fspi_writel(f, FSPI_MCR0_AHB_TIMEOUT(0xFF) | + FSPI_MCR0_IP_TIMEOUT(0xFF) | (u32) FSPI_MCR0_OCTCOMB_EN, + base + FSPI_MCR0); /* * Disable same device enable bit and configure all slave devices @@ -977,9 +1060,8 @@ static int nxp_fspi_probe(struct platform_device *pdev) /* find the resources - controller memory mapped space */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "fspi_mmap"); - f->ahb_addr = devm_ioremap_resource(dev, res); - if (IS_ERR(f->ahb_addr)) { - ret = PTR_ERR(f->ahb_addr); + if (IS_ERR(res)) { + ret = PTR_ERR(res); goto err_put_ctrl; } @@ -1000,9 +1082,14 @@ static int nxp_fspi_probe(struct platform_device *pdev) goto err_put_ctrl; } - ret = nxp_fspi_clk_prep_enable(f); - if (ret) { - dev_err(dev, "can not enable the clock\n"); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, FSPI_RPM_TIMEOUT); + pm_runtime_use_autosuspend(dev); + + /* enable clock */ + ret = pm_runtime_get_sync(f->dev); + if (ret < 0) { + dev_err(f->dev, "Failed to enable clock %d\n", __LINE__); goto err_put_ctrl; } @@ -1037,13 +1124,19 @@ static int nxp_fspi_probe(struct platform_device *pdev) if (ret) goto err_destroy_mutex; + pm_runtime_mark_last_busy(f->dev); + pm_runtime_put_autosuspend(f->dev); + + /* indicate the controller has been initialized */ + f->flags |= FSPI_INITILIZED; + return 0; err_destroy_mutex: mutex_destroy(&f->lock); err_disable_clk: - nxp_fspi_clk_disable_unprep(f); + pm_runtime_disable(dev); err_put_ctrl: spi_controller_put(ctlr); @@ -1063,34 +1156,65 @@ static int nxp_fspi_remove(struct platform_device *pdev) mutex_destroy(&f->lock); + if (f->ahb_addr) + iounmap(f->ahb_addr); + return 0; } -static int nxp_fspi_suspend(struct device *dev) +#ifdef CONFIG_PM +static int nxp_fspi_initialized(struct nxp_fspi *f) +{ + return f->flags & FSPI_INITILIZED; +} + +static int nxp_fspi_need_reinit(struct nxp_fspi *f) { + /* we always use the controller in combination mode, so we check this */ + /* register bit to determine if the controller once lost power, such as */ + /* suspend/resume, and need to be re-init */ + + return !(readl(f->iobase + FSPI_MCR0) & FSPI_MCR0_OCTCOMB_EN); +} + + +int nxp_fspi_runtime_suspend(struct device *dev) +{ + struct nxp_fspi *f = dev_get_drvdata(dev); + + nxp_fspi_clk_disable_unprep(f); + return 0; } -static int nxp_fspi_resume(struct device *dev) +int nxp_fspi_runtime_resume(struct device *dev) { struct nxp_fspi *f = dev_get_drvdata(dev); - nxp_fspi_default_setup(f); + nxp_fspi_clk_prep_enable(f); + + if (nxp_fspi_initialized(f) && nxp_fspi_need_reinit(f)) + nxp_fspi_default_setup(f); return 0; } +static const struct dev_pm_ops nxp_fspi_pm_ops = { + SET_RUNTIME_PM_OPS(nxp_fspi_runtime_suspend, nxp_fspi_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +#endif /* CONFIG_PM */ + static const struct of_device_id nxp_fspi_dt_ids[] = { { .compatible = "nxp,lx2160a-fspi", .data = (void *)&lx2160a_data, }, + { .compatible = "nxp,imx8mm-fspi", .data = (void *)&imx8mm_data, }, + { .compatible = "nxp,imx8qxp-fspi", .data = (void *)&imx8qxp_data, }, + { .compatible = "nxp,imx8dxl-fspi", .data = (void *)&imx8dxl_data, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, nxp_fspi_dt_ids); -static const struct dev_pm_ops nxp_fspi_pm_ops = { - .suspend = nxp_fspi_suspend, - .resume = nxp_fspi_resume, -}; - static struct platform_driver nxp_fspi_driver = { .driver = { .name = "nxp-fspi", |