1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/ |
4 | * Author: Rob Clark <rob@ti.com> |
5 | */ |
6 | |
7 | #include <linux/list.h> |
8 | |
9 | #include <drm/drm_bridge.h> |
10 | #include <drm/drm_crtc.h> |
11 | #include <drm/drm_modeset_helper_vtables.h> |
12 | #include <drm/drm_edid.h> |
13 | |
14 | #include "omap_drv.h" |
15 | |
16 | /* |
17 | * encoder funcs |
18 | */ |
19 | |
20 | #define to_omap_encoder(x) container_of(x, struct omap_encoder, base) |
21 | |
22 | /* The encoder and connector both map to same dssdev.. the encoder |
23 | * handles the 'active' parts, ie. anything the modifies the state |
24 | * of the hw, and the connector handles the 'read-only' parts, like |
25 | * detecting connection and reading edid. |
26 | */ |
27 | struct omap_encoder { |
28 | struct drm_encoder base; |
29 | struct omap_dss_device *output; |
30 | }; |
31 | |
32 | static void omap_encoder_destroy(struct drm_encoder *encoder) |
33 | { |
34 | struct omap_encoder *omap_encoder = to_omap_encoder(encoder); |
35 | |
36 | drm_encoder_cleanup(encoder); |
37 | kfree(objp: omap_encoder); |
38 | } |
39 | |
40 | static const struct drm_encoder_funcs omap_encoder_funcs = { |
41 | .destroy = omap_encoder_destroy, |
42 | }; |
43 | |
44 | static void omap_encoder_update_videomode_flags(struct videomode *vm, |
45 | u32 bus_flags) |
46 | { |
47 | if (!(vm->flags & (DISPLAY_FLAGS_DE_LOW | |
48 | DISPLAY_FLAGS_DE_HIGH))) { |
49 | if (bus_flags & DRM_BUS_FLAG_DE_LOW) |
50 | vm->flags |= DISPLAY_FLAGS_DE_LOW; |
51 | else if (bus_flags & DRM_BUS_FLAG_DE_HIGH) |
52 | vm->flags |= DISPLAY_FLAGS_DE_HIGH; |
53 | } |
54 | |
55 | if (!(vm->flags & (DISPLAY_FLAGS_PIXDATA_POSEDGE | |
56 | DISPLAY_FLAGS_PIXDATA_NEGEDGE))) { |
57 | if (bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE) |
58 | vm->flags |= DISPLAY_FLAGS_PIXDATA_POSEDGE; |
59 | else if (bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) |
60 | vm->flags |= DISPLAY_FLAGS_PIXDATA_NEGEDGE; |
61 | } |
62 | |
63 | if (!(vm->flags & (DISPLAY_FLAGS_SYNC_POSEDGE | |
64 | DISPLAY_FLAGS_SYNC_NEGEDGE))) { |
65 | if (bus_flags & DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE) |
66 | vm->flags |= DISPLAY_FLAGS_SYNC_POSEDGE; |
67 | else if (bus_flags & DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE) |
68 | vm->flags |= DISPLAY_FLAGS_SYNC_NEGEDGE; |
69 | } |
70 | } |
71 | |
72 | static void omap_encoder_mode_set(struct drm_encoder *encoder, |
73 | struct drm_display_mode *mode, |
74 | struct drm_display_mode *adjusted_mode) |
75 | { |
76 | struct omap_encoder *omap_encoder = to_omap_encoder(encoder); |
77 | struct omap_dss_device *output = omap_encoder->output; |
78 | struct drm_device *dev = encoder->dev; |
79 | struct drm_connector *connector; |
80 | struct drm_bridge *bridge; |
81 | struct videomode vm = { 0 }; |
82 | u32 bus_flags; |
83 | |
84 | list_for_each_entry(connector, &dev->mode_config.connector_list, head) { |
85 | if (connector->encoder == encoder) |
86 | break; |
87 | } |
88 | |
89 | drm_display_mode_to_videomode(dmode: adjusted_mode, vm: &vm); |
90 | |
91 | /* |
92 | * HACK: This fixes the vm flags. |
93 | * struct drm_display_mode does not contain the VSYNC/HSYNC/DE flags and |
94 | * they get lost when converting back and forth between struct |
95 | * drm_display_mode and struct videomode. The hack below goes and |
96 | * fetches the missing flags. |
97 | * |
98 | * A better solution is to use DRM's bus-flags through the whole driver. |
99 | */ |
100 | for (bridge = output->bridge; bridge; |
101 | bridge = drm_bridge_get_next_bridge(bridge)) { |
102 | if (!bridge->timings) |
103 | continue; |
104 | |
105 | bus_flags = bridge->timings->input_bus_flags; |
106 | omap_encoder_update_videomode_flags(vm: &vm, bus_flags); |
107 | } |
108 | |
109 | bus_flags = connector->display_info.bus_flags; |
110 | omap_encoder_update_videomode_flags(vm: &vm, bus_flags); |
111 | |
112 | /* Set timings for all devices in the display pipeline. */ |
113 | dss_mgr_set_timings(dssdev: output, vm: &vm); |
114 | } |
115 | |
116 | static const struct drm_encoder_helper_funcs omap_encoder_helper_funcs = { |
117 | .mode_set = omap_encoder_mode_set, |
118 | }; |
119 | |
120 | /* initialize encoder */ |
121 | struct drm_encoder *omap_encoder_init(struct drm_device *dev, |
122 | struct omap_dss_device *output) |
123 | { |
124 | struct drm_encoder *encoder = NULL; |
125 | struct omap_encoder *omap_encoder; |
126 | |
127 | omap_encoder = kzalloc(size: sizeof(*omap_encoder), GFP_KERNEL); |
128 | if (!omap_encoder) |
129 | goto fail; |
130 | |
131 | omap_encoder->output = output; |
132 | |
133 | encoder = &omap_encoder->base; |
134 | |
135 | drm_encoder_init(dev, encoder, funcs: &omap_encoder_funcs, |
136 | DRM_MODE_ENCODER_TMDS, NULL); |
137 | drm_encoder_helper_add(encoder, funcs: &omap_encoder_helper_funcs); |
138 | |
139 | return encoder; |
140 | |
141 | fail: |
142 | if (encoder) |
143 | omap_encoder_destroy(encoder); |
144 | |
145 | return NULL; |
146 | } |
147 | |