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 <[email protected]>
---
Changes v1 -> v2:
  - Expose slot 0 as the software-provided AES key slot
---
 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 +
 drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c  | 413 ++++++++++++++++++++++
 drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c | 375 ++++++++++++++++++++
 drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h      |  61 ++++
 10 files changed, 902 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 030f92a84f9..57e56680437 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -762,6 +762,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..6870eb38a65
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c
@@ -0,0 +1,413 @@
+// 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_get_software_key_slot(struct udevice *dev)
+{
+       return 0;
+}
+
+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,
+       .get_software_key_slot = sunxi_aes_get_software_key_slot,
+       .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

Reply via email to