1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2025 Loongson Technology Corporation Limited 4 * 5 * Author: Yinggang Gu <guyinggang@loongson.cn> 6 * Author: Qunqin Zhao <zhaoqunqin@loongson.cn> 7 */ 8 9 #include <linux/acpi.h> 10 #include <linux/delay.h> 11 #include <linux/device.h> 12 #include <linux/dma-mapping.h> 13 #include <linux/errno.h> 14 #include <linux/init.h> 15 #include <linux/interrupt.h> 16 #include <linux/iopoll.h> 17 #include <linux/kernel.h> 18 #include <linux/mfd/core.h> 19 #include <linux/mfd/loongson-se.h> 20 #include <linux/module.h> 21 #include <linux/platform_device.h> 22 23 struct loongson_se { 24 void __iomem *base; 25 spinlock_t dev_lock; 26 struct completion cmd_completion; 27 28 void *dmam_base; 29 int dmam_size; 30 31 struct mutex engine_init_lock; 32 struct loongson_se_engine engines[SE_ENGINE_MAX]; 33 }; 34 35 struct loongson_se_controller_cmd { 36 u32 command_id; 37 u32 info[7]; 38 }; 39 40 static int loongson_se_poll(struct loongson_se *se, u32 int_bit) 41 { 42 u32 status; 43 int err; 44 45 spin_lock_irq(&se->dev_lock); 46 47 /* Notify the controller that the engine needs to be started */ 48 writel(int_bit, se->base + SE_L2SINT_SET); 49 50 /* Polling until the controller has forwarded the engine command */ 51 err = readl_relaxed_poll_timeout_atomic(se->base + SE_L2SINT_STAT, status, 52 !(status & int_bit), 53 1, LOONGSON_ENGINE_CMD_TIMEOUT_US); 54 55 spin_unlock_irq(&se->dev_lock); 56 57 return err; 58 } 59 60 static int loongson_se_send_controller_cmd(struct loongson_se *se, 61 struct loongson_se_controller_cmd *cmd) 62 { 63 u32 *send_cmd = (u32 *)cmd; 64 int err, i; 65 66 for (i = 0; i < SE_SEND_CMD_REG_LEN; i++) 67 writel(send_cmd[i], se->base + SE_SEND_CMD_REG + i * 4); 68 69 err = loongson_se_poll(se, SE_INT_CONTROLLER); 70 if (err) 71 return err; 72 73 return wait_for_completion_interruptible(&se->cmd_completion); 74 } 75 76 int loongson_se_send_engine_cmd(struct loongson_se_engine *engine) 77 { 78 /* 79 * After engine initialization, the controller already knows 80 * where to obtain engine commands from. Now all we need to 81 * do is notify the controller that the engine needs to be started. 82 */ 83 int err = loongson_se_poll(engine->se, BIT(engine->id)); 84 85 if (err) 86 return err; 87 88 return wait_for_completion_interruptible(&engine->completion); 89 } 90 EXPORT_SYMBOL_GPL(loongson_se_send_engine_cmd); 91 92 struct loongson_se_engine *loongson_se_init_engine(struct device *dev, int id) 93 { 94 struct loongson_se *se = dev_get_drvdata(dev); 95 struct loongson_se_engine *engine = &se->engines[id]; 96 struct loongson_se_controller_cmd cmd; 97 98 engine->se = se; 99 engine->id = id; 100 init_completion(&engine->completion); 101 102 /* Divide DMA memory equally among all engines */ 103 engine->buffer_size = se->dmam_size / SE_ENGINE_MAX; 104 engine->buffer_off = (se->dmam_size / SE_ENGINE_MAX) * id; 105 engine->data_buffer = se->dmam_base + engine->buffer_off; 106 107 /* 108 * There has no engine0, use its data buffer as command buffer for other 109 * engines. The DMA memory size is obtained from the ACPI table, which 110 * ensures that the data buffer size of engine0 is larger than the 111 * command buffer size of all engines. 112 */ 113 engine->command = se->dmam_base + id * (2 * SE_ENGINE_CMD_SIZE); 114 engine->command_ret = engine->command + SE_ENGINE_CMD_SIZE; 115 116 mutex_lock(&se->engine_init_lock); 117 118 /* Tell the controller where to find engine command */ 119 cmd.command_id = SE_CMD_SET_ENGINE_CMDBUF; 120 cmd.info[0] = id; 121 cmd.info[1] = engine->command - se->dmam_base; 122 cmd.info[2] = 2 * SE_ENGINE_CMD_SIZE; 123 124 if (loongson_se_send_controller_cmd(se, &cmd)) 125 engine = NULL; 126 127 mutex_unlock(&se->engine_init_lock); 128 129 return engine; 130 } 131 EXPORT_SYMBOL_GPL(loongson_se_init_engine); 132 133 static irqreturn_t se_irq_handler(int irq, void *dev_id) 134 { 135 struct loongson_se *se = dev_id; 136 u32 int_status; 137 int id; 138 139 spin_lock(&se->dev_lock); 140 141 int_status = readl(se->base + SE_S2LINT_STAT); 142 143 /* For controller */ 144 if (int_status & SE_INT_CONTROLLER) { 145 complete(&se->cmd_completion); 146 int_status &= ~SE_INT_CONTROLLER; 147 writel(SE_INT_CONTROLLER, se->base + SE_S2LINT_CL); 148 } 149 150 /* For engines */ 151 while (int_status) { 152 id = __ffs(int_status); 153 complete(&se->engines[id].completion); 154 int_status &= ~BIT(id); 155 writel(BIT(id), se->base + SE_S2LINT_CL); 156 } 157 158 spin_unlock(&se->dev_lock); 159 160 return IRQ_HANDLED; 161 } 162 163 static int loongson_se_init(struct loongson_se *se, dma_addr_t addr, int size) 164 { 165 struct loongson_se_controller_cmd cmd; 166 int err; 167 168 cmd.command_id = SE_CMD_START; 169 err = loongson_se_send_controller_cmd(se, &cmd); 170 if (err) 171 return err; 172 173 cmd.command_id = SE_CMD_SET_DMA; 174 cmd.info[0] = lower_32_bits(addr); 175 cmd.info[1] = upper_32_bits(addr); 176 cmd.info[2] = size; 177 178 return loongson_se_send_controller_cmd(se, &cmd); 179 } 180 181 static const struct mfd_cell engines[] = { 182 { .name = "loongson-rng" }, 183 { .name = "tpm_loongson" }, 184 }; 185 186 static int loongson_se_probe(struct platform_device *pdev) 187 { 188 struct device *dev = &pdev->dev; 189 struct loongson_se *se; 190 int nr_irq, irq, err, i; 191 dma_addr_t paddr; 192 193 se = devm_kmalloc(dev, sizeof(*se), GFP_KERNEL); 194 if (!se) 195 return -ENOMEM; 196 197 dev_set_drvdata(dev, se); 198 init_completion(&se->cmd_completion); 199 spin_lock_init(&se->dev_lock); 200 mutex_init(&se->engine_init_lock); 201 202 dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); 203 if (device_property_read_u32(dev, "dmam_size", &se->dmam_size)) 204 return -ENODEV; 205 206 se->dmam_base = dmam_alloc_coherent(dev, se->dmam_size, &paddr, GFP_KERNEL); 207 if (!se->dmam_base) 208 return -ENOMEM; 209 210 se->base = devm_platform_ioremap_resource(pdev, 0); 211 if (IS_ERR(se->base)) 212 return PTR_ERR(se->base); 213 214 writel(SE_INT_ALL, se->base + SE_S2LINT_EN); 215 216 nr_irq = platform_irq_count(pdev); 217 if (nr_irq <= 0) 218 return -ENODEV; 219 220 for (i = 0; i < nr_irq; i++) { 221 irq = platform_get_irq(pdev, i); 222 err = devm_request_irq(dev, irq, se_irq_handler, 0, "loongson-se", se); 223 if (err) 224 dev_err(dev, "failed to request IRQ: %d\n", irq); 225 } 226 227 err = loongson_se_init(se, paddr, se->dmam_size); 228 if (err) 229 return err; 230 231 return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, engines, 232 ARRAY_SIZE(engines), NULL, 0, NULL); 233 } 234 235 static const struct acpi_device_id loongson_se_acpi_match[] = { 236 { "LOON0011", 0 }, 237 { } 238 }; 239 MODULE_DEVICE_TABLE(acpi, loongson_se_acpi_match); 240 241 static struct platform_driver loongson_se_driver = { 242 .probe = loongson_se_probe, 243 .driver = { 244 .name = "loongson-se", 245 .acpi_match_table = loongson_se_acpi_match, 246 }, 247 }; 248 module_platform_driver(loongson_se_driver); 249 250 MODULE_LICENSE("GPL"); 251 MODULE_AUTHOR("Yinggang Gu <guyinggang@loongson.cn>"); 252 MODULE_AUTHOR("Qunqin Zhao <zhaoqunqin@loongson.cn>"); 253 MODULE_DESCRIPTION("Loongson Security Engine chip controller driver"); 254