@@ -19,6 +19,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/spi/spi-mem.h>
@@ -35,7 +36,9 @@
#define QSPI_IAR 0x0030 /* Instruction Address Register */
#define QSPI_ICR 0x0034 /* Instruction Code Register */
+#define QSPI_WICR 0x0034 /* Write Instruction Code Register */
#define QSPI_IFR 0x0038 /* Instruction Frame Register */
+#define QSPI_RICR 0x003C /* Read Instruction Code Register */
#define QSPI_SMR 0x0040 /* Scrambling Mode Register */
#define QSPI_SKR 0x0044 /* Scrambling Key Register */
@@ -88,7 +91,7 @@
#define QSPI_SCR_DLYBS_MASK GENMASK(23, 16)
#define QSPI_SCR_DLYBS(n) (((n) << 16) & QSPI_SCR_DLYBS_MASK)
-/* Bitfields in QSPI_ICR (Instruction Code Register) */
+/* Bitfields in QSPI_ICR (Read/Write Instruction Code Register) */
#define QSPI_ICR_INST_MASK GENMASK(7, 0)
#define QSPI_ICR_INST(inst) (((inst) << 0) & QSPI_ICR_INST_MASK)
#define QSPI_ICR_OPT_MASK GENMASK(23, 16)
@@ -113,6 +116,8 @@
#define QSPI_IFR_OPTL_4BIT (2 << 8)
#define QSPI_IFR_OPTL_8BIT (3 << 8)
#define QSPI_IFR_ADDRL BIT(10)
+#define QSPI_IFR_TFRTYP_TRSFR_MEM BIT(12)
+#define QSPI_IFR_TFRTYP_TRSFR_REG (0 << 12)
#define QSPI_IFR_TFRTYP_MASK GENMASK(13, 12)
#define QSPI_IFR_TFRTYP_TRSFR_READ (0 << 12)
#define QSPI_IFR_TFRTYP_TRSFR_READ_MEM (1 << 12)
@@ -121,6 +126,8 @@
#define QSPI_IFR_CRM BIT(14)
#define QSPI_IFR_NBDUM_MASK GENMASK(20, 16)
#define QSPI_IFR_NBDUM(n) (((n) << 16) & QSPI_IFR_NBDUM_MASK)
+#define QSPI_IFR_APBTFRTYP_WRITE (0 << 24)
+#define QSPI_IFR_APBTFRTYP_READ BIT(24)
/* Bitfields in QSPI_SMR (Scrambling Mode Register) */
#define QSPI_SMR_SCREN BIT(0)
@@ -137,15 +144,37 @@
#define QSPI_WPSR_WPVSRC(src) (((src) << 8) & QSPI_WPSR_WPVSRC)
+/* Describes register values. */
+struct atmel_qspi_cfg {
+ u32 icr;
+ u32 iar;
+ u32 ifr;
+};
+
+struct atmel_qspi_caps;
+
struct atmel_qspi {
void __iomem *regs;
void __iomem *mem;
struct clk *clk;
+ struct clk *qspick;
struct platform_device *pdev;
+ const struct atmel_qspi_caps *caps;
u32 pending;
struct completion cmd_completion;
};
+struct atmel_qspi_ops {
+ int (*clk_prepare_enable)(struct atmel_qspi *aq);
+ void (*clk_disable_unprepare)(struct atmel_qspi *aq);
+ int (*set_qspi_cfg)(struct atmel_qspi *aq, const struct spi_mem_op *op,
+ struct atmel_qspi_cfg *cfg);
+};
+
+struct atmel_qspi_caps {
+ const struct atmel_qspi_ops *ops;
+};
+
struct atmel_qspi_mode {
u8 cmd_buswidth;
u8 addr_buswidth;
@@ -214,23 +243,36 @@ static bool atmel_qspi_supports_op(struct spi_mem *mem,
return true;
}
-static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
+static int atmel_qspi_set_mode(struct atmel_qspi_cfg *cfg,
+ const struct spi_mem_op *op)
{
- struct atmel_qspi *aq = spi_controller_get_devdata(mem->spi->master);
- int mode;
- u32 dummy_cycles = 0;
- u32 iar, icr, ifr, sr;
- int err = 0;
-
- iar = 0;
- icr = QSPI_ICR_INST(op->cmd.opcode);
- ifr = QSPI_IFR_INSTEN;
+ int mode = atmel_qspi_find_mode(op);
- mode = atmel_qspi_find_mode(op);
if (mode < 0)
return mode;
+ cfg->ifr = QSPI_IFR_INSTEN | sama5d2_qspi_modes[mode].config;
+ return 0;
+}
- ifr |= sama5d2_qspi_modes[mode].config;
+/*
+ * atmel_qspi_set_address_mode() - set address mode.
+ * @cfg: contains register values
+ * @op: describes a SPI memory operation
+ *
+ * The controller allows 24 and 32-bit addressing while NAND-flash requires
+ * 16-bit long. Handling 8-bit long addresses is done using the option field.
+ * For the 16-bit addresses, the workaround depends of the number of requested
+ * dummy bits. If there are 8 or more dummy cycles, the address is shifted and
+ * sent with the first dummy byte. Otherwise opcode is disabled and the first
+ * byte of the address contains the command opcode (works only if the opcode and
+ * address use the same buswidth). The limitation is when the 16-bit address is
+ * used without enough dummy cycles and the opcode is using a different buswidth
+ * than the address.
+ */
+static int atmel_qspi_set_address_mode(struct atmel_qspi_cfg *cfg,
+ const struct spi_mem_op *op)
+{
+ u32 dummy_cycles = 0;
if (op->dummy.buswidth && op->dummy.nbytes)
dummy_cycles = op->dummy.nbytes * 8 / op->dummy.buswidth;
@@ -240,28 +282,28 @@ static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
case 0:
break;
case 1:
- ifr |= QSPI_IFR_OPTEN | QSPI_IFR_OPTL_8BIT;
- icr |= QSPI_ICR_OPT(op->addr.val & 0xff);
+ cfg->ifr |= QSPI_IFR_OPTEN | QSPI_IFR_OPTL_8BIT;
+ cfg->icr |= QSPI_ICR_OPT(op->addr.val & 0xff);
break;
case 2:
if (dummy_cycles < 8 / op->addr.buswidth) {
- ifr &= ~QSPI_IFR_INSTEN;
- ifr |= QSPI_IFR_ADDREN;
- iar = (op->cmd.opcode << 16) |
- (op->addr.val & 0xffff);
+ cfg->ifr &= ~QSPI_IFR_INSTEN;
+ cfg->ifr |= QSPI_IFR_ADDREN;
+ cfg->iar = (op->cmd.opcode << 16) |
+ (op->addr.val & 0xffff);
} else {
- ifr |= QSPI_IFR_ADDREN;
- iar = (op->addr.val << 8) & 0xffffff;
+ cfg->ifr |= QSPI_IFR_ADDREN;
+ cfg->iar = (op->addr.val << 8) & 0xffffff;
dummy_cycles -= 8 / op->addr.buswidth;
}
break;
case 3:
- ifr |= QSPI_IFR_ADDREN;
- iar = op->addr.val & 0xffffff;
+ cfg->ifr |= QSPI_IFR_ADDREN;
+ cfg->iar = op->addr.val & 0xffffff;
break;
case 4:
- ifr |= QSPI_IFR_ADDREN | QSPI_IFR_ADDRL;
- iar = op->addr.val & 0x7ffffff;
+ cfg->ifr |= QSPI_IFR_ADDREN | QSPI_IFR_ADDRL;
+ cfg->iar = op->addr.val & 0x7ffffff;
break;
default:
return -ENOTSUPP;
@@ -270,24 +312,99 @@ static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
/* Set number of dummy cycles */
if (dummy_cycles)
- ifr |= QSPI_IFR_NBDUM(dummy_cycles);
+ cfg->ifr |= QSPI_IFR_NBDUM(dummy_cycles);
- /* Set data enable */
- if (op->data.nbytes)
- ifr |= QSPI_IFR_DATAEN;
+ return 0;
+}
+
+static int atmel_sama5d2_qspi_set_cfg(struct atmel_qspi *aq,
+ const struct spi_mem_op *op,
+ struct atmel_qspi_cfg *cfg)
+{
+ int ret = atmel_qspi_set_mode(cfg, op);
+
+ if (ret)
+ return ret;
+
+ cfg->icr = QSPI_ICR_INST(op->cmd.opcode);
if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes)
- ifr |= QSPI_IFR_TFRTYP_TRSFR_READ;
+ cfg->ifr |= QSPI_IFR_TFRTYP_TRSFR_READ;
else
- ifr |= QSPI_IFR_TFRTYP_TRSFR_WRITE;
+ cfg->ifr |= QSPI_IFR_TFRTYP_TRSFR_WRITE;
+
+ /* Set data enable */
+ if (op->data.nbytes)
+ cfg->ifr |= QSPI_IFR_DATAEN;
+
+ ret = atmel_qspi_set_address_mode(cfg, op);
+ if (ret)
+ return ret;
+
+ /* Clear pending interrupts */
+ (void)atmel_qspi_readl(aq, QSPI_SR);
+
+ /* Set QSPI Instruction Frame registers */
+ atmel_qspi_writel(aq, QSPI_IAR, cfg->iar);
+ atmel_qspi_writel(aq, QSPI_ICR, cfg->icr);
+ atmel_qspi_writel(aq, QSPI_IFR, cfg->ifr);
+
+ return 0;
+}
+
+static int atmel_sam9x60_qspi_set_cfg(struct atmel_qspi *aq,
+ const struct spi_mem_op *op,
+ struct atmel_qspi_cfg *cfg)
+{
+ int ret = atmel_qspi_set_mode(cfg, op);
+
+ if (ret)
+ return ret;
+
+ cfg->icr = QSPI_ICR_INST(op->cmd.opcode);
+
+ if (!op->addr.nbytes) {
+ cfg->ifr |= QSPI_IFR_TFRTYP_TRSFR_REG;
+ if (op->data.dir == SPI_MEM_DATA_OUT)
+ cfg->ifr |= QSPI_IFR_APBTFRTYP_WRITE;
+ else
+ cfg->ifr |= QSPI_IFR_APBTFRTYP_READ;
+ } else {
+ cfg->ifr |= QSPI_IFR_TFRTYP_TRSFR_MEM;
+ }
+
+ /* Set data enable */
+ if (op->data.nbytes)
+ cfg->ifr |= QSPI_IFR_DATAEN;
+
+ ret = atmel_qspi_set_address_mode(cfg, op);
+ if (ret)
+ return ret;
/* Clear pending interrupts */
(void)atmel_qspi_readl(aq, QSPI_SR);
/* Set QSPI Instruction Frame registers */
- atmel_qspi_writel(aq, QSPI_IAR, iar);
- atmel_qspi_writel(aq, QSPI_ICR, icr);
- atmel_qspi_writel(aq, QSPI_IFR, ifr);
+ atmel_qspi_writel(aq, QSPI_IAR, cfg->iar);
+ if (op->data.dir == SPI_MEM_DATA_OUT)
+ atmel_qspi_writel(aq, QSPI_ICR, cfg->icr);
+ else
+ atmel_qspi_writel(aq, QSPI_RICR, cfg->icr);
+ atmel_qspi_writel(aq, QSPI_IFR, cfg->ifr);
+
+ return 0;
+}
+
+static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+ struct atmel_qspi *aq = spi_controller_get_devdata(mem->spi->master);
+ struct atmel_qspi_cfg cfg = {0};
+ u32 sr;
+ int err;
+
+ err = aq->caps->ops->set_qspi_cfg(aq, op, &cfg);
+ if (err)
+ return err;
/* Skip to the final steps if there is no data */
if (op->data.nbytes) {
@@ -296,11 +413,11 @@ static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
/* Send/Receive data */
if (op->data.dir == SPI_MEM_DATA_IN)
- _memcpy_fromio(op->data.buf.in,
- aq->mem + iar, op->data.nbytes);
+ _memcpy_fromio(op->data.buf.in, aq->mem + cfg.iar,
+ op->data.nbytes);
else
- _memcpy_toio(aq->mem + iar,
- op->data.buf.out, op->data.nbytes);
+ _memcpy_toio(aq->mem + cfg.iar, op->data.buf.out,
+ op->data.nbytes);
/* Release the chip-select */
atmel_qspi_writel(aq, QSPI_CR, QSPI_CR_LASTXFER);
@@ -395,10 +512,84 @@ static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static int atmel_sama5d2_qspi_clk_prepare_enable(struct atmel_qspi *aq)
+{
+ int ret;
+
+ if (!aq->clk) {
+ /* Get the peripheral clock */
+ aq->clk = devm_clk_get(&aq->pdev->dev, NULL);
+ if (IS_ERR(aq->clk)) {
+ dev_err(&aq->pdev->dev, "missing peripheral clock\n");
+ return PTR_ERR(aq->clk);
+ }
+ }
+
+ /* Enable the peripheral clock */
+ ret = clk_prepare_enable(aq->clk);
+ if (ret)
+ dev_err(&aq->pdev->dev,
+ "failed to enable the peripheral clock\n");
+
+ return ret;
+}
+
+static void atmel_sama5d2_qspi_clk_disable_unprepare(struct atmel_qspi *aq)
+{
+ clk_disable_unprepare(aq->clk);
+}
+
+static int atmel_sam9x60_qspi_clk_prepare_enable(struct atmel_qspi *aq)
+{
+ struct device *dev = &aq->pdev->dev;
+ int ret;
+
+ if (!aq->clk) {
+ /* Get the peripheral clock */
+ aq->clk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(aq->clk)) {
+ dev_err(dev, "missing peripheral clock\n");
+ return PTR_ERR(aq->clk);
+ }
+ }
+
+ if (!aq->qspick) {
+ /* Get the QSPI system clock */
+ aq->qspick = devm_clk_get(dev, "qspick");
+ if (IS_ERR(aq->qspick)) {
+ dev_err(dev, "missing system clock\n");
+ return PTR_ERR(aq->qspick);
+ }
+ }
+
+ /* Enable the peripheral clock */
+ ret = clk_prepare_enable(aq->clk);
+ if (ret) {
+ dev_err(dev, "failed to enable the peripheral clock\n");
+ return ret;
+ }
+
+ /* Enable the QSPI system clock */
+ ret = clk_prepare_enable(aq->qspick);
+ if (ret) {
+ dev_err(dev, "failed to enable the QSPI system clock\n");
+ clk_disable_unprepare(aq->clk);
+ }
+
+ return ret;
+}
+
+static void atmel_sam9x60_qspi_clk_disable_unprepare(struct atmel_qspi *aq)
+{
+ clk_disable_unprepare(aq->qspick);
+ clk_disable_unprepare(aq->clk);
+}
+
static int atmel_qspi_probe(struct platform_device *pdev)
{
struct spi_controller *ctrl;
struct atmel_qspi *aq;
+ const struct atmel_qspi_caps *caps;
struct resource *res;
int irq, err = 0;
@@ -437,20 +628,22 @@ static int atmel_qspi_probe(struct platform_device *pdev)
goto exit;
}
- /* Get the peripheral clock */
- aq->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(aq->clk)) {
- dev_err(&pdev->dev, "missing peripheral clock\n");
- err = PTR_ERR(aq->clk);
- goto exit;
+ caps = of_device_get_match_data(&pdev->dev);
+ if (!caps) {
+ dev_err(&pdev->dev, "Could not retrieve QSPI caps\n");
+ return -EINVAL;
}
- /* Enable the peripheral clock */
- err = clk_prepare_enable(aq->clk);
- if (err) {
- dev_err(&pdev->dev, "failed to enable the peripheral clock\n");
- goto exit;
+ if (!caps->ops->clk_prepare_enable ||
+ !caps->ops->clk_disable_unprepare || !caps->ops->set_qspi_cfg) {
+ dev_err(&pdev->dev, "Could not retrieve QSPI ops\n");
+ return -EINVAL;
}
+ aq->caps = caps;
+
+ err = caps->ops->clk_prepare_enable(aq);
+ if (err)
+ return err;
/* Request the IRQ */
irq = platform_get_irq(pdev, 0);
@@ -475,7 +668,7 @@ static int atmel_qspi_probe(struct platform_device *pdev)
return 0;
disable_clk:
- clk_disable_unprepare(aq->clk);
+ caps->ops->clk_disable_unprepare(aq);
exit:
spi_controller_put(ctrl);
@@ -486,18 +679,20 @@ static int atmel_qspi_remove(struct platform_device *pdev)
{
struct spi_controller *ctrl = platform_get_drvdata(pdev);
struct atmel_qspi *aq = spi_controller_get_devdata(ctrl);
+ const struct atmel_qspi_caps *caps = aq->caps;
spi_unregister_controller(ctrl);
atmel_qspi_writel(aq, QSPI_CR, QSPI_CR_QSPIDIS);
- clk_disable_unprepare(aq->clk);
+ caps->ops->clk_disable_unprepare(aq);
return 0;
}
static int __maybe_unused atmel_qspi_suspend(struct device *dev)
{
struct atmel_qspi *aq = dev_get_drvdata(dev);
+ const struct atmel_qspi_caps *caps = aq->caps;
- clk_disable_unprepare(aq->clk);
+ caps->ops->clk_disable_unprepare(aq);
return 0;
}
@@ -505,8 +700,9 @@ static int __maybe_unused atmel_qspi_suspend(struct device *dev)
static int __maybe_unused atmel_qspi_resume(struct device *dev)
{
struct atmel_qspi *aq = dev_get_drvdata(dev);
+ const struct atmel_qspi_caps *caps = aq->caps;
- clk_prepare_enable(aq->clk);
+ caps->ops->clk_prepare_enable(aq);
return atmel_qspi_init(aq);
}
@@ -514,8 +710,35 @@ static int __maybe_unused atmel_qspi_resume(struct device *dev)
static SIMPLE_DEV_PM_OPS(atmel_qspi_pm_ops, atmel_qspi_suspend,
atmel_qspi_resume);
+static const struct atmel_qspi_ops atmel_sama5d2_qspi_ops = {
+ .clk_prepare_enable = atmel_sama5d2_qspi_clk_prepare_enable,
+ .clk_disable_unprepare = atmel_sama5d2_qspi_clk_disable_unprepare,
+ .set_qspi_cfg = atmel_sama5d2_qspi_set_cfg,
+};
+
+static const struct atmel_qspi_caps atmel_sama5d2_qspi_caps = {
+ .ops = &atmel_sama5d2_qspi_ops,
+};
+
+static const struct atmel_qspi_ops atmel_sam9x60_qspi_ops = {
+ .clk_prepare_enable = atmel_sam9x60_qspi_clk_prepare_enable,
+ .clk_disable_unprepare = atmel_sam9x60_qspi_clk_disable_unprepare,
+ .set_qspi_cfg = atmel_sam9x60_qspi_set_cfg,
+};
+
+static const struct atmel_qspi_caps atmel_sam9x60_qspi_caps = {
+ .ops = &atmel_sam9x60_qspi_ops,
+};
+
static const struct of_device_id atmel_qspi_dt_ids[] = {
- { .compatible = "atmel,sama5d2-qspi" },
+ {
+ .compatible = "atmel,sama5d2-qspi",
+ .data = &atmel_sama5d2_qspi_caps,
+ },
+ {
+ .compatible = "microchip,sam9x60-qspi",
+ .data = &atmel_sam9x60_qspi_caps,
+ },
{ /* sentinel */ }
};