Commit | Line | Data |
---|---|---|
af54b4f4 PK |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Copyright 2020-2022 Bootlin | |
4 | * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> | |
5 | */ | |
6 | ||
7 | #include <linux/clk.h> | |
8 | #include <linux/module.h> | |
9 | #include <linux/of.h> | |
10 | #include <linux/of_device.h> | |
11 | #include <linux/phy/phy.h> | |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/pm_runtime.h> | |
14 | #include <linux/regmap.h> | |
15 | #include <linux/reset.h> | |
16 | #include <media/mipi-csi2.h> | |
17 | #include <media/v4l2-ctrls.h> | |
18 | #include <media/v4l2-device.h> | |
19 | #include <media/v4l2-fwnode.h> | |
20 | ||
21 | #include "sun6i_mipi_csi2.h" | |
22 | #include "sun6i_mipi_csi2_reg.h" | |
23 | ||
24 | /* Format */ | |
25 | ||
26 | static const struct sun6i_mipi_csi2_format sun6i_mipi_csi2_formats[] = { | |
27 | { | |
28 | .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, | |
29 | .data_type = MIPI_CSI2_DT_RAW8, | |
30 | .bpp = 8, | |
31 | }, | |
32 | { | |
33 | .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, | |
34 | .data_type = MIPI_CSI2_DT_RAW8, | |
35 | .bpp = 8, | |
36 | }, | |
37 | { | |
38 | .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, | |
39 | .data_type = MIPI_CSI2_DT_RAW8, | |
40 | .bpp = 8, | |
41 | }, | |
42 | { | |
43 | .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, | |
44 | .data_type = MIPI_CSI2_DT_RAW8, | |
45 | .bpp = 8, | |
46 | }, | |
47 | { | |
48 | .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, | |
49 | .data_type = MIPI_CSI2_DT_RAW10, | |
50 | .bpp = 10, | |
51 | }, | |
52 | { | |
53 | .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, | |
54 | .data_type = MIPI_CSI2_DT_RAW10, | |
55 | .bpp = 10, | |
56 | }, | |
57 | { | |
58 | .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, | |
59 | .data_type = MIPI_CSI2_DT_RAW10, | |
60 | .bpp = 10, | |
61 | }, | |
62 | { | |
63 | .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, | |
64 | .data_type = MIPI_CSI2_DT_RAW10, | |
65 | .bpp = 10, | |
66 | }, | |
67 | }; | |
68 | ||
69 | static const struct sun6i_mipi_csi2_format * | |
70 | sun6i_mipi_csi2_format_find(u32 mbus_code) | |
71 | { | |
72 | unsigned int i; | |
73 | ||
74 | for (i = 0; i < ARRAY_SIZE(sun6i_mipi_csi2_formats); i++) | |
75 | if (sun6i_mipi_csi2_formats[i].mbus_code == mbus_code) | |
76 | return &sun6i_mipi_csi2_formats[i]; | |
77 | ||
78 | return NULL; | |
79 | } | |
80 | ||
81 | /* Controller */ | |
82 | ||
83 | static void sun6i_mipi_csi2_enable(struct sun6i_mipi_csi2_device *csi2_dev) | |
84 | { | |
85 | struct regmap *regmap = csi2_dev->regmap; | |
86 | ||
87 | regmap_update_bits(regmap, SUN6I_MIPI_CSI2_CTL_REG, | |
88 | SUN6I_MIPI_CSI2_CTL_EN, SUN6I_MIPI_CSI2_CTL_EN); | |
89 | } | |
90 | ||
91 | static void sun6i_mipi_csi2_disable(struct sun6i_mipi_csi2_device *csi2_dev) | |
92 | { | |
93 | struct regmap *regmap = csi2_dev->regmap; | |
94 | ||
95 | regmap_update_bits(regmap, SUN6I_MIPI_CSI2_CTL_REG, | |
96 | SUN6I_MIPI_CSI2_CTL_EN, 0); | |
97 | } | |
98 | ||
99 | static void sun6i_mipi_csi2_configure(struct sun6i_mipi_csi2_device *csi2_dev) | |
100 | { | |
101 | struct regmap *regmap = csi2_dev->regmap; | |
102 | unsigned int lanes_count = | |
103 | csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes; | |
104 | struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; | |
105 | const struct sun6i_mipi_csi2_format *format; | |
106 | struct device *dev = csi2_dev->dev; | |
107 | u32 version = 0; | |
108 | ||
109 | format = sun6i_mipi_csi2_format_find(mbus_format->code); | |
110 | if (WARN_ON(!format)) | |
111 | return; | |
112 | ||
113 | /* | |
114 | * The enable flow in the Allwinner BSP is a bit different: the enable | |
115 | * and reset bits are set together before starting the CSI controller. | |
116 | * | |
117 | * In mainline we enable the CSI controller first (due to subdev logic). | |
118 | * One reliable way to make this work is to deassert reset, configure | |
119 | * registers and enable the controller when everything's ready. | |
120 | * | |
121 | * However, setting the version enable bit and removing it afterwards | |
122 | * appears necessary for capture to work reliably, while replacing it | |
123 | * with a delay doesn't do the trick. | |
124 | */ | |
125 | regmap_write(regmap, SUN6I_MIPI_CSI2_CTL_REG, | |
126 | SUN6I_MIPI_CSI2_CTL_RESET_N | | |
127 | SUN6I_MIPI_CSI2_CTL_VERSION_EN | | |
128 | SUN6I_MIPI_CSI2_CTL_UNPK_EN); | |
129 | ||
130 | regmap_read(regmap, SUN6I_MIPI_CSI2_VERSION_REG, &version); | |
131 | ||
132 | regmap_update_bits(regmap, SUN6I_MIPI_CSI2_CTL_REG, | |
133 | SUN6I_MIPI_CSI2_CTL_VERSION_EN, 0); | |
134 | ||
135 | dev_dbg(dev, "A31 MIPI CSI-2 version: %04x\n", version); | |
136 | ||
137 | regmap_write(regmap, SUN6I_MIPI_CSI2_CFG_REG, | |
138 | SUN6I_MIPI_CSI2_CFG_CHANNEL_MODE(1) | | |
139 | SUN6I_MIPI_CSI2_CFG_LANE_COUNT(lanes_count)); | |
140 | ||
141 | /* | |
142 | * Only a single virtual channel (index 0) is currently supported. | |
143 | * While the registers do mention multiple physical channels being | |
144 | * available (which can be configured to match a specific virtual | |
145 | * channel or data type), it's unclear whether channels > 0 are actually | |
146 | * connected and available and the reference source code only makes use | |
147 | * of channel 0. | |
148 | * | |
149 | * Using extra channels would also require matching channels to be | |
150 | * available on the CSI (and ISP) side, which is also unsure although | |
151 | * some CSI implementations are said to support multiple channels for | |
152 | * BT656 time-sharing. | |
153 | * | |
154 | * We still configure virtual channel numbers to ensure that virtual | |
155 | * channel 0 only goes to channel 0. | |
156 | */ | |
157 | ||
158 | regmap_write(regmap, SUN6I_MIPI_CSI2_VCDT_RX_REG, | |
159 | SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(3, 3) | | |
160 | SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(2, 2) | | |
161 | SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(1, 1) | | |
162 | SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(0, 0) | | |
163 | SUN6I_MIPI_CSI2_VCDT_RX_CH_DT(0, format->data_type)); | |
164 | ||
165 | regmap_write(regmap, SUN6I_MIPI_CSI2_CH_INT_PD_REG, | |
166 | SUN6I_MIPI_CSI2_CH_INT_PD_CLEAR); | |
167 | } | |
168 | ||
169 | /* V4L2 Subdev */ | |
170 | ||
171 | static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) | |
172 | { | |
173 | struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); | |
174 | struct v4l2_subdev *source_subdev = csi2_dev->bridge.source_subdev; | |
175 | union phy_configure_opts dphy_opts = { 0 }; | |
176 | struct phy_configure_opts_mipi_dphy *dphy_cfg = &dphy_opts.mipi_dphy; | |
177 | struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; | |
178 | const struct sun6i_mipi_csi2_format *format; | |
179 | struct phy *dphy = csi2_dev->dphy; | |
180 | struct device *dev = csi2_dev->dev; | |
181 | struct v4l2_ctrl *ctrl; | |
182 | unsigned int lanes_count = | |
183 | csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes; | |
184 | unsigned long pixel_rate; | |
e39cc496 | 185 | int ret; |
af54b4f4 PK |
186 | |
187 | if (!source_subdev) | |
188 | return -ENODEV; | |
189 | ||
190 | if (!on) { | |
52109d91 PK |
191 | v4l2_subdev_call(source_subdev, video, s_stream, 0); |
192 | ret = 0; | |
af54b4f4 PK |
193 | goto disable; |
194 | } | |
195 | ||
196 | /* Runtime PM */ | |
197 | ||
198 | ret = pm_runtime_resume_and_get(dev); | |
199 | if (ret < 0) | |
200 | return ret; | |
201 | ||
202 | /* Sensor Pixel Rate */ | |
203 | ||
204 | ctrl = v4l2_ctrl_find(source_subdev->ctrl_handler, V4L2_CID_PIXEL_RATE); | |
205 | if (!ctrl) { | |
206 | dev_err(dev, "missing sensor pixel rate\n"); | |
207 | ret = -ENODEV; | |
208 | goto error_pm; | |
209 | } | |
210 | ||
211 | pixel_rate = (unsigned long)v4l2_ctrl_g_ctrl_int64(ctrl); | |
212 | if (!pixel_rate) { | |
213 | dev_err(dev, "missing (zero) sensor pixel rate\n"); | |
214 | ret = -ENODEV; | |
215 | goto error_pm; | |
216 | } | |
217 | ||
218 | /* D-PHY */ | |
219 | ||
220 | if (!lanes_count) { | |
221 | dev_err(dev, "missing (zero) MIPI CSI-2 lanes count\n"); | |
222 | ret = -ENODEV; | |
223 | goto error_pm; | |
224 | } | |
225 | ||
226 | format = sun6i_mipi_csi2_format_find(mbus_format->code); | |
227 | if (WARN_ON(!format)) { | |
228 | ret = -ENODEV; | |
229 | goto error_pm; | |
230 | } | |
231 | ||
232 | phy_mipi_dphy_get_default_config(pixel_rate, format->bpp, lanes_count, | |
233 | dphy_cfg); | |
234 | ||
235 | /* | |
236 | * Note that our hardware is using DDR, which is not taken in account by | |
237 | * phy_mipi_dphy_get_default_config when calculating hs_clk_rate from | |
238 | * the pixel rate, lanes count and bpp. | |
239 | * | |
240 | * The resulting clock rate is basically the symbol rate over the whole | |
241 | * link. The actual clock rate is calculated with division by two since | |
242 | * DDR samples both on rising and falling edges. | |
243 | */ | |
244 | ||
245 | dev_dbg(dev, "A31 MIPI CSI-2 config:\n"); | |
246 | dev_dbg(dev, "%ld pixels/s, %u bits/pixel, %u lanes, %lu Hz clock\n", | |
247 | pixel_rate, format->bpp, lanes_count, | |
248 | dphy_cfg->hs_clk_rate / 2); | |
249 | ||
250 | ret = phy_reset(dphy); | |
251 | if (ret) { | |
252 | dev_err(dev, "failed to reset MIPI D-PHY\n"); | |
253 | goto error_pm; | |
254 | } | |
255 | ||
256 | ret = phy_configure(dphy, &dphy_opts); | |
257 | if (ret) { | |
258 | dev_err(dev, "failed to configure MIPI D-PHY\n"); | |
259 | goto error_pm; | |
260 | } | |
261 | ||
262 | /* Controller */ | |
263 | ||
264 | sun6i_mipi_csi2_configure(csi2_dev); | |
265 | sun6i_mipi_csi2_enable(csi2_dev); | |
266 | ||
267 | /* D-PHY */ | |
268 | ||
269 | ret = phy_power_on(dphy); | |
270 | if (ret) { | |
271 | dev_err(dev, "failed to power on MIPI D-PHY\n"); | |
272 | goto error_pm; | |
273 | } | |
274 | ||
275 | /* Source */ | |
276 | ||
277 | ret = v4l2_subdev_call(source_subdev, video, s_stream, 1); | |
278 | if (ret && ret != -ENOIOCTLCMD) | |
279 | goto disable; | |
280 | ||
281 | return 0; | |
282 | ||
283 | disable: | |
284 | phy_power_off(dphy); | |
285 | sun6i_mipi_csi2_disable(csi2_dev); | |
286 | ||
287 | error_pm: | |
288 | pm_runtime_put(dev); | |
289 | ||
290 | return ret; | |
291 | } | |
292 | ||
293 | static const struct v4l2_subdev_video_ops sun6i_mipi_csi2_video_ops = { | |
294 | .s_stream = sun6i_mipi_csi2_s_stream, | |
295 | }; | |
296 | ||
297 | static void | |
298 | sun6i_mipi_csi2_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) | |
299 | { | |
300 | if (!sun6i_mipi_csi2_format_find(mbus_format->code)) | |
301 | mbus_format->code = sun6i_mipi_csi2_formats[0].mbus_code; | |
302 | ||
303 | mbus_format->field = V4L2_FIELD_NONE; | |
304 | mbus_format->colorspace = V4L2_COLORSPACE_RAW; | |
305 | mbus_format->quantization = V4L2_QUANTIZATION_DEFAULT; | |
306 | mbus_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; | |
307 | } | |
308 | ||
309 | static int sun6i_mipi_csi2_init_cfg(struct v4l2_subdev *subdev, | |
310 | struct v4l2_subdev_state *state) | |
311 | { | |
312 | struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); | |
313 | unsigned int pad = SUN6I_MIPI_CSI2_PAD_SINK; | |
314 | struct v4l2_mbus_framefmt *mbus_format = | |
315 | v4l2_subdev_get_try_format(subdev, state, pad); | |
316 | struct mutex *lock = &csi2_dev->bridge.lock; | |
317 | ||
318 | mutex_lock(lock); | |
319 | ||
320 | mbus_format->code = sun6i_mipi_csi2_formats[0].mbus_code; | |
321 | mbus_format->width = 640; | |
322 | mbus_format->height = 480; | |
323 | ||
324 | sun6i_mipi_csi2_mbus_format_prepare(mbus_format); | |
325 | ||
326 | mutex_unlock(lock); | |
327 | ||
328 | return 0; | |
329 | } | |
330 | ||
331 | static int | |
332 | sun6i_mipi_csi2_enum_mbus_code(struct v4l2_subdev *subdev, | |
333 | struct v4l2_subdev_state *state, | |
334 | struct v4l2_subdev_mbus_code_enum *code_enum) | |
335 | { | |
336 | if (code_enum->index >= ARRAY_SIZE(sun6i_mipi_csi2_formats)) | |
337 | return -EINVAL; | |
338 | ||
339 | code_enum->code = sun6i_mipi_csi2_formats[code_enum->index].mbus_code; | |
340 | ||
341 | return 0; | |
342 | } | |
343 | ||
344 | static int sun6i_mipi_csi2_get_fmt(struct v4l2_subdev *subdev, | |
345 | struct v4l2_subdev_state *state, | |
346 | struct v4l2_subdev_format *format) | |
347 | { | |
348 | struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); | |
349 | struct v4l2_mbus_framefmt *mbus_format = &format->format; | |
350 | struct mutex *lock = &csi2_dev->bridge.lock; | |
351 | ||
352 | mutex_lock(lock); | |
353 | ||
354 | if (format->which == V4L2_SUBDEV_FORMAT_TRY) | |
355 | *mbus_format = *v4l2_subdev_get_try_format(subdev, state, | |
356 | format->pad); | |
357 | else | |
358 | *mbus_format = csi2_dev->bridge.mbus_format; | |
359 | ||
360 | mutex_unlock(lock); | |
361 | ||
362 | return 0; | |
363 | } | |
364 | ||
365 | static int sun6i_mipi_csi2_set_fmt(struct v4l2_subdev *subdev, | |
366 | struct v4l2_subdev_state *state, | |
367 | struct v4l2_subdev_format *format) | |
368 | { | |
369 | struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); | |
370 | struct v4l2_mbus_framefmt *mbus_format = &format->format; | |
371 | struct mutex *lock = &csi2_dev->bridge.lock; | |
372 | ||
373 | mutex_lock(lock); | |
374 | ||
375 | sun6i_mipi_csi2_mbus_format_prepare(mbus_format); | |
376 | ||
377 | if (format->which == V4L2_SUBDEV_FORMAT_TRY) | |
378 | *v4l2_subdev_get_try_format(subdev, state, format->pad) = | |
379 | *mbus_format; | |
380 | else | |
381 | csi2_dev->bridge.mbus_format = *mbus_format; | |
382 | ||
383 | mutex_unlock(lock); | |
384 | ||
385 | return 0; | |
386 | } | |
387 | ||
388 | static const struct v4l2_subdev_pad_ops sun6i_mipi_csi2_pad_ops = { | |
389 | .init_cfg = sun6i_mipi_csi2_init_cfg, | |
390 | .enum_mbus_code = sun6i_mipi_csi2_enum_mbus_code, | |
391 | .get_fmt = sun6i_mipi_csi2_get_fmt, | |
392 | .set_fmt = sun6i_mipi_csi2_set_fmt, | |
393 | }; | |
394 | ||
395 | static const struct v4l2_subdev_ops sun6i_mipi_csi2_subdev_ops = { | |
396 | .video = &sun6i_mipi_csi2_video_ops, | |
397 | .pad = &sun6i_mipi_csi2_pad_ops, | |
398 | }; | |
399 | ||
400 | /* Media Entity */ | |
401 | ||
402 | static const struct media_entity_operations sun6i_mipi_csi2_entity_ops = { | |
403 | .link_validate = v4l2_subdev_link_validate, | |
404 | }; | |
405 | ||
406 | /* V4L2 Async */ | |
407 | ||
408 | static int | |
409 | sun6i_mipi_csi2_notifier_bound(struct v4l2_async_notifier *notifier, | |
410 | struct v4l2_subdev *remote_subdev, | |
411 | struct v4l2_async_subdev *async_subdev) | |
412 | { | |
413 | struct v4l2_subdev *subdev = notifier->sd; | |
414 | struct sun6i_mipi_csi2_device *csi2_dev = | |
415 | container_of(notifier, struct sun6i_mipi_csi2_device, | |
416 | bridge.notifier); | |
417 | struct media_entity *sink_entity = &subdev->entity; | |
418 | struct media_entity *source_entity = &remote_subdev->entity; | |
419 | struct device *dev = csi2_dev->dev; | |
420 | int sink_pad_index = 0; | |
421 | int source_pad_index; | |
422 | int ret; | |
423 | ||
424 | ret = media_entity_get_fwnode_pad(source_entity, remote_subdev->fwnode, | |
425 | MEDIA_PAD_FL_SOURCE); | |
426 | if (ret < 0) { | |
427 | dev_err(dev, "missing source pad in external entity %s\n", | |
428 | source_entity->name); | |
429 | return -EINVAL; | |
430 | } | |
431 | ||
432 | source_pad_index = ret; | |
433 | ||
434 | dev_dbg(dev, "creating %s:%u -> %s:%u link\n", source_entity->name, | |
435 | source_pad_index, sink_entity->name, sink_pad_index); | |
436 | ||
437 | ret = media_create_pad_link(source_entity, source_pad_index, | |
438 | sink_entity, sink_pad_index, | |
439 | MEDIA_LNK_FL_ENABLED | | |
440 | MEDIA_LNK_FL_IMMUTABLE); | |
441 | if (ret) { | |
442 | dev_err(dev, "failed to create %s:%u -> %s:%u link\n", | |
443 | source_entity->name, source_pad_index, | |
444 | sink_entity->name, sink_pad_index); | |
445 | return ret; | |
446 | } | |
447 | ||
448 | csi2_dev->bridge.source_subdev = remote_subdev; | |
449 | ||
450 | return 0; | |
451 | } | |
452 | ||
453 | static const struct v4l2_async_notifier_operations | |
454 | sun6i_mipi_csi2_notifier_ops = { | |
455 | .bound = sun6i_mipi_csi2_notifier_bound, | |
456 | }; | |
457 | ||
458 | /* Bridge */ | |
459 | ||
460 | static int | |
461 | sun6i_mipi_csi2_bridge_source_setup(struct sun6i_mipi_csi2_device *csi2_dev) | |
462 | { | |
463 | struct v4l2_async_notifier *notifier = &csi2_dev->bridge.notifier; | |
464 | struct v4l2_fwnode_endpoint *endpoint = &csi2_dev->bridge.endpoint; | |
465 | struct v4l2_async_subdev *subdev_async; | |
466 | struct fwnode_handle *handle; | |
467 | struct device *dev = csi2_dev->dev; | |
468 | int ret; | |
469 | ||
470 | handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, | |
471 | FWNODE_GRAPH_ENDPOINT_NEXT); | |
472 | if (!handle) | |
473 | return -ENODEV; | |
474 | ||
475 | endpoint->bus_type = V4L2_MBUS_CSI2_DPHY; | |
476 | ||
477 | ret = v4l2_fwnode_endpoint_parse(handle, endpoint); | |
478 | if (ret) | |
479 | goto complete; | |
480 | ||
481 | subdev_async = | |
482 | v4l2_async_nf_add_fwnode_remote(notifier, handle, | |
483 | struct v4l2_async_subdev); | |
484 | if (IS_ERR(subdev_async)) | |
485 | ret = PTR_ERR(subdev_async); | |
486 | ||
487 | complete: | |
488 | fwnode_handle_put(handle); | |
489 | ||
490 | return ret; | |
491 | } | |
492 | ||
493 | static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) | |
494 | { | |
495 | struct sun6i_mipi_csi2_bridge *bridge = &csi2_dev->bridge; | |
496 | struct v4l2_subdev *subdev = &bridge->subdev; | |
497 | struct v4l2_async_notifier *notifier = &bridge->notifier; | |
498 | struct media_pad *pads = bridge->pads; | |
499 | struct device *dev = csi2_dev->dev; | |
67182951 | 500 | bool notifier_registered = false; |
af54b4f4 PK |
501 | int ret; |
502 | ||
503 | mutex_init(&bridge->lock); | |
504 | ||
505 | /* V4L2 Subdev */ | |
506 | ||
507 | v4l2_subdev_init(subdev, &sun6i_mipi_csi2_subdev_ops); | |
508 | strscpy(subdev->name, SUN6I_MIPI_CSI2_NAME, sizeof(subdev->name)); | |
509 | subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; | |
510 | subdev->owner = THIS_MODULE; | |
511 | subdev->dev = dev; | |
512 | ||
513 | v4l2_set_subdevdata(subdev, csi2_dev); | |
514 | ||
515 | /* Media Entity */ | |
516 | ||
517 | subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; | |
518 | subdev->entity.ops = &sun6i_mipi_csi2_entity_ops; | |
519 | ||
520 | /* Media Pads */ | |
521 | ||
f042b08b PK |
522 | pads[SUN6I_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK | |
523 | MEDIA_PAD_FL_MUST_CONNECT; | |
524 | pads[SUN6I_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | | |
525 | MEDIA_PAD_FL_MUST_CONNECT; | |
af54b4f4 PK |
526 | |
527 | ret = media_entity_pads_init(&subdev->entity, SUN6I_MIPI_CSI2_PAD_COUNT, | |
528 | pads); | |
529 | if (ret) | |
530 | return ret; | |
531 | ||
532 | /* V4L2 Async */ | |
533 | ||
534 | v4l2_async_nf_init(notifier); | |
535 | notifier->ops = &sun6i_mipi_csi2_notifier_ops; | |
536 | ||
537 | ret = sun6i_mipi_csi2_bridge_source_setup(csi2_dev); | |
67182951 | 538 | if (ret && ret != -ENODEV) |
af54b4f4 PK |
539 | goto error_v4l2_notifier_cleanup; |
540 | ||
67182951 PK |
541 | /* Only register the notifier when a sensor is connected. */ |
542 | if (ret != -ENODEV) { | |
543 | ret = v4l2_async_subdev_nf_register(subdev, notifier); | |
544 | if (ret < 0) | |
545 | goto error_v4l2_notifier_cleanup; | |
546 | ||
547 | notifier_registered = true; | |
548 | } | |
af54b4f4 PK |
549 | |
550 | /* V4L2 Subdev */ | |
551 | ||
552 | ret = v4l2_async_register_subdev(subdev); | |
553 | if (ret < 0) | |
554 | goto error_v4l2_notifier_unregister; | |
555 | ||
556 | return 0; | |
557 | ||
558 | error_v4l2_notifier_unregister: | |
67182951 PK |
559 | if (notifier_registered) |
560 | v4l2_async_nf_unregister(notifier); | |
af54b4f4 PK |
561 | |
562 | error_v4l2_notifier_cleanup: | |
563 | v4l2_async_nf_cleanup(notifier); | |
564 | ||
565 | media_entity_cleanup(&subdev->entity); | |
566 | ||
567 | return ret; | |
568 | } | |
569 | ||
570 | static void | |
571 | sun6i_mipi_csi2_bridge_cleanup(struct sun6i_mipi_csi2_device *csi2_dev) | |
572 | { | |
573 | struct v4l2_subdev *subdev = &csi2_dev->bridge.subdev; | |
574 | struct v4l2_async_notifier *notifier = &csi2_dev->bridge.notifier; | |
575 | ||
576 | v4l2_async_unregister_subdev(subdev); | |
577 | v4l2_async_nf_unregister(notifier); | |
578 | v4l2_async_nf_cleanup(notifier); | |
579 | media_entity_cleanup(&subdev->entity); | |
580 | } | |
581 | ||
582 | /* Platform */ | |
583 | ||
584 | static int sun6i_mipi_csi2_suspend(struct device *dev) | |
585 | { | |
586 | struct sun6i_mipi_csi2_device *csi2_dev = dev_get_drvdata(dev); | |
587 | ||
588 | clk_disable_unprepare(csi2_dev->clock_mod); | |
589 | reset_control_assert(csi2_dev->reset); | |
590 | ||
591 | return 0; | |
592 | } | |
593 | ||
594 | static int sun6i_mipi_csi2_resume(struct device *dev) | |
595 | { | |
596 | struct sun6i_mipi_csi2_device *csi2_dev = dev_get_drvdata(dev); | |
597 | int ret; | |
598 | ||
599 | ret = reset_control_deassert(csi2_dev->reset); | |
600 | if (ret) { | |
601 | dev_err(dev, "failed to deassert reset\n"); | |
602 | return ret; | |
603 | } | |
604 | ||
605 | ret = clk_prepare_enable(csi2_dev->clock_mod); | |
606 | if (ret) { | |
607 | dev_err(dev, "failed to enable module clock\n"); | |
608 | goto error_reset; | |
609 | } | |
610 | ||
611 | return 0; | |
612 | ||
613 | error_reset: | |
614 | reset_control_assert(csi2_dev->reset); | |
615 | ||
616 | return ret; | |
617 | } | |
618 | ||
619 | static const struct dev_pm_ops sun6i_mipi_csi2_pm_ops = { | |
620 | .runtime_suspend = sun6i_mipi_csi2_suspend, | |
621 | .runtime_resume = sun6i_mipi_csi2_resume, | |
622 | }; | |
623 | ||
624 | static const struct regmap_config sun6i_mipi_csi2_regmap_config = { | |
625 | .reg_bits = 32, | |
626 | .reg_stride = 4, | |
627 | .val_bits = 32, | |
628 | .max_register = 0x400, | |
629 | }; | |
630 | ||
631 | static int | |
632 | sun6i_mipi_csi2_resources_setup(struct sun6i_mipi_csi2_device *csi2_dev, | |
633 | struct platform_device *platform_dev) | |
634 | { | |
635 | struct device *dev = csi2_dev->dev; | |
636 | void __iomem *io_base; | |
637 | int ret; | |
638 | ||
639 | /* Registers */ | |
640 | ||
641 | io_base = devm_platform_ioremap_resource(platform_dev, 0); | |
642 | if (IS_ERR(io_base)) | |
643 | return PTR_ERR(io_base); | |
644 | ||
645 | csi2_dev->regmap = | |
646 | devm_regmap_init_mmio_clk(dev, "bus", io_base, | |
647 | &sun6i_mipi_csi2_regmap_config); | |
648 | if (IS_ERR(csi2_dev->regmap)) { | |
649 | dev_err(dev, "failed to init register map\n"); | |
650 | return PTR_ERR(csi2_dev->regmap); | |
651 | } | |
652 | ||
653 | /* Clock */ | |
654 | ||
655 | csi2_dev->clock_mod = devm_clk_get(dev, "mod"); | |
656 | if (IS_ERR(csi2_dev->clock_mod)) { | |
657 | dev_err(dev, "failed to acquire mod clock\n"); | |
658 | return PTR_ERR(csi2_dev->clock_mod); | |
659 | } | |
660 | ||
661 | ret = clk_set_rate_exclusive(csi2_dev->clock_mod, 297000000); | |
662 | if (ret) { | |
663 | dev_err(dev, "failed to set mod clock rate\n"); | |
664 | return ret; | |
665 | } | |
666 | ||
667 | /* Reset */ | |
668 | ||
669 | csi2_dev->reset = devm_reset_control_get_shared(dev, NULL); | |
670 | if (IS_ERR(csi2_dev->reset)) { | |
671 | dev_err(dev, "failed to get reset controller\n"); | |
51e1440d CJ |
672 | ret = PTR_ERR(csi2_dev->reset); |
673 | goto error_clock_rate_exclusive; | |
af54b4f4 PK |
674 | } |
675 | ||
676 | /* D-PHY */ | |
677 | ||
678 | csi2_dev->dphy = devm_phy_get(dev, "dphy"); | |
679 | if (IS_ERR(csi2_dev->dphy)) { | |
680 | dev_err(dev, "failed to get MIPI D-PHY\n"); | |
51e1440d CJ |
681 | ret = PTR_ERR(csi2_dev->dphy); |
682 | goto error_clock_rate_exclusive; | |
af54b4f4 PK |
683 | } |
684 | ||
685 | ret = phy_init(csi2_dev->dphy); | |
686 | if (ret) { | |
687 | dev_err(dev, "failed to initialize MIPI D-PHY\n"); | |
51e1440d | 688 | goto error_clock_rate_exclusive; |
af54b4f4 PK |
689 | } |
690 | ||
691 | /* Runtime PM */ | |
692 | ||
693 | pm_runtime_enable(dev); | |
694 | ||
695 | return 0; | |
51e1440d CJ |
696 | |
697 | error_clock_rate_exclusive: | |
698 | clk_rate_exclusive_put(csi2_dev->clock_mod); | |
699 | ||
700 | return ret; | |
af54b4f4 PK |
701 | } |
702 | ||
703 | static void | |
704 | sun6i_mipi_csi2_resources_cleanup(struct sun6i_mipi_csi2_device *csi2_dev) | |
705 | { | |
706 | pm_runtime_disable(csi2_dev->dev); | |
707 | phy_exit(csi2_dev->dphy); | |
708 | clk_rate_exclusive_put(csi2_dev->clock_mod); | |
709 | } | |
710 | ||
711 | static int sun6i_mipi_csi2_probe(struct platform_device *platform_dev) | |
712 | { | |
713 | struct sun6i_mipi_csi2_device *csi2_dev; | |
714 | struct device *dev = &platform_dev->dev; | |
715 | int ret; | |
716 | ||
717 | csi2_dev = devm_kzalloc(dev, sizeof(*csi2_dev), GFP_KERNEL); | |
718 | if (!csi2_dev) | |
719 | return -ENOMEM; | |
720 | ||
721 | csi2_dev->dev = dev; | |
722 | platform_set_drvdata(platform_dev, csi2_dev); | |
723 | ||
724 | ret = sun6i_mipi_csi2_resources_setup(csi2_dev, platform_dev); | |
725 | if (ret) | |
726 | return ret; | |
727 | ||
728 | ret = sun6i_mipi_csi2_bridge_setup(csi2_dev); | |
729 | if (ret) | |
51e1440d | 730 | goto error_resources; |
af54b4f4 PK |
731 | |
732 | return 0; | |
51e1440d CJ |
733 | |
734 | error_resources: | |
735 | sun6i_mipi_csi2_resources_cleanup(csi2_dev); | |
736 | ||
737 | return ret; | |
af54b4f4 PK |
738 | } |
739 | ||
cdf5e383 | 740 | static void sun6i_mipi_csi2_remove(struct platform_device *platform_dev) |
af54b4f4 PK |
741 | { |
742 | struct sun6i_mipi_csi2_device *csi2_dev = | |
743 | platform_get_drvdata(platform_dev); | |
744 | ||
745 | sun6i_mipi_csi2_bridge_cleanup(csi2_dev); | |
746 | sun6i_mipi_csi2_resources_cleanup(csi2_dev); | |
af54b4f4 PK |
747 | } |
748 | ||
749 | static const struct of_device_id sun6i_mipi_csi2_of_match[] = { | |
750 | { .compatible = "allwinner,sun6i-a31-mipi-csi2" }, | |
751 | {}, | |
752 | }; | |
753 | MODULE_DEVICE_TABLE(of, sun6i_mipi_csi2_of_match); | |
754 | ||
755 | static struct platform_driver sun6i_mipi_csi2_platform_driver = { | |
756 | .probe = sun6i_mipi_csi2_probe, | |
cdf5e383 | 757 | .remove_new = sun6i_mipi_csi2_remove, |
af54b4f4 PK |
758 | .driver = { |
759 | .name = SUN6I_MIPI_CSI2_NAME, | |
760 | .of_match_table = of_match_ptr(sun6i_mipi_csi2_of_match), | |
761 | .pm = &sun6i_mipi_csi2_pm_ops, | |
762 | }, | |
763 | }; | |
764 | module_platform_driver(sun6i_mipi_csi2_platform_driver); | |
765 | ||
766 | MODULE_DESCRIPTION("Allwinner A31 MIPI CSI-2 Controller Driver"); | |
767 | MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>"); | |
768 | MODULE_LICENSE("GPL"); |