[PATCH 7/9] crypto: allwinner: add sun8i-ce AES driver
James Hilliard
james.hilliard1 at gmail.com
Thu Jul 2 03:46:57 CEST 2026
Add an Allwinner sun8i Crypto Engine driver using the same directory and
driver naming style as the Linux sun8i-ce driver. The parent device owns
the shared CE registers, clocks, resets and task submission path, while an
AES child exposes the standard UCLASS_AES interface.
Support AES-128, AES-192 and AES-256 in ECB and CBC modes with
software-provided keys. Process direct DMA-capable buffers in 128 KiB
chunks and use cache-aligned bounce buffers for unaligned heads, tails and
overlapping cases. CBC keeps the IV chain in software between chunks so
each CE task can run on a larger contiguous range without losing chaining
state.
Model the H6 and H616 compatibles after the Linux sun8i-ce variant data.
Both variants use byte-sized cipher task lengths; H616 additionally needs
word-addressed descriptors. In SPL, enable the shared H6-family CE clock,
bus gate, reset and MBUS gate directly because the full clock/reset
uclasses are not always available there.
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
MAINTAINERS | 1 +
drivers/crypto/Kconfig | 2 +
drivers/crypto/Makefile | 1 +
drivers/crypto/allwinner/Kconfig | 3 +
drivers/crypto/allwinner/Makefile | 3 +
drivers/crypto/allwinner/sun8i-ce/Kconfig | 39 ++
drivers/crypto/allwinner/sun8i-ce/Makefile | 4 +
.../crypto/allwinner/sun8i-ce/sun8i-ce-aes.c | 407 ++++++++++++++++++
.../crypto/allwinner/sun8i-ce/sun8i-ce-core.c | 375 ++++++++++++++++
drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h | 61 +++
10 files changed, 896 insertions(+)
create mode 100644 drivers/crypto/allwinner/Kconfig
create mode 100644 drivers/crypto/allwinner/Makefile
create mode 100644 drivers/crypto/allwinner/sun8i-ce/Kconfig
create mode 100644 drivers/crypto/allwinner/sun8i-ce/Makefile
create mode 100644 drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c
create mode 100644 drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
create mode 100644 drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
diff --git a/MAINTAINERS b/MAINTAINERS
index f4eb2c13a43..b43014db002 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -761,6 +761,7 @@ F: arch/arm/include/asm/arch-sunxi/
F: arch/arm/mach-sunxi/
F: board/sunxi/
F: drivers/clk/sunxi/
+F: drivers/crypto/allwinner/
F: drivers/phy/allwinner/
F: drivers/pinctrl/sunxi/
F: drivers/video/sunxi/
diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig
index 0d58e3910fe..d15a59f87ee 100644
--- a/drivers/crypto/Kconfig
+++ b/drivers/crypto/Kconfig
@@ -6,6 +6,8 @@ source "drivers/crypto/aes/Kconfig"
source "drivers/crypto/fsl/Kconfig"
+source "drivers/crypto/allwinner/Kconfig"
+
source "drivers/crypto/aspeed/Kconfig"
source "drivers/crypto/nuvoton/Kconfig"
diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile
index e4a4482b7f3..cbbd5dc1dbe 100644
--- a/drivers/crypto/Makefile
+++ b/drivers/crypto/Makefile
@@ -8,6 +8,7 @@ obj-y += aes/
obj-y += rsa_mod_exp/
obj-y += fsl/
obj-y += hash/
+obj-y += allwinner/
obj-y += aspeed/
obj-y += nuvoton/
obj-y += tegra/
diff --git a/drivers/crypto/allwinner/Kconfig b/drivers/crypto/allwinner/Kconfig
new file mode 100644
index 00000000000..9765b089e25
--- /dev/null
+++ b/drivers/crypto/allwinner/Kconfig
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+source "drivers/crypto/allwinner/sun8i-ce/Kconfig"
diff --git a/drivers/crypto/allwinner/Makefile b/drivers/crypto/allwinner/Makefile
new file mode 100644
index 00000000000..2dcae98bac9
--- /dev/null
+++ b/drivers/crypto/allwinner/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+obj-y += sun8i-ce/
diff --git a/drivers/crypto/allwinner/sun8i-ce/Kconfig b/drivers/crypto/allwinner/sun8i-ce/Kconfig
new file mode 100644
index 00000000000..f72cf3498fe
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/Kconfig
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+config SUNXI_CE
+ bool
+ depends on ARCH_SUNXI
+ depends on CLK && DM_RESET
+
+config SPL_SUNXI_CE
+ bool
+ depends on ARCH_SUNXI
+ depends on MACH_SUN50I_H6 || MACH_SUN50I_H616
+ depends on SPL_DM
+ depends on SPL_OF_CONTROL
+ select SPL_CRYPTO
+
+config SUNXI_CE_AES
+ bool "Allwinner sunxi CE AES"
+ depends on ARCH_SUNXI
+ depends on DM_AES
+ depends on CLK && DM_RESET
+ select AES
+ select SUNXI_CE
+ help
+ Select this option to enable AES encryption and decryption using
+ the Crypto Engine found in Allwinner sunxi SoCs. The driver
+ supports software-provided AES-128, AES-192 and AES-256 keys.
+
+config SPL_SUNXI_CE_AES
+ bool "Allwinner sunxi CE AES in SPL"
+ depends on ARCH_SUNXI
+ depends on MACH_SUN50I_H6 || MACH_SUN50I_H616
+ depends on SPL_DM_AES
+ depends on SPL_OF_CONTROL
+ select SPL_CRYPTO
+ select SPL_SUNXI_CE
+ help
+ Select this option to enable AES decryption in SPL using the Crypto
+ Engine found in Allwinner H6 and H616 compatible SoCs. This can be
+ used to decrypt FIT images before loading U-Boot proper.
diff --git a/drivers/crypto/allwinner/sun8i-ce/Makefile b/drivers/crypto/allwinner/sun8i-ce/Makefile
new file mode 100644
index 00000000000..2a8778065b1
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+obj-$(CONFIG_$(PHASE_)SUNXI_CE) += sun8i-ce-core.o
+obj-$(CONFIG_$(PHASE_)SUNXI_CE_AES) += sun8i-ce-aes.o
diff --git a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c
new file mode 100644
index 00000000000..9148cfefc8a
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c
@@ -0,0 +1,407 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 James Hilliard
+ */
+
+#define LOG_CATEGORY UCLASS_AES
+
+#include <dm.h>
+#include <malloc.h>
+#include <memalign.h>
+#include <uboot_aes.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include "sun8i-ce.h"
+
+#define SUNXI_CE_ENCRYPTION 0
+#define SUNXI_CE_DECRYPTION BIT(8)
+
+#define SUNXI_CE_OP_ECB 0
+#define SUNXI_CE_OP_CBC BIT(8)
+
+#define SUNXI_CE_AES_KEY_128BIT 0
+#define SUNXI_CE_AES_KEY_192BIT 1
+#define SUNXI_CE_AES_KEY_256BIT 2
+
+#define SUNXI_CE_KEY_SELECT_INPUT 0
+#define SUNXI_CE_KEY_SELECT_SHIFT 20
+#define SUNXI_CE_AES_DIRECT_CHUNK_SIZE (128 * 1024)
+#define SUNXI_CE_AES_BOUNCE_CHUNK_SIZE (64 * 1024)
+#define SUNXI_CE_AES_BOUNCE_CHUNK_BLOCKS \
+ (SUNXI_CE_AES_BOUNCE_CHUNK_SIZE / AES_BLOCK_LENGTH)
+
+struct sunxi_aes_priv {
+ u8 key[AES256_KEY_LENGTH];
+ u32 key_bits;
+ u32 ce_key_size;
+};
+
+static int sunxi_ce_key_size(u32 key_bits, u32 *ce_key_size)
+{
+ switch (key_bits) {
+ case AES128_KEY_LENGTH * 8:
+ *ce_key_size = SUNXI_CE_AES_KEY_128BIT;
+ return 0;
+ case AES192_KEY_LENGTH * 8:
+ *ce_key_size = SUNXI_CE_AES_KEY_192BIT;
+ return 0;
+ case AES256_KEY_LENGTH * 8:
+ *ce_key_size = SUNXI_CE_AES_KEY_256BIT;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static bool sunxi_aes_ranges_overlap(const u8 *src, const u8 *dst, size_t len)
+{
+ uintptr_t src_start = (uintptr_t)src;
+ uintptr_t dst_start = (uintptr_t)dst;
+ uintptr_t src_end = src_start + len;
+ uintptr_t dst_end = dst_start + len;
+
+ return src_start < dst_end && dst_start < src_end;
+}
+
+static u32 sunxi_aes_direct_blocks(const u8 *src, const u8 *dst,
+ u32 num_blocks)
+{
+ u32 len = min_t(u32, num_blocks * AES_BLOCK_LENGTH,
+ SUNXI_CE_AES_DIRECT_CHUNK_SIZE);
+
+ if (!IS_ALIGNED((uintptr_t)src, ARCH_DMA_MINALIGN) ||
+ !IS_ALIGNED((uintptr_t)dst, ARCH_DMA_MINALIGN))
+ return 0;
+
+ return ALIGN_DOWN(len, ARCH_DMA_MINALIGN) / AES_BLOCK_LENGTH;
+}
+
+static u32 sunxi_aes_bounce_blocks(const u8 *dst, u32 num_blocks)
+{
+ uintptr_t dst_addr = (uintptr_t)dst;
+ u32 blocks = min_t(u32, num_blocks, SUNXI_CE_AES_BOUNCE_CHUNK_BLOCKS);
+ u32 head;
+
+ if (!IS_ALIGNED(dst_addr, AES_BLOCK_LENGTH))
+ return blocks;
+
+ head = ALIGN(dst_addr, ARCH_DMA_MINALIGN) - dst_addr;
+ if (head)
+ blocks = min_t(u32, blocks, head / AES_BLOCK_LENGTH);
+
+ return blocks;
+}
+
+static void sunxi_aes_fill_task(struct sunxi_ce_priv *ce,
+ struct sunxi_ce_task *task,
+ dma_addr_t key, dma_addr_t iv,
+ dma_addr_t src, dma_addr_t dst,
+ u32 len, u32 comm_ctl, u32 sym_ctl)
+{
+ u32 task_len = len / sizeof(u32);
+
+ memset(task, 0, sizeof(*task));
+
+ task->t_id = SUNXI_CE_CHAN;
+ task->t_common_ctl = comm_ctl | SUNXI_CE_COMM_INT;
+ task->t_sym_ctl = sym_ctl;
+ task->t_key = sunxi_ce_desc_dma_addr(ce, key);
+ if (iv)
+ task->t_iv = sunxi_ce_desc_dma_addr(ce, iv);
+ task->t_dlen = ce->variant->cipher_t_dlen_in_bytes ? len : task_len;
+ task->t_src[0].addr = sunxi_ce_desc_dma_addr(ce, src);
+ task->t_src[0].len = task_len;
+ task->t_dst[0].addr = sunxi_ce_desc_dma_addr(ce, dst);
+ task->t_dst[0].len = task_len;
+}
+
+static void sunxi_aes_update_iv(u8 *iv_work, u8 *src, u8 *dst, u32 len,
+ bool decrypt)
+{
+ if (decrypt)
+ memcpy(iv_work, src + len - AES_BLOCK_LENGTH,
+ AES_BLOCK_LENGTH);
+ else
+ memcpy(iv_work, dst + len - AES_BLOCK_LENGTH,
+ AES_BLOCK_LENGTH);
+}
+
+static int sunxi_aes_run_hw(struct sunxi_ce_priv *ce,
+ struct sunxi_ce_task *task, void *key,
+ u8 *iv, u8 *src, u8 *dst, u32 num_blocks,
+ u32 comm_ctl, u32 sym_ctl)
+{
+ u8 iv_work[AES_BLOCK_LENGTH];
+ u8 iv_next[AES_BLOCK_LENGTH];
+ u8 *iv_buf = NULL, *src_buf = NULL, *dst_buf = NULL;
+ dma_addr_t key_dma;
+ bool cbc = iv;
+ bool decrypt = comm_ctl & SUNXI_CE_DECRYPTION;
+ int ret;
+
+ if (cbc) {
+ iv_buf = memalign(ARCH_DMA_MINALIGN, ARCH_DMA_MINALIGN);
+ if (!iv_buf) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+ }
+
+ if (cbc)
+ memcpy(iv_work, iv, AES_BLOCK_LENGTH);
+
+ key_dma = dma_map_single(key, ARCH_DMA_MINALIGN, DMA_TO_DEVICE);
+ if (dma_mapping_error(NULL, key_dma)) {
+ ret = -EIO;
+ goto out_free;
+ }
+
+ while (num_blocks) {
+ u32 blocks = sunxi_aes_direct_blocks(src, dst, num_blocks);
+ bool direct = blocks > 0;
+ bool in_place = direct && src == dst;
+ enum dma_data_direction src_dir = in_place ? DMA_BIDIRECTIONAL :
+ DMA_TO_DEVICE;
+ u32 len = blocks * AES_BLOCK_LENGTH;
+ u8 *src_dma_buf = direct ? src : src_buf;
+ u8 *dst_dma_buf = direct ? dst : dst_buf;
+ dma_addr_t iv_dma = 0, src_dma, dst_dma;
+
+ if (!direct) {
+ if (!src_buf) {
+ src_buf = memalign(ARCH_DMA_MINALIGN,
+ SUNXI_CE_AES_BOUNCE_CHUNK_SIZE);
+ dst_buf = memalign(ARCH_DMA_MINALIGN,
+ SUNXI_CE_AES_BOUNCE_CHUNK_SIZE);
+ if (!src_buf || !dst_buf) {
+ ret = -ENOMEM;
+ goto out_unmap_key;
+ }
+ }
+ blocks = sunxi_aes_bounce_blocks(dst, num_blocks);
+ len = blocks * AES_BLOCK_LENGTH;
+ src_dma_buf = src_buf;
+ dst_dma_buf = dst_buf;
+ memcpy(src_buf, src, len);
+ }
+ if (cbc && decrypt && in_place)
+ memcpy(iv_next, src_dma_buf + len - AES_BLOCK_LENGTH,
+ AES_BLOCK_LENGTH);
+ if (cbc) {
+ memset(iv_buf, 0, ARCH_DMA_MINALIGN);
+ memcpy(iv_buf, iv_work, AES_BLOCK_LENGTH);
+ iv_dma = dma_map_single(iv_buf, ARCH_DMA_MINALIGN,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(NULL, iv_dma)) {
+ ret = -EIO;
+ goto out_unmap_key;
+ }
+ }
+
+ src_dma = dma_map_single(src_dma_buf, len, src_dir);
+ if (dma_mapping_error(NULL, src_dma)) {
+ ret = -EIO;
+ goto out_unmap_iv;
+ }
+
+ if (in_place) {
+ dst_dma = src_dma;
+ } else {
+ dst_dma = dma_map_single(dst_dma_buf, len,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(NULL, dst_dma)) {
+ ret = -EIO;
+ goto out_unmap_src;
+ }
+ }
+
+ sunxi_aes_fill_task(ce, task, key_dma, iv_dma, src_dma,
+ dst_dma, len, comm_ctl, sym_ctl);
+ sunxi_ce_flush(task, sizeof(*task));
+
+ ret = sunxi_ce_run_task(ce, task);
+
+ if (!in_place)
+ dma_unmap_single(dst_dma, len, DMA_FROM_DEVICE);
+out_unmap_src:
+ dma_unmap_single(src_dma, len, src_dir);
+out_unmap_iv:
+ if (cbc)
+ dma_unmap_single(iv_dma, ARCH_DMA_MINALIGN,
+ DMA_TO_DEVICE);
+ if (ret)
+ goto out_unmap_key;
+
+ if (cbc) {
+ if (decrypt && in_place)
+ memcpy(iv_work, iv_next, AES_BLOCK_LENGTH);
+ else
+ sunxi_aes_update_iv(iv_work, src_dma_buf,
+ dst_dma_buf, len, decrypt);
+ }
+ if (!direct)
+ memcpy(dst, dst_buf, len);
+ num_blocks -= blocks;
+ src += len;
+ dst += len;
+ }
+
+ ret = 0;
+
+out_unmap_key:
+ dma_unmap_single(key_dma, ARCH_DMA_MINALIGN, DMA_TO_DEVICE);
+out_free:
+ free(iv_buf);
+ free(dst_buf);
+ free(src_buf);
+
+ return ret;
+}
+
+static int sunxi_aes_run(struct udevice *dev, u8 *iv, u8 *src, u8 *dst,
+ u32 num_blocks, u32 aes_mode, bool decrypt)
+{
+ struct sunxi_aes_priv *priv = dev_get_priv(dev);
+ struct sunxi_ce_priv *ce = dev_get_priv(dev_get_parent(dev));
+ struct sunxi_ce_task *tasks;
+ u8 *key_buf;
+ u32 comm_ctl, sym_ctl;
+ bool cbc = aes_mode == SUNXI_CE_OP_CBC;
+ size_t total_len;
+ int ret;
+
+ if (!priv->key_bits)
+ return -EINVAL;
+ if (!num_blocks)
+ return 0;
+ if (!src || !dst)
+ return -EINVAL;
+ if (cbc && !iv)
+ return -EINVAL;
+ total_len = (size_t)num_blocks * AES_BLOCK_LENGTH;
+
+ /*
+ * Exact in-place operation is supported, but chunked bounce processing
+ * does not provide memmove-style semantics for partial overlaps.
+ */
+ if (src != dst && sunxi_aes_ranges_overlap(src, dst, total_len))
+ return -EINVAL;
+
+ tasks = memalign(ARCH_DMA_MINALIGN,
+ ALIGN(sizeof(*tasks), ARCH_DMA_MINALIGN));
+ key_buf = memalign(ARCH_DMA_MINALIGN, ARCH_DMA_MINALIGN);
+ if (!tasks || !key_buf) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memset(key_buf, 0, ARCH_DMA_MINALIGN);
+ memcpy(key_buf, priv->key, priv->key_bits / 8);
+
+ comm_ctl = decrypt ? SUNXI_CE_DECRYPTION : SUNXI_CE_ENCRYPTION;
+ sym_ctl = priv->ce_key_size |
+ aes_mode |
+ (SUNXI_CE_KEY_SELECT_INPUT << SUNXI_CE_KEY_SELECT_SHIFT);
+
+ ret = sunxi_aes_run_hw(ce, tasks, key_buf, iv, src, dst, num_blocks,
+ comm_ctl, sym_ctl);
+
+out:
+ free(key_buf);
+ free(tasks);
+
+ return ret;
+}
+
+static int sunxi_aes_available_key_slots(struct udevice *dev)
+{
+ return 1;
+}
+
+static int sunxi_aes_select_key_slot(struct udevice *dev, u32 key_size,
+ u8 slot)
+{
+ struct sunxi_aes_priv *priv = dev_get_priv(dev);
+ u32 ce_key_size;
+ int ret;
+
+ if (slot)
+ return -EINVAL;
+
+ ret = sunxi_ce_key_size(key_size, &ce_key_size);
+ if (ret)
+ return ret;
+
+ priv->key_bits = key_size;
+ priv->ce_key_size = ce_key_size;
+
+ return 0;
+}
+
+static int sunxi_aes_set_key_for_key_slot(struct udevice *dev, u32 key_size,
+ u8 *key, u8 slot)
+{
+ struct sunxi_aes_priv *priv = dev_get_priv(dev);
+ u32 ce_key_size;
+ int ret;
+
+ if (slot || !key)
+ return -EINVAL;
+
+ ret = sunxi_ce_key_size(key_size, &ce_key_size);
+ if (ret)
+ return ret;
+
+ memcpy(priv->key, key, key_size / 8);
+ priv->key_bits = key_size;
+ priv->ce_key_size = ce_key_size;
+
+ return 0;
+}
+
+static int sunxi_aes_ecb_encrypt(struct udevice *dev, u8 *src, u8 *dst,
+ u32 num_blocks)
+{
+ return sunxi_aes_run(dev, NULL, src, dst, num_blocks,
+ SUNXI_CE_OP_ECB, false);
+}
+
+static int sunxi_aes_ecb_decrypt(struct udevice *dev, u8 *src, u8 *dst,
+ u32 num_blocks)
+{
+ return sunxi_aes_run(dev, NULL, src, dst, num_blocks,
+ SUNXI_CE_OP_ECB, true);
+}
+
+static int sunxi_aes_cbc_encrypt(struct udevice *dev, u8 *iv, u8 *src,
+ u8 *dst, u32 num_blocks)
+{
+ return sunxi_aes_run(dev, iv, src, dst, num_blocks,
+ SUNXI_CE_OP_CBC, false);
+}
+
+static int sunxi_aes_cbc_decrypt(struct udevice *dev, u8 *iv, u8 *src,
+ u8 *dst, u32 num_blocks)
+{
+ return sunxi_aes_run(dev, iv, src, dst, num_blocks,
+ SUNXI_CE_OP_CBC, true);
+}
+
+static const struct aes_ops sunxi_aes_ops = {
+ .available_key_slots = sunxi_aes_available_key_slots,
+ .select_key_slot = sunxi_aes_select_key_slot,
+ .set_key_for_key_slot = sunxi_aes_set_key_for_key_slot,
+ .aes_ecb_encrypt = sunxi_aes_ecb_encrypt,
+ .aes_ecb_decrypt = sunxi_aes_ecb_decrypt,
+ .aes_cbc_encrypt = sunxi_aes_cbc_encrypt,
+ .aes_cbc_decrypt = sunxi_aes_cbc_decrypt,
+};
+
+U_BOOT_DRIVER(sun8i_ce_aes) = {
+ .name = "sun8i-ce-aes",
+ .id = UCLASS_AES,
+ .ops = &sunxi_aes_ops,
+ .priv_auto = sizeof(struct sunxi_aes_priv),
+ .flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
new file mode 100644
index 00000000000..3674e54ec12
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 James Hilliard
+ */
+
+#include <cpu_func.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/lists.h>
+#include <asm/arch/cpu.h>
+#include <asm/cache.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include "sun8i-ce.h"
+
+#define SUNXI_CE_TDQ 0x00
+#define SUNXI_CE_ICR 0x08
+#define SUNXI_CE_ISR 0x0c
+#define SUNXI_CE_TLR 0x10
+#define SUNXI_CE_ESR 0x18
+#define SUNXI_CE_SCSA 0x24
+#define SUNXI_CE_SCDA 0x28
+
+#define SUNXI_CE_CHAN_ERR_MASK (0xff << (SUNXI_CE_CHAN * 8))
+#define SUNXI_CE_ERR_ALGO_NOTSUP BIT(0)
+#define SUNXI_CE_ERR_DATALEN BIT(1)
+#define SUNXI_CE_ERR_KEYSRAM BIT(2)
+#define SUNXI_CE_ERR_ADDR_INVALID BIT(5)
+#define SUNXI_CE_ERR_KEYLADDER BIT(6)
+#define SUNXI_CE_TASK_START BIT(0)
+#define SUNXI_CE_METHOD_MASK GENMASK(6, 0)
+#define SUNXI_CE_TLR_METHOD_SHIFT 8
+#define SUNXI_CE_WORD_SHIFT 2
+#define SUNXI_CE_TIMEOUT_US 3000000
+
+#define SUN50I_H6_CCU_CE_CLK 0x680
+#define SUN50I_H6_CCU_CE_BGR 0x68c
+#define SUN50I_H6_CCU_MBUS_GATE 0x804
+#define SUN50I_H6_CCU_CE_CLK_SRC_MASK BIT(24)
+#define SUN50I_H6_CCU_CE_CLK_N_MASK GENMASK(9, 8)
+#define SUN50I_H6_CCU_CE_CLK_M_MASK GENMASK(3, 0)
+#define SUN50I_H6_CCU_CE_CLK_GATE BIT(31)
+#define SUN50I_H6_CCU_CE_BUS_GATE BIT(0)
+#define SUN50I_H6_CCU_CE_BUS_RST BIT(16)
+#define SUN50I_H6_CCU_MBUS_GATE_CE BIT(2)
+#define SUN50I_H616_CCU_CE_CLK_M 7
+
+u32 sunxi_ce_desc_dma_addr(struct sunxi_ce_priv *priv, dma_addr_t addr)
+{
+ if (priv->variant->needs_word_addresses)
+ addr >>= SUNXI_CE_WORD_SHIFT;
+
+ return (u32)addr;
+}
+
+void sunxi_ce_flush(void *buf, size_t len)
+{
+ ulong start = ALIGN_DOWN((ulong)buf, ARCH_DMA_MINALIGN);
+ ulong end = ALIGN((ulong)buf + len, ARCH_DMA_MINALIGN);
+
+ flush_dcache_range(start, end);
+}
+
+static void sunxi_ce_print_error(u32 err)
+{
+ printf("CE ERROR: %#x\n", err);
+ if (err & SUNXI_CE_ERR_ALGO_NOTSUP)
+ printf("CE ERROR: algorithm not supported\n");
+ if (err & SUNXI_CE_ERR_DATALEN)
+ printf("CE ERROR: data length error\n");
+ if (err & SUNXI_CE_ERR_KEYSRAM)
+ printf("CE ERROR: keysram access error for AES\n");
+ if (err & SUNXI_CE_ERR_ADDR_INVALID)
+ printf("CE ERROR: address invalid\n");
+ if (err & SUNXI_CE_ERR_KEYLADDER)
+ printf("CE ERROR: key ladder configuration error\n");
+}
+
+int sunxi_ce_run_task(struct sunxi_ce_priv *priv, struct sunxi_ce_task *task)
+{
+ u32 method = task->t_common_ctl & SUNXI_CE_METHOD_MASK;
+ u32 val, err;
+ int ret;
+
+ val = readl(priv->base + SUNXI_CE_ICR);
+ writel(val | SUNXI_CE_CHAN_INT, priv->base + SUNXI_CE_ICR);
+ writel(SUNXI_CE_CHAN_INT, priv->base + SUNXI_CE_ISR);
+ writel(SUNXI_CE_CHAN_ERR_MASK, priv->base + SUNXI_CE_ESR);
+ ret = readl_poll_timeout(priv->base + SUNXI_CE_ISR, val,
+ !(val & SUNXI_CE_CHAN_INT),
+ SUNXI_CE_TIMEOUT_US);
+ if (ret) {
+ printf("%s: timeout waiting for stale interrupt\n", __func__);
+ return ret;
+ }
+ ret = readl_poll_timeout(priv->base + SUNXI_CE_TLR, val, !val,
+ SUNXI_CE_TIMEOUT_US);
+ if (ret) {
+ printf("%s: timeout waiting for task launcher\n", __func__);
+ return ret;
+ }
+
+ writel(sunxi_ce_desc_dma_addr(priv, virt_to_phys(task)),
+ priv->base + SUNXI_CE_TDQ);
+ /* Be sure all data is written before enabling the task. */
+ wmb();
+ writel((method << SUNXI_CE_TLR_METHOD_SHIFT) | SUNXI_CE_TASK_START,
+ priv->base + SUNXI_CE_TLR);
+
+ ret = readl_poll_timeout(priv->base + SUNXI_CE_ISR, val,
+ val & SUNXI_CE_CHAN_INT,
+ SUNXI_CE_TIMEOUT_US);
+ if (ret) {
+ printf("%s: DMA timeout\n", __func__);
+ writel(SUNXI_CE_CHAN_INT, priv->base + SUNXI_CE_ISR);
+ writel(SUNXI_CE_CHAN_ERR_MASK, priv->base + SUNXI_CE_ESR);
+ return ret;
+ }
+
+ ret = readl_poll_timeout(priv->base + SUNXI_CE_SCSA, val, !val,
+ SUNXI_CE_TIMEOUT_US);
+ if (ret) {
+ printf("%s: timeout waiting for source DMA idle\n", __func__);
+ return ret;
+ }
+ ret = readl_poll_timeout(priv->base + SUNXI_CE_SCDA, val, !val,
+ SUNXI_CE_TIMEOUT_US);
+ if (ret) {
+ printf("%s: timeout waiting for destination DMA idle\n",
+ __func__);
+ return ret;
+ }
+
+ writel(0, priv->base + SUNXI_CE_TLR);
+ clrbits_le32(priv->base + SUNXI_CE_ICR, SUNXI_CE_CHAN_INT);
+ writel(0, priv->base + SUNXI_CE_TDQ);
+ ret = readl_poll_timeout(priv->base + SUNXI_CE_TLR, val, !val,
+ SUNXI_CE_TIMEOUT_US);
+ if (ret) {
+ printf("%s: timeout clearing task launcher\n", __func__);
+ return ret;
+ }
+ readl(priv->base + SUNXI_CE_TDQ);
+
+ err = readl(priv->base + SUNXI_CE_ESR) & SUNXI_CE_CHAN_ERR_MASK;
+ writel(SUNXI_CE_CHAN_ERR_MASK, priv->base + SUNXI_CE_ESR);
+
+ if (err) {
+ sunxi_ce_print_error(err >> (SUNXI_CE_CHAN * 8));
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int sunxi_ce_reset(struct sunxi_ce_priv *priv)
+{
+ int ret;
+
+ if (IS_ENABLED(CONFIG_XPL_BUILD)) {
+ if (!priv->variant->spl_reset)
+ return -EOPNOTSUPP;
+
+ priv->variant->spl_reset();
+ return 0;
+ }
+
+ ret = reset_assert_bulk(&priv->resets);
+ if (ret)
+ return ret;
+
+ udelay(1);
+
+ ret = reset_deassert_bulk(&priv->resets);
+ if (ret)
+ return ret;
+
+ udelay(10);
+
+ return 0;
+}
+
+static void sun50i_h6_ce_spl_reset(void)
+{
+ void __iomem *ccu = (void __iomem *)SUNXI_CCM_BASE;
+
+ clrbits_le32(ccu + SUN50I_H6_CCU_CE_BGR,
+ SUN50I_H6_CCU_CE_BUS_RST);
+ udelay(1);
+ setbits_le32(ccu + SUN50I_H6_CCU_CE_BGR,
+ SUN50I_H6_CCU_CE_BUS_RST);
+ udelay(10);
+}
+
+static void sun50i_h6_ce_spl_enable_clocks(void)
+{
+ void __iomem *ccu = (void __iomem *)SUNXI_CCM_BASE;
+
+ clrsetbits_le32(ccu + SUN50I_H6_CCU_CE_CLK,
+ SUN50I_H6_CCU_CE_CLK_SRC_MASK |
+ SUN50I_H6_CCU_CE_CLK_N_MASK |
+ SUN50I_H6_CCU_CE_CLK_M_MASK,
+ SUN50I_H6_CCU_CE_CLK_GATE);
+ setbits_le32(ccu + SUN50I_H6_CCU_CE_BGR,
+ SUN50I_H6_CCU_CE_BUS_GATE |
+ SUN50I_H6_CCU_CE_BUS_RST);
+ setbits_le32(ccu + SUN50I_H6_CCU_MBUS_GATE,
+ SUN50I_H6_CCU_MBUS_GATE_CE);
+ sun50i_h6_ce_spl_reset();
+}
+
+static void sun50i_h616_ce_setup_mod_clock(void)
+{
+ void __iomem *ccu = (void __iomem *)SUNXI_CCM_BASE;
+
+ clrsetbits_le32(ccu + SUN50I_H6_CCU_CE_CLK,
+ SUN50I_H6_CCU_CE_CLK_GATE |
+ SUN50I_H6_CCU_CE_CLK_SRC_MASK |
+ SUN50I_H6_CCU_CE_CLK_N_MASK |
+ SUN50I_H6_CCU_CE_CLK_M_MASK,
+ SUN50I_H6_CCU_CE_CLK_SRC_MASK |
+ SUN50I_H616_CCU_CE_CLK_M);
+}
+
+static void sun50i_h616_ce_spl_enable_clocks(void)
+{
+ void __iomem *ccu = (void __iomem *)SUNXI_CCM_BASE;
+
+ sun50i_h616_ce_setup_mod_clock();
+ setbits_le32(ccu + SUN50I_H6_CCU_CE_BGR,
+ SUN50I_H6_CCU_CE_BUS_RST);
+ setbits_le32(ccu + SUN50I_H6_CCU_CE_BGR,
+ SUN50I_H6_CCU_CE_BUS_GATE);
+ setbits_le32(ccu + SUN50I_H6_CCU_CE_CLK,
+ SUN50I_H6_CCU_CE_CLK_GATE);
+ setbits_le32(ccu + SUN50I_H6_CCU_MBUS_GATE,
+ SUN50I_H6_CCU_MBUS_GATE_CE);
+ sun50i_h6_ce_spl_reset();
+}
+
+static int sunxi_ce_bind(struct udevice *dev)
+{
+ const struct sunxi_ce_variant *variant =
+ (const struct sunxi_ce_variant *)dev_get_driver_data(dev);
+ int ret;
+
+ if (CONFIG_IS_ENABLED(SUNXI_CE_AES) && variant->has_aes) {
+ ret = device_bind_driver(dev, "sun8i-ce-aes",
+ "sun8i-ce-aes", NULL);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sunxi_ce_probe(struct udevice *dev)
+{
+ struct sunxi_ce_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ priv->variant = (const struct sunxi_ce_variant *)
+ dev_get_driver_data(dev);
+ priv->base = dev_read_addr_ptr(dev);
+ if (!priv->base)
+ return -EINVAL;
+
+ if (IS_ENABLED(CONFIG_XPL_BUILD)) {
+ if (!priv->variant->spl_enable_clocks)
+ return -ENOSYS;
+
+ priv->variant->spl_enable_clocks();
+ return 0;
+ }
+
+ ret = reset_get_bulk(dev, &priv->resets);
+ if (ret) {
+ dev_err(dev, "failed to get resets: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_get_bulk(dev, &priv->clks);
+ if (ret) {
+ dev_err(dev, "failed to get clocks: %d\n", ret);
+ goto err_release_resets;
+ }
+
+ if (priv->variant->setup_mod_clock)
+ priv->variant->setup_mod_clock();
+
+ ret = reset_deassert_bulk(&priv->resets);
+ if (ret) {
+ dev_err(dev, "failed to deassert resets: %d\n", ret);
+ goto err_release_clks;
+ }
+
+ ret = clk_enable_bulk(&priv->clks);
+ if (ret) {
+ dev_err(dev, "failed to enable clocks: %d\n", ret);
+ goto err_assert_resets;
+ }
+
+ ret = sunxi_ce_reset(priv);
+ if (ret) {
+ dev_err(dev, "failed to reset CE: %d\n", ret);
+ goto err_disable_clks;
+ }
+
+ return 0;
+
+err_disable_clks:
+ clk_disable_bulk(&priv->clks);
+err_assert_resets:
+ reset_assert_bulk(&priv->resets);
+err_release_clks:
+ clk_release_bulk(&priv->clks);
+err_release_resets:
+ reset_release_bulk(&priv->resets);
+
+ return ret;
+}
+
+static int sunxi_ce_remove(struct udevice *dev)
+{
+ struct sunxi_ce_priv *priv = dev_get_priv(dev);
+
+ if (IS_ENABLED(CONFIG_XPL_BUILD))
+ return 0;
+
+ clk_disable_bulk(&priv->clks);
+ clk_release_bulk(&priv->clks);
+ reset_assert_bulk(&priv->resets);
+ reset_release_bulk(&priv->resets);
+
+ return 0;
+}
+
+static const struct sunxi_ce_variant sun50i_h6_variant = {
+ .has_aes = true,
+ .cipher_t_dlen_in_bytes = true,
+ .spl_enable_clocks = sun50i_h6_ce_spl_enable_clocks,
+ .spl_reset = sun50i_h6_ce_spl_reset,
+};
+
+static const struct sunxi_ce_variant sun50i_h616_variant = {
+ .has_aes = true,
+ .cipher_t_dlen_in_bytes = true,
+ .needs_word_addresses = true,
+ .setup_mod_clock = sun50i_h616_ce_setup_mod_clock,
+ .spl_enable_clocks = sun50i_h616_ce_spl_enable_clocks,
+ .spl_reset = sun50i_h6_ce_spl_reset,
+};
+
+static const struct udevice_id sunxi_ce_ids[] = {
+ {
+ .compatible = "allwinner,sun50i-h6-crypto",
+ .data = (ulong)&sun50i_h6_variant,
+ }, {
+ .compatible = "allwinner,sun50i-h616-crypto",
+ .data = (ulong)&sun50i_h616_variant,
+ },
+ { }
+};
+
+U_BOOT_DRIVER(sun8i_ce) = {
+ .name = "sun8i-ce",
+ .id = UCLASS_NOP,
+ .of_match = sunxi_ce_ids,
+ .bind = sunxi_ce_bind,
+ .probe = sunxi_ce_probe,
+ .remove = sunxi_ce_remove,
+ .priv_auto = sizeof(struct sunxi_ce_priv),
+ .flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
new file mode 100644
index 00000000000..1580fff35fe
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2026 James Hilliard
+ */
+
+#ifndef __SUN8I_CE_H
+#define __SUN8I_CE_H
+
+#include <clk.h>
+#include <reset.h>
+#include <linux/bitops.h>
+#include <linux/types.h>
+
+#define SUNXI_CE_CHAN 0
+#define SUNXI_CE_CHAN_INT BIT(SUNXI_CE_CHAN)
+#define SUNXI_CE_COMM_INT BIT(31)
+#define SUNXI_CE_METHOD_AES 0
+#define SUNXI_CE_MAX_SG 8
+
+struct sunxi_ce_sginfo {
+ u32 addr;
+ u32 len;
+};
+
+struct sunxi_ce_task {
+ u32 t_id;
+ u32 t_common_ctl;
+ u32 t_sym_ctl;
+ u32 t_asym_ctl;
+ u32 t_key;
+ u32 t_iv;
+ u32 t_ctr;
+ u32 t_dlen;
+ struct sunxi_ce_sginfo t_src[SUNXI_CE_MAX_SG];
+ struct sunxi_ce_sginfo t_dst[SUNXI_CE_MAX_SG];
+ u32 next;
+ u32 reserved[3];
+};
+
+struct sunxi_ce_variant {
+ bool has_aes;
+ bool cipher_t_dlen_in_bytes;
+ bool needs_word_addresses;
+ void (*setup_mod_clock)(void);
+ void (*spl_enable_clocks)(void);
+ void (*spl_reset)(void);
+};
+
+struct sunxi_ce_priv {
+ void __iomem *base;
+ const struct sunxi_ce_variant *variant;
+ struct clk_bulk clks;
+ struct reset_ctl_bulk resets;
+};
+
+u32 sunxi_ce_desc_dma_addr(struct sunxi_ce_priv *priv, dma_addr_t addr);
+void sunxi_ce_flush(void *buf, size_t len);
+int sunxi_ce_run_task(struct sunxi_ce_priv *priv, struct sunxi_ce_task *task);
+int sunxi_ce_reset(struct sunxi_ce_priv *priv);
+
+#endif
--
2.53.0
More information about the U-Boot
mailing list