diff --git a/MAINTAINERS b/MAINTAINERS
index 4ad2451e03..f07d9713af 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -583,6 +583,14 @@ F: include/hw/*/allwinner-h3*
 F: hw/arm/orangepi.c
 F: docs/system/arm/orangepi.rst
 
+Allwinner sun4i-ss cryptographic offloader
+M: Corentin LABBE <clabbe@baylibre.com>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: hw/crypto/allwinner-*
+F: include/hw/crypto/allwinner-*
+F: docs/system/arm/orangepi.rst
+
 ARM PrimeCell and CMSDK devices
 M: Peter Maydell <peter.maydell@linaro.org>
 L: qemu-arm@nongnu.org
diff --git a/docs/system/arm/cubieboard.rst b/docs/system/arm/cubieboard.rst
index 344ff8cef9..7836643ba4 100644
--- a/docs/system/arm/cubieboard.rst
+++ b/docs/system/arm/cubieboard.rst
@@ -14,3 +14,4 @@ Emulated devices:
 - SDHCI
 - USB controller
 - SATA controller
+- crypto
diff --git a/docs/system/devices/allwinner-sun4i-ss.rst b/docs/system/devices/allwinner-sun4i-ss.rst
new file mode 100644
index 0000000000..6e7d2142b5
--- /dev/null
+++ b/docs/system/devices/allwinner-sun4i-ss.rst
@@ -0,0 +1,31 @@
+Allwinner sun4i-ss
+==================
+
+The ``sun4i-ss`` emulates the Allwinner cryptographic offloader
+present on early Allwinner SoCs (A10, A10s, A13, A20, A33)
+In qemu only A10 via the cubieboard machine is supported.
+
+The emulated hardware is capable of handling the following algorithms:
+- SHA1 and MD5 hash algorithms
+- AES/DES/DES3 in CBC/ECB
+- PRNG
+
+The emulated hardware does not handle yet:
+- CTS for AES
+- CTR for AES/DES/DES3
+- IRQ and DMA mode
+Anyway the Linux driver also does not handle them yet.
+
+The emulation needs a real crypto backend, for the moment only gnutls/nettle is supported.
+So the device emulation needs qemu to be compiled with optionnal gnutls.
+
+Emulation limit
+---------------
+
+PRNG:
+The PRNG is not really emulated as its internal is not known.
+It is replaced by the g_random_int function from glib.
+
+SPEED:
+The emulated hardware is ""faster"" than real hw as any write
+already give results immediatly instead of a few delay in real HW.
diff --git a/hw/Kconfig b/hw/Kconfig
index ad20cce0a9..43bd7fc14d 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -6,6 +6,7 @@ source audio/Kconfig
 source block/Kconfig
 source char/Kconfig
 source core/Kconfig
+source crypto/Kconfig
 source display/Kconfig
 source dma/Kconfig
 source gpio/Kconfig
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 97f3b38019..fd8232b1d4 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -317,6 +317,7 @@ config ALLWINNER_A10
     select AHCI
     select ALLWINNER_A10_PIT
     select ALLWINNER_A10_PIC
+    select ALLWINNER_CRYPTO_SUN4I_SS
     select ALLWINNER_EMAC
     select SERIAL
     select UNIMP
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index 05e84728cb..e9104ee028 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -23,6 +23,7 @@
 #include "hw/misc/unimp.h"
 #include "sysemu/sysemu.h"
 #include "hw/boards.h"
+#include "hw/crypto/allwinner-sun4i-ss.h"
 #include "hw/usb/hcd-ohci.h"
 
 #define AW_A10_MMC0_BASE        0x01c0f000
@@ -32,6 +33,7 @@
 #define AW_A10_EMAC_BASE        0x01c0b000
 #define AW_A10_EHCI_BASE        0x01c14000
 #define AW_A10_OHCI_BASE        0x01c14400
+#define AW_A10_CRYPTO_BASE      0x01c15000
 #define AW_A10_SATA_BASE        0x01c18000
 #define AW_A10_RTC_BASE         0x01c20d00
 
@@ -48,6 +50,10 @@ static void aw_a10_init(Object *obj)
 
     object_initialize_child(obj, "emac", &s->emac, TYPE_AW_EMAC);
 
+#if defined CONFIG_NETTLE
+    object_initialize_child(obj, "crypto", &s->crypto, TYPE_AW_SUN4I_SS);
+#endif
+
     object_initialize_child(obj, "sata", &s->sata, TYPE_ALLWINNER_AHCI);
 
     if (machine_usb(current_machine)) {
@@ -115,6 +121,11 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
     sysbus_mmio_map(sysbusdev, 0, AW_A10_EMAC_BASE);
     sysbus_connect_irq(sysbusdev, 0, qdev_get_gpio_in(dev, 55));
 
+#if defined CONFIG_NETTLE
+    sysbus_realize(SYS_BUS_DEVICE(&s->crypto), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->crypto), 0, AW_A10_CRYPTO_BASE);
+#endif
+
     if (!sysbus_realize(SYS_BUS_DEVICE(&s->sata), errp)) {
         return;
     }
diff --git a/hw/crypto/Kconfig b/hw/crypto/Kconfig
new file mode 100644
index 0000000000..5ecfe44b10
--- /dev/null
+++ b/hw/crypto/Kconfig
@@ -0,0 +1,2 @@
+config ALLWINNER_CRYPTO_SUN4I_SS
+    bool
diff --git a/hw/crypto/allwinner-sun4i-ss.c b/hw/crypto/allwinner-sun4i-ss.c
new file mode 100644
index 0000000000..e7d85646e6
--- /dev/null
+++ b/hw/crypto/allwinner-sun4i-ss.c
@@ -0,0 +1,688 @@
+/*
+ * Allwinner sun4i-ss cryptographic offloader emulation
+ *
+ * Copyright (C) 2022 Corentin Labbe <clabbe@baylibre.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "qemu/module.h"
+#include "exec/cpu-common.h"
+#include "hw/crypto/allwinner-sun4i-ss.h"
+
+#include <nettle/aes.h>
+#include <nettle/cbc.h>
+#include <nettle/des.h>
+#include <nettle/md5.h>
+#include <nettle/sha1.h>
+
+#define SS_IV_ARBITRARY (1 << 14)
+
+/* SS operation mode - bits 12-13 */
+#define SS_ECB (0 << 12)
+#define SS_CBC (1 << 12)
+
+/* Key size for AES - bits 8-9 */
+#define SS_AES_128BITS (0 << 8)
+#define SS_AES_192BITS (1 << 8)
+#define SS_AES_256BITS (2 << 8)
+
+/* Operation direction - bit 7 */
+#define SS_ENCRYPTION  (0 << 7)
+#define SS_DECRYPTION  (1 << 7)
+
+/* SS Method - bits 4-6 */
+#define SS_OP_AES      (0 << 4)
+#define SS_OP_DES      (1 << 4)
+#define SS_OP_3DES     (2 << 4)
+#define SS_OP_SHA1     (3 << 4)
+#define SS_OP_MD5      (4 << 4)
+#define SS_OP_PRNG     (5 << 4)
+
+/* Data end bit - bit 2 */
+#define SS_DATA_END (1 << 2)
+
+/* SS Enable bit - bit 0 */
+#define SS_ENABLED (1 << 0)
+
+enum {
+    REG_CTL        = 0x0000,
+    REG_KEY_0      = 0x0004,
+    REG_KEY_1      = 0x0008,
+    REG_KEY_2      = 0x000c,
+    REG_KEY_3      = 0x0010,
+    REG_KEY_4      = 0x0014,
+    REG_KEY_5      = 0x0018,
+    REG_KEY_6      = 0x001c,
+    REG_KEY_7      = 0x0020,
+    REG_IV_0       = 0x0024,
+    REG_IV_1       = 0x0028,
+    REG_IV_2       = 0x002c,
+    REG_IV_3       = 0x0030,
+    REG_IV_4       = 0x0034,
+    REG_FCSR       = 0x0044,
+    REG_ICSR       = 0x0048,
+    REG_MD0        = 0x004c,
+    REG_MD1        = 0x0050,
+    REG_MD2        = 0x0054,
+    REG_MD3        = 0x0058,
+    REG_MD4        = 0x005c,
+    REG_RXFIFO     = 0x0200,
+    REG_TXFIFO     = 0x0204,
+};
+
+static void allwinner_sun4i_ss_try_work(AwSun4iSSState *s);
+
+/* return number of possible operation wih block size=bs */
+static unsigned int can_work(AwSun4iSSState *s, unsigned int bs)
+{
+    unsigned int avail_rx = s->rxc / (bs / 4);
+    unsigned int free_space_tx = (SS_TX_MAX - s->txc) / (bs / 4);
+
+    if (avail_rx > free_space_tx) {
+        return free_space_tx;
+    }
+    return avail_rx;
+}
+
+/*
+ * Without any knowledge on the PRNG, the only solution is
+ * to emulate it via g_random_int()
+ */
+static void do_prng(AwSun4iSSState *s)
+{
+    unsigned int size = 20;
+    int i;
+
+    for (i = 0; i < size / 4; i++) {
+        s->tx[i] = g_random_int();
+    }
+    s->txc += size / 4;
+}
+
+/* remove pop u32 words from RX */
+static void rx_pop(AwSun4iSSState *s, unsigned int pop)
+{
+    uint32_t *rx = (uint32_t *)s->rx;
+    int i;
+
+    for (i = 0; i < s->rxc; i++) {
+        rx[i] = rx[i + pop];
+    }
+}
+
+static void do_md5(AwSun4iSSState *s)
+{
+    unsigned int size = MD5_BLOCK_SIZE;
+    unsigned char *src = s->rx;
+
+    nettle_md5_compress(s->md, src);
+
+    s->rxc -= size / 4;
+    if (s->rxc > 0) {
+        rx_pop(s, size / 4);
+        allwinner_sun4i_ss_try_work(s);
+    }
+}
+
+static void do_sha1(AwSun4iSSState *s)
+{
+    unsigned int size = SHA1_BLOCK_SIZE;
+    unsigned char *src = s->rx;
+
+    nettle_sha1_compress(s->md, src);
+
+    s->rxc -= size / 4;
+    if (s->rxc > 0) {
+        rx_pop(s, size / 4);
+        allwinner_sun4i_ss_try_work(s);
+    }
+}
+
+static void do_des(AwSun4iSSState *s)
+{
+    struct CBC_CTX(struct des_ctx, DES_BLOCK_SIZE) cdes;
+    struct des_ctx des;
+    unsigned char *src = s->rx;
+    unsigned char *dst = s->tx + s->txc * 4;
+    unsigned char *key = (unsigned char *)s->key;
+    unsigned int size = DES_BLOCK_SIZE;
+    unsigned char biv[DES_BLOCK_SIZE];
+
+    if (s->ctl & SS_DECRYPTION) {
+        memcpy(biv, src, DES_BLOCK_SIZE);
+    }
+
+    if (s->ctl & SS_CBC) {
+        CBC_SET_IV(&cdes, s->iv);
+        if (s->ctl & SS_DECRYPTION) {
+            des_set_key(&cdes.ctx, key);
+            CBC_DECRYPT(&cdes, des_decrypt, size, dst, src);
+        } else {
+            des_set_key(&cdes.ctx, key);
+            CBC_ENCRYPT(&cdes, des_encrypt, size, dst, src);
+        }
+        /* Copy next IV in registers */
+        if (s->ctl & SS_DECRYPTION) {
+            memcpy(s->iv, biv, DES_BLOCK_SIZE);
+        } else {
+            memcpy(s->iv, dst, DES_BLOCK_SIZE);
+        }
+    } else {
+        if (s->ctl & SS_DECRYPTION) {
+            des_set_key(&des, key);
+            des_decrypt(&des, size, dst, src);
+        } else {
+                des_set_key(&des, key);
+                des_encrypt(&des, size, dst, src);
+            }
+    }
+    s->txc += size / 4;
+    s->rxc -= size / 4;
+
+    if (s->rxc > 0) {
+        rx_pop(s, size / 4);
+        allwinner_sun4i_ss_try_work(s);
+    }
+}
+
+static void do_des3(AwSun4iSSState *s)
+{
+    struct CBC_CTX(struct des3_ctx, DES3_BLOCK_SIZE) cdes3;
+    struct des3_ctx des3;
+    unsigned char *src = s->rx;
+    unsigned char *dst = s->tx + s->txc * 4;
+    unsigned char *key = (unsigned char *)s->key;
+    unsigned int size = DES3_BLOCK_SIZE;
+    unsigned char biv[DES3_BLOCK_SIZE];
+
+    if (s->ctl & SS_DECRYPTION) {
+        memcpy(biv, src, DES3_BLOCK_SIZE);
+    }
+
+    if (s->ctl & SS_CBC) {
+        CBC_SET_IV(&cdes3, s->iv);
+        if (s->ctl & SS_DECRYPTION) {
+            des3_set_key(&cdes3.ctx, key);
+            CBC_DECRYPT(&cdes3, des3_decrypt, size, dst, src);
+        } else {
+            des3_set_key(&cdes3.ctx, key);
+            CBC_ENCRYPT(&cdes3, des3_encrypt, size, dst, src);
+        }
+        /* Copy next IV in registers */
+        if (s->ctl & SS_DECRYPTION) {
+            memcpy(s->iv, biv, DES3_BLOCK_SIZE);
+        } else {
+            memcpy(s->iv, dst, DES3_BLOCK_SIZE);
+        }
+    } else {
+        if (s->ctl & SS_DECRYPTION) {
+            des3_set_key(&des3, key);
+            des3_decrypt(&des3, size, dst, src);
+        } else {
+            des3_set_key(&des3, key);
+            des3_encrypt(&des3, size, dst, src);
+        }
+    }
+    s->txc += size / 4;
+    s->rxc -= size / 4;
+
+    if (s->rxc > 0) {
+        rx_pop(s, size / 4);
+        allwinner_sun4i_ss_try_work(s);
+    }
+}
+
+static void do_aes(AwSun4iSSState *s)
+{
+    struct CBC_CTX(struct aes128_ctx, AES_BLOCK_SIZE) aes128;
+    struct CBC_CTX(struct aes192_ctx, AES_BLOCK_SIZE) aes192;
+    struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) aes256;
+    struct aes128_ctx ecb128;
+    struct aes192_ctx ecb192;
+    struct aes256_ctx ecb256;
+    unsigned char *src = s->rx;
+    unsigned char *dst = s->tx + s->txc * 4;
+    unsigned char *key = (unsigned char *)s->key;
+    unsigned int size = AES_BLOCK_SIZE;
+    unsigned char biv[AES_BLOCK_SIZE];
+
+    if (s->ctl & SS_DECRYPTION) {
+        memcpy(biv, src, AES_BLOCK_SIZE);
+    }
+
+    if (s->ctl & SS_CBC) {
+        switch (s->ctl & 0x300) {
+        case SS_AES_128BITS:
+            CBC_SET_IV(&aes128, s->iv);
+
+            if (s->ctl & SS_DECRYPTION) {
+                aes128_set_decrypt_key(&aes128.ctx, key);
+                CBC_DECRYPT(&aes128, aes128_decrypt, size, dst, src);
+            } else {
+                aes128_set_encrypt_key(&aes128.ctx, key);
+                CBC_ENCRYPT(&aes128, aes128_encrypt, size, dst, src);
+            }
+            break;
+        case SS_AES_192BITS:
+            CBC_SET_IV(&aes192, s->iv);
+
+            if (s->ctl & SS_DECRYPTION) {
+                aes192_set_decrypt_key(&aes192.ctx, key);
+                CBC_DECRYPT(&aes192, aes192_decrypt, size, dst, src);
+            } else {
+                aes192_set_encrypt_key(&aes192.ctx, key);
+                CBC_ENCRYPT(&aes192, aes192_encrypt, size, dst, src);
+            }
+            break;
+        case SS_AES_256BITS:
+            CBC_SET_IV(&aes256, s->iv);
+
+            if (s->ctl & SS_DECRYPTION) {
+                aes256_set_decrypt_key(&aes256.ctx, key);
+                CBC_DECRYPT(&aes256, aes256_decrypt, size, dst, src);
+            } else {
+                aes256_set_encrypt_key(&aes256.ctx, key);
+                CBC_ENCRYPT(&aes256, aes256_encrypt, size, dst, src);
+            }
+            break;
+        }
+        /* Copy next IV in registers */
+        if (s->ctl & SS_DECRYPTION) {
+            memcpy(s->iv, biv, AES_BLOCK_SIZE);
+        } else {
+            memcpy(s->iv, dst, AES_BLOCK_SIZE);
+        }
+    } else {
+        switch (s->ctl & 0x300) {
+        case SS_AES_128BITS:
+            if (s->ctl & SS_DECRYPTION) {
+                aes128_set_decrypt_key(&ecb128, key);
+                aes128_decrypt(&ecb128, size, dst, src);
+            } else {
+                aes128_set_encrypt_key(&ecb128, key);
+                aes128_encrypt(&ecb128, size, dst, src);
+            }
+            break;
+        case SS_AES_192BITS:
+            if (s->ctl & SS_DECRYPTION) {
+                aes192_set_decrypt_key(&ecb192, key);
+                aes192_decrypt(&ecb192, size, dst, src);
+            } else {
+                aes192_set_encrypt_key(&ecb192, key);
+                aes192_encrypt(&ecb192, size, dst, src);
+            }
+            break;
+        case SS_AES_256BITS:
+            if (s->ctl & SS_DECRYPTION) {
+                aes256_set_decrypt_key(&ecb256, (const unsigned char *) s->key);
+                aes256_decrypt(&ecb256, size, dst, src);
+            } else {
+                aes256_set_encrypt_key(&ecb256, (const unsigned char *) s->key);
+                aes256_encrypt(&ecb256, size, dst, src);
+            }
+            break;
+        }
+    }
+    s->txc += size / 4;
+    s->rxc -= size / 4;
+
+    if (s->rxc > 0) {
+        rx_pop(s, size / 4);
+        allwinner_sun4i_ss_try_work(s);
+    }
+}
+
+static void allwinner_sun4i_ss_update_fcsr(AwSun4iSSState *s)
+{
+    s->fcsr = (s->txc << 16) | ((32 - s->rxc) << 24);
+}
+
+static void allwinner_sun4i_ss_try_work(AwSun4iSSState *s)
+{
+    if (!(s->ctl & SS_ENABLED)) {
+        return;
+    }
+    if ((s->ctl & 0x70) == SS_OP_AES && can_work(s, AES_BLOCK_SIZE)) {
+        do_aes(s);
+        allwinner_sun4i_ss_update_fcsr(s);
+        return;
+    }
+    if ((s->ctl & 0x70) == SS_OP_DES && can_work(s, DES_BLOCK_SIZE)) {
+        do_des(s);
+        allwinner_sun4i_ss_update_fcsr(s);
+        return;
+    }
+    if ((s->ctl & 0x70) == SS_OP_3DES && can_work(s, DES3_BLOCK_SIZE)) {
+        do_des3(s);
+        allwinner_sun4i_ss_update_fcsr(s);
+        return;
+    }
+    if ((s->ctl & 0x70) == SS_OP_MD5 && s->rxc >= MD5_BLOCK_SIZE / 4) {
+        do_md5(s);
+        allwinner_sun4i_ss_update_fcsr(s);
+        return;
+    }
+    if ((s->ctl & 0x70) == SS_OP_SHA1 && s->rxc >= SHA1_BLOCK_SIZE / 4) {
+        do_sha1(s);
+        allwinner_sun4i_ss_update_fcsr(s);
+        return;
+    }
+    if ((s->ctl & 0x70) == SS_OP_PRNG) {
+        do_prng(s);
+        allwinner_sun4i_ss_update_fcsr(s);
+        return;
+    }
+}
+
+static uint32_t tx_pop(AwSun4iSSState *s)
+{
+    uint32_t *tx = (uint32_t *)s->tx;
+    uint32_t v = 0;
+    int i;
+
+    if (s->txc > 0) {
+        v = tx[0];
+        s->txc--;
+        for (i = 0; i < s->txc; i++) {
+            tx[i] = tx[i + 1];
+        }
+        allwinner_sun4i_ss_update_fcsr(s);
+        allwinner_sun4i_ss_try_work(s);
+    }
+    return v;
+}
+
+static void allwinner_sun4i_ss_reset_common(AwSun4iSSState *s)
+{
+    s->ctl = 0;
+    s->txc = 0;
+    s->rxc = 0;
+    allwinner_sun4i_ss_update_fcsr(s);
+}
+
+static void allwinner_sun4i_ss_reset(DeviceState *dev)
+{
+    AwSun4iSSState *s = AW_SUN4I_SS(dev);
+
+    trace_allwinner_sun4i_ss_reset();
+
+    allwinner_sun4i_ss_reset_common(s);
+}
+
+static uint64_t allwinner_sun4i_ss_read(void *opaque, hwaddr offset,
+                                          unsigned size)
+{
+    AwSun4iSSState *s = AW_SUN4I_SS(opaque);
+    uint64_t value = 0;
+
+    switch (offset) {
+    case REG_CTL:
+        value = s->ctl;
+        break;
+    case REG_IV_0:
+        value = s->iv[0];
+        break;
+    case REG_IV_1:
+        value = s->iv[1];
+        break;
+    case REG_IV_2:
+        value = s->iv[2];
+        break;
+    case REG_IV_3:
+        value = s->iv[3];
+        break;
+    case REG_IV_4:
+        value = s->iv[4];
+        break;
+    case REG_FCSR:
+        value = s->fcsr;
+        break;
+    case REG_KEY_0:
+        value = s->key[0];
+        break;
+    case REG_KEY_1:
+        value = s->key[1];
+        break;
+    case REG_KEY_2:
+        value = s->key[2];
+        break;
+    case REG_KEY_3:
+        value = s->key[3];
+        break;
+    case REG_KEY_4:
+        value = s->key[4];
+        break;
+    case REG_KEY_5:
+        value = s->key[5];
+        break;
+    case REG_KEY_6:
+        value = s->key[6];
+        break;
+    case REG_KEY_7:
+        value = s->key[7];
+        break;
+    case REG_MD0:
+        value = s->md[0];
+        break;
+    case REG_MD1:
+        value = s->md[1];
+        break;
+    case REG_MD2:
+        value = s->md[2];
+        break;
+    case REG_MD3:
+        value = s->md[3];
+        break;
+    case REG_MD4:
+        value = s->md[4];
+        break;
+    case REG_TXFIFO:
+        value = tx_pop(s);
+        break;
+    case REG_RXFIFO:
+        value = s->rx[0];
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP, "allwinner_sun4i_ss: read access to unknown "
+                                 "CRYPTO register 0x" TARGET_FMT_plx "\n",
+                                  offset);
+    }
+
+    trace_allwinner_sun4i_ss_read(offset, value);
+    return value;
+}
+
+static void rx_push(AwSun4iSSState *s, uint32_t value)
+{
+    uint32_t *rx = (uint32_t *)s->rx;
+
+    if (!(s->ctl & SS_ENABLED)) {
+        return;
+    }
+    if (s->rxc > SS_RX_MAX) {
+        return;
+    }
+    rx[s->rxc] = value;
+    s->rxc++;
+    allwinner_sun4i_ss_update_fcsr(s);
+    allwinner_sun4i_ss_try_work(s);
+
+    return;
+}
+
+static void allwinner_sun4i_ss_write(void *opaque, hwaddr offset,
+                                       uint64_t value, unsigned size)
+{
+    AwSun4iSSState *s = AW_SUN4I_SS(opaque);
+    bool was_disabled = !(s->ctl & SS_ENABLED);
+
+    trace_allwinner_sun4i_ss_write(offset, value);
+
+    switch (offset) {
+    case REG_CTL:
+        s->ctl = value;
+        if (!(s->ctl & SS_ENABLED)) {
+            allwinner_sun4i_ss_reset_common(s);
+            break;
+        }
+        if (was_disabled) {
+            if (s->ctl & SS_IV_ARBITRARY) {
+                s->md[0] = s->iv[0];
+                s->md[1] = s->iv[1];
+                s->md[2] = s->iv[2];
+                s->md[3] = s->iv[3];
+                s->md[4] = s->iv[4];
+            } else {
+                if ((s->ctl & 0x70) == SS_OP_MD5) {
+                    s->md[0] = 0x67452301;
+                    s->md[1] = 0xefcdab89;
+                    s->md[2] = 0x98badcfe;
+                    s->md[3] = 0x10325476;
+                } else {
+                    s->md[0] = 0x67452301;
+                    s->md[1] = 0xefcdab89;
+                    s->md[2] = 0x98badcfe;
+                    s->md[3] = 0x10325476;
+                    s->md[4] = 0xC3D2E1F0;
+                }
+            }
+        }
+        if ((s->ctl & 0x70) == SS_OP_PRNG) {
+            do_prng(s);
+            allwinner_sun4i_ss_update_fcsr(s);
+        }
+        if ((s->ctl & 0x70) == SS_OP_MD5 && s->ctl & SS_DATA_END) {
+            s->ctl &= ~SS_DATA_END;
+            return;
+        }
+        if ((s->ctl & 0x70) == SS_OP_SHA1 && s->ctl & SS_DATA_END) {
+            s->ctl &= ~SS_DATA_END;
+            return;
+        }
+        break;
+    case REG_IV_0:
+        s->iv[0] = value;
+        break;
+    case REG_IV_1:
+        s->iv[1] = value;
+        break;
+    case REG_IV_2:
+        s->iv[2] = value;
+        break;
+    case REG_IV_3:
+        s->iv[3] = value;
+        break;
+    case REG_IV_4:
+        s->iv[4] = value;
+        break;
+    case REG_KEY_0:
+        s->key[0] = value;
+        break;
+    case REG_KEY_1:
+        s->key[1] = value;
+        break;
+    case REG_KEY_2:
+        s->key[2] = value;
+        break;
+    case REG_KEY_3:
+        s->key[3] = value;
+        break;
+    case REG_KEY_4:
+        s->key[4] = value;
+        break;
+    case REG_KEY_5:
+        s->key[5] = value;
+        break;
+    case REG_KEY_6:
+        s->key[6] = value;
+        break;
+    case REG_KEY_7:
+        s->key[7] = value;
+        break;
+    case REG_RXFIFO:
+        rx_push(s, value);
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP, "allwinner_sun4i_ss: write access to unknown "
+                                 "CRYPTO register 0x" TARGET_FMT_plx "\n",
+                                  offset);
+    }
+}
+
+static const MemoryRegionOps allwinner_sun4i_ss_mem_ops = {
+    .read = allwinner_sun4i_ss_read,
+    .write = allwinner_sun4i_ss_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .impl.min_access_size = 4,
+};
+
+static void allwinner_sun4i_ss_init(Object *obj)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    AwSun4iSSState *s = AW_SUN4I_SS(obj);
+
+    memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_sun4i_ss_mem_ops,
+                           s, TYPE_AW_SUN4I_SS, 4 * KiB);
+    sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription vmstate_allwinner_sun4i_ss = {
+    .name = "allwinner-sun4i-ss",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(ctl, AwSun4iSSState),
+        VMSTATE_UINT32(fcsr, AwSun4iSSState),
+        VMSTATE_UINT32_ARRAY(iv, AwSun4iSSState, 5),
+        VMSTATE_UINT32_ARRAY(key, AwSun4iSSState, 8),
+        VMSTATE_UINT32_ARRAY(md, AwSun4iSSState, 5),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void allwinner_sun4i_ss_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->reset = allwinner_sun4i_ss_reset;
+    dc->vmsd = &vmstate_allwinner_sun4i_ss;
+}
+
+static const TypeInfo allwinner_sun4i_ss_info = {
+    .name           = TYPE_AW_SUN4I_SS,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(AwSun4iSSState),
+    .instance_init  = allwinner_sun4i_ss_init,
+    .class_init     = allwinner_sun4i_ss_class_init,
+};
+
+static void allwinner_sun4i_ss_register_types(void)
+{
+    type_register_static(&allwinner_sun4i_ss_info);
+}
+
+type_init(allwinner_sun4i_ss_register_types)
diff --git a/hw/crypto/meson.build b/hw/crypto/meson.build
new file mode 100644
index 0000000000..4368e1e401
--- /dev/null
+++ b/hw/crypto/meson.build
@@ -0,0 +1,3 @@
+if nettle.found()
+    softmmu_ss.add(when: 'CONFIG_ALLWINNER_CRYPTO_SUN4I_SS', if_true: files('allwinner-sun4i-ss.c'))
+endif
diff --git a/hw/crypto/trace-events b/hw/crypto/trace-events
new file mode 100644
index 0000000000..39739b317b
--- /dev/null
+++ b/hw/crypto/trace-events
@@ -0,0 +1,3 @@
+allwinner_sun4i_ss_reset(void) "HW reset"
+allwinner_sun4i_ss_read(uint64_t offset, uint64_t val) "MMIO read: offset=0x%" PRIx64 " value=0x%" PRIx64
+allwinner_sun4i_ss_write(uint64_t offset, uint64_t val) "MMIO write: offset=0x%" PRIx64 " value=0x%" PRIx64
diff --git a/hw/crypto/trace.h b/hw/crypto/trace.h
new file mode 100644
index 0000000000..36d01d937d
--- /dev/null
+++ b/hw/crypto/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_crypto.h"
diff --git a/hw/meson.build b/hw/meson.build
index b3366c888e..b8d2cdcc38 100644
--- a/hw/meson.build
+++ b/hw/meson.build
@@ -6,6 +6,7 @@ subdir('block')
 subdir('char')
 subdir('core')
 subdir('cpu')
+subdir('crypto')
 subdir('display')
 subdir('dma')
 subdir('gpio')
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index a76dc7b84d..d5b1d3f616 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -4,6 +4,7 @@
 #include "qemu/error-report.h"
 #include "hw/char/serial.h"
 #include "hw/arm/boot.h"
+#include "hw/crypto/allwinner-sun4i-ss.h"
 #include "hw/timer/allwinner-a10-pit.h"
 #include "hw/intc/allwinner-a10-pic.h"
 #include "hw/net/allwinner_emac.h"
@@ -33,6 +34,7 @@ struct AwA10State {
     AwA10PITState timer;
     AwA10PICState intc;
     AwEmacState emac;
+    AwSun4iSSState crypto;
     AllwinnerAHCIState sata;
     AwSdHostState mmc0;
     AwRtcState rtc;
diff --git a/include/hw/crypto/allwinner-sun4i-ss.h b/include/hw/crypto/allwinner-sun4i-ss.h
new file mode 100644
index 0000000000..e604819ad0
--- /dev/null
+++ b/include/hw/crypto/allwinner-sun4i-ss.h
@@ -0,0 +1,72 @@
+/*
+ * Allwinner sun4i-ss cryptographic offloader emulation
+ *
+ * Copyright (C) 2022 Corentin Labbe <clabbe@baylibre.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HW_CRYPTO_ALLWINNER_SUN4I_SS_H
+#define HW_CRYPTO_ALLWINNER_SUN4I_SS_H
+
+#include "qom/object.h"
+#include "hw/sysbus.h"
+
+#define SS_RX_MAX     32
+#define SS_RX_DEFAULT SS_RX_MAX
+#define SS_TX_MAX     33
+
+/**
+ * Object model
+ * @{
+ */
+
+#define TYPE_AW_SUN4I_SS "allwinner-sun4i-ss"
+OBJECT_DECLARE_SIMPLE_TYPE(AwSun4iSSState, AW_SUN4I_SS)
+
+/** @} */
+
+/**
+ * Allwinner sun4i-ss crypto object instance state
+ */
+struct AwSun4iSSState {
+    /*< private >*/
+    SysBusDevice  parent_obj;
+    /*< public >*/
+
+    /** Maps I/O registers in physical memory */
+    MemoryRegion iomem;
+
+    /** @} */
+    unsigned char rx[SS_RX_MAX * 4];
+    unsigned int rxc;
+    unsigned char tx[SS_TX_MAX * 4];
+    unsigned int txc;
+
+    /**
+     * @name Hardware Registers
+     * @{
+     */
+
+    uint32_t    ctl;    /**< Control register */
+    uint32_t    fcsr;   /**< FIFO control register */
+    uint32_t    iv[5];  /**< IV registers */
+    uint32_t    key[8]; /**< KEY registers */
+    uint32_t    md[5];  /**< Message Digest registers */
+
+    /** @} */
+
+};
+
+#endif /* HW_CRYPTO_ALLWINNER_SUN4I_SS_H */
diff --git a/meson.build b/meson.build
index 861de93c4f..cf0bf07bf4 100644
--- a/meson.build
+++ b/meson.build
@@ -2718,6 +2718,7 @@ if have_system
     'hw/block',
     'hw/block/dataplane',
     'hw/char',
+    'hw/crypto',
     'hw/display',
     'hw/dma',
     'hw/hppa',
