1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2021-2023 Digiteq Automotive |
4 | * author: Martin Tuma <martin.tuma@digiteqautomotive.com> |
5 | * |
6 | * This module handles all the sysfs info/configuration that is related to the |
7 | * v4l2 output devices. |
8 | */ |
9 | |
10 | #include <linux/device.h> |
11 | #include <linux/nospec.h> |
12 | #include "mgb4_core.h" |
13 | #include "mgb4_i2c.h" |
14 | #include "mgb4_vout.h" |
15 | #include "mgb4_vin.h" |
16 | #include "mgb4_cmt.h" |
17 | #include "mgb4_sysfs.h" |
18 | |
19 | static int loopin_cnt(struct mgb4_vin_dev *vindev) |
20 | { |
21 | struct mgb4_vout_dev *voutdev; |
22 | u32 config; |
23 | int i, cnt = 0; |
24 | |
25 | for (i = 0; i < MGB4_VOUT_DEVICES; i++) { |
26 | voutdev = vindev->mgbdev->vout[i]; |
27 | if (!voutdev) |
28 | continue; |
29 | |
30 | config = mgb4_read_reg(&voutdev->mgbdev->video, |
31 | voutdev->config->regs.config); |
32 | if ((config & 0xc) >> 2 == vindev->config->id) |
33 | cnt++; |
34 | } |
35 | |
36 | return cnt; |
37 | } |
38 | |
39 | static bool is_busy(struct video_device *dev) |
40 | { |
41 | bool ret; |
42 | |
43 | mutex_lock(dev->lock); |
44 | ret = vb2_is_busy(q: dev->queue); |
45 | mutex_unlock(lock: dev->lock); |
46 | |
47 | return ret; |
48 | } |
49 | |
50 | /* Common for both FPDL3 and GMSL */ |
51 | |
52 | static ssize_t output_id_show(struct device *dev, |
53 | struct device_attribute *attr, char *buf) |
54 | { |
55 | struct video_device *vdev = to_video_device(dev); |
56 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
57 | |
58 | return sprintf(buf, fmt: "%d\n" , voutdev->config->id); |
59 | } |
60 | |
61 | static ssize_t video_source_show(struct device *dev, |
62 | struct device_attribute *attr, char *buf) |
63 | { |
64 | struct video_device *vdev = to_video_device(dev); |
65 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
66 | u32 config = mgb4_read_reg(&voutdev->mgbdev->video, |
67 | voutdev->config->regs.config); |
68 | |
69 | return sprintf(buf, fmt: "%u\n" , (config & 0xc) >> 2); |
70 | } |
71 | |
72 | /* |
73 | * Video source change may affect the buffer queue of ANY video input/output on |
74 | * the card thus if any of the inputs/outputs is in use, we do not allow |
75 | * the change. |
76 | * |
77 | * As we do not want to lock all the video devices at the same time, a two-stage |
78 | * locking strategy is used. In addition to the video device locking there is |
79 | * a global (PCI device) variable "io_reconfig" atomically checked/set when |
80 | * the reconfiguration is running. All the video devices check the variable in |
81 | * their queue_setup() functions and do not allow to start the queue when |
82 | * the reconfiguration has started. |
83 | */ |
84 | static ssize_t video_source_store(struct device *dev, |
85 | struct device_attribute *attr, |
86 | const char *buf, size_t count) |
87 | { |
88 | struct video_device *vdev = to_video_device(dev); |
89 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
90 | struct mgb4_dev *mgbdev = voutdev->mgbdev; |
91 | struct mgb4_vin_dev *loopin_new = NULL, *loopin_old = NULL; |
92 | unsigned long val; |
93 | ssize_t ret; |
94 | u32 config; |
95 | int i; |
96 | |
97 | ret = kstrtoul(s: buf, base: 10, res: &val); |
98 | if (ret) |
99 | return ret; |
100 | if (val > 3) |
101 | return -EINVAL; |
102 | |
103 | if (test_and_set_bit(nr: 0, addr: &mgbdev->io_reconfig)) |
104 | return -EBUSY; |
105 | |
106 | ret = -EBUSY; |
107 | for (i = 0; i < MGB4_VIN_DEVICES; i++) |
108 | if (mgbdev->vin[i] && is_busy(dev: &mgbdev->vin[i]->vdev)) |
109 | goto end; |
110 | for (i = 0; i < MGB4_VOUT_DEVICES; i++) |
111 | if (mgbdev->vout[i] && is_busy(dev: &mgbdev->vout[i]->vdev)) |
112 | goto end; |
113 | |
114 | config = mgb4_read_reg(&mgbdev->video, voutdev->config->regs.config); |
115 | |
116 | if (((config & 0xc) >> 2) < MGB4_VIN_DEVICES) |
117 | loopin_old = mgbdev->vin[(config & 0xc) >> 2]; |
118 | if (val < MGB4_VIN_DEVICES) { |
119 | val = array_index_nospec(val, MGB4_VIN_DEVICES); |
120 | loopin_new = mgbdev->vin[val]; |
121 | } |
122 | if (loopin_old && loopin_cnt(vindev: loopin_old) == 1) |
123 | mgb4_mask_reg(regs: &mgbdev->video, reg: loopin_old->config->regs.config, |
124 | mask: 0x2, val: 0x0); |
125 | if (loopin_new) |
126 | mgb4_mask_reg(regs: &mgbdev->video, reg: loopin_new->config->regs.config, |
127 | mask: 0x2, val: 0x2); |
128 | |
129 | if (val == voutdev->config->id + MGB4_VIN_DEVICES) |
130 | mgb4_write_reg(&mgbdev->video, voutdev->config->regs.config, |
131 | config & ~(1 << 1)); |
132 | else |
133 | mgb4_write_reg(&mgbdev->video, voutdev->config->regs.config, |
134 | config | (1U << 1)); |
135 | |
136 | mgb4_mask_reg(regs: &mgbdev->video, reg: voutdev->config->regs.config, mask: 0xc, |
137 | val: val << 2); |
138 | |
139 | ret = count; |
140 | end: |
141 | clear_bit(nr: 0, addr: &mgbdev->io_reconfig); |
142 | |
143 | return ret; |
144 | } |
145 | |
146 | static ssize_t display_width_show(struct device *dev, |
147 | struct device_attribute *attr, char *buf) |
148 | { |
149 | struct video_device *vdev = to_video_device(dev); |
150 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
151 | u32 config = mgb4_read_reg(&voutdev->mgbdev->video, |
152 | voutdev->config->regs.resolution); |
153 | |
154 | return sprintf(buf, fmt: "%u\n" , config >> 16); |
155 | } |
156 | |
157 | static ssize_t display_width_store(struct device *dev, |
158 | struct device_attribute *attr, |
159 | const char *buf, size_t count) |
160 | { |
161 | struct video_device *vdev = to_video_device(dev); |
162 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
163 | unsigned long val; |
164 | int ret; |
165 | |
166 | ret = kstrtoul(s: buf, base: 10, res: &val); |
167 | if (ret) |
168 | return ret; |
169 | if (val > 0xFFFF) |
170 | return -EINVAL; |
171 | |
172 | mutex_lock(voutdev->vdev.lock); |
173 | if (vb2_is_busy(q: voutdev->vdev.queue)) { |
174 | mutex_unlock(lock: voutdev->vdev.lock); |
175 | return -EBUSY; |
176 | } |
177 | |
178 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.resolution, |
179 | mask: 0xFFFF0000, val: val << 16); |
180 | |
181 | mutex_unlock(lock: voutdev->vdev.lock); |
182 | |
183 | return count; |
184 | } |
185 | |
186 | static ssize_t display_height_show(struct device *dev, |
187 | struct device_attribute *attr, char *buf) |
188 | { |
189 | struct video_device *vdev = to_video_device(dev); |
190 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
191 | u32 config = mgb4_read_reg(&voutdev->mgbdev->video, |
192 | voutdev->config->regs.resolution); |
193 | |
194 | return sprintf(buf, fmt: "%u\n" , config & 0xFFFF); |
195 | } |
196 | |
197 | static ssize_t display_height_store(struct device *dev, |
198 | struct device_attribute *attr, |
199 | const char *buf, size_t count) |
200 | { |
201 | struct video_device *vdev = to_video_device(dev); |
202 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
203 | unsigned long val; |
204 | int ret; |
205 | |
206 | ret = kstrtoul(s: buf, base: 10, res: &val); |
207 | if (ret) |
208 | return ret; |
209 | if (val > 0xFFFF) |
210 | return -EINVAL; |
211 | |
212 | mutex_lock(voutdev->vdev.lock); |
213 | if (vb2_is_busy(q: voutdev->vdev.queue)) { |
214 | mutex_unlock(lock: voutdev->vdev.lock); |
215 | return -EBUSY; |
216 | } |
217 | |
218 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.resolution, |
219 | mask: 0xFFFF, val); |
220 | |
221 | mutex_unlock(lock: voutdev->vdev.lock); |
222 | |
223 | return count; |
224 | } |
225 | |
226 | static ssize_t frame_rate_show(struct device *dev, |
227 | struct device_attribute *attr, char *buf) |
228 | { |
229 | struct video_device *vdev = to_video_device(dev); |
230 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
231 | u32 period = mgb4_read_reg(&voutdev->mgbdev->video, |
232 | voutdev->config->regs.frame_period); |
233 | |
234 | return sprintf(buf, fmt: "%u\n" , 125000000 / period); |
235 | } |
236 | |
237 | /* |
238 | * Frame rate change is expected to be called on live streams. Video device |
239 | * locking/queue check is not needed. |
240 | */ |
241 | static ssize_t frame_rate_store(struct device *dev, |
242 | struct device_attribute *attr, const char *buf, |
243 | size_t count) |
244 | { |
245 | struct video_device *vdev = to_video_device(dev); |
246 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
247 | unsigned long val; |
248 | int ret; |
249 | |
250 | ret = kstrtoul(s: buf, base: 10, res: &val); |
251 | if (ret) |
252 | return ret; |
253 | |
254 | mgb4_write_reg(&voutdev->mgbdev->video, |
255 | voutdev->config->regs.frame_period, 125000000 / val); |
256 | |
257 | return count; |
258 | } |
259 | |
260 | static ssize_t hsync_width_show(struct device *dev, |
261 | struct device_attribute *attr, char *buf) |
262 | { |
263 | struct video_device *vdev = to_video_device(dev); |
264 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
265 | u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, |
266 | voutdev->config->regs.hsync); |
267 | |
268 | return sprintf(buf, fmt: "%u\n" , (sig & 0x00FF0000) >> 16); |
269 | } |
270 | |
271 | /* |
272 | * HSYNC width change is expected to be called on live streams. Video device |
273 | * locking/queue check is not needed. |
274 | */ |
275 | static ssize_t hsync_width_store(struct device *dev, |
276 | struct device_attribute *attr, const char *buf, |
277 | size_t count) |
278 | { |
279 | struct video_device *vdev = to_video_device(dev); |
280 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
281 | unsigned long val; |
282 | int ret; |
283 | |
284 | ret = kstrtoul(s: buf, base: 10, res: &val); |
285 | if (ret) |
286 | return ret; |
287 | if (val > 0xFF) |
288 | return -EINVAL; |
289 | |
290 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.hsync, |
291 | mask: 0x00FF0000, val: val << 16); |
292 | |
293 | return count; |
294 | } |
295 | |
296 | static ssize_t vsync_width_show(struct device *dev, |
297 | struct device_attribute *attr, char *buf) |
298 | { |
299 | struct video_device *vdev = to_video_device(dev); |
300 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
301 | u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, |
302 | voutdev->config->regs.vsync); |
303 | |
304 | return sprintf(buf, fmt: "%u\n" , (sig & 0x00FF0000) >> 16); |
305 | } |
306 | |
307 | /* |
308 | * VSYNC vidth change is expected to be called on live streams. Video device |
309 | * locking/queue check is not needed. |
310 | */ |
311 | static ssize_t vsync_width_store(struct device *dev, |
312 | struct device_attribute *attr, const char *buf, |
313 | size_t count) |
314 | { |
315 | struct video_device *vdev = to_video_device(dev); |
316 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
317 | unsigned long val; |
318 | int ret; |
319 | |
320 | ret = kstrtoul(s: buf, base: 10, res: &val); |
321 | if (ret) |
322 | return ret; |
323 | if (val > 0xFF) |
324 | return -EINVAL; |
325 | |
326 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.vsync, |
327 | mask: 0x00FF0000, val: val << 16); |
328 | |
329 | return count; |
330 | } |
331 | |
332 | static ssize_t hback_porch_show(struct device *dev, |
333 | struct device_attribute *attr, char *buf) |
334 | { |
335 | struct video_device *vdev = to_video_device(dev); |
336 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
337 | u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, |
338 | voutdev->config->regs.hsync); |
339 | |
340 | return sprintf(buf, fmt: "%u\n" , (sig & 0x0000FF00) >> 8); |
341 | } |
342 | |
343 | /* |
344 | * hback porch change is expected to be called on live streams. Video device |
345 | * locking/queue check is not needed. |
346 | */ |
347 | static ssize_t hback_porch_store(struct device *dev, |
348 | struct device_attribute *attr, const char *buf, |
349 | size_t count) |
350 | { |
351 | struct video_device *vdev = to_video_device(dev); |
352 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
353 | unsigned long val; |
354 | int ret; |
355 | |
356 | ret = kstrtoul(s: buf, base: 10, res: &val); |
357 | if (ret) |
358 | return ret; |
359 | if (val > 0xFF) |
360 | return -EINVAL; |
361 | |
362 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.hsync, |
363 | mask: 0x0000FF00, val: val << 8); |
364 | |
365 | return count; |
366 | } |
367 | |
368 | static ssize_t vback_porch_show(struct device *dev, |
369 | struct device_attribute *attr, char *buf) |
370 | { |
371 | struct video_device *vdev = to_video_device(dev); |
372 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
373 | u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, |
374 | voutdev->config->regs.vsync); |
375 | |
376 | return sprintf(buf, fmt: "%u\n" , (sig & 0x0000FF00) >> 8); |
377 | } |
378 | |
379 | /* |
380 | * vback porch change is expected to be called on live streams. Video device |
381 | * locking/queue check is not needed. |
382 | */ |
383 | static ssize_t vback_porch_store(struct device *dev, |
384 | struct device_attribute *attr, const char *buf, |
385 | size_t count) |
386 | { |
387 | struct video_device *vdev = to_video_device(dev); |
388 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
389 | unsigned long val; |
390 | int ret; |
391 | |
392 | ret = kstrtoul(s: buf, base: 10, res: &val); |
393 | if (ret) |
394 | return ret; |
395 | if (val > 0xFF) |
396 | return -EINVAL; |
397 | |
398 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.vsync, |
399 | mask: 0x0000FF00, val: val << 8); |
400 | |
401 | return count; |
402 | } |
403 | |
404 | static ssize_t hfront_porch_show(struct device *dev, |
405 | struct device_attribute *attr, char *buf) |
406 | { |
407 | struct video_device *vdev = to_video_device(dev); |
408 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
409 | u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, |
410 | voutdev->config->regs.hsync); |
411 | |
412 | return sprintf(buf, fmt: "%u\n" , (sig & 0x000000FF)); |
413 | } |
414 | |
415 | /* |
416 | * hfront porch change is expected to be called on live streams. Video device |
417 | * locking/queue check is not needed. |
418 | */ |
419 | static ssize_t hfront_porch_store(struct device *dev, |
420 | struct device_attribute *attr, |
421 | const char *buf, size_t count) |
422 | { |
423 | struct video_device *vdev = to_video_device(dev); |
424 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
425 | unsigned long val; |
426 | int ret; |
427 | |
428 | ret = kstrtoul(s: buf, base: 10, res: &val); |
429 | if (ret) |
430 | return ret; |
431 | if (val > 0xFF) |
432 | return -EINVAL; |
433 | |
434 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.hsync, |
435 | mask: 0x000000FF, val); |
436 | |
437 | return count; |
438 | } |
439 | |
440 | static ssize_t vfront_porch_show(struct device *dev, |
441 | struct device_attribute *attr, char *buf) |
442 | { |
443 | struct video_device *vdev = to_video_device(dev); |
444 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
445 | u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, |
446 | voutdev->config->regs.vsync); |
447 | |
448 | return sprintf(buf, fmt: "%u\n" , (sig & 0x000000FF)); |
449 | } |
450 | |
451 | /* |
452 | * vfront porch change is expected to be called on live streams. Video device |
453 | * locking/queue check is not needed. |
454 | */ |
455 | static ssize_t vfront_porch_store(struct device *dev, |
456 | struct device_attribute *attr, const char *buf, |
457 | size_t count) |
458 | { |
459 | struct video_device *vdev = to_video_device(dev); |
460 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
461 | unsigned long val; |
462 | int ret; |
463 | |
464 | ret = kstrtoul(s: buf, base: 10, res: &val); |
465 | if (ret) |
466 | return ret; |
467 | if (val > 0xFF) |
468 | return -EINVAL; |
469 | |
470 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.vsync, |
471 | mask: 0x000000FF, val); |
472 | |
473 | return count; |
474 | } |
475 | |
476 | /* FPDL3 only */ |
477 | |
478 | static ssize_t hsync_polarity_show(struct device *dev, |
479 | struct device_attribute *attr, char *buf) |
480 | { |
481 | struct video_device *vdev = to_video_device(dev); |
482 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
483 | u32 config = mgb4_read_reg(&voutdev->mgbdev->video, |
484 | voutdev->config->regs.hsync); |
485 | |
486 | return sprintf(buf, fmt: "%u\n" , (config & (1U << 31)) >> 31); |
487 | } |
488 | |
489 | /* |
490 | * HSYNC polarity change is expected to be called on live streams. Video device |
491 | * locking/queue check is not needed. |
492 | */ |
493 | static ssize_t hsync_polarity_store(struct device *dev, |
494 | struct device_attribute *attr, |
495 | const char *buf, size_t count) |
496 | { |
497 | struct video_device *vdev = to_video_device(dev); |
498 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
499 | unsigned long val; |
500 | int ret; |
501 | |
502 | ret = kstrtoul(s: buf, base: 10, res: &val); |
503 | if (ret) |
504 | return ret; |
505 | if (val > 1) |
506 | return -EINVAL; |
507 | |
508 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.hsync, |
509 | mask: (1U << 31), val: val << 31); |
510 | |
511 | return count; |
512 | } |
513 | |
514 | static ssize_t vsync_polarity_show(struct device *dev, |
515 | struct device_attribute *attr, char *buf) |
516 | { |
517 | struct video_device *vdev = to_video_device(dev); |
518 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
519 | u32 config = mgb4_read_reg(&voutdev->mgbdev->video, |
520 | voutdev->config->regs.vsync); |
521 | |
522 | return sprintf(buf, fmt: "%u\n" , (config & (1U << 31)) >> 31); |
523 | } |
524 | |
525 | /* |
526 | * VSYNC polarity change is expected to be called on live streams. Video device |
527 | * locking/queue check is not needed. |
528 | */ |
529 | static ssize_t vsync_polarity_store(struct device *dev, |
530 | struct device_attribute *attr, |
531 | const char *buf, size_t count) |
532 | { |
533 | struct video_device *vdev = to_video_device(dev); |
534 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
535 | unsigned long val; |
536 | int ret; |
537 | |
538 | ret = kstrtoul(s: buf, base: 10, res: &val); |
539 | if (ret) |
540 | return ret; |
541 | if (val > 1) |
542 | return -EINVAL; |
543 | |
544 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.vsync, |
545 | mask: (1U << 31), val: val << 31); |
546 | |
547 | return count; |
548 | } |
549 | |
550 | static ssize_t de_polarity_show(struct device *dev, |
551 | struct device_attribute *attr, char *buf) |
552 | { |
553 | struct video_device *vdev = to_video_device(dev); |
554 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
555 | u32 config = mgb4_read_reg(&voutdev->mgbdev->video, |
556 | voutdev->config->regs.vsync); |
557 | |
558 | return sprintf(buf, fmt: "%u\n" , (config & (1U << 30)) >> 30); |
559 | } |
560 | |
561 | /* |
562 | * DE polarity change is expected to be called on live streams. Video device |
563 | * locking/queue check is not needed. |
564 | */ |
565 | static ssize_t de_polarity_store(struct device *dev, |
566 | struct device_attribute *attr, const char *buf, |
567 | size_t count) |
568 | { |
569 | struct video_device *vdev = to_video_device(dev); |
570 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
571 | unsigned long val; |
572 | int ret; |
573 | |
574 | ret = kstrtoul(s: buf, base: 10, res: &val); |
575 | if (ret) |
576 | return ret; |
577 | if (val > 1) |
578 | return -EINVAL; |
579 | |
580 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.vsync, |
581 | mask: (1U << 30), val: val << 30); |
582 | |
583 | return count; |
584 | } |
585 | |
586 | static ssize_t fpdl3_output_width_show(struct device *dev, |
587 | struct device_attribute *attr, char *buf) |
588 | { |
589 | struct video_device *vdev = to_video_device(dev); |
590 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
591 | s32 ret; |
592 | |
593 | mutex_lock(&voutdev->mgbdev->i2c_lock); |
594 | ret = mgb4_i2c_read_byte(client: &voutdev->ser, reg: 0x5B); |
595 | mutex_unlock(lock: &voutdev->mgbdev->i2c_lock); |
596 | if (ret < 0) |
597 | return -EIO; |
598 | |
599 | switch ((u8)ret & 0x03) { |
600 | case 0: |
601 | return sprintf(buf, fmt: "0\n" ); |
602 | case 1: |
603 | return sprintf(buf, fmt: "1\n" ); |
604 | case 3: |
605 | return sprintf(buf, fmt: "2\n" ); |
606 | default: |
607 | return -EINVAL; |
608 | } |
609 | } |
610 | |
611 | /* |
612 | * FPD-Link width change is expected to be called on live streams. Video device |
613 | * locking/queue check is not needed. |
614 | */ |
615 | static ssize_t fpdl3_output_width_store(struct device *dev, |
616 | struct device_attribute *attr, |
617 | const char *buf, size_t count) |
618 | { |
619 | struct video_device *vdev = to_video_device(dev); |
620 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
621 | u8 i2c_data; |
622 | unsigned long val; |
623 | int ret; |
624 | |
625 | ret = kstrtoul(s: buf, base: 10, res: &val); |
626 | if (ret) |
627 | return ret; |
628 | |
629 | switch (val) { |
630 | case 0: /* auto */ |
631 | i2c_data = 0x00; |
632 | break; |
633 | case 1: /* single */ |
634 | i2c_data = 0x01; |
635 | break; |
636 | case 2: /* dual */ |
637 | i2c_data = 0x03; |
638 | break; |
639 | default: |
640 | return -EINVAL; |
641 | } |
642 | |
643 | mutex_lock(&voutdev->mgbdev->i2c_lock); |
644 | ret = mgb4_i2c_mask_byte(client: &voutdev->ser, reg: 0x5B, mask: 0x03, val: i2c_data); |
645 | mutex_unlock(lock: &voutdev->mgbdev->i2c_lock); |
646 | if (ret < 0) |
647 | return -EIO; |
648 | |
649 | return count; |
650 | } |
651 | |
652 | static ssize_t pclk_frequency_show(struct device *dev, |
653 | struct device_attribute *attr, char *buf) |
654 | { |
655 | struct video_device *vdev = to_video_device(dev); |
656 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
657 | |
658 | return sprintf(buf, fmt: "%u\n" , voutdev->freq); |
659 | } |
660 | |
661 | static ssize_t pclk_frequency_store(struct device *dev, |
662 | struct device_attribute *attr, |
663 | const char *buf, size_t count) |
664 | { |
665 | struct video_device *vdev = to_video_device(dev); |
666 | struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); |
667 | unsigned long val; |
668 | int ret; |
669 | unsigned int dp; |
670 | |
671 | ret = kstrtoul(s: buf, base: 10, res: &val); |
672 | if (ret) |
673 | return ret; |
674 | |
675 | mutex_lock(voutdev->vdev.lock); |
676 | if (vb2_is_busy(q: voutdev->vdev.queue)) { |
677 | mutex_unlock(lock: voutdev->vdev.lock); |
678 | return -EBUSY; |
679 | } |
680 | |
681 | dp = (val > 50000) ? 1 : 0; |
682 | voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, freq: val >> dp) << dp; |
683 | |
684 | mgb4_mask_reg(regs: &voutdev->mgbdev->video, reg: voutdev->config->regs.config, |
685 | mask: 0x10, val: dp << 4); |
686 | mutex_lock(&voutdev->mgbdev->i2c_lock); |
687 | ret = mgb4_i2c_mask_byte(client: &voutdev->ser, reg: 0x4F, mask: 1 << 6, val: ((~dp) & 1) << 6); |
688 | mutex_unlock(lock: &voutdev->mgbdev->i2c_lock); |
689 | |
690 | mutex_unlock(lock: voutdev->vdev.lock); |
691 | |
692 | return (ret < 0) ? -EIO : count; |
693 | } |
694 | |
695 | static DEVICE_ATTR_RO(output_id); |
696 | static DEVICE_ATTR_RW(video_source); |
697 | static DEVICE_ATTR_RW(display_width); |
698 | static DEVICE_ATTR_RW(display_height); |
699 | static DEVICE_ATTR_RW(frame_rate); |
700 | static DEVICE_ATTR_RW(hsync_polarity); |
701 | static DEVICE_ATTR_RW(vsync_polarity); |
702 | static DEVICE_ATTR_RW(de_polarity); |
703 | static DEVICE_ATTR_RW(pclk_frequency); |
704 | static DEVICE_ATTR_RW(hsync_width); |
705 | static DEVICE_ATTR_RW(vsync_width); |
706 | static DEVICE_ATTR_RW(hback_porch); |
707 | static DEVICE_ATTR_RW(hfront_porch); |
708 | static DEVICE_ATTR_RW(vback_porch); |
709 | static DEVICE_ATTR_RW(vfront_porch); |
710 | |
711 | static DEVICE_ATTR_RW(fpdl3_output_width); |
712 | |
713 | struct attribute *mgb4_fpdl3_out_attrs[] = { |
714 | &dev_attr_output_id.attr, |
715 | &dev_attr_video_source.attr, |
716 | &dev_attr_display_width.attr, |
717 | &dev_attr_display_height.attr, |
718 | &dev_attr_frame_rate.attr, |
719 | &dev_attr_hsync_polarity.attr, |
720 | &dev_attr_vsync_polarity.attr, |
721 | &dev_attr_de_polarity.attr, |
722 | &dev_attr_pclk_frequency.attr, |
723 | &dev_attr_hsync_width.attr, |
724 | &dev_attr_vsync_width.attr, |
725 | &dev_attr_hback_porch.attr, |
726 | &dev_attr_hfront_porch.attr, |
727 | &dev_attr_vback_porch.attr, |
728 | &dev_attr_vfront_porch.attr, |
729 | &dev_attr_fpdl3_output_width.attr, |
730 | NULL |
731 | }; |
732 | |
733 | struct attribute *mgb4_gmsl_out_attrs[] = { |
734 | &dev_attr_output_id.attr, |
735 | &dev_attr_video_source.attr, |
736 | &dev_attr_display_width.attr, |
737 | &dev_attr_display_height.attr, |
738 | &dev_attr_frame_rate.attr, |
739 | NULL |
740 | }; |
741 | |