1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
|
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_vspx.c -- R-Car Gen 4 VSPX
*
* Copyright (C) 2025 Ideas On Board Oy
* Copyright (C) 2025 Renesas Electronics Corporation
*/
#include "vsp1_vspx.h"
#include <linux/cleanup.h>
#include <linux/container_of.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/export.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <media/media-entity.h>
#include <media/v4l2-subdev.h>
#include <media/vsp1.h>
#include "vsp1_dl.h"
#include "vsp1_iif.h"
#include "vsp1_pipe.h"
#include "vsp1_rwpf.h"
/*
* struct vsp1_vspx_pipeline - VSPX pipeline
* @pipe: the VSP1 pipeline
* @partition: the pre-calculated partition used by the pipeline
* @mutex: protects the streaming start/stop sequences
* @lock: protect access to the enabled flag
* @enabled: the enable flag
* @vspx_frame_end: frame end callback
* @frame_end_data: data for the frame end callback
*/
struct vsp1_vspx_pipeline {
struct vsp1_pipeline pipe;
struct vsp1_partition partition;
/*
* Protects the streaming start/stop sequences.
*
* The start/stop sequences cannot be locked with the 'lock' spinlock
* as they acquire mutexes when handling the pm runtime and the vsp1
* pipe start/stop operations. Provide a dedicated mutex for this
* reason.
*/
struct mutex mutex;
/*
* Protects the enable flag.
*
* The enabled flag is contended between the start/stop streaming
* routines and the job_run one, which cannot take a mutex as it is
* called from the ISP irq context.
*/
spinlock_t lock;
bool enabled;
void (*vspx_frame_end)(void *frame_end_data);
void *frame_end_data;
};
static inline struct vsp1_vspx_pipeline *
to_vsp1_vspx_pipeline(struct vsp1_pipeline *pipe)
{
return container_of(pipe, struct vsp1_vspx_pipeline, pipe);
}
/*
* struct vsp1_vspx - VSPX device
* @vsp1: the VSP1 device
* @pipe: the VSPX pipeline
*/
struct vsp1_vspx {
struct vsp1_device *vsp1;
struct vsp1_vspx_pipeline pipe;
};
/* Apply the given width, height and fourcc to the RWPF's subdevice */
static int vsp1_vspx_rwpf_set_subdev_fmt(struct vsp1_device *vsp1,
struct vsp1_rwpf *rwpf,
u32 isp_fourcc,
unsigned int width,
unsigned int height)
{
struct vsp1_entity *ent = &rwpf->entity;
struct v4l2_subdev_format format = {};
u32 vspx_fourcc;
switch (isp_fourcc) {
case V4L2_PIX_FMT_GREY:
/* 8 bit RAW Bayer image. */
vspx_fourcc = V4L2_PIX_FMT_RGB332;
break;
case V4L2_PIX_FMT_Y10:
case V4L2_PIX_FMT_Y12:
case V4L2_PIX_FMT_Y16:
/* 10, 12 and 16 bit RAW Bayer image. */
vspx_fourcc = V4L2_PIX_FMT_RGB565;
break;
case V4L2_META_FMT_GENERIC_8:
/* ConfigDMA parameters buffer. */
vspx_fourcc = V4L2_PIX_FMT_XBGR32;
break;
default:
return -EINVAL;
}
rwpf->fmtinfo = vsp1_get_format_info(vsp1, vspx_fourcc);
format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
format.pad = RWPF_PAD_SINK;
format.format.width = width;
format.format.height = height;
format.format.field = V4L2_FIELD_NONE;
format.format.code = rwpf->fmtinfo->mbus;
return v4l2_subdev_call(&ent->subdev, pad, set_fmt, NULL, &format);
}
/* Configure the RPF->IIF->WPF pipeline for ConfigDMA or RAW image transfer. */
static int vsp1_vspx_pipeline_configure(struct vsp1_device *vsp1,
dma_addr_t addr, u32 isp_fourcc,
unsigned int width, unsigned int height,
unsigned int stride,
unsigned int iif_sink_pad,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
struct vsp1_pipeline *pipe = &vspx_pipe->pipe;
struct vsp1_rwpf *rpf0 = pipe->inputs[0];
int ret;
ret = vsp1_vspx_rwpf_set_subdev_fmt(vsp1, rpf0, isp_fourcc, width,
height);
if (ret)
return ret;
ret = vsp1_vspx_rwpf_set_subdev_fmt(vsp1, pipe->output, isp_fourcc,
width, height);
if (ret)
return ret;
vsp1_pipeline_calculate_partition(pipe, &pipe->part_table[0], width, 0);
rpf0->format.plane_fmt[0].bytesperline = stride;
rpf0->format.num_planes = 1;
rpf0->mem.addr[0] = addr;
/*
* Connect RPF0 to the IIF sink pad corresponding to the config or image
* path.
*/
rpf0->entity.sink_pad = iif_sink_pad;
vsp1_entity_route_setup(&rpf0->entity, pipe, dlb);
vsp1_entity_configure_stream(&rpf0->entity, rpf0->entity.state, pipe,
dl, dlb);
vsp1_entity_configure_partition(&rpf0->entity, pipe,
&pipe->part_table[0], dl, dlb);
return 0;
}
/* -----------------------------------------------------------------------------
* Interrupt handling
*/
static void vsp1_vspx_pipeline_frame_end(struct vsp1_pipeline *pipe,
unsigned int completion)
{
struct vsp1_vspx_pipeline *vspx_pipe = to_vsp1_vspx_pipeline(pipe);
scoped_guard(spinlock_irqsave, &pipe->irqlock) {
/*
* Operating the vsp1_pipe in singleshot mode requires to
* manually set the pipeline state to stopped when a transfer
* is completed.
*/
pipe->state = VSP1_PIPELINE_STOPPED;
}
if (vspx_pipe->vspx_frame_end)
vspx_pipe->vspx_frame_end(vspx_pipe->frame_end_data);
}
/* -----------------------------------------------------------------------------
* ISP Driver API (include/media/vsp1.h)
*/
/**
* vsp1_isp_init() - Initialize the VSPX
* @dev: The VSP1 struct device
*
* Return: %0 on success or a negative error code on failure
*/
int vsp1_isp_init(struct device *dev)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
if (!vsp1)
return -EPROBE_DEFER;
return 0;
}
EXPORT_SYMBOL_GPL(vsp1_isp_init);
/**
* vsp1_isp_get_bus_master - Get VSPX bus master
* @dev: The VSP1 struct device
*
* The VSPX accesses memory through an FCPX instance. When allocating memory
* buffers that will have to be accessed by the VSPX the 'struct device' of
* the FCPX should be used. Use this function to get a reference to it.
*
* Return: a pointer to the bus master's device
*/
struct device *vsp1_isp_get_bus_master(struct device *dev)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
if (!vsp1)
return ERR_PTR(-ENODEV);
return vsp1->bus_master;
}
EXPORT_SYMBOL_GPL(vsp1_isp_get_bus_master);
/**
* vsp1_isp_alloc_buffer - Allocate a buffer in the VSPX address space
* @dev: The VSP1 struct device
* @size: The size of the buffer to be allocated by the VSPX
* @buffer_desc: The buffer descriptor. Will be filled with the buffer
* CPU-mapped address, the bus address and the size of the
* allocated buffer
*
* Allocate a buffer that will be later accessed by the VSPX. Buffers allocated
* using vsp1_isp_alloc_buffer() shall be released with a call to
* vsp1_isp_free_buffer(). This function is used by the ISP driver to allocate
* memory for the ConfigDMA parameters buffer.
*
* Return: %0 on success or a negative error code on failure
*/
int vsp1_isp_alloc_buffer(struct device *dev, size_t size,
struct vsp1_isp_buffer_desc *buffer_desc)
{
struct device *bus_master = vsp1_isp_get_bus_master(dev);
if (IS_ERR_OR_NULL(bus_master))
return -ENODEV;
buffer_desc->cpu_addr = dma_alloc_coherent(bus_master, size,
&buffer_desc->dma_addr,
GFP_KERNEL);
if (!buffer_desc->cpu_addr)
return -ENOMEM;
buffer_desc->size = size;
return 0;
}
EXPORT_SYMBOL_GPL(vsp1_isp_alloc_buffer);
/**
* vsp1_isp_free_buffer - Release a buffer allocated by vsp1_isp_alloc_buffer()
* @dev: The VSP1 struct device
* @buffer_desc: The descriptor of the buffer to release as returned by
* vsp1_isp_alloc_buffer()
*
* Release memory in the VSPX address space allocated by
* vsp1_isp_alloc_buffer().
*/
void vsp1_isp_free_buffer(struct device *dev,
struct vsp1_isp_buffer_desc *buffer_desc)
{
struct device *bus_master = vsp1_isp_get_bus_master(dev);
if (IS_ERR_OR_NULL(bus_master))
return;
dma_free_coherent(bus_master, buffer_desc->size, buffer_desc->cpu_addr,
buffer_desc->dma_addr);
}
/**
* vsp1_isp_start_streaming - Start processing VSPX jobs
* @dev: The VSP1 struct device
* @frame_end: The frame end callback description
*
* Start the VSPX and prepare for accepting buffer transfer job requests.
* The caller is responsible for tracking the started state of the VSPX.
* Attempting to start an already started VSPX instance is an error.
*
* Return: %0 on success or a negative error code on failure
*/
int vsp1_isp_start_streaming(struct device *dev,
struct vsp1_vspx_frame_end *frame_end)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
struct vsp1_pipeline *pipe = &vspx_pipe->pipe;
u32 value;
int ret;
if (!frame_end)
return -EINVAL;
guard(mutex)(&vspx_pipe->mutex);
scoped_guard(spinlock_irq, &vspx_pipe->lock) {
if (vspx_pipe->enabled)
return -EBUSY;
}
vspx_pipe->vspx_frame_end = frame_end->vspx_frame_end;
vspx_pipe->frame_end_data = frame_end->frame_end_data;
/* Enable the VSP1 and prepare for streaming. */
vsp1_pipeline_dump(pipe, "VSPX job");
ret = vsp1_device_get(vsp1);
if (ret < 0)
return ret;
/*
* Make sure VSPX is not active. This should never happen in normal
* usage
*/
value = vsp1_read(vsp1, VI6_CMD(0));
if (value & VI6_CMD_STRCMD) {
dev_err(vsp1->dev,
"%s: Starting of WPF0 already reserved\n", __func__);
ret = -EBUSY;
goto error_put;
}
value = vsp1_read(vsp1, VI6_STATUS);
if (value & VI6_STATUS_SYS_ACT(0)) {
dev_err(vsp1->dev,
"%s: WPF0 has not entered idle state\n", __func__);
ret = -EBUSY;
goto error_put;
}
scoped_guard(spinlock_irq, &vspx_pipe->lock) {
vspx_pipe->enabled = true;
}
return 0;
error_put:
vsp1_device_put(vsp1);
return ret;
}
EXPORT_SYMBOL_GPL(vsp1_isp_start_streaming);
/**
* vsp1_isp_stop_streaming - Stop the VSPX
* @dev: The VSP1 struct device
*
* Stop the VSPX operation by stopping the vsp1 pipeline and waiting for the
* last frame in transfer, if any, to complete.
*
* The caller is responsible for tracking the stopped state of the VSPX.
* Attempting to stop an already stopped VSPX instance is a nop.
*/
void vsp1_isp_stop_streaming(struct device *dev)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
struct vsp1_pipeline *pipe = &vspx_pipe->pipe;
guard(mutex)(&vspx_pipe->mutex);
scoped_guard(spinlock_irq, &vspx_pipe->lock) {
if (!vspx_pipe->enabled)
return;
vspx_pipe->enabled = false;
}
WARN_ON_ONCE(vsp1_pipeline_stop(pipe));
vspx_pipe->vspx_frame_end = NULL;
vsp1_dlm_reset(pipe->output->dlm);
vsp1_device_put(vsp1);
}
EXPORT_SYMBOL_GPL(vsp1_isp_stop_streaming);
/**
* vsp1_isp_job_prepare - Prepare a new buffer transfer job
* @dev: The VSP1 struct device
* @job: The job description
*
* Prepare a new buffer transfer job by populating a display list that will be
* later executed by a call to vsp1_isp_job_run(). All pending jobs must be
* released after stopping the streaming operations with a call to
* vsp1_isp_job_release().
*
* In order for the VSPX to accept new jobs to prepare the VSPX must have been
* started.
*
* Return: %0 on success or a negative error code on failure
*/
int vsp1_isp_job_prepare(struct device *dev, struct vsp1_isp_job_desc *job)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
struct vsp1_pipeline *pipe = &vspx_pipe->pipe;
const struct v4l2_pix_format_mplane *pix_mp;
struct vsp1_dl_list *second_dl = NULL;
struct vsp1_dl_body *dlb;
struct vsp1_dl_list *dl;
int ret;
/*
* Transfer the buffers described in the job: an optional ConfigDMA
* parameters buffer and a RAW image.
*/
job->dl = vsp1_dl_list_get(pipe->output->dlm);
if (!job->dl)
return -ENOMEM;
dl = job->dl;
dlb = vsp1_dl_list_get_body0(dl);
/* Configure IIF routing and enable IIF function. */
vsp1_entity_route_setup(pipe->iif, pipe, dlb);
vsp1_entity_configure_stream(pipe->iif, pipe->iif->state, pipe,
dl, dlb);
/* Configure WPF0 to enable RPF0 as source. */
vsp1_entity_route_setup(&pipe->output->entity, pipe, dlb);
vsp1_entity_configure_stream(&pipe->output->entity,
pipe->output->entity.state, pipe,
dl, dlb);
if (job->config.pairs) {
/*
* Writing less than 17 pairs corrupts the output images ( < 16
* pairs) or freezes the VSPX operations (= 16 pairs). Only
* allow more than 16 pairs to be written.
*/
if (job->config.pairs <= 16) {
ret = -EINVAL;
goto error_put_dl;
}
/*
* Configure RPF0 for ConfigDMA data. Transfer the number of
* configuration pairs plus 2 words for the header.
*/
ret = vsp1_vspx_pipeline_configure(vsp1, job->config.mem,
V4L2_META_FMT_GENERIC_8,
job->config.pairs * 2 + 2, 1,
job->config.pairs * 2 + 2,
VSPX_IIF_SINK_PAD_CONFIG,
dl, dlb);
if (ret)
goto error_put_dl;
second_dl = vsp1_dl_list_get(pipe->output->dlm);
if (!second_dl) {
ret = -ENOMEM;
goto error_put_dl;
}
dl = second_dl;
dlb = vsp1_dl_list_get_body0(dl);
}
/* Configure RPF0 for RAW image transfer. */
pix_mp = &job->img.fmt;
ret = vsp1_vspx_pipeline_configure(vsp1, job->img.mem,
pix_mp->pixelformat,
pix_mp->width, pix_mp->height,
pix_mp->plane_fmt[0].bytesperline,
VSPX_IIF_SINK_PAD_IMG, dl, dlb);
if (ret)
goto error_put_dl;
if (second_dl)
vsp1_dl_list_add_chain(job->dl, second_dl);
return 0;
error_put_dl:
if (second_dl)
vsp1_dl_list_put(second_dl);
vsp1_dl_list_put(job->dl);
job->dl = NULL;
return ret;
}
EXPORT_SYMBOL_GPL(vsp1_isp_job_prepare);
/**
* vsp1_isp_job_run - Run a buffer transfer job
* @dev: The VSP1 struct device
* @job: The job to be run
*
* Run the display list contained in the job description provided by the caller.
* The job must have been prepared with a call to vsp1_isp_job_prepare() and
* the job's display list shall be valid.
*
* Jobs can be run only on VSPX instances which have been started. Requests
* to run a job after the VSPX has been stopped return -EINVAL and the job
* resources shall be released by the caller with vsp1_isp_job_release().
* When a job is run successfully all the resources acquired by
* vsp1_isp_job_prepare() are released by this function and no further action
* is required to the caller.
*
* Return: %0 on success or a negative error code on failure
*/
int vsp1_isp_job_run(struct device *dev, struct vsp1_isp_job_desc *job)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
struct vsp1_pipeline *pipe = &vspx_pipe->pipe;
u32 value;
/* Make sure VSPX is not busy processing a frame. */
value = vsp1_read(vsp1, VI6_CMD(0));
if (value) {
dev_err(vsp1->dev,
"%s: Starting of WPF0 already reserved\n", __func__);
return -EBUSY;
}
scoped_guard(spinlock_irqsave, &vspx_pipe->lock) {
/*
* If a new job is scheduled when the VSPX is stopped, do not
* run it.
*/
if (!vspx_pipe->enabled)
return -EINVAL;
vsp1_dl_list_commit(job->dl, 0);
/*
* The display list is now under control of the display list
* manager and will be released automatically when the job
* completes.
*/
job->dl = NULL;
}
scoped_guard(spinlock_irqsave, &pipe->irqlock) {
vsp1_pipeline_run(pipe);
}
return 0;
}
EXPORT_SYMBOL_GPL(vsp1_isp_job_run);
/**
* vsp1_isp_job_release - Release a non processed transfer job
* @dev: The VSP1 struct device
* @job: The job to release
*
* Release a job prepared by a call to vsp1_isp_job_prepare() and not yet
* run. All pending jobs shall be released after streaming has been stopped.
*/
void vsp1_isp_job_release(struct device *dev,
struct vsp1_isp_job_desc *job)
{
vsp1_dl_list_put(job->dl);
}
EXPORT_SYMBOL_GPL(vsp1_isp_job_release);
/* -----------------------------------------------------------------------------
* Initialization and cleanup
*/
int vsp1_vspx_init(struct vsp1_device *vsp1)
{
struct vsp1_vspx_pipeline *vspx_pipe;
struct vsp1_pipeline *pipe;
vsp1->vspx = devm_kzalloc(vsp1->dev, sizeof(*vsp1->vspx), GFP_KERNEL);
if (!vsp1->vspx)
return -ENOMEM;
vsp1->vspx->vsp1 = vsp1;
vspx_pipe = &vsp1->vspx->pipe;
vspx_pipe->enabled = false;
pipe = &vspx_pipe->pipe;
vsp1_pipeline_init(pipe);
pipe->partitions = 1;
pipe->part_table = &vspx_pipe->partition;
pipe->interlaced = false;
pipe->frame_end = vsp1_vspx_pipeline_frame_end;
mutex_init(&vspx_pipe->mutex);
spin_lock_init(&vspx_pipe->lock);
/*
* Initialize RPF0 as input for VSPX and use it unconditionally for
* now.
*/
pipe->inputs[0] = vsp1->rpf[0];
pipe->inputs[0]->entity.pipe = pipe;
pipe->inputs[0]->entity.sink = &vsp1->iif->entity;
list_add_tail(&pipe->inputs[0]->entity.list_pipe, &pipe->entities);
pipe->iif = &vsp1->iif->entity;
pipe->iif->pipe = pipe;
pipe->iif->sink = &vsp1->wpf[0]->entity;
pipe->iif->sink_pad = RWPF_PAD_SINK;
list_add_tail(&pipe->iif->list_pipe, &pipe->entities);
pipe->output = vsp1->wpf[0];
pipe->output->entity.pipe = pipe;
list_add_tail(&pipe->output->entity.list_pipe, &pipe->entities);
return 0;
}
void vsp1_vspx_cleanup(struct vsp1_device *vsp1)
{
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
mutex_destroy(&vspx_pipe->mutex);
}
|