diff mbox series

[v6,1/6] mfd: Add support for Loongson Security Module

Message ID 20250327021809.29954-2-zhaoqunqin@loongson.cn (mailing list archive)
State New
Headers show
Series Drivers for Loongson security engine | expand

Commit Message

Qunqin Zhao March 27, 2025, 2:18 a.m. UTC
This driver supports Loongson Security Module, which provides the control
for it's hardware encryption acceleration child devices.

Co-developed-by: Yinggang Gu <guyinggang@loongson.cn>
Signed-off-by: Yinggang Gu <guyinggang@loongson.cn>
Signed-off-by: Qunqin Zhao <zhaoqunqin@loongson.cn>
---
v6: Replace all "ls6000se" with "loongson"
v5: Registered "ls6000se-rng" device.
v3-v4: None

v2: Removed "ls6000se-sdf" device, added "ls6000se-tpm" device.
    Passed dmamem size to SE firmware in se_init_hw() function.

 drivers/mfd/Kconfig             |  10 +
 drivers/mfd/Makefile            |   2 +
 drivers/mfd/loongson-se.c       | 374 ++++++++++++++++++++++++++++++++
 include/linux/mfd/loongson-se.h |  75 +++++++
 4 files changed, 461 insertions(+)
 create mode 100644 drivers/mfd/loongson-se.c
 create mode 100644 include/linux/mfd/loongson-se.h

Comments

Huacai Chen March 31, 2025, 10:14 a.m. UTC | #1
Hi, Qunqin,

On Thu, Mar 27, 2025 at 10:17 AM Qunqin Zhao <zhaoqunqin@loongson.cn> wrote:
>
> This driver supports Loongson Security Module, which provides the control
> for it's hardware encryption acceleration child devices.
>
> Co-developed-by: Yinggang Gu <guyinggang@loongson.cn>
> Signed-off-by: Yinggang Gu <guyinggang@loongson.cn>
> Signed-off-by: Qunqin Zhao <zhaoqunqin@loongson.cn>
> ---
> v6: Replace all "ls6000se" with "loongson"
> v5: Registered "ls6000se-rng" device.
> v3-v4: None
>
> v2: Removed "ls6000se-sdf" device, added "ls6000se-tpm" device.
>     Passed dmamem size to SE firmware in se_init_hw() function.
>
>  drivers/mfd/Kconfig             |  10 +
>  drivers/mfd/Makefile            |   2 +
>  drivers/mfd/loongson-se.c       | 374 ++++++++++++++++++++++++++++++++
>  include/linux/mfd/loongson-se.h |  75 +++++++
>  4 files changed, 461 insertions(+)
>  create mode 100644 drivers/mfd/loongson-se.c
>  create mode 100644 include/linux/mfd/loongson-se.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 22b936310..a31ccb1b0 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -2422,5 +2422,15 @@ config MFD_UPBOARD_FPGA
>           To compile this driver as a module, choose M here: the module will be
>           called upboard-fpga.
>
> +config MFD_LOONGSON_SE
> +       tristate "Loongson Security Module Interface"
> +       depends on LOONGARCH && ACPI
> +       select MFD_CORE
> +       help
> +         The Loongson security module provides the control for hardware
> +         encryption acceleration devices. Each device uses at least one
> +         channel to interact with security module, and each channel may
> +         have its own buffer provided by security module.
This file is mostly sorted by alpha-betical order, so moving this
between MFD_INTEL_M10_BMC_PMCI and MFD_QNAP_MCU is better.

> +
>  endmenu
>  endif
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 948cbdf42..fc50601ca 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -290,3 +290,5 @@ obj-$(CONFIG_MFD_RSMU_I2C)  += rsmu_i2c.o rsmu_core.o
>  obj-$(CONFIG_MFD_RSMU_SPI)     += rsmu_spi.o rsmu_core.o
>
>  obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o
> +
> +obj-$(CONFIG_MFD_LOONGSON_SE)  += loongson-se.o
> diff --git a/drivers/mfd/loongson-se.c b/drivers/mfd/loongson-se.c
> new file mode 100644
> index 000000000..bf16f7df8
> --- /dev/null
> +++ b/drivers/mfd/loongson-se.c
> @@ -0,0 +1,374 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/* Copyright (C) 2025 Loongson Technology Corporation Limited */
> +
> +#include <linux/acpi.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/errno.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/iopoll.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/loongson-se.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +/*
> + * The Loongson Security Module provides the control for hardware
> + * encryption acceleration child devices. The SE framework is
> + * shown as follows:
> + *
> + *                   +------------+
> + *                   |    CPU     |
> + *                   +------------+
> + *                     ^       ^
> + *                 DMA |       | IRQ
> + *                     v       v
> + *        +-----------------------------------+
> + *        |     Loongson Security Module      |
> + *        +-----------------------------------+
> + *             ^                ^
> + *    channel1 |       channel2 |
> + *             v                v
> + *        +-----------+    +----------+
> + *        | sub-dev1  |    | sub-dev2 |  ..... Max sub-dev31
> + *        +-----------+    +----------+
> + *
> + * The CPU cannot directly communicate with SE's sub devices,
> + * but sends commands to SE, which processes the commands and
> + * sends them to the corresponding sub devices.
> + */
> +
> +struct loongson_se {
> +       void __iomem *base;
> +       u32 version;
> +       spinlock_t dev_lock;
> +       struct completion cmd_completion;
> +
> +       /* dma memory */
> +       void *mem_base;
> +       int mem_map_pages;
> +       unsigned long *mem_map;
> +
> +       /* channel */
> +       struct mutex ch_init_lock;
> +       struct lsse_ch chs[SE_CH_MAX];
lsse_ch is confusing, rename it to se_channel is better.

> +};
> +
> +union se_request {
> +       u32 info[8];
> +       struct se_cmd {
> +               u32 cmd;
> +               u32 info[7];
> +       } req;
> +       struct se_res {
> +               u32 cmd;
> +               u32 cmd_ret;
> +               u32 info[6];
> +       } res;
> +};
> +
> +static inline u32 se_readl(struct loongson_se *se, u32 off)
> +{
> +       return readl(se->base + off);
> +}
> +
> +static inline void se_writel(struct loongson_se *se, u32 val, u32 off)
> +{
> +       writel(val, se->base + off);
> +}
> +
> +static void se_enable_int_locked(struct loongson_se *se, u32 int_bit)
> +{
> +       u32 tmp;
> +
> +       tmp = se_readl(se, SE_S2LINT_EN);
> +       tmp |= int_bit;
> +       se_writel(se, tmp, SE_S2LINT_EN);
> +}
> +
> +static void se_disable_int(struct loongson_se *se, u32 int_bit)
> +{
> +       unsigned long flag;
> +       u32 tmp;
> +
> +       spin_lock_irqsave(&se->dev_lock, flag);
> +
> +       tmp = se_readl(se, SE_S2LINT_EN);
> +       tmp &= ~(int_bit);
> +       se_writel(se, tmp, SE_S2LINT_EN);
> +
> +       spin_unlock_irqrestore(&se->dev_lock, flag);
> +}
Enable/disable interrupt is not symmetric, it is better to rename
se_enable_int_locked() to se_enable_int(), then move the lock out of
se_disable_int().

> +static int se_poll(struct loongson_se *se, u32 int_bit)
> +{
> +       u32 status;
> +       int err;
> +
> +       spin_lock_irq(&se->dev_lock);
> +
> +       se_enable_int_locked(se, int_bit);
> +       se_writel(se, int_bit, SE_L2SINT_SET);
> +       err = readl_relaxed_poll_timeout_atomic(se->base + SE_L2SINT_STAT, status,
> +                                               !(status & int_bit), 1, 10000);
> +
> +       spin_unlock_irq(&se->dev_lock);
> +
> +       return err;
> +}
> +
> +static int se_send_requeset(struct loongson_se *se, union se_request *req)
> +{
> +       int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(req->info); i++)
> +               se_writel(se, req->info[i], SE_DATA_S + i * 4);
> +
> +       return se_poll(se, SE_INT_SETUP);
> +}
> +
> +/*
> + * Called by SE's child device driver.
> + * Send a request to the corresponding device.
> + */
> +int se_send_ch_requeset(struct lsse_ch *ch)
> +{
> +       return se_poll(ch->se, ch->int_bit);
> +}
> +EXPORT_SYMBOL_GPL(se_send_ch_requeset);
> +
> +static int se_get_res(struct loongson_se *se, u32 cmd, union se_request *res)
> +{
> +       int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(res->info); i++)
> +               res->info[i] = se_readl(se, SE_DATA_L + i * 4);
> +
> +       if (res->res.cmd != cmd)
> +               return -EFAULT;
> +
> +       return 0;
> +}
> +
> +static int se_send_genl_cmd(struct loongson_se *se, union se_request *req)
What does genl stand for? Maybe you can simply rename it to se_send_cmd().


Huacai

> +{
> +       int err;
> +
> +       err = se_send_requeset(se, req);
> +       if (err)
> +               return err;
> +
> +       if (!wait_for_completion_timeout(&se->cmd_completion, HZ))
> +               return -ETIME;
> +
> +       return se_get_res(se, req->req.cmd, req);
> +}
> +
> +static int se_set_msg(struct lsse_ch *ch)
> +{
> +       struct loongson_se *se = ch->se;
> +       union se_request req;
> +
> +       req.req.cmd = SE_CMD_SETMSG;
> +       req.req.info[0] = ch->id;
> +       req.req.info[1] = ch->smsg - se->mem_base;
> +       req.req.info[2] = ch->msg_size;
> +
> +       return se_send_genl_cmd(se, &req);
> +}
> +
> +static irqreturn_t se_irq(int irq, void *dev_id)
> +{
> +       struct loongson_se *se = (struct loongson_se *)dev_id;
> +       struct lsse_ch *ch;
> +       u32 int_status;
> +       int id;
> +
> +       int_status = se_readl(se, SE_S2LINT_STAT);
> +       se_disable_int(se, int_status);
> +       if (int_status & SE_INT_SETUP) {
> +               complete(&se->cmd_completion);
> +               int_status &= ~SE_INT_SETUP;
> +               se_writel(se, SE_INT_SETUP, SE_S2LINT_CL);
> +       }
> +
> +       while (int_status) {
> +               id = __ffs(int_status);
> +
> +               ch = &se->chs[id];
> +               if (ch->complete)
> +                       ch->complete(ch);
> +               int_status &= ~BIT(id);
> +               se_writel(se, BIT(id), SE_S2LINT_CL);
> +       }
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int se_init_hw(struct loongson_se *se, dma_addr_t addr, int size)
> +{
> +       union se_request req;
> +       int err;
> +
> +       /* Start engine */
> +       req.req.cmd = SE_CMD_START;
> +       err = se_send_genl_cmd(se, &req);
> +       if (err)
> +               return err;
> +
> +       /* Get Version */
> +       req.req.cmd = SE_CMD_GETVER;
> +       err = se_send_genl_cmd(se, &req);
> +       if (err)
> +               return err;
> +       se->version = req.res.info[0];
> +
> +       /* Setup dma memory */
> +       req.req.cmd = SE_CMD_SETBUF;
> +       req.req.info[0] = addr & 0xffffffff;
> +       req.req.info[1] = addr >> 32;
> +       req.req.info[2] = size;
> +
> +       return se_send_genl_cmd(se, &req);
> +}
> +
> +/*
> + * se_init_ch() - Init the channel used by child device.
> + *
> + * Allocate dma memory as agreed upon with SE on SE probe,
> + * and register the callback function when the data processing
> + * in this channel is completed.
> + */
> +struct lsse_ch *se_init_ch(struct device *dev, int id, int data_size, int msg_size,
> +                          void *priv, void (*complete)(struct lsse_ch *se_ch))
> +{
> +       struct loongson_se *se = dev_get_drvdata(dev);
> +       struct lsse_ch *ch;
> +       int data_first, data_nr;
> +       int msg_first, msg_nr;
> +
> +       mutex_lock(&se->ch_init_lock);
> +
> +       ch = &se->chs[id];
> +       ch->se = se;
> +       ch->id = id;
> +       ch->int_bit = BIT(id);
> +
> +       data_nr = round_up(data_size, PAGE_SIZE) / PAGE_SIZE;
> +       data_first = bitmap_find_next_zero_area(se->mem_map, se->mem_map_pages,
> +                                               0, data_nr, 0);
> +       if (data_first >= se->mem_map_pages) {
> +               ch = NULL;
> +               goto out_unlock;
> +       }
> +
> +       bitmap_set(se->mem_map, data_first, data_nr);
> +       ch->off = data_first * PAGE_SIZE;
> +       ch->data_buffer = se->mem_base + ch->off;
> +       ch->data_size = data_size;
> +
> +       msg_nr = round_up(msg_size, PAGE_SIZE) / PAGE_SIZE;
> +       msg_first = bitmap_find_next_zero_area(se->mem_map, se->mem_map_pages,
> +                                              0, msg_nr, 0);
> +       if (msg_first >= se->mem_map_pages) {
> +               ch = NULL;
> +               goto out_unlock;
> +       }
> +
> +       bitmap_set(se->mem_map, msg_first, msg_nr);
> +       ch->smsg = se->mem_base + msg_first * PAGE_SIZE;
> +       ch->rmsg = ch->smsg + msg_size / 2;
> +       ch->msg_size = msg_size;
> +       ch->complete = complete;
> +       ch->priv = priv;
> +       ch->version = se->version;
> +
> +       if (se_set_msg(ch))
> +               ch = NULL;
> +
> +out_unlock:
> +       mutex_unlock(&se->ch_init_lock);
> +
> +       return ch;
> +}
> +EXPORT_SYMBOL_GPL(se_init_ch);
> +
> +static const struct mfd_cell se_devs[] = {
> +       { .name = "loongson-rng" },
> +       { .name = "loongson-tpm" },
> +};
> +
> +static int loongson_se_probe(struct platform_device *pdev)
> +{
> +       struct loongson_se *se;
> +       struct device *dev = &pdev->dev;
> +       int nr_irq, irq, err, size;
> +       dma_addr_t paddr;
> +
> +       se = devm_kmalloc(dev, sizeof(*se), GFP_KERNEL);
> +       if (!se)
> +               return -ENOMEM;
> +       dev_set_drvdata(dev, se);
> +       init_completion(&se->cmd_completion);
> +       spin_lock_init(&se->dev_lock);
> +       mutex_init(&se->ch_init_lock);
> +       dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
> +       if (device_property_read_u32(dev, "dmam_size", &size))
> +               return -ENODEV;
> +       size = roundup_pow_of_two(size);
> +       se->mem_base = dmam_alloc_coherent(dev, size, &paddr, GFP_KERNEL);
> +       if (!se->mem_base)
> +               return -ENOMEM;
> +       se->mem_map_pages = size / PAGE_SIZE;
> +       se->mem_map = devm_bitmap_zalloc(dev, se->mem_map_pages, GFP_KERNEL);
> +       if (!se->mem_map)
> +               return -ENOMEM;
> +
> +       se->base = devm_platform_ioremap_resource(pdev, 0);
> +       if (IS_ERR(se->base))
> +               return PTR_ERR(se->base);
> +
> +       nr_irq = platform_irq_count(pdev);
> +       if (nr_irq <= 0)
> +               return -ENODEV;
> +       while (nr_irq) {
> +               irq = platform_get_irq(pdev, --nr_irq);
> +               /* Use the same interrupt handler address.
> +                * Determine which irq it is accroding
> +                * SE_S2LINT_STAT register.
> +                */
> +               err = devm_request_irq(dev, irq, se_irq, 0, "loongson-se", se);
> +               if (err)
> +                       dev_err(dev, "failed to request irq: %d\n", irq);
> +       }
> +
> +       err = se_init_hw(se, paddr, size);
> +       if (err)
> +               return err;
> +
> +       return devm_mfd_add_devices(dev, 0, se_devs, ARRAY_SIZE(se_devs),
> +                                   NULL, 0, NULL);
> +}
> +
> +static const struct acpi_device_id loongson_se_acpi_match[] = {
> +       {"LOON0011", 0},
> +       {}
> +};
> +MODULE_DEVICE_TABLE(acpi, loongson_se_acpi_match);
> +
> +static struct platform_driver loongson_se_driver = {
> +       .probe   = loongson_se_probe,
> +       .driver  = {
> +               .name  = "loongson-se",
> +               .acpi_match_table = loongson_se_acpi_match,
> +       },
> +};
> +module_platform_driver(loongson_se_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Yinggang Gu <guyinggang@loongson.cn>");
> +MODULE_AUTHOR("Qunqin Zhao <zhaoqunqin@loongson.cn>");
> +MODULE_DESCRIPTION("Loongson Security Module driver");
> diff --git a/include/linux/mfd/loongson-se.h b/include/linux/mfd/loongson-se.h
> new file mode 100644
> index 000000000..f70e9f196
> --- /dev/null
> +++ b/include/linux/mfd/loongson-se.h
> @@ -0,0 +1,75 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/* Copyright (C) 2025 Loongson Technology Corporation Limited */
> +
> +#ifndef __LOONGSON_SE_H__
> +#define __LOONGSON_SE_H__
> +
> +#define SE_DATA_S                      0x0
> +#define SE_DATA_L                      0x20
> +#define SE_S2LINT_STAT                 0x88
> +#define SE_S2LINT_EN                   0x8c
> +#define SE_S2LINT_SET                  0x90
> +#define SE_S2LINT_CL                   0x94
> +#define SE_L2SINT_STAT                 0x98
> +#define SE_L2SINT_EN                   0x9c
> +#define SE_L2SINT_SET                  0xa0
> +#define SE_L2SINT_CL                   0xa4
> +
> +/* INT bit definition */
> +#define SE_INT_SETUP                   BIT(0)
> +#define SE_INT_TPM                     BIT(5)
> +
> +#define SE_CMD_START                   0x0
> +#define SE_CMD_STOP                    0x1
> +#define SE_CMD_GETVER                  0x2
> +#define SE_CMD_SETBUF                  0x3
> +#define SE_CMD_SETMSG                  0x4
> +
> +#define SE_CMD_RNG                     0x100
> +#define SE_CMD_SM2_SIGN                        0x200
> +#define SE_CMD_SM2_VSIGN               0x201
> +#define SE_CMD_SM3_DIGEST              0x300
> +#define SE_CMD_SM3_UPDATE              0x301
> +#define SE_CMD_SM3_FINISH              0x302
> +#define SE_CMD_SM4_ECB_ENCRY           0x400
> +#define SE_CMD_SM4_ECB_DECRY           0x401
> +#define SE_CMD_SM4_CBC_ENCRY           0x402
> +#define SE_CMD_SM4_CBC_DECRY           0x403
> +#define SE_CMD_SM4_CTR                 0x404
> +#define SE_CMD_TPM                     0x500
> +#define SE_CMD_ZUC_INIT_READ           0x600
> +#define SE_CMD_ZUC_READ                        0x601
> +#define SE_CMD_SDF                     0x700
> +
> +#define SE_CH_MAX                      32
> +#define SE_CH_RNG                      1
> +#define SE_CH_SM2                      2
> +#define SE_CH_SM3                      3
> +#define SE_CH_SM4                      4
> +#define SE_CH_TPM                      5
> +#define SE_CH_ZUC                      6
> +#define SE_CH_SDF                      7
> +
> +struct lsse_ch {
> +       struct loongson_se *se;
> +       void *priv;
> +       u32 version;
> +       u32 id;
> +       u32 int_bit;
> +
> +       void *smsg;
> +       void *rmsg;
> +       int msg_size;
> +
> +       void *data_buffer;
> +       int data_size;
> +       u32 off;
> +
> +       void (*complete)(struct lsse_ch *se_ch);
> +};
> +
> +struct lsse_ch *se_init_ch(struct device *dev, int id, int data_size, int msg_size,
> +                          void *priv, void (*complete)(struct lsse_ch *se_ch));
> +int se_send_ch_requeset(struct lsse_ch *ch);
> +
> +#endif
> --
> 2.45.2
>
>
diff mbox series

Patch

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 22b936310..a31ccb1b0 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2422,5 +2422,15 @@  config MFD_UPBOARD_FPGA
 	  To compile this driver as a module, choose M here: the module will be
 	  called upboard-fpga.
 
+config MFD_LOONGSON_SE
+	tristate "Loongson Security Module Interface"
+	depends on LOONGARCH && ACPI
+	select MFD_CORE
+	help
+	  The Loongson security module provides the control for hardware
+	  encryption acceleration devices. Each device uses at least one
+	  channel to interact with security module, and each channel may
+	  have its own buffer provided by security module.
+
 endmenu
 endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 948cbdf42..fc50601ca 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -290,3 +290,5 @@  obj-$(CONFIG_MFD_RSMU_I2C)	+= rsmu_i2c.o rsmu_core.o
 obj-$(CONFIG_MFD_RSMU_SPI)	+= rsmu_spi.o rsmu_core.o
 
 obj-$(CONFIG_MFD_UPBOARD_FPGA)	+= upboard-fpga.o
+
+obj-$(CONFIG_MFD_LOONGSON_SE)	+= loongson-se.o
diff --git a/drivers/mfd/loongson-se.c b/drivers/mfd/loongson-se.c
new file mode 100644
index 000000000..bf16f7df8
--- /dev/null
+++ b/drivers/mfd/loongson-se.c
@@ -0,0 +1,374 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2025 Loongson Technology Corporation Limited */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/loongson-se.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/*
+ * The Loongson Security Module provides the control for hardware
+ * encryption acceleration child devices. The SE framework is
+ * shown as follows:
+ *
+ *                   +------------+
+ *                   |    CPU     |
+ *                   +------------+
+ *			^	^
+ *	            DMA |	| IRQ
+ *			v	v
+ *        +-----------------------------------+
+ *        |     Loongson Security Module      |
+ *        +-----------------------------------+
+ *             ^                ^
+ *    channel1 |       channel2 |
+ *             v                v
+ *        +-----------+    +----------+
+ *        | sub-dev1  |    | sub-dev2 |  ..... Max sub-dev31
+ *        +-----------+    +----------+
+ *
+ * The CPU cannot directly communicate with SE's sub devices,
+ * but sends commands to SE, which processes the commands and
+ * sends them to the corresponding sub devices.
+ */
+
+struct loongson_se {
+	void __iomem *base;
+	u32 version;
+	spinlock_t dev_lock;
+	struct completion cmd_completion;
+
+	/* dma memory */
+	void *mem_base;
+	int mem_map_pages;
+	unsigned long *mem_map;
+
+	/* channel */
+	struct mutex ch_init_lock;
+	struct lsse_ch chs[SE_CH_MAX];
+};
+
+union se_request {
+	u32 info[8];
+	struct se_cmd {
+		u32 cmd;
+		u32 info[7];
+	} req;
+	struct se_res {
+		u32 cmd;
+		u32 cmd_ret;
+		u32 info[6];
+	} res;
+};
+
+static inline u32 se_readl(struct loongson_se *se, u32 off)
+{
+	return readl(se->base + off);
+}
+
+static inline void se_writel(struct loongson_se *se, u32 val, u32 off)
+{
+	writel(val, se->base + off);
+}
+
+static void se_enable_int_locked(struct loongson_se *se, u32 int_bit)
+{
+	u32 tmp;
+
+	tmp = se_readl(se, SE_S2LINT_EN);
+	tmp |= int_bit;
+	se_writel(se, tmp, SE_S2LINT_EN);
+}
+
+static void se_disable_int(struct loongson_se *se, u32 int_bit)
+{
+	unsigned long flag;
+	u32 tmp;
+
+	spin_lock_irqsave(&se->dev_lock, flag);
+
+	tmp = se_readl(se, SE_S2LINT_EN);
+	tmp &= ~(int_bit);
+	se_writel(se, tmp, SE_S2LINT_EN);
+
+	spin_unlock_irqrestore(&se->dev_lock, flag);
+}
+
+static int se_poll(struct loongson_se *se, u32 int_bit)
+{
+	u32 status;
+	int err;
+
+	spin_lock_irq(&se->dev_lock);
+
+	se_enable_int_locked(se, int_bit);
+	se_writel(se, int_bit, SE_L2SINT_SET);
+	err = readl_relaxed_poll_timeout_atomic(se->base + SE_L2SINT_STAT, status,
+						!(status & int_bit), 1, 10000);
+
+	spin_unlock_irq(&se->dev_lock);
+
+	return err;
+}
+
+static int se_send_requeset(struct loongson_se *se, union se_request *req)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(req->info); i++)
+		se_writel(se, req->info[i], SE_DATA_S + i * 4);
+
+	return se_poll(se, SE_INT_SETUP);
+}
+
+/*
+ * Called by SE's child device driver.
+ * Send a request to the corresponding device.
+ */
+int se_send_ch_requeset(struct lsse_ch *ch)
+{
+	return se_poll(ch->se, ch->int_bit);
+}
+EXPORT_SYMBOL_GPL(se_send_ch_requeset);
+
+static int se_get_res(struct loongson_se *se, u32 cmd, union se_request *res)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(res->info); i++)
+		res->info[i] = se_readl(se, SE_DATA_L + i * 4);
+
+	if (res->res.cmd != cmd)
+		return -EFAULT;
+
+	return 0;
+}
+
+static int se_send_genl_cmd(struct loongson_se *se, union se_request *req)
+{
+	int err;
+
+	err = se_send_requeset(se, req);
+	if (err)
+		return err;
+
+	if (!wait_for_completion_timeout(&se->cmd_completion, HZ))
+		return -ETIME;
+
+	return se_get_res(se, req->req.cmd, req);
+}
+
+static int se_set_msg(struct lsse_ch *ch)
+{
+	struct loongson_se *se = ch->se;
+	union se_request req;
+
+	req.req.cmd = SE_CMD_SETMSG;
+	req.req.info[0] = ch->id;
+	req.req.info[1] = ch->smsg - se->mem_base;
+	req.req.info[2] = ch->msg_size;
+
+	return se_send_genl_cmd(se, &req);
+}
+
+static irqreturn_t se_irq(int irq, void *dev_id)
+{
+	struct loongson_se *se = (struct loongson_se *)dev_id;
+	struct lsse_ch *ch;
+	u32 int_status;
+	int id;
+
+	int_status = se_readl(se, SE_S2LINT_STAT);
+	se_disable_int(se, int_status);
+	if (int_status & SE_INT_SETUP) {
+		complete(&se->cmd_completion);
+		int_status &= ~SE_INT_SETUP;
+		se_writel(se, SE_INT_SETUP, SE_S2LINT_CL);
+	}
+
+	while (int_status) {
+		id = __ffs(int_status);
+
+		ch = &se->chs[id];
+		if (ch->complete)
+			ch->complete(ch);
+		int_status &= ~BIT(id);
+		se_writel(se, BIT(id), SE_S2LINT_CL);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int se_init_hw(struct loongson_se *se, dma_addr_t addr, int size)
+{
+	union se_request req;
+	int err;
+
+	/* Start engine */
+	req.req.cmd = SE_CMD_START;
+	err = se_send_genl_cmd(se, &req);
+	if (err)
+		return err;
+
+	/* Get Version */
+	req.req.cmd = SE_CMD_GETVER;
+	err = se_send_genl_cmd(se, &req);
+	if (err)
+		return err;
+	se->version = req.res.info[0];
+
+	/* Setup dma memory */
+	req.req.cmd = SE_CMD_SETBUF;
+	req.req.info[0] = addr & 0xffffffff;
+	req.req.info[1] = addr >> 32;
+	req.req.info[2] = size;
+
+	return se_send_genl_cmd(se, &req);
+}
+
+/*
+ * se_init_ch() - Init the channel used by child device.
+ *
+ * Allocate dma memory as agreed upon with SE on SE probe,
+ * and register the callback function when the data processing
+ * in this channel is completed.
+ */
+struct lsse_ch *se_init_ch(struct device *dev, int id, int data_size, int msg_size,
+			   void *priv, void (*complete)(struct lsse_ch *se_ch))
+{
+	struct loongson_se *se = dev_get_drvdata(dev);
+	struct lsse_ch *ch;
+	int data_first, data_nr;
+	int msg_first, msg_nr;
+
+	mutex_lock(&se->ch_init_lock);
+
+	ch = &se->chs[id];
+	ch->se = se;
+	ch->id = id;
+	ch->int_bit = BIT(id);
+
+	data_nr = round_up(data_size, PAGE_SIZE) / PAGE_SIZE;
+	data_first = bitmap_find_next_zero_area(se->mem_map, se->mem_map_pages,
+						0, data_nr, 0);
+	if (data_first >= se->mem_map_pages) {
+		ch = NULL;
+		goto out_unlock;
+	}
+
+	bitmap_set(se->mem_map, data_first, data_nr);
+	ch->off = data_first * PAGE_SIZE;
+	ch->data_buffer = se->mem_base + ch->off;
+	ch->data_size = data_size;
+
+	msg_nr = round_up(msg_size, PAGE_SIZE) / PAGE_SIZE;
+	msg_first = bitmap_find_next_zero_area(se->mem_map, se->mem_map_pages,
+					       0, msg_nr, 0);
+	if (msg_first >= se->mem_map_pages) {
+		ch = NULL;
+		goto out_unlock;
+	}
+
+	bitmap_set(se->mem_map, msg_first, msg_nr);
+	ch->smsg = se->mem_base + msg_first * PAGE_SIZE;
+	ch->rmsg = ch->smsg + msg_size / 2;
+	ch->msg_size = msg_size;
+	ch->complete = complete;
+	ch->priv = priv;
+	ch->version = se->version;
+
+	if (se_set_msg(ch))
+		ch = NULL;
+
+out_unlock:
+	mutex_unlock(&se->ch_init_lock);
+
+	return ch;
+}
+EXPORT_SYMBOL_GPL(se_init_ch);
+
+static const struct mfd_cell se_devs[] = {
+	{ .name = "loongson-rng" },
+	{ .name = "loongson-tpm" },
+};
+
+static int loongson_se_probe(struct platform_device *pdev)
+{
+	struct loongson_se *se;
+	struct device *dev = &pdev->dev;
+	int nr_irq, irq, err, size;
+	dma_addr_t paddr;
+
+	se = devm_kmalloc(dev, sizeof(*se), GFP_KERNEL);
+	if (!se)
+		return -ENOMEM;
+	dev_set_drvdata(dev, se);
+	init_completion(&se->cmd_completion);
+	spin_lock_init(&se->dev_lock);
+	mutex_init(&se->ch_init_lock);
+	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	if (device_property_read_u32(dev, "dmam_size", &size))
+		return -ENODEV;
+	size = roundup_pow_of_two(size);
+	se->mem_base = dmam_alloc_coherent(dev, size, &paddr, GFP_KERNEL);
+	if (!se->mem_base)
+		return -ENOMEM;
+	se->mem_map_pages = size / PAGE_SIZE;
+	se->mem_map = devm_bitmap_zalloc(dev, se->mem_map_pages, GFP_KERNEL);
+	if (!se->mem_map)
+		return -ENOMEM;
+
+	se->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(se->base))
+		return PTR_ERR(se->base);
+
+	nr_irq = platform_irq_count(pdev);
+	if (nr_irq <= 0)
+		return -ENODEV;
+	while (nr_irq) {
+		irq = platform_get_irq(pdev, --nr_irq);
+		/* Use the same interrupt handler address.
+		 * Determine which irq it is accroding
+		 * SE_S2LINT_STAT register.
+		 */
+		err = devm_request_irq(dev, irq, se_irq, 0, "loongson-se", se);
+		if (err)
+			dev_err(dev, "failed to request irq: %d\n", irq);
+	}
+
+	err = se_init_hw(se, paddr, size);
+	if (err)
+		return err;
+
+	return devm_mfd_add_devices(dev, 0, se_devs, ARRAY_SIZE(se_devs),
+				    NULL, 0, NULL);
+}
+
+static const struct acpi_device_id loongson_se_acpi_match[] = {
+	{"LOON0011", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(acpi, loongson_se_acpi_match);
+
+static struct platform_driver loongson_se_driver = {
+	.probe   = loongson_se_probe,
+	.driver  = {
+		.name  = "loongson-se",
+		.acpi_match_table = loongson_se_acpi_match,
+	},
+};
+module_platform_driver(loongson_se_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yinggang Gu <guyinggang@loongson.cn>");
+MODULE_AUTHOR("Qunqin Zhao <zhaoqunqin@loongson.cn>");
+MODULE_DESCRIPTION("Loongson Security Module driver");
diff --git a/include/linux/mfd/loongson-se.h b/include/linux/mfd/loongson-se.h
new file mode 100644
index 000000000..f70e9f196
--- /dev/null
+++ b/include/linux/mfd/loongson-se.h
@@ -0,0 +1,75 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2025 Loongson Technology Corporation Limited */
+
+#ifndef __LOONGSON_SE_H__
+#define __LOONGSON_SE_H__
+
+#define SE_DATA_S			0x0
+#define SE_DATA_L			0x20
+#define SE_S2LINT_STAT			0x88
+#define SE_S2LINT_EN			0x8c
+#define SE_S2LINT_SET			0x90
+#define SE_S2LINT_CL			0x94
+#define SE_L2SINT_STAT			0x98
+#define SE_L2SINT_EN			0x9c
+#define SE_L2SINT_SET			0xa0
+#define SE_L2SINT_CL			0xa4
+
+/* INT bit definition */
+#define SE_INT_SETUP			BIT(0)
+#define SE_INT_TPM			BIT(5)
+
+#define SE_CMD_START			0x0
+#define SE_CMD_STOP			0x1
+#define SE_CMD_GETVER			0x2
+#define SE_CMD_SETBUF			0x3
+#define SE_CMD_SETMSG			0x4
+
+#define SE_CMD_RNG			0x100
+#define SE_CMD_SM2_SIGN			0x200
+#define SE_CMD_SM2_VSIGN		0x201
+#define SE_CMD_SM3_DIGEST		0x300
+#define SE_CMD_SM3_UPDATE		0x301
+#define SE_CMD_SM3_FINISH		0x302
+#define SE_CMD_SM4_ECB_ENCRY		0x400
+#define SE_CMD_SM4_ECB_DECRY		0x401
+#define SE_CMD_SM4_CBC_ENCRY		0x402
+#define SE_CMD_SM4_CBC_DECRY		0x403
+#define SE_CMD_SM4_CTR			0x404
+#define SE_CMD_TPM			0x500
+#define SE_CMD_ZUC_INIT_READ		0x600
+#define SE_CMD_ZUC_READ			0x601
+#define SE_CMD_SDF			0x700
+
+#define SE_CH_MAX			32
+#define SE_CH_RNG			1
+#define SE_CH_SM2			2
+#define SE_CH_SM3			3
+#define SE_CH_SM4			4
+#define SE_CH_TPM			5
+#define SE_CH_ZUC			6
+#define SE_CH_SDF			7
+
+struct lsse_ch {
+	struct loongson_se *se;
+	void *priv;
+	u32 version;
+	u32 id;
+	u32 int_bit;
+
+	void *smsg;
+	void *rmsg;
+	int msg_size;
+
+	void *data_buffer;
+	int data_size;
+	u32 off;
+
+	void (*complete)(struct lsse_ch *se_ch);
+};
+
+struct lsse_ch *se_init_ch(struct device *dev, int id, int data_size, int msg_size,
+			   void *priv, void (*complete)(struct lsse_ch *se_ch));
+int se_send_ch_requeset(struct lsse_ch *ch);
+
+#endif