Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
29e57fab MR |
2 | /* |
3 | * Copyright (C) 2015 Free Electrons | |
4 | * Copyright (C) 2015 NextThing Co | |
5 | * | |
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> | |
29e57fab MR |
7 | */ |
8 | ||
9 | #include <linux/clk.h> | |
10 | ||
29e57fab | 11 | #include <drm/drm_atomic_helper.h> |
ee68c743 | 12 | #include <drm/drm_bridge.h> |
ebc94461 | 13 | #include <drm/drm_of.h> |
29e57fab | 14 | #include <drm/drm_panel.h> |
9c25a297 | 15 | #include <drm/drm_print.h> |
fcd70cd3 | 16 | #include <drm/drm_probe_helper.h> |
f9f3a38d | 17 | #include <drm/drm_simple_kms_helper.h> |
29e57fab | 18 | |
a5154a4d | 19 | #include "sun4i_crtc.h" |
29e57fab | 20 | #include "sun4i_tcon.h" |
0c3ff44c | 21 | #include "sun4i_rgb.h" |
29e57fab MR |
22 | |
23 | struct sun4i_rgb { | |
24 | struct drm_connector connector; | |
25 | struct drm_encoder encoder; | |
26 | ||
b9c8506c | 27 | struct sun4i_tcon *tcon; |
1ce6f91c | 28 | struct drm_panel *panel; |
19d0ffe0 | 29 | struct drm_bridge *bridge; |
29e57fab MR |
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); | |
29e57fab | 50 | |
06c4a9c2 | 51 | return drm_panel_get_modes(rgb->panel, connector); |
29e57fab MR |
52 | } |
53 | ||
e2771deb MR |
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 | ||
cde8b754 GB |
62 | static enum drm_mode_status sun4i_rgb_mode_valid(struct drm_encoder *crtc, |
63 | const struct drm_display_mode *mode) | |
29e57fab | 64 | { |
cde8b754 | 65 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(crtc); |
b9c8506c | 66 | struct sun4i_tcon *tcon = rgb->tcon; |
29e57fab MR |
67 | u32 hsync = mode->hsync_end - mode->hsync_start; |
68 | u32 vsync = mode->vsync_end - mode->vsync_start; | |
9f7dfd0c | 69 | unsigned long long rate = mode->clock * 1000; |
e2771deb | 70 | unsigned long long lowest, highest; |
9f7dfd0c | 71 | unsigned long long rounded_rate; |
29e57fab MR |
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 | ||
e2771deb MR |
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 | ||
5af894bd MR |
121 | tcon->dclk_min_div = 6; |
122 | tcon->dclk_max_div = 127; | |
bb43d40d | 123 | rounded_rate = clk_round_rate(tcon->dclk, rate); |
e2771deb MR |
124 | |
125 | lowest = rate * (1000 - SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE); | |
126 | do_div(lowest, 1000); | |
127 | if (rounded_rate < lowest) | |
bb43d40d MR |
128 | return MODE_CLOCK_LOW; |
129 | ||
e2771deb MR |
130 | highest = rate * (1000 + SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE); |
131 | do_div(highest, 1000); | |
132 | if (rounded_rate > highest) | |
bb43d40d MR |
133 | return MODE_CLOCK_HIGH; |
134 | ||
e2771deb | 135 | out: |
bb43d40d MR |
136 | DRM_DEBUG_DRIVER("Clock rate OK\n"); |
137 | ||
29e57fab MR |
138 | return MODE_OK; |
139 | } | |
140 | ||
f13478c9 | 141 | static const struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { |
29e57fab | 142 | .get_modes = sun4i_rgb_get_modes, |
29e57fab MR |
143 | }; |
144 | ||
29e57fab MR |
145 | static void |
146 | sun4i_rgb_connector_destroy(struct drm_connector *connector) | |
147 | { | |
29e57fab MR |
148 | drm_connector_cleanup(connector); |
149 | } | |
150 | ||
32b4d575 | 151 | static const struct drm_connector_funcs sun4i_rgb_con_funcs = { |
29e57fab MR |
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 | ||
29e57fab MR |
159 | static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) |
160 | { | |
161 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); | |
29e57fab MR |
162 | |
163 | DRM_DEBUG_DRIVER("Enabling RGB output\n"); | |
164 | ||
1ce6f91c MR |
165 | if (rgb->panel) { |
166 | drm_panel_prepare(rgb->panel); | |
167 | drm_panel_enable(rgb->panel); | |
45e88f99 | 168 | } |
29e57fab MR |
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); | |
29e57fab MR |
174 | |
175 | DRM_DEBUG_DRIVER("Disabling RGB output\n"); | |
176 | ||
1ce6f91c MR |
177 | if (rgb->panel) { |
178 | drm_panel_disable(rgb->panel); | |
179 | drm_panel_unprepare(rgb->panel); | |
45e88f99 | 180 | } |
29e57fab MR |
181 | } |
182 | ||
f13478c9 | 183 | static const struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { |
29e57fab MR |
184 | .disable = sun4i_rgb_encoder_disable, |
185 | .enable = sun4i_rgb_encoder_enable, | |
cde8b754 | 186 | .mode_valid = sun4i_rgb_mode_valid, |
29e57fab MR |
187 | }; |
188 | ||
b9c8506c | 189 | int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon) |
29e57fab | 190 | { |
894f5a9f | 191 | struct drm_encoder *encoder; |
29e57fab MR |
192 | struct sun4i_rgb *rgb; |
193 | int ret; | |
194 | ||
29e57fab MR |
195 | rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL); |
196 | if (!rgb) | |
197 | return -ENOMEM; | |
b9c8506c | 198 | rgb->tcon = tcon; |
894f5a9f | 199 | encoder = &rgb->encoder; |
29e57fab | 200 | |
ebc94461 | 201 | ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0, |
19d0ffe0 | 202 | &rgb->panel, &rgb->bridge); |
ebc94461 | 203 | if (ret) { |
894f5a9f | 204 | dev_info(drm->dev, "No panel or bridge found... RGB output disabled\n"); |
a8444c7e MR |
205 | return 0; |
206 | } | |
207 | ||
29e57fab MR |
208 | drm_encoder_helper_add(&rgb->encoder, |
209 | &sun4i_rgb_enc_helper_funcs); | |
f9f3a38d TZ |
210 | ret = drm_simple_encoder_init(drm, &rgb->encoder, |
211 | DRM_MODE_ENCODER_NONE); | |
29e57fab MR |
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 */ | |
dbf8f9e4 | 218 | rgb->encoder.possible_crtcs = drm_crtc_mask(&tcon->crtc->crtc); |
29e57fab | 219 | |
1ce6f91c | 220 | if (rgb->panel) { |
894f5a9f MR |
221 | drm_connector_helper_add(&rgb->connector, |
222 | &sun4i_rgb_con_helper_funcs); | |
223 | ret = drm_connector_init(drm, &rgb->connector, | |
224 | &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 | ||
cde4c44d | 231 | drm_connector_attach_encoder(&rgb->connector, |
894f5a9f | 232 | &rgb->encoder); |
29e57fab MR |
233 | } |
234 | ||
19d0ffe0 | 235 | if (rgb->bridge) { |
a25b988f | 236 | ret = drm_bridge_attach(encoder, rgb->bridge, NULL, 0); |
894f5a9f MR |
237 | if (ret) { |
238 | dev_err(drm->dev, "Couldn't attach our bridge\n"); | |
239 | goto err_cleanup_connector; | |
240 | } | |
241 | } | |
29e57fab MR |
242 | |
243 | return 0; | |
244 | ||
245 | err_cleanup_connector: | |
246 | drm_encoder_cleanup(&rgb->encoder); | |
247 | err_out: | |
248 | return ret; | |
249 | } | |
250 | EXPORT_SYMBOL(sun4i_rgb_init); |