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