1 | // SPDX-License-Identifier: GPL-2.0 |
---|---|
2 | /* |
3 | * Maxim MAX96712 Quad GMSL2 Deserializer Driver |
4 | * |
5 | * Copyright (C) 2021 Renesas Electronics Corporation |
6 | * Copyright (C) 2021 Niklas Söderlund |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of_graph.h> |
13 | #include <linux/regmap.h> |
14 | |
15 | #include <media/v4l2-ctrls.h> |
16 | #include <media/v4l2-fwnode.h> |
17 | #include <media/v4l2-subdev.h> |
18 | |
19 | #define DEBUG_EXTRA_REG 0x09 |
20 | #define DEBUG_EXTRA_PCLK_25MHZ 0x00 |
21 | #define DEBUG_EXTRA_PCLK_75MHZ 0x01 |
22 | |
23 | enum max96712_pattern { |
24 | MAX96712_PATTERN_CHECKERBOARD = 0, |
25 | MAX96712_PATTERN_GRADIENT, |
26 | }; |
27 | |
28 | struct max96712_info { |
29 | unsigned int dpllfreq; |
30 | bool have_debug_extra; |
31 | }; |
32 | |
33 | struct max96712_priv { |
34 | struct i2c_client *client; |
35 | struct regmap *regmap; |
36 | struct gpio_desc *gpiod_pwdn; |
37 | |
38 | const struct max96712_info *info; |
39 | |
40 | bool cphy; |
41 | struct v4l2_mbus_config_mipi_csi2 mipi; |
42 | |
43 | struct v4l2_subdev sd; |
44 | struct v4l2_ctrl_handler ctrl_handler; |
45 | struct media_pad pads[1]; |
46 | |
47 | enum max96712_pattern pattern; |
48 | }; |
49 | |
50 | static int max96712_write(struct max96712_priv *priv, unsigned int reg, u8 val) |
51 | { |
52 | int ret; |
53 | |
54 | ret = regmap_write(map: priv->regmap, reg, val); |
55 | if (ret) |
56 | dev_err(&priv->client->dev, "write 0x%04x failed\n", reg); |
57 | |
58 | return ret; |
59 | } |
60 | |
61 | static int max96712_update_bits(struct max96712_priv *priv, unsigned int reg, |
62 | u8 mask, u8 val) |
63 | { |
64 | int ret; |
65 | |
66 | ret = regmap_update_bits(map: priv->regmap, reg, mask, val); |
67 | if (ret) |
68 | dev_err(&priv->client->dev, "update 0x%04x failed\n", reg); |
69 | |
70 | return ret; |
71 | } |
72 | |
73 | static int max96712_write_bulk(struct max96712_priv *priv, unsigned int reg, |
74 | const void *val, size_t val_count) |
75 | { |
76 | int ret; |
77 | |
78 | ret = regmap_bulk_write(map: priv->regmap, reg, val, val_count); |
79 | if (ret) |
80 | dev_err(&priv->client->dev, "bulk write 0x%04x failed\n", reg); |
81 | |
82 | return ret; |
83 | } |
84 | |
85 | static int max96712_write_bulk_value(struct max96712_priv *priv, |
86 | unsigned int reg, unsigned int val, |
87 | size_t val_count) |
88 | { |
89 | unsigned int i; |
90 | u8 values[4]; |
91 | |
92 | for (i = 1; i <= val_count; i++) |
93 | values[i - 1] = (val >> ((val_count - i) * 8)) & 0xff; |
94 | |
95 | return max96712_write_bulk(priv, reg, val: &values, val_count); |
96 | } |
97 | |
98 | static void max96712_reset(struct max96712_priv *priv) |
99 | { |
100 | max96712_update_bits(priv, reg: 0x13, mask: 0x40, val: 0x40); |
101 | msleep(msecs: 20); |
102 | } |
103 | |
104 | static void max96712_mipi_enable(struct max96712_priv *priv, bool enable) |
105 | { |
106 | if (enable) { |
107 | max96712_update_bits(priv, reg: 0x40b, mask: 0x02, val: 0x02); |
108 | max96712_update_bits(priv, reg: 0x8a0, mask: 0x80, val: 0x80); |
109 | } else { |
110 | max96712_update_bits(priv, reg: 0x8a0, mask: 0x80, val: 0x00); |
111 | max96712_update_bits(priv, reg: 0x40b, mask: 0x02, val: 0x00); |
112 | } |
113 | } |
114 | |
115 | static void max96712_mipi_configure(struct max96712_priv *priv) |
116 | { |
117 | unsigned int i; |
118 | u8 phy5 = 0; |
119 | |
120 | max96712_mipi_enable(priv, enable: false); |
121 | |
122 | /* Select 2x4 mode. */ |
123 | max96712_write(priv, reg: 0x8a0, val: 0x04); |
124 | |
125 | /* TODO: Add support for 2-lane and 1-lane configurations. */ |
126 | if (priv->cphy) { |
127 | /* Configure a 3-lane C-PHY using PHY0 and PHY1. */ |
128 | max96712_write(priv, reg: 0x94a, val: 0xa0); |
129 | |
130 | /* Configure C-PHY timings. */ |
131 | max96712_write(priv, reg: 0x8ad, val: 0x3f); |
132 | max96712_write(priv, reg: 0x8ae, val: 0x7d); |
133 | } else { |
134 | /* Configure a 4-lane D-PHY using PHY0 and PHY1. */ |
135 | max96712_write(priv, reg: 0x94a, val: 0xc0); |
136 | } |
137 | |
138 | /* Configure lane mapping for PHY0 and PHY1. */ |
139 | /* TODO: Add support for lane swapping. */ |
140 | max96712_write(priv, reg: 0x8a3, val: 0xe4); |
141 | |
142 | /* Configure lane polarity for PHY0 and PHY1. */ |
143 | for (i = 0; i < priv->mipi.num_data_lanes + 1; i++) |
144 | if (priv->mipi.lane_polarities[i]) |
145 | phy5 |= BIT(i == 0 ? 5 : i < 3 ? i - 1 : i); |
146 | max96712_write(priv, reg: 0x8a5, val: phy5); |
147 | |
148 | /* Set link frequency for PHY0 and PHY1. */ |
149 | max96712_update_bits(priv, reg: 0x415, mask: 0x3f, |
150 | val: ((priv->info->dpllfreq / 100) & 0x1f) | BIT(5)); |
151 | max96712_update_bits(priv, reg: 0x418, mask: 0x3f, |
152 | val: ((priv->info->dpllfreq / 100) & 0x1f) | BIT(5)); |
153 | |
154 | /* Enable PHY0 and PHY1 */ |
155 | max96712_update_bits(priv, reg: 0x8a2, mask: 0xf0, val: 0x30); |
156 | } |
157 | |
158 | static void max96712_pattern_enable(struct max96712_priv *priv, bool enable) |
159 | { |
160 | const u32 h_active = 1920; |
161 | const u32 h_fp = 88; |
162 | const u32 h_sw = 44; |
163 | const u32 h_bp = 148; |
164 | const u32 h_tot = h_active + h_fp + h_sw + h_bp; |
165 | |
166 | const u32 v_active = 1080; |
167 | const u32 v_fp = 4; |
168 | const u32 v_sw = 5; |
169 | const u32 v_bp = 36; |
170 | const u32 v_tot = v_active + v_fp + v_sw + v_bp; |
171 | |
172 | if (!enable) { |
173 | max96712_write(priv, reg: 0x1051, val: 0x00); |
174 | return; |
175 | } |
176 | |
177 | /* Set PCLK to 75MHz if device have DEBUG_EXTRA register. */ |
178 | if (priv->info->have_debug_extra) |
179 | max96712_write(priv, DEBUG_EXTRA_REG, DEBUG_EXTRA_PCLK_75MHZ); |
180 | |
181 | /* Configure Video Timing Generator for 1920x1080 @ 30 fps. */ |
182 | max96712_write_bulk_value(priv, reg: 0x1052, val: 0, val_count: 3); |
183 | max96712_write_bulk_value(priv, reg: 0x1055, val: v_sw * h_tot, val_count: 3); |
184 | max96712_write_bulk_value(priv, reg: 0x1058, |
185 | val: (v_active + v_fp + + v_bp) * h_tot, val_count: 3); |
186 | max96712_write_bulk_value(priv, reg: 0x105b, val: 0, val_count: 3); |
187 | max96712_write_bulk_value(priv, reg: 0x105e, val: h_sw, val_count: 2); |
188 | max96712_write_bulk_value(priv, reg: 0x1060, val: h_active + h_fp + h_bp, val_count: 2); |
189 | max96712_write_bulk_value(priv, reg: 0x1062, val: v_tot, val_count: 2); |
190 | max96712_write_bulk_value(priv, reg: 0x1064, |
191 | val: h_tot * (v_sw + v_bp) + (h_sw + h_bp), val_count: 3); |
192 | max96712_write_bulk_value(priv, reg: 0x1067, val: h_active, val_count: 2); |
193 | max96712_write_bulk_value(priv, reg: 0x1069, val: h_fp + h_sw + h_bp, val_count: 2); |
194 | max96712_write_bulk_value(priv, reg: 0x106b, val: v_active, val_count: 2); |
195 | |
196 | /* Generate VS, HS and DE in free-running mode. */ |
197 | max96712_write(priv, reg: 0x1050, val: 0xfb); |
198 | |
199 | /* Configure Video Pattern Generator. */ |
200 | if (priv->pattern == MAX96712_PATTERN_CHECKERBOARD) { |
201 | /* Set checkerboard pattern size. */ |
202 | max96712_write(priv, reg: 0x1074, val: 0x3c); |
203 | max96712_write(priv, reg: 0x1075, val: 0x3c); |
204 | max96712_write(priv, reg: 0x1076, val: 0x3c); |
205 | |
206 | /* Set checkerboard pattern colors. */ |
207 | max96712_write_bulk_value(priv, reg: 0x106e, val: 0xfecc00, val_count: 3); |
208 | max96712_write_bulk_value(priv, reg: 0x1071, val: 0x006aa7, val_count: 3); |
209 | |
210 | /* Generate checkerboard pattern. */ |
211 | max96712_write(priv, reg: 0x1051, val: 0x10); |
212 | } else { |
213 | /* Set gradient increment. */ |
214 | max96712_write(priv, reg: 0x106d, val: 0x10); |
215 | |
216 | /* Generate gradient pattern. */ |
217 | max96712_write(priv, reg: 0x1051, val: 0x20); |
218 | } |
219 | } |
220 | |
221 | static int max96712_s_stream(struct v4l2_subdev *sd, int enable) |
222 | { |
223 | struct max96712_priv *priv = v4l2_get_subdevdata(sd); |
224 | |
225 | if (enable) { |
226 | max96712_pattern_enable(priv, enable: true); |
227 | max96712_mipi_enable(priv, enable: true); |
228 | } else { |
229 | max96712_mipi_enable(priv, enable: false); |
230 | max96712_pattern_enable(priv, enable: false); |
231 | } |
232 | |
233 | return 0; |
234 | } |
235 | |
236 | static const struct v4l2_subdev_video_ops max96712_video_ops = { |
237 | .s_stream = max96712_s_stream, |
238 | }; |
239 | |
240 | static int max96712_init_state(struct v4l2_subdev *sd, |
241 | struct v4l2_subdev_state *state) |
242 | { |
243 | static const struct v4l2_mbus_framefmt default_fmt = { |
244 | .width = 1920, |
245 | .height = 1080, |
246 | .code = MEDIA_BUS_FMT_RGB888_1X24, |
247 | .colorspace = V4L2_COLORSPACE_SRGB, |
248 | .field = V4L2_FIELD_NONE, |
249 | .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, |
250 | .quantization = V4L2_QUANTIZATION_DEFAULT, |
251 | .xfer_func = V4L2_XFER_FUNC_DEFAULT, |
252 | }; |
253 | struct v4l2_mbus_framefmt *fmt; |
254 | |
255 | fmt = v4l2_subdev_state_get_format(state, 0); |
256 | *fmt = default_fmt; |
257 | |
258 | return 0; |
259 | } |
260 | |
261 | static const struct v4l2_subdev_internal_ops max96712_internal_ops = { |
262 | .init_state = max96712_init_state, |
263 | }; |
264 | |
265 | static const struct v4l2_subdev_pad_ops max96712_pad_ops = { |
266 | .get_fmt = v4l2_subdev_get_fmt, |
267 | .set_fmt = v4l2_subdev_get_fmt, |
268 | }; |
269 | |
270 | static const struct v4l2_subdev_ops max96712_subdev_ops = { |
271 | .video = &max96712_video_ops, |
272 | .pad = &max96712_pad_ops, |
273 | }; |
274 | |
275 | static const char * const max96712_test_pattern[] = { |
276 | "Checkerboard", |
277 | "Gradient", |
278 | }; |
279 | |
280 | static int max96712_s_ctrl(struct v4l2_ctrl *ctrl) |
281 | { |
282 | struct max96712_priv *priv = |
283 | container_of(ctrl->handler, struct max96712_priv, ctrl_handler); |
284 | |
285 | switch (ctrl->id) { |
286 | case V4L2_CID_TEST_PATTERN: |
287 | priv->pattern = ctrl->val ? |
288 | MAX96712_PATTERN_GRADIENT : |
289 | MAX96712_PATTERN_CHECKERBOARD; |
290 | break; |
291 | } |
292 | return 0; |
293 | } |
294 | |
295 | static const struct v4l2_ctrl_ops max96712_ctrl_ops = { |
296 | .s_ctrl = max96712_s_ctrl, |
297 | }; |
298 | |
299 | static int max96712_v4l2_register(struct max96712_priv *priv) |
300 | { |
301 | long pixel_rate; |
302 | int ret; |
303 | |
304 | priv->sd.internal_ops = &max96712_internal_ops; |
305 | v4l2_i2c_subdev_init(sd: &priv->sd, client: priv->client, ops: &max96712_subdev_ops); |
306 | priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; |
307 | priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; |
308 | |
309 | v4l2_ctrl_handler_init(&priv->ctrl_handler, 2); |
310 | |
311 | /* |
312 | * TODO: Once V4L2_CID_LINK_FREQ is changed from a menu control to an |
313 | * INT64 control it should be used here instead of V4L2_CID_PIXEL_RATE. |
314 | */ |
315 | pixel_rate = priv->info->dpllfreq / priv->mipi.num_data_lanes * 1000000; |
316 | v4l2_ctrl_new_std(hdl: &priv->ctrl_handler, NULL, V4L2_CID_PIXEL_RATE, |
317 | min: pixel_rate, max: pixel_rate, step: 1, def: pixel_rate); |
318 | |
319 | v4l2_ctrl_new_std_menu_items(hdl: &priv->ctrl_handler, ops: &max96712_ctrl_ops, |
320 | V4L2_CID_TEST_PATTERN, |
321 | ARRAY_SIZE(max96712_test_pattern) - 1, |
322 | mask: 0, def: 0, qmenu: max96712_test_pattern); |
323 | |
324 | priv->sd.ctrl_handler = &priv->ctrl_handler; |
325 | ret = priv->ctrl_handler.error; |
326 | if (ret) |
327 | goto error; |
328 | |
329 | priv->pads[0].flags = MEDIA_PAD_FL_SOURCE; |
330 | ret = media_entity_pads_init(entity: &priv->sd.entity, num_pads: 1, pads: priv->pads); |
331 | if (ret) |
332 | goto error; |
333 | |
334 | v4l2_set_subdevdata(sd: &priv->sd, p: priv); |
335 | |
336 | priv->sd.state_lock = priv->ctrl_handler.lock; |
337 | ret = v4l2_subdev_init_finalize(&priv->sd); |
338 | if (ret) |
339 | goto error; |
340 | |
341 | ret = v4l2_async_register_subdev(&priv->sd); |
342 | if (ret < 0) { |
343 | dev_err(&priv->client->dev, "Unable to register subdevice\n"); |
344 | goto error; |
345 | } |
346 | |
347 | return 0; |
348 | error: |
349 | v4l2_ctrl_handler_free(hdl: &priv->ctrl_handler); |
350 | |
351 | return ret; |
352 | } |
353 | |
354 | static int max96712_parse_dt(struct max96712_priv *priv) |
355 | { |
356 | struct fwnode_handle *ep; |
357 | struct v4l2_fwnode_endpoint v4l2_ep = { |
358 | .bus_type = V4L2_MBUS_UNKNOWN, |
359 | }; |
360 | unsigned int supported_lanes; |
361 | int ret; |
362 | |
363 | ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(&priv->client->dev), port: 4, |
364 | endpoint: 0, flags: 0); |
365 | if (!ep) { |
366 | dev_err(&priv->client->dev, "Not connected to subdevice\n"); |
367 | return -EINVAL; |
368 | } |
369 | |
370 | ret = v4l2_fwnode_endpoint_parse(fwnode: ep, vep: &v4l2_ep); |
371 | fwnode_handle_put(fwnode: ep); |
372 | if (ret) { |
373 | dev_err(&priv->client->dev, "Could not parse v4l2 endpoint\n"); |
374 | return -EINVAL; |
375 | } |
376 | |
377 | switch (v4l2_ep.bus_type) { |
378 | case V4L2_MBUS_CSI2_DPHY: |
379 | supported_lanes = 4; |
380 | priv->cphy = false; |
381 | break; |
382 | case V4L2_MBUS_CSI2_CPHY: |
383 | supported_lanes = 3; |
384 | priv->cphy = true; |
385 | break; |
386 | default: |
387 | dev_err(&priv->client->dev, "Unsupported bus-type %u\n", |
388 | v4l2_ep.bus_type); |
389 | return -EINVAL; |
390 | } |
391 | |
392 | if (v4l2_ep.bus.mipi_csi2.num_data_lanes != supported_lanes) { |
393 | dev_err(&priv->client->dev, "Only %u data lanes supported\n", |
394 | supported_lanes); |
395 | return -EINVAL; |
396 | } |
397 | |
398 | priv->mipi = v4l2_ep.bus.mipi_csi2; |
399 | |
400 | return 0; |
401 | } |
402 | |
403 | static const struct regmap_config max96712_i2c_regmap = { |
404 | .reg_bits = 16, |
405 | .val_bits = 8, |
406 | .max_register = 0x1f00, |
407 | }; |
408 | |
409 | static int max96712_probe(struct i2c_client *client) |
410 | { |
411 | struct max96712_priv *priv; |
412 | int ret; |
413 | |
414 | priv = devm_kzalloc(dev: &client->dev, size: sizeof(*priv), GFP_KERNEL); |
415 | if (!priv) |
416 | return -ENOMEM; |
417 | |
418 | priv->info = of_device_get_match_data(dev: &client->dev); |
419 | |
420 | priv->client = client; |
421 | |
422 | priv->regmap = devm_regmap_init_i2c(client, &max96712_i2c_regmap); |
423 | if (IS_ERR(ptr: priv->regmap)) |
424 | return PTR_ERR(ptr: priv->regmap); |
425 | |
426 | priv->gpiod_pwdn = devm_gpiod_get_optional(dev: &client->dev, con_id: "enable", |
427 | flags: GPIOD_OUT_HIGH); |
428 | if (IS_ERR(ptr: priv->gpiod_pwdn)) |
429 | return PTR_ERR(ptr: priv->gpiod_pwdn); |
430 | |
431 | gpiod_set_consumer_name(desc: priv->gpiod_pwdn, name: "max96712-pwdn"); |
432 | gpiod_set_value_cansleep(desc: priv->gpiod_pwdn, value: 1); |
433 | |
434 | if (priv->gpiod_pwdn) |
435 | usleep_range(min: 4000, max: 5000); |
436 | |
437 | max96712_reset(priv); |
438 | |
439 | ret = max96712_parse_dt(priv); |
440 | if (ret) |
441 | return ret; |
442 | |
443 | max96712_mipi_configure(priv); |
444 | |
445 | return max96712_v4l2_register(priv); |
446 | } |
447 | |
448 | static void max96712_remove(struct i2c_client *client) |
449 | { |
450 | struct v4l2_subdev *sd = i2c_get_clientdata(client); |
451 | struct max96712_priv *priv = container_of(sd, struct max96712_priv, sd); |
452 | |
453 | v4l2_async_unregister_subdev(sd: &priv->sd); |
454 | |
455 | gpiod_set_value_cansleep(desc: priv->gpiod_pwdn, value: 0); |
456 | } |
457 | |
458 | static const struct max96712_info max96712_info_max96712 = { |
459 | .dpllfreq = 1000, |
460 | .have_debug_extra = true, |
461 | }; |
462 | |
463 | static const struct max96712_info max96712_info_max96724 = { |
464 | .dpllfreq = 1200, |
465 | }; |
466 | |
467 | static const struct of_device_id max96712_of_table[] = { |
468 | { .compatible = "maxim,max96712", .data = &max96712_info_max96712 }, |
469 | { .compatible = "maxim,max96724", .data = &max96712_info_max96724 }, |
470 | { /* sentinel */ } |
471 | }; |
472 | MODULE_DEVICE_TABLE(of, max96712_of_table); |
473 | |
474 | static struct i2c_driver max96712_i2c_driver = { |
475 | .driver = { |
476 | .name = "max96712", |
477 | .of_match_table = of_match_ptr(max96712_of_table), |
478 | }, |
479 | .probe = max96712_probe, |
480 | .remove = max96712_remove, |
481 | }; |
482 | |
483 | module_i2c_driver(max96712_i2c_driver); |
484 | |
485 | MODULE_DESCRIPTION("Maxim MAX96712 Quad GMSL2 Deserializer Driver"); |
486 | MODULE_AUTHOR("Niklas Söderlund <niklas.soderlund@ragnatech.se>"); |
487 | MODULE_LICENSE("GPL"); |
488 |
Definitions
- max96712_pattern
- max96712_info
- max96712_priv
- max96712_write
- max96712_update_bits
- max96712_write_bulk
- max96712_write_bulk_value
- max96712_reset
- max96712_mipi_enable
- max96712_mipi_configure
- max96712_pattern_enable
- max96712_s_stream
- max96712_video_ops
- max96712_init_state
- max96712_internal_ops
- max96712_pad_ops
- max96712_subdev_ops
- max96712_test_pattern
- max96712_s_ctrl
- max96712_ctrl_ops
- max96712_v4l2_register
- max96712_parse_dt
- max96712_i2c_regmap
- max96712_probe
- max96712_remove
- max96712_info_max96712
- max96712_info_max96724
- max96712_of_table
Improve your Profiling and Debugging skills
Find out more