1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2015 Free Electrons |
4 | * Copyright (C) 2015 NextThing Co |
5 | * |
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | |
11 | #include <drm/drm_atomic_helper.h> |
12 | #include <drm/drm_bridge.h> |
13 | #include <drm/drm_of.h> |
14 | #include <drm/drm_panel.h> |
15 | #include <drm/drm_print.h> |
16 | #include <drm/drm_probe_helper.h> |
17 | #include <drm/drm_simple_kms_helper.h> |
18 | |
19 | #include "sun4i_crtc.h" |
20 | #include "sun4i_tcon.h" |
21 | #include "sun4i_rgb.h" |
22 | |
23 | struct sun4i_rgb { |
24 | struct drm_connector connector; |
25 | struct drm_encoder encoder; |
26 | |
27 | struct sun4i_tcon *tcon; |
28 | struct drm_panel *panel; |
29 | struct drm_bridge *bridge; |
30 | }; |
31 | |
32 | static inline struct sun4i_rgb * |
33 | drm_connector_to_sun4i_rgb(struct drm_connector *connector) |
34 | { |
35 | return container_of(connector, struct sun4i_rgb, |
36 | connector); |
37 | } |
38 | |
39 | static inline struct sun4i_rgb * |
40 | drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder) |
41 | { |
42 | return container_of(encoder, struct sun4i_rgb, |
43 | encoder); |
44 | } |
45 | |
46 | static int sun4i_rgb_get_modes(struct drm_connector *connector) |
47 | { |
48 | struct sun4i_rgb *rgb = |
49 | drm_connector_to_sun4i_rgb(connector); |
50 | |
51 | return drm_panel_get_modes(panel: rgb->panel, connector); |
52 | } |
53 | |
54 | /* |
55 | * VESA DMT defines a tolerance of 0.5% on the pixel clock, while the |
56 | * CVT spec reuses that tolerance in its examples, so it looks to be a |
57 | * good default tolerance for the EDID-based modes. Define it to 5 per |
58 | * mille to avoid floating point operations. |
59 | */ |
60 | #define SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE 5 |
61 | |
62 | static enum drm_mode_status sun4i_rgb_mode_valid(struct drm_encoder *crtc, |
63 | const struct drm_display_mode *mode) |
64 | { |
65 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder: crtc); |
66 | struct sun4i_tcon *tcon = rgb->tcon; |
67 | u32 hsync = mode->hsync_end - mode->hsync_start; |
68 | u32 vsync = mode->vsync_end - mode->vsync_start; |
69 | unsigned long long rate = mode->clock * 1000; |
70 | unsigned long long lowest, highest; |
71 | unsigned long long rounded_rate; |
72 | |
73 | DRM_DEBUG_DRIVER("Validating modes...\n" ); |
74 | |
75 | if (hsync < 1) |
76 | return MODE_HSYNC_NARROW; |
77 | |
78 | if (hsync > 0x3ff) |
79 | return MODE_HSYNC_WIDE; |
80 | |
81 | if ((mode->hdisplay < 1) || (mode->htotal < 1)) |
82 | return MODE_H_ILLEGAL; |
83 | |
84 | if ((mode->hdisplay > 0x7ff) || (mode->htotal > 0xfff)) |
85 | return MODE_BAD_HVALUE; |
86 | |
87 | DRM_DEBUG_DRIVER("Horizontal parameters OK\n" ); |
88 | |
89 | if (vsync < 1) |
90 | return MODE_VSYNC_NARROW; |
91 | |
92 | if (vsync > 0x3ff) |
93 | return MODE_VSYNC_WIDE; |
94 | |
95 | if ((mode->vdisplay < 1) || (mode->vtotal < 1)) |
96 | return MODE_V_ILLEGAL; |
97 | |
98 | if ((mode->vdisplay > 0x7ff) || (mode->vtotal > 0xfff)) |
99 | return MODE_BAD_VVALUE; |
100 | |
101 | DRM_DEBUG_DRIVER("Vertical parameters OK\n" ); |
102 | |
103 | /* |
104 | * TODO: We should use the struct display_timing if available |
105 | * and / or trying to stretch the timings within that |
106 | * tolerancy to take care of panels that we wouldn't be able |
107 | * to have a exact match for. |
108 | */ |
109 | if (rgb->panel) { |
110 | DRM_DEBUG_DRIVER("RGB panel used, skipping clock rate checks" ); |
111 | goto out; |
112 | } |
113 | |
114 | /* |
115 | * That shouldn't ever happen unless something is really wrong, but it |
116 | * doesn't harm to check. |
117 | */ |
118 | if (!rgb->bridge) |
119 | goto out; |
120 | |
121 | tcon->dclk_min_div = 6; |
122 | tcon->dclk_max_div = 127; |
123 | rounded_rate = clk_round_rate(clk: tcon->dclk, rate); |
124 | |
125 | lowest = rate * (1000 - SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE); |
126 | do_div(lowest, 1000); |
127 | if (rounded_rate < lowest) |
128 | return MODE_CLOCK_LOW; |
129 | |
130 | highest = rate * (1000 + SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE); |
131 | do_div(highest, 1000); |
132 | if (rounded_rate > highest) |
133 | return MODE_CLOCK_HIGH; |
134 | |
135 | out: |
136 | DRM_DEBUG_DRIVER("Clock rate OK\n" ); |
137 | |
138 | return MODE_OK; |
139 | } |
140 | |
141 | static const struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { |
142 | .get_modes = sun4i_rgb_get_modes, |
143 | }; |
144 | |
145 | static void |
146 | sun4i_rgb_connector_destroy(struct drm_connector *connector) |
147 | { |
148 | drm_connector_cleanup(connector); |
149 | } |
150 | |
151 | static const struct drm_connector_funcs sun4i_rgb_con_funcs = { |
152 | .fill_modes = drm_helper_probe_single_connector_modes, |
153 | .destroy = sun4i_rgb_connector_destroy, |
154 | .reset = drm_atomic_helper_connector_reset, |
155 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
156 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
157 | }; |
158 | |
159 | static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) |
160 | { |
161 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); |
162 | |
163 | DRM_DEBUG_DRIVER("Enabling RGB output\n" ); |
164 | |
165 | if (rgb->panel) { |
166 | drm_panel_prepare(panel: rgb->panel); |
167 | drm_panel_enable(panel: rgb->panel); |
168 | } |
169 | } |
170 | |
171 | static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) |
172 | { |
173 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); |
174 | |
175 | DRM_DEBUG_DRIVER("Disabling RGB output\n" ); |
176 | |
177 | if (rgb->panel) { |
178 | drm_panel_disable(panel: rgb->panel); |
179 | drm_panel_unprepare(panel: rgb->panel); |
180 | } |
181 | } |
182 | |
183 | static const struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { |
184 | .disable = sun4i_rgb_encoder_disable, |
185 | .enable = sun4i_rgb_encoder_enable, |
186 | .mode_valid = sun4i_rgb_mode_valid, |
187 | }; |
188 | |
189 | int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon) |
190 | { |
191 | struct drm_encoder *encoder; |
192 | struct sun4i_rgb *rgb; |
193 | int ret; |
194 | |
195 | rgb = devm_kzalloc(dev: drm->dev, size: sizeof(*rgb), GFP_KERNEL); |
196 | if (!rgb) |
197 | return -ENOMEM; |
198 | rgb->tcon = tcon; |
199 | encoder = &rgb->encoder; |
200 | |
201 | ret = drm_of_find_panel_or_bridge(np: tcon->dev->of_node, port: 1, endpoint: 0, |
202 | panel: &rgb->panel, bridge: &rgb->bridge); |
203 | if (ret) { |
204 | dev_info(drm->dev, "No panel or bridge found... RGB output disabled\n" ); |
205 | return 0; |
206 | } |
207 | |
208 | drm_encoder_helper_add(encoder: &rgb->encoder, |
209 | funcs: &sun4i_rgb_enc_helper_funcs); |
210 | ret = drm_simple_encoder_init(dev: drm, encoder: &rgb->encoder, |
211 | DRM_MODE_ENCODER_NONE); |
212 | if (ret) { |
213 | dev_err(drm->dev, "Couldn't initialise the rgb encoder\n" ); |
214 | goto err_out; |
215 | } |
216 | |
217 | /* The RGB encoder can only work with the TCON channel 0 */ |
218 | rgb->encoder.possible_crtcs = drm_crtc_mask(crtc: &tcon->crtc->crtc); |
219 | |
220 | if (rgb->panel) { |
221 | drm_connector_helper_add(connector: &rgb->connector, |
222 | funcs: &sun4i_rgb_con_helper_funcs); |
223 | ret = drm_connector_init(dev: drm, connector: &rgb->connector, |
224 | funcs: &sun4i_rgb_con_funcs, |
225 | DRM_MODE_CONNECTOR_Unknown); |
226 | if (ret) { |
227 | dev_err(drm->dev, "Couldn't initialise the rgb connector\n" ); |
228 | goto err_cleanup_connector; |
229 | } |
230 | |
231 | drm_connector_attach_encoder(connector: &rgb->connector, |
232 | encoder: &rgb->encoder); |
233 | } |
234 | |
235 | if (rgb->bridge) { |
236 | ret = drm_bridge_attach(encoder, bridge: rgb->bridge, NULL, flags: 0); |
237 | if (ret) |
238 | goto err_cleanup_connector; |
239 | } |
240 | |
241 | return 0; |
242 | |
243 | err_cleanup_connector: |
244 | drm_encoder_cleanup(encoder: &rgb->encoder); |
245 | err_out: |
246 | return ret; |
247 | } |
248 | EXPORT_SYMBOL(sun4i_rgb_init); |
249 | |