diff options
Diffstat (limited to 'drivers')
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(¶ms->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(¶ms->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(¶ms->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(¶ms->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(¶ms->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 = ¶ms->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 = ¶ms->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, ¶ms); + 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, ¶ms, 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); } /** |