1 | // SPDX-License-Identifier: MIT |
2 | /* |
3 | * Copyright 2020 Noralf Trønnes |
4 | */ |
5 | |
6 | #include <linux/backlight.h> |
7 | #include <linux/workqueue.h> |
8 | |
9 | #include <drm/drm_atomic.h> |
10 | #include <drm/drm_atomic_state_helper.h> |
11 | #include <drm/drm_connector.h> |
12 | #include <drm/drm_drv.h> |
13 | #include <drm/drm_edid.h> |
14 | #include <drm/drm_encoder.h> |
15 | #include <drm/drm_file.h> |
16 | #include <drm/drm_modeset_helper_vtables.h> |
17 | #include <drm/drm_print.h> |
18 | #include <drm/drm_probe_helper.h> |
19 | #include <drm/drm_simple_kms_helper.h> |
20 | #include <drm/gud.h> |
21 | |
22 | #include "gud_internal.h" |
23 | |
24 | struct gud_connector { |
25 | struct drm_connector connector; |
26 | struct drm_encoder encoder; |
27 | struct backlight_device *backlight; |
28 | struct work_struct backlight_work; |
29 | |
30 | /* Supported properties */ |
31 | u16 *properties; |
32 | unsigned int num_properties; |
33 | |
34 | /* Initial gadget tv state if applicable, applied on state reset */ |
35 | struct drm_tv_connector_state initial_tv_state; |
36 | |
37 | /* |
38 | * Initial gadget backlight brightness if applicable, applied on state reset. |
39 | * The value -ENODEV is used to signal no backlight. |
40 | */ |
41 | int initial_brightness; |
42 | }; |
43 | |
44 | static inline struct gud_connector *to_gud_connector(struct drm_connector *connector) |
45 | { |
46 | return container_of(connector, struct gud_connector, connector); |
47 | } |
48 | |
49 | static void gud_conn_err(struct drm_connector *connector, const char *msg, int ret) |
50 | { |
51 | dev_err(connector->dev->dev, "%s: %s (ret=%d)\n" , connector->name, msg, ret); |
52 | } |
53 | |
54 | /* |
55 | * Use a worker to avoid taking kms locks inside the backlight lock. |
56 | * Other display drivers use backlight within their kms locks. |
57 | * This avoids inconsistent locking rules, which would upset lockdep. |
58 | */ |
59 | static void gud_connector_backlight_update_status_work(struct work_struct *work) |
60 | { |
61 | struct gud_connector *gconn = container_of(work, struct gud_connector, backlight_work); |
62 | struct drm_connector *connector = &gconn->connector; |
63 | struct drm_connector_state *connector_state; |
64 | struct drm_device *drm = connector->dev; |
65 | struct drm_modeset_acquire_ctx ctx; |
66 | struct drm_atomic_state *state; |
67 | int idx, ret; |
68 | |
69 | if (!drm_dev_enter(dev: drm, idx: &idx)) |
70 | return; |
71 | |
72 | state = drm_atomic_state_alloc(dev: drm); |
73 | if (!state) { |
74 | ret = -ENOMEM; |
75 | goto exit; |
76 | } |
77 | |
78 | drm_modeset_acquire_init(ctx: &ctx, flags: 0); |
79 | state->acquire_ctx = &ctx; |
80 | retry: |
81 | connector_state = drm_atomic_get_connector_state(state, connector); |
82 | if (IS_ERR(ptr: connector_state)) { |
83 | ret = PTR_ERR(ptr: connector_state); |
84 | goto out; |
85 | } |
86 | |
87 | /* Reuse tv.brightness to avoid having to subclass */ |
88 | connector_state->tv.brightness = gconn->backlight->props.brightness; |
89 | |
90 | ret = drm_atomic_commit(state); |
91 | out: |
92 | if (ret == -EDEADLK) { |
93 | drm_atomic_state_clear(state); |
94 | drm_modeset_backoff(ctx: &ctx); |
95 | goto retry; |
96 | } |
97 | |
98 | drm_atomic_state_put(state); |
99 | |
100 | drm_modeset_drop_locks(ctx: &ctx); |
101 | drm_modeset_acquire_fini(ctx: &ctx); |
102 | exit: |
103 | drm_dev_exit(idx); |
104 | |
105 | if (ret) |
106 | dev_err(drm->dev, "Failed to update backlight, err=%d\n" , ret); |
107 | } |
108 | |
109 | static int gud_connector_backlight_update_status(struct backlight_device *bd) |
110 | { |
111 | struct drm_connector *connector = bl_get_data(bl_dev: bd); |
112 | struct gud_connector *gconn = to_gud_connector(connector); |
113 | |
114 | /* The USB timeout is 5 seconds so use system_long_wq for worst case scenario */ |
115 | queue_work(wq: system_long_wq, work: &gconn->backlight_work); |
116 | |
117 | return 0; |
118 | } |
119 | |
120 | static const struct backlight_ops gud_connector_backlight_ops = { |
121 | .update_status = gud_connector_backlight_update_status, |
122 | }; |
123 | |
124 | static int gud_connector_backlight_register(struct gud_connector *gconn) |
125 | { |
126 | struct drm_connector *connector = &gconn->connector; |
127 | struct backlight_device *bd; |
128 | const char *name; |
129 | const struct backlight_properties props = { |
130 | .type = BACKLIGHT_RAW, |
131 | .scale = BACKLIGHT_SCALE_NON_LINEAR, |
132 | .max_brightness = 100, |
133 | .brightness = gconn->initial_brightness, |
134 | }; |
135 | |
136 | name = kasprintf(GFP_KERNEL, fmt: "card%d-%s-backlight" , |
137 | connector->dev->primary->index, connector->name); |
138 | if (!name) |
139 | return -ENOMEM; |
140 | |
141 | bd = backlight_device_register(name, dev: connector->kdev, devdata: connector, |
142 | ops: &gud_connector_backlight_ops, props: &props); |
143 | kfree(objp: name); |
144 | if (IS_ERR(ptr: bd)) |
145 | return PTR_ERR(ptr: bd); |
146 | |
147 | gconn->backlight = bd; |
148 | |
149 | return 0; |
150 | } |
151 | |
152 | static int gud_connector_detect(struct drm_connector *connector, |
153 | struct drm_modeset_acquire_ctx *ctx, bool force) |
154 | { |
155 | struct gud_device *gdrm = to_gud_device(drm: connector->dev); |
156 | int idx, ret; |
157 | u8 status; |
158 | |
159 | if (!drm_dev_enter(dev: connector->dev, idx: &idx)) |
160 | return connector_status_disconnected; |
161 | |
162 | if (force) { |
163 | ret = gud_usb_set(gdrm, GUD_REQ_SET_CONNECTOR_FORCE_DETECT, |
164 | index: connector->index, NULL, len: 0); |
165 | if (ret) { |
166 | ret = connector_status_unknown; |
167 | goto exit; |
168 | } |
169 | } |
170 | |
171 | ret = gud_usb_get_u8(gdrm, GUD_REQ_GET_CONNECTOR_STATUS, index: connector->index, val: &status); |
172 | if (ret) { |
173 | ret = connector_status_unknown; |
174 | goto exit; |
175 | } |
176 | |
177 | switch (status & GUD_CONNECTOR_STATUS_CONNECTED_MASK) { |
178 | case GUD_CONNECTOR_STATUS_DISCONNECTED: |
179 | ret = connector_status_disconnected; |
180 | break; |
181 | case GUD_CONNECTOR_STATUS_CONNECTED: |
182 | ret = connector_status_connected; |
183 | break; |
184 | default: |
185 | ret = connector_status_unknown; |
186 | break; |
187 | } |
188 | |
189 | if (status & GUD_CONNECTOR_STATUS_CHANGED) |
190 | connector->epoch_counter += 1; |
191 | exit: |
192 | drm_dev_exit(idx); |
193 | |
194 | return ret; |
195 | } |
196 | |
197 | struct gud_connector_get_edid_ctx { |
198 | void *buf; |
199 | size_t len; |
200 | bool edid_override; |
201 | }; |
202 | |
203 | static int gud_connector_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len) |
204 | { |
205 | struct gud_connector_get_edid_ctx *ctx = data; |
206 | size_t start = block * EDID_LENGTH; |
207 | |
208 | ctx->edid_override = false; |
209 | |
210 | if (start + len > ctx->len) |
211 | return -1; |
212 | |
213 | memcpy(buf, ctx->buf + start, len); |
214 | |
215 | return 0; |
216 | } |
217 | |
218 | static int gud_connector_get_modes(struct drm_connector *connector) |
219 | { |
220 | struct gud_device *gdrm = to_gud_device(drm: connector->dev); |
221 | struct gud_display_mode_req *reqmodes = NULL; |
222 | struct gud_connector_get_edid_ctx edid_ctx; |
223 | unsigned int i, num_modes = 0; |
224 | struct edid *edid = NULL; |
225 | int idx, ret; |
226 | |
227 | if (!drm_dev_enter(dev: connector->dev, idx: &idx)) |
228 | return 0; |
229 | |
230 | edid_ctx.edid_override = true; |
231 | edid_ctx.buf = kmalloc(GUD_CONNECTOR_MAX_EDID_LEN, GFP_KERNEL); |
232 | if (!edid_ctx.buf) |
233 | goto out; |
234 | |
235 | ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_EDID, index: connector->index, |
236 | buf: edid_ctx.buf, GUD_CONNECTOR_MAX_EDID_LEN); |
237 | if (ret > 0 && ret % EDID_LENGTH) { |
238 | gud_conn_err(connector, msg: "Invalid EDID size" , ret); |
239 | } else if (ret > 0) { |
240 | edid_ctx.len = ret; |
241 | edid = drm_do_get_edid(connector, get_edid_block: gud_connector_get_edid_block, data: &edid_ctx); |
242 | } |
243 | |
244 | kfree(objp: edid_ctx.buf); |
245 | drm_connector_update_edid_property(connector, edid); |
246 | |
247 | if (edid && edid_ctx.edid_override) |
248 | goto out; |
249 | |
250 | reqmodes = kmalloc_array(GUD_CONNECTOR_MAX_NUM_MODES, size: sizeof(*reqmodes), GFP_KERNEL); |
251 | if (!reqmodes) |
252 | goto out; |
253 | |
254 | ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_MODES, index: connector->index, |
255 | buf: reqmodes, GUD_CONNECTOR_MAX_NUM_MODES * sizeof(*reqmodes)); |
256 | if (ret <= 0) |
257 | goto out; |
258 | if (ret % sizeof(*reqmodes)) { |
259 | gud_conn_err(connector, msg: "Invalid display mode array size" , ret); |
260 | goto out; |
261 | } |
262 | |
263 | num_modes = ret / sizeof(*reqmodes); |
264 | |
265 | for (i = 0; i < num_modes; i++) { |
266 | struct drm_display_mode *mode; |
267 | |
268 | mode = drm_mode_create(dev: connector->dev); |
269 | if (!mode) { |
270 | num_modes = i; |
271 | goto out; |
272 | } |
273 | |
274 | gud_to_display_mode(dst: mode, src: &reqmodes[i]); |
275 | drm_mode_probed_add(connector, mode); |
276 | } |
277 | out: |
278 | if (!num_modes) |
279 | num_modes = drm_add_edid_modes(connector, edid); |
280 | |
281 | kfree(objp: reqmodes); |
282 | kfree(objp: edid); |
283 | drm_dev_exit(idx); |
284 | |
285 | return num_modes; |
286 | } |
287 | |
288 | static int gud_connector_atomic_check(struct drm_connector *connector, |
289 | struct drm_atomic_state *state) |
290 | { |
291 | struct drm_connector_state *new_state; |
292 | struct drm_crtc_state *new_crtc_state; |
293 | struct drm_connector_state *old_state; |
294 | |
295 | new_state = drm_atomic_get_new_connector_state(state, connector); |
296 | if (!new_state->crtc) |
297 | return 0; |
298 | |
299 | old_state = drm_atomic_get_old_connector_state(state, connector); |
300 | new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc: new_state->crtc); |
301 | |
302 | if (old_state->tv.margins.left != new_state->tv.margins.left || |
303 | old_state->tv.margins.right != new_state->tv.margins.right || |
304 | old_state->tv.margins.top != new_state->tv.margins.top || |
305 | old_state->tv.margins.bottom != new_state->tv.margins.bottom || |
306 | old_state->tv.legacy_mode != new_state->tv.legacy_mode || |
307 | old_state->tv.brightness != new_state->tv.brightness || |
308 | old_state->tv.contrast != new_state->tv.contrast || |
309 | old_state->tv.flicker_reduction != new_state->tv.flicker_reduction || |
310 | old_state->tv.overscan != new_state->tv.overscan || |
311 | old_state->tv.saturation != new_state->tv.saturation || |
312 | old_state->tv.hue != new_state->tv.hue) |
313 | new_crtc_state->connectors_changed = true; |
314 | |
315 | return 0; |
316 | } |
317 | |
318 | static const struct drm_connector_helper_funcs gud_connector_helper_funcs = { |
319 | .detect_ctx = gud_connector_detect, |
320 | .get_modes = gud_connector_get_modes, |
321 | .atomic_check = gud_connector_atomic_check, |
322 | }; |
323 | |
324 | static int gud_connector_late_register(struct drm_connector *connector) |
325 | { |
326 | struct gud_connector *gconn = to_gud_connector(connector); |
327 | |
328 | if (gconn->initial_brightness < 0) |
329 | return 0; |
330 | |
331 | return gud_connector_backlight_register(gconn); |
332 | } |
333 | |
334 | static void gud_connector_early_unregister(struct drm_connector *connector) |
335 | { |
336 | struct gud_connector *gconn = to_gud_connector(connector); |
337 | |
338 | backlight_device_unregister(bd: gconn->backlight); |
339 | cancel_work_sync(work: &gconn->backlight_work); |
340 | } |
341 | |
342 | static void gud_connector_destroy(struct drm_connector *connector) |
343 | { |
344 | struct gud_connector *gconn = to_gud_connector(connector); |
345 | |
346 | drm_connector_cleanup(connector); |
347 | kfree(objp: gconn->properties); |
348 | kfree(objp: gconn); |
349 | } |
350 | |
351 | static void gud_connector_reset(struct drm_connector *connector) |
352 | { |
353 | struct gud_connector *gconn = to_gud_connector(connector); |
354 | |
355 | drm_atomic_helper_connector_reset(connector); |
356 | connector->state->tv = gconn->initial_tv_state; |
357 | /* Set margins from command line */ |
358 | drm_atomic_helper_connector_tv_margins_reset(connector); |
359 | if (gconn->initial_brightness >= 0) |
360 | connector->state->tv.brightness = gconn->initial_brightness; |
361 | } |
362 | |
363 | static const struct drm_connector_funcs gud_connector_funcs = { |
364 | .fill_modes = drm_helper_probe_single_connector_modes, |
365 | .late_register = gud_connector_late_register, |
366 | .early_unregister = gud_connector_early_unregister, |
367 | .destroy = gud_connector_destroy, |
368 | .reset = gud_connector_reset, |
369 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
370 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
371 | }; |
372 | |
373 | /* |
374 | * The tv.mode property is shared among the connectors and its enum names are |
375 | * driver specific. This means that if more than one connector uses tv.mode, |
376 | * the enum names has to be the same. |
377 | */ |
378 | static int gud_connector_add_tv_mode(struct gud_device *gdrm, struct drm_connector *connector) |
379 | { |
380 | size_t buf_len = GUD_CONNECTOR_TV_MODE_MAX_NUM * GUD_CONNECTOR_TV_MODE_NAME_LEN; |
381 | const char *modes[GUD_CONNECTOR_TV_MODE_MAX_NUM]; |
382 | unsigned int i, num_modes; |
383 | char *buf; |
384 | int ret; |
385 | |
386 | buf = kmalloc(size: buf_len, GFP_KERNEL); |
387 | if (!buf) |
388 | return -ENOMEM; |
389 | |
390 | ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES, |
391 | index: connector->index, buf, len: buf_len); |
392 | if (ret < 0) |
393 | goto free; |
394 | if (!ret || ret % GUD_CONNECTOR_TV_MODE_NAME_LEN) { |
395 | ret = -EIO; |
396 | goto free; |
397 | } |
398 | |
399 | num_modes = ret / GUD_CONNECTOR_TV_MODE_NAME_LEN; |
400 | for (i = 0; i < num_modes; i++) |
401 | modes[i] = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN]; |
402 | |
403 | ret = drm_mode_create_tv_properties_legacy(dev: connector->dev, num_modes, modes); |
404 | free: |
405 | kfree(objp: buf); |
406 | if (ret < 0) |
407 | gud_conn_err(connector, msg: "Failed to add TV modes" , ret); |
408 | |
409 | return ret; |
410 | } |
411 | |
412 | static struct drm_property * |
413 | gud_connector_property_lookup(struct drm_connector *connector, u16 prop) |
414 | { |
415 | struct drm_mode_config *config = &connector->dev->mode_config; |
416 | |
417 | switch (prop) { |
418 | case GUD_PROPERTY_TV_LEFT_MARGIN: |
419 | return config->tv_left_margin_property; |
420 | case GUD_PROPERTY_TV_RIGHT_MARGIN: |
421 | return config->tv_right_margin_property; |
422 | case GUD_PROPERTY_TV_TOP_MARGIN: |
423 | return config->tv_top_margin_property; |
424 | case GUD_PROPERTY_TV_BOTTOM_MARGIN: |
425 | return config->tv_bottom_margin_property; |
426 | case GUD_PROPERTY_TV_MODE: |
427 | return config->legacy_tv_mode_property; |
428 | case GUD_PROPERTY_TV_BRIGHTNESS: |
429 | return config->tv_brightness_property; |
430 | case GUD_PROPERTY_TV_CONTRAST: |
431 | return config->tv_contrast_property; |
432 | case GUD_PROPERTY_TV_FLICKER_REDUCTION: |
433 | return config->tv_flicker_reduction_property; |
434 | case GUD_PROPERTY_TV_OVERSCAN: |
435 | return config->tv_overscan_property; |
436 | case GUD_PROPERTY_TV_SATURATION: |
437 | return config->tv_saturation_property; |
438 | case GUD_PROPERTY_TV_HUE: |
439 | return config->tv_hue_property; |
440 | default: |
441 | return ERR_PTR(error: -EINVAL); |
442 | } |
443 | } |
444 | |
445 | static unsigned int *gud_connector_tv_state_val(u16 prop, struct drm_tv_connector_state *state) |
446 | { |
447 | switch (prop) { |
448 | case GUD_PROPERTY_TV_LEFT_MARGIN: |
449 | return &state->margins.left; |
450 | case GUD_PROPERTY_TV_RIGHT_MARGIN: |
451 | return &state->margins.right; |
452 | case GUD_PROPERTY_TV_TOP_MARGIN: |
453 | return &state->margins.top; |
454 | case GUD_PROPERTY_TV_BOTTOM_MARGIN: |
455 | return &state->margins.bottom; |
456 | case GUD_PROPERTY_TV_MODE: |
457 | return &state->legacy_mode; |
458 | case GUD_PROPERTY_TV_BRIGHTNESS: |
459 | return &state->brightness; |
460 | case GUD_PROPERTY_TV_CONTRAST: |
461 | return &state->contrast; |
462 | case GUD_PROPERTY_TV_FLICKER_REDUCTION: |
463 | return &state->flicker_reduction; |
464 | case GUD_PROPERTY_TV_OVERSCAN: |
465 | return &state->overscan; |
466 | case GUD_PROPERTY_TV_SATURATION: |
467 | return &state->saturation; |
468 | case GUD_PROPERTY_TV_HUE: |
469 | return &state->hue; |
470 | default: |
471 | return ERR_PTR(error: -EINVAL); |
472 | } |
473 | } |
474 | |
475 | static int gud_connector_add_properties(struct gud_device *gdrm, struct gud_connector *gconn) |
476 | { |
477 | struct drm_connector *connector = &gconn->connector; |
478 | struct drm_device *drm = &gdrm->drm; |
479 | struct gud_property_req *properties; |
480 | unsigned int i, num_properties; |
481 | int ret; |
482 | |
483 | properties = kcalloc(GUD_CONNECTOR_PROPERTIES_MAX_NUM, size: sizeof(*properties), GFP_KERNEL); |
484 | if (!properties) |
485 | return -ENOMEM; |
486 | |
487 | ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_PROPERTIES, index: connector->index, |
488 | buf: properties, GUD_CONNECTOR_PROPERTIES_MAX_NUM * sizeof(*properties)); |
489 | if (ret <= 0) |
490 | goto out; |
491 | if (ret % sizeof(*properties)) { |
492 | ret = -EIO; |
493 | goto out; |
494 | } |
495 | |
496 | num_properties = ret / sizeof(*properties); |
497 | ret = 0; |
498 | |
499 | gconn->properties = kcalloc(n: num_properties, size: sizeof(*gconn->properties), GFP_KERNEL); |
500 | if (!gconn->properties) { |
501 | ret = -ENOMEM; |
502 | goto out; |
503 | } |
504 | |
505 | for (i = 0; i < num_properties; i++) { |
506 | u16 prop = le16_to_cpu(properties[i].prop); |
507 | u64 val = le64_to_cpu(properties[i].val); |
508 | struct drm_property *property; |
509 | unsigned int *state_val; |
510 | |
511 | drm_dbg(drm, "property: %u = %llu(0x%llx)\n" , prop, val, val); |
512 | |
513 | switch (prop) { |
514 | case GUD_PROPERTY_TV_LEFT_MARGIN: |
515 | fallthrough; |
516 | case GUD_PROPERTY_TV_RIGHT_MARGIN: |
517 | fallthrough; |
518 | case GUD_PROPERTY_TV_TOP_MARGIN: |
519 | fallthrough; |
520 | case GUD_PROPERTY_TV_BOTTOM_MARGIN: |
521 | ret = drm_mode_create_tv_margin_properties(dev: drm); |
522 | if (ret) |
523 | goto out; |
524 | break; |
525 | case GUD_PROPERTY_TV_MODE: |
526 | ret = gud_connector_add_tv_mode(gdrm, connector); |
527 | if (ret) |
528 | goto out; |
529 | break; |
530 | case GUD_PROPERTY_TV_BRIGHTNESS: |
531 | fallthrough; |
532 | case GUD_PROPERTY_TV_CONTRAST: |
533 | fallthrough; |
534 | case GUD_PROPERTY_TV_FLICKER_REDUCTION: |
535 | fallthrough; |
536 | case GUD_PROPERTY_TV_OVERSCAN: |
537 | fallthrough; |
538 | case GUD_PROPERTY_TV_SATURATION: |
539 | fallthrough; |
540 | case GUD_PROPERTY_TV_HUE: |
541 | /* This is a no-op if already added. */ |
542 | ret = drm_mode_create_tv_properties_legacy(dev: drm, num_modes: 0, NULL); |
543 | if (ret) |
544 | goto out; |
545 | break; |
546 | case GUD_PROPERTY_BACKLIGHT_BRIGHTNESS: |
547 | if (val > 100) { |
548 | ret = -EINVAL; |
549 | goto out; |
550 | } |
551 | gconn->initial_brightness = val; |
552 | break; |
553 | default: |
554 | /* New ones might show up in future devices, skip those we don't know. */ |
555 | drm_dbg(drm, "Ignoring unknown property: %u\n" , prop); |
556 | continue; |
557 | } |
558 | |
559 | gconn->properties[gconn->num_properties++] = prop; |
560 | |
561 | if (prop == GUD_PROPERTY_BACKLIGHT_BRIGHTNESS) |
562 | continue; /* not a DRM property */ |
563 | |
564 | property = gud_connector_property_lookup(connector, prop); |
565 | if (WARN_ON(IS_ERR(property))) |
566 | continue; |
567 | |
568 | state_val = gud_connector_tv_state_val(prop, state: &gconn->initial_tv_state); |
569 | if (WARN_ON(IS_ERR(state_val))) |
570 | continue; |
571 | |
572 | *state_val = val; |
573 | drm_object_attach_property(obj: &connector->base, property, init_val: 0); |
574 | } |
575 | out: |
576 | kfree(objp: properties); |
577 | |
578 | return ret; |
579 | } |
580 | |
581 | int gud_connector_fill_properties(struct drm_connector_state *connector_state, |
582 | struct gud_property_req *properties) |
583 | { |
584 | struct gud_connector *gconn = to_gud_connector(connector: connector_state->connector); |
585 | unsigned int i; |
586 | |
587 | for (i = 0; i < gconn->num_properties; i++) { |
588 | u16 prop = gconn->properties[i]; |
589 | u64 val; |
590 | |
591 | if (prop == GUD_PROPERTY_BACKLIGHT_BRIGHTNESS) { |
592 | val = connector_state->tv.brightness; |
593 | } else { |
594 | unsigned int *state_val; |
595 | |
596 | state_val = gud_connector_tv_state_val(prop, state: &connector_state->tv); |
597 | if (WARN_ON_ONCE(IS_ERR(state_val))) |
598 | return PTR_ERR(ptr: state_val); |
599 | |
600 | val = *state_val; |
601 | } |
602 | |
603 | properties[i].prop = cpu_to_le16(prop); |
604 | properties[i].val = cpu_to_le64(val); |
605 | } |
606 | |
607 | return gconn->num_properties; |
608 | } |
609 | |
610 | static int gud_connector_create(struct gud_device *gdrm, unsigned int index, |
611 | struct gud_connector_descriptor_req *desc) |
612 | { |
613 | struct drm_device *drm = &gdrm->drm; |
614 | struct gud_connector *gconn; |
615 | struct drm_connector *connector; |
616 | struct drm_encoder *encoder; |
617 | int ret, connector_type; |
618 | u32 flags; |
619 | |
620 | gconn = kzalloc(size: sizeof(*gconn), GFP_KERNEL); |
621 | if (!gconn) |
622 | return -ENOMEM; |
623 | |
624 | INIT_WORK(&gconn->backlight_work, gud_connector_backlight_update_status_work); |
625 | gconn->initial_brightness = -ENODEV; |
626 | flags = le32_to_cpu(desc->flags); |
627 | connector = &gconn->connector; |
628 | |
629 | drm_dbg(drm, "Connector: index=%u type=%u flags=0x%x\n" , index, desc->connector_type, flags); |
630 | |
631 | switch (desc->connector_type) { |
632 | case GUD_CONNECTOR_TYPE_PANEL: |
633 | connector_type = DRM_MODE_CONNECTOR_USB; |
634 | break; |
635 | case GUD_CONNECTOR_TYPE_VGA: |
636 | connector_type = DRM_MODE_CONNECTOR_VGA; |
637 | break; |
638 | case GUD_CONNECTOR_TYPE_DVI: |
639 | connector_type = DRM_MODE_CONNECTOR_DVID; |
640 | break; |
641 | case GUD_CONNECTOR_TYPE_COMPOSITE: |
642 | connector_type = DRM_MODE_CONNECTOR_Composite; |
643 | break; |
644 | case GUD_CONNECTOR_TYPE_SVIDEO: |
645 | connector_type = DRM_MODE_CONNECTOR_SVIDEO; |
646 | break; |
647 | case GUD_CONNECTOR_TYPE_COMPONENT: |
648 | connector_type = DRM_MODE_CONNECTOR_Component; |
649 | break; |
650 | case GUD_CONNECTOR_TYPE_DISPLAYPORT: |
651 | connector_type = DRM_MODE_CONNECTOR_DisplayPort; |
652 | break; |
653 | case GUD_CONNECTOR_TYPE_HDMI: |
654 | connector_type = DRM_MODE_CONNECTOR_HDMIA; |
655 | break; |
656 | default: /* future types */ |
657 | connector_type = DRM_MODE_CONNECTOR_USB; |
658 | break; |
659 | } |
660 | |
661 | drm_connector_helper_add(connector, funcs: &gud_connector_helper_funcs); |
662 | ret = drm_connector_init(dev: drm, connector, funcs: &gud_connector_funcs, connector_type); |
663 | if (ret) { |
664 | kfree(objp: connector); |
665 | return ret; |
666 | } |
667 | |
668 | if (WARN_ON(connector->index != index)) |
669 | return -EINVAL; |
670 | |
671 | if (flags & GUD_CONNECTOR_FLAGS_POLL_STATUS) |
672 | connector->polled = (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT); |
673 | if (flags & GUD_CONNECTOR_FLAGS_INTERLACE) |
674 | connector->interlace_allowed = true; |
675 | if (flags & GUD_CONNECTOR_FLAGS_DOUBLESCAN) |
676 | connector->doublescan_allowed = true; |
677 | |
678 | ret = gud_connector_add_properties(gdrm, gconn); |
679 | if (ret) { |
680 | gud_conn_err(connector, msg: "Failed to add properties" , ret); |
681 | return ret; |
682 | } |
683 | |
684 | /* The first connector is attached to the existing simple pipe encoder */ |
685 | if (!connector->index) { |
686 | encoder = &gdrm->pipe.encoder; |
687 | } else { |
688 | encoder = &gconn->encoder; |
689 | |
690 | ret = drm_simple_encoder_init(dev: drm, encoder, DRM_MODE_ENCODER_NONE); |
691 | if (ret) |
692 | return ret; |
693 | |
694 | encoder->possible_crtcs = 1; |
695 | } |
696 | |
697 | return drm_connector_attach_encoder(connector, encoder); |
698 | } |
699 | |
700 | int gud_get_connectors(struct gud_device *gdrm) |
701 | { |
702 | struct gud_connector_descriptor_req *descs; |
703 | unsigned int i, num_connectors; |
704 | int ret; |
705 | |
706 | descs = kmalloc_array(GUD_CONNECTORS_MAX_NUM, size: sizeof(*descs), GFP_KERNEL); |
707 | if (!descs) |
708 | return -ENOMEM; |
709 | |
710 | ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTORS, index: 0, |
711 | buf: descs, GUD_CONNECTORS_MAX_NUM * sizeof(*descs)); |
712 | if (ret < 0) |
713 | goto free; |
714 | if (!ret || ret % sizeof(*descs)) { |
715 | ret = -EIO; |
716 | goto free; |
717 | } |
718 | |
719 | num_connectors = ret / sizeof(*descs); |
720 | |
721 | for (i = 0; i < num_connectors; i++) { |
722 | ret = gud_connector_create(gdrm, index: i, desc: &descs[i]); |
723 | if (ret) |
724 | goto free; |
725 | } |
726 | free: |
727 | kfree(objp: descs); |
728 | |
729 | return ret; |
730 | } |
731 | |