1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright (C) 2013--2024 Intel Corporation
6 #include <linux/device.h>
7 #include <linux/dma-mapping.h>
9 #include <linux/math.h>
10 #include <linux/overflow.h>
11 #include <linux/slab.h>
12 #include <linux/types.h>
16 #include "ipu6-fw-com.h"
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
32 /* Shared structure between driver and FW - do not modify */
33 struct ipu6_fw_sys_queue
{
38 u32 wr_reg
; /* reg number in subsystem's regmem */
43 struct ipu6_fw_sys_queue_res
{
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,
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,
65 /* firmware config: data that sent from the host to SP via DDR */
66 /* Cell copies data into a context */
68 struct ipu6_fw_syscom_config
{
72 u32 num_output_queues
;
74 /* ISP pointers to an array of ipu6_fw_sys_queue structures */
78 /* ISYS / PSYS private data */
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
);
91 unsigned int dma_size
;
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 */
98 unsigned int buttress_boot_offset
;
99 void __iomem
*base_addr
;
102 #define FW_COM_WR_REG 0
103 #define FW_COM_RD_REG 4
105 #define REGMEM_OFFSET 0
106 #define TUNIT_MAGIC_PATTERN 0x5a5a5a5a
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 */
117 /* first syscom queue pointer register */
118 SYSCOM_QPR_BASE_REG
= 4
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)
125 enum buttress_syscom_id
{
126 /* pass syscom configuration to SPC */
127 SYSCOM_CONFIG_ID
= 0,
128 /* syscom state - modified by SP */
130 /* syscom vtl0 addr mask */
131 SYSCOM_VTL0_ADDR_MASK_ID
= 2,
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
)
139 unsigned int buf_size
= (size
+ 1) * token_size
;
142 q
->token_size
= token_size
;
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
;
150 /* acquire the shared read and writer pointers */
151 q
->wr_reg
= res
->reg
;
153 q
->rd_reg
= res
->reg
;
157 void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg
*cfg
,
158 struct ipu6_bus_device
*adev
, void __iomem
*base
)
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
;
170 if (!cfg
|| !cfg
->cell_start
|| !cfg
->cell_ready
)
173 ctx
= kzalloc(sizeof(*ctx
), GFP_KERNEL
);
176 ctx
->dmem_addr
= base
+ cfg
->dmem_addr
+ REGMEM_OFFSET
;
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
;
184 * Allocate DMA mapped memory. Allocate one big chunk.
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);
196 sizeall
= conf_size
+ inq_size
+ outq_size
+ specific_size
;
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
);
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
);
206 sizeall
+= sizeinput
+ sizeoutput
;
208 ctx
->dma_buffer
= ipu6_dma_alloc(adev
, sizeall
, &ctx
->dma_addr
,
210 if (!ctx
->dma_buffer
) {
211 dev_err(dev
, "failed to allocate dma memory\n");
216 ctx
->dma_size
= sizeall
;
218 config_host_addr
= ctx
->dma_buffer
;
219 ctx
->config_vied_addr
= ctx
->dma_addr
;
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
;
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
;
231 /* copy firmware specific data */
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
,
240 ipu6_dma_sync_single(adev
, ctx
->config_vied_addr
, sizeall
);
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
);
252 /* initialize output queues */
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
);
264 EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare
, "INTEL_IPU6");
266 int ipu6_fw_com_open(struct ipu6_fw_com_context
*ctx
)
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
))
274 /* store syscom uninitialized command */
275 writel(SYSCOM_COMMAND_UNINIT
, ctx
->dmem_addr
+ SYSCOM_COMMAND_REG
* 4);
277 /* store syscom uninitialized state */
278 writel(SYSCOM_STATE_UNINIT
,
279 BUTTRESS_FW_BOOT_PARAM_REG(ctx
->base_addr
,
280 ctx
->buttress_boot_offset
,
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
,
288 ctx
->cell_start(ctx
->adev
);
292 EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open
, "INTEL_IPU6");
294 int ipu6_fw_com_close(struct ipu6_fw_com_context
*ctx
)
298 state
= readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx
->base_addr
,
299 ctx
->buttress_boot_offset
,
301 if (state
!= SYSCOM_STATE_READY
)
304 /* set close request flag */
305 writel(SYSCOM_COMMAND_INACTIVE
, ctx
->dmem_addr
+
306 SYSCOM_COMMAND_REG
* 4);
310 EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close
, "INTEL_IPU6");
312 int ipu6_fw_com_release(struct ipu6_fw_com_context
*ctx
, unsigned int force
)
314 /* check if release is forced, an verify cell state if it is not */
315 if (!force
&& !ctx
->cell_ready(ctx
->adev
))
318 ipu6_dma_free(ctx
->adev
, ctx
->dma_size
,
319 ctx
->dma_buffer
, ctx
->dma_addr
, 0);
323 EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release
, "INTEL_IPU6");
325 bool ipu6_fw_com_ready(struct ipu6_fw_com_context
*ctx
)
329 state
= readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx
->base_addr
,
330 ctx
->buttress_boot_offset
,
333 return state
== SYSCOM_STATE_READY
;
335 EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready
, "INTEL_IPU6");
337 void *ipu6_send_get_token(struct ipu6_fw_com_context
*ctx
, int q_nbr
)
339 struct ipu6_fw_sys_queue
*q
= &ctx
->input_queue
[q_nbr
];
340 void __iomem
*q_dmem
= ctx
->dmem_addr
+ q
->wr_reg
* 4;
342 unsigned int packets
;
345 wr
= readl(q_dmem
+ FW_COM_WR_REG
);
346 rd
= readl(q_dmem
+ FW_COM_RD_REG
);
348 if (WARN_ON_ONCE(wr
>= q
->size
|| rd
>= q
->size
))
352 packets
= rd
- wr
- 1;
354 packets
= q
->size
- (wr
- rd
+ 1);
359 index
= readl(q_dmem
+ FW_COM_WR_REG
);
361 return (void *)((uintptr_t)q
->host_address
+ index
* q
->token_size
);
363 EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token
, "INTEL_IPU6");
365 void ipu6_send_put_token(struct ipu6_fw_com_context
*ctx
, int q_nbr
)
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;
374 writel(wr
, q_dmem
+ FW_COM_WR_REG
);
376 EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token
, "INTEL_IPU6");
378 void *ipu6_recv_get_token(struct ipu6_fw_com_context
*ctx
, int q_nbr
)
380 struct ipu6_fw_sys_queue
*q
= &ctx
->output_queue
[q_nbr
];
381 void __iomem
*q_dmem
= ctx
->dmem_addr
+ q
->wr_reg
* 4;
383 unsigned int packets
;
385 wr
= readl(q_dmem
+ FW_COM_WR_REG
);
386 rd
= readl(q_dmem
+ FW_COM_RD_REG
);
388 if (WARN_ON_ONCE(wr
>= q
->size
|| rd
>= q
->size
))
398 return (void *)((uintptr_t)q
->host_address
+ rd
* q
->token_size
);
400 EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token
, "INTEL_IPU6");
402 void ipu6_recv_put_token(struct ipu6_fw_com_context
*ctx
, int q_nbr
)
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;
411 writel(rd
, q_dmem
+ FW_COM_RD_REG
);
413 EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token
, "INTEL_IPU6");