1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * NXP i.MX8MQ SoC series MIPI-CSI2 receiver driver |
4 | * |
5 | * Copyright (C) 2021 Purism SPC |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/interconnect.h> |
13 | #include <linux/interrupt.h> |
14 | #include <linux/io.h> |
15 | #include <linux/kernel.h> |
16 | #include <linux/mfd/syscon.h> |
17 | #include <linux/module.h> |
18 | #include <linux/mutex.h> |
19 | #include <linux/of.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/pm_runtime.h> |
22 | #include <linux/regmap.h> |
23 | #include <linux/regulator/consumer.h> |
24 | #include <linux/reset.h> |
25 | #include <linux/spinlock.h> |
26 | |
27 | #include <media/v4l2-common.h> |
28 | #include <media/v4l2-device.h> |
29 | #include <media/v4l2-fwnode.h> |
30 | #include <media/v4l2-mc.h> |
31 | #include <media/v4l2-subdev.h> |
32 | |
33 | #define MIPI_CSI2_DRIVER_NAME "imx8mq-mipi-csi2" |
34 | #define MIPI_CSI2_SUBDEV_NAME MIPI_CSI2_DRIVER_NAME |
35 | |
36 | #define MIPI_CSI2_PAD_SINK 0 |
37 | #define MIPI_CSI2_PAD_SOURCE 1 |
38 | #define MIPI_CSI2_PADS_NUM 2 |
39 | |
40 | #define MIPI_CSI2_DEF_PIX_WIDTH 640 |
41 | #define MIPI_CSI2_DEF_PIX_HEIGHT 480 |
42 | |
43 | /* Register map definition */ |
44 | |
45 | /* i.MX8MQ CSI-2 controller CSR */ |
46 | #define CSI2RX_CFG_NUM_LANES 0x100 |
47 | #define CSI2RX_CFG_DISABLE_DATA_LANES 0x104 |
48 | #define CSI2RX_BIT_ERR 0x108 |
49 | #define CSI2RX_IRQ_STATUS 0x10c |
50 | #define CSI2RX_IRQ_MASK 0x110 |
51 | #define CSI2RX_IRQ_MASK_ALL 0x1ff |
52 | #define CSI2RX_IRQ_MASK_ULPS_STATUS_CHANGE 0x8 |
53 | #define CSI2RX_ULPS_STATUS 0x114 |
54 | #define CSI2RX_PPI_ERRSOT_HS 0x118 |
55 | #define CSI2RX_PPI_ERRSOTSYNC_HS 0x11c |
56 | #define CSI2RX_PPI_ERRESC 0x120 |
57 | #define CSI2RX_PPI_ERRSYNCESC 0x124 |
58 | #define CSI2RX_PPI_ERRCONTROL 0x128 |
59 | #define CSI2RX_CFG_DISABLE_PAYLOAD_0 0x12c |
60 | #define CSI2RX_CFG_VID_VC_IGNORE 0x180 |
61 | #define CSI2RX_CFG_VID_VC 0x184 |
62 | #define CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL 0x188 |
63 | #define CSI2RX_CFG_DISABLE_PAYLOAD_1 0x130 |
64 | |
65 | enum { |
66 | ST_POWERED = 1, |
67 | ST_STREAMING = 2, |
68 | ST_SUSPENDED = 4, |
69 | }; |
70 | |
71 | enum imx8mq_mipi_csi_clk { |
72 | CSI2_CLK_CORE, |
73 | CSI2_CLK_ESC, |
74 | CSI2_CLK_UI, |
75 | CSI2_NUM_CLKS, |
76 | }; |
77 | |
78 | static const char * const imx8mq_mipi_csi_clk_id[CSI2_NUM_CLKS] = { |
79 | [CSI2_CLK_CORE] = "core" , |
80 | [CSI2_CLK_ESC] = "esc" , |
81 | [CSI2_CLK_UI] = "ui" , |
82 | }; |
83 | |
84 | #define CSI2_NUM_CLKS ARRAY_SIZE(imx8mq_mipi_csi_clk_id) |
85 | |
86 | #define GPR_CSI2_1_RX_ENABLE BIT(13) |
87 | #define GPR_CSI2_1_VID_INTFC_ENB BIT(12) |
88 | #define GPR_CSI2_1_HSEL BIT(10) |
89 | #define GPR_CSI2_1_CONT_CLK_MODE BIT(8) |
90 | #define GPR_CSI2_1_S_PRG_RXHS_SETTLE(x) (((x) & 0x3f) << 2) |
91 | |
92 | /* |
93 | * The send level configures the number of entries that must accumulate in |
94 | * the Pixel FIFO before the data will be transferred to the video output. |
95 | * The exact value needed for this configuration is dependent on the rate at |
96 | * which the sensor transfers data to the CSI-2 Controller and the user |
97 | * video clock. |
98 | * |
99 | * The calculation is the classical rate-in rate-out type of problem: If the |
100 | * video bandwidth is 10% faster than the incoming mipi data and the video |
101 | * line length is 500 pixels, then the fifo should be allowed to fill |
102 | * 10% of the line length or 50 pixels. If the gap data is ok, then the level |
103 | * can be set to 16 and ignored. |
104 | */ |
105 | #define CSI2RX_SEND_LEVEL 64 |
106 | |
107 | struct csi_state { |
108 | struct device *dev; |
109 | void __iomem *regs; |
110 | struct clk_bulk_data clks[CSI2_NUM_CLKS]; |
111 | struct reset_control *rst; |
112 | struct regulator *mipi_phy_regulator; |
113 | |
114 | struct v4l2_subdev sd; |
115 | struct media_pad pads[MIPI_CSI2_PADS_NUM]; |
116 | struct v4l2_async_notifier notifier; |
117 | struct v4l2_subdev *src_sd; |
118 | |
119 | struct v4l2_mbus_config_mipi_csi2 bus; |
120 | |
121 | struct mutex lock; /* Protect state */ |
122 | u32 state; |
123 | |
124 | struct regmap *phy_gpr; |
125 | u8 phy_gpr_reg; |
126 | |
127 | struct icc_path *icc_path; |
128 | s32 icc_path_bw; |
129 | }; |
130 | |
131 | /* ----------------------------------------------------------------------------- |
132 | * Format helpers |
133 | */ |
134 | |
135 | struct csi2_pix_format { |
136 | u32 code; |
137 | u8 width; |
138 | }; |
139 | |
140 | static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = { |
141 | /* RAW (Bayer and greyscale) formats. */ |
142 | { |
143 | .code = MEDIA_BUS_FMT_SBGGR8_1X8, |
144 | .width = 8, |
145 | }, { |
146 | .code = MEDIA_BUS_FMT_SGBRG8_1X8, |
147 | .width = 8, |
148 | }, { |
149 | .code = MEDIA_BUS_FMT_SGRBG8_1X8, |
150 | .width = 8, |
151 | }, { |
152 | .code = MEDIA_BUS_FMT_SRGGB8_1X8, |
153 | .width = 8, |
154 | }, { |
155 | .code = MEDIA_BUS_FMT_Y8_1X8, |
156 | .width = 8, |
157 | }, { |
158 | .code = MEDIA_BUS_FMT_SBGGR10_1X10, |
159 | .width = 10, |
160 | }, { |
161 | .code = MEDIA_BUS_FMT_SGBRG10_1X10, |
162 | .width = 10, |
163 | }, { |
164 | .code = MEDIA_BUS_FMT_SGRBG10_1X10, |
165 | .width = 10, |
166 | }, { |
167 | .code = MEDIA_BUS_FMT_SRGGB10_1X10, |
168 | .width = 10, |
169 | }, { |
170 | .code = MEDIA_BUS_FMT_Y10_1X10, |
171 | .width = 10, |
172 | }, { |
173 | .code = MEDIA_BUS_FMT_SBGGR12_1X12, |
174 | .width = 12, |
175 | }, { |
176 | .code = MEDIA_BUS_FMT_SGBRG12_1X12, |
177 | .width = 12, |
178 | }, { |
179 | .code = MEDIA_BUS_FMT_SGRBG12_1X12, |
180 | .width = 12, |
181 | }, { |
182 | .code = MEDIA_BUS_FMT_SRGGB12_1X12, |
183 | .width = 12, |
184 | }, { |
185 | .code = MEDIA_BUS_FMT_Y12_1X12, |
186 | .width = 12, |
187 | }, { |
188 | .code = MEDIA_BUS_FMT_SBGGR14_1X14, |
189 | .width = 14, |
190 | }, { |
191 | .code = MEDIA_BUS_FMT_SGBRG14_1X14, |
192 | .width = 14, |
193 | }, { |
194 | .code = MEDIA_BUS_FMT_SGRBG14_1X14, |
195 | .width = 14, |
196 | }, { |
197 | .code = MEDIA_BUS_FMT_SRGGB14_1X14, |
198 | .width = 14, |
199 | }, |
200 | /* YUV formats */ |
201 | { |
202 | .code = MEDIA_BUS_FMT_YUYV8_1X16, |
203 | .width = 16, |
204 | }, { |
205 | .code = MEDIA_BUS_FMT_UYVY8_1X16, |
206 | .width = 16, |
207 | } |
208 | }; |
209 | |
210 | static const struct csi2_pix_format *find_csi2_format(u32 code) |
211 | { |
212 | unsigned int i; |
213 | |
214 | for (i = 0; i < ARRAY_SIZE(imx8mq_mipi_csi_formats); i++) |
215 | if (code == imx8mq_mipi_csi_formats[i].code) |
216 | return &imx8mq_mipi_csi_formats[i]; |
217 | return NULL; |
218 | } |
219 | |
220 | /* ----------------------------------------------------------------------------- |
221 | * Hardware configuration |
222 | */ |
223 | |
224 | static inline void imx8mq_mipi_csi_write(struct csi_state *state, u32 reg, u32 val) |
225 | { |
226 | writel(val, addr: state->regs + reg); |
227 | } |
228 | |
229 | static int imx8mq_mipi_csi_sw_reset(struct csi_state *state) |
230 | { |
231 | int ret; |
232 | |
233 | /* |
234 | * these are most likely self-clearing reset bits. to make it |
235 | * more clear, the reset-imx7 driver should implement the |
236 | * .reset() operation. |
237 | */ |
238 | ret = reset_control_assert(rstc: state->rst); |
239 | if (ret < 0) { |
240 | dev_err(state->dev, "Failed to assert resets: %d\n" , ret); |
241 | return ret; |
242 | } |
243 | |
244 | return 0; |
245 | } |
246 | |
247 | static void imx8mq_mipi_csi_set_params(struct csi_state *state) |
248 | { |
249 | int lanes = state->bus.num_data_lanes; |
250 | |
251 | imx8mq_mipi_csi_write(state, CSI2RX_CFG_NUM_LANES, val: lanes - 1); |
252 | imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, |
253 | val: (0xf << lanes) & 0xf); |
254 | imx8mq_mipi_csi_write(state, CSI2RX_IRQ_MASK, CSI2RX_IRQ_MASK_ALL); |
255 | /* |
256 | * 0x180 bit 0 controls the Virtual Channel behaviour: when set the |
257 | * interface ignores the Virtual Channel (VC) field in received packets; |
258 | * when cleared it causes the interface to only accept packets whose VC |
259 | * matches the value to which VC is set at offset 0x184. |
260 | */ |
261 | imx8mq_mipi_csi_write(state, CSI2RX_CFG_VID_VC_IGNORE, val: 1); |
262 | imx8mq_mipi_csi_write(state, CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL, |
263 | CSI2RX_SEND_LEVEL); |
264 | } |
265 | |
266 | static int imx8mq_mipi_csi_clk_enable(struct csi_state *state) |
267 | { |
268 | return clk_bulk_prepare_enable(CSI2_NUM_CLKS, clks: state->clks); |
269 | } |
270 | |
271 | static void imx8mq_mipi_csi_clk_disable(struct csi_state *state) |
272 | { |
273 | clk_bulk_disable_unprepare(CSI2_NUM_CLKS, clks: state->clks); |
274 | } |
275 | |
276 | static int imx8mq_mipi_csi_clk_get(struct csi_state *state) |
277 | { |
278 | unsigned int i; |
279 | |
280 | for (i = 0; i < CSI2_NUM_CLKS; i++) |
281 | state->clks[i].id = imx8mq_mipi_csi_clk_id[i]; |
282 | |
283 | return devm_clk_bulk_get(dev: state->dev, CSI2_NUM_CLKS, clks: state->clks); |
284 | } |
285 | |
286 | static int imx8mq_mipi_csi_calc_hs_settle(struct csi_state *state, |
287 | struct v4l2_subdev_state *sd_state, |
288 | u32 *hs_settle) |
289 | { |
290 | s64 link_freq; |
291 | u32 lane_rate; |
292 | unsigned long esc_clk_rate; |
293 | u32 min_ths_settle, max_ths_settle, ths_settle_ns, esc_clk_period_ns; |
294 | const struct v4l2_mbus_framefmt *fmt; |
295 | const struct csi2_pix_format *csi2_fmt; |
296 | |
297 | /* Calculate the line rate from the pixel rate. */ |
298 | |
299 | fmt = v4l2_subdev_state_get_format(sd_state, MIPI_CSI2_PAD_SINK); |
300 | csi2_fmt = find_csi2_format(code: fmt->code); |
301 | |
302 | link_freq = v4l2_get_link_freq(handler: state->src_sd->ctrl_handler, |
303 | mul: csi2_fmt->width, |
304 | div: state->bus.num_data_lanes * 2); |
305 | if (link_freq < 0) { |
306 | dev_err(state->dev, "Unable to obtain link frequency: %d\n" , |
307 | (int)link_freq); |
308 | return link_freq; |
309 | } |
310 | |
311 | lane_rate = link_freq * 2; |
312 | if (lane_rate < 80000000 || lane_rate > 1500000000) { |
313 | dev_dbg(state->dev, "Out-of-bound lane rate %u\n" , lane_rate); |
314 | return -EINVAL; |
315 | } |
316 | |
317 | /* |
318 | * The D-PHY specification requires Ths-settle to be in the range |
319 | * 85ns + 6*UI to 140ns + 10*UI, with the unit interval UI being half |
320 | * the clock period. |
321 | * |
322 | * The Ths-settle value is expressed in the hardware as a multiple of |
323 | * the Esc clock period: |
324 | * |
325 | * Ths-settle = (PRG_RXHS_SETTLE + 1) * Tperiod of RxClkInEsc |
326 | * |
327 | * Due to the one cycle inaccuracy introduced by rounding, the |
328 | * documentation recommends picking a value away from the boundaries. |
329 | * Let's pick the average. |
330 | */ |
331 | esc_clk_rate = clk_get_rate(clk: state->clks[CSI2_CLK_ESC].clk); |
332 | if (!esc_clk_rate) { |
333 | dev_err(state->dev, "Could not get esc clock rate.\n" ); |
334 | return -EINVAL; |
335 | } |
336 | |
337 | dev_dbg(state->dev, "esc clk rate: %lu\n" , esc_clk_rate); |
338 | esc_clk_period_ns = 1000000000 / esc_clk_rate; |
339 | |
340 | min_ths_settle = 85 + 6 * 1000000 / (lane_rate / 1000); |
341 | max_ths_settle = 140 + 10 * 1000000 / (lane_rate / 1000); |
342 | ths_settle_ns = (min_ths_settle + max_ths_settle) / 2; |
343 | |
344 | *hs_settle = ths_settle_ns / esc_clk_period_ns - 1; |
345 | |
346 | dev_dbg(state->dev, "lane rate %u Ths_settle %u hs_settle %u\n" , |
347 | lane_rate, ths_settle_ns, *hs_settle); |
348 | |
349 | return 0; |
350 | } |
351 | |
352 | static int imx8mq_mipi_csi_start_stream(struct csi_state *state, |
353 | struct v4l2_subdev_state *sd_state) |
354 | { |
355 | int ret; |
356 | u32 hs_settle = 0; |
357 | |
358 | ret = imx8mq_mipi_csi_sw_reset(state); |
359 | if (ret) |
360 | return ret; |
361 | |
362 | imx8mq_mipi_csi_set_params(state); |
363 | ret = imx8mq_mipi_csi_calc_hs_settle(state, sd_state, hs_settle: &hs_settle); |
364 | if (ret) |
365 | return ret; |
366 | |
367 | regmap_update_bits(map: state->phy_gpr, |
368 | reg: state->phy_gpr_reg, |
369 | mask: 0x3fff, |
370 | GPR_CSI2_1_RX_ENABLE | |
371 | GPR_CSI2_1_VID_INTFC_ENB | |
372 | GPR_CSI2_1_HSEL | |
373 | GPR_CSI2_1_CONT_CLK_MODE | |
374 | GPR_CSI2_1_S_PRG_RXHS_SETTLE(hs_settle)); |
375 | |
376 | return 0; |
377 | } |
378 | |
379 | static void imx8mq_mipi_csi_stop_stream(struct csi_state *state) |
380 | { |
381 | imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, val: 0xf); |
382 | } |
383 | |
384 | /* ----------------------------------------------------------------------------- |
385 | * V4L2 subdev operations |
386 | */ |
387 | |
388 | static struct csi_state *mipi_sd_to_csi2_state(struct v4l2_subdev *sdev) |
389 | { |
390 | return container_of(sdev, struct csi_state, sd); |
391 | } |
392 | |
393 | static int imx8mq_mipi_csi_s_stream(struct v4l2_subdev *sd, int enable) |
394 | { |
395 | struct csi_state *state = mipi_sd_to_csi2_state(sdev: sd); |
396 | struct v4l2_subdev_state *sd_state; |
397 | int ret = 0; |
398 | |
399 | if (enable) { |
400 | ret = pm_runtime_resume_and_get(dev: state->dev); |
401 | if (ret < 0) |
402 | return ret; |
403 | } |
404 | |
405 | mutex_lock(&state->lock); |
406 | |
407 | if (enable) { |
408 | if (state->state & ST_SUSPENDED) { |
409 | ret = -EBUSY; |
410 | goto unlock; |
411 | } |
412 | |
413 | sd_state = v4l2_subdev_lock_and_get_active_state(sd); |
414 | ret = imx8mq_mipi_csi_start_stream(state, sd_state); |
415 | v4l2_subdev_unlock_state(state: sd_state); |
416 | |
417 | if (ret < 0) |
418 | goto unlock; |
419 | |
420 | ret = v4l2_subdev_call(state->src_sd, video, s_stream, 1); |
421 | if (ret < 0) |
422 | goto unlock; |
423 | |
424 | state->state |= ST_STREAMING; |
425 | } else { |
426 | v4l2_subdev_call(state->src_sd, video, s_stream, 0); |
427 | imx8mq_mipi_csi_stop_stream(state); |
428 | state->state &= ~ST_STREAMING; |
429 | } |
430 | |
431 | unlock: |
432 | mutex_unlock(lock: &state->lock); |
433 | |
434 | if (!enable || ret < 0) |
435 | pm_runtime_put(dev: state->dev); |
436 | |
437 | return ret; |
438 | } |
439 | |
440 | static int imx8mq_mipi_csi_init_state(struct v4l2_subdev *sd, |
441 | struct v4l2_subdev_state *sd_state) |
442 | { |
443 | struct v4l2_mbus_framefmt *fmt_sink; |
444 | struct v4l2_mbus_framefmt *fmt_source; |
445 | |
446 | fmt_sink = v4l2_subdev_state_get_format(sd_state, MIPI_CSI2_PAD_SINK); |
447 | fmt_source = v4l2_subdev_state_get_format(sd_state, |
448 | MIPI_CSI2_PAD_SOURCE); |
449 | |
450 | fmt_sink->code = MEDIA_BUS_FMT_SGBRG10_1X10; |
451 | fmt_sink->width = MIPI_CSI2_DEF_PIX_WIDTH; |
452 | fmt_sink->height = MIPI_CSI2_DEF_PIX_HEIGHT; |
453 | fmt_sink->field = V4L2_FIELD_NONE; |
454 | |
455 | fmt_sink->colorspace = V4L2_COLORSPACE_RAW; |
456 | fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace); |
457 | fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace); |
458 | fmt_sink->quantization = |
459 | V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace, |
460 | fmt_sink->ycbcr_enc); |
461 | |
462 | *fmt_source = *fmt_sink; |
463 | |
464 | return 0; |
465 | } |
466 | |
467 | static int imx8mq_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd, |
468 | struct v4l2_subdev_state *sd_state, |
469 | struct v4l2_subdev_mbus_code_enum *code) |
470 | { |
471 | /* |
472 | * We can't transcode in any way, the source format is identical |
473 | * to the sink format. |
474 | */ |
475 | if (code->pad == MIPI_CSI2_PAD_SOURCE) { |
476 | struct v4l2_mbus_framefmt *fmt; |
477 | |
478 | if (code->index > 0) |
479 | return -EINVAL; |
480 | |
481 | fmt = v4l2_subdev_state_get_format(sd_state, code->pad); |
482 | code->code = fmt->code; |
483 | return 0; |
484 | } |
485 | |
486 | if (code->pad != MIPI_CSI2_PAD_SINK) |
487 | return -EINVAL; |
488 | |
489 | if (code->index >= ARRAY_SIZE(imx8mq_mipi_csi_formats)) |
490 | return -EINVAL; |
491 | |
492 | code->code = imx8mq_mipi_csi_formats[code->index].code; |
493 | |
494 | return 0; |
495 | } |
496 | |
497 | static int imx8mq_mipi_csi_set_fmt(struct v4l2_subdev *sd, |
498 | struct v4l2_subdev_state *sd_state, |
499 | struct v4l2_subdev_format *sdformat) |
500 | { |
501 | const struct csi2_pix_format *csi2_fmt; |
502 | struct v4l2_mbus_framefmt *fmt; |
503 | |
504 | /* |
505 | * The device can't transcode in any way, the source format can't be |
506 | * modified. |
507 | */ |
508 | if (sdformat->pad == MIPI_CSI2_PAD_SOURCE) |
509 | return v4l2_subdev_get_fmt(sd, state: sd_state, format: sdformat); |
510 | |
511 | if (sdformat->pad != MIPI_CSI2_PAD_SINK) |
512 | return -EINVAL; |
513 | |
514 | csi2_fmt = find_csi2_format(code: sdformat->format.code); |
515 | if (!csi2_fmt) |
516 | csi2_fmt = &imx8mq_mipi_csi_formats[0]; |
517 | |
518 | fmt = v4l2_subdev_state_get_format(sd_state, sdformat->pad); |
519 | |
520 | fmt->code = csi2_fmt->code; |
521 | fmt->width = sdformat->format.width; |
522 | fmt->height = sdformat->format.height; |
523 | |
524 | sdformat->format = *fmt; |
525 | |
526 | /* Propagate the format from sink to source. */ |
527 | fmt = v4l2_subdev_state_get_format(sd_state, MIPI_CSI2_PAD_SOURCE); |
528 | *fmt = sdformat->format; |
529 | |
530 | return 0; |
531 | } |
532 | |
533 | static const struct v4l2_subdev_video_ops imx8mq_mipi_csi_video_ops = { |
534 | .s_stream = imx8mq_mipi_csi_s_stream, |
535 | }; |
536 | |
537 | static const struct v4l2_subdev_pad_ops imx8mq_mipi_csi_pad_ops = { |
538 | .enum_mbus_code = imx8mq_mipi_csi_enum_mbus_code, |
539 | .get_fmt = v4l2_subdev_get_fmt, |
540 | .set_fmt = imx8mq_mipi_csi_set_fmt, |
541 | }; |
542 | |
543 | static const struct v4l2_subdev_ops imx8mq_mipi_csi_subdev_ops = { |
544 | .video = &imx8mq_mipi_csi_video_ops, |
545 | .pad = &imx8mq_mipi_csi_pad_ops, |
546 | }; |
547 | |
548 | static const struct v4l2_subdev_internal_ops imx8mq_mipi_csi_internal_ops = { |
549 | .init_state = imx8mq_mipi_csi_init_state, |
550 | }; |
551 | |
552 | /* ----------------------------------------------------------------------------- |
553 | * Media entity operations |
554 | */ |
555 | |
556 | static const struct media_entity_operations imx8mq_mipi_csi_entity_ops = { |
557 | .link_validate = v4l2_subdev_link_validate, |
558 | .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, |
559 | }; |
560 | |
561 | /* ----------------------------------------------------------------------------- |
562 | * Async subdev notifier |
563 | */ |
564 | |
565 | static struct csi_state * |
566 | mipi_notifier_to_csi2_state(struct v4l2_async_notifier *n) |
567 | { |
568 | return container_of(n, struct csi_state, notifier); |
569 | } |
570 | |
571 | static int imx8mq_mipi_csi_notify_bound(struct v4l2_async_notifier *notifier, |
572 | struct v4l2_subdev *sd, |
573 | struct v4l2_async_connection *asd) |
574 | { |
575 | struct csi_state *state = mipi_notifier_to_csi2_state(n: notifier); |
576 | struct media_pad *sink = &state->sd.entity.pads[MIPI_CSI2_PAD_SINK]; |
577 | |
578 | state->src_sd = sd; |
579 | |
580 | return v4l2_create_fwnode_links_to_pad(src_sd: sd, sink, MEDIA_LNK_FL_ENABLED | |
581 | MEDIA_LNK_FL_IMMUTABLE); |
582 | } |
583 | |
584 | static const struct v4l2_async_notifier_operations imx8mq_mipi_csi_notify_ops = { |
585 | .bound = imx8mq_mipi_csi_notify_bound, |
586 | }; |
587 | |
588 | static int imx8mq_mipi_csi_async_register(struct csi_state *state) |
589 | { |
590 | struct v4l2_fwnode_endpoint vep = { |
591 | .bus_type = V4L2_MBUS_CSI2_DPHY, |
592 | }; |
593 | struct v4l2_async_connection *asd; |
594 | struct fwnode_handle *ep; |
595 | unsigned int i; |
596 | int ret; |
597 | |
598 | v4l2_async_subdev_nf_init(notifier: &state->notifier, sd: &state->sd); |
599 | |
600 | ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(state->dev), port: 0, endpoint: 0, |
601 | FWNODE_GRAPH_ENDPOINT_NEXT); |
602 | if (!ep) |
603 | return -ENOTCONN; |
604 | |
605 | ret = v4l2_fwnode_endpoint_parse(fwnode: ep, vep: &vep); |
606 | if (ret) |
607 | goto err_parse; |
608 | |
609 | for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) { |
610 | if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) { |
611 | dev_err(state->dev, |
612 | "data lanes reordering is not supported" ); |
613 | ret = -EINVAL; |
614 | goto err_parse; |
615 | } |
616 | } |
617 | |
618 | state->bus = vep.bus.mipi_csi2; |
619 | |
620 | dev_dbg(state->dev, "data lanes: %d flags: 0x%08x\n" , |
621 | state->bus.num_data_lanes, |
622 | state->bus.flags); |
623 | |
624 | asd = v4l2_async_nf_add_fwnode_remote(&state->notifier, ep, |
625 | struct v4l2_async_connection); |
626 | if (IS_ERR(ptr: asd)) { |
627 | ret = PTR_ERR(ptr: asd); |
628 | goto err_parse; |
629 | } |
630 | |
631 | fwnode_handle_put(fwnode: ep); |
632 | |
633 | state->notifier.ops = &imx8mq_mipi_csi_notify_ops; |
634 | |
635 | ret = v4l2_async_nf_register(notifier: &state->notifier); |
636 | if (ret) |
637 | return ret; |
638 | |
639 | return v4l2_async_register_subdev(sd: &state->sd); |
640 | |
641 | err_parse: |
642 | fwnode_handle_put(fwnode: ep); |
643 | |
644 | return ret; |
645 | } |
646 | |
647 | /* ----------------------------------------------------------------------------- |
648 | * Suspend/resume |
649 | */ |
650 | |
651 | static void imx8mq_mipi_csi_pm_suspend(struct device *dev) |
652 | { |
653 | struct v4l2_subdev *sd = dev_get_drvdata(dev); |
654 | struct csi_state *state = mipi_sd_to_csi2_state(sdev: sd); |
655 | |
656 | mutex_lock(&state->lock); |
657 | |
658 | if (state->state & ST_POWERED) { |
659 | imx8mq_mipi_csi_stop_stream(state); |
660 | imx8mq_mipi_csi_clk_disable(state); |
661 | state->state &= ~ST_POWERED; |
662 | } |
663 | |
664 | mutex_unlock(lock: &state->lock); |
665 | } |
666 | |
667 | static int imx8mq_mipi_csi_pm_resume(struct device *dev) |
668 | { |
669 | struct v4l2_subdev *sd = dev_get_drvdata(dev); |
670 | struct csi_state *state = mipi_sd_to_csi2_state(sdev: sd); |
671 | struct v4l2_subdev_state *sd_state; |
672 | int ret = 0; |
673 | |
674 | mutex_lock(&state->lock); |
675 | |
676 | if (!(state->state & ST_POWERED)) { |
677 | state->state |= ST_POWERED; |
678 | ret = imx8mq_mipi_csi_clk_enable(state); |
679 | } |
680 | if (state->state & ST_STREAMING) { |
681 | sd_state = v4l2_subdev_lock_and_get_active_state(sd); |
682 | ret = imx8mq_mipi_csi_start_stream(state, sd_state); |
683 | v4l2_subdev_unlock_state(state: sd_state); |
684 | if (ret) |
685 | goto unlock; |
686 | } |
687 | |
688 | state->state &= ~ST_SUSPENDED; |
689 | |
690 | unlock: |
691 | mutex_unlock(lock: &state->lock); |
692 | |
693 | return ret ? -EAGAIN : 0; |
694 | } |
695 | |
696 | static int __maybe_unused imx8mq_mipi_csi_suspend(struct device *dev) |
697 | { |
698 | struct v4l2_subdev *sd = dev_get_drvdata(dev); |
699 | struct csi_state *state = mipi_sd_to_csi2_state(sdev: sd); |
700 | |
701 | imx8mq_mipi_csi_pm_suspend(dev); |
702 | |
703 | state->state |= ST_SUSPENDED; |
704 | |
705 | return 0; |
706 | } |
707 | |
708 | static int __maybe_unused imx8mq_mipi_csi_resume(struct device *dev) |
709 | { |
710 | struct v4l2_subdev *sd = dev_get_drvdata(dev); |
711 | struct csi_state *state = mipi_sd_to_csi2_state(sdev: sd); |
712 | |
713 | if (!(state->state & ST_SUSPENDED)) |
714 | return 0; |
715 | |
716 | return imx8mq_mipi_csi_pm_resume(dev); |
717 | } |
718 | |
719 | static int __maybe_unused imx8mq_mipi_csi_runtime_suspend(struct device *dev) |
720 | { |
721 | struct v4l2_subdev *sd = dev_get_drvdata(dev); |
722 | struct csi_state *state = mipi_sd_to_csi2_state(sdev: sd); |
723 | int ret; |
724 | |
725 | imx8mq_mipi_csi_pm_suspend(dev); |
726 | |
727 | ret = icc_set_bw(path: state->icc_path, avg_bw: 0, peak_bw: 0); |
728 | if (ret) |
729 | dev_err(dev, "icc_set_bw failed with %d\n" , ret); |
730 | |
731 | return ret; |
732 | } |
733 | |
734 | static int __maybe_unused imx8mq_mipi_csi_runtime_resume(struct device *dev) |
735 | { |
736 | struct v4l2_subdev *sd = dev_get_drvdata(dev); |
737 | struct csi_state *state = mipi_sd_to_csi2_state(sdev: sd); |
738 | int ret; |
739 | |
740 | ret = icc_set_bw(path: state->icc_path, avg_bw: 0, peak_bw: state->icc_path_bw); |
741 | if (ret) { |
742 | dev_err(dev, "icc_set_bw failed with %d\n" , ret); |
743 | return ret; |
744 | } |
745 | |
746 | return imx8mq_mipi_csi_pm_resume(dev); |
747 | } |
748 | |
749 | static const struct dev_pm_ops imx8mq_mipi_csi_pm_ops = { |
750 | SET_RUNTIME_PM_OPS(imx8mq_mipi_csi_runtime_suspend, |
751 | imx8mq_mipi_csi_runtime_resume, |
752 | NULL) |
753 | SET_SYSTEM_SLEEP_PM_OPS(imx8mq_mipi_csi_suspend, imx8mq_mipi_csi_resume) |
754 | }; |
755 | |
756 | /* ----------------------------------------------------------------------------- |
757 | * Probe/remove & platform driver |
758 | */ |
759 | |
760 | static int imx8mq_mipi_csi_subdev_init(struct csi_state *state) |
761 | { |
762 | struct v4l2_subdev *sd = &state->sd; |
763 | int ret; |
764 | |
765 | v4l2_subdev_init(sd, ops: &imx8mq_mipi_csi_subdev_ops); |
766 | sd->internal_ops = &imx8mq_mipi_csi_internal_ops; |
767 | sd->owner = THIS_MODULE; |
768 | snprintf(buf: sd->name, size: sizeof(sd->name), fmt: "%s %s" , |
769 | MIPI_CSI2_SUBDEV_NAME, dev_name(dev: state->dev)); |
770 | |
771 | sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; |
772 | |
773 | sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; |
774 | sd->entity.ops = &imx8mq_mipi_csi_entity_ops; |
775 | |
776 | sd->dev = state->dev; |
777 | |
778 | state->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
779 | | MEDIA_PAD_FL_MUST_CONNECT; |
780 | state->pads[MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE |
781 | | MEDIA_PAD_FL_MUST_CONNECT; |
782 | ret = media_entity_pads_init(entity: &sd->entity, MIPI_CSI2_PADS_NUM, |
783 | pads: state->pads); |
784 | if (ret) |
785 | return ret; |
786 | |
787 | ret = v4l2_subdev_init_finalize(sd); |
788 | if (ret) { |
789 | media_entity_cleanup(entity: &sd->entity); |
790 | return ret; |
791 | } |
792 | |
793 | return 0; |
794 | } |
795 | |
796 | static void imx8mq_mipi_csi_release_icc(struct platform_device *pdev) |
797 | { |
798 | struct v4l2_subdev *sd = dev_get_drvdata(dev: &pdev->dev); |
799 | struct csi_state *state = mipi_sd_to_csi2_state(sdev: sd); |
800 | |
801 | icc_put(path: state->icc_path); |
802 | } |
803 | |
804 | static int imx8mq_mipi_csi_init_icc(struct platform_device *pdev) |
805 | { |
806 | struct v4l2_subdev *sd = dev_get_drvdata(dev: &pdev->dev); |
807 | struct csi_state *state = mipi_sd_to_csi2_state(sdev: sd); |
808 | |
809 | /* Optional interconnect request */ |
810 | state->icc_path = of_icc_get(dev: &pdev->dev, name: "dram" ); |
811 | if (IS_ERR_OR_NULL(ptr: state->icc_path)) |
812 | return PTR_ERR_OR_ZERO(ptr: state->icc_path); |
813 | |
814 | state->icc_path_bw = MBps_to_icc(700); |
815 | |
816 | return 0; |
817 | } |
818 | |
819 | static int imx8mq_mipi_csi_parse_dt(struct csi_state *state) |
820 | { |
821 | struct device *dev = state->dev; |
822 | struct device_node *np = state->dev->of_node; |
823 | struct device_node *node; |
824 | phandle ph; |
825 | u32 out_val[2]; |
826 | int ret = 0; |
827 | |
828 | state->rst = devm_reset_control_array_get_exclusive(dev); |
829 | if (IS_ERR(ptr: state->rst)) { |
830 | dev_err(dev, "Failed to get reset: %pe\n" , state->rst); |
831 | return PTR_ERR(ptr: state->rst); |
832 | } |
833 | |
834 | ret = of_property_read_u32_array(np, propname: "fsl,mipi-phy-gpr" , out_values: out_val, |
835 | ARRAY_SIZE(out_val)); |
836 | if (ret) { |
837 | dev_err(dev, "no fsl,mipi-phy-gpr property found: %d\n" , ret); |
838 | return ret; |
839 | } |
840 | |
841 | ph = *out_val; |
842 | |
843 | node = of_find_node_by_phandle(handle: ph); |
844 | if (!node) { |
845 | dev_err(dev, "Error finding node by phandle\n" ); |
846 | return -ENODEV; |
847 | } |
848 | state->phy_gpr = syscon_node_to_regmap(np: node); |
849 | of_node_put(node); |
850 | if (IS_ERR(ptr: state->phy_gpr)) { |
851 | dev_err(dev, "failed to get gpr regmap: %pe\n" , state->phy_gpr); |
852 | return PTR_ERR(ptr: state->phy_gpr); |
853 | } |
854 | |
855 | state->phy_gpr_reg = out_val[1]; |
856 | dev_dbg(dev, "phy gpr register set to 0x%x\n" , state->phy_gpr_reg); |
857 | |
858 | return ret; |
859 | } |
860 | |
861 | static int imx8mq_mipi_csi_probe(struct platform_device *pdev) |
862 | { |
863 | struct device *dev = &pdev->dev; |
864 | struct csi_state *state; |
865 | int ret; |
866 | |
867 | state = devm_kzalloc(dev, size: sizeof(*state), GFP_KERNEL); |
868 | if (!state) |
869 | return -ENOMEM; |
870 | |
871 | state->dev = dev; |
872 | |
873 | ret = imx8mq_mipi_csi_parse_dt(state); |
874 | if (ret < 0) { |
875 | dev_err(dev, "Failed to parse device tree: %d\n" , ret); |
876 | return ret; |
877 | } |
878 | |
879 | /* Acquire resources. */ |
880 | state->regs = devm_platform_ioremap_resource(pdev, index: 0); |
881 | if (IS_ERR(ptr: state->regs)) |
882 | return PTR_ERR(ptr: state->regs); |
883 | |
884 | ret = imx8mq_mipi_csi_clk_get(state); |
885 | if (ret < 0) |
886 | return ret; |
887 | |
888 | platform_set_drvdata(pdev, data: &state->sd); |
889 | |
890 | mutex_init(&state->lock); |
891 | |
892 | ret = imx8mq_mipi_csi_subdev_init(state); |
893 | if (ret < 0) |
894 | goto mutex; |
895 | |
896 | ret = imx8mq_mipi_csi_init_icc(pdev); |
897 | if (ret) |
898 | goto mutex; |
899 | |
900 | /* Enable runtime PM. */ |
901 | pm_runtime_enable(dev); |
902 | if (!pm_runtime_enabled(dev)) { |
903 | ret = imx8mq_mipi_csi_runtime_resume(dev); |
904 | if (ret < 0) |
905 | goto icc; |
906 | } |
907 | |
908 | ret = imx8mq_mipi_csi_async_register(state); |
909 | if (ret < 0) |
910 | goto cleanup; |
911 | |
912 | return 0; |
913 | |
914 | cleanup: |
915 | pm_runtime_disable(dev: &pdev->dev); |
916 | imx8mq_mipi_csi_runtime_suspend(dev: &pdev->dev); |
917 | |
918 | media_entity_cleanup(entity: &state->sd.entity); |
919 | v4l2_subdev_cleanup(sd: &state->sd); |
920 | v4l2_async_nf_unregister(notifier: &state->notifier); |
921 | v4l2_async_nf_cleanup(notifier: &state->notifier); |
922 | v4l2_async_unregister_subdev(sd: &state->sd); |
923 | icc: |
924 | imx8mq_mipi_csi_release_icc(pdev); |
925 | mutex: |
926 | mutex_destroy(lock: &state->lock); |
927 | |
928 | return ret; |
929 | } |
930 | |
931 | static void imx8mq_mipi_csi_remove(struct platform_device *pdev) |
932 | { |
933 | struct v4l2_subdev *sd = platform_get_drvdata(pdev); |
934 | struct csi_state *state = mipi_sd_to_csi2_state(sdev: sd); |
935 | |
936 | v4l2_async_nf_unregister(notifier: &state->notifier); |
937 | v4l2_async_nf_cleanup(notifier: &state->notifier); |
938 | v4l2_async_unregister_subdev(sd: &state->sd); |
939 | |
940 | pm_runtime_disable(dev: &pdev->dev); |
941 | imx8mq_mipi_csi_runtime_suspend(dev: &pdev->dev); |
942 | media_entity_cleanup(entity: &state->sd.entity); |
943 | v4l2_subdev_cleanup(sd: &state->sd); |
944 | mutex_destroy(lock: &state->lock); |
945 | pm_runtime_set_suspended(dev: &pdev->dev); |
946 | imx8mq_mipi_csi_release_icc(pdev); |
947 | } |
948 | |
949 | static const struct of_device_id imx8mq_mipi_csi_of_match[] = { |
950 | { .compatible = "fsl,imx8mq-mipi-csi2" , }, |
951 | { /* sentinel */ }, |
952 | }; |
953 | MODULE_DEVICE_TABLE(of, imx8mq_mipi_csi_of_match); |
954 | |
955 | static struct platform_driver imx8mq_mipi_csi_driver = { |
956 | .probe = imx8mq_mipi_csi_probe, |
957 | .remove_new = imx8mq_mipi_csi_remove, |
958 | .driver = { |
959 | .of_match_table = imx8mq_mipi_csi_of_match, |
960 | .name = MIPI_CSI2_DRIVER_NAME, |
961 | .pm = &imx8mq_mipi_csi_pm_ops, |
962 | }, |
963 | }; |
964 | |
965 | module_platform_driver(imx8mq_mipi_csi_driver); |
966 | |
967 | MODULE_DESCRIPTION("i.MX8MQ MIPI CSI-2 receiver driver" ); |
968 | MODULE_AUTHOR("Martin Kepplinger <martin.kepplinger@puri.sm>" ); |
969 | MODULE_LICENSE("GPL v2" ); |
970 | MODULE_ALIAS("platform:imx8mq-mipi-csi2" ); |
971 | |