#include <linux/kernel.h> | |
#include <linux/module.h> | |
#include <linux/slab.h> | |
#include <linux/err.h> | |
#include <linux/clk-provider.h> | |
#include <linux/clk.h> | |
#include <linux/io.h> | |
#include <linux/hw_random.h> | |
#include <linux/platform_device.h> | |
#include <linux/device.h> | |
#include <linux/init.h> | |
#include <linux/errno.h> | |
#include <linux/interrupt.h> | |
#include <linux/irq.h> | |
#include <linux/scatterlist.h> | |
#include <linux/dma-mapping.h> | |
#include <linux/of_device.h> | |
#include <linux/delay.h> | |
#include <linux/crypto.h> | |
#include <crypto/scatterwalk.h> | |
#include <crypto/algapi.h> | |
#include <crypto/aes.h> | |
#include <linux/cputype.h> | |
#include "asr-geu.h" | |
#include "../../../crypto/asr/asr_aes_clk.h" | |
static inline u32 asr_geu_read(struct asr_geu_dev *dd, u32 offset) | |
{ | |
u32 value = readl_relaxed(dd->io_base + offset); | |
return value; | |
} | |
static inline void asr_geu_write(struct asr_geu_dev *dd, | |
u32 offset, u32 value) | |
{ | |
writel_relaxed(value, dd->io_base + offset); | |
} | |
static int asr_geu_clk_sync(struct asr_geu_dev *dd) | |
{ | |
struct clk *geu_clk; | |
if (dd->clk_synced) | |
return 0; | |
geu_clk = dd->geu_clk; | |
/* GEU clk will be disable by CP core, but the enable count is still 1. | |
* Need to sync the clk enable state here and re-enable the clk. | |
*/ | |
if (__clk_is_enabled(geu_clk) == false && | |
__clk_get_enable_count(geu_clk)) | |
{ | |
asr_aes_clk_put(geu_clk); | |
asr_aes_clk_get(geu_clk); | |
dd->clk_synced = 1; | |
dev_dbg(dd->dev, "sync geu clk done\n"); | |
return 1; | |
} | |
return 0; | |
} | |
static int asr_geu_dev_get(struct asr_geu_dev *dd) | |
{ | |
mutex_lock(&dd->geu_lock); | |
asr_geu_clk_sync(dd); | |
asr_aes_clk_get(dd->geu_clk); | |
return 0; | |
} | |
static int asr_geu_dev_put(struct asr_geu_dev *dd) | |
{ | |
asr_aes_clk_put(dd->geu_clk); | |
mutex_unlock(&dd->geu_lock); | |
return 0; | |
} | |
static void asr_geu_hw_init(struct asr_geu_dev *dd) | |
{ | |
asr_geu_write(dd, GEU_CONFIG, 0); | |
asr_geu_write(dd, GEU_STATUS, 0); | |
} | |
static irqreturn_t asr_geu_irq(int irq, void *dev_id) | |
{ | |
u32 status; | |
irqreturn_t ret = IRQ_NONE; | |
struct asr_geu_dev *geu_dd = dev_id; | |
struct asr_geu_aes *aes_dd = &geu_dd->asr_aes; | |
status = asr_geu_read(geu_dd, GEU_STATUS); | |
if (aes_dd->aes_irq) { | |
ret = aes_dd->aes_irq(status, aes_dd); | |
} | |
return ret; | |
} | |
#if defined(CONFIG_OF) | |
static const struct of_device_id asr_geu_dt_ids[] = { | |
{ .compatible = "asr,asr-geu" }, | |
{ /* sentinel */ } | |
}; | |
MODULE_DEVICE_TABLE(of, asr_geu_dt_ids); | |
#endif | |
static struct asr_geu_ops geu_ops = { | |
.dev_get = asr_geu_dev_get, | |
.dev_put = asr_geu_dev_put, | |
}; | |
static int asr_geu_probe(struct platform_device *pdev) | |
{ | |
struct asr_geu_dev *geu_dd; | |
struct device *dev = &pdev->dev; | |
struct resource *geu_res; | |
struct device_node *np = NULL; | |
int err = 0, devnum = 0; | |
geu_dd = devm_kzalloc(&pdev->dev, sizeof(*geu_dd), GFP_KERNEL); | |
if (geu_dd == NULL) { | |
err = -ENOMEM; | |
goto res_err; | |
} | |
np = dev->of_node; | |
geu_dd->dev = dev; | |
geu_dd->geu_ops = &geu_ops; | |
platform_set_drvdata(pdev, geu_dd); | |
mutex_init(&geu_dd->geu_lock); | |
/* Get the base address */ | |
geu_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
if (!geu_res) { | |
dev_err(dev, "no MEM resource info\n"); | |
err = -ENODEV; | |
goto res_err; | |
} | |
geu_dd->phys_base = geu_res->start; | |
/* Get the IRQ */ | |
geu_dd->irq = platform_get_irq(pdev, 0); | |
if (geu_dd->irq < 0) { | |
err = geu_dd->irq; | |
goto res_err; | |
} | |
err = devm_request_irq(&pdev->dev, geu_dd->irq, asr_geu_irq, | |
IRQF_SHARED, "asr-geu", geu_dd); | |
if (err) { | |
dev_err(dev, "unable to request geu irq.\n"); | |
goto no_mem_err; | |
} | |
/* Initializing the clock */ | |
geu_dd->geu_clk = devm_clk_get(&pdev->dev, NULL); | |
if (IS_ERR(geu_dd->geu_clk)) { | |
dev_err(dev, "clock initialization failed.\n"); | |
err = PTR_ERR(geu_dd->geu_clk); | |
goto res_err; | |
} | |
geu_dd->clk_synced = 0; | |
geu_dd->io_base = devm_ioremap_resource(&pdev->dev, geu_res); | |
if (IS_ERR(geu_dd->io_base)) { | |
dev_err(dev, "can't ioremap\n"); | |
err = PTR_ERR(geu_dd->io_base); | |
goto res_err; | |
} | |
err = clk_prepare(geu_dd->geu_clk); | |
if (err) | |
goto res_err; | |
err = asr_aes_clk_get(geu_dd->geu_clk); | |
if (err) | |
goto geu_clk_unprepare; | |
asr_geu_hw_init(geu_dd); | |
#ifdef CONFIG_ASR_FUSE | |
if (of_get_property(np, "asr,asr-fuse", NULL)) { | |
err = asr_geu_fuse_register(geu_dd); | |
if (err) | |
goto geu_asr_aes_clk_put; | |
dev_info(dev, "Fuse is initialized\n"); | |
devnum ++; | |
} | |
#endif | |
#ifdef CONFIG_ASR_RNG | |
if (of_get_property(np, "asr,asr-hwrng", NULL)) { | |
err = asr_geu_rng_register(geu_dd); | |
if (err) | |
goto rng_error; | |
dev_info(dev, "H/W RNG is initialized\n"); | |
devnum ++; | |
} | |
#endif | |
#ifdef CONFIG_ASR_AES | |
if (of_get_property(np, "asr,asr-aes", NULL)) { | |
if (!cpu_is_asr1903_b0()) { | |
err = asr_geu_aes_register(geu_dd); | |
if (err) | |
goto aes_error; | |
dev_info(dev, "AES engine is initialized\n"); | |
devnum ++; | |
} | |
} | |
#endif | |
if (!devnum) { | |
dev_err(dev, "No GEU device enabled\n"); | |
err = -ENODEV; | |
goto geu_asr_aes_clk_put; | |
} | |
return 0; | |
aes_error: | |
#ifdef CONFIG_ASR_RNG | |
asr_geu_rng_unregister(geu_dd); | |
#endif | |
rng_error: | |
#ifdef CONFIG_ASR_FUSE | |
asr_geu_fuse_unregister(geu_dd); | |
#endif | |
geu_asr_aes_clk_put: | |
asr_aes_clk_put(geu_dd->geu_clk); | |
geu_clk_unprepare: | |
clk_unprepare(geu_dd->geu_clk); | |
res_err: | |
devm_kfree(dev, geu_dd); | |
no_mem_err: | |
dev_err(dev, "initialization failed.\n"); | |
return err; | |
} | |
static int asr_geu_remove(struct platform_device *pdev) | |
{ | |
struct asr_geu_dev *geu_dd; | |
geu_dd = platform_get_drvdata(pdev); | |
if (!geu_dd) | |
return -ENODEV; | |
clk_unprepare(geu_dd->geu_clk); | |
asr_aes_clk_put(geu_dd->geu_clk); | |
#ifdef CONFIG_ASR_RNG | |
asr_geu_rng_unregister(geu_dd); | |
#endif | |
#ifdef CONFIG_ASR_FUSE | |
asr_geu_fuse_unregister(geu_dd); | |
#endif | |
#ifdef CONFIG_ASR_AES | |
asr_geu_aes_unregister(geu_dd); | |
#endif | |
devm_kfree(geu_dd->dev, geu_dd); | |
return 0; | |
} | |
#ifdef CONFIG_PM | |
static int asr_geu_suspend(struct device *dev) | |
{ | |
struct asr_geu_dev *geu_dd = dev_get_drvdata(dev); | |
asr_aes_clk_put(geu_dd->geu_clk); | |
return 0; | |
} | |
static int asr_geu_resume(struct device *dev) | |
{ | |
struct asr_geu_dev *geu_dd = dev_get_drvdata(dev); | |
return asr_aes_clk_get(geu_dd->geu_clk); | |
} | |
static const struct dev_pm_ops asr_geu_pm_ops = { | |
.suspend = asr_geu_suspend, | |
.resume = asr_geu_resume, | |
}; | |
#endif /* CONFIG_PM */ | |
static struct platform_driver asr_geu_driver = { | |
.probe = asr_geu_probe, | |
.remove = asr_geu_remove, | |
.driver = { | |
.name = "asr_geu", | |
#ifdef CONFIG_PM | |
.pm = &asr_geu_pm_ops, | |
#endif | |
.of_match_table = of_match_ptr(asr_geu_dt_ids), | |
}, | |
}; | |
static int __init asr_geu_init(void) | |
{ | |
int ret; | |
ret = platform_driver_register(&asr_geu_driver); | |
return ret; | |
} | |
device_initcall_sync(asr_geu_init); | |
MODULE_DESCRIPTION("ASR Generic Encryption Unit support."); | |
MODULE_LICENSE("GPL v2"); | |
MODULE_AUTHOR("Yu Zhang"); |