@@ -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)
@@ -118,6 +121,7 @@
#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_READ BIT(24) /* Defined in SAM9X60 */
/* Bitfields in QSPI_SMR (Scrambling Mode Register) */
#define QSPI_SMR_SCREN BIT(0)
@@ -134,16 +138,40 @@
#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 *pclk;
+ struct clk *qspick;
struct platform_device *pdev;
+ const struct atmel_qspi_caps *caps;
u32 pending;
u32 mr;
struct completion cmd_completion;
};
+struct atmel_qspi_ops {
+ void (*set_tfrtyp)(const struct spi_mem_op *op,
+ struct atmel_qspi_cfg *cfg);
+ void (*write_regs)(const struct atmel_qspi *aq,
+ const struct spi_mem_op *op,
+ const struct atmel_qspi_cfg *cfg);
+};
+
+struct atmel_qspi_caps {
+ const struct atmel_qspi_ops *ops;
+ bool has_qspick;
+};
+
struct atmel_qspi_mode {
u8 cmd_buswidth;
u8 addr_buswidth;
@@ -151,7 +179,7 @@ struct atmel_qspi_mode {
u32 config;
};
-static const struct atmel_qspi_mode sama5d2_qspi_modes[] = {
+static const struct atmel_qspi_mode atmel_qspi_modes[] = {
{ 1, 1, 1, QSPI_IFR_WIDTH_SINGLE_BIT_SPI },
{ 1, 1, 2, QSPI_IFR_WIDTH_DUAL_OUTPUT },
{ 1, 1, 4, QSPI_IFR_WIDTH_QUAD_OUTPUT },
@@ -180,8 +208,8 @@ static int atmel_qspi_find_mode(const struct spi_mem_op *op)
{
u32 i;
- for (i = 0; i < ARRAY_SIZE(sama5d2_qspi_modes); i++)
- if (atmel_qspi_is_compatible(op, &sama5d2_qspi_modes[i]))
+ for (i = 0; i < ARRAY_SIZE(atmel_qspi_modes); i++)
+ if (atmel_qspi_is_compatible(op, &atmel_qspi_modes[i]))
return i;
return -ENOTSUPP;
@@ -201,63 +229,69 @@ 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;
-
- /*
- * If the QSPI controller is set in regular SPI mode, set it in
- * Serial Memory Mode (SMM).
- */
- if (aq->mr != QSPI_MR_SMM) {
- writel_relaxed(QSPI_MR_SMM, aq->regs + QSPI_MR);
- aq->mr = QSPI_MR_SMM;
- }
+ int mode = atmel_qspi_find_mode(op);
- mode = atmel_qspi_find_mode(op);
if (mode < 0)
return mode;
+ cfg->ifr = atmel_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
+ */
+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;
+ /*
+ * 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.
+ */
if (op->addr.buswidth) {
switch (op->addr.nbytes) {
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;
@@ -266,22 +300,100 @@ 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 void atmel_qspi_sama5d2_set_tfrtyp(const struct spi_mem_op *op,
+ struct atmel_qspi_cfg *cfg)
+{
if (op->data.dir == SPI_MEM_DATA_OUT)
- ifr |= QSPI_IFR_SAMA5D2_WRITE_TRSFR;
+ cfg->ifr |= QSPI_IFR_SAMA5D2_WRITE_TRSFR;
+}
+static void atmel_qspi_sama5d2_write_regs(const struct atmel_qspi *aq,
+ const struct spi_mem_op *op,
+ const struct atmel_qspi_cfg *cfg)
+{
/* Clear pending interrupts */
(void)readl_relaxed(aq->regs + QSPI_SR);
/* Set QSPI Instruction Frame registers */
- writel_relaxed(iar, aq->regs + QSPI_IAR);
- writel_relaxed(icr, aq->regs + QSPI_ICR);
- writel_relaxed(ifr, aq->regs + QSPI_IFR);
+ writel_relaxed(cfg->iar, aq->regs + QSPI_IAR);
+ writel_relaxed(cfg->icr, aq->regs + QSPI_ICR);
+ writel_relaxed(cfg->ifr, aq->regs + QSPI_IFR);
+}
+
+static void atmel_qspi_sam9x60_set_tfrtyp(const struct spi_mem_op *op,
+ struct atmel_qspi_cfg *cfg)
+{
+ if (!op->addr.nbytes && op->data.dir == SPI_MEM_DATA_IN)
+ cfg->ifr |= QSPI_IFR_APBTFRTYP_READ;
+}
+
+static void atmel_qspi_sam9x60_write_regs(const struct atmel_qspi *aq,
+ const struct spi_mem_op *op,
+ const struct atmel_qspi_cfg *cfg)
+{
+ /* Clear pending interrupts */
+ (void)readl_relaxed(aq->regs + QSPI_SR);
+
+ /* Set QSPI Instruction Frame registers */
+ writel_relaxed(cfg->iar, aq->regs + QSPI_IAR);
+ if (op->data.dir == SPI_MEM_DATA_IN)
+ writel_relaxed(cfg->icr, aq->regs + QSPI_RICR);
+ else
+ writel_relaxed(cfg->icr, aq->regs + QSPI_ICR);
+ writel_relaxed(cfg->ifr, aq->regs + QSPI_IFR);
+}
+
+static int atmel_qspi_set_cfg(struct atmel_qspi *aq,
+ const struct spi_mem_op *op,
+ struct atmel_qspi_cfg *cfg)
+{
+ int ret;
+
+ /*
+ * If the QSPI controller is set in regular SPI mode, set it in
+ * Serial Memory Mode (SMM).
+ */
+ if (aq->mr != QSPI_MR_SMM) {
+ writel_relaxed(QSPI_MR_SMM, aq->regs + QSPI_MR);
+ aq->mr = QSPI_MR_SMM;
+ }
+
+ ret = atmel_qspi_set_mode(cfg, op);
+ if (ret)
+ return ret;
+
+ ret = atmel_qspi_set_address_mode(cfg, op);
+ if (ret)
+ return ret;
+
+ cfg->ifr |= QSPI_IFR_INSTEN;
+ cfg->icr |= QSPI_ICR_INST(op->cmd.opcode);
+
+ /* Set data enable */
+ if (op->data.nbytes)
+ cfg->ifr |= QSPI_IFR_DATAEN;
+
+ aq->caps->ops->set_tfrtyp(op, cfg);
+ aq->caps->ops->write_regs(aq, op, cfg);
+
+ 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 = atmel_qspi_set_cfg(aq, op, &cfg);
+ if (err)
+ return err;
/* Skip to the final steps if there is no data */
if (op->data.nbytes) {
@@ -290,11 +402,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 */
writel_relaxed(QSPI_CR_LASTXFER, aq->regs + QSPI_CR);
@@ -395,8 +507,20 @@ static int atmel_qspi_probe(struct platform_device *pdev)
struct spi_controller *ctrl;
struct atmel_qspi *aq;
struct resource *res;
+ const struct atmel_qspi_caps *caps;
int irq, err = 0;
+ caps = of_device_get_match_data(&pdev->dev);
+ if (!caps) {
+ dev_err(&pdev->dev, "Could not retrieve QSPI caps\n");
+ return -EINVAL;
+ }
+
+ if (!caps->ops->set_tfrtyp || !caps->ops->write_regs) {
+ dev_err(&pdev->dev, "Could not retrieve QSPI ops\n");
+ return -EINVAL;
+ }
+
ctrl = spi_alloc_master(&pdev->dev, sizeof(*aq));
if (!ctrl)
return -ENOMEM;
@@ -413,6 +537,7 @@ static int atmel_qspi_probe(struct platform_device *pdev)
init_completion(&aq->cmd_completion);
aq->pdev = pdev;
+ aq->caps = caps;
/* Map the registers */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_base");
@@ -450,28 +575,48 @@ static int atmel_qspi_probe(struct platform_device *pdev)
goto exit;
}
+ if (caps->has_qspick) {
+ /* Get the QSPI system clock */
+ aq->qspick = devm_clk_get(&pdev->dev, "qspick");
+ if (IS_ERR(aq->qspick)) {
+ dev_err(&pdev->dev, "missing system clock\n");
+ err = PTR_ERR(aq->qspick);
+ goto disable_pclk;
+ }
+
+ /* Enable the QSPI system clock */
+ err = clk_prepare_enable(aq->qspick);
+ if (err) {
+ dev_err(&pdev->dev,
+ "failed to enable the QSPI system clock\n");
+ goto disable_pclk;
+ }
+ }
+
/* Request the IRQ */
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "missing IRQ\n");
err = irq;
- goto disable_pclk;
+ goto disable_qspick;
}
err = devm_request_irq(&pdev->dev, irq, atmel_qspi_interrupt,
0, dev_name(&pdev->dev), aq);
if (err)
- goto disable_pclk;
+ goto disable_qspick;
err = atmel_qspi_init(aq);
if (err)
- goto disable_pclk;
+ goto disable_qspick;
err = spi_register_controller(ctrl);
if (err)
- goto disable_pclk;
+ goto disable_qspick;
return 0;
+disable_qspick:
+ clk_disable_unprepare(aq->qspick);
disable_pclk:
clk_disable_unprepare(aq->pclk);
exit:
@@ -487,6 +632,7 @@ static int atmel_qspi_remove(struct platform_device *pdev)
spi_unregister_controller(ctrl);
writel_relaxed(QSPI_CR_QSPIDIS, aq->regs + QSPI_CR);
+ clk_disable_unprepare(aq->qspick);
clk_disable_unprepare(aq->pclk);
return 0;
}
@@ -495,6 +641,7 @@ static int __maybe_unused atmel_qspi_suspend(struct device *dev)
{
struct atmel_qspi *aq = dev_get_drvdata(dev);
+ clk_disable_unprepare(aq->qspick);
clk_disable_unprepare(aq->pclk);
return 0;
@@ -505,6 +652,7 @@ static int __maybe_unused atmel_qspi_resume(struct device *dev)
struct atmel_qspi *aq = dev_get_drvdata(dev);
clk_prepare_enable(aq->pclk);
+ clk_prepare_enable(aq->qspick);
return atmel_qspi_init(aq);
}
@@ -512,8 +660,34 @@ 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 = {
+ .set_tfrtyp = atmel_qspi_sama5d2_set_tfrtyp,
+ .write_regs = atmel_qspi_sama5d2_write_regs,
+};
+
+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 = {
+ .set_tfrtyp = atmel_qspi_sam9x60_set_tfrtyp,
+ .write_regs = atmel_qspi_sam9x60_write_regs,
+};
+
+static const struct atmel_qspi_caps atmel_sam9x60_qspi_caps = {
+ .ops = &atmel_sam9x60_qspi_ops,
+ .has_qspick = true,
+};
+
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 */ }
};