xref: /linux/drivers/mfd/loongson-se.c (revision 68a052239fc4b351e961f698b824f7654a346091)
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