sdk-hwV1.3/lichee/linux-4.9/drivers/crypto/sunxi-ss/sunxi_ce_cdev.c

768 lines
18 KiB
C

/*
* The driver of SUNXI SecuritySystem controller.
*
* Copyright (C) 2013 Allwinner.
*
* Mintow <duanmintao@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/kernel.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/clk/sunxi.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/dma/sunxi-dma.h>
#include <linux/dmapool.h>
#include "sunxi_ce_cdev.h"
#include "sunxi_ss_reg.h"
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
sunxi_ce_cdev_t *ce_cdev;
static void ioctl_hash_crypto(unsigned int cmd, unsigned long arg);
static void ioctl_rsa_crypto(unsigned int cmd, unsigned long arg);
static DEFINE_MUTEX(ce_lock);
void ce_dev_lock(void)
{
mutex_lock(&ce_lock);
}
void ce_dev_unlock(void)
{
mutex_unlock(&ce_lock);
}
void __iomem *ss_membase(void)
{
return ce_cdev->base_addr;
}
void ce_reset(void)
{
SS_ENTER();
sunxi_periph_reset_assert(ce_cdev->ce_clk);
sunxi_periph_reset_deassert(ce_cdev->ce_clk);
}
/* Requeset the resource: IRQ, mem */
static int sunxi_ce_res_request(sunxi_ce_cdev_t *p_cdev)
{
int ret = 0;
struct device_node *pnode = p_cdev->pnode;
p_cdev->irq = irq_of_parse_and_map(pnode, SS_RES_INDEX);
if (p_cdev->irq == 0) {
SS_ERR("Failed to get the CE IRQ.\n");
return -EINVAL;
}
ret = request_irq(p_cdev->irq,
sunxi_ce_irq_handler, 0, p_cdev->dev_name, p_cdev);
if (ret != 0) {
SS_ERR("Cannot request IRQ\n");
return ret;
}
#ifdef CONFIG_OF
p_cdev->base_addr = of_iomap(pnode, SS_RES_INDEX);
if (p_cdev->base_addr == NULL) {
SS_ERR("Unable to remap IO\n");
return -ENXIO;
}
#endif
return 0;
}
/* Release the resource: IRQ, mem */
static int sunxi_ce_res_release(sunxi_ce_cdev_t *p_cdev)
{
if (p_cdev->base_addr)
iounmap(p_cdev->base_addr);
if (p_cdev->irq)
free_irq(p_cdev->irq, p_cdev);
if (p_cdev->pnode)
of_node_put(p_cdev->pnode);
return 0;
}
static s32 sunxi_get_ce_device_node(struct device_node **pnode, void __iomem **base,
s8 *compatible)
{
*pnode = of_find_compatible_node(NULL, NULL, compatible);
if (IS_ERR_OR_NULL(*pnode)) {
SS_ERR("Failed to find \"%s\" in dts.\n", compatible);
return -1;
}
*base = of_iomap(*pnode, 0); /* reg[0] must be accessible. */
if (*base == NULL) {
SS_ERR("Unable to remap IO\n");
return -2;
}
SS_DBG("Base addr of \"%s\" is %p\n", compatible, *base);
return 0;
}
static int sunxi_ce_hw_init(sunxi_ce_cdev_t *p_cdev)
{
int ret = 0;
struct clk *pclk = NULL;
u32 gen_clkrate;
ret = sunxi_get_ce_device_node(&p_cdev->pnode, &p_cdev->base_addr, SUNXI_CE_DEV_NODE_NAME);
if (ret) {
SS_ERR("sunxi_get_ce_device_node fail, return %x\n", ret);
return -1;
}
/*get periph0 clk reg*/
pclk = of_clk_get(p_cdev->pnode, 1);
if (IS_ERR_OR_NULL(pclk)) {
SS_ERR("Unable to get pll clock, return %x\n", PTR_RET(pclk));
return PTR_RET(pclk);
}
/*get ce clk reg*/
p_cdev->ce_clk = of_clk_get(p_cdev->pnode, 0);
if (IS_ERR_OR_NULL(p_cdev->ce_clk)) {
SS_ERR("Fail to get module clk, ret %x\n", PTR_RET(p_cdev->ce_clk));
return PTR_RET(p_cdev->ce_clk);
}
#ifndef SET_CE_CLKFRE_MODE2
/*get ce need configure clk*/
if (of_property_read_u32(p_cdev->pnode, "clock-frequency", &gen_clkrate)) {
SS_ERR("Unable to get clock-frequency.\n");
return -EINVAL;
}
SS_DBG("The clk freq: %d\n", gen_clkrate);
#endif
/*configure ce clk form periph0*/
#ifdef CONFIG_EVB_PLATFORM
ret = clk_set_parent(p_cdev->ce_clk, pclk);
if (ret != 0) {
SS_ERR("clk_set_parent() failed! return %d\n", ret);
return ret;
}
#ifndef SET_CE_CLKFRE_MODE2
ret = clk_set_rate(p_cdev->ce_clk, gen_clkrate);
if (ret != 0) {
SS_ERR("Set rate(%d) failed! ret %d\n", gen_clkrate, ret);
return ret;
}
#endif
#endif
SS_DBG("SS mclk %luMHz, pclk %luMHz\n", clk_get_rate(p_cdev->ce_clk)/1000000,
clk_get_rate(pclk)/1000000);
/*enable ce clk*/
if (clk_prepare_enable(p_cdev->ce_clk)) {
SS_ERR("Couldn't enable module clock\n");
return -EBUSY;
}
clk_put(pclk);
sunxi_ce_res_request(p_cdev);
return 0;
}
static int sunxi_ce_hw_exit(void)
{
clk_disable_unprepare(ce_cdev->ce_clk);
clk_put(ce_cdev->ce_clk);
ce_cdev->ce_clk = NULL;
sunxi_ce_res_release(ce_cdev);
return 0;
}
static u64 sunxi_ss_dma_mask = DMA_BIT_MASK(64);
static int sunxi_ce_open(struct inode *inode, struct file *file)
{
file->private_data = ce_cdev;
return 0;
}
static int sunxi_ce_release(struct inode *inode, struct file *file)
{
file->private_data = NULL;
return 0;
}
static ssize_t sunxi_ce_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
return 0;
}
static ssize_t sunxi_ce_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
return 0;
}
static int sunxi_ce_mmap(struct file *file, struct vm_area_struct *vma)
{
return 0;
}
static int sunxi_copy_from_user(u8 **src, u32 size)
{
int ret = -1;
u8 *tmp = NULL;
tmp = kzalloc(size, GFP_KERNEL);
if (!tmp) {
SS_ERR("kzalloc fail\n");
return -ENOMEM;
}
ret = copy_from_user(tmp, *src, size);
if (ret) {
SS_ERR("copy_from_user fail\n");
return -EAGAIN;
}
*src = tmp;
return 0;
}
static int sunxi_ce_channel_request(void)
{
int i;
unsigned long flags;
spin_lock_irqsave(&ce_cdev->lock, flags);
for (i = 0; i < SS_FLOW_NUM; i++) {
if (ce_cdev->flows[i].available == SS_FLOW_AVAILABLE) {
ce_cdev->flows[i].available = SS_FLOW_UNAVAILABLE;
SS_DBG("The flow %d is available.\n", i);
break;
}
}
ce_cdev->flag = 1;
spin_unlock_irqrestore(&ce_cdev->lock, flags);
if (i == SS_FLOW_NUM) {
SS_ERR("Failed to get an available flow.\n");
i = -1;
}
return i;
}
static void sunxi_ce_channel_free(int id)
{
unsigned long flags;
spin_lock_irqsave(&ce_cdev->lock, flags);
ce_cdev->flows[id].available = SS_FLOW_AVAILABLE;
ce_cdev->flag = 0;
SS_DBG("The flow %d is free.\n", id);
spin_unlock_irqrestore(&ce_cdev->lock, flags);
}
static int ce_release_resources(void *req_ctx, ulong cmd)
{
crypto_aes_req_ctx_t *aes_req_ctx = (crypto_aes_req_ctx_t *)req_ctx;
crypto_rsa_req_ctx_t *rsa_req_ctx = (crypto_rsa_req_ctx_t *)req_ctx;
crypto_hash_req_ctx_t *hash_req_ctx = (crypto_hash_req_ctx_t *)req_ctx;
switch (cmd) {
case CE_IOC_AES_CRYPTO:
if (!aes_req_ctx) {
SS_ERR("input is NULL\n");
return -1;
}
if (aes_req_ctx->key_buffer)
kfree(aes_req_ctx->key_buffer);
if (aes_req_ctx->iv_buf)
kfree(aes_req_ctx->iv_buf);
if (aes_req_ctx->ion_flag == 0) {
if (aes_req_ctx->src_buffer)
kfree(aes_req_ctx->src_buffer);
if (aes_req_ctx->dst_buffer)
kfree(aes_req_ctx->dst_buffer);
}
if (aes_req_ctx)
kfree(aes_req_ctx);
ce_dev_unlock();
break;
case CE_IOC_RSA_CRYPTO:
if (!rsa_req_ctx) {
SS_ERR("input is NULL\n");
return -1;
}
if (rsa_req_ctx->sign_buffer)
kfree(rsa_req_ctx->sign_buffer);
if (rsa_req_ctx->pkey_buffer)
kfree(rsa_req_ctx->pkey_buffer);
if (rsa_req_ctx->dst_buffer)
kfree(rsa_req_ctx->dst_buffer);
if (rsa_req_ctx)
kfree(rsa_req_ctx);
ce_dev_unlock();
break;
case CE_IOC_HASH_CRYPTO:
if (!hash_req_ctx) {
SS_ERR("input is NULL\n");
return -1;
}
if (hash_req_ctx->text_buffer)
kfree(hash_req_ctx->text_buffer);
if (hash_req_ctx->key_buffer)
kfree(hash_req_ctx->key_buffer);
if (hash_req_ctx->dst_buffer)
kfree(hash_req_ctx->dst_buffer);
if (hash_req_ctx->iv_buffer)
kfree(hash_req_ctx->iv_buffer);
if (hash_req_ctx)
kfree(hash_req_ctx);
ce_dev_unlock();
}
return 0;
}
static long sunxi_ce_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
crypto_aes_req_ctx_t *aes_req_ctx;
int channel_id = -1;
int ret;
phys_addr_t usr_key_addr;
phys_addr_t usr_dst_addr;
SS_DBG("cmd = 0x%x\n", cmd);
switch (cmd) {
case CE_IOC_REQUEST:
{
channel_id = sunxi_ce_channel_request();
if (channel_id < 0) {
SS_DBG("Failed to sunxi_ce_channel_request\n");
return -EAGAIN;
}
ret = put_user(channel_id, (int *)arg);
if (ret < 0) {
SS_ERR("Failed to put_user\n");
return -EAGAIN;
}
break;
}
case CE_IOC_FREE:
{
ret = get_user(channel_id, (int *)arg);
if (ret < 0) {
SS_ERR("Failed to get_user\n");
return -EAGAIN;
}
sunxi_ce_channel_free(channel_id);
break;
}
case CE_IOC_AES_CRYPTO:
{
SS_DBG("arg_size = 0x%x\n", _IOC_SIZE(cmd));
if (_IOC_SIZE(cmd) != sizeof(crypto_aes_req_ctx_t)) {
SS_DBG("arg_size != sizeof(crypto_aes_req_ctx_t)\n");
return -EINVAL;
}
aes_req_ctx = kzalloc(sizeof(crypto_aes_req_ctx_t), GFP_KERNEL);
if (!aes_req_ctx) {
SS_ERR("kzalloc aes_req_ctx fail\n");
return -ENOMEM;
}
ret = copy_from_user(aes_req_ctx, (crypto_aes_req_ctx_t *)arg,
sizeof(crypto_aes_req_ctx_t));
if (ret) {
SS_ERR("copy_from_user fail\n");
kfree(aes_req_ctx);
return -EAGAIN;
}
usr_key_addr = (phys_addr_t)aes_req_ctx->key_buffer;
usr_dst_addr = (phys_addr_t)aes_req_ctx->dst_buffer;
SS_DBG("usr_key_addr = 0x%px usr_dst_addr = 0x%px\n",
(void *)usr_key_addr, (void *)usr_dst_addr);
ret = sunxi_copy_from_user(&aes_req_ctx->key_buffer, aes_req_ctx->key_length);
if (ret) {
SS_ERR("key_buffer copy_from_user fail\n");
return -EAGAIN;
}
SS_DBG("aes_req_ctx->key_buffer = 0x%px\n", aes_req_ctx->key_buffer);
ret = sunxi_copy_from_user(&aes_req_ctx->iv_buf, aes_req_ctx->iv_length);
if (ret) {
SS_ERR("iv_buffer copy_from_user fail\n");
return -EAGAIN;
}
if (aes_req_ctx->ion_flag) {
SS_DBG("src_phy = 0x%lx, dst_phy = 0x%lx\n", aes_req_ctx->src_phy, aes_req_ctx->dst_phy);
} else {
ret = sunxi_copy_from_user(&aes_req_ctx->src_buffer, aes_req_ctx->src_length);
if (ret) {
SS_ERR("src_buffer copy_from_user fail\n");
return -EAGAIN;
}
ret = sunxi_copy_from_user(&aes_req_ctx->dst_buffer, aes_req_ctx->dst_length);
if (ret) {
SS_ERR("dst_buffer copy_from_user fail\n");
return -EAGAIN;
}
}
SS_DBG("do_aes_crypto start\n");
ret = do_aes_crypto(aes_req_ctx);
if (ret) {
kfree(aes_req_ctx);
SS_ERR("do_aes_crypto fail\n");
return -3;
}
if (aes_req_ctx->ion_flag) {
} else {
ret = copy_to_user((u8 *)usr_dst_addr, aes_req_ctx->dst_buffer, aes_req_ctx->dst_length);
if (ret) {
SS_ERR(" dst_buffer copy_from_user fail\n");
kfree(aes_req_ctx);
return -EAGAIN;
}
}
break;
}
case CE_IOC_RSA_CRYPTO:
{
ioctl_rsa_crypto(CE_IOC_RSA_CRYPTO, arg);
break;
}
case CE_IOC_HASH_CRYPTO:
{
ioctl_hash_crypto(CE_IOC_HASH_CRYPTO, arg);
break;
}
default:
return -EINVAL;
break;
}
return 0;
}
static void ioctl_rsa_crypto(unsigned int cmd, unsigned long arg)
{
crypto_rsa_req_ctx_t *rsa_req_ctx;
int ret;
phys_addr_t usr_dst_addr;
SS_DBG("arg_size = 0x%x\n", _IOC_SIZE(cmd));
if (_IOC_SIZE(cmd) != sizeof(crypto_rsa_req_ctx_t)) {
SS_DBG("arg_size != sizeof(crypto_rsa_req_ctx_t)\n");
return -EINVAL;
}
ce_dev_lock();
rsa_req_ctx = kzalloc(sizeof(crypto_rsa_req_ctx_t), GFP_KERNEL);
if (!rsa_req_ctx) {
SS_ERR("kzalloc rsa_req_ctx fail\n");
return -ENOMEM;
}
ret = copy_from_user(rsa_req_ctx, (crypto_rsa_req_ctx_t *)arg,
sizeof(crypto_rsa_req_ctx_t));
if (ret) {
SS_ERR("copy_from_user fail\n");
ce_release_resources((void *)rsa_req_ctx, CE_IOC_RSA_CRYPTO);
return -EAGAIN;
}
usr_dst_addr = (phys_addr_t)rsa_req_ctx->dst_buffer;
ret = sunxi_copy_from_user(&rsa_req_ctx->dst_buffer,
rsa_req_ctx->dst_length);
if (ret) {
SS_ERR("dst_buffer copy_from_user fail\n");
ce_release_resources((void *)rsa_req_ctx, CE_IOC_RSA_CRYPTO);
return -EAGAIN;
};
ret = sunxi_copy_from_user(&rsa_req_ctx->sign_buffer,
rsa_req_ctx->sign_length);
if (ret) {
SS_ERR("sign_buffer copy_from_user fail\n");
ce_release_resources((void *)rsa_req_ctx, CE_IOC_RSA_CRYPTO);
return -EAGAIN;
};
ret = sunxi_copy_from_user(&rsa_req_ctx->pkey_buffer,
rsa_req_ctx->pkey_length);
if (ret) {
SS_ERR("pkey_buffer copy_from_user fail\n");
ce_release_resources((void *)rsa_req_ctx, CE_IOC_RSA_CRYPTO);
return -EAGAIN;
};
SS_ERR("do_rsa_crypto start\n");
ret = do_rsa_crypto(rsa_req_ctx);
if (ret) {
ce_release_resources((void *)rsa_req_ctx, CE_IOC_RSA_CRYPTO);
SS_ERR("do_aes_crypto fail\n");
return -3;
}
ret = copy_to_user((u8 *)usr_dst_addr, rsa_req_ctx->dst_buffer,
rsa_req_ctx->dst_length);
if (ret) {
SS_ERR(" dst_buffer copy_from_user fail\n");
ce_release_resources((void *)rsa_req_ctx, CE_IOC_RSA_CRYPTO);
return -EAGAIN;
}
ce_release_resources((void *)rsa_req_ctx, CE_IOC_RSA_CRYPTO);
}
static void ioctl_hash_crypto(unsigned int cmd, unsigned long arg)
{
crypto_hash_req_ctx_t *hash_req_ctx;
int ret;
phys_addr_t usr_dst_addr;
SS_DBG("arg_size = 0x%x\n", _IOC_SIZE(cmd));
if (_IOC_SIZE(cmd) != sizeof(crypto_hash_req_ctx_t)) {
SS_DBG("arg_size != sizeof(crypto_rsa_req_ctx_t)\n");
return -EINVAL;
}
ce_dev_lock();
hash_req_ctx = kzalloc(sizeof(crypto_hash_req_ctx_t), GFP_KERNEL);
if (!hash_req_ctx) {
SS_ERR("kzalloc hash_req_ctx fail\n");
return -ENOMEM;
}
ret = copy_from_user(hash_req_ctx, (crypto_hash_req_ctx_t *)arg,
sizeof(crypto_hash_req_ctx_t));
if (ret) {
SS_ERR("copy_from_user fail\n");
ce_release_resources((void *)hash_req_ctx, CE_IOC_HASH_CRYPTO);
return -EAGAIN;
}
usr_dst_addr = (phys_addr_t)hash_req_ctx->dst_buffer;
ret = sunxi_copy_from_user(&hash_req_ctx->dst_buffer,
hash_req_ctx->dst_length);
if (ret) {
SS_ERR("dst_buffer copy_from_user fail\n");
ce_release_resources((void *)hash_req_ctx, CE_IOC_HASH_CRYPTO);
return -EAGAIN;
}
if (hash_req_ctx->ion_flag) {
;
} else {
ret = sunxi_copy_from_user(&hash_req_ctx->text_buffer,
hash_req_ctx->text_length);
if (ret) {
SS_ERR("text_buffer copy_from_user fail\n");
ce_release_resources((void *)hash_req_ctx,
CE_IOC_HASH_CRYPTO);
return -EAGAIN;
}
}
if (hash_req_ctx->key_length) {
ret = sunxi_copy_from_user(&hash_req_ctx->key_buffer,
hash_req_ctx->key_length);
if (ret) {
SS_ERR("hash_buffer copy_from_user fail\n");
ce_release_resources((void *)hash_req_ctx,
CE_IOC_HASH_CRYPTO);
return -EAGAIN;
}
}
if (hash_req_ctx->iv_length) {
ret = sunxi_copy_from_user(&hash_req_ctx->iv_buffer,
hash_req_ctx->iv_length);
if (ret) {
SS_ERR("iv_buffer copy_from_user fail\n");
ce_release_resources((void *)hash_req_ctx,
CE_IOC_HASH_CRYPTO);
return -EAGAIN;
}
}
SS_ERR("do_hash_crypto start\n");
ret = do_hash_crypto(hash_req_ctx);
if (ret) {
ce_release_resources((void *)hash_req_ctx, CE_IOC_HASH_CRYPTO);
SS_ERR("do_hash_crypto fail\n");
return -3;
}
ret = copy_to_user((u8 *)usr_dst_addr, hash_req_ctx->dst_buffer,
hash_req_ctx->dst_length);
if (ret) {
SS_ERR(" dst_buffer copy_from_user fail\n");
ce_release_resources((void *)hash_req_ctx, CE_IOC_HASH_CRYPTO);
return -EAGAIN;
}
ce_release_resources((void *)hash_req_ctx, CE_IOC_HASH_CRYPTO);
}
static const struct file_operations ce_fops = {
.owner = THIS_MODULE,
.open = sunxi_ce_open,
.release = sunxi_ce_release,
.write = sunxi_ce_write,
.read = sunxi_ce_read,
.unlocked_ioctl = sunxi_ce_ioctl,
.mmap = sunxi_ce_mmap,
};
static int sunxi_ce_setup_cdev(void)
{
int ret = 0;
ce_cdev = kzalloc(sizeof(sunxi_ce_cdev_t), GFP_KERNEL);
if (!ce_cdev) {
SS_ERR("kzalloc fail\n");
return -ENOMEM;
}
/*alloc devid*/
ret = alloc_chrdev_region(&ce_cdev->devid, 0, 1, SUNXI_SS_DEV_NAME);
if (ret < 0) {
SS_ERR("alloc_chrdev_region fail\n");
kfree(ce_cdev);
return -1;
}
SS_DBG("ce_cdev->devid = %d\n", ce_cdev->devid & 0xfff00000);
/*alloc pcdev*/
ce_cdev->pcdev = cdev_alloc();
if (!ce_cdev->pcdev) {
SS_ERR("cdev_alloc fail\n");
ret = -ENOMEM;
goto err0;
}
/*bing ce_fops*/
cdev_init(ce_cdev->pcdev, &ce_fops);
ce_cdev->pcdev->owner = THIS_MODULE;
/*register cdev*/
ret = cdev_add(ce_cdev->pcdev, ce_cdev->devid, 1);
if (ret) {
SS_ERR("cdev_add fail\n");
ret = -1;
goto err0;
}
/*create device note*/
ce_cdev->pclass = class_create(THIS_MODULE, SUNXI_SS_DEV_NAME);
if (IS_ERR(ce_cdev->pclass)) {
SS_ERR("class_create fail\n");
ret = -1;
goto err0;
}
ce_cdev->pdevice = device_create(ce_cdev->pclass, NULL, ce_cdev->devid, NULL,
SUNXI_SS_DEV_NAME);
/*init task_pool*/
ce_cdev->task_pool = dma_pool_create("task_pool", ce_cdev->pdevice,
sizeof(struct ce_task_desc), 4, 0);
if (ce_cdev->task_pool == NULL)
return -ENOMEM;
#ifdef CONFIG_OF
ce_cdev->pdevice->dma_mask = &sunxi_ss_dma_mask;
ce_cdev->pdevice->coherent_dma_mask = DMA_BIT_MASK(32);
#endif
memcpy(ce_cdev->dev_name, SUNXI_SS_DEV_NAME, 3);
return 0;
err0:
if (ce_cdev)
kfree(ce_cdev);
unregister_chrdev_region(ce_cdev->devid, 1);
return ret;
}
static int sunxi_ce_exit_cdev(void)
{
device_destroy(ce_cdev->pclass, ce_cdev->devid);
class_destroy(ce_cdev->pclass);
unregister_chrdev_region(ce_cdev->devid, 1);
cdev_del(ce_cdev->pcdev);
kfree(ce_cdev);
if (ce_cdev->task_pool)
dma_pool_destroy(ce_cdev->task_pool);
return 0;
}
static int __init sunxi_ce_module_init(void)
{
int ret = 0;
SS_DBG("sunxi_ce_cdev_init\n");
ret = sunxi_ce_setup_cdev();
if (ret < 0) {
SS_ERR("sunxi_ce_setup_cdev() failed, return %d\n", ret);
return ret;
}
ret = sunxi_ce_hw_init(ce_cdev);
if (ret < 0) {
SS_ERR("sunxi_ce_hw_init failed, return %d\n", ret);
return ret;
}
return ret;
}
static void __exit sunxi_ce_module_exit(void)
{
SS_DBG("sunxi_ce_module_exit\n");
sunxi_ce_exit_cdev();
sunxi_ce_hw_exit();
}
module_init(sunxi_ce_module_init);
module_exit(sunxi_ce_module_exit);
MODULE_AUTHOR("mintow");
MODULE_DESCRIPTION("SUNXI CE Controller Driver");
MODULE_ALIAS("platform:"SUNXI_SS_DEV_NAME);
MODULE_LICENSE("GPL");