oleavr-rgl-a500-mini-linux-.../drivers/crypto/sunxi-emce/sunxi_emce.c
Ole André Vadla Ravnås 169c65d57e Initial commit
2022-05-07 01:01:45 +02:00

577 lines
13 KiB
C

/*
* 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 "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;
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");