File Index
Files and directories
Sign in
FILE

loongson-se.c

File preview with syntax highlighting.

linux-6.19.14/drivers/mfd/loongson-se.c C
  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
 23struct 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
 35struct loongson_se_controller_cmd {
 36	u32 command_id;
 37	u32 info[7];
 38};
 39
 40static 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
 60static 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
 76int 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}
 90EXPORT_SYMBOL_GPL(loongson_se_send_engine_cmd);
 91
 92struct 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}
131EXPORT_SYMBOL_GPL(loongson_se_init_engine);
132
133static 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
163static 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
181static const struct mfd_cell engines[] = {
182	{ .name = "loongson-rng" },
183	{ .name = "tpm_loongson" },
184};
185
186static 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
235static const struct acpi_device_id loongson_se_acpi_match[] = {
236	{ "LOON0011", 0 },
237	{ }
238};
239MODULE_DEVICE_TABLE(acpi, loongson_se_acpi_match);
240
241static 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};
248module_platform_driver(loongson_se_driver);
249
250MODULE_LICENSE("GPL");
251MODULE_AUTHOR("Yinggang Gu <guyinggang@loongson.cn>");
252MODULE_AUTHOR("Qunqin Zhao <zhaoqunqin@loongson.cn>");
253MODULE_DESCRIPTION("Loongson Security Engine chip controller driver");
~/ Read-only file preview.