diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/clk/imx/clk-imx25.c | 7 | ||||
-rw-r--r-- | drivers/clk/imx/clk-vf610.c | 33 | ||||
-rw-r--r-- | drivers/crypto/Kconfig | 1 | ||||
-rw-r--r-- | drivers/crypto/Makefile | 1 | ||||
-rw-r--r-- | drivers/crypto/caam/Makefile | 1 | ||||
-rw-r--r-- | drivers/crypto/caam/caam-blobgen.c | 229 | ||||
-rw-r--r-- | drivers/crypto/caam/ctrl.c | 9 | ||||
-rw-r--r-- | drivers/crypto/caam/intern.h | 1 | ||||
-rw-r--r-- | drivers/crypto/caam/rng_self_test.c | 1 | ||||
-rw-r--r-- | drivers/crypto/imx-scc/Kconfig | 14 | ||||
-rw-r--r-- | drivers/crypto/imx-scc/Makefile | 2 | ||||
-rw-r--r-- | drivers/crypto/imx-scc/scc-blobgen.c | 159 | ||||
-rw-r--r-- | drivers/crypto/imx-scc/scc.c | 504 | ||||
-rw-r--r-- | drivers/crypto/imx-scc/scc.h | 13 | ||||
-rw-r--r-- | drivers/ddr/fsl/fsl_ddr.h | 2 | ||||
-rw-r--r-- | drivers/mci/Kconfig | 4 | ||||
-rw-r--r-- | drivers/mci/Makefile | 1 | ||||
-rw-r--r-- | drivers/mci/bcm2835-sdhost.c | 638 | ||||
-rw-r--r-- | drivers/mci/imx-esdhc-pbl.c | 5 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/spi-nor.c | 1 | ||||
-rw-r--r-- | drivers/mtd/ubi/ubi-barebox.h | 1 | ||||
-rw-r--r-- | drivers/net/fsl-fman.c | 2 | ||||
-rw-r--r-- | drivers/rtc/Kconfig | 6 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-imxdi.c | 623 |
25 files changed, 2239 insertions, 20 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/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index 85b55c6982..1595349c4c 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -646,6 +646,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) }, 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); |