1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (C) 2015 Free Electrons
4 * Copyright (C) 2015 NextThing Co
6 * Maxime Ripard <maxime.ripard@free-electrons.com>
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>
18 #include "sun4i_crtc.h"
19 #include "sun4i_tcon.h"
20 #include "sun4i_rgb.h"
23 struct drm_connector connector
;
24 struct drm_encoder encoder
;
26 struct sun4i_tcon
*tcon
;
27 struct drm_panel
*panel
;
28 struct drm_bridge
*bridge
;
31 static inline struct sun4i_rgb
*
32 drm_connector_to_sun4i_rgb(struct drm_connector
*connector
)
34 return container_of(connector
, struct sun4i_rgb
,
38 static inline struct sun4i_rgb
*
39 drm_encoder_to_sun4i_rgb(struct drm_encoder
*encoder
)
41 return container_of(encoder
, struct sun4i_rgb
,
45 static int sun4i_rgb_get_modes(struct drm_connector
*connector
)
47 struct sun4i_rgb
*rgb
=
48 drm_connector_to_sun4i_rgb(connector
);
50 return drm_panel_get_modes(rgb
->panel
, connector
);
54 * VESA DMT defines a tolerance of 0.5% on the pixel clock, while the
55 * CVT spec reuses that tolerance in its examples, so it looks to be a
56 * good default tolerance for the EDID-based modes. Define it to 5 per
57 * mille to avoid floating point operations.
59 #define SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE 5
61 static enum drm_mode_status
sun4i_rgb_mode_valid(struct drm_encoder
*crtc
,
62 const struct drm_display_mode
*mode
)
64 struct sun4i_rgb
*rgb
= drm_encoder_to_sun4i_rgb(crtc
);
65 struct sun4i_tcon
*tcon
= rgb
->tcon
;
66 u32 hsync
= mode
->hsync_end
- mode
->hsync_start
;
67 u32 vsync
= mode
->vsync_end
- mode
->vsync_start
;
68 unsigned long long rate
= mode
->clock
* 1000;
69 unsigned long long lowest
, highest
;
70 unsigned long long rounded_rate
;
72 DRM_DEBUG_DRIVER("Validating modes...\n");
75 return MODE_HSYNC_NARROW
;
78 return MODE_HSYNC_WIDE
;
80 if ((mode
->hdisplay
< 1) || (mode
->htotal
< 1))
81 return MODE_H_ILLEGAL
;
83 if ((mode
->hdisplay
> 0x7ff) || (mode
->htotal
> 0xfff))
84 return MODE_BAD_HVALUE
;
86 DRM_DEBUG_DRIVER("Horizontal parameters OK\n");
89 return MODE_VSYNC_NARROW
;
92 return MODE_VSYNC_WIDE
;
94 if ((mode
->vdisplay
< 1) || (mode
->vtotal
< 1))
95 return MODE_V_ILLEGAL
;
97 if ((mode
->vdisplay
> 0x7ff) || (mode
->vtotal
> 0xfff))
98 return MODE_BAD_VVALUE
;
100 DRM_DEBUG_DRIVER("Vertical parameters OK\n");
103 * TODO: We should use the struct display_timing if available
104 * and / or trying to stretch the timings within that
105 * tolerancy to take care of panels that we wouldn't be able
106 * to have a exact match for.
109 DRM_DEBUG_DRIVER("RGB panel used, skipping clock rate checks");
114 * That shouldn't ever happen unless something is really wrong, but it
115 * doesn't harm to check.
120 tcon
->dclk_min_div
= 6;
121 tcon
->dclk_max_div
= 127;
122 rounded_rate
= clk_round_rate(tcon
->dclk
, rate
);
124 lowest
= rate
* (1000 - SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE
);
125 do_div(lowest
, 1000);
126 if (rounded_rate
< lowest
)
127 return MODE_CLOCK_LOW
;
129 highest
= rate
* (1000 + SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE
);
130 do_div(highest
, 1000);
131 if (rounded_rate
> highest
)
132 return MODE_CLOCK_HIGH
;
135 DRM_DEBUG_DRIVER("Clock rate OK\n");
140 static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs
= {
141 .get_modes
= sun4i_rgb_get_modes
,
145 sun4i_rgb_connector_destroy(struct drm_connector
*connector
)
147 struct sun4i_rgb
*rgb
= drm_connector_to_sun4i_rgb(connector
);
149 drm_panel_detach(rgb
->panel
);
150 drm_connector_cleanup(connector
);
153 static const struct drm_connector_funcs sun4i_rgb_con_funcs
= {
154 .fill_modes
= drm_helper_probe_single_connector_modes
,
155 .destroy
= sun4i_rgb_connector_destroy
,
156 .reset
= drm_atomic_helper_connector_reset
,
157 .atomic_duplicate_state
= drm_atomic_helper_connector_duplicate_state
,
158 .atomic_destroy_state
= drm_atomic_helper_connector_destroy_state
,
161 static void sun4i_rgb_encoder_enable(struct drm_encoder
*encoder
)
163 struct sun4i_rgb
*rgb
= drm_encoder_to_sun4i_rgb(encoder
);
165 DRM_DEBUG_DRIVER("Enabling RGB output\n");
168 drm_panel_prepare(rgb
->panel
);
169 drm_panel_enable(rgb
->panel
);
173 static void sun4i_rgb_encoder_disable(struct drm_encoder
*encoder
)
175 struct sun4i_rgb
*rgb
= drm_encoder_to_sun4i_rgb(encoder
);
177 DRM_DEBUG_DRIVER("Disabling RGB output\n");
180 drm_panel_disable(rgb
->panel
);
181 drm_panel_unprepare(rgb
->panel
);
185 static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs
= {
186 .disable
= sun4i_rgb_encoder_disable
,
187 .enable
= sun4i_rgb_encoder_enable
,
188 .mode_valid
= sun4i_rgb_mode_valid
,
191 static void sun4i_rgb_enc_destroy(struct drm_encoder
*encoder
)
193 drm_encoder_cleanup(encoder
);
196 static struct drm_encoder_funcs sun4i_rgb_enc_funcs
= {
197 .destroy
= sun4i_rgb_enc_destroy
,
200 int sun4i_rgb_init(struct drm_device
*drm
, struct sun4i_tcon
*tcon
)
202 struct drm_encoder
*encoder
;
203 struct sun4i_rgb
*rgb
;
206 rgb
= devm_kzalloc(drm
->dev
, sizeof(*rgb
), GFP_KERNEL
);
210 encoder
= &rgb
->encoder
;
212 ret
= drm_of_find_panel_or_bridge(tcon
->dev
->of_node
, 1, 0,
213 &rgb
->panel
, &rgb
->bridge
);
215 dev_info(drm
->dev
, "No panel or bridge found... RGB output disabled\n");
219 drm_encoder_helper_add(&rgb
->encoder
,
220 &sun4i_rgb_enc_helper_funcs
);
221 ret
= drm_encoder_init(drm
,
223 &sun4i_rgb_enc_funcs
,
224 DRM_MODE_ENCODER_NONE
,
227 dev_err(drm
->dev
, "Couldn't initialise the rgb encoder\n");
231 /* The RGB encoder can only work with the TCON channel 0 */
232 rgb
->encoder
.possible_crtcs
= drm_crtc_mask(&tcon
->crtc
->crtc
);
235 drm_connector_helper_add(&rgb
->connector
,
236 &sun4i_rgb_con_helper_funcs
);
237 ret
= drm_connector_init(drm
, &rgb
->connector
,
238 &sun4i_rgb_con_funcs
,
239 DRM_MODE_CONNECTOR_Unknown
);
241 dev_err(drm
->dev
, "Couldn't initialise the rgb connector\n");
242 goto err_cleanup_connector
;
245 drm_connector_attach_encoder(&rgb
->connector
,
248 ret
= drm_panel_attach(rgb
->panel
, &rgb
->connector
);
250 dev_err(drm
->dev
, "Couldn't attach our panel\n");
251 goto err_cleanup_connector
;
256 ret
= drm_bridge_attach(encoder
, rgb
->bridge
, NULL
);
258 dev_err(drm
->dev
, "Couldn't attach our bridge\n");
259 goto err_cleanup_connector
;
265 err_cleanup_connector
:
266 drm_encoder_cleanup(&rgb
->encoder
);
270 EXPORT_SYMBOL(sun4i_rgb_init
);