xref: /linux/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c (revision 6edb685abb2af445773876a326292b989dcb3c9f)
1*6edb685aSTomi Valkeinen // SPDX-License-Identifier: GPL-2.0-only
2*6edb685aSTomi Valkeinen /*
3*6edb685aSTomi Valkeinen  * RP1 CSI-2 Driver
4*6edb685aSTomi Valkeinen  *
5*6edb685aSTomi Valkeinen  * Copyright (c) 2021-2024 Raspberry Pi Ltd.
6*6edb685aSTomi Valkeinen  * Copyright (c) 2023-2024 Ideas on Board Oy
7*6edb685aSTomi Valkeinen  */
8*6edb685aSTomi Valkeinen 
9*6edb685aSTomi Valkeinen #include <linux/delay.h>
10*6edb685aSTomi Valkeinen #include <linux/moduleparam.h>
11*6edb685aSTomi Valkeinen #include <linux/pm_runtime.h>
12*6edb685aSTomi Valkeinen #include <linux/seq_file.h>
13*6edb685aSTomi Valkeinen 
14*6edb685aSTomi Valkeinen #include <media/videobuf2-dma-contig.h>
15*6edb685aSTomi Valkeinen 
16*6edb685aSTomi Valkeinen #include "cfe.h"
17*6edb685aSTomi Valkeinen #include "csi2.h"
18*6edb685aSTomi Valkeinen 
19*6edb685aSTomi Valkeinen #include "cfe-trace.h"
20*6edb685aSTomi Valkeinen 
21*6edb685aSTomi Valkeinen static bool csi2_track_errors;
22*6edb685aSTomi Valkeinen module_param_named(track_csi2_errors, csi2_track_errors, bool, 0);
23*6edb685aSTomi Valkeinen MODULE_PARM_DESC(track_csi2_errors, "track csi-2 errors");
24*6edb685aSTomi Valkeinen 
25*6edb685aSTomi Valkeinen #define csi2_dbg(csi2, fmt, arg...) dev_dbg((csi2)->v4l2_dev->dev, fmt, ##arg)
26*6edb685aSTomi Valkeinen #define csi2_err(csi2, fmt, arg...) dev_err((csi2)->v4l2_dev->dev, fmt, ##arg)
27*6edb685aSTomi Valkeinen 
28*6edb685aSTomi Valkeinen /* CSI2-DMA registers */
29*6edb685aSTomi Valkeinen #define CSI2_STATUS		0x000
30*6edb685aSTomi Valkeinen #define CSI2_QOS		0x004
31*6edb685aSTomi Valkeinen #define CSI2_DISCARDS_OVERFLOW	0x008
32*6edb685aSTomi Valkeinen #define CSI2_DISCARDS_INACTIVE	0x00c
33*6edb685aSTomi Valkeinen #define CSI2_DISCARDS_UNMATCHED	0x010
34*6edb685aSTomi Valkeinen #define CSI2_DISCARDS_LEN_LIMIT	0x014
35*6edb685aSTomi Valkeinen 
36*6edb685aSTomi Valkeinen #define CSI2_DISCARDS_AMOUNT_SHIFT	0
37*6edb685aSTomi Valkeinen #define CSI2_DISCARDS_AMOUNT_MASK	GENMASK(23, 0)
38*6edb685aSTomi Valkeinen #define CSI2_DISCARDS_DT_SHIFT		24
39*6edb685aSTomi Valkeinen #define CSI2_DISCARDS_DT_MASK		GENMASK(29, 24)
40*6edb685aSTomi Valkeinen #define CSI2_DISCARDS_VC_SHIFT		30
41*6edb685aSTomi Valkeinen #define CSI2_DISCARDS_VC_MASK		GENMASK(31, 30)
42*6edb685aSTomi Valkeinen 
43*6edb685aSTomi Valkeinen #define CSI2_LLEV_PANICS	0x018
44*6edb685aSTomi Valkeinen #define CSI2_ULEV_PANICS	0x01c
45*6edb685aSTomi Valkeinen #define CSI2_IRQ_MASK		0x020
46*6edb685aSTomi Valkeinen #define CSI2_IRQ_MASK_IRQ_OVERFLOW		BIT(0)
47*6edb685aSTomi Valkeinen #define CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW	BIT(1)
48*6edb685aSTomi Valkeinen #define CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT	BIT(2)
49*6edb685aSTomi Valkeinen #define CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED	BIT(3)
50*6edb685aSTomi Valkeinen #define CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE	BIT(4)
51*6edb685aSTomi Valkeinen #define CSI2_IRQ_MASK_IRQ_ALL                                              \
52*6edb685aSTomi Valkeinen 	(CSI2_IRQ_MASK_IRQ_OVERFLOW | CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW | \
53*6edb685aSTomi Valkeinen 	 CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT |                          \
54*6edb685aSTomi Valkeinen 	 CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED |                             \
55*6edb685aSTomi Valkeinen 	 CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE)
56*6edb685aSTomi Valkeinen 
57*6edb685aSTomi Valkeinen #define CSI2_CTRL		0x024
58*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL(x)		((x) * 0x40 + 0x28)
59*6edb685aSTomi Valkeinen #define CSI2_CH_ADDR0(x)	((x) * 0x40 + 0x2c)
60*6edb685aSTomi Valkeinen #define CSI2_CH_ADDR1(x)	((x) * 0x40 + 0x3c)
61*6edb685aSTomi Valkeinen #define CSI2_CH_STRIDE(x)	((x) * 0x40 + 0x30)
62*6edb685aSTomi Valkeinen #define CSI2_CH_LENGTH(x)	((x) * 0x40 + 0x34)
63*6edb685aSTomi Valkeinen #define CSI2_CH_DEBUG(x)	((x) * 0x40 + 0x38)
64*6edb685aSTomi Valkeinen #define CSI2_CH_FRAME_SIZE(x)	((x) * 0x40 + 0x40)
65*6edb685aSTomi Valkeinen #define CSI2_CH_COMP_CTRL(x)	((x) * 0x40 + 0x44)
66*6edb685aSTomi Valkeinen #define CSI2_CH_FE_FRAME_ID(x)	((x) * 0x40 + 0x48)
67*6edb685aSTomi Valkeinen 
68*6edb685aSTomi Valkeinen /* CSI2_STATUS */
69*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_FS(x)			(BIT(0) << (x))
70*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_FE(x)			(BIT(4) << (x))
71*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_FE_ACK(x)		(BIT(8) << (x))
72*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_LE(x)			(BIT(12) << (x))
73*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_LE_ACK(x)		(BIT(16) << (x))
74*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_CH_MASK(x) \
75*6edb685aSTomi Valkeinen 	(CSI2_STATUS_IRQ_FS(x) | CSI2_STATUS_IRQ_FE(x) | \
76*6edb685aSTomi Valkeinen 	 CSI2_STATUS_IRQ_FE_ACK(x) | CSI2_STATUS_IRQ_LE(x) | \
77*6edb685aSTomi Valkeinen 	 CSI2_STATUS_IRQ_LE_ACK(x))
78*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_OVERFLOW		BIT(20)
79*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_DISCARD_OVERFLOW	BIT(21)
80*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_DISCARD_LEN_LIMIT	BIT(22)
81*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_DISCARD_UNMATCHED	BIT(23)
82*6edb685aSTomi Valkeinen #define CSI2_STATUS_IRQ_DISCARD_INACTIVE	BIT(24)
83*6edb685aSTomi Valkeinen 
84*6edb685aSTomi Valkeinen /* CSI2_CTRL */
85*6edb685aSTomi Valkeinen #define CSI2_CTRL_EOP_IS_EOL			BIT(0)
86*6edb685aSTomi Valkeinen 
87*6edb685aSTomi Valkeinen /* CSI2_CH_CTRL */
88*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_DMA_EN			BIT(0)
89*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_FORCE			BIT(3)
90*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_AUTO_ARM			BIT(4)
91*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_IRQ_EN_FS			BIT(13)
92*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_IRQ_EN_FE			BIT(14)
93*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_IRQ_EN_FE_ACK		BIT(15)
94*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_IRQ_EN_LE			BIT(16)
95*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_IRQ_EN_LE_ACK		BIT(17)
96*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_FLUSH_FE			BIT(28)
97*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_PACK_LINE			BIT(29)
98*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_PACK_BYTES			BIT(30)
99*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_CH_MODE_MASK		GENMASK(2, 1)
100*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_VC_MASK			GENMASK(6, 5)
101*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_DT_MASK			GENMASK(12, 7)
102*6edb685aSTomi Valkeinen #define CSI2_CH_CTRL_LC_MASK			GENMASK(27, 18)
103*6edb685aSTomi Valkeinen 
104*6edb685aSTomi Valkeinen /* CHx_COMPRESSION_CONTROL */
105*6edb685aSTomi Valkeinen #define CSI2_CH_COMP_CTRL_OFFSET_MASK		GENMASK(15, 0)
106*6edb685aSTomi Valkeinen #define CSI2_CH_COMP_CTRL_SHIFT_MASK		GENMASK(19, 16)
107*6edb685aSTomi Valkeinen #define CSI2_CH_COMP_CTRL_MODE_MASK		GENMASK(25, 24)
108*6edb685aSTomi Valkeinen 
109*6edb685aSTomi Valkeinen static inline u32 csi2_reg_read(struct csi2_device *csi2, u32 offset)
110*6edb685aSTomi Valkeinen {
111*6edb685aSTomi Valkeinen 	return readl(csi2->base + offset);
112*6edb685aSTomi Valkeinen }
113*6edb685aSTomi Valkeinen 
114*6edb685aSTomi Valkeinen static inline void csi2_reg_write(struct csi2_device *csi2, u32 offset, u32 val)
115*6edb685aSTomi Valkeinen {
116*6edb685aSTomi Valkeinen 	writel(val, csi2->base + offset);
117*6edb685aSTomi Valkeinen }
118*6edb685aSTomi Valkeinen 
119*6edb685aSTomi Valkeinen static inline void set_field(u32 *valp, u32 field, u32 mask)
120*6edb685aSTomi Valkeinen {
121*6edb685aSTomi Valkeinen 	u32 val = *valp;
122*6edb685aSTomi Valkeinen 
123*6edb685aSTomi Valkeinen 	val &= ~mask;
124*6edb685aSTomi Valkeinen 	val |= (field << __ffs(mask)) & mask;
125*6edb685aSTomi Valkeinen 	*valp = val;
126*6edb685aSTomi Valkeinen }
127*6edb685aSTomi Valkeinen 
128*6edb685aSTomi Valkeinen static int csi2_regs_show(struct seq_file *s, void *data)
129*6edb685aSTomi Valkeinen {
130*6edb685aSTomi Valkeinen 	struct csi2_device *csi2 = s->private;
131*6edb685aSTomi Valkeinen 	int ret;
132*6edb685aSTomi Valkeinen 
133*6edb685aSTomi Valkeinen 	ret = pm_runtime_resume_and_get(csi2->v4l2_dev->dev);
134*6edb685aSTomi Valkeinen 	if (ret)
135*6edb685aSTomi Valkeinen 		return ret;
136*6edb685aSTomi Valkeinen 
137*6edb685aSTomi Valkeinen #define DUMP(reg) seq_printf(s, #reg " \t0x%08x\n", csi2_reg_read(csi2, reg))
138*6edb685aSTomi Valkeinen #define DUMP_CH(idx, reg) seq_printf(s, #reg "(%u) \t0x%08x\n", idx, \
139*6edb685aSTomi Valkeinen 				     csi2_reg_read(csi2, reg(idx)))
140*6edb685aSTomi Valkeinen 
141*6edb685aSTomi Valkeinen 	DUMP(CSI2_STATUS);
142*6edb685aSTomi Valkeinen 	DUMP(CSI2_DISCARDS_OVERFLOW);
143*6edb685aSTomi Valkeinen 	DUMP(CSI2_DISCARDS_INACTIVE);
144*6edb685aSTomi Valkeinen 	DUMP(CSI2_DISCARDS_UNMATCHED);
145*6edb685aSTomi Valkeinen 	DUMP(CSI2_DISCARDS_LEN_LIMIT);
146*6edb685aSTomi Valkeinen 	DUMP(CSI2_LLEV_PANICS);
147*6edb685aSTomi Valkeinen 	DUMP(CSI2_ULEV_PANICS);
148*6edb685aSTomi Valkeinen 	DUMP(CSI2_IRQ_MASK);
149*6edb685aSTomi Valkeinen 	DUMP(CSI2_CTRL);
150*6edb685aSTomi Valkeinen 
151*6edb685aSTomi Valkeinen 	for (unsigned int i = 0; i < CSI2_NUM_CHANNELS; ++i) {
152*6edb685aSTomi Valkeinen 		DUMP_CH(i, CSI2_CH_CTRL);
153*6edb685aSTomi Valkeinen 		DUMP_CH(i, CSI2_CH_ADDR0);
154*6edb685aSTomi Valkeinen 		DUMP_CH(i, CSI2_CH_ADDR1);
155*6edb685aSTomi Valkeinen 		DUMP_CH(i, CSI2_CH_STRIDE);
156*6edb685aSTomi Valkeinen 		DUMP_CH(i, CSI2_CH_LENGTH);
157*6edb685aSTomi Valkeinen 		DUMP_CH(i, CSI2_CH_DEBUG);
158*6edb685aSTomi Valkeinen 		DUMP_CH(i, CSI2_CH_FRAME_SIZE);
159*6edb685aSTomi Valkeinen 		DUMP_CH(i, CSI2_CH_COMP_CTRL);
160*6edb685aSTomi Valkeinen 		DUMP_CH(i, CSI2_CH_FE_FRAME_ID);
161*6edb685aSTomi Valkeinen 	}
162*6edb685aSTomi Valkeinen 
163*6edb685aSTomi Valkeinen #undef DUMP
164*6edb685aSTomi Valkeinen #undef DUMP_CH
165*6edb685aSTomi Valkeinen 
166*6edb685aSTomi Valkeinen 	pm_runtime_put(csi2->v4l2_dev->dev);
167*6edb685aSTomi Valkeinen 
168*6edb685aSTomi Valkeinen 	return 0;
169*6edb685aSTomi Valkeinen }
170*6edb685aSTomi Valkeinen 
171*6edb685aSTomi Valkeinen DEFINE_SHOW_ATTRIBUTE(csi2_regs);
172*6edb685aSTomi Valkeinen 
173*6edb685aSTomi Valkeinen static int csi2_errors_show(struct seq_file *s, void *data)
174*6edb685aSTomi Valkeinen {
175*6edb685aSTomi Valkeinen 	struct csi2_device *csi2 = s->private;
176*6edb685aSTomi Valkeinen 	unsigned long flags;
177*6edb685aSTomi Valkeinen 	u32 discards_table[DISCARDS_TABLE_NUM_VCS][DISCARDS_TABLE_NUM_ENTRIES];
178*6edb685aSTomi Valkeinen 	u32 discards_dt_table[DISCARDS_TABLE_NUM_ENTRIES];
179*6edb685aSTomi Valkeinen 	u32 overflows;
180*6edb685aSTomi Valkeinen 
181*6edb685aSTomi Valkeinen 	spin_lock_irqsave(&csi2->errors_lock, flags);
182*6edb685aSTomi Valkeinen 
183*6edb685aSTomi Valkeinen 	memcpy(discards_table, csi2->discards_table, sizeof(discards_table));
184*6edb685aSTomi Valkeinen 	memcpy(discards_dt_table, csi2->discards_dt_table,
185*6edb685aSTomi Valkeinen 	       sizeof(discards_dt_table));
186*6edb685aSTomi Valkeinen 	overflows = csi2->overflows;
187*6edb685aSTomi Valkeinen 
188*6edb685aSTomi Valkeinen 	csi2->overflows = 0;
189*6edb685aSTomi Valkeinen 	memset(csi2->discards_table, 0, sizeof(discards_table));
190*6edb685aSTomi Valkeinen 	memset(csi2->discards_dt_table, 0, sizeof(discards_dt_table));
191*6edb685aSTomi Valkeinen 
192*6edb685aSTomi Valkeinen 	spin_unlock_irqrestore(&csi2->errors_lock, flags);
193*6edb685aSTomi Valkeinen 
194*6edb685aSTomi Valkeinen 	seq_printf(s, "Overflows %u\n", overflows);
195*6edb685aSTomi Valkeinen 	seq_puts(s, "Discards:\n");
196*6edb685aSTomi Valkeinen 	seq_puts(s, "VC            OVLF        LEN  UNMATCHED   INACTIVE\n");
197*6edb685aSTomi Valkeinen 
198*6edb685aSTomi Valkeinen 	for (unsigned int vc = 0; vc < DISCARDS_TABLE_NUM_VCS; ++vc) {
199*6edb685aSTomi Valkeinen 		seq_printf(s, "%u       %10u %10u %10u %10u\n", vc,
200*6edb685aSTomi Valkeinen 			   discards_table[vc][DISCARDS_TABLE_OVERFLOW],
201*6edb685aSTomi Valkeinen 			   discards_table[vc][DISCARDS_TABLE_LENGTH_LIMIT],
202*6edb685aSTomi Valkeinen 			   discards_table[vc][DISCARDS_TABLE_UNMATCHED],
203*6edb685aSTomi Valkeinen 			   discards_table[vc][DISCARDS_TABLE_INACTIVE]);
204*6edb685aSTomi Valkeinen 	}
205*6edb685aSTomi Valkeinen 
206*6edb685aSTomi Valkeinen 	seq_printf(s, "Last DT %10u %10u %10u %10u\n",
207*6edb685aSTomi Valkeinen 		   discards_dt_table[DISCARDS_TABLE_OVERFLOW],
208*6edb685aSTomi Valkeinen 		   discards_dt_table[DISCARDS_TABLE_LENGTH_LIMIT],
209*6edb685aSTomi Valkeinen 		   discards_dt_table[DISCARDS_TABLE_UNMATCHED],
210*6edb685aSTomi Valkeinen 		   discards_dt_table[DISCARDS_TABLE_INACTIVE]);
211*6edb685aSTomi Valkeinen 
212*6edb685aSTomi Valkeinen 	return 0;
213*6edb685aSTomi Valkeinen }
214*6edb685aSTomi Valkeinen 
215*6edb685aSTomi Valkeinen DEFINE_SHOW_ATTRIBUTE(csi2_errors);
216*6edb685aSTomi Valkeinen 
217*6edb685aSTomi Valkeinen static void csi2_isr_handle_errors(struct csi2_device *csi2, u32 status)
218*6edb685aSTomi Valkeinen {
219*6edb685aSTomi Valkeinen 	spin_lock(&csi2->errors_lock);
220*6edb685aSTomi Valkeinen 
221*6edb685aSTomi Valkeinen 	if (status & CSI2_STATUS_IRQ_OVERFLOW)
222*6edb685aSTomi Valkeinen 		csi2->overflows++;
223*6edb685aSTomi Valkeinen 
224*6edb685aSTomi Valkeinen 	for (unsigned int i = 0; i < DISCARDS_TABLE_NUM_ENTRIES; ++i) {
225*6edb685aSTomi Valkeinen 		static const u32 discard_bits[] = {
226*6edb685aSTomi Valkeinen 			CSI2_STATUS_IRQ_DISCARD_OVERFLOW,
227*6edb685aSTomi Valkeinen 			CSI2_STATUS_IRQ_DISCARD_LEN_LIMIT,
228*6edb685aSTomi Valkeinen 			CSI2_STATUS_IRQ_DISCARD_UNMATCHED,
229*6edb685aSTomi Valkeinen 			CSI2_STATUS_IRQ_DISCARD_INACTIVE,
230*6edb685aSTomi Valkeinen 		};
231*6edb685aSTomi Valkeinen 		static const u8 discard_regs[] = {
232*6edb685aSTomi Valkeinen 			CSI2_DISCARDS_OVERFLOW,
233*6edb685aSTomi Valkeinen 			CSI2_DISCARDS_LEN_LIMIT,
234*6edb685aSTomi Valkeinen 			CSI2_DISCARDS_UNMATCHED,
235*6edb685aSTomi Valkeinen 			CSI2_DISCARDS_INACTIVE,
236*6edb685aSTomi Valkeinen 		};
237*6edb685aSTomi Valkeinen 		u32 amount;
238*6edb685aSTomi Valkeinen 		u8 dt, vc;
239*6edb685aSTomi Valkeinen 		u32 v;
240*6edb685aSTomi Valkeinen 
241*6edb685aSTomi Valkeinen 		if (!(status & discard_bits[i]))
242*6edb685aSTomi Valkeinen 			continue;
243*6edb685aSTomi Valkeinen 
244*6edb685aSTomi Valkeinen 		v = csi2_reg_read(csi2, discard_regs[i]);
245*6edb685aSTomi Valkeinen 		csi2_reg_write(csi2, discard_regs[i], 0);
246*6edb685aSTomi Valkeinen 
247*6edb685aSTomi Valkeinen 		amount = (v & CSI2_DISCARDS_AMOUNT_MASK) >>
248*6edb685aSTomi Valkeinen 			 CSI2_DISCARDS_AMOUNT_SHIFT;
249*6edb685aSTomi Valkeinen 		dt = (v & CSI2_DISCARDS_DT_MASK) >> CSI2_DISCARDS_DT_SHIFT;
250*6edb685aSTomi Valkeinen 		vc = (v & CSI2_DISCARDS_VC_MASK) >> CSI2_DISCARDS_VC_SHIFT;
251*6edb685aSTomi Valkeinen 
252*6edb685aSTomi Valkeinen 		csi2->discards_table[vc][i] += amount;
253*6edb685aSTomi Valkeinen 		csi2->discards_dt_table[i] = dt;
254*6edb685aSTomi Valkeinen 	}
255*6edb685aSTomi Valkeinen 
256*6edb685aSTomi Valkeinen 	spin_unlock(&csi2->errors_lock);
257*6edb685aSTomi Valkeinen }
258*6edb685aSTomi Valkeinen 
259*6edb685aSTomi Valkeinen void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof)
260*6edb685aSTomi Valkeinen {
261*6edb685aSTomi Valkeinen 	u32 status;
262*6edb685aSTomi Valkeinen 
263*6edb685aSTomi Valkeinen 	status = csi2_reg_read(csi2, CSI2_STATUS);
264*6edb685aSTomi Valkeinen 
265*6edb685aSTomi Valkeinen 	/* Write value back to clear the interrupts */
266*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_STATUS, status);
267*6edb685aSTomi Valkeinen 
268*6edb685aSTomi Valkeinen 	for (unsigned int i = 0; i < CSI2_NUM_CHANNELS; i++) {
269*6edb685aSTomi Valkeinen 		u32 dbg;
270*6edb685aSTomi Valkeinen 
271*6edb685aSTomi Valkeinen 		if ((status & CSI2_STATUS_IRQ_CH_MASK(i)) == 0)
272*6edb685aSTomi Valkeinen 			continue;
273*6edb685aSTomi Valkeinen 
274*6edb685aSTomi Valkeinen 		dbg = csi2_reg_read(csi2, CSI2_CH_DEBUG(i));
275*6edb685aSTomi Valkeinen 
276*6edb685aSTomi Valkeinen 		trace_csi2_irq(i, status, dbg);
277*6edb685aSTomi Valkeinen 
278*6edb685aSTomi Valkeinen 		sof[i] = !!(status & CSI2_STATUS_IRQ_FS(i));
279*6edb685aSTomi Valkeinen 		eof[i] = !!(status & CSI2_STATUS_IRQ_FE_ACK(i));
280*6edb685aSTomi Valkeinen 	}
281*6edb685aSTomi Valkeinen 
282*6edb685aSTomi Valkeinen 	if (csi2_track_errors)
283*6edb685aSTomi Valkeinen 		csi2_isr_handle_errors(csi2, status);
284*6edb685aSTomi Valkeinen }
285*6edb685aSTomi Valkeinen 
286*6edb685aSTomi Valkeinen void csi2_set_buffer(struct csi2_device *csi2, unsigned int channel,
287*6edb685aSTomi Valkeinen 		     dma_addr_t dmaaddr, unsigned int stride, unsigned int size)
288*6edb685aSTomi Valkeinen {
289*6edb685aSTomi Valkeinen 	u64 addr = dmaaddr;
290*6edb685aSTomi Valkeinen 	/*
291*6edb685aSTomi Valkeinen 	 * ADDRESS0 must be written last as it triggers the double buffering
292*6edb685aSTomi Valkeinen 	 * mechanism for all buffer registers within the hardware.
293*6edb685aSTomi Valkeinen 	 */
294*6edb685aSTomi Valkeinen 	addr >>= 4;
295*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_LENGTH(channel), size >> 4);
296*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_STRIDE(channel), stride >> 4);
297*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_ADDR1(channel), addr >> 32);
298*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), addr & 0xffffffff);
299*6edb685aSTomi Valkeinen }
300*6edb685aSTomi Valkeinen 
301*6edb685aSTomi Valkeinen void csi2_set_compression(struct csi2_device *csi2, unsigned int channel,
302*6edb685aSTomi Valkeinen 			  enum csi2_compression_mode mode, unsigned int shift,
303*6edb685aSTomi Valkeinen 			  unsigned int offset)
304*6edb685aSTomi Valkeinen {
305*6edb685aSTomi Valkeinen 	u32 compression = 0;
306*6edb685aSTomi Valkeinen 
307*6edb685aSTomi Valkeinen 	set_field(&compression, CSI2_CH_COMP_CTRL_OFFSET_MASK, offset);
308*6edb685aSTomi Valkeinen 	set_field(&compression, CSI2_CH_COMP_CTRL_SHIFT_MASK, shift);
309*6edb685aSTomi Valkeinen 	set_field(&compression, CSI2_CH_COMP_CTRL_MODE_MASK, mode);
310*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_COMP_CTRL(channel), compression);
311*6edb685aSTomi Valkeinen }
312*6edb685aSTomi Valkeinen 
313*6edb685aSTomi Valkeinen void csi2_start_channel(struct csi2_device *csi2, unsigned int channel,
314*6edb685aSTomi Valkeinen 			enum csi2_mode mode, bool auto_arm, bool pack_bytes,
315*6edb685aSTomi Valkeinen 			unsigned int width, unsigned int height,
316*6edb685aSTomi Valkeinen 			u8 vc, u8 dt)
317*6edb685aSTomi Valkeinen {
318*6edb685aSTomi Valkeinen 	u32 ctrl;
319*6edb685aSTomi Valkeinen 
320*6edb685aSTomi Valkeinen 	csi2_dbg(csi2, "%s [%u]\n", __func__, channel);
321*6edb685aSTomi Valkeinen 
322*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_CTRL(channel), 0);
323*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_DEBUG(channel), 0);
324*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_STATUS, CSI2_STATUS_IRQ_CH_MASK(channel));
325*6edb685aSTomi Valkeinen 
326*6edb685aSTomi Valkeinen 	/* Enable channel and FS/FE interrupts. */
327*6edb685aSTomi Valkeinen 	ctrl = CSI2_CH_CTRL_DMA_EN | CSI2_CH_CTRL_IRQ_EN_FS |
328*6edb685aSTomi Valkeinen 	       CSI2_CH_CTRL_IRQ_EN_FE_ACK | CSI2_CH_CTRL_PACK_LINE;
329*6edb685aSTomi Valkeinen 	/* PACK_BYTES ensures no striding for embedded data. */
330*6edb685aSTomi Valkeinen 	if (pack_bytes)
331*6edb685aSTomi Valkeinen 		ctrl |= CSI2_CH_CTRL_PACK_BYTES;
332*6edb685aSTomi Valkeinen 
333*6edb685aSTomi Valkeinen 	if (auto_arm)
334*6edb685aSTomi Valkeinen 		ctrl |= CSI2_CH_CTRL_AUTO_ARM;
335*6edb685aSTomi Valkeinen 
336*6edb685aSTomi Valkeinen 	if (width && height) {
337*6edb685aSTomi Valkeinen 		set_field(&ctrl, mode, CSI2_CH_CTRL_CH_MODE_MASK);
338*6edb685aSTomi Valkeinen 		csi2_reg_write(csi2, CSI2_CH_FRAME_SIZE(channel),
339*6edb685aSTomi Valkeinen 			       (height << 16) | width);
340*6edb685aSTomi Valkeinen 	} else {
341*6edb685aSTomi Valkeinen 		set_field(&ctrl, 0x0, CSI2_CH_CTRL_CH_MODE_MASK);
342*6edb685aSTomi Valkeinen 		csi2_reg_write(csi2, CSI2_CH_FRAME_SIZE(channel), 0);
343*6edb685aSTomi Valkeinen 	}
344*6edb685aSTomi Valkeinen 
345*6edb685aSTomi Valkeinen 	set_field(&ctrl, vc, CSI2_CH_CTRL_VC_MASK);
346*6edb685aSTomi Valkeinen 	set_field(&ctrl, dt, CSI2_CH_CTRL_DT_MASK);
347*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_CTRL(channel), ctrl);
348*6edb685aSTomi Valkeinen 	csi2->num_lines[channel] = height;
349*6edb685aSTomi Valkeinen }
350*6edb685aSTomi Valkeinen 
351*6edb685aSTomi Valkeinen void csi2_stop_channel(struct csi2_device *csi2, unsigned int channel)
352*6edb685aSTomi Valkeinen {
353*6edb685aSTomi Valkeinen 	csi2_dbg(csi2, "%s [%u]\n", __func__, channel);
354*6edb685aSTomi Valkeinen 
355*6edb685aSTomi Valkeinen 	/* Channel disable.  Use FORCE to allow stopping mid-frame. */
356*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_CTRL(channel), CSI2_CH_CTRL_FORCE);
357*6edb685aSTomi Valkeinen 	/* Latch the above change by writing to the ADDR0 register. */
358*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), 0);
359*6edb685aSTomi Valkeinen 	/* Write this again, the HW needs it! */
360*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), 0);
361*6edb685aSTomi Valkeinen }
362*6edb685aSTomi Valkeinen 
363*6edb685aSTomi Valkeinen void csi2_open_rx(struct csi2_device *csi2)
364*6edb685aSTomi Valkeinen {
365*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_IRQ_MASK,
366*6edb685aSTomi Valkeinen 		       csi2_track_errors ? CSI2_IRQ_MASK_IRQ_ALL : 0);
367*6edb685aSTomi Valkeinen 
368*6edb685aSTomi Valkeinen 	dphy_start(&csi2->dphy);
369*6edb685aSTomi Valkeinen 
370*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_CTRL, CSI2_CTRL_EOP_IS_EOL);
371*6edb685aSTomi Valkeinen }
372*6edb685aSTomi Valkeinen 
373*6edb685aSTomi Valkeinen void csi2_close_rx(struct csi2_device *csi2)
374*6edb685aSTomi Valkeinen {
375*6edb685aSTomi Valkeinen 	dphy_stop(&csi2->dphy);
376*6edb685aSTomi Valkeinen 
377*6edb685aSTomi Valkeinen 	csi2_reg_write(csi2, CSI2_IRQ_MASK, 0);
378*6edb685aSTomi Valkeinen }
379*6edb685aSTomi Valkeinen 
380*6edb685aSTomi Valkeinen static int csi2_init_state(struct v4l2_subdev *sd,
381*6edb685aSTomi Valkeinen 			   struct v4l2_subdev_state *state)
382*6edb685aSTomi Valkeinen {
383*6edb685aSTomi Valkeinen 	struct v4l2_subdev_route routes[] = { {
384*6edb685aSTomi Valkeinen 		.sink_pad = CSI2_PAD_SINK,
385*6edb685aSTomi Valkeinen 		.sink_stream = 0,
386*6edb685aSTomi Valkeinen 		.source_pad = CSI2_PAD_FIRST_SOURCE,
387*6edb685aSTomi Valkeinen 		.source_stream = 0,
388*6edb685aSTomi Valkeinen 		.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
389*6edb685aSTomi Valkeinen 	} };
390*6edb685aSTomi Valkeinen 
391*6edb685aSTomi Valkeinen 	struct v4l2_subdev_krouting routing = {
392*6edb685aSTomi Valkeinen 		.num_routes = ARRAY_SIZE(routes),
393*6edb685aSTomi Valkeinen 		.routes = routes,
394*6edb685aSTomi Valkeinen 	};
395*6edb685aSTomi Valkeinen 
396*6edb685aSTomi Valkeinen 	int ret;
397*6edb685aSTomi Valkeinen 
398*6edb685aSTomi Valkeinen 	ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
399*6edb685aSTomi Valkeinen 					       &cfe_default_format);
400*6edb685aSTomi Valkeinen 	if (ret)
401*6edb685aSTomi Valkeinen 		return ret;
402*6edb685aSTomi Valkeinen 
403*6edb685aSTomi Valkeinen 	return 0;
404*6edb685aSTomi Valkeinen }
405*6edb685aSTomi Valkeinen 
406*6edb685aSTomi Valkeinen static int csi2_pad_set_fmt(struct v4l2_subdev *sd,
407*6edb685aSTomi Valkeinen 			    struct v4l2_subdev_state *state,
408*6edb685aSTomi Valkeinen 			    struct v4l2_subdev_format *format)
409*6edb685aSTomi Valkeinen {
410*6edb685aSTomi Valkeinen 	if (format->pad == CSI2_PAD_SINK) {
411*6edb685aSTomi Valkeinen 		/* Store the sink format and propagate it to the source. */
412*6edb685aSTomi Valkeinen 
413*6edb685aSTomi Valkeinen 		const struct cfe_fmt *cfe_fmt;
414*6edb685aSTomi Valkeinen 
415*6edb685aSTomi Valkeinen 		cfe_fmt = find_format_by_code(format->format.code);
416*6edb685aSTomi Valkeinen 		if (!cfe_fmt) {
417*6edb685aSTomi Valkeinen 			cfe_fmt = find_format_by_code(MEDIA_BUS_FMT_SRGGB10_1X10);
418*6edb685aSTomi Valkeinen 			format->format.code = cfe_fmt->code;
419*6edb685aSTomi Valkeinen 		}
420*6edb685aSTomi Valkeinen 
421*6edb685aSTomi Valkeinen 		struct v4l2_mbus_framefmt *fmt;
422*6edb685aSTomi Valkeinen 
423*6edb685aSTomi Valkeinen 		fmt = v4l2_subdev_state_get_format(state, format->pad,
424*6edb685aSTomi Valkeinen 						   format->stream);
425*6edb685aSTomi Valkeinen 		if (!fmt)
426*6edb685aSTomi Valkeinen 			return -EINVAL;
427*6edb685aSTomi Valkeinen 
428*6edb685aSTomi Valkeinen 		*fmt = format->format;
429*6edb685aSTomi Valkeinen 
430*6edb685aSTomi Valkeinen 		fmt = v4l2_subdev_state_get_opposite_stream_format(state,
431*6edb685aSTomi Valkeinen 								   format->pad,
432*6edb685aSTomi Valkeinen 								   format->stream);
433*6edb685aSTomi Valkeinen 		if (!fmt)
434*6edb685aSTomi Valkeinen 			return -EINVAL;
435*6edb685aSTomi Valkeinen 
436*6edb685aSTomi Valkeinen 		format->format.field = V4L2_FIELD_NONE;
437*6edb685aSTomi Valkeinen 
438*6edb685aSTomi Valkeinen 		*fmt = format->format;
439*6edb685aSTomi Valkeinen 	} else {
440*6edb685aSTomi Valkeinen 		/* Only allow changing the source pad mbus code. */
441*6edb685aSTomi Valkeinen 
442*6edb685aSTomi Valkeinen 		struct v4l2_mbus_framefmt *sink_fmt, *source_fmt;
443*6edb685aSTomi Valkeinen 		u32 sink_code;
444*6edb685aSTomi Valkeinen 		u32 code;
445*6edb685aSTomi Valkeinen 
446*6edb685aSTomi Valkeinen 		sink_fmt = v4l2_subdev_state_get_opposite_stream_format(state,
447*6edb685aSTomi Valkeinen 									format->pad,
448*6edb685aSTomi Valkeinen 									format->stream);
449*6edb685aSTomi Valkeinen 		if (!sink_fmt)
450*6edb685aSTomi Valkeinen 			return -EINVAL;
451*6edb685aSTomi Valkeinen 
452*6edb685aSTomi Valkeinen 		source_fmt = v4l2_subdev_state_get_format(state, format->pad,
453*6edb685aSTomi Valkeinen 							  format->stream);
454*6edb685aSTomi Valkeinen 		if (!source_fmt)
455*6edb685aSTomi Valkeinen 			return -EINVAL;
456*6edb685aSTomi Valkeinen 
457*6edb685aSTomi Valkeinen 		sink_code = sink_fmt->code;
458*6edb685aSTomi Valkeinen 		code = format->format.code;
459*6edb685aSTomi Valkeinen 
460*6edb685aSTomi Valkeinen 		/*
461*6edb685aSTomi Valkeinen 		 * Only allow changing the mbus code to:
462*6edb685aSTomi Valkeinen 		 * - The sink's mbus code
463*6edb685aSTomi Valkeinen 		 * - The 16-bit version of the sink's mbus code
464*6edb685aSTomi Valkeinen 		 * - The compressed version of the sink's mbus code
465*6edb685aSTomi Valkeinen 		 */
466*6edb685aSTomi Valkeinen 		if (code == sink_code ||
467*6edb685aSTomi Valkeinen 		    code == cfe_find_16bit_code(sink_code) ||
468*6edb685aSTomi Valkeinen 		    code == cfe_find_compressed_code(sink_code))
469*6edb685aSTomi Valkeinen 			source_fmt->code = code;
470*6edb685aSTomi Valkeinen 
471*6edb685aSTomi Valkeinen 		format->format.code = source_fmt->code;
472*6edb685aSTomi Valkeinen 	}
473*6edb685aSTomi Valkeinen 
474*6edb685aSTomi Valkeinen 	return 0;
475*6edb685aSTomi Valkeinen }
476*6edb685aSTomi Valkeinen 
477*6edb685aSTomi Valkeinen static int csi2_set_routing(struct v4l2_subdev *sd,
478*6edb685aSTomi Valkeinen 			    struct v4l2_subdev_state *state,
479*6edb685aSTomi Valkeinen 			    enum v4l2_subdev_format_whence which,
480*6edb685aSTomi Valkeinen 			    struct v4l2_subdev_krouting *routing)
481*6edb685aSTomi Valkeinen {
482*6edb685aSTomi Valkeinen 	int ret;
483*6edb685aSTomi Valkeinen 
484*6edb685aSTomi Valkeinen 	ret = v4l2_subdev_routing_validate(sd, routing,
485*6edb685aSTomi Valkeinen 					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 |
486*6edb685aSTomi Valkeinen 					   V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING);
487*6edb685aSTomi Valkeinen 	if (ret)
488*6edb685aSTomi Valkeinen 		return ret;
489*6edb685aSTomi Valkeinen 
490*6edb685aSTomi Valkeinen 	/* Only stream ID 0 allowed on source pads */
491*6edb685aSTomi Valkeinen 	for (unsigned int i = 0; i < routing->num_routes; ++i) {
492*6edb685aSTomi Valkeinen 		const struct v4l2_subdev_route *route = &routing->routes[i];
493*6edb685aSTomi Valkeinen 
494*6edb685aSTomi Valkeinen 		if (route->source_stream != 0)
495*6edb685aSTomi Valkeinen 			return -EINVAL;
496*6edb685aSTomi Valkeinen 	}
497*6edb685aSTomi Valkeinen 
498*6edb685aSTomi Valkeinen 	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing,
499*6edb685aSTomi Valkeinen 					       &cfe_default_format);
500*6edb685aSTomi Valkeinen 	if (ret)
501*6edb685aSTomi Valkeinen 		return ret;
502*6edb685aSTomi Valkeinen 
503*6edb685aSTomi Valkeinen 	return 0;
504*6edb685aSTomi Valkeinen }
505*6edb685aSTomi Valkeinen 
506*6edb685aSTomi Valkeinen static const struct v4l2_subdev_pad_ops csi2_subdev_pad_ops = {
507*6edb685aSTomi Valkeinen 	.get_fmt = v4l2_subdev_get_fmt,
508*6edb685aSTomi Valkeinen 	.set_fmt = csi2_pad_set_fmt,
509*6edb685aSTomi Valkeinen 	.set_routing = csi2_set_routing,
510*6edb685aSTomi Valkeinen 	.link_validate = v4l2_subdev_link_validate_default,
511*6edb685aSTomi Valkeinen };
512*6edb685aSTomi Valkeinen 
513*6edb685aSTomi Valkeinen static const struct media_entity_operations csi2_entity_ops = {
514*6edb685aSTomi Valkeinen 	.link_validate = v4l2_subdev_link_validate,
515*6edb685aSTomi Valkeinen 	.has_pad_interdep = v4l2_subdev_has_pad_interdep,
516*6edb685aSTomi Valkeinen };
517*6edb685aSTomi Valkeinen 
518*6edb685aSTomi Valkeinen static const struct v4l2_subdev_ops csi2_subdev_ops = {
519*6edb685aSTomi Valkeinen 	.pad = &csi2_subdev_pad_ops,
520*6edb685aSTomi Valkeinen };
521*6edb685aSTomi Valkeinen 
522*6edb685aSTomi Valkeinen static const struct v4l2_subdev_internal_ops csi2_internal_ops = {
523*6edb685aSTomi Valkeinen 	.init_state = csi2_init_state,
524*6edb685aSTomi Valkeinen };
525*6edb685aSTomi Valkeinen 
526*6edb685aSTomi Valkeinen int csi2_init(struct csi2_device *csi2, struct dentry *debugfs)
527*6edb685aSTomi Valkeinen {
528*6edb685aSTomi Valkeinen 	unsigned int ret;
529*6edb685aSTomi Valkeinen 
530*6edb685aSTomi Valkeinen 	spin_lock_init(&csi2->errors_lock);
531*6edb685aSTomi Valkeinen 
532*6edb685aSTomi Valkeinen 	csi2->dphy.dev = csi2->v4l2_dev->dev;
533*6edb685aSTomi Valkeinen 	dphy_probe(&csi2->dphy);
534*6edb685aSTomi Valkeinen 
535*6edb685aSTomi Valkeinen 	debugfs_create_file("csi2_regs", 0440, debugfs, csi2, &csi2_regs_fops);
536*6edb685aSTomi Valkeinen 
537*6edb685aSTomi Valkeinen 	if (csi2_track_errors)
538*6edb685aSTomi Valkeinen 		debugfs_create_file("csi2_errors", 0440, debugfs, csi2,
539*6edb685aSTomi Valkeinen 				    &csi2_errors_fops);
540*6edb685aSTomi Valkeinen 
541*6edb685aSTomi Valkeinen 	csi2->pad[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
542*6edb685aSTomi Valkeinen 
543*6edb685aSTomi Valkeinen 	for (unsigned int i = CSI2_PAD_FIRST_SOURCE;
544*6edb685aSTomi Valkeinen 	     i < CSI2_PAD_FIRST_SOURCE + CSI2_PAD_NUM_SOURCES; i++)
545*6edb685aSTomi Valkeinen 		csi2->pad[i].flags = MEDIA_PAD_FL_SOURCE;
546*6edb685aSTomi Valkeinen 
547*6edb685aSTomi Valkeinen 	ret = media_entity_pads_init(&csi2->sd.entity, ARRAY_SIZE(csi2->pad),
548*6edb685aSTomi Valkeinen 				     csi2->pad);
549*6edb685aSTomi Valkeinen 	if (ret)
550*6edb685aSTomi Valkeinen 		return ret;
551*6edb685aSTomi Valkeinen 
552*6edb685aSTomi Valkeinen 	/* Initialize subdev */
553*6edb685aSTomi Valkeinen 	v4l2_subdev_init(&csi2->sd, &csi2_subdev_ops);
554*6edb685aSTomi Valkeinen 	csi2->sd.internal_ops = &csi2_internal_ops;
555*6edb685aSTomi Valkeinen 	csi2->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
556*6edb685aSTomi Valkeinen 	csi2->sd.entity.ops = &csi2_entity_ops;
557*6edb685aSTomi Valkeinen 	csi2->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
558*6edb685aSTomi Valkeinen 	csi2->sd.owner = THIS_MODULE;
559*6edb685aSTomi Valkeinen 	snprintf(csi2->sd.name, sizeof(csi2->sd.name), "csi2");
560*6edb685aSTomi Valkeinen 
561*6edb685aSTomi Valkeinen 	ret = v4l2_subdev_init_finalize(&csi2->sd);
562*6edb685aSTomi Valkeinen 	if (ret)
563*6edb685aSTomi Valkeinen 		goto err_entity_cleanup;
564*6edb685aSTomi Valkeinen 
565*6edb685aSTomi Valkeinen 	ret = v4l2_device_register_subdev(csi2->v4l2_dev, &csi2->sd);
566*6edb685aSTomi Valkeinen 	if (ret) {
567*6edb685aSTomi Valkeinen 		csi2_err(csi2, "Failed register csi2 subdev (%d)\n", ret);
568*6edb685aSTomi Valkeinen 		goto err_subdev_cleanup;
569*6edb685aSTomi Valkeinen 	}
570*6edb685aSTomi Valkeinen 
571*6edb685aSTomi Valkeinen 	return 0;
572*6edb685aSTomi Valkeinen 
573*6edb685aSTomi Valkeinen err_subdev_cleanup:
574*6edb685aSTomi Valkeinen 	v4l2_subdev_cleanup(&csi2->sd);
575*6edb685aSTomi Valkeinen err_entity_cleanup:
576*6edb685aSTomi Valkeinen 	media_entity_cleanup(&csi2->sd.entity);
577*6edb685aSTomi Valkeinen 
578*6edb685aSTomi Valkeinen 	return ret;
579*6edb685aSTomi Valkeinen }
580*6edb685aSTomi Valkeinen 
581*6edb685aSTomi Valkeinen void csi2_uninit(struct csi2_device *csi2)
582*6edb685aSTomi Valkeinen {
583*6edb685aSTomi Valkeinen 	v4l2_device_unregister_subdev(&csi2->sd);
584*6edb685aSTomi Valkeinen 	v4l2_subdev_cleanup(&csi2->sd);
585*6edb685aSTomi Valkeinen 	media_entity_cleanup(&csi2->sd.entity);
586*6edb685aSTomi Valkeinen }
587