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