xref: /linux/drivers/media/pci/intel/ipu6/ipu6-fw-com.c (revision fcb3ad4366b9c810cbb9da34c076a9a52d8aa1e0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2013--2024 Intel Corporation
4  */
5 
6 #include <linux/device.h>
7 #include <linux/dma-mapping.h>
8 #include <linux/io.h>
9 #include <linux/math.h>
10 #include <linux/overflow.h>
11 #include <linux/slab.h>
12 #include <linux/types.h>
13 
14 #include "ipu6-bus.h"
15 #include "ipu6-dma.h"
16 #include "ipu6-fw-com.h"
17 
18 /*
19  * FWCOM layer is a shared resource between FW and driver. It consist
20  * of token queues to both send and receive directions. Queue is simply
21  * an array of structures with read and write indexes to the queue.
22  * There are 1...n queues to both directions. Queues locates in
23  * system RAM and are mapped to ISP MMU so that both CPU and ISP can
24  * see the same buffer. Indexes are located in ISP DMEM so that FW code
25  * can poll those with very low latency and cost. CPU access to indexes is
26  * more costly but that happens only at message sending time and
27  * interrupt triggered message handling. CPU doesn't need to poll indexes.
28  * wr_reg / rd_reg are offsets to those dmem location. They are not
29  * the indexes itself.
30  */
31 
32 /* Shared structure between driver and FW - do not modify */
33 struct ipu6_fw_sys_queue {
34 	u64 host_address;
35 	u32 vied_address;
36 	u32 size;
37 	u32 token_size;
38 	u32 wr_reg;	/* reg number in subsystem's regmem */
39 	u32 rd_reg;
40 	u32 _align;
41 } __packed;
42 
43 struct ipu6_fw_sys_queue_res {
44 	u64 host_address;
45 	u32 vied_address;
46 	u32 reg;
47 } __packed;
48 
49 enum syscom_state {
50 	/* Program load or explicit host setting should init to this */
51 	SYSCOM_STATE_UNINIT = 0x57a7e000,
52 	/* SP Syscom sets this when it is ready for use */
53 	SYSCOM_STATE_READY = 0x57a7e001,
54 	/* SP Syscom sets this when no more syscom accesses will happen */
55 	SYSCOM_STATE_INACTIVE = 0x57a7e002,
56 };
57 
58 enum syscom_cmd {
59 	/* Program load or explicit host setting should init to this */
60 	SYSCOM_COMMAND_UNINIT = 0x57a7f000,
61 	/* Host Syscom requests syscom to become inactive */
62 	SYSCOM_COMMAND_INACTIVE = 0x57a7f001,
63 };
64 
65 /* firmware config: data that sent from the host to SP via DDR */
66 /* Cell copies data into a context */
67 
68 struct ipu6_fw_syscom_config {
69 	u32 firmware_address;
70 
71 	u32 num_input_queues;
72 	u32 num_output_queues;
73 
74 	/* ISP pointers to an array of ipu6_fw_sys_queue structures */
75 	u32 input_queue;
76 	u32 output_queue;
77 
78 	/* ISYS / PSYS private data */
79 	u32 specific_addr;
80 	u32 specific_size;
81 };
82 
83 struct ipu6_fw_com_context {
84 	struct ipu6_bus_device *adev;
85 	void __iomem *dmem_addr;
86 	int (*cell_ready)(struct ipu6_bus_device *adev);
87 	void (*cell_start)(struct ipu6_bus_device *adev);
88 
89 	void *dma_buffer;
90 	dma_addr_t dma_addr;
91 	unsigned int dma_size;
92 
93 	struct ipu6_fw_sys_queue *input_queue;	/* array of host to SP queues */
94 	struct ipu6_fw_sys_queue *output_queue;	/* array of SP to host */
95 
96 	u32 config_vied_addr;
97 
98 	unsigned int buttress_boot_offset;
99 	void __iomem *base_addr;
100 };
101 
102 #define FW_COM_WR_REG 0
103 #define FW_COM_RD_REG 4
104 
105 #define REGMEM_OFFSET 0
106 #define TUNIT_MAGIC_PATTERN 0x5a5a5a5a
107 
108 enum regmem_id {
109 	/* pass pkg_dir address to SPC in non-secure mode */
110 	PKG_DIR_ADDR_REG = 0,
111 	/* Tunit CFG blob for secure - provided by host.*/
112 	TUNIT_CFG_DWR_REG = 1,
113 	/* syscom commands - modified by the host */
114 	SYSCOM_COMMAND_REG = 2,
115 	/* Store interrupt status - updated by SP */
116 	SYSCOM_IRQ_REG = 3,
117 	/* first syscom queue pointer register */
118 	SYSCOM_QPR_BASE_REG = 4
119 };
120 
121 #define BUTTRESS_FW_BOOT_PARAMS_0 0x4000
122 #define BUTTRESS_FW_BOOT_PARAM_REG(base, offset, id)			\
123 	((base) + BUTTRESS_FW_BOOT_PARAMS_0 + ((offset) + (id)) * 4)
124 
125 enum buttress_syscom_id {
126 	/* pass syscom configuration to SPC */
127 	SYSCOM_CONFIG_ID		= 0,
128 	/* syscom state - modified by SP */
129 	SYSCOM_STATE_ID			= 1,
130 	/* syscom vtl0 addr mask */
131 	SYSCOM_VTL0_ADDR_MASK_ID	= 2,
132 	SYSCOM_ID_MAX
133 };
134 
135 static void ipu6_sys_queue_init(struct ipu6_fw_sys_queue *q, unsigned int size,
136 				unsigned int token_size,
137 				struct ipu6_fw_sys_queue_res *res)
138 {
139 	unsigned int buf_size = (size + 1) * token_size;
140 
141 	q->size = size + 1;
142 	q->token_size = token_size;
143 
144 	/* acquire the shared buffer space */
145 	q->host_address = res->host_address;
146 	res->host_address += buf_size;
147 	q->vied_address = res->vied_address;
148 	res->vied_address += buf_size;
149 
150 	/* acquire the shared read and writer pointers */
151 	q->wr_reg = res->reg;
152 	res->reg++;
153 	q->rd_reg = res->reg;
154 	res->reg++;
155 }
156 
157 void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg,
158 			  struct ipu6_bus_device *adev, void __iomem *base)
159 {
160 	size_t conf_size, inq_size, outq_size, specific_size;
161 	struct ipu6_fw_syscom_config *config_host_addr;
162 	unsigned int sizeinput = 0, sizeoutput = 0;
163 	struct ipu6_fw_sys_queue_res res;
164 	struct ipu6_fw_com_context *ctx;
165 	struct device *dev = &adev->auxdev.dev;
166 	size_t sizeall, offset;
167 	void *specific_host_addr;
168 	unsigned int i;
169 
170 	if (!cfg || !cfg->cell_start || !cfg->cell_ready)
171 		return NULL;
172 
173 	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
174 	if (!ctx)
175 		return NULL;
176 	ctx->dmem_addr = base + cfg->dmem_addr + REGMEM_OFFSET;
177 	ctx->adev = adev;
178 	ctx->cell_start = cfg->cell_start;
179 	ctx->cell_ready = cfg->cell_ready;
180 	ctx->buttress_boot_offset = cfg->buttress_boot_offset;
181 	ctx->base_addr  = base;
182 
183 	/*
184 	 * Allocate DMA mapped memory. Allocate one big chunk.
185 	 */
186 	/* Base cfg for FW */
187 	conf_size = roundup(sizeof(struct ipu6_fw_syscom_config), 8);
188 	/* Descriptions of the queues */
189 	inq_size = size_mul(cfg->num_input_queues,
190 			    sizeof(struct ipu6_fw_sys_queue));
191 	outq_size = size_mul(cfg->num_output_queues,
192 			     sizeof(struct ipu6_fw_sys_queue));
193 	/* FW specific information structure */
194 	specific_size = roundup(cfg->specific_size, 8);
195 
196 	sizeall = conf_size + inq_size + outq_size + specific_size;
197 
198 	for (i = 0; i < cfg->num_input_queues; i++)
199 		sizeinput += size_mul(cfg->input[i].queue_size + 1,
200 				      cfg->input[i].token_size);
201 
202 	for (i = 0; i < cfg->num_output_queues; i++)
203 		sizeoutput += size_mul(cfg->output[i].queue_size + 1,
204 				       cfg->output[i].token_size);
205 
206 	sizeall += sizeinput + sizeoutput;
207 
208 	ctx->dma_buffer = ipu6_dma_alloc(adev, sizeall, &ctx->dma_addr,
209 					 GFP_KERNEL, 0);
210 	if (!ctx->dma_buffer) {
211 		dev_err(dev, "failed to allocate dma memory\n");
212 		kfree(ctx);
213 		return NULL;
214 	}
215 
216 	ctx->dma_size = sizeall;
217 
218 	config_host_addr = ctx->dma_buffer;
219 	ctx->config_vied_addr = ctx->dma_addr;
220 
221 	offset = conf_size;
222 	ctx->input_queue = ctx->dma_buffer + offset;
223 	config_host_addr->input_queue = ctx->dma_addr + offset;
224 	config_host_addr->num_input_queues = cfg->num_input_queues;
225 
226 	offset += inq_size;
227 	ctx->output_queue = ctx->dma_buffer + offset;
228 	config_host_addr->output_queue = ctx->dma_addr + offset;
229 	config_host_addr->num_output_queues = cfg->num_output_queues;
230 
231 	/* copy firmware specific data */
232 	offset += outq_size;
233 	specific_host_addr = ctx->dma_buffer + offset;
234 	config_host_addr->specific_addr = ctx->dma_addr + offset;
235 	config_host_addr->specific_size = cfg->specific_size;
236 	if (cfg->specific_addr && cfg->specific_size)
237 		memcpy(specific_host_addr, cfg->specific_addr,
238 		       cfg->specific_size);
239 
240 	ipu6_dma_sync_single(adev, ctx->config_vied_addr, sizeall);
241 
242 	/* initialize input queues */
243 	offset += specific_size;
244 	res.reg = SYSCOM_QPR_BASE_REG;
245 	res.host_address = (uintptr_t)(ctx->dma_buffer + offset);
246 	res.vied_address = ctx->dma_addr + offset;
247 	for (i = 0; i < cfg->num_input_queues; i++)
248 		ipu6_sys_queue_init(ctx->input_queue + i,
249 				    cfg->input[i].queue_size,
250 				    cfg->input[i].token_size, &res);
251 
252 	/* initialize output queues */
253 	offset += sizeinput;
254 	res.host_address = (uintptr_t)(ctx->dma_buffer + offset);
255 	res.vied_address = ctx->dma_addr + offset;
256 	for (i = 0; i < cfg->num_output_queues; i++) {
257 		ipu6_sys_queue_init(ctx->output_queue + i,
258 				    cfg->output[i].queue_size,
259 				    cfg->output[i].token_size, &res);
260 	}
261 
262 	return ctx;
263 }
264 EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, INTEL_IPU6);
265 
266 int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx)
267 {
268 	/* write magic pattern to disable the tunit trace */
269 	writel(TUNIT_MAGIC_PATTERN, ctx->dmem_addr + TUNIT_CFG_DWR_REG * 4);
270 	/* Check if SP is in valid state */
271 	if (!ctx->cell_ready(ctx->adev))
272 		return -EIO;
273 
274 	/* store syscom uninitialized command */
275 	writel(SYSCOM_COMMAND_UNINIT, ctx->dmem_addr + SYSCOM_COMMAND_REG * 4);
276 
277 	/* store syscom uninitialized state */
278 	writel(SYSCOM_STATE_UNINIT,
279 	       BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
280 					  ctx->buttress_boot_offset,
281 					  SYSCOM_STATE_ID));
282 
283 	/* store firmware configuration address */
284 	writel(ctx->config_vied_addr,
285 	       BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
286 					  ctx->buttress_boot_offset,
287 					  SYSCOM_CONFIG_ID));
288 	ctx->cell_start(ctx->adev);
289 
290 	return 0;
291 }
292 EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, INTEL_IPU6);
293 
294 int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx)
295 {
296 	int state;
297 
298 	state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
299 						 ctx->buttress_boot_offset,
300 						 SYSCOM_STATE_ID));
301 	if (state != SYSCOM_STATE_READY)
302 		return -EBUSY;
303 
304 	/* set close request flag */
305 	writel(SYSCOM_COMMAND_INACTIVE, ctx->dmem_addr +
306 	       SYSCOM_COMMAND_REG * 4);
307 
308 	return 0;
309 }
310 EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, INTEL_IPU6);
311 
312 int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force)
313 {
314 	/* check if release is forced, an verify cell state if it is not */
315 	if (!force && !ctx->cell_ready(ctx->adev))
316 		return -EBUSY;
317 
318 	ipu6_dma_free(ctx->adev, ctx->dma_size,
319 		      ctx->dma_buffer, ctx->dma_addr, 0);
320 	kfree(ctx);
321 	return 0;
322 }
323 EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, INTEL_IPU6);
324 
325 bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx)
326 {
327 	int state;
328 
329 	state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
330 						 ctx->buttress_boot_offset,
331 						 SYSCOM_STATE_ID));
332 
333 	return state == SYSCOM_STATE_READY;
334 }
335 EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, INTEL_IPU6);
336 
337 void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr)
338 {
339 	struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr];
340 	void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
341 	unsigned int wr, rd;
342 	unsigned int packets;
343 	unsigned int index;
344 
345 	wr = readl(q_dmem + FW_COM_WR_REG);
346 	rd = readl(q_dmem + FW_COM_RD_REG);
347 
348 	if (WARN_ON_ONCE(wr >= q->size || rd >= q->size))
349 		return NULL;
350 
351 	if (wr < rd)
352 		packets = rd - wr - 1;
353 	else
354 		packets = q->size - (wr - rd + 1);
355 
356 	if (!packets)
357 		return NULL;
358 
359 	index = readl(q_dmem + FW_COM_WR_REG);
360 
361 	return (void *)((uintptr_t)q->host_address + index * q->token_size);
362 }
363 EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, INTEL_IPU6);
364 
365 void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr)
366 {
367 	struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr];
368 	void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
369 	unsigned int wr = readl(q_dmem + FW_COM_WR_REG) + 1;
370 
371 	if (wr >= q->size)
372 		wr = 0;
373 
374 	writel(wr, q_dmem + FW_COM_WR_REG);
375 }
376 EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, INTEL_IPU6);
377 
378 void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr)
379 {
380 	struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr];
381 	void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
382 	unsigned int wr, rd;
383 	unsigned int packets;
384 
385 	wr = readl(q_dmem + FW_COM_WR_REG);
386 	rd = readl(q_dmem + FW_COM_RD_REG);
387 
388 	if (WARN_ON_ONCE(wr >= q->size || rd >= q->size))
389 		return NULL;
390 
391 	if (wr < rd)
392 		wr += q->size;
393 
394 	packets = wr - rd;
395 	if (!packets)
396 		return NULL;
397 
398 	return (void *)((uintptr_t)q->host_address + rd * q->token_size);
399 }
400 EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, INTEL_IPU6);
401 
402 void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr)
403 {
404 	struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr];
405 	void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
406 	unsigned int rd = readl(q_dmem + FW_COM_RD_REG) + 1;
407 
408 	if (rd >= q->size)
409 		rd = 0;
410 
411 	writel(rd, q_dmem + FW_COM_RD_REG);
412 }
413 EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, INTEL_IPU6);
414