/* * The driver of sunxi emce. * * Copyright (C) 2016 Allwinner. * * zhouhuacai * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied. */ #include #include #include #include #include #include #include #include "sunxi_emce.h" #ifdef CONFIG_OF #include #include #include static const struct of_device_id sunxi_emce_of_match[] = { {.compatible = "allwinner,sunxi-emce",}, {}, }; MODULE_DEVICE_TABLE(of, sunxi_emce_of_match); #endif #define STORAGE_NAND 0 #define STORAGE_EMMC 2 static int boot_type_cmdline = -1; static unsigned int boot_type_parse(char *boot_type) { long int ret; int tmp; tmp = kstrtol(boot_type, 0, &ret); if (STORAGE_NAND == ret) return 0; if (STORAGE_EMMC == ret) return 1; return -1; } static int __init early_boot_type(char *boot_type) { pr_debug("%s(%s)\n", __func__, boot_type); boot_type_cmdline = boot_type_parse(boot_type); return 0; } early_param("boot_type", early_boot_type); struct sunxi_emce_t *emce_dev; void sunxi_emce_dump(void *addr, u32 size) { int i; pr_debug("=========dump:size:0x%x=========\n", size); for (i = 0; i < size; i += 4) { if (!(i&0xf)) pr_debug("\n0x%p : ", (addr + i)); pr_debug("%08x ", readl(addr + i)); } pr_debug("\n"); return; } static DEFINE_MUTEX(emce_lock); static void emce_dev_lock(void) { mutex_lock(&emce_lock); } static void emce_dev_unlock(void) { mutex_unlock(&emce_lock); } static int sunxi_emce_gen_salt(const u8 *mkey, u32 mklen, u8 *skey, u32 *sklen) { int ret = 0; struct scatterlist sg = {0}; struct crypto_hash *tfm = NULL; struct hash_desc desc = {0}; EMCE_ENTER(); tfm = crypto_alloc_hash("sha256", 0, CRYPTO_ALG_ASYNC); if (IS_ERR(tfm)) { ret = PTR_ERR(tfm); EMCE_ERR("Failed to alloc sha256 tfm. %p\n", tfm); goto out2; } desc.tfm = tfm; desc.flags = 0; sg_init_one(&sg, mkey, mklen); ret = crypto_hash_digest(&desc, &sg, mklen, skey); if (ret) { EMCE_ERR("Failed to do SHA256(), mklen: %d\n", mklen); goto out1; } *sklen = EMCE_SALT_KEY_LEN; out1: crypto_free_hash(tfm); out2: return ret; } static inline void sunxi_emce_set_bit(void __iomem *reg_addr, u32 mask, u32 bit, u32 val) { u32 reg_val = 0; reg_val = readl(reg_addr); reg_val &= (~mask); reg_val |= (val << bit); writel(reg_val, reg_addr); } static inline u32 sunxi_emce_get_bit(void __iomem *reg_addr, u32 mask, u32 bit) { u32 reg_val = 0; reg_val = readl(reg_addr); reg_val &= mask; return reg_val >> bit; } static int sunxi_emce_set_cfg(enum cfg_cmd cmd, u32 para) { int ret = 0; void __iomem *reg_addr = emce_dev->base_addr + EMCE_MODE; EMCE_ENTER(); emce_dev_lock(); switch (cmd) { case EMCE_SET_SECTOR_SIZE: sunxi_emce_set_bit(reg_addr, EMCE_SECTOR_SIZE_MASK, EMCE_SECTOR_SIZE_BIT, para); break; case EMCE_SET_IV_LEN: sunxi_emce_set_bit(reg_addr, EMCE_IV_LEN_MASK, EMCE_IV_LEN_BIT, para); break; case EMCE_SET_KEY_LEN: sunxi_emce_set_bit(reg_addr, EMCE_KEY_LEN_MASK, EMCE_KEY_LEN_BIT, para); break; case EMCE_SET_MODE: sunxi_emce_set_bit(reg_addr, EMCE_MODE_MASK, EMCE_MODE_BIT, para); break; default: EMCE_ERR("Unsupport cmd %d\n", cmd); break; } emce_dev_unlock(); return ret; } void sunxi_emce_set_sector_size(u32 para) { sunxi_emce_set_cfg(EMCE_SET_SECTOR_SIZE, para); } EXPORT_SYMBOL_GPL(sunxi_emce_set_sector_size); void sunxi_emce_set_mode(u32 para) { sunxi_emce_set_cfg(EMCE_SET_MODE, para); } EXPORT_SYMBOL_GPL(sunxi_emce_set_mode); void sunxi_emce_set_key_len(u32 para) { sunxi_emce_set_cfg(EMCE_SET_IV_LEN, para); sunxi_emce_set_cfg(EMCE_SET_KEY_LEN, para); } EXPORT_SYMBOL_GPL(sunxi_emce_set_key_len); static void sunxi_emce_set_cur_controller(struct sunxi_emce_t *emce_pdev, u32 para) { void __iomem *reg_addr = emce_pdev->base_addr + EMCE_MODE; sunxi_emce_set_bit(reg_addr, EMCE_CUR_CTR_MASK, EMCE_CUR_CTR_BIT, para); } static void sunxi_emce_set_masterkey(const u8 *mkey, u32 klen) { int i = 0; void __iomem *reg_addr = emce_dev->base_addr + EMCE_MASTER_KEY_OFFSET; if ((NULL == mkey) || (klen <= 0)) EMCE_ERR("sunxi emce masterkey error\n "); for (i = 0; i < klen; i = i + 4) writel(*(u32 *)(mkey + i) , (reg_addr + i)); } static void sunxi_emce_set_saltkey(const u8 *skey, u32 klen) { int i = 0; void __iomem *reg_addr = emce_dev->base_addr + EMCE_SALT_KEY_OFFSET; if ((NULL == skey) || (klen <= 0)) EMCE_ERR("sunxi emce saltkey error\n "); for (i = 0; i < klen; i = i + 4) writel((*(u32 *)(skey + i)), reg_addr + i); } int sunxi_emce_set_key(const u8 *mkey, u32 len) { u8 saltkey[EMCE_SALT_KEY_LEN]; u32 skey_len = 0; int ret = 0; EMCE_ENTER(); if ((NULL == mkey) || (len <= 0)) EMCE_ERR("sunxi emce key error\n "); ret = sunxi_emce_gen_salt(mkey, len, saltkey, &skey_len); if (!ret) { sunxi_emce_set_saltkey(saltkey, skey_len); sunxi_emce_set_masterkey(mkey, len); } return ret; } EXPORT_SYMBOL_GPL(sunxi_emce_set_key); static int sunxi_emce_get_version(void) { int ver = 0; ver = readl(emce_dev->base_addr + EMCE_VER); pr_info("[EMCE]:VER:0x%x\n", ver); return ver; } static void sunxi_emce_show_cur_controller(u32 cur_controller) { if (cur_controller) pr_info("[EMCE]:Current Controller is EMMC!\n"); else pr_info("[EMCE]:Current Controller is NAND!\n"); } static int sunxi_emce_hw_init(struct sunxi_emce_t *emce) { struct device_node *pnode = emce->pdev->dev.of_node; struct clk *pclk = NULL; int ret; pclk = of_clk_get(pnode, 1); if (IS_ERR_OR_NULL(pclk)) { EMCE_ERR("Unable to get pclk, return %x\n", PTR_RET(pclk)); return PTR_RET(pclk); } emce->mclk = of_clk_get(pnode, 0); if (IS_ERR_OR_NULL(emce->mclk)) { EMCE_ERR("Unable to get mclk, return %x\n", PTR_RET(emce->mclk)); return PTR_RET(emce->mclk); } if (of_property_read_u32(pnode, "clock-frequency", &emce->gen_clkrate)) { EMCE_ERR("Unable to get clock-frequency.\n"); return -EINVAL; } ret = clk_set_parent(emce->mclk, pclk); if (ret != 0) { EMCE_ERR("clk_set_parent() failed! return %d\n", ret); return ret; } ret = clk_set_rate(emce->mclk, emce->gen_clkrate); if (ret != 0) { EMCE_ERR("clk_set_rate(%d) failed! return %d\n", emce->gen_clkrate, ret); return ret; } EMCE_DBG("[EMCE]: mclk %luMHz, pclk %luMHz\n", clk_get_rate(emce->mclk)/1000000, clk_get_rate(pclk)/1000000); if (clk_prepare_enable(emce->mclk)) { EMCE_ERR("Couldn't enable module clock\n"); return -EBUSY; } if (boot_type_cmdline == -1) { pr_info("[EMCE]:Unsupport Current Storage!\n"); return -EINVAL; } else { sunxi_emce_set_cur_controller(emce, boot_type_cmdline); sunxi_emce_show_cur_controller(boot_type_cmdline); } clk_put(pclk); return 0; } static int sunxi_emce_hw_exit(struct sunxi_emce_t *emce) { EMCE_EXIT(); clk_disable_unprepare(emce->mclk); clk_put(emce->mclk); emce->mclk = NULL; return 0; } static int sunxi_emce_res_request(struct platform_device *pdev) { struct device_node *np = NULL; struct sunxi_emce_t *emce = platform_get_drvdata(pdev); np = pdev->dev.of_node; if (!of_device_is_available(np)) { EMCE_ERR("sunxi emce is disable\n"); return -EPERM; } emce->base_addr = of_iomap(np, 0); if (emce->base_addr == 0) { EMCE_ERR("Failed to ioremap() io memory region.\n"); return -EBUSY; } else EMCE_DBG("sunxi emce reg base: %p !\n", emce->base_addr); return 0; } static int sunxi_emce_res_release(struct sunxi_emce_t *emce) { iounmap(emce->base_addr); return 0; } static void emce_get_cur_ctl_str(struct sunxi_emce_t *emce, char *str) { if (str == NULL) return; if (!sunxi_emce_get_bit(emce->base_addr + EMCE_MODE, EMCE_CUR_CTR_MASK, EMCE_CUR_CTR_BIT)) sprintf(str, "%s", "NDFC"); else sprintf(str, "%s", "SMHC"); } static void emce_get_key_len_str(u32 val, char *str) { if (str == NULL) return; switch (val) { case EMCE_KEY_128_BIT: sprintf(str, "%s", "128bit"); break; case EMCE_KEY_192_BIT: sprintf(str, "%s", "192bit"); break; case EMCE_KEY_256_BIT: sprintf(str, "%s", "256bit"); break; default: sprintf(str, "%s", "reserved"); break; } } static void emce_get_mode(struct sunxi_emce_t *emce, char *str) { if (str == NULL) return; if (!sunxi_emce_get_bit(emce->base_addr + EMCE_MODE, EMCE_MODE_MASK, EMCE_MODE_BIT)) sprintf(str, "%s", "ECB"); else sprintf(str, "%s", "CBC"); } static ssize_t sunxi_emce_info_show(struct device *dev, struct device_attribute *attr, char *buf) { u32 iv_len; u32 key_len; char cur_ctl_str[MAXNAME]; char iv_len_str[MAXNAME]; char key_len_str[MAXNAME]; char mode_str[MAXNAME]; struct platform_device *pdev; struct sunxi_emce_t *emce; pdev = container_of(dev, struct platform_device, dev); emce = platform_get_drvdata(pdev); iv_len = sunxi_emce_get_bit(emce->base_addr + EMCE_MODE, EMCE_IV_LEN_MASK, EMCE_IV_LEN_BIT), key_len = sunxi_emce_get_bit(emce->base_addr + EMCE_MODE, EMCE_KEY_LEN_MASK, EMCE_KEY_LEN_BIT), emce_get_cur_ctl_str(emce, cur_ctl_str); emce_get_mode(emce, mode_str); emce_get_key_len_str(iv_len, iv_len_str); emce_get_key_len_str(key_len, key_len_str); return snprintf(buf, PAGE_SIZE, "pdev->id = %d\n" "pdev->name = %s\n" "module clk rate = %ld Mhz\n" "IO membase = 0x%p\n" "sector size = 0x%x\n" "cur controller = %s\n" "AES key len(when calcu IV) = %s\n" "key len = %s\n" "mode = %s\n" "emce version = 0x%x\n", pdev->id, pdev->name, (clk_get_rate(emce->mclk)/1000000), emce->base_addr, sunxi_emce_get_bit(emce->base_addr + EMCE_MODE, EMCE_SECTOR_SIZE_MASK, EMCE_SECTOR_SIZE_BIT), cur_ctl_str, iv_len_str, key_len_str, mode_str, readl(emce->base_addr + EMCE_VER)); } static struct device_attribute sunxi_emce_info_attr = __ATTR(info, S_IRUGO, sunxi_emce_info_show, NULL); static void sunxi_emce_sysfs_create(struct platform_device *_pdev) { device_create_file(&_pdev->dev, &sunxi_emce_info_attr); } static void sunxi_emce_sysfs_remove(struct platform_device *_pdev) { device_remove_file(&_pdev->dev, &sunxi_emce_info_attr); } static int sunxi_emce_probe(struct platform_device *pdev) { u32 ret = 0; struct sunxi_emce_t *emce = NULL; emce = devm_kzalloc(&pdev->dev, sizeof(emce), GFP_KERNEL); if (emce == NULL) { EMCE_ERR("Unable to allocate emce_data\n"); return -ENOMEM; } snprintf(emce->dev_name, sizeof(emce->dev_name), SUNXI_EMCE_DEV_NAME); platform_set_drvdata(pdev, emce); ret = sunxi_emce_res_request(pdev); if (ret != 0) goto err0; emce->pdev = pdev; ret = sunxi_emce_hw_init(emce); if (ret != 0) { EMCE_ERR("emce hw init failed!\n"); goto err1; } emce_dev = emce; ret = sunxi_emce_get_version(); if (!ret) { EMCE_ERR("sunxi emce version error\n"); ret = -ENXIO; goto err1; } sunxi_emce_sysfs_create(pdev); return 0; err1: sunxi_emce_res_release(emce); err0: platform_set_drvdata(pdev, NULL); return ret; } static int sunxi_emce_remove(struct platform_device *pdev) { struct sunxi_emce_t *emce = platform_get_drvdata(pdev); sunxi_emce_sysfs_remove(pdev); sunxi_emce_hw_exit(emce); sunxi_emce_res_release(emce); platform_set_drvdata(pdev, NULL); return 0; } #ifdef CONFIG_PM static int sunxi_emce_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct sunxi_emce_t *emce = platform_get_drvdata(pdev); EMCE_ENTER(); sunxi_emce_hw_exit(emce); return 0; } static int sunxi_emce_resume(struct device *dev) { int ret = 0; struct platform_device *pdev = to_platform_device(dev); struct sunxi_emce_t *emce = platform_get_drvdata(pdev); EMCE_ENTER(); ret = sunxi_emce_hw_init(emce); return ret; } static const struct dev_pm_ops sunxi_emce_dev_pm_ops = { .suspend = sunxi_emce_suspend, .resume = sunxi_emce_resume, }; #define SUNXI_EMCE_DEV_PM_OPS (&sunxi_emce_dev_pm_ops) #else #define SUNXI_EMCE_DEV_PM_OPS NULL #endif /* CONFIG_PM */ static struct platform_driver sunxi_emce_driver = { .probe = sunxi_emce_probe, .remove = sunxi_emce_remove, .driver = { .name = SUNXI_EMCE_DEV_NAME, .owner = THIS_MODULE, .pm = SUNXI_EMCE_DEV_PM_OPS, .of_match_table = sunxi_emce_of_match, }, }; static int __init sunxi_emce_init(void) { int ret = 0; EMCE_DBG("Sunxi EMCE init ...\n"); ret = platform_driver_register(&sunxi_emce_driver); if (ret < 0) EMCE_ERR("platform_driver_register() failed, return %d\n", ret); return ret; } static void __exit sunxi_emce_exit(void) { platform_driver_unregister(&sunxi_emce_driver); } module_init(sunxi_emce_init); module_exit(sunxi_emce_exit); MODULE_AUTHOR("zhouhuacai"); MODULE_DESCRIPTION("SUNXI EMCE Driver"); MODULE_ALIAS("platform:"SUNXI_EMCE_DEV_NAME); MODULE_LICENSE("GPL");