xref: /openbmc/linux/drivers/gpu/drm/sun4i/sun4i_rgb.c (revision 4984dd069f2995f239f075199ee8c0d9f020bcd9)
1 /*
2  * Copyright (C) 2015 Free Electrons
3  * Copyright (C) 2015 NextThing Co
4  *
5  * 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  */
12 
13 #include <linux/clk.h>
14 
15 #include <drm/drmP.h>
16 #include <drm/drm_atomic_helper.h>
17 #include <drm/drm_of.h>
18 #include <drm/drm_panel.h>
19 #include <drm/drm_probe_helper.h>
20 
21 #include "sun4i_crtc.h"
22 #include "sun4i_tcon.h"
23 #include "sun4i_rgb.h"
24 
25 struct sun4i_rgb {
26 	struct drm_connector	connector;
27 	struct drm_encoder	encoder;
28 
29 	struct sun4i_tcon	*tcon;
30 	struct drm_panel	*panel;
31 	struct drm_bridge	*bridge;
32 };
33 
34 static inline struct sun4i_rgb *
35 drm_connector_to_sun4i_rgb(struct drm_connector *connector)
36 {
37 	return container_of(connector, struct sun4i_rgb,
38 			    connector);
39 }
40 
41 static inline struct sun4i_rgb *
42 drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder)
43 {
44 	return container_of(encoder, struct sun4i_rgb,
45 			    encoder);
46 }
47 
48 static int sun4i_rgb_get_modes(struct drm_connector *connector)
49 {
50 	struct sun4i_rgb *rgb =
51 		drm_connector_to_sun4i_rgb(connector);
52 
53 	return drm_panel_get_modes(rgb->panel);
54 }
55 
56 /*
57  * VESA DMT defines a tolerance of 0.5% on the pixel clock, while the
58  * CVT spec reuses that tolerance in its examples, so it looks to be a
59  * good default tolerance for the EDID-based modes. Define it to 5 per
60  * mille to avoid floating point operations.
61  */
62 #define SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE	5
63 
64 static enum drm_mode_status sun4i_rgb_mode_valid(struct drm_encoder *crtc,
65 						 const struct drm_display_mode *mode)
66 {
67 	struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(crtc);
68 	struct sun4i_tcon *tcon = rgb->tcon;
69 	u32 hsync = mode->hsync_end - mode->hsync_start;
70 	u32 vsync = mode->vsync_end - mode->vsync_start;
71 	unsigned long long rate = mode->clock * 1000;
72 	unsigned long long lowest, highest;
73 	unsigned long long rounded_rate;
74 
75 	DRM_DEBUG_DRIVER("Validating modes...\n");
76 
77 	if (hsync < 1)
78 		return MODE_HSYNC_NARROW;
79 
80 	if (hsync > 0x3ff)
81 		return MODE_HSYNC_WIDE;
82 
83 	if ((mode->hdisplay < 1) || (mode->htotal < 1))
84 		return MODE_H_ILLEGAL;
85 
86 	if ((mode->hdisplay > 0x7ff) || (mode->htotal > 0xfff))
87 		return MODE_BAD_HVALUE;
88 
89 	DRM_DEBUG_DRIVER("Horizontal parameters OK\n");
90 
91 	if (vsync < 1)
92 		return MODE_VSYNC_NARROW;
93 
94 	if (vsync > 0x3ff)
95 		return MODE_VSYNC_WIDE;
96 
97 	if ((mode->vdisplay < 1) || (mode->vtotal < 1))
98 		return MODE_V_ILLEGAL;
99 
100 	if ((mode->vdisplay > 0x7ff) || (mode->vtotal > 0xfff))
101 		return MODE_BAD_VVALUE;
102 
103 	DRM_DEBUG_DRIVER("Vertical parameters OK\n");
104 
105 	/*
106 	 * TODO: We should use the struct display_timing if available
107 	 * and / or trying to stretch the timings within that
108 	 * tolerancy to take care of panels that we wouldn't be able
109 	 * to have a exact match for.
110 	 */
111 	if (rgb->panel) {
112 		DRM_DEBUG_DRIVER("RGB panel used, skipping clock rate checks");
113 		goto out;
114 	}
115 
116 	/*
117 	 * That shouldn't ever happen unless something is really wrong, but it
118 	 * doesn't harm to check.
119 	 */
120 	if (!rgb->bridge)
121 		goto out;
122 
123 	tcon->dclk_min_div = 6;
124 	tcon->dclk_max_div = 127;
125 	rounded_rate = clk_round_rate(tcon->dclk, rate);
126 
127 	lowest = rate * (1000 - SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE);
128 	do_div(lowest, 1000);
129 	if (rounded_rate < lowest)
130 		return MODE_CLOCK_LOW;
131 
132 	highest = rate * (1000 + SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE);
133 	do_div(highest, 1000);
134 	if (rounded_rate > highest)
135 		return MODE_CLOCK_HIGH;
136 
137 out:
138 	DRM_DEBUG_DRIVER("Clock rate OK\n");
139 
140 	return MODE_OK;
141 }
142 
143 static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = {
144 	.get_modes	= sun4i_rgb_get_modes,
145 };
146 
147 static void
148 sun4i_rgb_connector_destroy(struct drm_connector *connector)
149 {
150 	struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector);
151 
152 	drm_panel_detach(rgb->panel);
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 struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = {
189 	.disable	= sun4i_rgb_encoder_disable,
190 	.enable		= sun4i_rgb_encoder_enable,
191 	.mode_valid	= sun4i_rgb_mode_valid,
192 };
193 
194 static void sun4i_rgb_enc_destroy(struct drm_encoder *encoder)
195 {
196 	drm_encoder_cleanup(encoder);
197 }
198 
199 static struct drm_encoder_funcs sun4i_rgb_enc_funcs = {
200 	.destroy	= sun4i_rgb_enc_destroy,
201 };
202 
203 int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon)
204 {
205 	struct drm_encoder *encoder;
206 	struct sun4i_rgb *rgb;
207 	int ret;
208 
209 	rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL);
210 	if (!rgb)
211 		return -ENOMEM;
212 	rgb->tcon = tcon;
213 	encoder = &rgb->encoder;
214 
215 	ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0,
216 					  &rgb->panel, &rgb->bridge);
217 	if (ret) {
218 		dev_info(drm->dev, "No panel or bridge found... RGB output disabled\n");
219 		return 0;
220 	}
221 
222 	drm_encoder_helper_add(&rgb->encoder,
223 			       &sun4i_rgb_enc_helper_funcs);
224 	ret = drm_encoder_init(drm,
225 			       &rgb->encoder,
226 			       &sun4i_rgb_enc_funcs,
227 			       DRM_MODE_ENCODER_NONE,
228 			       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 		ret = drm_panel_attach(rgb->panel, &rgb->connector);
252 		if (ret) {
253 			dev_err(drm->dev, "Couldn't attach our panel\n");
254 			goto err_cleanup_connector;
255 		}
256 	}
257 
258 	if (rgb->bridge) {
259 		ret = drm_bridge_attach(encoder, rgb->bridge, NULL);
260 		if (ret) {
261 			dev_err(drm->dev, "Couldn't attach our bridge\n");
262 			goto err_cleanup_connector;
263 		}
264 	}
265 
266 	return 0;
267 
268 err_cleanup_connector:
269 	drm_encoder_cleanup(&rgb->encoder);
270 err_out:
271 	return ret;
272 }
273 EXPORT_SYMBOL(sun4i_rgb_init);
274