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/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 |
22 files changed, 1596 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/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); |