.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
---|
1 | 2 | /* |
---|
2 | 3 | * Copyright (C) 2015 Free Electrons |
---|
3 | 4 | * Copyright (C) 2015 NextThing Co |
---|
4 | 5 | * |
---|
5 | 6 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
---|
6 | | - * |
---|
7 | | - * This program is free software; you can redistribute it and/or |
---|
8 | | - * modify it under the terms of the GNU General Public License as |
---|
9 | | - * published by the Free Software Foundation; either version 2 of |
---|
10 | | - * the License, or (at your option) any later version. |
---|
11 | 7 | */ |
---|
12 | 8 | |
---|
13 | 9 | #include <linux/clk.h> |
---|
14 | 10 | |
---|
15 | | -#include <drm/drmP.h> |
---|
16 | 11 | #include <drm/drm_atomic_helper.h> |
---|
17 | | -#include <drm/drm_crtc_helper.h> |
---|
| 12 | +#include <drm/drm_bridge.h> |
---|
18 | 13 | #include <drm/drm_of.h> |
---|
19 | 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> |
---|
20 | 18 | |
---|
21 | 19 | #include "sun4i_crtc.h" |
---|
22 | 20 | #include "sun4i_tcon.h" |
---|
.. | .. |
---|
27 | 25 | struct drm_encoder encoder; |
---|
28 | 26 | |
---|
29 | 27 | struct sun4i_tcon *tcon; |
---|
| 28 | + struct drm_panel *panel; |
---|
| 29 | + struct drm_bridge *bridge; |
---|
30 | 30 | }; |
---|
31 | 31 | |
---|
32 | 32 | static inline struct sun4i_rgb * |
---|
.. | .. |
---|
47 | 47 | { |
---|
48 | 48 | struct sun4i_rgb *rgb = |
---|
49 | 49 | drm_connector_to_sun4i_rgb(connector); |
---|
50 | | - struct sun4i_tcon *tcon = rgb->tcon; |
---|
51 | 50 | |
---|
52 | | - return drm_panel_get_modes(tcon->panel); |
---|
| 51 | + return drm_panel_get_modes(rgb->panel, connector); |
---|
53 | 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 |
---|
54 | 61 | |
---|
55 | 62 | static enum drm_mode_status sun4i_rgb_mode_valid(struct drm_encoder *crtc, |
---|
56 | 63 | const struct drm_display_mode *mode) |
---|
.. | .. |
---|
59 | 66 | struct sun4i_tcon *tcon = rgb->tcon; |
---|
60 | 67 | u32 hsync = mode->hsync_end - mode->hsync_start; |
---|
61 | 68 | u32 vsync = mode->vsync_end - mode->vsync_start; |
---|
62 | | - unsigned long rate = mode->clock * 1000; |
---|
63 | | - long rounded_rate; |
---|
| 69 | + unsigned long long rate = mode->clock * 1000; |
---|
| 70 | + unsigned long long lowest, highest; |
---|
| 71 | + unsigned long long rounded_rate; |
---|
64 | 72 | |
---|
65 | 73 | DRM_DEBUG_DRIVER("Validating modes...\n"); |
---|
66 | 74 | |
---|
.. | .. |
---|
92 | 100 | |
---|
93 | 101 | DRM_DEBUG_DRIVER("Vertical parameters OK\n"); |
---|
94 | 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 | + |
---|
95 | 121 | tcon->dclk_min_div = 6; |
---|
96 | 122 | tcon->dclk_max_div = 127; |
---|
97 | 123 | rounded_rate = clk_round_rate(tcon->dclk, rate); |
---|
98 | | - if (rounded_rate < rate) |
---|
| 124 | + |
---|
| 125 | + lowest = rate * (1000 - SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE); |
---|
| 126 | + do_div(lowest, 1000); |
---|
| 127 | + if (rounded_rate < lowest) |
---|
99 | 128 | return MODE_CLOCK_LOW; |
---|
100 | 129 | |
---|
101 | | - if (rounded_rate > rate) |
---|
| 130 | + highest = rate * (1000 + SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE); |
---|
| 131 | + do_div(highest, 1000); |
---|
| 132 | + if (rounded_rate > highest) |
---|
102 | 133 | return MODE_CLOCK_HIGH; |
---|
103 | 134 | |
---|
| 135 | +out: |
---|
104 | 136 | DRM_DEBUG_DRIVER("Clock rate OK\n"); |
---|
105 | 137 | |
---|
106 | 138 | return MODE_OK; |
---|
107 | 139 | } |
---|
108 | 140 | |
---|
109 | | -static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { |
---|
| 141 | +static const struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { |
---|
110 | 142 | .get_modes = sun4i_rgb_get_modes, |
---|
111 | 143 | }; |
---|
112 | 144 | |
---|
113 | 145 | static void |
---|
114 | 146 | sun4i_rgb_connector_destroy(struct drm_connector *connector) |
---|
115 | 147 | { |
---|
116 | | - struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); |
---|
117 | | - struct sun4i_tcon *tcon = rgb->tcon; |
---|
118 | | - |
---|
119 | | - drm_panel_detach(tcon->panel); |
---|
120 | 148 | drm_connector_cleanup(connector); |
---|
121 | 149 | } |
---|
122 | 150 | |
---|
.. | .. |
---|
131 | 159 | static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) |
---|
132 | 160 | { |
---|
133 | 161 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); |
---|
134 | | - struct sun4i_tcon *tcon = rgb->tcon; |
---|
135 | 162 | |
---|
136 | 163 | DRM_DEBUG_DRIVER("Enabling RGB output\n"); |
---|
137 | 164 | |
---|
138 | | - if (!IS_ERR(tcon->panel)) { |
---|
139 | | - drm_panel_prepare(tcon->panel); |
---|
140 | | - drm_panel_enable(tcon->panel); |
---|
| 165 | + if (rgb->panel) { |
---|
| 166 | + drm_panel_prepare(rgb->panel); |
---|
| 167 | + drm_panel_enable(rgb->panel); |
---|
141 | 168 | } |
---|
142 | 169 | } |
---|
143 | 170 | |
---|
144 | 171 | static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) |
---|
145 | 172 | { |
---|
146 | 173 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); |
---|
147 | | - struct sun4i_tcon *tcon = rgb->tcon; |
---|
148 | 174 | |
---|
149 | 175 | DRM_DEBUG_DRIVER("Disabling RGB output\n"); |
---|
150 | 176 | |
---|
151 | | - if (!IS_ERR(tcon->panel)) { |
---|
152 | | - drm_panel_disable(tcon->panel); |
---|
153 | | - drm_panel_unprepare(tcon->panel); |
---|
| 177 | + if (rgb->panel) { |
---|
| 178 | + drm_panel_disable(rgb->panel); |
---|
| 179 | + drm_panel_unprepare(rgb->panel); |
---|
154 | 180 | } |
---|
155 | 181 | } |
---|
156 | 182 | |
---|
157 | | -static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { |
---|
| 183 | +static const struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { |
---|
158 | 184 | .disable = sun4i_rgb_encoder_disable, |
---|
159 | 185 | .enable = sun4i_rgb_encoder_enable, |
---|
160 | 186 | .mode_valid = sun4i_rgb_mode_valid, |
---|
161 | 187 | }; |
---|
162 | 188 | |
---|
163 | | -static void sun4i_rgb_enc_destroy(struct drm_encoder *encoder) |
---|
164 | | -{ |
---|
165 | | - drm_encoder_cleanup(encoder); |
---|
166 | | -} |
---|
167 | | - |
---|
168 | | -static struct drm_encoder_funcs sun4i_rgb_enc_funcs = { |
---|
169 | | - .destroy = sun4i_rgb_enc_destroy, |
---|
170 | | -}; |
---|
171 | | - |
---|
172 | 189 | int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon) |
---|
173 | 190 | { |
---|
174 | 191 | struct drm_encoder *encoder; |
---|
175 | | - struct drm_bridge *bridge; |
---|
176 | 192 | struct sun4i_rgb *rgb; |
---|
177 | 193 | int ret; |
---|
178 | 194 | |
---|
.. | .. |
---|
183 | 199 | encoder = &rgb->encoder; |
---|
184 | 200 | |
---|
185 | 201 | ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0, |
---|
186 | | - &tcon->panel, &bridge); |
---|
| 202 | + &rgb->panel, &rgb->bridge); |
---|
187 | 203 | if (ret) { |
---|
188 | 204 | dev_info(drm->dev, "No panel or bridge found... RGB output disabled\n"); |
---|
189 | 205 | return 0; |
---|
.. | .. |
---|
191 | 207 | |
---|
192 | 208 | drm_encoder_helper_add(&rgb->encoder, |
---|
193 | 209 | &sun4i_rgb_enc_helper_funcs); |
---|
194 | | - ret = drm_encoder_init(drm, |
---|
195 | | - &rgb->encoder, |
---|
196 | | - &sun4i_rgb_enc_funcs, |
---|
197 | | - DRM_MODE_ENCODER_NONE, |
---|
198 | | - NULL); |
---|
| 210 | + ret = drm_simple_encoder_init(drm, &rgb->encoder, |
---|
| 211 | + DRM_MODE_ENCODER_NONE); |
---|
199 | 212 | if (ret) { |
---|
200 | 213 | dev_err(drm->dev, "Couldn't initialise the rgb encoder\n"); |
---|
201 | 214 | goto err_out; |
---|
.. | .. |
---|
204 | 217 | /* The RGB encoder can only work with the TCON channel 0 */ |
---|
205 | 218 | rgb->encoder.possible_crtcs = drm_crtc_mask(&tcon->crtc->crtc); |
---|
206 | 219 | |
---|
207 | | - if (tcon->panel) { |
---|
| 220 | + if (rgb->panel) { |
---|
208 | 221 | drm_connector_helper_add(&rgb->connector, |
---|
209 | 222 | &sun4i_rgb_con_helper_funcs); |
---|
210 | 223 | ret = drm_connector_init(drm, &rgb->connector, |
---|
.. | .. |
---|
217 | 230 | |
---|
218 | 231 | drm_connector_attach_encoder(&rgb->connector, |
---|
219 | 232 | &rgb->encoder); |
---|
220 | | - |
---|
221 | | - ret = drm_panel_attach(tcon->panel, &rgb->connector); |
---|
222 | | - if (ret) { |
---|
223 | | - dev_err(drm->dev, "Couldn't attach our panel\n"); |
---|
224 | | - goto err_cleanup_connector; |
---|
225 | | - } |
---|
226 | 233 | } |
---|
227 | 234 | |
---|
228 | | - if (bridge) { |
---|
229 | | - ret = drm_bridge_attach(encoder, bridge, NULL); |
---|
| 235 | + if (rgb->bridge) { |
---|
| 236 | + ret = drm_bridge_attach(encoder, rgb->bridge, NULL, 0); |
---|
230 | 237 | if (ret) { |
---|
231 | 238 | dev_err(drm->dev, "Couldn't attach our bridge\n"); |
---|
232 | 239 | goto err_cleanup_connector; |
---|