File Index
Files and directories
Sign in
FILE

ipu7-isys-csi2.c

File preview with syntax highlighting.

linux-6.19.14/drivers/staging/media/ipu7/ipu7-isys-csi2.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Copyright (C) 2013 - 2025 Intel Corporation
  4 */
  5
  6#include <linux/atomic.h>
  7#include <linux/bits.h>
  8#include <linux/bug.h>
  9#include <linux/delay.h>
 10#include <linux/device.h>
 11#include <linux/io.h>
 12#include <linux/minmax.h>
 13#include <linux/mutex.h>
 14#include <linux/slab.h>
 15
 16#include <media/media-entity.h>
 17#include <media/v4l2-ctrls.h>
 18#include <media/v4l2-device.h>
 19#include <media/v4l2-event.h>
 20#include <media/v4l2-subdev.h>
 21
 22#include "ipu7.h"
 23#include "ipu7-bus.h"
 24#include "ipu7-isys.h"
 25#include "ipu7-isys-csi2.h"
 26#include "ipu7-isys-csi2-regs.h"
 27#include "ipu7-isys-csi-phy.h"
 28
 29static const u32 csi2_supported_codes[] = {
 30	MEDIA_BUS_FMT_Y10_1X10,
 31	MEDIA_BUS_FMT_RGB565_1X16,
 32	MEDIA_BUS_FMT_RGB888_1X24,
 33	MEDIA_BUS_FMT_UYVY8_1X16,
 34	MEDIA_BUS_FMT_YUYV8_1X16,
 35	MEDIA_BUS_FMT_YUYV10_1X20,
 36	MEDIA_BUS_FMT_SBGGR10_1X10,
 37	MEDIA_BUS_FMT_SGBRG10_1X10,
 38	MEDIA_BUS_FMT_SGRBG10_1X10,
 39	MEDIA_BUS_FMT_SRGGB10_1X10,
 40	MEDIA_BUS_FMT_SBGGR12_1X12,
 41	MEDIA_BUS_FMT_SGBRG12_1X12,
 42	MEDIA_BUS_FMT_SGRBG12_1X12,
 43	MEDIA_BUS_FMT_SRGGB12_1X12,
 44	MEDIA_BUS_FMT_SBGGR8_1X8,
 45	MEDIA_BUS_FMT_SGBRG8_1X8,
 46	MEDIA_BUS_FMT_SGRBG8_1X8,
 47	MEDIA_BUS_FMT_SRGGB8_1X8,
 48	0,
 49};
 50
 51s64 ipu7_isys_csi2_get_link_freq(struct ipu7_isys_csi2 *csi2)
 52{
 53	struct media_pad *src_pad;
 54
 55	src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity);
 56	if (IS_ERR(src_pad)) {
 57		dev_err(&csi2->isys->adev->auxdev.dev,
 58			"can't get source pad of %s (%pe)\n",
 59			csi2->asd.sd.name, src_pad);
 60		return PTR_ERR(src_pad);
 61	}
 62
 63	return v4l2_get_link_freq(src_pad, 0, 0);
 64}
 65
 66static int csi2_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
 67				struct v4l2_event_subscription *sub)
 68{
 69	struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd);
 70	struct ipu7_isys_csi2 *csi2 = to_ipu7_isys_csi2(asd);
 71	struct device *dev = &csi2->isys->adev->auxdev.dev;
 72
 73	dev_dbg(dev, "csi2 subscribe event(type %u id %u)\n",
 74		sub->type, sub->id);
 75
 76	switch (sub->type) {
 77	case V4L2_EVENT_FRAME_SYNC:
 78		return v4l2_event_subscribe(fh, sub, 10, NULL);
 79	case V4L2_EVENT_CTRL:
 80		return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub);
 81	default:
 82		return -EINVAL;
 83	}
 84}
 85
 86static const struct v4l2_subdev_core_ops csi2_sd_core_ops = {
 87	.subscribe_event = csi2_subscribe_event,
 88	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
 89};
 90
 91static void csi2_irq_enable(struct ipu7_isys_csi2 *csi2)
 92{
 93	struct ipu7_device *isp = csi2->isys->adev->isp;
 94	unsigned int offset, mask;
 95
 96	/* enable CSI2 legacy error irq */
 97	offset = IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port);
 98	mask = IPU7_CSI_RX_ERROR_IRQ_MASK;
 99	writel(mask, csi2->base + offset + IRQ_CTL_CLEAR);
100	writel(mask, csi2->base + offset + IRQ_CTL_MASK);
101	writel(mask, csi2->base + offset + IRQ_CTL_ENABLE);
102
103	/* enable CSI2 legacy sync irq */
104	offset = IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port);
105	mask = IPU7_CSI_RX_SYNC_IRQ_MASK;
106	writel(mask, csi2->base + offset + IRQ_CTL_CLEAR);
107	writel(mask, csi2->base + offset + IRQ_CTL_MASK);
108	writel(mask, csi2->base + offset + IRQ_CTL_ENABLE);
109
110	mask = IPU7P5_CSI_RX_SYNC_FE_IRQ_MASK;
111	if (!is_ipu7(isp->hw_ver)) {
112		writel(mask, csi2->base + offset + IRQ1_CTL_CLEAR);
113		writel(mask, csi2->base + offset + IRQ1_CTL_MASK);
114		writel(mask, csi2->base + offset + IRQ1_CTL_ENABLE);
115	}
116}
117
118static void csi2_irq_disable(struct ipu7_isys_csi2 *csi2)
119{
120	struct ipu7_device *isp = csi2->isys->adev->isp;
121	unsigned int offset, mask;
122
123	/* disable CSI2 legacy error irq */
124	offset = IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port);
125	mask = IPU7_CSI_RX_ERROR_IRQ_MASK;
126	writel(mask, csi2->base + offset + IRQ_CTL_CLEAR);
127	writel(0, csi2->base + offset + IRQ_CTL_MASK);
128	writel(0, csi2->base + offset + IRQ_CTL_ENABLE);
129
130	/* disable CSI2 legacy sync irq */
131	offset = IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port);
132	mask = IPU7_CSI_RX_SYNC_IRQ_MASK;
133	writel(mask, csi2->base + offset + IRQ_CTL_CLEAR);
134	writel(0, csi2->base + offset + IRQ_CTL_MASK);
135	writel(0, csi2->base + offset + IRQ_CTL_ENABLE);
136
137	if (!is_ipu7(isp->hw_ver)) {
138		writel(mask, csi2->base + offset + IRQ1_CTL_CLEAR);
139		writel(0, csi2->base + offset + IRQ1_CTL_MASK);
140		writel(0, csi2->base + offset + IRQ1_CTL_ENABLE);
141	}
142}
143
144static void ipu7_isys_csi2_disable_stream(struct ipu7_isys_csi2 *csi2)
145{
146	struct ipu7_isys *isys = csi2->isys;
147	void __iomem *isys_base = isys->pdata->base;
148
149	ipu7_isys_csi_phy_powerdown(csi2);
150
151	writel(0x4, isys_base + IS_IO_GPREGS_BASE + CLK_DIV_FACTOR_APB_CLK);
152	csi2_irq_disable(csi2);
153}
154
155static int ipu7_isys_csi2_enable_stream(struct ipu7_isys_csi2 *csi2)
156{
157	struct ipu7_isys *isys = csi2->isys;
158	struct device *dev = &isys->adev->auxdev.dev;
159	void __iomem *isys_base = isys->pdata->base;
160	unsigned int port, nlanes, offset;
161	int ret;
162
163	port = csi2->port;
164	nlanes = csi2->nlanes;
165
166	offset = IS_IO_GPREGS_BASE;
167	writel(0x2, isys_base + offset + CLK_DIV_FACTOR_APB_CLK);
168	dev_dbg(dev, "port %u CLK_GATE = 0x%04x DIV_FACTOR_APB_CLK=0x%04x\n",
169		port, readl(isys_base + offset + CSI_PORT_CLK_GATE),
170		readl(isys_base + offset + CLK_DIV_FACTOR_APB_CLK));
171	if (port == 0U && nlanes == 4U && !is_ipu7(isys->adev->isp->hw_ver)) {
172		dev_dbg(dev, "CSI port %u in aggregation mode\n", port);
173		writel(0x1, isys_base + offset + CSI_PORTAB_AGGREGATION);
174	}
175
176	/* input is coming from CSI receiver (sensor) */
177	offset = IS_IO_CSI2_ADPL_PORT_BASE(port);
178	writel(CSI_SENSOR_INPUT, isys_base + offset + CSI2_ADPL_INPUT_MODE);
179	writel(1, isys_base + offset + CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_EN);
180
181	ret = ipu7_isys_csi_phy_powerup(csi2);
182	if (ret) {
183		dev_err(dev, "CSI-%d PHY power up failed %d\n", port, ret);
184		return ret;
185	}
186
187	csi2_irq_enable(csi2);
188
189	return 0;
190}
191
192static int ipu7_isys_csi2_set_sel(struct v4l2_subdev *sd,
193				  struct v4l2_subdev_state *state,
194				  struct v4l2_subdev_selection *sel)
195{
196	struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd);
197	struct device *dev = &asd->isys->adev->auxdev.dev;
198	struct v4l2_mbus_framefmt *sink_ffmt;
199	struct v4l2_mbus_framefmt *src_ffmt;
200	struct v4l2_rect *crop;
201
202	if (sel->pad == IPU7_CSI2_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP)
203		return -EINVAL;
204
205	sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state,
206								 sel->pad,
207								 sel->stream);
208	if (!sink_ffmt)
209		return -EINVAL;
210
211	src_ffmt = v4l2_subdev_state_get_format(state, sel->pad, sel->stream);
212	if (!src_ffmt)
213		return -EINVAL;
214
215	crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
216	if (!crop)
217		return -EINVAL;
218
219	/* Only vertical cropping is supported */
220	sel->r.left = 0;
221	sel->r.width = sink_ffmt->width;
222	/* Non-bayer formats can't be single line cropped */
223	if (!ipu7_isys_is_bayer_format(sink_ffmt->code))
224		sel->r.top &= ~1U;
225	sel->r.height = clamp(sel->r.height & ~1U, IPU_ISYS_MIN_HEIGHT,
226			      sink_ffmt->height - sel->r.top);
227	*crop = sel->r;
228
229	/* update source pad format */
230	src_ffmt->width = sel->r.width;
231	src_ffmt->height = sel->r.height;
232	if (ipu7_isys_is_bayer_format(sink_ffmt->code))
233		src_ffmt->code = ipu7_isys_convert_bayer_order(sink_ffmt->code,
234							       sel->r.left,
235							       sel->r.top);
236	dev_dbg(dev, "set crop for %s sel: %d,%d,%d,%d code: 0x%x\n",
237		sd->name, sel->r.left, sel->r.top, sel->r.width, sel->r.height,
238		src_ffmt->code);
239
240	return 0;
241}
242
243static int ipu7_isys_csi2_get_sel(struct v4l2_subdev *sd,
244				  struct v4l2_subdev_state *state,
245				  struct v4l2_subdev_selection *sel)
246{
247	struct v4l2_mbus_framefmt *sink_ffmt;
248	struct v4l2_rect *crop;
249	int ret = 0;
250
251	if (sd->entity.pads[sel->pad].flags & MEDIA_PAD_FL_SINK)
252		return -EINVAL;
253
254	sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state,
255								 sel->pad,
256								 sel->stream);
257	if (!sink_ffmt)
258		return -EINVAL;
259
260	crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
261	if (!crop)
262		return -EINVAL;
263
264	switch (sel->target) {
265	case V4L2_SEL_TGT_CROP_DEFAULT:
266	case V4L2_SEL_TGT_CROP_BOUNDS:
267		sel->r.left = 0;
268		sel->r.top = 0;
269		sel->r.width = sink_ffmt->width;
270		sel->r.height = sink_ffmt->height;
271		break;
272	case V4L2_SEL_TGT_CROP:
273		sel->r = *crop;
274		break;
275	default:
276		ret = -EINVAL;
277	}
278
279	return ret;
280}
281
282/*
283 * Maximum stream ID is 63 for now, as we use u64 bitmask to represent a set
284 * of streams.
285 */
286#define CSI2_SUBDEV_MAX_STREAM_ID 63
287
288static int ipu7_isys_csi2_enable_streams(struct v4l2_subdev *sd,
289					 struct v4l2_subdev_state *state,
290					 u32 pad, u64 streams_mask)
291{
292	struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd);
293	struct ipu7_isys_csi2 *csi2 = to_ipu7_isys_csi2(asd);
294	struct v4l2_subdev *r_sd;
295	struct media_pad *rp;
296	u32 sink_pad, sink_stream;
297	int ret, i;
298
299	if (!csi2->stream_count) {
300		dev_dbg(&csi2->isys->adev->auxdev.dev,
301			"stream on CSI2-%u with %u lanes\n", csi2->port,
302			csi2->nlanes);
303		ret = ipu7_isys_csi2_enable_stream(csi2);
304		if (ret)
305			return ret;
306	}
307
308	for (i = 0; i <= CSI2_SUBDEV_MAX_STREAM_ID; i++) {
309		if (streams_mask & BIT_ULL(i))
310			break;
311	}
312
313	ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, i,
314						    &sink_pad, &sink_stream);
315	if (ret)
316		return ret;
317
318	rp = media_pad_remote_pad_first(&sd->entity.pads[IPU7_CSI2_PAD_SINK]);
319	r_sd = media_entity_to_v4l2_subdev(rp->entity);
320
321	ret = v4l2_subdev_enable_streams(r_sd, rp->index,
322					 BIT_ULL(sink_stream));
323	if (!ret) {
324		csi2->stream_count++;
325		return 0;
326	}
327
328	if (!csi2->stream_count)
329		ipu7_isys_csi2_disable_stream(csi2);
330
331	return ret;
332}
333
334static int ipu7_isys_csi2_disable_streams(struct v4l2_subdev *sd,
335					  struct v4l2_subdev_state *state,
336					  u32 pad, u64 streams_mask)
337{
338	struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd);
339	struct ipu7_isys_csi2 *csi2 = to_ipu7_isys_csi2(asd);
340	struct v4l2_subdev *r_sd;
341	struct media_pad *rp;
342	u32 sink_pad, sink_stream;
343	int ret, i;
344
345	for (i = 0; i <= CSI2_SUBDEV_MAX_STREAM_ID; i++) {
346		if (streams_mask & BIT_ULL(i))
347			break;
348	}
349
350	ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, i,
351						    &sink_pad, &sink_stream);
352	if (ret)
353		return ret;
354
355	rp = media_pad_remote_pad_first(&sd->entity.pads[IPU7_CSI2_PAD_SINK]);
356	r_sd = media_entity_to_v4l2_subdev(rp->entity);
357
358	v4l2_subdev_disable_streams(r_sd, rp->index, BIT_ULL(sink_stream));
359
360	if (--csi2->stream_count)
361		return 0;
362
363	dev_dbg(&csi2->isys->adev->auxdev.dev,
364		"stream off CSI2-%u with %u lanes\n", csi2->port, csi2->nlanes);
365
366	ipu7_isys_csi2_disable_stream(csi2);
367
368	return 0;
369}
370
371static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = {
372	.get_fmt = v4l2_subdev_get_fmt,
373	.set_fmt = ipu7_isys_subdev_set_fmt,
374	.get_selection = ipu7_isys_csi2_get_sel,
375	.set_selection = ipu7_isys_csi2_set_sel,
376	.enum_mbus_code = ipu7_isys_subdev_enum_mbus_code,
377	.enable_streams = ipu7_isys_csi2_enable_streams,
378	.disable_streams = ipu7_isys_csi2_disable_streams,
379	.set_routing = ipu7_isys_subdev_set_routing,
380};
381
382static const struct v4l2_subdev_ops csi2_sd_ops = {
383	.core = &csi2_sd_core_ops,
384	.pad = &csi2_sd_pad_ops,
385};
386
387static const struct media_entity_operations csi2_entity_ops = {
388	.link_validate = v4l2_subdev_link_validate,
389	.has_pad_interdep = v4l2_subdev_has_pad_interdep,
390};
391
392void ipu7_isys_csi2_cleanup(struct ipu7_isys_csi2 *csi2)
393{
394	if (!csi2->isys)
395		return;
396
397	v4l2_device_unregister_subdev(&csi2->asd.sd);
398	v4l2_subdev_cleanup(&csi2->asd.sd);
399	ipu7_isys_subdev_cleanup(&csi2->asd);
400	csi2->isys = NULL;
401}
402
403int ipu7_isys_csi2_init(struct ipu7_isys_csi2 *csi2,
404			struct ipu7_isys *isys,
405			void __iomem *base, unsigned int index)
406{
407	struct device *dev = &isys->adev->auxdev.dev;
408	int ret;
409
410	csi2->isys = isys;
411	csi2->base = base;
412	csi2->port = index;
413
414	if (!is_ipu7(isys->adev->isp->hw_ver))
415		csi2->legacy_irq_mask = 0x7U << (index * 3U);
416	else
417		csi2->legacy_irq_mask = 0x3U << (index * 2U);
418
419	dev_dbg(dev, "csi-%d legacy irq mask = 0x%x\n", index,
420		csi2->legacy_irq_mask);
421
422	csi2->asd.sd.entity.ops = &csi2_entity_ops;
423	csi2->asd.isys = isys;
424
425	ret = ipu7_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0,
426				    IPU7_NR_OF_CSI2_SINK_PADS,
427				    IPU7_NR_OF_CSI2_SRC_PADS);
428	if (ret)
429		return ret;
430
431	csi2->asd.source = (int)index;
432	csi2->asd.supported_codes = csi2_supported_codes;
433	snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name),
434		 IPU_ISYS_ENTITY_PREFIX " CSI2 %u", index);
435	v4l2_set_subdevdata(&csi2->asd.sd, &csi2->asd);
436
437	ret = v4l2_subdev_init_finalize(&csi2->asd.sd);
438	if (ret) {
439		dev_err(dev, "failed to init v4l2 subdev (%d)\n", ret);
440		goto isys_subdev_cleanup;
441	}
442
443	ret = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2->asd.sd);
444	if (ret) {
445		dev_err(dev, "failed to register v4l2 subdev (%d)\n", ret);
446		goto v4l2_subdev_cleanup;
447	}
448
449	return 0;
450
451v4l2_subdev_cleanup:
452	v4l2_subdev_cleanup(&csi2->asd.sd);
453isys_subdev_cleanup:
454	ipu7_isys_subdev_cleanup(&csi2->asd);
455
456	return ret;
457}
458
459void ipu7_isys_csi2_sof_event_by_stream(struct ipu7_isys_stream *stream)
460{
461	struct ipu7_isys_csi2 *csi2 = ipu7_isys_subdev_to_csi2(stream->asd);
462	struct device *dev = &stream->isys->adev->auxdev.dev;
463	struct video_device *vdev = csi2->asd.sd.devnode;
464	struct v4l2_event ev = {
465		.type = V4L2_EVENT_FRAME_SYNC,
466	};
467
468	ev.id = stream->vc;
469	ev.u.frame_sync.frame_sequence = atomic_fetch_inc(&stream->sequence);
470	v4l2_event_queue(vdev, &ev);
471
472	dev_dbg(dev, "sof_event::csi2-%i sequence: %i, vc: %d\n",
473		csi2->port, ev.u.frame_sync.frame_sequence, stream->vc);
474}
475
476void ipu7_isys_csi2_eof_event_by_stream(struct ipu7_isys_stream *stream)
477{
478	struct ipu7_isys_csi2 *csi2 = ipu7_isys_subdev_to_csi2(stream->asd);
479	struct device *dev = &stream->isys->adev->auxdev.dev;
480	u32 frame_sequence = atomic_read(&stream->sequence);
481
482	dev_dbg(dev, "eof_event::csi2-%i sequence: %i\n",
483		csi2->port, frame_sequence);
484}
485
486int ipu7_isys_csi2_get_remote_desc(u32 source_stream,
487				   struct ipu7_isys_csi2 *csi2,
488				   struct media_entity *source_entity,
489				   struct v4l2_mbus_frame_desc_entry *entry,
490				   int *nr_queues)
491{
492	struct v4l2_mbus_frame_desc_entry *desc_entry = NULL;
493	struct device *dev = &csi2->isys->adev->auxdev.dev;
494	struct v4l2_mbus_frame_desc desc;
495	struct v4l2_subdev *source;
496	struct media_pad *pad;
497	unsigned int i;
498	int ret;
499
500	source = media_entity_to_v4l2_subdev(source_entity);
501	if (!source)
502		return -EPIPE;
503
504	pad = media_pad_remote_pad_first(&csi2->asd.pad[IPU7_CSI2_PAD_SINK]);
505	if (!pad)
506		return -EPIPE;
507
508	ret = v4l2_subdev_call(source, pad, get_frame_desc, pad->index, &desc);
509	if (ret)
510		return ret;
511
512	if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) {
513		dev_err(dev, "Unsupported frame descriptor type\n");
514		return -EINVAL;
515	}
516
517	for (i = 0; i < desc.num_entries; i++) {
518		if (source_stream == desc.entry[i].stream) {
519			desc_entry = &desc.entry[i];
520			break;
521		}
522	}
523
524	if (!desc_entry) {
525		dev_err(dev, "Failed to find stream %u from remote subdev\n",
526			source_stream);
527		return -EINVAL;
528	}
529
530	if (desc_entry->bus.csi2.vc >= IPU7_NR_OF_CSI2_VC) {
531		dev_err(dev, "invalid vc %d\n", desc_entry->bus.csi2.vc);
532		return -EINVAL;
533	}
534
535	*entry = *desc_entry;
536
537	for (i = 0; i < desc.num_entries; i++) {
538		if (desc_entry->bus.csi2.vc == desc.entry[i].bus.csi2.vc)
539			(*nr_queues)++;
540	}
541
542	return 0;
543}
~/ Read-only file preview.