SmartAudio/lichee/linux-4.9/drivers/crypto/sunxi-emce/sunxi_emce.c

745 lines
17 KiB
C
Executable File

/*
* The driver of sunxi emce.
*
* Copyright (C) 2016 Allwinner.
*
* zhouhuacai <zhouhuacai@allwinnertech.com>
*
* 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 <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <linux/clk.h>
#include <linux/clk/sunxi.h>
#include <linux/dma-mapping.h>
#include <crypto/hash.h>
#include <linux/delay.h>
#include "sunxi_emce.h"
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
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;
struct sunxi_emce_t *emce_dev;
struct task_des *task_des_buf;
dma_addr_t task_des_addr;
static unsigned int boot_type_parse(char *boot_type)
{
long int ret;
int tmp;
tmp = kstrtol(boot_type, 0, &ret);
if (ret == STORAGE_NAND)
return 0;
if (ret == STORAGE_EMMC)
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);
void sunxi_emce_dump(void *addr, u32 size)
{
int i;
pr_cont("=========dump:size:0x%x=========\n", size);
for (i = 0; i < size; i += 4) {
if (!(i&0xf))
pr_cont("\n0x%p : ", (addr + i));
pr_cont("%08x ", readl(addr + i));
}
pr_cont("\n");
}
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_ahash *tfm = NULL;
struct ahash_request *req = NULL;
EMCE_ENTER();
tfm = crypto_alloc_ahash("sha256", CRYPTO_ALG_TYPE_AHASH, CRYPTO_ALG_TYPE_AHASH_MASK);
if (IS_ERR(tfm)) {
ret = PTR_ERR(tfm);
EMCE_ERR("Failed to alloc sha256 tfm. %p\n", tfm);
goto out2;
}
req = ahash_request_alloc(tfm, GFP_KERNEL);
if (!req)
EMCE_ERR("Failed to alloc ahash request!\n");
ahash_request_set_callback(req, 0, NULL, NULL);
sg_init_one(&sg, mkey, mklen);
ahash_request_set_crypt(req, &sg, skey, mklen);
ret = crypto_ahash_digest(req);
if (ret) {
EMCE_ERR("Failed to do SHA256(), mklen: %d\n", mklen);
goto out1;
}
*sklen = EMCE_SALT_KEY_LEN;
out1:
ahash_request_free(req);
crypto_free_ahash(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 void emce_get_key_len(u32 key1_len, u32 key2_len,
struct sunxi_emce_regs *reg)
{
if (reg) {
switch (key1_len) {
case 128:
reg->key1_len = EMCE_KEY_128_BIT;
break;
case 192:
reg->key1_len = EMCE_KEY_192_BIT;
break;
case 256:
reg->key1_len = EMCE_KEY_256_BIT;
break;
default:
break;
}
switch (key2_len) {
case 128:
reg->key2_len = EMCE_KEY_128_BIT;
break;
case 192:
reg->key2_len = EMCE_KEY_192_BIT;
break;
case 256:
reg->key2_len = EMCE_KEY_256_BIT;
break;
default:
break;
}
}
}
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_mode(u32 para)
{
if (emce_dev->emce_vision == EMCE_VISION_1)
sunxi_emce_set_cfg(EMCE_SET_MODE, para);
else
emce_dev->regs.crypto_mode = para;
}
EXPORT_SYMBOL_GPL(sunxi_emce_set_mode);
void sunxi_emce_set_task_des(int data_len, int bypass)
{
struct sunxi_emce_regs *reg = &emce_dev->regs;
int i;
if (emce_dev->emce_vision == EMCE_VISION_1)
return;
sunxi_emce_set_mode(0x1);
task_des_buf->com_ctrl =
(reg->key2_len << 12) | (reg->key1_len << 8) |
(reg->crypto_mode << 4) | bypass;
task_des_buf->data_len = data_len / 4;
for (i = 0; i < 8; i++) {
task_des_buf->key1[i] = reg->emce_key[i];
task_des_buf->key2[i] = reg->emce_salt[i];
}
task_des_buf->next_task = 0x0;
emce_writel(emce_dev, EMCE_TASK_DES_ADD, (u32)(task_des_addr));
udelay(10);
}
EXPORT_SYMBOL_GPL(sunxi_emce_set_task_des);
void sunxi_emce_set_task_load(int para)
{
if (emce_dev->emce_vision == EMCE_VISION_1)
return;
emce_writel(emce_dev, EMCE_TASK_LOAD, para);
}
EXPORT_SYMBOL_GPL(sunxi_emce_set_task_load);
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_key_len(u32 para)
{
sunxi_emce_set_cfg(EMCE_SET_KEY_LEN, para);
sunxi_emce_set_cfg(EMCE_SET_IV_LEN, para);
}
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 ((mkey == NULL) || (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 ((skey == NULL) || (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;
struct sunxi_emce_regs *reg = &emce_dev->regs;
int ret = 0;
int i;
EMCE_ENTER();
if ((mkey == NULL) || (len <= 0))
EMCE_ERR("sunxi emce key error\n ");
if (emce_dev->emce_vision == EMCE_VISION_1) {
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);
}
} else {
ret = sunxi_emce_gen_salt(mkey, len, saltkey, &skey_len);
if (!ret) {
for (i = 0; i < len; i = i + 4)
reg->emce_key[(i >> 2)] = *(u32 *)(mkey + i);
for (i = 0; i < skey_len; i = i + 4)
reg->emce_salt[(i >> 2)] = *(u32 *)(saltkey + i);
}
emce_get_key_len(len, skey_len, reg);
}
return ret;
}
EXPORT_SYMBOL_GPL(sunxi_emce_set_key);
static int sunxi_emce_get_version(void)
{
emce_dev->emce_vision = readl(emce_dev->base_addr + EMCE_VER);
pr_info("[EMCE]:VER:0x%x\n", emce_dev->emce_vision);
return emce_dev->emce_vision;
}
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 = 0;
#if (defined CONFIG_FPGA_V4_PLATFORM) | (defined CONFIG_FPGA_V7_PLATFORM)
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);
}
#else
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) {
EMCE_ERR("clk_set_parent() failed! return %d\n", ret);
return ret;
}
ret = clk_set_rate(emce->mclk, emce->gen_clkrate);
if (ret) {
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);
#endif
if (clk_prepare_enable(emce->mclk)) {
EMCE_ERR("Couldn't enable module clock\n");
return -EBUSY;
}
/*just for fpga*/
#if (defined CONFIG_FPGA_V4_PLATFORM) | (defined CONFIG_FPGA_V7_PLATFORM)
void __iomem *ccmu_gat_reg = NULL;
ccmu_gat_reg = ioremap(0x3001804, 0x1);
writel((readl(ccmu_gat_reg) | (0x1 << 24)), ccmu_gat_reg);
pr_info("[EMCE] ccmu mask gating %08x\n", readl(ccmu_gat_reg));
#endif
if (boot_type_cmdline != -1) {
sunxi_emce_set_cur_controller(emce, boot_type_cmdline);
sunxi_emce_show_cur_controller(boot_type_cmdline);
} else {
pr_info("[EMCE]:Unsupport Current Storage!\n");
return -EINVAL;
}
clk_put(pclk);
return ret;
}
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_DBG("sunxi emce reg base: %p !\n", emce->base_addr);
else {
EMCE_ERR("Failed to ioremap() io memory region.\n");
return -EBUSY;
}
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;
if (emce_dev->emce_vision == EMCE_VISION_1) {
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 (emce_dev->emce_vision == EMCE_VISION_1) {
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 IV len = %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 int sunxi_emce_sysfs_create(struct platform_device *_pdev)
{
int ret;
ret = device_create_file(&_pdev->dev, &sunxi_emce_info_attr);
return ret;
}
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(struct sunxi_emce_t), 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;
}
task_des_buf = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &task_des_addr, GFP_KERNEL);
if (!task_des_buf) {
EMCE_ERR("%s: Counld not allocate buffer\n", __func__);
ret = -ENOMEM;
goto err1;
}
ret = sunxi_emce_sysfs_create(pdev);
if (ret) {
EMCE_ERR("%s:create sys fs failed\n", __func__);
goto err_free_buf;
}
return 0;
err_free_buf:
dma_free_coherent(&pdev->dev, PAGE_SIZE, task_des_buf, task_des_addr);
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);
dma_free_coherent(&pdev->dev, PAGE_SIZE, task_des_buf, task_des_addr);
sunxi_emce_res_release(emce);
platform_set_drvdata(pdev, NULL);
return 0;
}
static void sunxi_emce_regs_save(struct sunxi_emce_t *emce)
{
int i;
struct sunxi_emce_regs *regs = &emce->regs;
if (emce_dev->emce_vision == EMCE_VISION_1) {
for (i = 0; i < 8; i++) {
regs->emce_key[i] =
emce_readl(emce, (EMCE_MASTER_KEY_OFFSET + 4 * i));
regs->emce_salt[i] =
emce_readl(emce, (EMCE_SALT_KEY_OFFSET + 4 * i));
}
}
regs->emce_mode = emce_readl(emce, EMCE_MODE);
}
static void sunxi_emce_regs_restore(struct sunxi_emce_t *emce)
{
int i;
struct sunxi_emce_regs *regs = &emce->regs;
if (emce_dev->emce_vision == EMCE_VISION_1) {
for (i = 0; i < 8; i++) {
emce_writel(emce, (EMCE_MASTER_KEY_OFFSET + 4 * i),
regs->emce_key[i]);
emce_writel(emce, (EMCE_SALT_KEY_OFFSET + 4 * i),
regs->emce_salt[i]);
}
}
emce_writel(emce, EMCE_MODE, regs->emce_mode);
}
#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);
pr_info("[EMCE]:suspend enter...\n");
sunxi_emce_regs_save(emce);
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);
pr_info("[EMCE]:resume enter...\n");
ret = sunxi_emce_hw_init(emce);
sunxi_emce_regs_restore(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");