summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/clk/imx/clk-imx25.c7
-rw-r--r--drivers/clk/imx/clk-vf610.c33
-rw-r--r--drivers/crypto/Kconfig1
-rw-r--r--drivers/crypto/Makefile1
-rw-r--r--drivers/crypto/caam/Makefile1
-rw-r--r--drivers/crypto/caam/caam-blobgen.c229
-rw-r--r--drivers/crypto/caam/ctrl.c9
-rw-r--r--drivers/crypto/caam/intern.h1
-rw-r--r--drivers/crypto/caam/rng_self_test.c1
-rw-r--r--drivers/crypto/imx-scc/Kconfig14
-rw-r--r--drivers/crypto/imx-scc/Makefile2
-rw-r--r--drivers/crypto/imx-scc/scc-blobgen.c159
-rw-r--r--drivers/crypto/imx-scc/scc.c504
-rw-r--r--drivers/crypto/imx-scc/scc.h13
-rw-r--r--drivers/ddr/fsl/fsl_ddr.h2
-rw-r--r--drivers/mci/Kconfig4
-rw-r--r--drivers/mci/Makefile1
-rw-r--r--drivers/mci/bcm2835-sdhost.c638
-rw-r--r--drivers/mci/imx-esdhc-pbl.c5
-rw-r--r--drivers/mtd/devices/Kconfig1
-rw-r--r--drivers/mtd/devices/m25p80.c160
-rw-r--r--drivers/mtd/spi-nor/cadence-quadspi.c257
-rw-r--r--drivers/mtd/spi-nor/spi-nor.c673
-rw-r--r--drivers/mtd/ubi/ubi-barebox.h1
-rw-r--r--drivers/net/fsl-fman.c2
-rw-r--r--drivers/rtc/Kconfig6
-rw-r--r--drivers/rtc/Makefile1
-rw-r--r--drivers/rtc/rtc-imxdi.c623
-rw-r--r--drivers/spi/Kconfig18
-rw-r--r--drivers/spi/Makefile2
-rw-r--r--drivers/spi/spi-fsl-qspi.c869
-rw-r--r--drivers/spi/spi-mem.c524
-rw-r--r--drivers/spi/spi.c110
33 files changed, 4452 insertions, 420 deletions
diff --git a/drivers/clk/imx/clk-imx25.c b/drivers/clk/imx/clk-imx25.c
index baa42e14f4..ce4fbed68c 100644
--- a/drivers/clk/imx/clk-imx25.c
+++ b/drivers/clk/imx/clk-imx25.c
@@ -95,7 +95,7 @@ static int imx25_ccm_probe(struct device_d *dev)
writel((1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 8) | (1 << 9) |
(1 << 10) | (1 << 15) | (1 << 19) | (1 << 21) | (1 << 22) |
- (1 << 23) | (1 << 24) | (1 << 28),
+ (1 << 23) | (1 << 24) | (1 << 25) | (1 << 28),
base + CCM_CGCR0);
writel((1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 13) | (1 << 14) |
@@ -152,7 +152,9 @@ static int imx25_ccm_probe(struct device_d *dev)
clks[lcdc_ahb] = imx_clk_gate("lcdc_ahb", "ahb", base + CCM_CGCR0, 24);
clks[lcdc_ipg] = imx_clk_gate("lcdc_ipg", "ipg", base + CCM_CGCR1, 29);
clks[lcdc_ipg_per] = imx_clk_gate("lcdc_ipg_per", "per7", base + CCM_CGCR0, 7);
+ clks[scc_ipg] = imx_clk_gate("scc_ipg", "ipg", base + CCM_CGCR2, 5);
clks[rngb_ipg] = imx_clk_gate("rngb_ipg", "ipg", base + CCM_CGCR2, 3);
+ clks[dryice_ipg] = imx_clk_gate("dryice_ipg", "ipg", base + CCM_CGCR1, 8);
clkdev_add_physbase(clks[per15], MX25_UART1_BASE_ADDR, NULL);
clkdev_add_physbase(clks[per15], MX25_UART2_BASE_ADDR, NULL);
@@ -176,6 +178,9 @@ static int imx25_ccm_probe(struct device_d *dev)
clkdev_add_physbase(clks[lcdc_ipg_per], MX25_LCDC_BASE_ADDR, "per");
clkdev_add_physbase(clks[lcdc_ipg], MX25_LCDC_BASE_ADDR, "ipg");
clkdev_add_physbase(clks[lcdc_ahb], MX25_LCDC_BASE_ADDR, "ahb");
+ clkdev_add_physbase(clks[scc_ipg], MX25_SCC_BASE_ADDR, "ipg");
+ clkdev_add_physbase(clks[rngb_ipg], MX25_RNGB_BASE_ADDR, "ipg");
+ clkdev_add_physbase(clks[dryice_ipg], MX25_DRYICE_BASE_ADDR, NULL);
return 0;
}
diff --git a/drivers/clk/imx/clk-vf610.c b/drivers/clk/imx/clk-vf610.c
index 1b1b881052..d70f4416cb 100644
--- a/drivers/clk/imx/clk-vf610.c
+++ b/drivers/clk/imx/clk-vf610.c
@@ -459,26 +459,30 @@ enum {
DDRMC_CR117_AXI0_FITYPEREG_SYNC = 0b01 << 16,
};
-static int vf610_switch_cpu_clock_to_500mhz(void)
+static bool vf610_cpu_clk_changeable(void)
{
- int ret;
-
/*
* When switching A5 CPU to 500Mhz we expect DDRC to be
* clocked by PLL2_PFD2 and the system to be configured in
* asynchronous mode.
- *
- * We also can't just use default PFD1 output of PLL1 due to
- * Errata e6235, so we have to re-clock the PLL itself and use
- * its output to clock the CPU directly.
*/
-
if (clk_get_parent(clk[VF610_CLK_DDR_SEL]) != clk[VF610_CLK_PLL2_PFD2]) {
- pr_warn("DDRC is clocked by PLL1, can't switch CPU clock");
- return -EINVAL;
+ pr_warn("DDRC is clocked by PLL1, can't switch CPU clock\n");
+ return false;
}
+ return true;
+}
+
+static int vf610_switch_cpu_clock_to_500mhz(void)
+{
+ int ret;
+
/*
+ * We can't just use default PFD1 output of PLL1 due to
+ * Errata e6235, so we have to re-clock the PLL itself and use
+ * its output to clock the CPU directly.
+ *
* Code below alters the frequency of PLL1, and doing so would
* require us to wait for PLL1 lock before proceeding to
* select it as a clock source again.
@@ -533,11 +537,6 @@ static int vf610_switch_cpu_clock_to_400mhz(void)
uint32_t cr117;
void * __iomem ddrmc = IOMEM(VF610_DDR_BASE_ADDR);
- if (clk_get_parent(clk[VF610_CLK_DDR_SEL]) != clk[VF610_CLK_PLL2_PFD2]) {
- pr_warn("DDRC is clocked by PLL1, can't switch CPU clock");
- return -EINVAL;
- }
-
ret = clk_set_parent(clk[VF610_CLK_PLL2_PFD_SEL], clk[VF610_CLK_PLL2_PFD2]);
if (ret < 0) {
pr_crit("Unable to re-parent '%s'\n",
@@ -595,10 +594,14 @@ static int vf610_switch_cpu_clock(void)
return 0;
case VF610_SPEED_500:
+ if (!vf610_cpu_clk_changeable())
+ return 0;
ret = vf610_switch_cpu_clock_to_500mhz();
break;
case VF610_SPEED_400:
+ if (!vf610_cpu_clk_changeable())
+ return 0;
ret = vf610_switch_cpu_clock_to_400mhz();
break;
}
diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig
index b2709f00f8..77d3782bde 100644
--- a/drivers/crypto/Kconfig
+++ b/drivers/crypto/Kconfig
@@ -5,5 +5,6 @@ menuconfig CRYPTO_HW
if CRYPTO_HW
source "drivers/crypto/caam/Kconfig"
+source "drivers/crypto/imx-scc/Kconfig"
endif
diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile
index 67f968f76c..1999929bc2 100644
--- a/drivers/crypto/Makefile
+++ b/drivers/crypto/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_CRYPTO_DEV_FSL_CAAM) += caam/
+obj-$(CONFIG_CRYPTO_DEV_MXC_SCC) += imx-scc/
diff --git a/drivers/crypto/caam/Makefile b/drivers/crypto/caam/Makefile
index 7bd6f3e23c..933b9c0592 100644
--- a/drivers/crypto/caam/Makefile
+++ b/drivers/crypto/caam/Makefile
@@ -4,3 +4,4 @@
obj-$(CONFIG_CRYPTO_DEV_FSL_CAAM) += ctrl.o error.o jr.o
obj-$(CONFIG_CRYPTO_DEV_FSL_CAAM_RNG) += caamrng.o
obj-$(CONFIG_CRYPTO_DEV_FSL_CAAM_RNG_SELF_TEST) += rng_self_test.o
+obj-$(CONFIG_BLOBGEN) += caam-blobgen.o
diff --git a/drivers/crypto/caam/caam-blobgen.c b/drivers/crypto/caam/caam-blobgen.c
new file mode 100644
index 0000000000..acbe5a110d
--- /dev/null
+++ b/drivers/crypto/caam/caam-blobgen.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2015 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+#include <common.h>
+#include <asm/io.h>
+#include <base64.h>
+#include <blobgen.h>
+#include <crypto.h>
+#include <dma.h>
+#include <driver.h>
+#include <init.h>
+#include <fs.h>
+#include <fcntl.h>
+#include "intern.h"
+#include "desc.h"
+#include "desc_constr.h"
+#include "error.h"
+#include "jr.h"
+
+/*
+ * Upon completion, desc points to a buffer containing a CAAM job
+ * descriptor which encapsulates data into an externally-storable
+ * blob.
+ */
+#define INITIAL_DESCSZ 16
+/* 32 bytes key blob + 16 bytes HMAC identifier */
+#define BLOB_OVERHEAD (32 + 16)
+#define KEYMOD_LENGTH 16
+#define RED_BLOB_LENGTH 64
+#define MAX_BLOB_LEN 4096
+#define DESC_LEN 64
+
+struct blob_job_result {
+ int err;
+};
+
+struct blob_priv {
+ struct blobgen bg;
+ u32 desc[DESC_LEN];
+ dma_addr_t dma_modifier;
+ dma_addr_t dma_plaintext;
+ dma_addr_t dma_ciphertext;
+};
+
+static struct blob_priv *to_blob_priv(struct blobgen *bg)
+{
+ return container_of(bg, struct blob_priv, bg);
+}
+
+static void jr_jobdesc_blob_decap(struct blob_priv *ctx, u8 modlen, u16 input_size)
+{
+ u32 *desc = ctx->desc;
+ u16 in_sz;
+ u16 out_sz;
+
+ in_sz = input_size;
+ out_sz = input_size - BLOB_OVERHEAD;
+
+ init_job_desc(desc, 0);
+ /*
+ * The key modifier can be used to differentiate specific data.
+ * Or to prevent replay attacks.
+ */
+ append_key(desc, ctx->dma_modifier, modlen, CLASS_2);
+ append_seq_in_ptr(desc, ctx->dma_ciphertext, in_sz, 0);
+ append_seq_out_ptr(desc, ctx->dma_plaintext, out_sz, 0);
+ append_operation(desc, OP_TYPE_DECAP_PROTOCOL | OP_PCLID_BLOB);
+}
+
+static void jr_jobdesc_blob_encap(struct blob_priv *ctx, u8 modlen, u16 input_size)
+{
+ u32 *desc = ctx->desc;
+ u16 in_sz;
+ u16 out_sz;
+
+ in_sz = input_size;
+ out_sz = input_size + BLOB_OVERHEAD;
+
+ init_job_desc(desc, 0);
+ /*
+ * The key modifier can be used to differentiate specific data.
+ * Or to prevent replay attacks.
+ */
+ append_key(desc, ctx->dma_modifier, modlen, CLASS_2);
+ append_seq_in_ptr(desc, ctx->dma_plaintext, in_sz, 0);
+ append_seq_out_ptr(desc, ctx->dma_ciphertext, out_sz, 0);
+ append_operation(desc, OP_TYPE_ENCAP_PROTOCOL | OP_PCLID_BLOB);
+}
+
+static void blob_job_done(struct device_d *dev, u32 *desc, u32 err, void *arg)
+{
+ struct blob_job_result *res = arg;
+
+ if (!res)
+ return;
+
+ if (err)
+ caam_jr_strstatus(dev, err);
+
+ res->err = err;
+}
+
+static int caam_blob_decrypt(struct blobgen *bg, const char *modifier,
+ const void *blob, int blobsize, void **plain,
+ int *plainsize)
+{
+ struct blob_priv *ctx = to_blob_priv(bg);
+ struct device_d *jrdev = bg->dev.parent;
+ struct blob_job_result testres;
+ int modifier_len = strlen(modifier);
+ u32 *desc = ctx->desc;
+ int ret;
+
+ if (blobsize <= BLOB_OVERHEAD)
+ return -EINVAL;
+
+ *plainsize = blobsize - BLOB_OVERHEAD;
+
+ *plain = dma_alloc(*plainsize);
+ if (!*plain)
+ return -ENOMEM;
+
+ memset(desc, 0, DESC_LEN);
+
+ ctx->dma_modifier = (dma_addr_t)modifier;
+ ctx->dma_plaintext = (dma_addr_t)*plain;
+ ctx->dma_ciphertext = (dma_addr_t)blob;
+
+ jr_jobdesc_blob_decap(ctx, modifier_len, blobsize);
+
+ dma_sync_single_for_device((unsigned long)desc, desc_bytes(desc),
+ DMA_TO_DEVICE);
+
+ dma_sync_single_for_device((unsigned long)modifier, modifier_len,
+ DMA_TO_DEVICE);
+ dma_sync_single_for_device((unsigned long)*plain, *plainsize,
+ DMA_FROM_DEVICE);
+ dma_sync_single_for_device((unsigned long)blob, blobsize,
+ DMA_TO_DEVICE);
+
+ testres.err = 0;
+
+ ret = caam_jr_enqueue(jrdev, desc, blob_job_done, &testres);
+ if (ret)
+ dev_err(jrdev, "decryption error\n");
+
+ ret = testres.err;
+
+ dma_sync_single_for_cpu((unsigned long)modifier, modifier_len,
+ DMA_TO_DEVICE);
+ dma_sync_single_for_cpu((unsigned long)*plain, *plainsize,
+ DMA_FROM_DEVICE);
+ dma_sync_single_for_cpu((unsigned long)blob, blobsize,
+ DMA_TO_DEVICE);
+
+ return ret;
+}
+
+static int caam_blob_encrypt(struct blobgen *bg, const char *modifier,
+ const void *plain, int plainsize, void *blob,
+ int *blobsize)
+{
+ struct blob_priv *ctx = to_blob_priv(bg);
+ struct device_d *jrdev = bg->dev.parent;
+ struct blob_job_result testres;
+ int modifier_len = strlen(modifier);
+ u32 *desc = ctx->desc;
+ int ret;
+
+ *blobsize = plainsize + BLOB_OVERHEAD;
+
+ memset(desc, 0, DESC_LEN);
+
+ ctx->dma_modifier = (dma_addr_t)modifier;
+ ctx->dma_plaintext = (dma_addr_t)plain;
+ ctx->dma_ciphertext = (dma_addr_t)blob;
+
+ jr_jobdesc_blob_encap(ctx, modifier_len, plainsize);
+
+ dma_sync_single_for_device((unsigned long)desc, desc_bytes(desc),
+ DMA_TO_DEVICE);
+
+ dma_sync_single_for_device((unsigned long)modifier, modifier_len,
+ DMA_TO_DEVICE);
+ dma_sync_single_for_device((unsigned long)plain, plainsize,
+ DMA_TO_DEVICE);
+ dma_sync_single_for_device((unsigned long)blob, *blobsize,
+ DMA_FROM_DEVICE);
+
+ testres.err = 0;
+
+ ret = caam_jr_enqueue(jrdev, desc, blob_job_done, &testres);
+ if (ret)
+ dev_err(jrdev, "encryption error\n");
+
+ ret = testres.err;
+
+ dma_sync_single_for_cpu((unsigned long)modifier, modifier_len,
+ DMA_TO_DEVICE);
+ dma_sync_single_for_cpu((unsigned long)plain, plainsize,
+ DMA_TO_DEVICE);
+ dma_sync_single_for_cpu((unsigned long)blob, *blobsize,
+ DMA_FROM_DEVICE);
+
+ return ret;
+}
+
+int caam_blob_gen_probe(struct device_d *dev, struct device_d *jrdev)
+{
+ struct blob_priv *ctx;
+ struct blobgen *bg;
+ int ret;
+
+ ctx = xzalloc(sizeof(*ctx));
+ bg = &ctx->bg;
+ bg->max_payload_size = MAX_BLOB_LEN - BLOB_OVERHEAD;
+ bg->encrypt = caam_blob_encrypt;
+ bg->decrypt = caam_blob_decrypt;
+
+ ret = blob_gen_register(jrdev, bg);
+ if (ret)
+ free(ctx);
+
+ return ret;
+}
diff --git a/drivers/crypto/caam/ctrl.c b/drivers/crypto/caam/ctrl.c
index 4fe3eea3e6..06b075e74a 100644
--- a/drivers/crypto/caam/ctrl.c
+++ b/drivers/crypto/caam/ctrl.c
@@ -654,6 +654,15 @@ static int caam_probe(struct device_d *dev)
}
}
+ if (IS_ENABLED(CONFIG_BLOBGEN)) {
+ ret = caam_blob_gen_probe(dev, ctrlpriv->jrpdev[0]);
+ if (ret) {
+ dev_err(dev, "failed to instantiate blobgen device");
+ caam_remove(dev);
+ return ret;
+ }
+ }
+
/* NOTE: RTIC detection ought to go here, around Si time */
caam_id = (u64)rd_reg32(&ctrl->perfmon.caam_id_ms) << 32 |
(u64)rd_reg32(&ctrl->perfmon.caam_id_ls);
diff --git a/drivers/crypto/caam/intern.h b/drivers/crypto/caam/intern.h
index fe19a2c8d2..6dfcea26ac 100644
--- a/drivers/crypto/caam/intern.h
+++ b/drivers/crypto/caam/intern.h
@@ -93,5 +93,6 @@ void caam_jr_algapi_init(struct device *dev);
void caam_jr_algapi_remove(struct device *dev);
int caam_rng_probe(struct device_d *dev, struct device_d *jrdev);
+int caam_blob_gen_probe(struct device_d *dev, struct device_d *jrdev);
int caam_jr_probe(struct device_d *dev);
#endif /* INTERN_H */
diff --git a/drivers/crypto/caam/rng_self_test.c b/drivers/crypto/caam/rng_self_test.c
index aab4fa2e47..7816cd152c 100644
--- a/drivers/crypto/caam/rng_self_test.c
+++ b/drivers/crypto/caam/rng_self_test.c
@@ -51,6 +51,7 @@
#include "error.h"
#include "regs.h"
#include "jr.h"
+#include "rng_self_test.h"
static const u32 rng_dsc1[] = {
0xb0800036, 0x04800010, 0x3c85a15b, 0x50a9d0b1,
diff --git a/drivers/crypto/imx-scc/Kconfig b/drivers/crypto/imx-scc/Kconfig
new file mode 100644
index 0000000000..531304f432
--- /dev/null
+++ b/drivers/crypto/imx-scc/Kconfig
@@ -0,0 +1,14 @@
+config CRYPTO_DEV_MXC_SCC
+ tristate "Support for Freescale Security Controller (SCC)"
+ depends on ARCH_IMX25 && OFTREE
+ select CRYPTO_BLKCIPHER
+ select CRYPTO_DES
+ help
+ This option enables support for the Security Controller (SCC)
+ found in Freescale i.MX25 chips.
+
+config CRYPTO_DEV_MXC_SCC_BLOB_GEN
+ tristate "Support for SCC blob gen"
+ depends on ARCH_IMX25
+ select BLOBGEN
+ select CRYPTO_DEV_MXC_SCC
diff --git a/drivers/crypto/imx-scc/Makefile b/drivers/crypto/imx-scc/Makefile
new file mode 100644
index 0000000000..c30fd1e12d
--- /dev/null
+++ b/drivers/crypto/imx-scc/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_CRYPTO_DEV_MXC_SCC) += scc.o
+obj-$(CONFIG_CRYPTO_DEV_MXC_SCC_BLOB_GEN) += scc-blobgen.o
diff --git a/drivers/crypto/imx-scc/scc-blobgen.c b/drivers/crypto/imx-scc/scc-blobgen.c
new file mode 100644
index 0000000000..e1a1372420
--- /dev/null
+++ b/drivers/crypto/imx-scc/scc-blobgen.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+#include <common.h>
+#include <dma.h>
+#include <digest.h>
+#include <driver.h>
+#include <init.h>
+#include <blobgen.h>
+#include <stdlib.h>
+#include <crypto.h>
+#include <crypto/sha.h>
+
+#include "scc.h"
+
+#define MAX_IVLEN BLOCKSIZE_BYTES
+
+static struct digest *sha256;
+
+static int sha256sum(uint8_t *src, uint8_t *dst, unsigned int size)
+{
+ if (!sha256)
+ sha256 = digest_alloc("sha256");
+
+ if (!sha256) {
+ pr_err("Unable to allocate sha256 digest\n");
+ return -EINVAL;
+ }
+
+ return digest_digest(sha256, src, size, dst);
+}
+
+static int imx_scc_blob_encrypt(struct blobgen *bg, const char *modifier,
+ const void *plain, int plainsize, void *blob,
+ int *blobsize)
+{
+ char *s;
+ int bufsiz;
+ struct ablkcipher_request req = {};
+ uint8_t iv[MAX_IVLEN];
+ uint8_t hash[SHA256_DIGEST_SIZE];
+ int ret;
+
+ bufsiz = ALIGN(plainsize + KEYMOD_LENGTH, 8);
+
+ s = malloc(bufsiz + SHA256_DIGEST_SIZE);
+ if (!s)
+ return -ENOMEM;
+
+ memset(s, 0, bufsiz);
+
+ strncpy(s, modifier, KEYMOD_LENGTH);
+ memcpy(s + KEYMOD_LENGTH, plain, plainsize);
+
+ ret = sha256sum(s, hash, bufsiz);
+ if (ret)
+ goto out;
+
+ memcpy(s + bufsiz, hash, SHA256_DIGEST_SIZE);
+
+ bufsiz += SHA256_DIGEST_SIZE;
+
+ req.info = iv;
+ req.src = s;
+ req.dst = blob;
+ req.nbytes = bufsiz;
+
+ get_random_bytes(req.info, MAX_IVLEN);
+
+ ret = imx_scc_cbc_des_encrypt(&req);
+ if (ret)
+ goto out;
+
+ memcpy(blob + bufsiz, req.info, MAX_IVLEN);
+ *blobsize = bufsiz + MAX_IVLEN;
+
+out:
+ free(s);
+
+ return ret;
+}
+
+static int imx_scc_blob_decrypt(struct blobgen *bg, const char *modifier,
+ const void *blob, int blobsize, void **plain,
+ int *plainsize)
+{
+ struct ablkcipher_request req = {};
+ uint8_t iv[MAX_IVLEN];
+ uint8_t hash[SHA256_DIGEST_SIZE];
+ int ret;
+ uint8_t *data;
+ int ciphersize = blobsize - MAX_IVLEN;
+
+ if (blobsize <= MAX_IVLEN + SHA256_DIGEST_SIZE + KEYMOD_LENGTH)
+ return -EINVAL;
+
+ data = malloc(ciphersize);
+ if (!data)
+ return -ENOMEM;
+
+ req.info = iv;
+ req.nbytes = ciphersize;
+ req.src = (void *)blob;
+ req.dst = data;
+
+ memcpy(req.info, blob + req.nbytes, MAX_IVLEN);
+
+ ret = imx_scc_cbc_des_decrypt(&req);
+ if (ret)
+ goto out;
+
+ ret = sha256sum(data, hash, ciphersize - SHA256_DIGEST_SIZE);
+ if (ret)
+ goto out;
+
+ if (memcmp(data + ciphersize - SHA256_DIGEST_SIZE, hash,
+ SHA256_DIGEST_SIZE)) {
+ pr_err("%s: Corrupted SHA256 digest. Can't continue.\n",
+ bg->dev.name);
+ pr_err("%s: Calculated hash:\n", bg->dev.name);
+ memory_display(hash, 0, SHA256_DIGEST_SIZE, 1, 0);
+ pr_err("%s: Received hash:\n", bg->dev.name);
+ memory_display(data + ciphersize - SHA256_DIGEST_SIZE,
+ 0, SHA256_DIGEST_SIZE, 1, 0);
+
+ ret = -EILSEQ;
+ goto out;
+ }
+
+ *plainsize = ciphersize - SHA256_DIGEST_SIZE - KEYMOD_LENGTH;
+ *plain = xmemdup(data + KEYMOD_LENGTH, *plainsize);
+out:
+ free(data);
+
+ return ret;
+}
+
+int imx_scc_blob_gen_probe(struct device_d *dev)
+{
+ struct blobgen *bg;
+ int ret;
+
+ bg = xzalloc(sizeof(*bg));
+
+ bg->max_payload_size = MAX_BLOB_LEN - MAX_IVLEN -
+ SHA256_DIGEST_SIZE - KEYMOD_LENGTH;
+ bg->encrypt = imx_scc_blob_encrypt;
+ bg->decrypt = imx_scc_blob_decrypt;
+
+ ret = blob_gen_register(dev, bg);
+ if (ret)
+ free(bg);
+
+ return ret;
+}
diff --git a/drivers/crypto/imx-scc/scc.c b/drivers/crypto/imx-scc/scc.c
new file mode 100644
index 0000000000..5a35c3506d
--- /dev/null
+++ b/drivers/crypto/imx-scc/scc.c
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2016 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de>
+ *
+ * The driver is based on information gathered from
+ * drivers/mxc/security/imx_scc.c which can be found in
+ * the Freescale linux-2.6-imx.git in the imx_2.6.35_maintain branch.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <common.h>
+#include <clock.h>
+#include <driver.h>
+#include <init.h>
+#include <io.h>
+#include <crypto.h>
+#include <linux/barebox-wrapper.h>
+#include <linux/clk.h>
+#include <crypto/des.h>
+
+#include "scc.h"
+
+/* Secure Memory (SCM) registers */
+#define SCC_SCM_RED_START 0x0000
+#define SCC_SCM_BLACK_START 0x0004
+#define SCC_SCM_LENGTH 0x0008
+#define SCC_SCM_CTRL 0x000C
+#define SCC_SCM_STATUS 0x0010
+#define SCC_SCM_ERROR_STATUS 0x0014
+#define SCC_SCM_INTR_CTRL 0x0018
+#define SCC_SCM_CFG 0x001C
+#define SCC_SCM_INIT_VECTOR_0 0x0020
+#define SCC_SCM_INIT_VECTOR_1 0x0024
+#define SCC_SCM_RED_MEMORY 0x0400
+#define SCC_SCM_BLACK_MEMORY 0x0800
+
+/* Security Monitor (SMN) Registers */
+#define SCC_SMN_STATUS 0x1000
+#define SCC_SMN_COMMAND 0x1004
+#define SCC_SMN_SEQ_START 0x1008
+#define SCC_SMN_SEQ_END 0x100C
+#define SCC_SMN_SEQ_CHECK 0x1010
+#define SCC_SMN_BIT_COUNT 0x1014
+#define SCC_SMN_BITBANK_INC_SIZE 0x1018
+#define SCC_SMN_BITBANK_DECREMENT 0x101C
+#define SCC_SMN_COMPARE_SIZE 0x1020
+#define SCC_SMN_PLAINTEXT_CHECK 0x1024
+#define SCC_SMN_CIPHERTEXT_CHECK 0x1028
+#define SCC_SMN_TIMER_IV 0x102C
+#define SCC_SMN_TIMER_CONTROL 0x1030
+#define SCC_SMN_DEBUG_DETECT_STAT 0x1034
+#define SCC_SMN_TIMER 0x1038
+
+#define SCC_SCM_CTRL_START_CIPHER BIT(2)
+#define SCC_SCM_CTRL_CBC_MODE BIT(1)
+#define SCC_SCM_CTRL_DECRYPT_MODE BIT(0)
+
+#define SCC_SCM_STATUS_LEN_ERR BIT(12)
+#define SCC_SCM_STATUS_SMN_UNBLOCKED BIT(11)
+#define SCC_SCM_STATUS_CIPHERING_DONE BIT(10)
+#define SCC_SCM_STATUS_ZEROIZING_DONE BIT(9)
+#define SCC_SCM_STATUS_INTR_STATUS BIT(8)
+#define SCC_SCM_STATUS_SEC_KEY BIT(7)
+#define SCC_SCM_STATUS_INTERNAL_ERR BIT(6)
+#define SCC_SCM_STATUS_BAD_SEC_KEY BIT(5)
+#define SCC_SCM_STATUS_ZEROIZE_FAIL BIT(4)
+#define SCC_SCM_STATUS_SMN_BLOCKED BIT(3)
+#define SCC_SCM_STATUS_CIPHERING BIT(2)
+#define SCC_SCM_STATUS_ZEROIZING BIT(1)
+#define SCC_SCM_STATUS_BUSY BIT(0)
+
+#define SCC_SMN_STATUS_STATE_MASK 0x0000001F
+#define SCC_SMN_STATE_START 0x0
+/* The SMN is zeroizing its RAM during reset */
+#define SCC_SMN_STATE_ZEROIZE_RAM 0x5
+/* SMN has passed internal checks */
+#define SCC_SMN_STATE_HEALTH_CHECK 0x6
+/* Fatal Security Violation. SMN is locked, SCM is inoperative. */
+#define SCC_SMN_STATE_FAIL 0x9
+/* SCC is in secure state. SCM is using secret key. */
+#define SCC_SMN_STATE_SECURE 0xA
+/* SCC is not secure. SCM is using default key. */
+#define SCC_SMN_STATE_NON_SECURE 0xC
+
+#define SCC_SCM_INTR_CTRL_ZEROIZE_MEM BIT(2)
+#define SCC_SCM_INTR_CTRL_CLR_INTR BIT(1)
+#define SCC_SCM_INTR_CTRL_MASK_INTR BIT(0)
+
+/* Size, in blocks, of Red memory. */
+#define SCC_SCM_CFG_BLACK_SIZE_MASK 0x07fe0000
+#define SCC_SCM_CFG_BLACK_SIZE_SHIFT 17
+/* Size, in blocks, of Black memory. */
+#define SCC_SCM_CFG_RED_SIZE_MASK 0x0001ff80
+#define SCC_SCM_CFG_RED_SIZE_SHIFT 7
+/* Number of bytes per block. */
+#define SCC_SCM_CFG_BLOCK_SIZE_MASK 0x0000007f
+
+#define SCC_SMN_COMMAND_TAMPER_LOCK BIT(4)
+#define SCC_SMN_COMMAND_CLR_INTR BIT(3)
+#define SCC_SMN_COMMAND_CLR_BIT_BANK BIT(2)
+#define SCC_SMN_COMMAND_EN_INTR BIT(1)
+#define SCC_SMN_COMMAND_SET_SOFTWARE_ALARM BIT(0)
+
+#define SCC_KEY_SLOTS 20
+#define SCC_MAX_KEY_SIZE 32
+#define SCC_KEY_SLOT_SIZE 32
+
+#define SCC_CRC_CCITT_START 0xFFFF
+
+/*
+ * Offset into each RAM of the base of the area which is not
+ * used for Stored Keys.
+ */
+#define SCC_NON_RESERVED_OFFSET (SCC_KEY_SLOTS * SCC_KEY_SLOT_SIZE)
+
+/* Fixed padding for appending to plaintext to fill out a block */
+static char scc_block_padding[8] = { 0x80, 0, 0, 0, 0, 0, 0, 0 };
+
+struct imx_scc {
+ struct device_d *dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct ablkcipher_request *req;
+ unsigned int block_size_bytes;
+ unsigned int black_ram_size_blocks;
+ unsigned int memory_size_bytes;
+ unsigned int bytes_remaining;
+
+ void __iomem *red_memory;
+ void __iomem *black_memory;
+};
+
+struct imx_scc_ctx {
+ struct imx_scc *scc;
+ unsigned int offset;
+ unsigned int size;
+ unsigned int ctrl;
+};
+
+static struct imx_scc *scc_dev;
+
+static int imx_scc_get_data(struct imx_scc_ctx *ctx,
+ struct ablkcipher_request *ablkreq)
+{
+ struct imx_scc *scc = ctx->scc;
+ void __iomem *from;
+
+ if (ctx->ctrl & SCC_SCM_CTRL_DECRYPT_MODE)
+ from = scc->red_memory;
+ else
+ from = scc->black_memory;
+
+ memcpy(ablkreq->dst, from + ctx->offset, ctx->size);
+
+ pr_debug("GET_DATA:\n");
+ pr_memory_display(MSG_DEBUG, from, 0, ctx->size, 0x40 >> 3, 0);
+
+ ctx->offset += ctx->size;
+
+ if (ctx->offset < ablkreq->nbytes)
+ return -EINPROGRESS;
+
+ return 0;
+}
+
+static int imx_scc_ablkcipher_req_init(struct ablkcipher_request *req,
+ struct imx_scc_ctx *ctx)
+{
+ ctx->size = 0;
+ ctx->offset = 0;
+
+ return 0;
+}
+
+static int imx_scc_put_data(struct imx_scc_ctx *ctx,
+ struct ablkcipher_request *req)
+{
+ u8 padding_buffer[sizeof(u16) + sizeof(scc_block_padding)];
+ size_t len = min(req->nbytes - ctx->offset, ctx->scc->bytes_remaining);
+ unsigned int padding_byte_count = 0;
+ struct imx_scc *scc = ctx->scc;
+ void __iomem *to;
+
+ if (ctx->ctrl & SCC_SCM_CTRL_DECRYPT_MODE)
+ to = scc->black_memory;
+ else
+ to = scc->red_memory;
+
+ if (ctx->ctrl & SCC_SCM_CTRL_CBC_MODE) {
+ dev_dbg(scc->dev, "set IV@0x%p\n", scc->base + SCC_SCM_INIT_VECTOR_0);
+ memcpy(scc->base + SCC_SCM_INIT_VECTOR_0, req->info,
+ scc->block_size_bytes);
+ }
+
+ memcpy(to, req->src + ctx->offset, len);
+
+ ctx->size = len;
+
+ scc->bytes_remaining -= len;
+
+ padding_byte_count = ((len + scc->block_size_bytes - 1) &
+ ~(scc->block_size_bytes-1)) - len;
+
+ if (padding_byte_count) {
+ memcpy(padding_buffer, scc_block_padding, padding_byte_count);
+ memcpy(to + len, padding_buffer, padding_byte_count);
+ ctx->size += padding_byte_count;
+ }
+
+ dev_dbg(scc->dev, "copied %d bytes to 0x%p\n", ctx->size, to);
+ pr_debug("IV:\n");
+ pr_memory_display(MSG_DEBUG, scc->base + SCC_SCM_INIT_VECTOR_0, 0,
+ scc->block_size_bytes,
+ 0x40 >> 3, 0);
+ pr_debug("DATA:\n");
+ pr_memory_display(MSG_DEBUG, to, 0, ctx->size, 0x40 >> 3, 0);
+
+ return 0;
+}
+
+static int imx_scc_ablkcipher_next(struct imx_scc_ctx *ctx,
+ struct ablkcipher_request *ablkreq)
+{
+ struct imx_scc *scc = ctx->scc;
+ int err;
+
+ writel(0, scc->base + SCC_SCM_ERROR_STATUS);
+
+ err = imx_scc_put_data(ctx, ablkreq);
+ if (err)
+ return err;
+
+ dev_dbg(scc->dev, "Start encryption (0x%p/0x%p)\n",
+ (void *)readl(scc->base + SCC_SCM_RED_START),
+ (void *)readl(scc->base + SCC_SCM_BLACK_START));
+
+ /* clear interrupt control registers */
+ writel(SCC_SCM_INTR_CTRL_CLR_INTR,
+ scc->base + SCC_SCM_INTR_CTRL);
+
+ writel((ctx->size / ctx->scc->block_size_bytes) - 1,
+ scc->base + SCC_SCM_LENGTH);
+
+ dev_dbg(scc->dev, "Process %d block(s) in 0x%p\n",
+ ctx->size / ctx->scc->block_size_bytes,
+ (ctx->ctrl & SCC_SCM_CTRL_DECRYPT_MODE) ? scc->black_memory :
+ scc->red_memory);
+
+ writel(ctx->ctrl, scc->base + SCC_SCM_CTRL);
+
+ return 0;
+}
+
+static int imx_scc_int(struct imx_scc_ctx *ctx)
+{
+ struct ablkcipher_request *ablkreq;
+ struct imx_scc *scc = ctx->scc;
+ uint64_t start;
+
+ start = get_time_ns();
+ while (readl(scc->base + SCC_SCM_STATUS) & SCC_SCM_STATUS_BUSY) {
+ if (is_timeout(start, 100 * MSECOND)) {
+ dev_err(scc->dev, "timeout waiting for interrupt\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ /* clear interrupt control registers */
+ writel(SCC_SCM_INTR_CTRL_CLR_INTR, scc->base + SCC_SCM_INTR_CTRL);
+
+ ablkreq = scc->req;
+
+ if (ablkreq)
+ return imx_scc_get_data(ctx, ablkreq);
+
+ return 0;
+}
+
+static int imx_scc_process_req(struct imx_scc_ctx *ctx,
+ struct ablkcipher_request *ablkreq)
+{
+ int ret = -EINPROGRESS;
+
+ ctx->scc->req = ablkreq;
+
+ while (ret == -EINPROGRESS) {
+ ret = imx_scc_ablkcipher_next(ctx, ablkreq);
+ if (ret)
+ break;
+ ret = imx_scc_int(ctx);
+ }
+
+ ctx->scc->req = NULL;
+ ctx->scc->bytes_remaining = ctx->scc->memory_size_bytes;
+
+ return 0;
+}
+
+static int imx_scc_des3_op(struct imx_scc_ctx *ctx,
+ struct ablkcipher_request *req)
+{
+ int err;
+
+ err = imx_scc_ablkcipher_req_init(req, ctx);
+ if (err)
+ return err;
+
+ return imx_scc_process_req(ctx, req);
+}
+
+int imx_scc_cbc_des_encrypt(struct ablkcipher_request *req)
+{
+ struct imx_scc_ctx *ctx;
+
+ ctx = xzalloc(sizeof(*ctx));
+ ctx->scc = scc_dev;
+
+ ctx->ctrl = SCC_SCM_CTRL_START_CIPHER;
+ ctx->ctrl |= SCC_SCM_CTRL_CBC_MODE;
+
+ return imx_scc_des3_op(ctx, req);
+}
+
+int imx_scc_cbc_des_decrypt(struct ablkcipher_request *req)
+{
+ struct imx_scc_ctx *ctx;
+
+ ctx = xzalloc(sizeof(*ctx));
+ ctx->scc = scc_dev;
+
+ ctx->ctrl = SCC_SCM_CTRL_START_CIPHER;
+ ctx->ctrl |= SCC_SCM_CTRL_CBC_MODE;
+ ctx->ctrl |= SCC_SCM_CTRL_DECRYPT_MODE;
+
+ return imx_scc_des3_op(ctx, req);
+}
+
+static void imx_scc_hw_init(struct imx_scc *scc)
+{
+ int offset;
+
+ offset = SCC_NON_RESERVED_OFFSET / scc->block_size_bytes;
+
+ /* Fill the RED_START register */
+ writel(offset, scc->base + SCC_SCM_RED_START);
+
+ /* Fill the BLACK_START register */
+ writel(offset, scc->base + SCC_SCM_BLACK_START);
+
+ scc->red_memory = scc->base + SCC_SCM_RED_MEMORY +
+ SCC_NON_RESERVED_OFFSET;
+
+ scc->black_memory = scc->base + SCC_SCM_BLACK_MEMORY +
+ SCC_NON_RESERVED_OFFSET;
+
+ scc->bytes_remaining = scc->memory_size_bytes;
+}
+
+static int imx_scc_get_config(struct imx_scc *scc)
+{
+ int config;
+
+ config = readl(scc->base + SCC_SCM_CFG);
+
+ scc->block_size_bytes = config & SCC_SCM_CFG_BLOCK_SIZE_MASK;
+
+ scc->black_ram_size_blocks = config & SCC_SCM_CFG_BLACK_SIZE_MASK;
+
+ scc->memory_size_bytes = (scc->block_size_bytes *
+ scc->black_ram_size_blocks) -
+ SCC_NON_RESERVED_OFFSET;
+
+ return 0;
+}
+
+static int imx_scc_get_state(struct imx_scc *scc)
+{
+ int status, ret;
+ const char *statestr;
+
+ status = readl(scc->base + SCC_SMN_STATUS) &
+ SCC_SMN_STATUS_STATE_MASK;
+
+ /* If in Health Check, try to bringup to secure state */
+ if (status & SCC_SMN_STATE_HEALTH_CHECK) {
+ /*
+ * Write a simple algorithm to the Algorithm Sequence
+ * Checker (ASC)
+ */
+ writel(0xaaaa, scc->base + SCC_SMN_SEQ_START);
+ writel(0x5555, scc->base + SCC_SMN_SEQ_END);
+ writel(0x5555, scc->base + SCC_SMN_SEQ_CHECK);
+
+ status = readl(scc->base + SCC_SMN_STATUS) &
+ SCC_SMN_STATUS_STATE_MASK;
+ }
+
+ switch (status) {
+ case SCC_SMN_STATE_NON_SECURE:
+ statestr = "non-secure";
+ ret = 0;
+ break;
+ case SCC_SMN_STATE_SECURE:
+ statestr = "secure";
+ ret = 0;
+ break;
+ case SCC_SMN_STATE_FAIL:
+ statestr = "fail";
+ ret = -EIO;
+ break;
+ default:
+ statestr = "unknown";
+ ret = -EINVAL;
+ break;
+ }
+
+ dev_info(scc->dev, "starting in %s mode\n", statestr);
+
+ return ret;
+}
+
+static int imx_scc_probe(struct device_d *dev)
+{
+ struct imx_scc *scc;
+ int ret;
+
+ scc = xzalloc(sizeof(*scc));
+
+ scc->base = dev_request_mem_region(dev, 0);
+ if (IS_ERR(scc->base))
+ return PTR_ERR(scc->base);
+
+ scc->clk = clk_get(dev, "ipg");
+ if (IS_ERR(scc->clk)) {
+ dev_err(dev, "Could not get ipg clock\n");
+ return PTR_ERR(scc->clk);
+ }
+
+ clk_enable(scc->clk);
+
+ /* clear error status register */
+
+ writel(0x0, scc->base + SCC_SCM_ERROR_STATUS);
+
+ /* clear interrupt control registers */
+ writel(SCC_SCM_INTR_CTRL_CLR_INTR |
+ SCC_SCM_INTR_CTRL_MASK_INTR,
+ scc->base + SCC_SCM_INTR_CTRL);
+
+ writel(SCC_SMN_COMMAND_CLR_INTR |
+ SCC_SMN_COMMAND_EN_INTR,
+ scc->base + SCC_SMN_COMMAND);
+
+ scc->dev = dev;
+
+ ret = imx_scc_get_config(scc);
+ if (ret)
+ goto err_out;
+
+ ret = imx_scc_get_state(scc);
+
+ if (ret) {
+ dev_err(dev, "SCC in unusable state\n");
+ goto err_out;
+ }
+
+ imx_scc_hw_init(scc);
+
+ scc_dev = scc;
+
+ if (IS_ENABLED(CONFIG_BLOBGEN)) {
+ ret = imx_scc_blob_gen_probe(dev);
+ if (ret)
+ goto err_out;
+ }
+
+ return 0;
+
+err_out:
+ clk_disable(scc->clk);
+ clk_put(scc->clk);
+ free(scc);
+
+ return ret;
+}
+
+static __maybe_unused struct of_device_id imx_scc_dt_ids[] = {
+ { .compatible = "fsl,imx25-scc", },
+ { /* sentinel */ }
+};
+
+static struct driver_d imx_scc_driver = {
+ .name = "mxc-scc",
+ .probe = imx_scc_probe,
+ .of_compatible = imx_scc_dt_ids,
+};
+device_platform_driver(imx_scc_driver);
diff --git a/drivers/crypto/imx-scc/scc.h b/drivers/crypto/imx-scc/scc.h
new file mode 100644
index 0000000000..5c5c25c4a0
--- /dev/null
+++ b/drivers/crypto/imx-scc/scc.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright (C) 2016 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+
+struct ablkcipher_request;
+
+int imx_scc_cbc_des_encrypt(struct ablkcipher_request *req);
+int imx_scc_cbc_des_decrypt(struct ablkcipher_request *req);
+int imx_scc_blob_gen_probe(struct device_d *dev);
diff --git a/drivers/ddr/fsl/fsl_ddr.h b/drivers/ddr/fsl/fsl_ddr.h
index ee6069d812..ab991a5bf4 100644
--- a/drivers/ddr/fsl/fsl_ddr.h
+++ b/drivers/ddr/fsl/fsl_ddr.h
@@ -227,8 +227,6 @@ unsigned int mclk_to_picos(struct fsl_ddr_controller *c, unsigned int mclk);
unsigned int get_memory_clk_period_ps(struct fsl_ddr_controller *c);
unsigned int picos_to_mclk(struct fsl_ddr_controller *c, unsigned int picos);
-void fsl_ddr_set_memctl_regs(struct fsl_ddr_controller *c, int step);
-
void erratum_a009942_check_cpo(void);
#endif
diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig
index 911cc0cb1e..08c8c84e8c 100644
--- a/drivers/mci/Kconfig
+++ b/drivers/mci/Kconfig
@@ -66,6 +66,10 @@ config MCI_BCM283X
bool "MCI support for BCM283X"
depends on ARCH_BCM283X
+config MCI_BCM283X_SDHOST
+ bool "BCM283X sdhost"
+ depends on ARCH_BCM283X
+
config MCI_DOVE
bool "Marvell Dove SDHCI"
depends on ARCH_DOVE
diff --git a/drivers/mci/Makefile b/drivers/mci/Makefile
index f6214c0cbb..25a1d073dc 100644
--- a/drivers/mci/Makefile
+++ b/drivers/mci/Makefile
@@ -1,6 +1,7 @@
obj-$(CONFIG_MCI) += mci-core.o
obj-$(CONFIG_MCI_ATMEL) += atmel_mci.o
obj-$(CONFIG_MCI_BCM283X) += mci-bcm2835.o
+obj-$(CONFIG_MCI_BCM283X_SDHOST) += bcm2835-sdhost.o
obj-$(CONFIG_MCI_DOVE) += dove-sdhci.o
obj-$(CONFIG_MCI_IMX) += imx.o
obj-$(CONFIG_MCI_IMX_ESDHC) += imx-esdhc.o
diff --git a/drivers/mci/bcm2835-sdhost.c b/drivers/mci/bcm2835-sdhost.c
new file mode 100644
index 0000000000..1d3a6c0969
--- /dev/null
+++ b/drivers/mci/bcm2835-sdhost.c
@@ -0,0 +1,638 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This code is ported from U-Boot by Lucas Stach <l.stach@pengutronix.de> and
+ * has the following contributors listed in the original license header:
+ * Alexander Graf <agraf@suse.de>
+ * Phil Elwell <phil@raspberrypi.org>
+ * Gellert Weisz
+ * Stephen Warren
+ * Oleksandr Tymoshenko
+ */
+
+#include <clock.h>
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/log2.h>
+#include <mci.h>
+
+#define SDCMD 0x00 /* Command to SD card - 16 R/W */
+#define SDARG 0x04 /* Argument to SD card - 32 R/W */
+#define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */
+#define SDCDIV 0x0c /* Start value for clock divider - 11 R/W */
+#define SDRSP0 0x10 /* SD card response (31:0) - 32 R */
+#define SDRSP1 0x14 /* SD card response (63:32) - 32 R */
+#define SDRSP2 0x18 /* SD card response (95:64) - 32 R */
+#define SDRSP3 0x1c /* SD card response (127:96) - 32 R */
+#define SDHSTS 0x20 /* SD host status - 11 R/W */
+#define SDVDD 0x30 /* SD card power control - 1 R/W */
+#define SDEDM 0x34 /* Emergency Debug Mode - 13 R/W */
+#define SDHCFG 0x38 /* Host configuration - 2 R/W */
+#define SDHBCT 0x3c /* Host byte count (debug) - 32 R/W */
+#define SDDATA 0x40 /* Data to/from SD card - 32 R/W */
+#define SDHBLC 0x50 /* Host block count (SDIO/SDHC) - 9 R/W */
+
+#define SDCMD_NEW_FLAG 0x8000
+#define SDCMD_FAIL_FLAG 0x4000
+#define SDCMD_BUSYWAIT 0x800
+#define SDCMD_NO_RESPONSE 0x400
+#define SDCMD_LONG_RESPONSE 0x200
+#define SDCMD_WRITE_CMD 0x80
+#define SDCMD_READ_CMD 0x40
+#define SDCMD_CMD_MASK 0x3f
+
+#define SDCDIV_MAX_CDIV 0x7ff
+
+#define SDHSTS_BUSY_IRPT 0x400
+#define SDHSTS_BLOCK_IRPT 0x200
+#define SDHSTS_SDIO_IRPT 0x100
+#define SDHSTS_REW_TIME_OUT 0x80
+#define SDHSTS_CMD_TIME_OUT 0x40
+#define SDHSTS_CRC16_ERROR 0x20
+#define SDHSTS_CRC7_ERROR 0x10
+#define SDHSTS_FIFO_ERROR 0x08
+#define SDHSTS_DATA_FLAG 0x01
+
+#define SDHSTS_CLEAR_MASK (SDHSTS_BUSY_IRPT | \
+ SDHSTS_BLOCK_IRPT | \
+ SDHSTS_SDIO_IRPT | \
+ SDHSTS_REW_TIME_OUT | \
+ SDHSTS_CMD_TIME_OUT | \
+ SDHSTS_CRC16_ERROR | \
+ SDHSTS_CRC7_ERROR | \
+ SDHSTS_FIFO_ERROR)
+
+#define SDHSTS_TRANSFER_ERROR_MASK (SDHSTS_CRC7_ERROR | \
+ SDHSTS_CRC16_ERROR | \
+ SDHSTS_REW_TIME_OUT | \
+ SDHSTS_FIFO_ERROR)
+
+#define SDHSTS_ERROR_MASK (SDHSTS_CMD_TIME_OUT | \
+ SDHSTS_TRANSFER_ERROR_MASK)
+
+#define SDHCFG_BUSY_IRPT_EN BIT(10)
+#define SDHCFG_BLOCK_IRPT_EN BIT(8)
+#define SDHCFG_SDIO_IRPT_EN BIT(5)
+#define SDHCFG_DATA_IRPT_EN BIT(4)
+#define SDHCFG_SLOW_CARD BIT(3)
+#define SDHCFG_WIDE_EXT_BUS BIT(2)
+#define SDHCFG_WIDE_INT_BUS BIT(1)
+#define SDHCFG_REL_CMD_LINE BIT(0)
+
+#define SDVDD_POWER_OFF 0
+#define SDVDD_POWER_ON 1
+
+#define SDEDM_FORCE_DATA_MODE BIT(19)
+#define SDEDM_CLOCK_PULSE BIT(20)
+#define SDEDM_BYPASS BIT(21)
+
+#define SDEDM_FIFO_FILL_SHIFT 4
+#define SDEDM_FIFO_FILL_MASK 0x1f
+static u32 edm_fifo_fill(u32 edm)
+{
+ return (edm >> SDEDM_FIFO_FILL_SHIFT) & SDEDM_FIFO_FILL_MASK;
+}
+
+#define SDEDM_WRITE_THRESHOLD_SHIFT 9
+#define SDEDM_READ_THRESHOLD_SHIFT 14
+#define SDEDM_THRESHOLD_MASK 0x1f
+
+#define SDEDM_FSM_MASK 0xf
+#define SDEDM_FSM_IDENTMODE 0x0
+#define SDEDM_FSM_DATAMODE 0x1
+#define SDEDM_FSM_READDATA 0x2
+#define SDEDM_FSM_WRITEDATA 0x3
+#define SDEDM_FSM_READWAIT 0x4
+#define SDEDM_FSM_READCRC 0x5
+#define SDEDM_FSM_WRITECRC 0x6
+#define SDEDM_FSM_WRITEWAIT1 0x7
+#define SDEDM_FSM_POWERDOWN 0x8
+#define SDEDM_FSM_POWERUP 0x9
+#define SDEDM_FSM_WRITESTART1 0xa
+#define SDEDM_FSM_WRITESTART2 0xb
+#define SDEDM_FSM_GENPULSES 0xc
+#define SDEDM_FSM_WRITEWAIT2 0xd
+#define SDEDM_FSM_STARTPOWDOWN 0xf
+
+#define SDDATA_FIFO_WORDS 16
+
+#define FIFO_READ_THRESHOLD 4
+#define FIFO_WRITE_THRESHOLD 4
+#define SDDATA_FIFO_PIO_BURST 8
+
+#define SDHST_TIMEOUT_MAX_USEC 100000
+
+struct bcm2835_host {
+ struct mci_host mci;
+ void __iomem *regs;
+ struct clk *clk;
+};
+
+static inline struct bcm2835_host *to_bcm2835_host(struct mci_host *mci)
+{
+ return container_of(mci, struct bcm2835_host, mci);
+}
+
+static int bcm2835_sdhost_init(struct mci_host *mci, struct device_d *dev)
+{
+ struct bcm2835_host *host = to_bcm2835_host(mci);
+ u32 temp;
+
+ writel(SDVDD_POWER_OFF, host->regs + SDVDD);
+ writel(0, host->regs + SDCMD);
+ writel(0, host->regs + SDARG);
+ /* Set timeout to a big enough value so we don't hit it */
+ writel(0xf00000, host->regs + SDTOUT);
+ writel(0, host->regs + SDCDIV);
+ /* Clear status register */
+ writel(SDHSTS_CLEAR_MASK, host->regs + SDHSTS);
+ writel(0, host->regs + SDHCFG);
+ writel(0, host->regs + SDHBCT);
+ writel(0, host->regs + SDHBLC);
+
+ /* Limit fifo usage due to silicon bug */
+ temp = readl(host->regs + SDEDM);
+ temp &= ~((SDEDM_THRESHOLD_MASK << SDEDM_READ_THRESHOLD_SHIFT) |
+ (SDEDM_THRESHOLD_MASK << SDEDM_WRITE_THRESHOLD_SHIFT));
+ temp |= (FIFO_READ_THRESHOLD << SDEDM_READ_THRESHOLD_SHIFT) |
+ (FIFO_WRITE_THRESHOLD << SDEDM_WRITE_THRESHOLD_SHIFT);
+ writel(temp, host->regs + SDEDM);
+ /* Wait for FIFO threshold to populate */
+ mdelay(20);
+ writel(SDVDD_POWER_ON, host->regs + SDVDD);
+ /* Wait for all components to go through power on cycle */
+ mdelay(20);
+ writel(0, host->regs + SDHCFG);
+ writel(0, host->regs + SDCDIV);
+
+ return 0;
+}
+
+static int bcm2835_wait_transfer_complete(struct bcm2835_host *host)
+{
+ uint64_t start = get_time_ns();
+
+ while (1) {
+ u32 edm, fsm;
+
+ edm = readl(host->regs + SDEDM);
+ fsm = edm & SDEDM_FSM_MASK;
+
+ if ((fsm == SDEDM_FSM_IDENTMODE) ||
+ (fsm == SDEDM_FSM_DATAMODE))
+ break;
+
+ if ((fsm == SDEDM_FSM_READWAIT) ||
+ (fsm == SDEDM_FSM_WRITESTART1) ||
+ (fsm == SDEDM_FSM_READDATA)) {
+ writel(edm | SDEDM_FORCE_DATA_MODE,
+ host->regs + SDEDM);
+ break;
+ }
+
+ /* Error out after 1 second */
+ if (is_timeout(start, 1 * SECOND)) {
+ dev_err(host->mci.hw_dev,
+ "wait_transfer_complete - still waiting 1s\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+}
+
+static int bcm2835_transfer_block_pio(struct bcm2835_host *host,
+ struct mci_data *data, unsigned int block,
+ bool is_read)
+{
+ u32 *buf = is_read ? (u32 *)data->dest : (u32 *)data->src;
+ int copy_words = data->blocksize / sizeof(u32);
+ uint64_t start = get_time_ns();
+
+ if (data->blocksize % sizeof(u32))
+ return -EINVAL;
+
+ buf += (block * data->blocksize / sizeof(u32));
+
+ /* Copy all contents from/to the FIFO as far as it reaches. */
+ while (copy_words) {
+ int fifo_words;
+ u32 edm;
+
+ if (is_timeout(start, 100 * MSECOND)) {
+ dev_err(host->mci.hw_dev,
+ "transfer_block_pio timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ edm = readl(host->regs + SDEDM);
+ if (is_read)
+ fifo_words = edm_fifo_fill(edm);
+ else
+ fifo_words = SDDATA_FIFO_WORDS - edm_fifo_fill(edm);
+
+ if (fifo_words > copy_words)
+ fifo_words = copy_words;
+
+ /* Copy current chunk to/from the FIFO */
+ while (fifo_words) {
+ if (is_read)
+ *(buf++) = readl(host->regs + SDDATA);
+ else
+ writel(*(buf++), host->regs + SDDATA);
+ fifo_words--;
+ copy_words--;
+ }
+ }
+
+ return 0;
+}
+
+static int bcm2835_transfer_pio(struct bcm2835_host *host,
+ struct mci_data *data)
+{
+ u32 sdhsts;
+ bool is_read = !!(data->flags & MMC_DATA_READ);
+ unsigned int block = 0;
+ int ret = 0;
+
+ while (block < data->blocks) {
+ ret = bcm2835_transfer_block_pio(host, data, block, is_read);
+ if (ret)
+ return ret;
+
+ sdhsts = readl(host->regs + SDHSTS);
+ if (sdhsts & (SDHSTS_CRC16_ERROR |
+ SDHSTS_CRC7_ERROR |
+ SDHSTS_FIFO_ERROR)) {
+ dev_err(host->mci.hw_dev,
+ "%s transfer error - HSTS %08x\n",
+ is_read ? "read" : "write", sdhsts);
+ ret = -EILSEQ;
+ } else if ((sdhsts & (SDHSTS_CMD_TIME_OUT |
+ SDHSTS_REW_TIME_OUT))) {
+ dev_err(host->mci.hw_dev,
+ "%s timeout error - HSTS %08x\n",
+ is_read ? "read" : "write", sdhsts);
+ ret = -ETIMEDOUT;
+ }
+ block++;
+ }
+
+ return ret;
+}
+
+static u32 bcm2835_read_wait_sdcmd(struct bcm2835_host *host)
+{
+ u32 value;
+ int ret;
+ int timeout_us = SDHST_TIMEOUT_MAX_USEC;
+
+ ret = readl_poll_timeout(host->regs + SDCMD, value,
+ !(value & SDCMD_NEW_FLAG), timeout_us);
+ if (ret == -ETIMEDOUT)
+ dev_err(host->mci.hw_dev, "%s: timeout (%d us)\n",
+ __func__, timeout_us);
+
+ return value;
+}
+
+static int bcm2835_send_command(struct bcm2835_host *host, struct mci_cmd *cmd,
+ struct mci_data *data)
+{
+ u32 sdcmd, sdhsts;
+
+ if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) {
+ dev_err(host->mci.hw_dev, "unsupported response type!\n");
+ return -EINVAL;
+ }
+
+ sdcmd = bcm2835_read_wait_sdcmd(host);
+ if (sdcmd & SDCMD_NEW_FLAG) {
+ dev_err(host->mci.hw_dev, "previous command never completed.\n");
+ return -EBUSY;
+ }
+
+ /* Clear any error flags */
+ sdhsts = readl(host->regs + SDHSTS);
+ if (sdhsts & SDHSTS_ERROR_MASK)
+ writel(sdhsts, host->regs + SDHSTS);
+
+ if (data) {
+ writel(data->blocksize, host->regs + SDHBCT);
+ writel(data->blocks, host->regs + SDHBLC);
+ }
+
+ writel(cmd->cmdarg, host->regs + SDARG);
+
+ sdcmd = cmd->cmdidx & SDCMD_CMD_MASK;
+
+ if (!(cmd->resp_type & MMC_RSP_PRESENT)) {
+ sdcmd |= SDCMD_NO_RESPONSE;
+ } else {
+ if (cmd->resp_type & MMC_RSP_136)
+ sdcmd |= SDCMD_LONG_RESPONSE;
+ if (cmd->resp_type & MMC_RSP_BUSY)
+ sdcmd |= SDCMD_BUSYWAIT;
+ }
+
+ if (data) {
+ if (data->flags & MMC_DATA_WRITE)
+ sdcmd |= SDCMD_WRITE_CMD;
+ if (data->flags & MMC_DATA_READ)
+ sdcmd |= SDCMD_READ_CMD;
+ }
+
+ writel(sdcmd | SDCMD_NEW_FLAG, host->regs + SDCMD);
+
+ return 0;
+}
+
+static int bcm2835_finish_command(struct bcm2835_host *host,
+ struct mci_cmd *cmd)
+{
+ u32 sdcmd;
+ int ret = 0;
+
+ sdcmd = bcm2835_read_wait_sdcmd(host);
+
+ /* Check for errors */
+ if (sdcmd & SDCMD_NEW_FLAG) {
+ dev_err(host->mci.hw_dev, "command never completed.\n");
+ return -EIO;
+ } else if (sdcmd & SDCMD_FAIL_FLAG) {
+ u32 sdhsts = readl(host->regs + SDHSTS);
+
+ /* Clear the errors */
+ writel(SDHSTS_ERROR_MASK, host->regs + SDHSTS);
+
+ if (!(sdhsts & SDHSTS_CRC7_ERROR) ||
+ (cmd->cmdidx != MMC_CMD_SEND_OP_COND)) {
+ if (sdhsts & SDHSTS_CMD_TIME_OUT) {
+ ret = -ETIMEDOUT;
+ } else {
+ dev_err(host->mci.hw_dev,
+ "unexpected command %d error\n",
+ cmd->cmdidx);
+ ret = -EILSEQ;
+ }
+
+ return ret;
+ }
+ }
+
+ if (cmd->resp_type & MMC_RSP_PRESENT) {
+ if (cmd->resp_type & MMC_RSP_136) {
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ cmd->response[3 - i] =
+ readl(host->regs + SDRSP0 + i * 4);
+ }
+ } else {
+ cmd->response[0] = readl(host->regs + SDRSP0);
+ }
+ }
+
+ return ret;
+}
+
+static int bcm2835_check_cmd_error(struct bcm2835_host *host, u32 intmask)
+{
+ int ret = -EINVAL;
+
+ if (!(intmask & SDHSTS_ERROR_MASK))
+ return 0;
+
+ dev_err(host->mci.hw_dev, "sdhost_busy_irq: intmask %08x\n", intmask);
+ if (intmask & SDHSTS_CRC7_ERROR) {
+ ret = -EILSEQ;
+ } else if (intmask & (SDHSTS_CRC16_ERROR |
+ SDHSTS_FIFO_ERROR)) {
+ ret = -EILSEQ;
+ } else if (intmask & (SDHSTS_REW_TIME_OUT | SDHSTS_CMD_TIME_OUT)) {
+ ret = -ETIMEDOUT;
+ }
+
+ return ret;
+}
+
+static int bcm2835_check_data_error(struct bcm2835_host *host, u32 intmask)
+{
+ int ret = 0;
+
+ if (intmask & (SDHSTS_CRC16_ERROR | SDHSTS_FIFO_ERROR))
+ ret = -EILSEQ;
+ if (intmask & SDHSTS_REW_TIME_OUT)
+ ret = -ETIMEDOUT;
+
+ if (ret)
+ dev_err(host->mci.hw_dev, "data error %d\n", ret);
+
+ return ret;
+}
+
+static int bcm2835_transmit(struct bcm2835_host *host, struct mci_cmd *cmd,
+ struct mci_data *data)
+{
+ u32 intmask = readl(host->regs + SDHSTS);
+ int ret;
+
+ /* Check for errors */
+ if (data) {
+ ret = bcm2835_check_data_error(host, intmask);
+ if (ret)
+ return ret;
+ }
+
+ ret = bcm2835_check_cmd_error(host, intmask);
+ if (ret)
+ return ret;
+
+ /* Handle wait for busy end */
+ if ((cmd->resp_type & MMC_RSP_BUSY) &&
+ (intmask & SDHSTS_BUSY_IRPT)) {
+ writel(SDHSTS_BUSY_IRPT, host->regs + SDHSTS);
+ bcm2835_finish_command(host, cmd);
+ }
+
+ /* Handle PIO data transfer */
+ if (data) {
+ ret = bcm2835_transfer_pio(host, data);
+ if (ret)
+ return ret;
+ /* Transfer successful: wait for command to complete for real */
+ ret = bcm2835_wait_transfer_complete(host);
+ }
+
+ return ret;
+}
+
+static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock)
+{
+ int div;
+
+ /* The SDCDIV register has 11 bits, and holds (div - 2). But
+ * in data mode the max is 50MHz without a minimum, and only
+ * the bottom 3 bits are used. Since the switch over is
+ * automatic (unless we have marked the card as slow...),
+ * chosen values have to make sense in both modes. Ident mode
+ * must be 100-400KHz, so can range check the requested
+ * clock. CMD15 must be used to return to data mode, so this
+ * can be monitored.
+ *
+ * clock 250MHz -> 0->125MHz, 1->83.3MHz, 2->62.5MHz, 3->50.0MHz
+ * 4->41.7MHz, 5->35.7MHz, 6->31.3MHz, 7->27.8MHz
+ *
+ * 623->400KHz/27.8MHz
+ * reset value (507)->491159/50MHz
+ *
+ * BUT, the 3-bit clock divisor in data mode is too small if
+ * the core clock is higher than 250MHz, so instead use the
+ * SLOW_CARD configuration bit to force the use of the ident
+ * clock divisor at all times.
+ */
+
+ if (clock < 100000) {
+ /* Can't stop the clock, but make it as slow as possible
+ * to show willing
+ */
+ writel(SDCDIV_MAX_CDIV, host->regs + SDCDIV);
+ return;
+ }
+
+ div = host->mci.f_max / clock;
+ if (div < 2)
+ div = 2;
+ if ((host->mci.f_max / div) > clock)
+ div++;
+ div -= 2;
+
+ if (div > SDCDIV_MAX_CDIV)
+ div = SDCDIV_MAX_CDIV;
+
+ clock = host->mci.f_max / (div + 2);
+
+ writel(div, host->regs + SDCDIV);
+
+ /* Set the timeout to 500ms */
+ writel(clock / 2, host->regs + SDTOUT);
+}
+
+static int bcm2835_send_cmd(struct mci_host *mci, struct mci_cmd *cmd,
+ struct mci_data *data)
+{
+ struct bcm2835_host *host = to_bcm2835_host(mci);
+ u32 edm, fsm;
+ int ret = 0;
+
+ if (data && !is_power_of_2(data->blocksize)) {
+ dev_err(mci->hw_dev, "unsupported block size (%d bytes)\n",
+ data->blocksize);
+ return -EINVAL;
+ }
+
+ edm = readl(host->regs + SDEDM);
+ fsm = edm & SDEDM_FSM_MASK;
+
+ if ((fsm != SDEDM_FSM_IDENTMODE) &&
+ (fsm != SDEDM_FSM_DATAMODE) &&
+ (cmd->cmdidx != MMC_CMD_STOP_TRANSMISSION)) {
+ dev_err(mci->hw_dev,
+ "previous command (%d) not complete (EDM %08x)\n",
+ readl(host->regs + SDCMD) & SDCMD_CMD_MASK, edm);
+
+ return -EILSEQ;
+ }
+
+ ret = bcm2835_send_command(host, cmd, data);
+ if (ret)
+ return ret;
+
+ if (!(cmd->resp_type & MMC_RSP_BUSY)) {
+ ret = bcm2835_finish_command(host, cmd);
+ if (ret)
+ return ret;
+ }
+
+ /* Wait for completion of busy signal or data transfer */
+ if ((cmd->resp_type & MMC_RSP_BUSY) || data)
+ ret = bcm2835_transmit(host, cmd, data);
+
+ return ret;
+}
+
+static void bcm2835_set_ios(struct mci_host *mci, struct mci_ios *ios)
+{
+ struct bcm2835_host *host = to_bcm2835_host(mci);
+ u32 hcfg = SDHCFG_WIDE_INT_BUS | SDHCFG_SLOW_CARD;
+
+ if (ios->clock)
+ bcm2835_set_clock(host, ios->clock);
+
+ /* set bus width */
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ hcfg |= SDHCFG_WIDE_EXT_BUS;
+
+ writel(hcfg, host->regs + SDHCFG);
+}
+
+static int bcm2835_sdhost_detect(struct device_d *dev)
+{
+ struct bcm2835_host *host = dev->priv;
+
+ return mci_detect_card(&host->mci);
+}
+
+static int bcm2835_sdhost_probe(struct device_d *dev)
+{
+ struct bcm2835_host *host;
+ struct resource *iores;
+ struct mci_host *mci;
+
+ host = xzalloc(sizeof(*host));
+ mci = &host->mci;
+
+ host->clk = clk_get(dev, NULL);
+ if (IS_ERR(host->clk))
+ return PTR_ERR(host->clk);
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores)) {
+ dev_err(dev, "could not get iomem region\n");
+ return PTR_ERR(iores);
+ }
+ host->regs = IOMEM(iores->start);
+
+ mci->hw_dev = dev;
+ mci->f_max = clk_get_rate(host->clk);
+ mci->f_min = mci->f_max / SDCDIV_MAX_CDIV;
+ mci->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mci->host_caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED_52MHZ |
+ MMC_CAP_SD_HIGHSPEED;
+
+ mci->init = bcm2835_sdhost_init;
+ mci->set_ios = bcm2835_set_ios;
+ mci->send_cmd = bcm2835_send_cmd;
+
+ dev->priv = host;
+ dev->detect = bcm2835_sdhost_detect,
+
+ mci_of_parse(mci);
+
+ return mci_register(mci);
+}
+
+static __maybe_unused struct of_device_id bcm2835_sdhost_compatible[] = {
+ { .compatible = "brcm,bcm2835-sdhost" },
+ { /* sentinel */ }
+};
+
+static struct driver_d bcm2835_sdhost_driver = {
+ .name = "bcm2835-sdhost",
+ .probe = bcm2835_sdhost_probe,
+ .of_compatible = DRV_OF_COMPAT(bcm2835_sdhost_compatible),
+};
+device_platform_driver(bcm2835_sdhost_driver);
diff --git a/drivers/mci/imx-esdhc-pbl.c b/drivers/mci/imx-esdhc-pbl.c
index f7f8c3348d..0251757a2a 100644
--- a/drivers/mci/imx-esdhc-pbl.c
+++ b/drivers/mci/imx-esdhc-pbl.c
@@ -16,6 +16,7 @@
#include <mci.h>
#include <linux/sizes.h>
#include <asm-generic/sections.h>
+#include <asm/cache.h>
#include <mach/xload.h>
#ifdef CONFIG_ARCH_IMX
#include <mach/atf.h>
@@ -445,7 +446,7 @@ int ls1046a_esdhc_start_image(unsigned long r0, unsigned long r1, unsigned long
*/
val = esdhc_read32(&esdhc, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET);
val &= ~0x0000fff0;
- val |= (2 << 8) | (6 << 4);
+ val |= (8 << 8) | (3 << 4);
esdhc_write32(&esdhc, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET, val);
esdhc_write32(&esdhc, ESDHC_DMA_SYSCTL, ESDHC_SYSCTL_DMA_SNOOP);
@@ -457,6 +458,8 @@ int ls1046a_esdhc_start_image(unsigned long r0, unsigned long r1, unsigned long
return ret;
}
+ icache_invalidate();
+
printf("Starting barebox\n");
barebox(r0, r1, r2);
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index 9c3925bde1..25db10a9b2 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -23,6 +23,7 @@ config MTD_M25P80
tristate "Support most SPI Flash chips (AT26DF, M25P, W25X, ...)"
depends on SPI
select MTD_SPI_NOR
+ select SPI_MEM
help
This enables access to most modern SPI flash chips, used for
program and data storage. Series supported include Atmel AT26DF,
diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c
index 8a67792125..09a8714247 100644
--- a/drivers/mtd/devices/m25p80.c
+++ b/drivers/mtd/devices/m25p80.c
@@ -27,12 +27,13 @@
#include <clock.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/spi-nor.h>
+#include <linux/spi/spi-mem.h>
#include <linux/mod_devicetable.h>
#define MAX_CMD_SIZE 6
struct m25p {
- struct spi_device *spi;
+ struct spi_mem *spimem;
struct spi_nor spi_nor;
struct mtd_info mtd;
u8 command[MAX_CMD_SIZE];
@@ -41,71 +42,60 @@ struct m25p {
static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
{
struct m25p *flash = nor->priv;
- struct spi_device *spi = flash->spi;
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(code, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(len, val, 1));
int ret;
- ret = spi_write_then_read(spi, &code, 1, val, len);
+ ret = spi_mem_exec_op(flash->spimem, &op);
if (ret < 0)
- dev_err(&spi->dev, "error %d reading %x\n", ret, code);
+ dev_err(&flash->spimem->spi->dev, "error %d reading %x\n", ret,
+ code);
return ret;
}
-static void m25p_addr2cmd(struct spi_nor *nor, unsigned int addr, u8 *cmd)
-{
- /* opcode is in cmd[0] */
- cmd[1] = addr >> (nor->addr_width * 8 - 8);
- cmd[2] = addr >> (nor->addr_width * 8 - 16);
- cmd[3] = addr >> (nor->addr_width * 8 - 24);
- cmd[4] = addr >> (nor->addr_width * 8 - 32);
-}
-
-static int m25p_cmdsz(struct spi_nor *nor)
-{
- return 1 + nor->addr_width;
-}
-
-static int m25p80_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len,
- int wr_en)
+static int m25p80_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
{
struct m25p *flash = nor->priv;
- struct spi_device *spi = flash->spi;
-
- flash->command[0] = opcode;
- if (buf)
- memcpy(&flash->command[1], buf, len);
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(len, buf, 1));
- return spi_write(spi, flash->command, len + 1);
+ return spi_mem_exec_op(flash->spimem, &op);
}
static void m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct m25p *flash = nor->priv;
- struct spi_device *spi = flash->spi;
- struct spi_transfer t[2] = {};
- struct spi_message m;
- int cmd_sz = m25p_cmdsz(nor);
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 1),
+ SPI_MEM_OP_ADDR(nor->addr_width, to, 1),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(len, buf, 1));
+ int ret;
- spi_message_init(&m);
+ op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->write_proto);
+ op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->write_proto);
+ op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto);
if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
- cmd_sz = 1;
-
- flash->command[0] = nor->program_opcode;
- m25p_addr2cmd(nor, to, flash->command);
+ op.addr.nbytes = 0;
- t[0].tx_buf = flash->command;
- t[0].len = cmd_sz;
- spi_message_add_tail(&t[0], &m);
+ ret = spi_mem_adjust_op_size(flash->spimem, &op);
+ if (ret)
+ return;
- t[1].tx_buf = buf;
- t[1].len = len;
- spi_message_add_tail(&t[1], &m);
+ op.data.nbytes = len < op.data.nbytes ? len : op.data.nbytes;
- spi_sync(spi, &m);
+ ret = spi_mem_exec_op(flash->spimem, &op);
+ if (ret)
+ return;
- *retlen += m.actual_length - cmd_sz;
+ *retlen = op.data.nbytes;
}
/*
@@ -116,46 +106,35 @@ static int m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct m25p *flash = nor->priv;
- struct spi_device *spi = flash->spi;
- struct spi_transfer t[2];
- struct spi_message m;
- unsigned int dummy = nor->read_dummy;
-
- /* convert the dummy cycles to the number of bytes */
- dummy /= 8;
-
- spi_message_init(&m);
- memset(t, 0, (sizeof t));
-
- flash->command[0] = nor->read_opcode;
- m25p_addr2cmd(nor, from, flash->command);
-
- t[0].tx_buf = flash->command;
- t[0].len = m25p_cmdsz(nor) + dummy;
- spi_message_add_tail(&t[0], &m);
-
- t[1].rx_buf = buf;
- t[1].len = len;
- spi_message_add_tail(&t[1], &m);
-
- spi_sync(spi, &m);
-
- *retlen = m.actual_length - m25p_cmdsz(nor) - dummy;
- return 0;
-}
-
-static int m25p80_erase(struct spi_nor *nor, loff_t offset)
-{
- struct m25p *flash = nor->priv;
-
- dev_dbg(nor->dev, "%dKiB at 0x%08x\n",
- flash->mtd.erasesize / 1024, (u32)offset);
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1),
+ SPI_MEM_OP_ADDR(nor->addr_width, from, 1),
+ SPI_MEM_OP_DUMMY(nor->read_dummy, 1),
+ SPI_MEM_OP_DATA_IN(len, buf, 1));
+ size_t remaining = len;
+ int ret;
- /* Set up command buffer. */
- flash->command[0] = nor->erase_opcode;
- m25p_addr2cmd(nor, offset, flash->command);
+ op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto);
+ op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->read_proto);
+ op.dummy.buswidth = op.addr.buswidth;
+ op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto);
+
+ op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8;
+
+ while (remaining) {
+ op.data.nbytes = remaining < UINT_MAX ? remaining : UINT_MAX;
+ ret = spi_mem_adjust_op_size(flash->spimem, &op);
+ if (ret)
+ return ret;
+ ret = spi_mem_exec_op(flash->spimem, &op);
+ if (ret)
+ return ret;
+ op.addr.val += op.data.nbytes;
+ remaining -= op.data.nbytes;
+ op.data.buf.in += op.data.nbytes;
+ }
- spi_write(flash->spi, flash->command, m25p_cmdsz(nor));
+ *retlen = len;
return 0;
}
@@ -229,10 +208,15 @@ static const struct platform_device_id m25p_ids[] = {
static int m25p_probe(struct device_d *dev)
{
struct spi_device *spi = (struct spi_device *)dev->type_data;
+ struct spi_mem *spimem = spi->mem;
struct flash_platform_data *data;
struct m25p *flash;
struct spi_nor *nor;
- enum read_mode mode = SPI_NOR_NORMAL;
+ struct spi_nor_hwcaps hwcaps = {
+ .mask = SNOR_HWCAPS_READ |
+ SNOR_HWCAPS_READ_FAST |
+ SNOR_HWCAPS_PP,
+ };
const char *flash_name = NULL;
int device_id;
bool use_large_blocks;
@@ -247,17 +231,21 @@ static int m25p_probe(struct device_d *dev)
/* install the hooks */
nor->read = m25p80_read;
nor->write = m25p80_write;
- nor->erase = m25p80_erase;
nor->write_reg = m25p80_write_reg;
nor->read_reg = m25p80_read_reg;
- nor->dev = dev;
+ nor->dev = &spimem->spi->dev;
nor->mtd = &flash->mtd;
nor->priv = flash;
flash->mtd.priv = nor;
flash->mtd.parent = &spi->dev;
- flash->spi = spi;
+ flash->spimem = spimem;
+
+ if (spi->mode & SPI_RX_QUAD)
+ hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
+ else if (spi->mode & SPI_RX_DUAL)
+ hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
dev->priv = (void *)flash;
@@ -276,7 +264,7 @@ static int m25p_probe(struct device_d *dev)
use_large_blocks = of_property_read_bool(dev->device_node,
"use-large-blocks");
- ret = spi_nor_scan(nor, flash_name, mode, use_large_blocks);
+ ret = spi_nor_scan(nor, flash_name, &hwcaps, use_large_blocks);
if (ret)
return ret;
diff --git a/drivers/mtd/spi-nor/cadence-quadspi.c b/drivers/mtd/spi-nor/cadence-quadspi.c
index ed5377bd4b..11e4d236dd 100644
--- a/drivers/mtd/spi-nor/cadence-quadspi.c
+++ b/drivers/mtd/spi-nor/cadence-quadspi.c
@@ -45,6 +45,9 @@ struct cqspi_flash_pdata {
unsigned int tsd2d_ns;
unsigned int tchsh_ns;
unsigned int tslch_ns;
+ u8 inst_width;
+ u8 addr_width;
+ u8 data_width;
};
struct cqspi_st {
@@ -287,9 +290,10 @@ static unsigned int cqspi_calc_rdreg(struct spi_nor *nor, u8 opcode)
f_pdata = &cqspi->f_pdata[cqspi->current_cs];
- if (nor->flash_read == SPI_NOR_QUAD)
- rdreg |= (CQSPI_INST_TYPE_QUAD
- << CQSPI_REG_RD_INSTR_TYPE_DATA_LSB);
+ rdreg |= f_pdata->inst_width << CQSPI_REG_RD_INSTR_TYPE_INSTR_LSB;
+ rdreg |= f_pdata->addr_width << CQSPI_REG_RD_INSTR_TYPE_ADDR_LSB;
+ rdreg |= f_pdata->data_width << CQSPI_REG_RD_INSTR_TYPE_DATA_LSB;
+
return rdreg;
}
@@ -427,6 +431,7 @@ static int cqspi_command_write_addr(struct spi_nor *nor,
static int cqspi_indirect_read_setup(struct spi_nor *nor,
unsigned int from_addr)
{
+ struct cqspi_flash_pdata *f_pdata;
struct cqspi_st *cqspi = nor->priv;
unsigned int ahb_base = (unsigned int) cqspi->ahb_base;
void __iomem *reg_base = cqspi->iobase;
@@ -437,6 +442,7 @@ static int cqspi_indirect_read_setup(struct spi_nor *nor,
writel(ahb_base & CQSPI_INDIRECTTRIGGER_ADDR_MASK,
reg_base + CQSPI_REG_INDIRECTTRIGGER);
writel(from_addr, reg_base + CQSPI_REG_INDIRECTRDSTARTADDR);
+ f_pdata = &cqspi->f_pdata[cqspi->current_cs];
reg = nor->read_opcode << CQSPI_REG_RD_INSTR_OPCODE_LSB;
reg |= cqspi_calc_rdreg(nor, nor->read_opcode);
@@ -444,14 +450,10 @@ static int cqspi_indirect_read_setup(struct spi_nor *nor,
/* Setup dummy clock cycles */
dummy_bytes = nor->read_dummy / 8;
- if (dummy_bytes) {
- struct cqspi_flash_pdata *f_pdata;
-
- f_pdata = &cqspi->f_pdata[cqspi->current_cs];
-
- if (dummy_bytes > CQSPI_DUMMY_BYTES_MAX)
- dummy_bytes = CQSPI_DUMMY_BYTES_MAX;
+ if (dummy_bytes > CQSPI_DUMMY_BYTES_MAX)
+ dummy_bytes = CQSPI_DUMMY_BYTES_MAX;
+ if (dummy_bytes) {
reg |= (1 << CQSPI_REG_RD_INSTR_MODE_EN_LSB);
/* Set mode bits high to ensure chip doesn't enter XIP */
writel(0xFF, reg_base + CQSPI_REG_MODE_BIT);
@@ -459,7 +461,8 @@ static int cqspi_indirect_read_setup(struct spi_nor *nor,
/* Convert to clock cycles. */
dummy_clk = dummy_bytes * CQSPI_DUMMY_CLKS_PER_BYTE;
/* Need to subtract the mode byte (8 clocks). */
- dummy_clk -= CQSPI_DUMMY_CLKS_PER_BYTE;
+ if (f_pdata->inst_width != CQSPI_INST_TYPE_QUAD)
+ dummy_clk -= CQSPI_DUMMY_CLKS_PER_BYTE;
if (dummy_clk)
reg |= (dummy_clk & CQSPI_REG_RD_INSTR_DUMMY_MASK)
@@ -676,51 +679,53 @@ failwr:
return ret;
}
-static void cqspi_write(struct spi_nor *nor, loff_t to,
- size_t len, size_t *retlen, const u_char *buf)
+static void cqspi_controller_enable(struct cqspi_st *cqspi)
{
- int ret;
-
- if (!IS_ENABLED(CONFIG_MTD_WRITE))
- return;
+ void __iomem *reg_base = cqspi->iobase;
+ unsigned int reg;
- ret = cqspi_indirect_write_setup(nor, to);
- if (ret == 0) {
- ret = cqspi_indirect_write_execute(nor, buf, len);
- if (ret == 0)
- *retlen += len;
- }
+ reg = readl(reg_base + CQSPI_REG_CONFIG);
+ reg |= CQSPI_REG_CONFIG_ENABLE_MASK;
+ writel(reg, reg_base + CQSPI_REG_CONFIG);
}
-static int cqspi_read(struct spi_nor *nor, loff_t from,
- size_t len, size_t *retlen, u_char *buf)
+static void cqspi_controller_disable(struct cqspi_st *cqspi)
{
- int ret;
+ void __iomem *reg_base = cqspi->iobase;
+ unsigned int reg;
- ret = cqspi_indirect_read_setup(nor, from);
- if (ret == 0) {
- ret = cqspi_indirect_read_execute(nor, buf, len);
- if (ret == 0)
- *retlen += len;
- }
- return ret;
+ reg = readl(reg_base + CQSPI_REG_CONFIG);
+ reg &= ~CQSPI_REG_CONFIG_ENABLE_MASK;
+ writel(reg, reg_base + CQSPI_REG_CONFIG);
}
-static int cqspi_erase(struct spi_nor *nor, loff_t offs)
+static void cqspi_chipselect(struct cqspi_st *cqspi,
+ unsigned int chip_select,
+ unsigned int decoder_enable)
{
- int ret;
+ void __iomem *reg_base = cqspi->iobase;
+ unsigned int reg;
- /* Send write enable, then erase commands. */
- ret = nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0, 0);
- if (ret)
- return ret;
+ reg = readl(reg_base + CQSPI_REG_CONFIG);
+ if (decoder_enable) {
+ reg |= CQSPI_REG_CONFIG_DECODE_MASK;
+ } else {
+ reg &= ~CQSPI_REG_CONFIG_DECODE_MASK;
- /* Set up command buffer. */
- ret = cqspi_command_write_addr(nor, nor->erase_opcode, offs);
- if (ret)
- return ret;
+ /* Convert CS if without decoder.
+ * CS0 to 4b'1110
+ * CS1 to 4b'1101
+ * CS2 to 4b'1011
+ * CS3 to 4b'0111
+ */
+ chip_select = 0xF & ~(1 << chip_select);
+ }
- return 0;
+ reg &= ~(CQSPI_REG_CONFIG_CHIPSELECT_MASK
+ << CQSPI_REG_CONFIG_CHIPSELECT_LSB);
+ reg |= (chip_select & CQSPI_REG_CONFIG_CHIPSELECT_MASK)
+ << CQSPI_REG_CONFIG_CHIPSELECT_LSB;
+ writel(reg, reg_base + CQSPI_REG_CONFIG);
}
static unsigned int calculate_ticks_for_ns(unsigned int ref_clk_hz,
@@ -834,55 +839,6 @@ static void cqspi_readdata_capture(struct cqspi_st *cqspi,
writel(reg, reg_base + CQSPI_REG_READCAPTURE);
}
-static void cqspi_chipselect(struct cqspi_st *cqspi,
- unsigned int chip_select,
- unsigned int decoder_enable)
-{
- void __iomem *reg_base = cqspi->iobase;
- unsigned int reg;
-
- reg = readl(reg_base + CQSPI_REG_CONFIG);
- if (decoder_enable) {
- reg |= CQSPI_REG_CONFIG_DECODE_MASK;
- } else {
- reg &= ~CQSPI_REG_CONFIG_DECODE_MASK;
-
- /* Convert CS if without decoder.
- * CS0 to 4b'1110
- * CS1 to 4b'1101
- * CS2 to 4b'1011
- * CS3 to 4b'0111
- */
- chip_select = 0xF & ~(1 << chip_select);
- }
-
- reg &= ~(CQSPI_REG_CONFIG_CHIPSELECT_MASK
- << CQSPI_REG_CONFIG_CHIPSELECT_LSB);
- reg |= (chip_select & CQSPI_REG_CONFIG_CHIPSELECT_MASK)
- << CQSPI_REG_CONFIG_CHIPSELECT_LSB;
- writel(reg, reg_base + CQSPI_REG_CONFIG);
-}
-
-static void cqspi_controller_enable(struct cqspi_st *cqspi)
-{
- void __iomem *reg_base = cqspi->iobase;
- unsigned int reg;
-
- reg = readl(reg_base + CQSPI_REG_CONFIG);
- reg |= CQSPI_REG_CONFIG_ENABLE_MASK;
- writel(reg, reg_base + CQSPI_REG_CONFIG);
-}
-
-static void cqspi_controller_disable(struct cqspi_st *cqspi)
-{
- void __iomem *reg_base = cqspi->iobase;
- unsigned int reg;
-
- reg = readl(reg_base + CQSPI_REG_CONFIG);
- reg &= ~CQSPI_REG_CONFIG_ENABLE_MASK;
- writel(reg, reg_base + CQSPI_REG_CONFIG);
-}
-
static void cqspi_switch_cs(struct cqspi_st *cqspi, unsigned int cs)
{
unsigned int reg;
@@ -908,7 +864,7 @@ static void cqspi_switch_cs(struct cqspi_st *cqspi, unsigned int cs)
cqspi_controller_enable(cqspi);
}
-static int cqspi_prep(struct spi_nor *nor, enum spi_nor_ops ops)
+static int cqspi_configure(struct spi_nor *nor)
{
struct cqspi_st *cqspi = nor->priv;
int cs = cqspi_find_chipselect(nor);
@@ -936,27 +892,119 @@ static int cqspi_prep(struct spi_nor *nor, enum spi_nor_ops ops)
return 0;
}
+static int cqspi_set_protocol(struct spi_nor *nor, const int read)
+{
+ struct cqspi_st *cqspi = nor->priv;
+ struct cqspi_flash_pdata *f_pdata;
+
+ f_pdata = &cqspi->f_pdata[cqspi->current_cs];
+
+ f_pdata->inst_width = CQSPI_INST_TYPE_SINGLE;
+ f_pdata->addr_width = CQSPI_INST_TYPE_SINGLE;
+ f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
+
+ if (read) {
+ switch (nor->read_proto) {
+ case SNOR_PROTO_1_1_1:
+ f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
+ break;
+ case SNOR_PROTO_1_1_2:
+ f_pdata->data_width = CQSPI_INST_TYPE_DUAL;
+ break;
+ case SNOR_PROTO_1_1_4:
+ f_pdata->data_width = CQSPI_INST_TYPE_QUAD;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ cqspi_configure(nor);
+
+ return 0;
+}
+
+static void cqspi_write(struct spi_nor *nor, loff_t to,
+ size_t len, size_t *retlen, const u_char *buf)
+{
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_MTD_WRITE))
+ return;
+
+ ret = cqspi_set_protocol(nor, 0);
+ if (ret)
+ return;
+
+ ret = cqspi_indirect_write_setup(nor, to);
+ if (ret == 0) {
+ ret = cqspi_indirect_write_execute(nor, buf, len);
+ if (ret == 0)
+ *retlen += len;
+ }
+}
+
+static int cqspi_read(struct spi_nor *nor, loff_t from,
+ size_t len, size_t *retlen, u_char *buf)
+{
+ int ret;
+
+ ret = cqspi_set_protocol(nor, 1);
+ if (ret)
+ return ret;
+
+ ret = cqspi_indirect_read_setup(nor, from);
+ if (ret == 0) {
+ ret = cqspi_indirect_read_execute(nor, buf, len);
+ if (ret == 0)
+ *retlen += len;
+ }
+ return ret;
+}
+
+static int cqspi_erase(struct spi_nor *nor, loff_t offs)
+{
+ int ret;
+
+ ret = cqspi_set_protocol(nor, 0);
+ if (ret)
+ return ret;
+
+ /* Send write enable, then erase commands. */
+ ret = nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0);
+ if (ret)
+ return ret;
+
+ /* Set up command buffer. */
+ ret = cqspi_command_write_addr(nor, nor->erase_opcode, offs);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
static int cqspi_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
{
int ret;
- cqspi_prep(nor, SPI_NOR_OPS_READ);
+ ret = cqspi_set_protocol(nor, 0);
+ if (!ret)
+ ret = cqspi_command_read(nor, &opcode, 1, buf, len);
- ret = cqspi_command_read(nor, &opcode, 1, buf, len);
return ret;
}
-static int cqspi_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len,
- int write_enable)
+static int cqspi_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
{
int ret = 0;
if (!IS_ENABLED(CONFIG_MTD_WRITE))
return -ENOTSUPP;
- cqspi_prep(nor, SPI_NOR_OPS_WRITE);
+ ret = cqspi_set_protocol(nor, 0);
+ if (!ret)
+ ret = cqspi_command_write(nor, opcode, buf, len);
- ret = cqspi_command_write(nor, opcode, buf, len);
return ret;
}
@@ -1034,6 +1082,13 @@ static int cqspi_setup_flash(struct device_d *dev,
struct cqspi_flash_pdata *f_pdata,
struct device_node *np)
{
+ const struct spi_nor_hwcaps hwcaps = {
+ .mask = SNOR_HWCAPS_READ |
+ SNOR_HWCAPS_READ_FAST |
+ SNOR_HWCAPS_READ_1_1_2 |
+ SNOR_HWCAPS_READ_1_1_4 |
+ SNOR_HWCAPS_PP,
+ };
struct cqspi_st *cqspi = dev->priv;
struct mtd_info *mtd;
struct spi_nor *nor;
@@ -1075,7 +1130,7 @@ static int cqspi_setup_flash(struct device_d *dev,
nor->write = cqspi_write;
nor->erase = cqspi_erase;
- ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD, false);
+ ret = spi_nor_scan(nor, NULL, &hwcaps, false);
if (ret)
goto probe_failed;
diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 85b55c6982..43bd402f9f 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -25,6 +25,7 @@
#include <spi/flash.h>
#define SPI_NOR_MAX_ID_LEN 6
+#define SPI_NOR_MAX_ADDR_WIDTH 4
/*
* For everything but full-chip erase; probably could be much smaller, but kept
@@ -57,14 +58,85 @@ struct flash_info {
u16 addr_width;
u16 flags;
-#define SECT_4K 0x01 /* SPINOR_OP_BE_4K works uniformly */
-#define SPI_NOR_NO_ERASE 0x02 /* No erase command needed */
-#define SST_WRITE 0x04 /* use SST byte programming */
-#define SPI_NOR_NO_FR 0x08 /* Can't do fastread */
-#define SECT_4K_PMC 0x10 /* SPINOR_OP_BE_4K_PMC works uniformly */
-#define SPI_NOR_DUAL_READ 0x20 /* Flash supports Dual Read */
-#define SPI_NOR_QUAD_READ 0x40 /* Flash supports Quad Read */
-#define USE_FSR 0x80 /* use flag status register */
+#define SECT_4K BIT(0) /* SPINOR_OP_BE_4K works uniformly */
+#define SPI_NOR_NO_ERASE BIT(1) /* No erase command needed */
+#define SST_WRITE BIT(2) /* use SST byte programming */
+#define SPI_NOR_NO_FR BIT(3) /* Can't do fastread */
+#define SECT_4K_PMC BIT(4) /* SPINOR_OP_BE_4K_PMC works uniformly */
+#define SPI_NOR_DUAL_READ BIT(5) /* Flash supports Dual Read */
+#define SPI_NOR_QUAD_READ BIT(6) /* Flash supports Quad Read */
+#define USE_FSR BIT(7) /* use flag status register */
+#define SPI_NOR_HAS_LOCK BIT(8) /* Flash supports lock/unlock via SR */
+#define SPI_NOR_HAS_TB BIT(9) /*
+ * Flash SR has Top/Bottom (TB) protect
+ * bit. Must be used with
+ * SPI_NOR_HAS_LOCK.
+ */
+#define SPI_S3AN BIT(10) /*
+ * Xilinx Spartan 3AN In-System Flash
+ * (MFR cannot be used for probing
+ * because it has the same value as
+ * ATMEL flashes)
+ */
+#define SPI_NOR_4B_OPCODES BIT(11) /*
+ * Use dedicated 4byte address op codes
+ * to support memory size above 128Mib.
+ */
+#define NO_CHIP_ERASE BIT(12) /* Chip does not support chip erase */
+#define SPI_NOR_SKIP_SFDP BIT(13) /* Skip parsing of SFDP tables */
+#define USE_CLSR BIT(14) /* use CLSR command */
+#define SPI_NOR_OCTAL_READ BIT(15) /* Flash supports Octal Read */
+};
+
+enum spi_nor_read_command_index {
+ SNOR_CMD_READ,
+ SNOR_CMD_READ_FAST,
+
+ /* Dual SPI */
+ SNOR_CMD_READ_1_1_2,
+ SNOR_CMD_READ_1_2_2,
+ SNOR_CMD_READ_2_2_2,
+
+ /* Quad SPI */
+ SNOR_CMD_READ_1_1_4,
+ SNOR_CMD_READ_1_4_4,
+ SNOR_CMD_READ_4_4_4,
+
+ SNOR_CMD_READ_MAX
+};
+
+struct spi_nor_read_command {
+ u8 num_mode_clocks;
+ u8 num_wait_states;
+ u8 opcode;
+ enum spi_nor_protocol proto;
+};
+
+struct spi_nor_pp_command {
+ u8 opcode;
+ enum spi_nor_protocol proto;
+};
+
+enum spi_nor_pp_command_index {
+ SNOR_CMD_PP,
+
+ /* Quad SPI */
+ SNOR_CMD_PP_1_1_4,
+ SNOR_CMD_PP_1_4_4,
+ SNOR_CMD_PP_4_4_4,
+
+ SNOR_CMD_PP_MAX
+};
+
+struct spi_nor_flash_parameter {
+ u64 size;
+ u32 page_size;
+
+ struct spi_nor_hwcaps hwcaps;
+ struct spi_nor_read_command reads[SNOR_CMD_READ_MAX];
+ struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX];
+
+ int (*quad_enable)(struct spi_nor *nor);
};
#define JEDEC_MFR(info) ((info)->id[0])
@@ -129,31 +201,13 @@ static int read_cr(struct spi_nor *nor)
}
/*
- * Dummy Cycle calculation for different type of read.
- * It can be used to support more commands with
- * different dummy cycle requirements.
- */
-static inline int spi_nor_read_dummy_cycles(struct spi_nor *nor)
-{
- switch (nor->flash_read) {
- case SPI_NOR_FAST:
- case SPI_NOR_DUAL:
- case SPI_NOR_QUAD:
- return 8;
- case SPI_NOR_NORMAL:
- return 0;
- }
- return 0;
-}
-
-/*
* Write status register 1 byte
* Returns negative if error occurred.
*/
static inline int write_sr(struct spi_nor *nor, u8 val)
{
nor->cmd_buf[0] = val;
- return nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1, 0);
+ return nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1);
}
/*
@@ -162,7 +216,7 @@ static inline int write_sr(struct spi_nor *nor, u8 val)
*/
static inline int write_enable(struct spi_nor *nor)
{
- return nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0, 0);
+ return nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0);
}
/*
@@ -170,7 +224,7 @@ static inline int write_enable(struct spi_nor *nor)
*/
static inline int write_disable(struct spi_nor *nor)
{
- return nor->write_reg(nor, SPINOR_OP_WRDI, NULL, 0, 0);
+ return nor->write_reg(nor, SPINOR_OP_WRDI, NULL, 0);
}
static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
@@ -178,6 +232,81 @@ static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
return mtd->priv;
}
+static u8 spi_nor_convert_opcode(u8 opcode, const u8 table[][2], size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ if (table[i][0] == opcode)
+ return table[i][1];
+
+ /* No conversion found, keep input op code. */
+ return opcode;
+}
+
+static u8 spi_nor_convert_3to4_read(u8 opcode)
+{
+ static const u8 spi_nor_3to4_read[][2] = {
+ { SPINOR_OP_READ, SPINOR_OP_READ_4B },
+ { SPINOR_OP_READ_FAST, SPINOR_OP_READ_FAST_4B },
+ { SPINOR_OP_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B },
+ { SPINOR_OP_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B },
+ { SPINOR_OP_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B },
+ { SPINOR_OP_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B },
+
+ { SPINOR_OP_READ_1_1_1_DTR, SPINOR_OP_READ_1_1_1_DTR_4B },
+ { SPINOR_OP_READ_1_2_2_DTR, SPINOR_OP_READ_1_2_2_DTR_4B },
+ { SPINOR_OP_READ_1_4_4_DTR, SPINOR_OP_READ_1_4_4_DTR_4B },
+ };
+
+ return spi_nor_convert_opcode(opcode, spi_nor_3to4_read,
+ ARRAY_SIZE(spi_nor_3to4_read));
+}
+
+static u8 spi_nor_convert_3to4_program(u8 opcode)
+{
+ static const u8 spi_nor_3to4_program[][2] = {
+ { SPINOR_OP_PP, SPINOR_OP_PP_4B },
+ { SPINOR_OP_PP_1_1_4, SPINOR_OP_PP_1_1_4_4B },
+ { SPINOR_OP_PP_1_4_4, SPINOR_OP_PP_1_4_4_4B },
+ };
+
+ return spi_nor_convert_opcode(opcode, spi_nor_3to4_program,
+ ARRAY_SIZE(spi_nor_3to4_program));
+}
+
+static u8 spi_nor_convert_3to4_erase(u8 opcode)
+{
+ static const u8 spi_nor_3to4_erase[][2] = {
+ { SPINOR_OP_BE_4K, SPINOR_OP_BE_4K_4B },
+ { SPINOR_OP_BE_32K, SPINOR_OP_BE_32K_4B },
+ { SPINOR_OP_SE, SPINOR_OP_SE_4B },
+ };
+
+ return spi_nor_convert_opcode(opcode, spi_nor_3to4_erase,
+ ARRAY_SIZE(spi_nor_3to4_erase));
+}
+
+static void spi_nor_set_4byte_opcodes(struct spi_nor *nor)
+{
+ /* Do some manufacturer fixups first */
+ switch (JEDEC_MFR(nor->info)) {
+ case SNOR_MFR_SPANSION:
+ /* No small sector erase for 4-byte command set */
+ nor->erase_opcode = SPINOR_OP_SE;
+ nor->mtd->erasesize = nor->info->sector_size;
+ break;
+
+ default:
+ break;
+ }
+
+ nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode);
+ nor->program_opcode = spi_nor_convert_3to4_program(nor->program_opcode);
+ nor->erase_opcode = spi_nor_convert_3to4_erase(nor->erase_opcode);
+}
+
+
/* Enable/disable 4-byte addressing mode. */
static inline int set_4byte(struct spi_nor *nor, struct flash_info *info,
int enable)
@@ -196,7 +325,7 @@ static inline int set_4byte(struct spi_nor *nor, struct flash_info *info,
write_enable(nor);
cmd = enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B;
- status = nor->write_reg(nor, cmd, NULL, 0, 0);
+ status = nor->write_reg(nor, cmd, NULL, 0);
if (need_wren)
write_disable(nor);
@@ -204,7 +333,7 @@ static inline int set_4byte(struct spi_nor *nor, struct flash_info *info,
default:
/* Spansion style */
nor->cmd_buf[0] = enable << 7;
- return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1, 0);
+ return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1);
}
}
static inline int spi_nor_sr_ready(struct spi_nor *nor)
@@ -279,7 +408,7 @@ static int erase_chip(struct spi_nor *nor)
{
dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd->size >> 10));
- return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0, 0);
+ return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0);
}
static int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops)
@@ -307,6 +436,29 @@ static void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
}
/*
+ * Initiate the erasure of a single sector
+ */
+static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
+{
+ u8 buf[SPI_NOR_MAX_ADDR_WIDTH];
+ int i;
+
+ if (nor->erase)
+ return nor->erase(nor, addr);
+
+ /*
+ * Default implementation, if driver doesn't have a specialized HW
+ * control
+ */
+ for (i = nor->addr_width - 1; i >= 0; i--) {
+ buf[i] = addr & 0xff;
+ addr >>= 8;
+ }
+
+ return nor->write_reg(nor, nor->erase_opcode, buf, nor->addr_width);
+}
+
+/*
* Erase an address range on the nor chip. The address range may extend
* one or more erase sectors. Return an error is there is a problem erasing.
*/
@@ -370,10 +522,9 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
while (len) {
write_enable(nor);
- if (nor->erase(nor, addr)) {
- ret = -EIO;
+ ret = spi_nor_erase_sector(nor, addr);
+ if (ret)
goto erase_err;
- }
addr += mtd->erasesize;
len -= mtd->erasesize;
@@ -540,6 +691,9 @@ static const struct spi_device_id spi_nor_ids[] = {
{ "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) },
{ "mx66l51235l", INFO(0xc2201a, 0, 64 * 1024, 1024, SPI_NOR_QUAD_READ) },
{ "mx66l1g55g", INFO(0xc2261b, 0, 64 * 1024, 2048, SPI_NOR_QUAD_READ) },
+ { "mx66u51235f", INFO(0xc2253a, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
+ { "mx66l1g45g", INFO(0xc2201b, 0, 64 * 1024, 2048, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
+ { "mx66l1g55g", INFO(0xc2261b, 0, 64 * 1024, 2048, SPI_NOR_QUAD_READ) },
/* Micron */
{ "n25q032", INFO(0x20ba16, 0, 64 * 1024, 64, SPI_NOR_QUAD_READ) },
@@ -646,6 +800,7 @@ static const struct spi_device_id spi_nor_ids[] = {
{ "w25q20cl", INFO(0xef4012, 0, 64 * 1024, 4, SECT_4K) },
{ "w25q20bw", INFO(0xef5012, 0, 64 * 1024, 4, SECT_4K) },
{ "w25q20ew", INFO(0xef6012, 0, 64 * 1024, 4, SECT_4K) },
+ { "w25q40bw", INFO(0xef5013, 0, 64 * 1024, 8, SECT_4K) },
{ "w25q16dw", INFO(0xef6015, 0, 64 * 1024, 32, SECT_4K) },
{ "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) },
{ "w25q32dw", INFO(0xef6016, 0, 64 * 1024, 64, SECT_4K) },
@@ -784,7 +939,8 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct spi_nor *nor = mtd_to_spi_nor(mtd);
- u32 page_offset, page_size, i;
+ size_t page_offset, page_remain, i;
+ size_t retval;
int ret;
dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len);
@@ -793,32 +949,23 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
if (ret)
return ret;
- write_enable(nor);
-
- page_offset = to & (nor->page_size - 1);
-
- /* do all the bytes fit onto one page? */
- if (page_offset + len <= nor->page_size) {
- nor->write(nor, to, len, retlen, buf);
- } else {
- /* the size of data remaining on the first page */
- page_size = nor->page_size - page_offset;
- nor->write(nor, to, page_size, retlen, buf);
+ for (i = 0; i < len; ) {
+ ssize_t written;
- /* write everything in nor->page_size chunks */
- for (i = page_size; i < len; i += page_size) {
- page_size = len - i;
- if (page_size > nor->page_size)
- page_size = nor->page_size;
+ page_offset = (to + i) & (nor->page_size - 1);
+ page_remain = min_t(size_t, nor->page_size - page_offset,
+ len - i);
- ret = spi_nor_wait_till_ready(nor);
- if (ret)
- goto write_err;
+ write_enable(nor);
+ nor->write(nor, to + i, page_remain, &retval, buf + i);
+ written = retval;
- write_enable(nor);
+ ret = spi_nor_wait_till_ready(nor);
+ if (ret)
+ goto write_err;
- nor->write(nor, to + i, page_size, retlen, buf + i);
- }
+ *retlen += written;
+ i += written;
}
ret = spi_nor_wait_till_ready(nor);
@@ -827,28 +974,6 @@ write_err:
return ret;
}
-static int macronix_quad_enable(struct spi_nor *nor)
-{
- int ret, val;
-
- val = read_sr(nor);
- write_enable(nor);
-
- nor->cmd_buf[0] = val | SR_QUAD_EN_MX;
- nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1, 0);
-
- if (spi_nor_wait_till_ready(nor))
- return 1;
-
- ret = read_sr(nor);
- if (!(ret > 0 && (ret & SR_QUAD_EN_MX))) {
- dev_err(nor->dev, "Macronix Quad bit not set\n");
- return -EINVAL;
- }
-
- return 0;
-}
-
/*
* Write status Register and configuration register with 2 bytes
* The first byte will be written to the status register, while the
@@ -860,7 +985,7 @@ static int write_sr_cr(struct spi_nor *nor, u16 val)
nor->cmd_buf[0] = val & 0xff;
nor->cmd_buf[1] = (val >> 8);
- return nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 2, 0);
+ return nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 2);
}
static int spansion_quad_enable(struct spi_nor *nor)
@@ -887,42 +1012,272 @@ static int spansion_quad_enable(struct spi_nor *nor)
return 0;
}
-static int set_quad_mode(struct spi_nor *nor, struct flash_info *info)
+static int spi_nor_check(struct spi_nor *nor)
{
- int status;
+ if (!nor->dev || !nor->read || !nor->write ||
+ !nor->read_reg || !nor->write_reg) {
+ pr_err("spi-nor: please fill all the necessary fields!\n");
+ return -EINVAL;
+ }
- switch (JEDEC_MFR(info)) {
- case CFI_MFR_MACRONIX:
- status = macronix_quad_enable(nor);
- if (status) {
- dev_err(nor->dev, "Macronix quad-read not enabled\n");
- return -EINVAL;
- }
- return status;
- default:
- status = spansion_quad_enable(nor);
- if (status) {
- dev_err(nor->dev, "Spansion quad-read not enabled\n");
- return -EINVAL;
- }
- return status;
+ return 0;
+}
+
+static void
+spi_nor_set_read_settings(struct spi_nor_read_command *read,
+ u8 num_mode_clocks,
+ u8 num_wait_states,
+ u8 opcode,
+ enum spi_nor_protocol proto)
+{
+ read->num_mode_clocks = num_mode_clocks;
+ read->num_wait_states = num_wait_states;
+ read->opcode = opcode;
+ read->proto = proto;
+}
+
+static void
+spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
+ u8 opcode,
+ enum spi_nor_protocol proto)
+{
+ pp->opcode = opcode;
+ pp->proto = proto;
+}
+
+static int spi_nor_init_params(struct spi_nor *nor,
+ const struct flash_info *info,
+ struct spi_nor_flash_parameter *params)
+{
+ /* Set legacy flash parameters as default. */
+ memset(params, 0, sizeof(*params));
+
+ /* Set SPI NOR sizes. */
+ params->size = info->sector_size * info->n_sectors;
+ params->page_size = info->page_size;
+
+ /* (Fast) Read settings. */
+ params->hwcaps.mask |= SNOR_HWCAPS_READ;
+ spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ],
+ 0, 0, SPINOR_OP_READ,
+ SNOR_PROTO_1_1_1);
+
+ if (!(info->flags & SPI_NOR_NO_FR)) {
+ params->hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
+ spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_FAST],
+ 0, 8, SPINOR_OP_READ_FAST,
+ SNOR_PROTO_1_1_1);
+ }
+
+ if (info->flags & SPI_NOR_DUAL_READ) {
+ params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
+ spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_2],
+ 0, 8, SPINOR_OP_READ_1_1_2,
+ SNOR_PROTO_1_1_2);
}
+
+ if (info->flags & SPI_NOR_QUAD_READ) {
+ params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
+ spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_1_1_4],
+ 0, 8, SPINOR_OP_READ_1_1_4,
+ SNOR_PROTO_1_1_4);
+ }
+
+ /* Page Program settings. */
+ params->hwcaps.mask |= SNOR_HWCAPS_PP;
+ spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
+ SPINOR_OP_PP, SNOR_PROTO_1_1_1);
+
+ /* Select the procedure to set the Quad Enable bit. */
+ if (params->hwcaps.mask & (SNOR_HWCAPS_READ_QUAD |
+ SNOR_HWCAPS_PP_QUAD))
+ params->quad_enable = spansion_quad_enable;
+
+ return 0;
}
-static int spi_nor_check(struct spi_nor *nor)
+static int spi_nor_hwcaps2cmd(u32 hwcaps, const int table[][2], size_t size)
{
- if (!nor->dev || !nor->read || !nor->write ||
- !nor->read_reg || !nor->write_reg || !nor->erase) {
- pr_err("spi-nor: please fill all the necessary fields!\n");
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ if (table[i][0] == (int)hwcaps)
+ return table[i][1];
+
+ return -EINVAL;
+}
+
+static int spi_nor_hwcaps_read2cmd(u32 hwcaps)
+{
+ static const int hwcaps_read2cmd[][2] = {
+ { SNOR_HWCAPS_READ, SNOR_CMD_READ },
+ { SNOR_HWCAPS_READ_FAST, SNOR_CMD_READ_FAST },
+ { SNOR_HWCAPS_READ_1_1_2, SNOR_CMD_READ_1_1_2 },
+ { SNOR_HWCAPS_READ_1_2_2, SNOR_CMD_READ_1_2_2 },
+ { SNOR_HWCAPS_READ_2_2_2, SNOR_CMD_READ_2_2_2 },
+ { SNOR_HWCAPS_READ_1_1_4, SNOR_CMD_READ_1_1_4 },
+ { SNOR_HWCAPS_READ_1_4_4, SNOR_CMD_READ_1_4_4 },
+ { SNOR_HWCAPS_READ_4_4_4, SNOR_CMD_READ_4_4_4 },
+ };
+
+ return spi_nor_hwcaps2cmd(hwcaps, hwcaps_read2cmd,
+ ARRAY_SIZE(hwcaps_read2cmd));
+}
+
+static int spi_nor_hwcaps_pp2cmd(u32 hwcaps)
+{
+ static const int hwcaps_pp2cmd[][2] = {
+ { SNOR_HWCAPS_PP, SNOR_CMD_PP },
+ { SNOR_HWCAPS_PP_1_1_4, SNOR_CMD_PP_1_1_4 },
+ { SNOR_HWCAPS_PP_1_4_4, SNOR_CMD_PP_1_4_4 },
+ { SNOR_HWCAPS_PP_4_4_4, SNOR_CMD_PP_4_4_4 },
+ };
+
+ return spi_nor_hwcaps2cmd(hwcaps, hwcaps_pp2cmd,
+ ARRAY_SIZE(hwcaps_pp2cmd));
+}
+
+static int spi_nor_select_read(struct spi_nor *nor,
+ const struct spi_nor_flash_parameter *params,
+ u32 shared_hwcaps)
+{
+ int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_READ_MASK) - 1;
+ const struct spi_nor_read_command *read;
+
+ if (best_match < 0)
+ return -EINVAL;
+
+ cmd = spi_nor_hwcaps_read2cmd(BIT(best_match));
+ if (cmd < 0)
+ return -EINVAL;
+
+ read = &params->reads[cmd];
+ nor->read_opcode = read->opcode;
+ nor->read_proto = read->proto;
+
+ /*
+ * In the spi-nor framework, we don't need to make the difference
+ * between mode clock cycles and wait state clock cycles.
+ * Indeed, the value of the mode clock cycles is used by a QSPI
+ * flash memory to know whether it should enter or leave its 0-4-4
+ * (Continuous Read / XIP) mode.
+ * eXecution In Place is out of the scope of the mtd sub-system.
+ * Hence we choose to merge both mode and wait state clock cycles
+ * into the so called dummy clock cycles.
+ */
+ nor->read_dummy = read->num_mode_clocks + read->num_wait_states;
+ return 0;
+}
+
+static int spi_nor_select_pp(struct spi_nor *nor,
+ const struct spi_nor_flash_parameter *params,
+ u32 shared_hwcaps)
+{
+ int cmd, best_match = fls(shared_hwcaps & SNOR_HWCAPS_PP_MASK) - 1;
+ const struct spi_nor_pp_command *pp;
+
+ if (best_match < 0)
+ return -EINVAL;
+
+ cmd = spi_nor_hwcaps_pp2cmd(BIT(best_match));
+ if (cmd < 0)
return -EINVAL;
+
+ pp = &params->page_programs[cmd];
+ nor->program_opcode = pp->opcode;
+ nor->write_proto = pp->proto;
+ return 0;
+}
+
+static int spi_nor_select_erase(struct spi_nor *nor,
+ const struct flash_info *info)
+{
+ struct mtd_info *mtd = nor->mtd;
+
+#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+ /* prefer "small sector" erase if possible */
+ if (info->flags & SECT_4K) {
+ nor->erase_opcode = SPINOR_OP_BE_4K;
+ mtd->erasesize = 4096;
+ } else if (info->flags & SECT_4K_PMC) {
+ nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
+ mtd->erasesize = 4096;
+ } else
+#endif
+ {
+ nor->erase_opcode = SPINOR_OP_SE;
+ mtd->erasesize = info->sector_size;
+ }
+ return 0;
+}
+
+static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
+ const struct spi_nor_flash_parameter *params,
+ const struct spi_nor_hwcaps *hwcaps)
+{
+ u32 ignored_mask, shared_mask;
+ bool enable_quad_io;
+ int err;
+
+ /*
+ * Keep only the hardware capabilities supported by both the SPI
+ * controller and the SPI flash memory.
+ */
+ shared_mask = hwcaps->mask & params->hwcaps.mask;
+
+ /* SPI n-n-n protocols are not supported yet. */
+ ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
+ SNOR_HWCAPS_READ_4_4_4 |
+ SNOR_HWCAPS_PP_4_4_4);
+ if (shared_mask & ignored_mask) {
+ dev_dbg(nor->dev,
+ "SPI n-n-n protocols are not supported yet.\n");
+ shared_mask &= ~ignored_mask;
+ }
+
+ /* Select the (Fast) Read command. */
+ err = spi_nor_select_read(nor, params, shared_mask);
+ if (err) {
+ dev_err(nor->dev,
+ "can't select read settings supported by both the SPI controller and memory.\n");
+ return err;
+ }
+
+ /* Select the Page Program command. */
+ err = spi_nor_select_pp(nor, params, shared_mask);
+ if (err) {
+ dev_err(nor->dev,
+ "can't select write settings supported by both the SPI controller and memory.\n");
+ return err;
+ }
+
+ /* Select the Sector Erase command. */
+ err = spi_nor_select_erase(nor, info);
+ if (err) {
+ dev_err(nor->dev,
+ "can't select erase settings supported by both the SPI controller and memory.\n");
+ return err;
+ }
+
+ /* Enable Quad I/O if needed. */
+ enable_quad_io = (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+ spi_nor_get_protocol_width(nor->write_proto) == 4);
+ if (enable_quad_io && params->quad_enable) {
+ err = params->quad_enable(nor);
+ if (err) {
+ dev_err(nor->dev, "quad mode not supported\n");
+ return err;
+ }
}
return 0;
}
-int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode,
+int spi_nor_scan(struct spi_nor *nor, const char *name,
+ const struct spi_nor_hwcaps *hwcaps,
bool use_large_blocks)
{
+ struct spi_nor_flash_parameter params;
const struct spi_device_id *id = NULL;
struct flash_info *info;
struct device_d *dev = nor->dev;
@@ -935,6 +1290,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode,
if (ret)
return ret;
+ /* Reset SPI protocol for all commands. */
+ nor->reg_proto = SNOR_PROTO_1_1_1;
+ nor->read_proto = SNOR_PROTO_1_1_1;
+ nor->write_proto = SNOR_PROTO_1_1_1;
+
/* Try to auto-detect if chip name wasn't specified */
if (!name)
id = spi_nor_read_id(nor);
@@ -970,6 +1330,8 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode,
}
}
+ nor->info = info;
+
mutex_init(&nor->lock);
/*
@@ -984,12 +1346,17 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode,
write_sr(nor, 0);
}
+ /* Parse the Serial Flash Discoverable Parameters table. */
+ ret = spi_nor_init_params(nor, info, &params);
+ if (ret)
+ return ret;
+
if (!mtd->name)
mtd->name = (char *) dev_name(dev);
mtd->type = MTD_NORFLASH;
mtd->writesize = 1;
mtd->flags = MTD_CAP_NORFLASH;
- mtd->size = info->sector_size * info->n_sectors;
+ mtd->size = params.size;
mtd->erase = spi_nor_erase;
mtd->read = spi_nor_read;
@@ -1008,107 +1375,57 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode,
if (info->flags & USE_FSR)
nor->flags |= SNOR_F_USE_FSR;
-#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
- /* prefer "small sector" erase if possible */
- if (info->flags & SECT_4K && !use_large_blocks) {
- nor->erase_opcode = SPINOR_OP_BE_4K;
- mtd->erasesize = 4096;
- } else if (info->flags & SECT_4K_PMC && !use_large_blocks) {
- nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
- mtd->erasesize = 4096;
- } else
-#endif
- {
- nor->erase_opcode = SPINOR_OP_SE;
- mtd->erasesize = info->sector_size;
- }
-
if (info->flags & SPI_NOR_NO_ERASE)
mtd->flags |= MTD_NO_ERASE;
- nor->page_size = info->page_size;
+ nor->page_size = params.page_size;
mtd->writebufsize = nor->page_size;
if (np) {
/* If we were instantiated by DT, use it */
if (of_property_read_bool(np, "m25p,fast-read"))
- nor->flash_read = SPI_NOR_FAST;
+ params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
else
- nor->flash_read = SPI_NOR_NORMAL;
+ params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
} else {
/* If we weren't instantiated by DT, default to fast-read */
- nor->flash_read = SPI_NOR_FAST;
+ params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
}
/* Some devices cannot do fast-read, no matter what DT tells us */
if (info->flags & SPI_NOR_NO_FR)
- nor->flash_read = SPI_NOR_NORMAL;
+ params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
- /* Quad/Dual-read mode takes precedence over fast/normal */
- if (mode == SPI_NOR_QUAD && info->flags & SPI_NOR_QUAD_READ) {
- ret = set_quad_mode(nor, info);
- if (ret) {
- dev_err(dev, "quad mode not supported\n");
- return ret;
- }
- nor->flash_read = SPI_NOR_QUAD;
- } else if (mode == SPI_NOR_DUAL && info->flags & SPI_NOR_DUAL_READ) {
- nor->flash_read = SPI_NOR_DUAL;
- }
-
- /* Default commands */
- switch (nor->flash_read) {
- case SPI_NOR_QUAD:
- nor->read_opcode = SPINOR_OP_READ_1_1_4;
- break;
- case SPI_NOR_DUAL:
- nor->read_opcode = SPINOR_OP_READ_1_1_2;
- break;
- case SPI_NOR_FAST:
- nor->read_opcode = SPINOR_OP_READ_FAST;
- break;
- case SPI_NOR_NORMAL:
- nor->read_opcode = SPINOR_OP_READ;
- break;
- default:
- dev_err(dev, "No Read opcode defined\n");
- return -EINVAL;
- }
-
- nor->program_opcode = SPINOR_OP_PP;
+ /*
+ * Configure the SPI memory:
+ * - select op codes for (Fast) Read, Page Program and Sector Erase.
+ * - set the number of dummy cycles (mode cycles + wait states).
+ * - set the SPI protocols for register and memory accesses.
+ * - set the Quad Enable bit if needed (required by SPI x-y-4 protos).
+ */
+ ret = spi_nor_setup(nor, info, &params, hwcaps);
+ if (ret)
+ return ret;
if (info->addr_width)
nor->addr_width = info->addr_width;
else if (mtd->size > 0x1000000) {
/* enable 4-byte addressing if the device exceeds 16MiB */
nor->addr_width = 4;
- if (JEDEC_MFR(info) == CFI_MFR_AMD) {
- /* Dedicated 4-byte command set */
- switch (nor->flash_read) {
- case SPI_NOR_QUAD:
- nor->read_opcode = SPINOR_OP_READ4_1_1_4;
- break;
- case SPI_NOR_DUAL:
- nor->read_opcode = SPINOR_OP_READ4_1_1_2;
- break;
- case SPI_NOR_FAST:
- nor->read_opcode = SPINOR_OP_READ4_FAST;
- break;
- case SPI_NOR_NORMAL:
- nor->read_opcode = SPINOR_OP_READ4;
- break;
- }
- nor->program_opcode = SPINOR_OP_PP_4B;
- /* No small sector erase for 4-byte command set */
- nor->erase_opcode = SPINOR_OP_SE_4B;
- mtd->erasesize = info->sector_size;
- } else
+ if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
+ info->flags & SPI_NOR_4B_OPCODES)
+ spi_nor_set_4byte_opcodes(nor);
+ else
set_4byte(nor, info, 1);
} else {
nor->addr_width = 3;
}
- nor->read_dummy = spi_nor_read_dummy_cycles(nor);
+ if (nor->addr_width > SPI_NOR_MAX_ADDR_WIDTH) {
+ dev_err(dev, "address width is too large: %u\n",
+ nor->addr_width);
+ return -EINVAL;
+ }
dev_info(dev, "%s (%lld Kbytes)\n", id->name,
(long long)mtd->size >> 10);
diff --git a/drivers/mtd/ubi/ubi-barebox.h b/drivers/mtd/ubi/ubi-barebox.h
index 557ad88316..7ee87ffd3e 100644
--- a/drivers/mtd/ubi/ubi-barebox.h
+++ b/drivers/mtd/ubi/ubi-barebox.h
@@ -30,7 +30,6 @@
#define crc32(seed, data, length) crc32_no_comp(seed, (unsigned char * const)data, length)
/* configurable */
-#define CONFIG_MTD_UBI_WL_THRESHOLD 4096
#define UBI_IO_DEBUG 0
/* upd.c */
diff --git a/drivers/net/fsl-fman.c b/drivers/net/fsl-fman.c
index 1a11ca4926..4e6bb2ecfd 100644
--- a/drivers/net/fsl-fman.c
+++ b/drivers/net/fsl-fman.c
@@ -640,6 +640,8 @@ static int fm_eth_rx_port_parameter_init(struct fm_eth *fm_eth)
i * MAX_RXBUF_LEN));
buf_lo = lower_32_bits(virt_to_phys(rx_buf_pool +
i * MAX_RXBUF_LEN));
+ dma_sync_single_for_device((unsigned long)rx_buf_pool + i * MAX_RXBUF_LEN,
+ MAX_RXBUF_LEN, DMA_FROM_DEVICE);
muram_writew(&rxbd->buf_ptr_hi, (u16)buf_hi);
out_be32(&rxbd->buf_ptr_lo, buf_lo);
rxbd++;
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 7d181949ee..9d2c6e614b 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -38,6 +38,12 @@ config RTC_DRV_ABRACON
endif # I2C
+config RTC_DRV_IMXDI
+ tristate "Freescale IMX DryIce Real Time Clock"
+ depends on ARCH_IMX
+ help
+ Support for Freescale IMX DryIce RTC
+
config RTC_DRV_JZ4740
tristate "Ingenic JZ4740 RTC"
depends on MACH_MIPS_XBURST
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 68741c26a1..1308beff38 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -9,4 +9,5 @@ obj-$(CONFIG_RTC_CLASS) += class.o
obj-$(CONFIG_RTC_DRV_ABRACON) += rtc-abracon.o
obj-$(CONFIG_RTC_DRV_DS1307) += rtc-ds1307.o
+obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o
obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o
diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c
new file mode 100644
index 0000000000..8fcaf631ff
--- /dev/null
+++ b/drivers/rtc/rtc-imxdi.c
@@ -0,0 +1,623 @@
+/*
+ * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2010 Orex Computed Radiography
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/* based on rtc-mc13892.c */
+
+/*
+ * This driver uses the 47-bit 32 kHz counter in the Freescale DryIce block
+ * to implement a Linux RTC. Times and alarms are truncated to seconds.
+ * Since the RTC framework performs API locking via rtc->ops_lock the
+ * only simultaneous accesses we need to deal with is updating DryIce
+ * registers while servicing an alarm.
+ *
+ * Note that reading the DSR (DryIce Status Register) automatically clears
+ * the WCF (Write Complete Flag). All DryIce writes are synchronized to the
+ * LP (Low Power) domain and set the WCF upon completion. Writes to the
+ * DIER (DryIce Interrupt Enable Register) are the only exception. These
+ * occur at normal bus speeds and do not set WCF. Periodic interrupts are
+ * not supported by the hardware.
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <rtc.h>
+#include <io.h>
+#include <linux/clk.h>
+#include <linux/rtc.h>
+#include <linux/nvmem-provider.h>
+
+/* DryIce Register Definitions */
+
+#define DTCMR 0x00 /* Time Counter MSB Reg */
+#define DTCLR 0x04 /* Time Counter LSB Reg */
+
+#define DCAMR 0x08 /* Clock Alarm MSB Reg */
+#define DCALR 0x0c /* Clock Alarm LSB Reg */
+#define DCAMR_UNSET 0xFFFFFFFF /* doomsday - 1 sec */
+
+#define DCR 0x10 /* Control Reg */
+#define DCR_TDCHL (1 << 30) /* Tamper-detect configuration hard lock */
+#define DCR_TDCSL (1 << 29) /* Tamper-detect configuration soft lock */
+#define DCR_KSSL (1 << 27) /* Key-select soft lock */
+#define DCR_MCHL (1 << 20) /* Monotonic-counter hard lock */
+#define DCR_MCSL (1 << 19) /* Monotonic-counter soft lock */
+#define DCR_TCHL (1 << 18) /* Timer-counter hard lock */
+#define DCR_TCSL (1 << 17) /* Timer-counter soft lock */
+#define DCR_FSHL (1 << 16) /* Failure state hard lock */
+#define DCR_TCE (1 << 3) /* Time Counter Enable */
+#define DCR_MCE (1 << 2) /* Monotonic Counter Enable */
+
+#define DSR 0x14 /* Status Reg */
+#define DSR_WTD (1 << 23) /* Wire-mesh tamper detected */
+#define DSR_ETBD (1 << 22) /* External tamper B detected */
+#define DSR_ETAD (1 << 21) /* External tamper A detected */
+#define DSR_EBD (1 << 20) /* External boot detected */
+#define DSR_SAD (1 << 19) /* SCC alarm detected */
+#define DSR_TTD (1 << 18) /* Temperature tamper detected */
+#define DSR_CTD (1 << 17) /* Clock tamper detected */
+#define DSR_VTD (1 << 16) /* Voltage tamper detected */
+#define DSR_WBF (1 << 10) /* Write Busy Flag (synchronous) */
+#define DSR_WNF (1 << 9) /* Write Next Flag (synchronous) */
+#define DSR_WCF (1 << 8) /* Write Complete Flag (synchronous)*/
+#define DSR_WEF (1 << 7) /* Write Error Flag */
+#define DSR_CAF (1 << 4) /* Clock Alarm Flag */
+#define DSR_MCO (1 << 3) /* monotonic counter overflow */
+#define DSR_TCO (1 << 2) /* time counter overflow */
+#define DSR_NVF (1 << 1) /* Non-Valid Flag */
+#define DSR_SVF (1 << 0) /* Security Violation Flag */
+
+#define DIER 0x18 /* Interrupt Enable Reg (synchronous) */
+#define DIER_WNIE (1 << 9) /* Write Next Interrupt Enable */
+#define DIER_WCIE (1 << 8) /* Write Complete Interrupt Enable */
+#define DIER_WEIE (1 << 7) /* Write Error Interrupt Enable */
+#define DIER_CAIE (1 << 4) /* Clock Alarm Interrupt Enable */
+#define DIER_SVIE (1 << 0) /* Security-violation Interrupt Enable */
+
+#define DMCR 0x1c /* DryIce Monotonic Counter Reg */
+
+#define DTCR 0x28 /* DryIce Tamper Configuration Reg */
+#define DTCR_MOE (1 << 9) /* monotonic overflow enabled */
+#define DTCR_TOE (1 << 8) /* time overflow enabled */
+#define DTCR_WTE (1 << 7) /* wire-mesh tamper enabled */
+#define DTCR_ETBE (1 << 6) /* external B tamper enabled */
+#define DTCR_ETAE (1 << 5) /* external A tamper enabled */
+#define DTCR_EBE (1 << 4) /* external boot tamper enabled */
+#define DTCR_SAIE (1 << 3) /* SCC enabled */
+#define DTCR_TTE (1 << 2) /* temperature tamper enabled */
+#define DTCR_CTE (1 << 1) /* clock tamper enabled */
+#define DTCR_VTE (1 << 0) /* voltage tamper enabled */
+
+#define DGPR 0x3c /* DryIce General Purpose Reg */
+
+/**
+ * struct imxdi_dev - private imxdi rtc data
+ * @dev: pionter to dev
+ * @rtc: pointer to rtc struct
+ * @ioaddr: IO registers pointer
+ * @clk: input reference clock
+ * @dsr: copy of the DSR register
+ */
+struct imxdi_dev {
+ struct device_d *dev;
+ struct rtc_device rtc;
+ void __iomem *ioaddr;
+ struct clk *clk;
+ u32 dsr;
+ struct nvmem_device *nvmem;
+};
+
+/* Some background:
+ *
+ * The DryIce unit is a complex security/tamper monitor device. To be able do
+ * its job in a useful manner it runs a bigger statemachine to bring it into
+ * security/tamper failure state and once again to bring it out of this state.
+ *
+ * This unit can be in one of three states:
+ *
+ * - "NON-VALID STATE"
+ * always after the battery power was removed
+ * - "FAILURE STATE"
+ * if one of the enabled security events has happened
+ * - "VALID STATE"
+ * if the unit works as expected
+ *
+ * Everything stops when the unit enters the failure state including the RTC
+ * counter (to be able to detect the time the security event happened).
+ *
+ * The following events (when enabled) let the DryIce unit enter the failure
+ * state:
+ *
+ * - wire-mesh-tamper detect
+ * - external tamper B detect
+ * - external tamper A detect
+ * - temperature tamper detect
+ * - clock tamper detect
+ * - voltage tamper detect
+ * - RTC counter overflow
+ * - monotonic counter overflow
+ * - external boot
+ *
+ * If we find the DryIce unit in "FAILURE STATE" and the TDCHL cleared, we
+ * can only detect this state. In this case the unit is completely locked and
+ * must force a second "SYSTEM POR" to bring the DryIce into the
+ * "NON-VALID STATE" + "FAILURE STATE" where a recovery is possible.
+ * If the TDCHL is set in the "FAILURE STATE" we are out of luck. In this case
+ * a battery power cycle is required.
+ *
+ * In the "NON-VALID STATE" + "FAILURE STATE" we can clear the "FAILURE STATE"
+ * and recover the DryIce unit. By clearing the "NON-VALID STATE" as the last
+ * task, we bring back this unit into life.
+ */
+
+/*
+ * Do a write into the unit without interrupt support.
+ * We do not need to check the WEF here, because the only reason this kind of
+ * write error can happen is if we write to the unit twice within the 122 us
+ * interval. This cannot happen, since we are using this function only while
+ * setting up the unit.
+ */
+static void di_write_busy_wait(const struct imxdi_dev *imxdi, u32 val,
+ unsigned reg)
+{
+ /* do the register write */
+ writel(val, imxdi->ioaddr + reg);
+
+ /*
+ * now it takes four 32,768 kHz clock cycles to take
+ * the change into effect = 122 us
+ */
+ udelay(130);
+}
+
+static void di_what_is_to_be_done(struct imxdi_dev *imxdi,
+ const char *power_supply)
+{
+ dev_emerg(imxdi->dev, "Please cycle the %s power supply in order to get the DryIce/RTC unit working again\n",
+ power_supply);
+}
+
+static int di_handle_failure_state(struct imxdi_dev *imxdi, u32 dsr)
+{
+ u32 dcr;
+
+ dev_dbg(imxdi->dev, "DSR register reports: %08X\n", dsr);
+
+ dcr = readl(imxdi->ioaddr + DCR);
+
+ if (dcr & DCR_FSHL) {
+ /* we are out of luck */
+ di_what_is_to_be_done(imxdi, "battery");
+ return -ENODEV;
+ }
+ /*
+ * with the next SYSTEM POR we will transit from the "FAILURE STATE"
+ * into the "NON-VALID STATE" + "FAILURE STATE"
+ */
+ di_what_is_to_be_done(imxdi, "main");
+
+ return -ENODEV;
+}
+
+static int di_handle_valid_state(struct imxdi_dev *imxdi, u32 dsr)
+{
+ /* initialize alarm */
+ di_write_busy_wait(imxdi, DCAMR_UNSET, DCAMR);
+ di_write_busy_wait(imxdi, 0, DCALR);
+
+ /* clear alarm flag */
+ if (dsr & DSR_CAF)
+ di_write_busy_wait(imxdi, DSR_CAF, DSR);
+
+ return 0;
+}
+
+static int di_handle_invalid_state(struct imxdi_dev *imxdi, u32 dsr)
+{
+ u32 dcr, sec;
+
+ /*
+ * lets disable all sources which can force the DryIce unit into
+ * the "FAILURE STATE" for now
+ */
+ di_write_busy_wait(imxdi, 0x00000000, DTCR);
+ /* and lets protect them at runtime from any change */
+ di_write_busy_wait(imxdi, DCR_TDCSL, DCR);
+
+ sec = readl(imxdi->ioaddr + DTCMR);
+ if (sec != 0)
+ dev_warn(imxdi->dev,
+ "The security violation has happened at %u seconds\n",
+ sec);
+ /*
+ * the timer cannot be set/modified if
+ * - the TCHL or TCSL bit is set in DCR
+ */
+ dcr = readl(imxdi->ioaddr + DCR);
+ if (!(dcr & DCR_TCE)) {
+ if (dcr & DCR_TCHL) {
+ /* we are out of luck */
+ di_what_is_to_be_done(imxdi, "battery");
+ return -ENODEV;
+ }
+ if (dcr & DCR_TCSL) {
+ di_what_is_to_be_done(imxdi, "main");
+ return -ENODEV;
+ }
+ }
+ /*
+ * - the timer counter stops/is stopped if
+ * - its overflow flag is set (TCO in DSR)
+ * -> clear overflow bit to make it count again
+ * - NVF is set in DSR
+ * -> clear non-valid bit to make it count again
+ * - its TCE (DCR) is cleared
+ * -> set TCE to make it count
+ * - it was never set before
+ * -> write a time into it (required again if the NVF was set)
+ */
+ /* state handled */
+ di_write_busy_wait(imxdi, DSR_NVF, DSR);
+ /* clear overflow flag */
+ di_write_busy_wait(imxdi, DSR_TCO, DSR);
+ /* enable the counter */
+ di_write_busy_wait(imxdi, dcr | DCR_TCE, DCR);
+ /* set and trigger it to make it count */
+ di_write_busy_wait(imxdi, sec, DTCMR);
+
+ /* now prepare for the valid state */
+ return di_handle_valid_state(imxdi, __raw_readl(imxdi->ioaddr + DSR));
+}
+
+static int di_handle_invalid_and_failure_state(struct imxdi_dev *imxdi, u32 dsr)
+{
+ u32 dcr;
+
+ /*
+ * now we must first remove the tamper sources in order to get the
+ * device out of the "FAILURE STATE"
+ * To disable any of the following sources we need to modify the DTCR
+ */
+ if (dsr & (DSR_WTD | DSR_ETBD | DSR_ETAD | DSR_EBD | DSR_SAD |
+ DSR_TTD | DSR_CTD | DSR_VTD | DSR_MCO | DSR_TCO)) {
+ dcr = __raw_readl(imxdi->ioaddr + DCR);
+ if (dcr & DCR_TDCHL) {
+ /*
+ * the tamper register is locked. We cannot disable the
+ * tamper detection. The TDCHL can only be reset by a
+ * DRYICE POR, but we cannot force a DRYICE POR in
+ * softwere because we are still in "FAILURE STATE".
+ * We need a DRYICE POR via battery power cycling....
+ */
+ /*
+ * out of luck!
+ * we cannot disable them without a DRYICE POR
+ */
+ di_what_is_to_be_done(imxdi, "battery");
+ return -ENODEV;
+ }
+ if (dcr & DCR_TDCSL) {
+ /* a soft lock can be removed by a SYSTEM POR */
+ di_what_is_to_be_done(imxdi, "main");
+ return -ENODEV;
+ }
+ }
+
+ /* disable all sources */
+ di_write_busy_wait(imxdi, 0x00000000, DTCR);
+
+ /* clear the status bits now */
+ di_write_busy_wait(imxdi, dsr & (DSR_WTD | DSR_ETBD | DSR_ETAD |
+ DSR_EBD | DSR_SAD | DSR_TTD | DSR_CTD | DSR_VTD |
+ DSR_MCO | DSR_TCO), DSR);
+
+ dsr = readl(imxdi->ioaddr + DSR);
+ if ((dsr & ~(DSR_NVF | DSR_SVF | DSR_WBF | DSR_WNF |
+ DSR_WCF | DSR_WEF)) != 0)
+ dev_warn(imxdi->dev,
+ "There are still some sources of pain in DSR: %08x!\n",
+ dsr & ~(DSR_NVF | DSR_SVF | DSR_WBF | DSR_WNF |
+ DSR_WCF | DSR_WEF));
+
+ /*
+ * now we are trying to clear the "Security-violation flag" to
+ * get the DryIce out of this state
+ */
+ di_write_busy_wait(imxdi, DSR_SVF, DSR);
+
+ /* success? */
+ dsr = readl(imxdi->ioaddr + DSR);
+ if (dsr & DSR_SVF) {
+ dev_crit(imxdi->dev,
+ "Cannot clear the security violation flag. We are ending up in an endless loop!\n");
+ /* last resort */
+ di_what_is_to_be_done(imxdi, "battery");
+ return -ENODEV;
+ }
+
+ /*
+ * now we have left the "FAILURE STATE" and ending up in the
+ * "NON-VALID STATE" time to recover everything
+ */
+ return di_handle_invalid_state(imxdi, dsr);
+}
+
+static int di_handle_state(struct imxdi_dev *imxdi)
+{
+ int rc;
+ u32 dsr;
+
+ dsr = readl(imxdi->ioaddr + DSR);
+
+ switch (dsr & (DSR_NVF | DSR_SVF)) {
+ case DSR_NVF:
+ dev_warn(imxdi->dev, "Invalid stated unit detected\n");
+ rc = di_handle_invalid_state(imxdi, dsr);
+ break;
+ case DSR_SVF:
+ dev_warn(imxdi->dev, "Failure stated unit detected\n");
+ rc = di_handle_failure_state(imxdi, dsr);
+ break;
+ case DSR_NVF | DSR_SVF:
+ dev_warn(imxdi->dev,
+ "Failure+Invalid stated unit detected\n");
+ rc = di_handle_invalid_and_failure_state(imxdi, dsr);
+ break;
+ default:
+ dev_notice(imxdi->dev, "Unlocked unit detected\n");
+ rc = di_handle_valid_state(imxdi, dsr);
+ }
+
+ return rc;
+}
+
+/*
+ * This function attempts to clear the dryice write-error flag.
+ *
+ * A dryice write error is similar to a bus fault and should not occur in
+ * normal operation. Clearing the flag requires another write, so the root
+ * cause of the problem may need to be fixed before the flag can be cleared.
+ */
+static void clear_write_error(struct imxdi_dev *imxdi)
+{
+ int cnt;
+
+ dev_warn(imxdi->dev, "WARNING: Register write error!\n");
+
+ /* clear the write error flag */
+ writel(DSR_WEF, imxdi->ioaddr + DSR);
+
+ /* wait for it to take effect */
+ for (cnt = 0; cnt < 1000; cnt++) {
+ if ((readl(imxdi->ioaddr + DSR) & DSR_WEF) == 0)
+ return;
+ udelay(10);
+ }
+ dev_err(imxdi->dev,
+ "ERROR: Cannot clear write-error flag!\n");
+}
+
+/*
+ * Write a dryice register and wait until it completes.
+ *
+ * This function uses interrupts to determine when the
+ * write has completed.
+ */
+static int di_write_wait(struct imxdi_dev *imxdi, u32 val, int reg)
+{
+ int rc = 0;
+ uint32_t dsr;
+ uint64_t start;
+
+ /* do the register write */
+ writel(val, imxdi->ioaddr + reg);
+
+ start = get_time_ns();
+
+ /* wait for the write to finish */
+ while (1) {
+ dsr = readl(imxdi->ioaddr + DSR);
+
+ if (dsr & (DSR_WCF | DSR_WEF))
+ break;
+ if (is_timeout(start, MSECOND))
+ return -EIO;
+ }
+
+ /* check for write error */
+ if (dsr & DSR_WEF) {
+ clear_write_error(imxdi);
+ rc = -EIO;
+ }
+
+ return rc;
+}
+
+static struct imxdi_dev *to_imxdi_dev(struct rtc_device *rtc)
+{
+ return container_of(rtc, struct imxdi_dev, rtc);
+}
+
+/*
+ * read the seconds portion of the current time from the dryice time counter
+ */
+static int dryice_rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
+{
+ struct imxdi_dev *imxdi = to_imxdi_dev(rtc);
+ unsigned long now;
+
+ now = readl(imxdi->ioaddr + DTCMR);
+ rtc_time_to_tm(now, tm);
+
+ return 0;
+}
+
+/*
+ * set the seconds portion of dryice time counter and clear the
+ * fractional part.
+ */
+static int dryice_rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
+{
+ struct imxdi_dev *imxdi = to_imxdi_dev(rtc);
+ u32 dcr, dsr;
+ int ret;
+ unsigned long secs;
+
+ ret = rtc_tm_to_time(tm, &secs);
+ if (ret)
+ return ret;
+
+ dcr = readl(imxdi->ioaddr + DCR);
+ dsr = readl(imxdi->ioaddr + DSR);
+
+ if (!(dcr & DCR_TCE) || (dsr & DSR_SVF)) {
+ if (dcr & DCR_TCHL) {
+ /* we are even more out of luck */
+ di_what_is_to_be_done(imxdi, "battery");
+ return -EPERM;
+ }
+ if ((dcr & DCR_TCSL) || (dsr & DSR_SVF)) {
+ /* we are out of luck for now */
+ di_what_is_to_be_done(imxdi, "main");
+ return -EPERM;
+ }
+ }
+
+ /* zero the fractional part first */
+ ret = di_write_wait(imxdi, 0, DTCLR);
+ if (ret)
+ return ret;
+
+ ret = di_write_wait(imxdi, secs, DTCMR);
+ if (ret)
+ return ret;
+
+ return di_write_wait(imxdi, readl(imxdi->ioaddr + DCR) | DCR_TCE, DCR);
+}
+
+static const struct rtc_class_ops dryice_rtc_ops = {
+ .read_time = dryice_rtc_read_time,
+ .set_time = dryice_rtc_set_time,
+};
+
+static int nvstore_write(struct device_d *dev, const int reg, const void *val,
+ int bytes)
+{
+ struct imxdi_dev *imxdi = dev->parent->priv;
+ const u32 *val32 = val;
+
+ if (bytes != 4)
+ return 0;
+
+ writel(*val32, imxdi->ioaddr + DGPR);
+
+ return 0;
+}
+
+static int nvstore_read(struct device_d *dev, const int reg, void *val,
+ int bytes)
+{
+ struct imxdi_dev *imxdi = dev->parent->priv;
+ u32 *val32 = val;
+
+ if (bytes != 4)
+ return 0;
+
+ *val32 = readl(imxdi->ioaddr + DGPR);
+
+ return 0;
+}
+
+static struct nvmem_bus nvstore_nvmem_bus = {
+ .write = nvstore_write,
+ .read = nvstore_read,
+};
+
+static struct nvmem_config nvstore_nvmem_config = {
+ .name = "nvstore",
+ .stride = 4,
+ .word_size = 4,
+ .size = 4,
+ .bus = &nvstore_nvmem_bus,
+};
+
+static int __init dryice_rtc_probe(struct device_d *dev)
+{
+ struct resource *res;
+ struct imxdi_dev *imxdi;
+ int ret;
+
+ imxdi = xzalloc(sizeof(*imxdi));
+
+ imxdi->dev = dev;
+ imxdi->rtc.ops = &dryice_rtc_ops;
+
+ res = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(res))
+ return PTR_ERR(res);
+
+ imxdi->ioaddr = IOMEM(res->start);
+
+ imxdi->clk = clk_get(dev, NULL);
+ if (IS_ERR(imxdi->clk))
+ return PTR_ERR(imxdi->clk);
+
+ ret = clk_enable(imxdi->clk);
+ if (ret)
+ return ret;
+
+ /*
+ * Initialize dryice hardware
+ */
+
+ /* mask all interrupts */
+ writel(0, imxdi->ioaddr + DIER);
+
+ ret = di_handle_state(imxdi);
+ if (ret)
+ goto err;
+
+ dev->priv = imxdi;
+
+ nvstore_nvmem_config.dev = dev;
+
+ imxdi->nvmem = nvmem_register(&nvstore_nvmem_config);
+ if (IS_ENABLED(CONFIG_NVMEM) && IS_ERR(imxdi->nvmem)) {
+ ret = PTR_ERR(imxdi->nvmem);
+ goto err;
+ }
+
+ ret = rtc_register(&imxdi->rtc);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ clk_disable(imxdi->clk);
+
+ return ret;
+}
+
+static __maybe_unused const struct of_device_id dryice_dt_ids[] = {
+ { .compatible = "fsl,imx25-rtc" },
+ { /* sentinel */ }
+};
+
+static struct driver_d dryice_rtc_driver = {
+ .name = "imx-di-rtc",
+ .probe = dryice_rtc_probe,
+ .of_compatible = DRV_OF_COMPAT(dryice_dt_ids),
+};
+device_platform_driver(dryice_rtc_driver);
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index fed628c589..d687105ea4 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -6,6 +6,13 @@ config SPI
if SPI
+config SPI_MEM
+ bool "SPI memory extension"
+ help
+ Enable this option if you want to enable the SPI memory extension.
+ This extension is meant to simplify interaction with SPI memories
+ by providing a high-level interface to send memory-like commands.
+
config DRIVER_SPI_ALTERA
bool "Altera SPI Master driver"
depends on NIOS2
@@ -18,6 +25,17 @@ config DRIVER_SPI_ATMEL
bool "Atmel (AT91) SPI Master driver"
depends on ARCH_AT91
+config DRIVER_SPI_FSL_QUADSPI
+ bool "Freescale QSPI controller"
+ depends on ARCH_IMX25 || ARCH_IMX31 || ARCH_IMX35 || ARCH_IMX50 || ARCH_IMX53 || ARCH_LAYERSCAPE
+ depends on SPI_MEM
+ help
+ This enables support for the Quad SPI controller in master mode.
+ Up to four flash chips can be connected on two buses with two
+ chipselects each.
+ This controller does not support generic SPI messages. It only
+ supports the high-level SPI memory interface.
+
config DRIVER_SPI_GPIO
bool "GPIO SPI Master driver"
depends on GPIOLIB
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 2329cbfb8d..dd8a8cb8b0 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -1,6 +1,8 @@
obj-$(CONFIG_SPI) += spi.o
+obj-$(CONFIG_SPI_MEM) += spi-mem.o
obj-$(CONFIG_DRIVER_SPI_ATH79) += ath79_spi.o
obj-$(CONFIG_DRIVER_SPI_GPIO) += gpio_spi.o
+obj-$(CONFIG_DRIVER_SPI_FSL_QUADSPI) += spi-fsl-qspi.o
obj-$(CONFIG_DRIVER_SPI_IMX) += imx_spi.o
obj-$(CONFIG_DRIVER_SPI_MVEBU) += mvebu_spi.o
obj-$(CONFIG_DRIVER_SPI_MXS) += mxs_spi.o
diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c
new file mode 100644
index 0000000000..e22c3099fe
--- /dev/null
+++ b/drivers/spi/spi-fsl-qspi.c
@@ -0,0 +1,869 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Freescale QuadSPI driver.
+ *
+ * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2018 Bootlin
+ * Copyright (C) 2018 exceet electronics GmbH
+ * Copyright (C) 2018 Kontron Electronics GmbH
+ *
+ * Transition to SPI MEM interface:
+ * Authors:
+ * Boris Brezillon <bbrezillon@kernel.org>
+ * Frieder Schrempf <frieder.schrempf@kontron.de>
+ * Yogesh Gaur <yogeshnarayan.gaur@nxp.com>
+ * Suresh Gupta <suresh.gupta@nxp.com>
+ *
+ * Based on the original fsl-quadspi.c spi-nor driver:
+ * Author: Freescale Semiconductor, Inc.
+ *
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <errno.h>
+#include <init.h>
+#include <io.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/mutex.h>
+#include <linux/sizes.h>
+#include <of.h>
+#include <of_device.h>
+
+#include <spi/spi.h>
+#include <linux/spi/spi-mem.h>
+
+/*
+ * 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
+ * read operation, so let's use the last entry (15).
+ */
+#define SEQID_LUT 15
+
+/* Registers used by the driver */
+#define QUADSPI_MCR 0x00
+#define QUADSPI_MCR_RESERVED_MASK GENMASK(19, 16)
+#define QUADSPI_MCR_MDIS_MASK BIT(14)
+#define QUADSPI_MCR_CLR_TXF_MASK BIT(11)
+#define QUADSPI_MCR_CLR_RXF_MASK BIT(10)
+#define QUADSPI_MCR_DDR_EN_MASK BIT(7)
+#define QUADSPI_MCR_END_CFG_MASK GENMASK(3, 2)
+#define QUADSPI_MCR_SWRSTHD_MASK BIT(1)
+#define QUADSPI_MCR_SWRSTSD_MASK BIT(0)
+
+#define QUADSPI_IPCR 0x08
+#define QUADSPI_IPCR_SEQID(x) ((x) << 24)
+
+#define QUADSPI_BUF3CR 0x1c
+#define QUADSPI_BUF3CR_ALLMST_MASK BIT(31)
+#define QUADSPI_BUF3CR_ADATSZ(x) ((x) << 8)
+#define QUADSPI_BUF3CR_ADATSZ_MASK GENMASK(15, 8)
+
+#define QUADSPI_BFGENCR 0x20
+#define QUADSPI_BFGENCR_SEQID(x) ((x) << 12)
+
+#define QUADSPI_BUF0IND 0x30
+#define QUADSPI_BUF1IND 0x34
+#define QUADSPI_BUF2IND 0x38
+#define QUADSPI_SFAR 0x100
+
+#define QUADSPI_SMPR 0x108
+#define QUADSPI_SMPR_DDRSMP_MASK GENMASK(18, 16)
+#define QUADSPI_SMPR_FSDLY_MASK BIT(6)
+#define QUADSPI_SMPR_FSPHS_MASK BIT(5)
+#define QUADSPI_SMPR_HSENA_MASK BIT(0)
+
+#define QUADSPI_RBCT 0x110
+#define QUADSPI_RBCT_WMRK_MASK GENMASK(4, 0)
+#define QUADSPI_RBCT_RXBRD_USEIPS BIT(8)
+
+#define QUADSPI_TBSR 0x150
+#define QUADSPI_TBDR 0x154
+
+#define QUADSPI_SR 0x15c
+#define QUADSPI_SR_BUSY_MASK BIT(0)
+#define QUADSPI_SR_IP_ACC_MASK BIT(1)
+#define QUADSPI_SR_AHB_ACC_MASK BIT(2)
+
+#define QUADSPI_FR 0x160
+#define QUADSPI_FR_TFF_MASK BIT(0)
+
+#define QUADSPI_SPTRCLR 0x16c
+#define QUADSPI_SPTRCLR_IPPTRC BIT(8)
+#define QUADSPI_SPTRCLR_BFPTRC BIT(0)
+
+#define QUADSPI_SFA1AD 0x180
+#define QUADSPI_SFA2AD 0x184
+#define QUADSPI_SFB1AD 0x188
+#define QUADSPI_SFB2AD 0x18c
+#define QUADSPI_RBDR(x) (0x200 + ((x) * 4))
+
+#define QUADSPI_LUTKEY 0x300
+#define QUADSPI_LUTKEY_VALUE 0x5AF05AF0
+
+#define QUADSPI_LCKCR 0x304
+#define QUADSPI_LCKER_LOCK BIT(0)
+#define QUADSPI_LCKER_UNLOCK BIT(1)
+
+#define QUADSPI_RSER 0x164
+#define QUADSPI_RSER_TFIE BIT(0)
+
+#define QUADSPI_LUT_BASE 0x310
+#define QUADSPI_LUT_OFFSET (SEQID_LUT * 4 * 4)
+#define QUADSPI_LUT_REG(idx) \
+ (QUADSPI_LUT_BASE + QUADSPI_LUT_OFFSET + (idx) * 4)
+
+/* Instruction set for the LUT register */
+#define LUT_STOP 0
+#define LUT_CMD 1
+#define LUT_ADDR 2
+#define LUT_DUMMY 3
+#define LUT_MODE 4
+#define LUT_MODE2 5
+#define LUT_MODE4 6
+#define LUT_FSL_READ 7
+#define LUT_FSL_WRITE 8
+#define LUT_JMP_ON_CS 9
+#define LUT_ADDR_DDR 10
+#define LUT_MODE_DDR 11
+#define LUT_MODE2_DDR 12
+#define LUT_MODE4_DDR 13
+#define LUT_FSL_READ_DDR 14
+#define LUT_FSL_WRITE_DDR 15
+#define LUT_DATA_LEARN 16
+
+/*
+ * The PAD definitions for LUT register.
+ *
+ * The pad stands for the number of IO lines [0:3].
+ * For example, the quad read needs four IO lines,
+ * so you should use LUT_PAD(4).
+ */
+#define LUT_PAD(x) (fls(x) - 1)
+
+/*
+ * Macro for constructing the LUT entries with the following
+ * register layout:
+ *
+ * ---------------------------------------------------
+ * | INSTR1 | PAD1 | OPRND1 | INSTR0 | PAD0 | OPRND0 |
+ * ---------------------------------------------------
+ */
+#define LUT_DEF(idx, ins, pad, opr) \
+ ((((ins) << 10) | ((pad) << 8) | (opr)) << (((idx) % 2) * 16))
+
+/* Controller needs driver to swap endianness */
+#define QUADSPI_QUIRK_SWAP_ENDIAN BIT(0)
+
+/* Controller needs 4x internal clock */
+#define QUADSPI_QUIRK_4X_INT_CLK BIT(1)
+
+/*
+ * TKT253890, the controller needs the driver to fill the txfifo with
+ * 16 bytes at least to trigger a data transfer, even though the extra
+ * data won't be transferred.
+ */
+#define QUADSPI_QUIRK_TKT253890 BIT(2)
+
+/* TKT245618, the controller cannot wake up from wait mode */
+#define QUADSPI_QUIRK_TKT245618 BIT(3)
+
+/*
+ * Controller adds QSPI_AMBA_BASE (base address of the mapped memory)
+ * internally. No need to add it when setting SFXXAD and SFAR registers
+ */
+#define QUADSPI_QUIRK_BASE_INTERNAL BIT(4)
+
+struct fsl_qspi_devtype_data {
+ unsigned int rxfifo;
+ unsigned int txfifo;
+ unsigned int ahb_buf_size;
+ unsigned int quirks;
+ bool little_endian;
+};
+
+static const struct fsl_qspi_devtype_data vybrid_data = {
+ .rxfifo = SZ_128,
+ .txfifo = SZ_64,
+ .ahb_buf_size = SZ_1K,
+ .quirks = QUADSPI_QUIRK_SWAP_ENDIAN,
+ .little_endian = true,
+};
+
+static const struct fsl_qspi_devtype_data imx6sx_data = {
+ .rxfifo = SZ_128,
+ .txfifo = SZ_512,
+ .ahb_buf_size = SZ_1K,
+ .quirks = QUADSPI_QUIRK_4X_INT_CLK | QUADSPI_QUIRK_TKT245618,
+ .little_endian = true,
+};
+
+static const struct fsl_qspi_devtype_data imx7d_data = {
+ .rxfifo = SZ_512,
+ .txfifo = SZ_512,
+ .ahb_buf_size = SZ_1K,
+ .quirks = QUADSPI_QUIRK_TKT253890 | QUADSPI_QUIRK_4X_INT_CLK,
+ .little_endian = true,
+};
+
+static const struct fsl_qspi_devtype_data imx6ul_data = {
+ .rxfifo = SZ_128,
+ .txfifo = SZ_512,
+ .ahb_buf_size = SZ_1K,
+ .quirks = QUADSPI_QUIRK_TKT253890 | QUADSPI_QUIRK_4X_INT_CLK,
+ .little_endian = true,
+};
+
+static const struct fsl_qspi_devtype_data ls1021a_data = {
+ .rxfifo = SZ_128,
+ .txfifo = SZ_64,
+ .ahb_buf_size = SZ_1K,
+ .quirks = 0,
+ .little_endian = false,
+};
+
+static const struct fsl_qspi_devtype_data ls2080a_data = {
+ .rxfifo = SZ_128,
+ .txfifo = SZ_64,
+ .ahb_buf_size = SZ_1K,
+ .quirks = QUADSPI_QUIRK_TKT253890 | QUADSPI_QUIRK_BASE_INTERNAL,
+ .little_endian = true,
+};
+
+struct fsl_qspi {
+ void __iomem *iobase;
+ void __iomem *ahb_addr;
+ u32 memmap_phy;
+ struct clk *clk, *clk_en;
+ struct device_d *dev;
+ struct spi_controller ctlr;
+ const struct fsl_qspi_devtype_data *devtype_data;
+ struct mutex lock;
+ int selected;
+};
+
+static inline int needs_swap_endian(struct fsl_qspi *q)
+{
+ return q->devtype_data->quirks & QUADSPI_QUIRK_SWAP_ENDIAN;
+}
+
+static inline int needs_4x_clock(struct fsl_qspi *q)
+{
+ return q->devtype_data->quirks & QUADSPI_QUIRK_4X_INT_CLK;
+}
+
+static inline int needs_fill_txfifo(struct fsl_qspi *q)
+{
+ return q->devtype_data->quirks & QUADSPI_QUIRK_TKT253890;
+}
+
+static inline int needs_amba_base_offset(struct fsl_qspi *q)
+{
+ return !(q->devtype_data->quirks & QUADSPI_QUIRK_BASE_INTERNAL);
+}
+
+/*
+ * An IC bug makes it necessary to rearrange the 32-bit data.
+ * Later chips, such as IMX6SLX, have fixed this bug.
+ */
+static inline u32 fsl_qspi_endian_xchg(struct fsl_qspi *q, u32 a)
+{
+ return needs_swap_endian(q) ? __swab32(a) : a;
+}
+
+/*
+ * R/W functions for big- or little-endian registers:
+ * The QSPI controller's endianness is independent of
+ * the CPU core's endianness. So far, although the CPU
+ * core is little-endian the QSPI controller can use
+ * big-endian or little-endian.
+ */
+static void qspi_writel(struct fsl_qspi *q, u32 val, void __iomem *addr)
+{
+ if (q->devtype_data->little_endian)
+ iowrite32(val, addr);
+ else
+ iowrite32be(val, addr);
+}
+
+static u32 qspi_readl(struct fsl_qspi *q, void __iomem *addr)
+{
+ if (q->devtype_data->little_endian)
+ return ioread32(addr);
+
+ return ioread32be(addr);
+}
+
+static int fsl_qspi_check_buswidth(struct fsl_qspi *q, u8 width)
+{
+ switch (width) {
+ case 1:
+ case 2:
+ case 4:
+ return 0;
+ }
+
+ return -ENOTSUPP;
+}
+
+static bool fsl_qspi_supports_op(struct spi_mem *mem,
+ const struct spi_mem_op *op)
+{
+ struct fsl_qspi *q = spi_controller_get_devdata(mem->spi->controller);
+ int ret;
+
+ ret = fsl_qspi_check_buswidth(q, op->cmd.buswidth);
+
+ if (op->addr.nbytes)
+ ret |= fsl_qspi_check_buswidth(q, op->addr.buswidth);
+
+ if (op->dummy.nbytes)
+ ret |= fsl_qspi_check_buswidth(q, op->dummy.buswidth);
+
+ if (op->data.nbytes)
+ ret |= fsl_qspi_check_buswidth(q, op->data.buswidth);
+
+ if (ret)
+ return false;
+
+ /*
+ * The number of instructions needed for the op, needs
+ * to fit into a single LUT entry.
+ */
+ if (op->addr.nbytes +
+ (op->dummy.nbytes ? 1:0) +
+ (op->data.nbytes ? 1:0) > 6)
+ return false;
+
+ /* Max 64 dummy clock cycles supported */
+ if (op->dummy.nbytes &&
+ (op->dummy.nbytes * 8 / op->dummy.buswidth > 64))
+ return false;
+
+ /* Max data length, check controller limits and alignment */
+ if (op->data.dir == SPI_MEM_DATA_IN &&
+ (op->data.nbytes > q->devtype_data->ahb_buf_size ||
+ (op->data.nbytes > q->devtype_data->rxfifo - 4 &&
+ !IS_ALIGNED(op->data.nbytes, 8))))
+ return false;
+
+ if (op->data.dir == SPI_MEM_DATA_OUT &&
+ op->data.nbytes > q->devtype_data->txfifo)
+ return false;
+
+ return true;
+}
+
+static void fsl_qspi_prepare_lut(struct fsl_qspi *q,
+ const struct spi_mem_op *op)
+{
+ void __iomem *base = q->iobase;
+ u32 lutval[4] = {};
+ int lutidx = 1, i;
+
+ lutval[0] |= LUT_DEF(0, LUT_CMD, LUT_PAD(op->cmd.buswidth),
+ op->cmd.opcode);
+
+ /*
+ * For some unknown reason, using LUT_ADDR doesn't work in some
+ * cases (at least with only one byte long addresses), so
+ * let's use LUT_MODE to write the address bytes one by one
+ */
+ for (i = 0; i < op->addr.nbytes; i++) {
+ u8 addrbyte = op->addr.val >> (8 * (op->addr.nbytes - i - 1));
+
+ lutval[lutidx / 2] |= LUT_DEF(lutidx, LUT_MODE,
+ LUT_PAD(op->addr.buswidth),
+ addrbyte);
+ lutidx++;
+ }
+
+ if (op->dummy.nbytes) {
+ lutval[lutidx / 2] |= LUT_DEF(lutidx, LUT_DUMMY,
+ LUT_PAD(op->dummy.buswidth),
+ op->dummy.nbytes * 8 /
+ op->dummy.buswidth);
+ lutidx++;
+ }
+
+ if (op->data.nbytes) {
+ lutval[lutidx / 2] |= LUT_DEF(lutidx,
+ op->data.dir == SPI_MEM_DATA_IN ?
+ LUT_FSL_READ : LUT_FSL_WRITE,
+ LUT_PAD(op->data.buswidth),
+ 0);
+ lutidx++;
+ }
+
+ lutval[lutidx / 2] |= LUT_DEF(lutidx, LUT_STOP, 0, 0);
+
+ /* unlock LUT */
+ qspi_writel(q, QUADSPI_LUTKEY_VALUE, q->iobase + QUADSPI_LUTKEY);
+ qspi_writel(q, QUADSPI_LCKER_UNLOCK, q->iobase + QUADSPI_LCKCR);
+
+ /* fill LUT */
+ for (i = 0; i < ARRAY_SIZE(lutval); i++)
+ qspi_writel(q, lutval[i], base + QUADSPI_LUT_REG(i));
+
+ /* lock LUT */
+ qspi_writel(q, QUADSPI_LUTKEY_VALUE, q->iobase + QUADSPI_LUTKEY);
+ qspi_writel(q, QUADSPI_LCKER_LOCK, q->iobase + QUADSPI_LCKCR);
+}
+
+static int fsl_qspi_clk_prep_enable(struct fsl_qspi *q)
+{
+ int ret;
+
+ ret = clk_enable(q->clk_en);
+ if (ret)
+ return ret;
+
+ ret = clk_enable(q->clk);
+ if (ret) {
+ clk_disable(q->clk_en);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void fsl_qspi_clk_disable_unprep(struct fsl_qspi *q)
+{
+ clk_disable(q->clk);
+ clk_disable(q->clk_en);
+}
+
+/*
+ * If we have changed the content of the flash by writing or erasing, or if we
+ * read from flash with a different offset into the page buffer, we need to
+ * invalidate the AHB buffer. If we do not do so, we may read out the wrong
+ * data. The spec tells us reset the AHB domain and Serial Flash domain at
+ * the same time.
+ */
+static void fsl_qspi_invalidate(struct fsl_qspi *q)
+{
+ u32 reg;
+
+ reg = qspi_readl(q, q->iobase + QUADSPI_MCR);
+ reg |= QUADSPI_MCR_SWRSTHD_MASK | QUADSPI_MCR_SWRSTSD_MASK;
+ qspi_writel(q, reg, q->iobase + QUADSPI_MCR);
+
+ /*
+ * The minimum delay : 1 AHB + 2 SFCK clocks.
+ * Delay 1 us is enough.
+ */
+ udelay(1);
+
+ reg &= ~(QUADSPI_MCR_SWRSTHD_MASK | QUADSPI_MCR_SWRSTSD_MASK);
+ qspi_writel(q, reg, q->iobase + QUADSPI_MCR);
+}
+
+static void fsl_qspi_select_mem(struct fsl_qspi *q, struct spi_device *spi)
+{
+ unsigned long rate = spi->max_speed_hz;
+ int ret;
+
+ if (q->selected == spi->chip_select)
+ return;
+
+ if (needs_4x_clock(q))
+ rate *= 4;
+
+ fsl_qspi_clk_disable_unprep(q);
+
+ ret = clk_set_rate(q->clk, rate);
+ if (ret)
+ return;
+
+ ret = fsl_qspi_clk_prep_enable(q);
+ if (ret)
+ return;
+
+ q->selected = spi->chip_select;
+
+ fsl_qspi_invalidate(q);
+}
+
+static void fsl_qspi_read_ahb(struct fsl_qspi *q, const struct spi_mem_op *op)
+{
+ memcpy(op->data.buf.in,
+ q->ahb_addr + q->selected * q->devtype_data->ahb_buf_size,
+ op->data.nbytes);
+}
+
+static void fsl_qspi_fill_txfifo(struct fsl_qspi *q,
+ const struct spi_mem_op *op)
+{
+ void __iomem *base = q->iobase;
+ int i;
+ u32 val;
+
+ for (i = 0; i < ALIGN_DOWN(op->data.nbytes, 4); i += 4) {
+ memcpy(&val, op->data.buf.out + i, 4);
+ val = fsl_qspi_endian_xchg(q, val);
+ qspi_writel(q, val, base + QUADSPI_TBDR);
+ }
+
+ if (i < op->data.nbytes) {
+ memcpy(&val, op->data.buf.out + i, op->data.nbytes - i);
+ val = fsl_qspi_endian_xchg(q, val);
+ qspi_writel(q, val, base + QUADSPI_TBDR);
+ }
+
+ if (needs_fill_txfifo(q)) {
+ for (i = op->data.nbytes; i < 16; i += 4)
+ qspi_writel(q, 0, base + QUADSPI_TBDR);
+ }
+}
+
+static void fsl_qspi_read_rxfifo(struct fsl_qspi *q,
+ const struct spi_mem_op *op)
+{
+ void __iomem *base = q->iobase;
+ int i;
+ u8 *buf = op->data.buf.in;
+ u32 val;
+
+ for (i = 0; i < ALIGN_DOWN(op->data.nbytes, 4); i += 4) {
+ val = qspi_readl(q, base + QUADSPI_RBDR(i / 4));
+ val = fsl_qspi_endian_xchg(q, val);
+ memcpy(buf + i, &val, 4);
+ }
+
+ if (i < op->data.nbytes) {
+ val = qspi_readl(q, base + QUADSPI_RBDR(i / 4));
+ val = fsl_qspi_endian_xchg(q, val);
+ memcpy(buf + i, &val, op->data.nbytes - i);
+ }
+}
+
+static int fsl_qspi_do_op(struct fsl_qspi *q, const struct spi_mem_op *op)
+{
+ void __iomem *base = q->iobase;
+ uint64_t timeout = 1000;
+ uint64_t start;
+ u32 reg;
+
+ /*
+ * Always start the sequence at the same index since we update
+ * the LUT at each exec_op() call. And also specify the DATA
+ * length, since it's has not been specified in the LUT.
+ */
+ qspi_writel(q, op->data.nbytes | QUADSPI_IPCR_SEQID(SEQID_LUT),
+ base + QUADSPI_IPCR);
+
+ start = get_time_ns();
+ do {
+ reg = qspi_readl(q, q->iobase + QUADSPI_FR);
+ if (reg & QUADSPI_FR_TFF_MASK) {
+ /* clear interrupt */
+ qspi_writel(q, reg, q->iobase + QUADSPI_FR);
+ if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_IN)
+ fsl_qspi_read_rxfifo(q, op);
+ return 0;
+ }
+
+ } while (!is_timeout(start, timeout * MSECOND));
+
+ return -ETIMEDOUT;
+}
+
+static int fsl_qspi_readl_poll_tout(struct fsl_qspi *q, void __iomem *base,
+ u32 mask, u32 delay_us, u32 timeout_us)
+{
+ uint64_t timeout = MSEC_PER_SEC * timeout_us;
+ u32 reg;
+
+ if (!q->devtype_data->little_endian)
+ mask = (u32)cpu_to_be32(mask);
+
+ return readl_poll_timeout(base, reg, !(reg & mask), timeout);
+}
+
+static int fsl_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+ struct fsl_qspi *q = spi_controller_get_devdata(mem->spi->controller);
+ void __iomem *base;
+ u32 addr_offset = 0;
+ int err = 0;
+
+ base = q->iobase;
+
+ mutex_lock(&q->lock);
+
+ /* wait for the controller being ready */
+ fsl_qspi_readl_poll_tout(q, base + QUADSPI_SR, (QUADSPI_SR_IP_ACC_MASK |
+ QUADSPI_SR_AHB_ACC_MASK), 10, 1000);
+
+ fsl_qspi_select_mem(q, mem->spi);
+
+ if (needs_amba_base_offset(q))
+ addr_offset = q->memmap_phy;
+
+ qspi_writel(q,
+ q->selected * q->devtype_data->ahb_buf_size + addr_offset,
+ base + QUADSPI_SFAR);
+
+ qspi_writel(q, qspi_readl(q, base + QUADSPI_MCR) |
+ QUADSPI_MCR_CLR_RXF_MASK | QUADSPI_MCR_CLR_TXF_MASK,
+ base + QUADSPI_MCR);
+
+ qspi_writel(q, QUADSPI_SPTRCLR_BFPTRC | QUADSPI_SPTRCLR_IPPTRC,
+ base + QUADSPI_SPTRCLR);
+
+ fsl_qspi_prepare_lut(q, 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.
+ */
+ if (op->data.nbytes > (q->devtype_data->rxfifo - 4) &&
+ op->data.dir == SPI_MEM_DATA_IN) {
+ fsl_qspi_read_ahb(q, op);
+ } else {
+ qspi_writel(q, QUADSPI_RBCT_WMRK_MASK |
+ QUADSPI_RBCT_RXBRD_USEIPS, base + QUADSPI_RBCT);
+
+ if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_OUT)
+ fsl_qspi_fill_txfifo(q, op);
+
+ err = fsl_qspi_do_op(q, op);
+ }
+
+ /* Invalidate the data in the AHB buffer. */
+ fsl_qspi_invalidate(q);
+
+ mutex_unlock(&q->lock);
+
+ return err;
+}
+
+static int fsl_qspi_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
+{
+ struct fsl_qspi *q = spi_controller_get_devdata(mem->spi->controller);
+
+ if (op->data.dir == SPI_MEM_DATA_OUT) {
+ if (op->data.nbytes > q->devtype_data->txfifo)
+ op->data.nbytes = q->devtype_data->txfifo;
+ } else {
+ if (op->data.nbytes > q->devtype_data->ahb_buf_size)
+ op->data.nbytes = q->devtype_data->ahb_buf_size;
+ else if (op->data.nbytes > (q->devtype_data->rxfifo - 4))
+ op->data.nbytes = ALIGN_DOWN(op->data.nbytes, 8);
+ }
+
+ return 0;
+}
+
+static int fsl_qspi_setup(struct spi_device *spi)
+{
+ struct fsl_qspi *q = container_of(spi->controller, struct fsl_qspi, ctlr);
+ void __iomem *base = q->iobase;
+ u32 reg, addr_offset = 0;
+ int ret;
+
+ /* disable and unprepare clock to avoid glitch pass to controller */
+ fsl_qspi_clk_disable_unprep(q);
+
+ /* the default frequency, we will change it later if necessary. */
+ ret = clk_set_rate(q->clk, 66000000);
+ if (ret)
+ return ret;
+
+ ret = fsl_qspi_clk_prep_enable(q);
+ if (ret)
+ return ret;
+
+ /* Reset the module */
+ qspi_writel(q, QUADSPI_MCR_SWRSTSD_MASK | QUADSPI_MCR_SWRSTHD_MASK,
+ base + QUADSPI_MCR);
+ udelay(1);
+
+ /* Disable the module */
+ qspi_writel(q, QUADSPI_MCR_MDIS_MASK | QUADSPI_MCR_RESERVED_MASK,
+ base + QUADSPI_MCR);
+
+ reg = qspi_readl(q, base + QUADSPI_SMPR);
+ qspi_writel(q, reg & ~(QUADSPI_SMPR_FSDLY_MASK
+ | QUADSPI_SMPR_FSPHS_MASK
+ | QUADSPI_SMPR_HSENA_MASK
+ | QUADSPI_SMPR_DDRSMP_MASK), base + QUADSPI_SMPR);
+
+ /* We only use the buffer3 for AHB read */
+ qspi_writel(q, 0, base + QUADSPI_BUF0IND);
+ qspi_writel(q, 0, base + QUADSPI_BUF1IND);
+ qspi_writel(q, 0, base + QUADSPI_BUF2IND);
+
+ qspi_writel(q, QUADSPI_BFGENCR_SEQID(SEQID_LUT),
+ q->iobase + QUADSPI_BFGENCR);
+ qspi_writel(q, QUADSPI_RBCT_WMRK_MASK, base + QUADSPI_RBCT);
+ qspi_writel(q, QUADSPI_BUF3CR_ALLMST_MASK |
+ QUADSPI_BUF3CR_ADATSZ(q->devtype_data->ahb_buf_size / 8),
+ base + QUADSPI_BUF3CR);
+
+ if (needs_amba_base_offset(q))
+ addr_offset = q->memmap_phy;
+
+ /*
+ * In HW there can be a maximum of four chips on two buses with
+ * two chip selects on each bus. We use four chip selects in SW
+ * to differentiate between the four chips.
+ * We use ahb_buf_size for each chip and set SFA1AD, SFA2AD, SFB1AD,
+ * SFB2AD accordingly.
+ */
+ qspi_writel(q, q->devtype_data->ahb_buf_size + addr_offset,
+ base + QUADSPI_SFA1AD);
+ qspi_writel(q, q->devtype_data->ahb_buf_size * 2 + addr_offset,
+ base + QUADSPI_SFA2AD);
+ qspi_writel(q, q->devtype_data->ahb_buf_size * 3 + addr_offset,
+ base + QUADSPI_SFB1AD);
+ qspi_writel(q, q->devtype_data->ahb_buf_size * 4 + addr_offset,
+ base + QUADSPI_SFB2AD);
+
+ q->selected = -1;
+
+ /* Enable the module */
+ qspi_writel(q, QUADSPI_MCR_RESERVED_MASK | QUADSPI_MCR_END_CFG_MASK,
+ base + QUADSPI_MCR);
+
+ /* clear all interrupt status */
+ qspi_writel(q, 0xffffffff, q->iobase + QUADSPI_FR);
+
+ /* enable the interrupt */
+ qspi_writel(q, QUADSPI_RSER_TFIE, q->iobase + QUADSPI_RSER);
+
+ return 0;
+}
+
+static const char *fsl_qspi_get_name(struct spi_mem *mem)
+{
+ struct fsl_qspi *q = spi_controller_get_devdata(mem->spi->controller);
+ struct device_d *dev = &mem->spi->dev;
+ const char *name;
+
+ /*
+ * In order to keep mtdparts compatible with the old MTD driver at
+ * mtd/spi-nor/fsl-quadspi.c, we set a custom name derived from the
+ * platform_device of the controller.
+ */
+ if (of_get_available_child_count(q->dev->device_node) == 1)
+ return dev_name(q->dev);
+
+ name = basprintf("%s-%d", dev_name(q->dev), mem->spi->chip_select);
+ if (!name) {
+ dev_err(dev, "failed to get memory for custom flash name\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return name;
+}
+
+static const struct spi_controller_mem_ops fsl_qspi_mem_ops = {
+ .adjust_op_size = fsl_qspi_adjust_op_size,
+ .supports_op = fsl_qspi_supports_op,
+ .exec_op = fsl_qspi_exec_op,
+ .get_name = fsl_qspi_get_name,
+};
+
+static int fsl_qspi_probe(struct device_d *dev)
+{
+ struct spi_controller *ctlr;
+ struct resource *res;
+ struct fsl_qspi *q;
+ int ret;
+
+ q = xzalloc(sizeof(*q));
+
+ ctlr = &q->ctlr;
+
+ /* /\* ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | *\/ */
+ /* /\* SPI_TX_DUAL | SPI_TX_QUAD; *\/ */
+
+ q->dev = dev;
+ q->devtype_data = of_device_get_match_data(dev);
+ if (!q->devtype_data) {
+ ret = -ENODEV;
+ goto err_put_ctrl;
+ }
+
+ ctlr->dev = dev;
+ ctlr->bus_num = dev->id;
+ ctlr->setup = fsl_qspi_setup;
+ ctlr->num_chipselect = 4;
+ ctlr->mem_ops = &fsl_qspi_mem_ops;
+
+ spi_controller_set_devdata(ctlr, q);
+
+ /* find the resources */
+ res = dev_request_mem_resource(dev, 0);
+ q->iobase = IOMEM(res->start);
+ if (IS_ERR(q->iobase)) {
+ ret = PTR_ERR(q->iobase);
+ goto err_put_ctrl;
+ }
+
+ res = dev_request_mem_resource(dev, 1);
+ q->ahb_addr = IOMEM(res->start);
+ if (IS_ERR(q->ahb_addr)) {
+ ret = PTR_ERR(q->ahb_addr);
+ goto err_put_ctrl;
+ }
+
+ q->memmap_phy = res->start;
+
+ /* find the clocks */
+ q->clk_en = clk_get(dev, "qspi_en");
+ if (IS_ERR(q->clk_en)) {
+ ret = PTR_ERR(q->clk_en);
+ goto err_put_ctrl;
+ }
+
+ q->clk = clk_get(dev, "qspi");
+ if (IS_ERR(q->clk)) {
+ ret = PTR_ERR(q->clk);
+ goto err_put_ctrl;
+ }
+
+ ret = fsl_qspi_clk_prep_enable(q);
+ if (ret) {
+ dev_err(dev, "can not enable the clock\n");
+ goto err_put_ctrl;
+ }
+
+ mutex_init(&q->lock);
+
+ ret = spi_register_controller(ctlr);
+ if (ret)
+ goto err_disable_clk;
+
+ return 0;
+
+err_disable_clk:
+ fsl_qspi_clk_disable_unprep(q);
+
+err_put_ctrl:
+ dev_err(dev, "Freescale QuadSPI probe failed\n");
+ return ret;
+}
+
+static const struct of_device_id fsl_qspi_dt_ids[] = {
+ { .compatible = "fsl,vf610-qspi", .data = &vybrid_data, },
+ { .compatible = "fsl,imx6sx-qspi", .data = &imx6sx_data, },
+ { .compatible = "fsl,imx7d-qspi", .data = &imx7d_data, },
+ { .compatible = "fsl,imx6ul-qspi", .data = &imx6ul_data, },
+ { .compatible = "fsl,ls1021a-qspi", .data = &ls1021a_data, },
+ { .compatible = "fsl,ls2080a-qspi", .data = &ls2080a_data, },
+ { /* sentinel */ }
+};
+
+static struct driver_d fsl_qspi_driver = {
+ .name = "fsl-quadspi",
+ .probe = fsl_qspi_probe,
+ .of_compatible = DRV_OF_COMPAT(fsl_qspi_dt_ids),
+};
+device_platform_driver(fsl_qspi_driver);
diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
new file mode 100644
index 0000000000..b438ed3dcc
--- /dev/null
+++ b/drivers/spi/spi-mem.c
@@ -0,0 +1,524 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Exceet Electronics GmbH
+ * Copyright (C) 2018 Bootlin
+ *
+ * Author: Boris Brezillon <boris.brezillon@bootlin.com>
+ */
+#include <common.h>
+#include <module.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi-mem.h>
+#include <spi/spi.h>
+
+#define SPI_MEM_MAX_BUSWIDTH 8
+
+static int spi_check_buswidth_req(struct spi_mem *mem, u8 buswidth, bool tx)
+{
+ u32 mode = mem->spi->mode;
+
+ switch (buswidth) {
+ case 1:
+ return 0;
+
+ case 2:
+ if ((tx && (mode & (SPI_TX_DUAL | SPI_TX_QUAD))) ||
+ (!tx && (mode & (SPI_RX_DUAL | SPI_RX_QUAD))))
+ return 0;
+
+ break;
+
+ case 4:
+ if ((tx && (mode & SPI_TX_QUAD)) ||
+ (!tx && (mode & SPI_RX_QUAD)))
+ return 0;
+
+ break;
+
+ case 8:
+ if ((tx && (mode & SPI_TX_OCTAL)) ||
+ (!tx && (mode & SPI_RX_OCTAL)))
+ return 0;
+
+ break;
+
+ default:
+ break;
+ }
+
+ return -ENOTSUPP;
+}
+
+static bool spi_mem_default_supports_op(struct spi_mem *mem,
+ const struct spi_mem_op *op)
+{
+ if (spi_check_buswidth_req(mem, op->cmd.buswidth, true))
+ return false;
+
+ if (op->addr.nbytes &&
+ spi_check_buswidth_req(mem, op->addr.buswidth, true))
+ return false;
+
+ if (op->dummy.nbytes &&
+ spi_check_buswidth_req(mem, op->dummy.buswidth, true))
+ return false;
+
+ if (op->data.dir != SPI_MEM_NO_DATA &&
+ spi_check_buswidth_req(mem, op->data.buswidth,
+ op->data.dir == SPI_MEM_DATA_OUT))
+ return false;
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(spi_mem_default_supports_op);
+
+static bool spi_mem_buswidth_is_valid(u8 buswidth)
+{
+ if (hweight8(buswidth) > 1 || buswidth > SPI_MEM_MAX_BUSWIDTH)
+ return false;
+
+ return true;
+}
+
+static int spi_mem_check_op(const struct spi_mem_op *op)
+{
+ if (!op->cmd.buswidth)
+ return -EINVAL;
+
+ if ((op->addr.nbytes && !op->addr.buswidth) ||
+ (op->dummy.nbytes && !op->dummy.buswidth) ||
+ (op->data.nbytes && !op->data.buswidth))
+ return -EINVAL;
+
+ if (!spi_mem_buswidth_is_valid(op->cmd.buswidth) ||
+ !spi_mem_buswidth_is_valid(op->addr.buswidth) ||
+ !spi_mem_buswidth_is_valid(op->dummy.buswidth) ||
+ !spi_mem_buswidth_is_valid(op->data.buswidth))
+ return -EINVAL;
+
+ return 0;
+}
+
+static bool spi_mem_internal_supports_op(struct spi_mem *mem,
+ const struct spi_mem_op *op)
+{
+ struct spi_controller *ctlr = mem->spi->controller;
+
+ if (ctlr->mem_ops && ctlr->mem_ops->supports_op)
+ return ctlr->mem_ops->supports_op(mem, op);
+
+ return spi_mem_default_supports_op(mem, op);
+}
+
+/**
+ * spi_mem_supports_op() - Check if a memory device and the controller it is
+ * connected to support a specific memory operation
+ * @mem: the SPI memory
+ * @op: the memory operation to check
+ *
+ * Some controllers are only supporting Single or Dual IOs, others might only
+ * support specific opcodes, or it can even be that the controller and device
+ * both support Quad IOs but the hardware prevents you from using it because
+ * only 2 IO lines are connected.
+ *
+ * This function checks whether a specific operation is supported.
+ *
+ * Return: true if @op is supported, false otherwise.
+ */
+bool spi_mem_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+ if (spi_mem_check_op(op))
+ return false;
+
+ return spi_mem_internal_supports_op(mem, op);
+}
+EXPORT_SYMBOL_GPL(spi_mem_supports_op);
+
+static int spi_mem_access_start(struct spi_mem *mem)
+{
+ return 0;
+}
+
+static void spi_mem_access_end(struct spi_mem *mem)
+{
+ return;
+}
+
+/**
+ * spi_mem_exec_op() - Execute a memory operation
+ * @mem: the SPI memory
+ * @op: the memory operation to execute
+ *
+ * Executes a memory operation.
+ *
+ * This function first checks that @op is supported and then tries to execute
+ * it.
+ *
+ * Return: 0 in case of success, a negative error code otherwise.
+ */
+int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+ unsigned int tmpbufsize, xferpos = 0, totalxferlen = 0;
+ struct spi_controller *ctlr = mem->spi->controller;
+ struct spi_transfer xfers[4] = { };
+ struct spi_message msg;
+ u8 *tmpbuf;
+ int ret;
+
+ ret = spi_mem_check_op(op);
+ if (ret)
+ return ret;
+
+ if (!spi_mem_internal_supports_op(mem, op))
+ return -ENOTSUPP;
+
+ if (ctlr->mem_ops) {
+ ret = spi_mem_access_start(mem);
+ if (ret)
+ return ret;
+
+ ret = ctlr->mem_ops->exec_op(mem, op);
+
+ spi_mem_access_end(mem);
+
+ /*
+ * Some controllers only optimize specific paths (typically the
+ * read path) and expect the core to use the regular SPI
+ * interface in other cases.
+ */
+ if (!ret || ret != -ENOTSUPP)
+ return ret;
+ }
+
+ tmpbufsize = sizeof(op->cmd.opcode) + op->addr.nbytes +
+ op->dummy.nbytes;
+
+ /*
+ * Allocate a buffer to transmit the CMD, ADDR cycles with kmalloc() so
+ * we're guaranteed that this buffer is DMA-able, as required by the
+ * SPI layer.
+ */
+ tmpbuf = kzalloc(tmpbufsize, GFP_KERNEL);
+ if (!tmpbuf)
+ return -ENOMEM;
+
+ spi_message_init(&msg);
+
+ tmpbuf[0] = op->cmd.opcode;
+ xfers[xferpos].tx_buf = tmpbuf;
+ xfers[xferpos].len = sizeof(op->cmd.opcode);
+ spi_message_add_tail(&xfers[xferpos], &msg);
+ xferpos++;
+ totalxferlen++;
+
+ if (op->addr.nbytes) {
+ int i;
+
+ for (i = 0; i < op->addr.nbytes; i++)
+ tmpbuf[i + 1] = op->addr.val >>
+ (8 * (op->addr.nbytes - i - 1));
+
+ xfers[xferpos].tx_buf = tmpbuf + 1;
+ xfers[xferpos].len = op->addr.nbytes;
+ spi_message_add_tail(&xfers[xferpos], &msg);
+ xferpos++;
+ totalxferlen += op->addr.nbytes;
+ }
+
+ if (op->dummy.nbytes) {
+ memset(tmpbuf + op->addr.nbytes + 1, 0xff, op->dummy.nbytes);
+ xfers[xferpos].tx_buf = tmpbuf + op->addr.nbytes + 1;
+ xfers[xferpos].len = op->dummy.nbytes;
+ spi_message_add_tail(&xfers[xferpos], &msg);
+ xferpos++;
+ totalxferlen += op->dummy.nbytes;
+ }
+
+ if (op->data.nbytes) {
+ if (op->data.dir == SPI_MEM_DATA_IN)
+ xfers[xferpos].rx_buf = op->data.buf.in;
+ else
+ xfers[xferpos].tx_buf = op->data.buf.out;
+
+ xfers[xferpos].len = op->data.nbytes;
+ spi_message_add_tail(&xfers[xferpos], &msg);
+ xferpos++;
+ totalxferlen += op->data.nbytes;
+ }
+
+ ret = spi_sync(mem->spi, &msg);
+
+ kfree(tmpbuf);
+
+ if (ret)
+ return ret;
+
+ if (msg.actual_length != totalxferlen)
+ return -EIO;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spi_mem_exec_op);
+
+/**
+ * spi_mem_get_name() - Return the SPI mem device name to be used by the
+ * upper layer if necessary
+ * @mem: the SPI memory
+ *
+ * This function allows SPI mem users to retrieve the SPI mem device name.
+ * It is useful if the upper layer needs to expose a custom name for
+ * compatibility reasons.
+ *
+ * Return: a string containing the name of the memory device to be used
+ * by the SPI mem user
+ */
+const char *spi_mem_get_name(struct spi_mem *mem)
+{
+ return mem->name;
+}
+EXPORT_SYMBOL_GPL(spi_mem_get_name);
+
+/**
+ * spi_mem_adjust_op_size() - Adjust the data size of a SPI mem operation to
+ * match controller limitations
+ * @mem: the SPI memory
+ * @op: the operation to adjust
+ *
+ * Some controllers have FIFO limitations and must split a data transfer
+ * operation into multiple ones, others require a specific alignment for
+ * optimized accesses. This function allows SPI mem drivers to split a single
+ * operation into multiple sub-operations when required.
+ *
+ * Return: a negative error code if the controller can't properly adjust @op,
+ * 0 otherwise. Note that @op->data.nbytes will be updated if @op
+ * can't be handled in a single step.
+ */
+int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
+{
+ struct spi_controller *ctlr = mem->spi->controller;
+ size_t len;
+
+ len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes;
+
+ if (ctlr->mem_ops && ctlr->mem_ops->adjust_op_size)
+ return ctlr->mem_ops->adjust_op_size(mem, op);
+
+ if (!ctlr->mem_ops || !ctlr->mem_ops->exec_op) {
+ if (len > spi_max_transfer_size(mem->spi))
+ return -EINVAL;
+
+ op->data.nbytes = min3((size_t)op->data.nbytes,
+ spi_max_transfer_size(mem->spi),
+ spi_max_message_size(mem->spi) -
+ len);
+ if (!op->data.nbytes)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size);
+
+static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc,
+ u64 offs, size_t len, void *buf)
+{
+ struct spi_mem_op op = desc->info.op_tmpl;
+ int ret;
+
+ op.addr.val = desc->info.offset + offs;
+ op.data.buf.in = buf;
+ op.data.nbytes = len;
+ ret = spi_mem_adjust_op_size(desc->mem, &op);
+ if (ret)
+ return ret;
+
+ ret = spi_mem_exec_op(desc->mem, &op);
+ if (ret)
+ return ret;
+
+ return op.data.nbytes;
+}
+
+static ssize_t spi_mem_no_dirmap_write(struct spi_mem_dirmap_desc *desc,
+ u64 offs, size_t len, const void *buf)
+{
+ struct spi_mem_op op = desc->info.op_tmpl;
+ int ret;
+
+ op.addr.val = desc->info.offset + offs;
+ op.data.buf.out = buf;
+ op.data.nbytes = len;
+ ret = spi_mem_adjust_op_size(desc->mem, &op);
+ if (ret)
+ return ret;
+
+ ret = spi_mem_exec_op(desc->mem, &op);
+ if (ret)
+ return ret;
+
+ return op.data.nbytes;
+}
+
+/**
+ * spi_mem_dirmap_create() - Create a direct mapping descriptor
+ * @mem: SPI mem device this direct mapping should be created for
+ * @info: direct mapping information
+ *
+ * This function is creating a direct mapping descriptor which can then be used
+ * to access the memory using spi_mem_dirmap_read() or spi_mem_dirmap_write().
+ * If the SPI controller driver does not support direct mapping, this function
+ * fallback to an implementation using spi_mem_exec_op(), so that the caller
+ * doesn't have to bother implementing a fallback on his own.
+ *
+ * Return: a valid pointer in case of success, and ERR_PTR() otherwise.
+ */
+struct spi_mem_dirmap_desc *
+spi_mem_dirmap_create(struct spi_mem *mem,
+ const struct spi_mem_dirmap_info *info)
+{
+ struct spi_controller *ctlr = mem->spi->controller;
+ struct spi_mem_dirmap_desc *desc;
+ int ret = -ENOTSUPP;
+
+ /* Make sure the number of address cycles is between 1 and 8 bytes. */
+ if (!info->op_tmpl.addr.nbytes || info->op_tmpl.addr.nbytes > 8)
+ return ERR_PTR(-EINVAL);
+
+ /* data.dir should either be SPI_MEM_DATA_IN or SPI_MEM_DATA_OUT. */
+ if (info->op_tmpl.data.dir == SPI_MEM_NO_DATA)
+ return ERR_PTR(-EINVAL);
+
+ desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+ if (!desc)
+ return ERR_PTR(-ENOMEM);
+
+ desc->mem = mem;
+ desc->info = *info;
+ if (ctlr->mem_ops && ctlr->mem_ops->dirmap_create)
+ ret = ctlr->mem_ops->dirmap_create(desc);
+
+ if (ret) {
+ desc->nodirmap = true;
+ if (!spi_mem_supports_op(desc->mem, &desc->info.op_tmpl))
+ ret = -ENOTSUPP;
+ else
+ ret = 0;
+ }
+
+ if (ret) {
+ kfree(desc);
+ return ERR_PTR(ret);
+ }
+
+ return desc;
+}
+EXPORT_SYMBOL_GPL(spi_mem_dirmap_create);
+
+/**
+ * spi_mem_dirmap_destroy() - Destroy a direct mapping descriptor
+ * @desc: the direct mapping descriptor to destroy
+ * @info: direct mapping information
+ *
+ * This function destroys a direct mapping descriptor previously created by
+ * spi_mem_dirmap_create().
+ */
+void spi_mem_dirmap_destroy(struct spi_mem_dirmap_desc *desc)
+{
+ struct spi_controller *ctlr = desc->mem->spi->controller;
+
+ if (!desc->nodirmap && ctlr->mem_ops && ctlr->mem_ops->dirmap_destroy)
+ ctlr->mem_ops->dirmap_destroy(desc);
+}
+EXPORT_SYMBOL_GPL(spi_mem_dirmap_destroy);
+
+/**
+ * spi_mem_dirmap_dirmap_read() - Read data through a direct mapping
+ * @desc: direct mapping descriptor
+ * @offs: offset to start reading from. Note that this is not an absolute
+ * offset, but the offset within the direct mapping which already has
+ * its own offset
+ * @len: length in bytes
+ * @buf: destination buffer. This buffer must be DMA-able
+ *
+ * This function reads data from a memory device using a direct mapping
+ * previously instantiated with spi_mem_dirmap_create().
+ *
+ * Return: the amount of data read from the memory device or a negative error
+ * code. Note that the returned size might be smaller than @len, and the caller
+ * is responsible for calling spi_mem_dirmap_read() again when that happens.
+ */
+ssize_t spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc,
+ u64 offs, size_t len, void *buf)
+{
+ struct spi_controller *ctlr = desc->mem->spi->controller;
+ ssize_t ret;
+
+ if (desc->info.op_tmpl.data.dir != SPI_MEM_DATA_IN)
+ return -EINVAL;
+
+ if (!len)
+ return 0;
+
+ if (desc->nodirmap) {
+ ret = spi_mem_no_dirmap_read(desc, offs, len, buf);
+ } else if (ctlr->mem_ops && ctlr->mem_ops->dirmap_read) {
+ ret = spi_mem_access_start(desc->mem);
+ if (ret)
+ return ret;
+
+ ret = ctlr->mem_ops->dirmap_read(desc, offs, len, buf);
+
+ spi_mem_access_end(desc->mem);
+ } else {
+ ret = -ENOTSUPP;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(spi_mem_dirmap_read);
+
+/**
+ * spi_mem_dirmap_dirmap_write() - Write data through a direct mapping
+ * @desc: direct mapping descriptor
+ * @offs: offset to start writing from. Note that this is not an absolute
+ * offset, but the offset within the direct mapping which already has
+ * its own offset
+ * @len: length in bytes
+ * @buf: source buffer. This buffer must be DMA-able
+ *
+ * This function writes data to a memory device using a direct mapping
+ * previously instantiated with spi_mem_dirmap_create().
+ *
+ * Return: the amount of data written to the memory device or a negative error
+ * code. Note that the returned size might be smaller than @len, and the caller
+ * is responsible for calling spi_mem_dirmap_write() again when that happens.
+ */
+ssize_t spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc,
+ u64 offs, size_t len, const void *buf)
+{
+ struct spi_controller *ctlr = desc->mem->spi->controller;
+ ssize_t ret;
+
+ if (desc->info.op_tmpl.data.dir != SPI_MEM_DATA_OUT)
+ return -EINVAL;
+
+ if (!len)
+ return 0;
+
+ if (desc->nodirmap) {
+ ret = spi_mem_no_dirmap_write(desc, offs, len, buf);
+ } else if (ctlr->mem_ops && ctlr->mem_ops->dirmap_write) {
+ ret = spi_mem_access_start(desc->mem);
+ if (ret)
+ return ret;
+
+ ret = ctlr->mem_ops->dirmap_write(desc, offs, len, buf);
+
+ spi_mem_access_end(desc->mem);
+ } else {
+ ret = -ENOTSUPP;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(spi_mem_dirmap_write);
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 25bb988794..d9311d4af5 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -19,6 +19,7 @@
*/
#include <common.h>
+#include <linux/spi/spi-mem.h>
#include <spi/spi.h>
#include <xfuncs.h>
#include <malloc.h>
@@ -54,22 +55,23 @@ static LIST_HEAD(board_list);
*
* Returns the new device, or NULL.
*/
-struct spi_device *spi_new_device(struct spi_master *master,
+struct spi_device *spi_new_device(struct spi_controller *ctrl,
struct spi_board_info *chip)
{
struct spi_device *proxy;
+ struct spi_mem *mem;
int status;
/* Chipselects are numbered 0..max; validate. */
- if (chip->chip_select >= master->num_chipselect) {
+ if (chip->chip_select >= ctrl->num_chipselect) {
debug("cs%d > max %d\n",
chip->chip_select,
- master->num_chipselect);
+ ctrl->num_chipselect);
return NULL;
}
proxy = xzalloc(sizeof *proxy);
- proxy->master = master;
+ proxy->master = ctrl;
proxy->chip_select = chip->chip_select;
proxy->max_speed_hz = chip->max_speed_hz;
proxy->mode = chip->mode;
@@ -81,10 +83,20 @@ struct spi_device *spi_new_device(struct spi_master *master,
proxy->dev.id = DEVICE_ID_DYNAMIC;
proxy->dev.type_data = proxy;
proxy->dev.device_node = chip->device_node;
- proxy->dev.parent = master->dev;
+ proxy->dev.parent = ctrl->dev;
+ proxy->master = proxy->controller = ctrl;
+
+ mem = xzalloc(sizeof *mem);
+ mem->spi = proxy;
+
+ if (ctrl->mem_ops && ctrl->mem_ops->get_name)
+ mem->name = ctrl->mem_ops->get_name(mem);
+ else
+ mem->name = dev_name(&proxy->dev);
+ proxy->mem = mem;
/* drivers may modify this initial i/o setup */
- status = master->setup(proxy);
+ status = ctrl->setup(proxy);
if (status < 0) {
printf("can't setup %s, status %d\n",
proxy->dev.name, status);
@@ -100,12 +112,12 @@ fail:
}
EXPORT_SYMBOL(spi_new_device);
-static void spi_of_register_slaves(struct spi_master *master)
+static void spi_of_register_slaves(struct spi_controller *ctrl)
{
struct device_node *n;
struct spi_board_info chip;
struct property *reg;
- struct device_node *node = master->dev->device_node;
+ struct device_node *node = ctrl->dev->device_node;
if (!IS_ENABLED(CONFIG_OFDEVICE))
return;
@@ -116,7 +128,7 @@ static void spi_of_register_slaves(struct spi_master *master)
for_each_available_child_of_node(node, n) {
memset(&chip, 0, sizeof(chip));
chip.name = xstrdup(n->name);
- chip.bus_num = master->bus_num;
+ chip.bus_num = ctrl->bus_num;
/* Mode (clock phase/polarity/etc.) */
if (of_property_read_bool(n, "spi-cpha"))
chip.mode |= SPI_CPHA;
@@ -171,7 +183,7 @@ spi_register_board_info(struct spi_board_info const *info, int n)
return 0;
}
-static void scan_boardinfo(struct spi_master *master)
+static void scan_boardinfo(struct spi_controller *ctrl)
{
struct boardinfo *bi;
@@ -180,27 +192,47 @@ static void scan_boardinfo(struct spi_master *master)
unsigned n;
for (n = bi->n_board_info; n > 0; n--, chip++) {
- debug("%s %d %d\n", __FUNCTION__, chip->bus_num, master->bus_num);
- if (chip->bus_num != master->bus_num)
+ debug("%s %d %d\n", __FUNCTION__, chip->bus_num, ctrl->bus_num);
+ if (chip->bus_num != ctrl->bus_num)
continue;
/* NOTE: this relies on spi_new_device to
* issue diagnostics when given bogus inputs
*/
- (void) spi_new_device(master, chip);
+ (void) spi_new_device(ctrl, chip);
}
}
}
-static LIST_HEAD(spi_master_list);
+static LIST_HEAD(spi_controller_list);
+
+static int spi_controller_check_ops(struct spi_controller *ctlr)
+{
+ /*
+ * The controller may implement only the high-level SPI-memory like
+ * operations if it does not support regular SPI transfers, and this is
+ * valid use case.
+ * If ->mem_ops is NULL, we request that at least one of the
+ * ->transfer_xxx() method be implemented.
+ */
+ if (ctlr->mem_ops) {
+ if (!ctlr->mem_ops->exec_op)
+ return -EINVAL;
+ } else if (!ctlr->transfer) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
/**
- * spi_register_master - register SPI master controller
- * @master: initialized master, originally from spi_alloc_master()
+ * spi_register_ctrl - register SPI ctrl controller
+ * @ctrl: initialized ctrl, originally from spi_alloc_ctrl()
* Context: can sleep
*
- * SPI master controllers connect to their drivers using some non-SPI bus,
+ * SPI controllers connect to their drivers using some non-SPI bus,
* such as the platform bus. The final stage of probe() in that code
- * includes calling spi_register_master() to hook up to this SPI bus glue.
+ * includes calling spi_register_ctrl() to hook up to this SPI bus glue.
*
* SPI controllers use board specific (often SOC specific) bus numbers,
* and board-specific addressing for SPI devices combines those numbers
@@ -209,47 +241,55 @@ static LIST_HEAD(spi_master_list);
* chip is at which address.
*
* This must be called from context that can sleep. It returns zero on
- * success, else a negative error code (dropping the master's refcount).
+ * success, else a negative error code (dropping the ctrl's refcount).
* After a successful return, the caller is responsible for calling
- * spi_unregister_master().
+ * spi_unregister_ctrl().
*/
-int spi_register_master(struct spi_master *master)
+int spi_register_controller(struct spi_controller *ctrl)
{
static int dyn_bus_id = (1 << 15) - 1;
int status = -ENODEV;
- debug("%s: %s:%d\n", __func__, master->dev->name, master->dev->id);
+ debug("%s: %s:%d\n", __func__, ctrl->dev->name, ctrl->dev->id);
+
+ /*
+ * Make sure all necessary hooks are implemented before registering
+ * the SPI controller.
+ */
+ status = spi_controller_check_ops(ctrl);
+ if (status)
+ return status;
/* even if it's just one always-selected device, there must
* be at least one chipselect
*/
- if (master->num_chipselect == 0)
+ if (ctrl->num_chipselect == 0)
return -EINVAL;
- if ((master->bus_num < 0) && master->dev->device_node)
- master->bus_num = of_alias_get_id(master->dev->device_node, "spi");
+ if ((ctrl->bus_num < 0) && ctrl->dev->device_node)
+ ctrl->bus_num = of_alias_get_id(ctrl->dev->device_node, "spi");
/* convention: dynamically assigned bus IDs count down from the max */
- if (master->bus_num < 0)
- master->bus_num = dyn_bus_id--;
+ if (ctrl->bus_num < 0)
+ ctrl->bus_num = dyn_bus_id--;
- list_add_tail(&master->list, &spi_master_list);
+ list_add_tail(&ctrl->list, &spi_controller_list);
- spi_of_register_slaves(master);
+ spi_of_register_slaves(ctrl);
/* populate children from any spi device tables */
- scan_boardinfo(master);
+ scan_boardinfo(ctrl);
status = 0;
return status;
}
-EXPORT_SYMBOL(spi_register_master);
+EXPORT_SYMBOL(spi_register_ctrl);
-struct spi_master *spi_get_master(int bus)
+struct spi_controller *spi_get_controller(int bus)
{
- struct spi_master* m;
+ struct spi_controller* m;
- list_for_each_entry(m, &spi_master_list, list) {
+ list_for_each_entry(m, &spi_controller_list, list) {
if (m->bus_num == bus)
return m;
}
@@ -259,7 +299,7 @@ struct spi_master *spi_get_master(int bus)
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
- return spi->master->transfer(spi, message);
+ return spi->controller->transfer(spi, message);
}
/**