745 lines
17 KiB
C
Executable File
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");
|
|
|