Commit | Line | Data |
---|---|---|
29e57fab MR |
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_crtc_helper.h> | |
18 | #include <drm/drm_panel.h> | |
19 | ||
20 | #include "sun4i_drv.h" | |
21 | #include "sun4i_tcon.h" | |
22 | ||
23 | struct sun4i_rgb { | |
24 | struct drm_connector connector; | |
25 | struct drm_encoder encoder; | |
26 | ||
27 | struct sun4i_drv *drv; | |
28 | }; | |
29 | ||
30 | static inline struct sun4i_rgb * | |
31 | drm_connector_to_sun4i_rgb(struct drm_connector *connector) | |
32 | { | |
33 | return container_of(connector, struct sun4i_rgb, | |
34 | connector); | |
35 | } | |
36 | ||
37 | static inline struct sun4i_rgb * | |
38 | drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder) | |
39 | { | |
40 | return container_of(encoder, struct sun4i_rgb, | |
41 | encoder); | |
42 | } | |
43 | ||
44 | static int sun4i_rgb_get_modes(struct drm_connector *connector) | |
45 | { | |
46 | struct sun4i_rgb *rgb = | |
47 | drm_connector_to_sun4i_rgb(connector); | |
48 | struct sun4i_drv *drv = rgb->drv; | |
49 | struct sun4i_tcon *tcon = drv->tcon; | |
50 | ||
51 | return drm_panel_get_modes(tcon->panel); | |
52 | } | |
53 | ||
54 | static int sun4i_rgb_mode_valid(struct drm_connector *connector, | |
55 | struct drm_display_mode *mode) | |
56 | { | |
bb43d40d MR |
57 | struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); |
58 | struct sun4i_drv *drv = rgb->drv; | |
59 | struct sun4i_tcon *tcon = drv->tcon; | |
29e57fab MR |
60 | u32 hsync = mode->hsync_end - mode->hsync_start; |
61 | u32 vsync = mode->vsync_end - mode->vsync_start; | |
bb43d40d MR |
62 | unsigned long rate = mode->clock * 1000; |
63 | long rounded_rate; | |
29e57fab MR |
64 | |
65 | DRM_DEBUG_DRIVER("Validating modes...\n"); | |
66 | ||
67 | if (hsync < 1) | |
68 | return MODE_HSYNC_NARROW; | |
69 | ||
70 | if (hsync > 0x3ff) | |
71 | return MODE_HSYNC_WIDE; | |
72 | ||
73 | if ((mode->hdisplay < 1) || (mode->htotal < 1)) | |
74 | return MODE_H_ILLEGAL; | |
75 | ||
76 | if ((mode->hdisplay > 0x7ff) || (mode->htotal > 0xfff)) | |
77 | return MODE_BAD_HVALUE; | |
78 | ||
79 | DRM_DEBUG_DRIVER("Horizontal parameters OK\n"); | |
80 | ||
81 | if (vsync < 1) | |
82 | return MODE_VSYNC_NARROW; | |
83 | ||
84 | if (vsync > 0x3ff) | |
85 | return MODE_VSYNC_WIDE; | |
86 | ||
87 | if ((mode->vdisplay < 1) || (mode->vtotal < 1)) | |
88 | return MODE_V_ILLEGAL; | |
89 | ||
90 | if ((mode->vdisplay > 0x7ff) || (mode->vtotal > 0xfff)) | |
91 | return MODE_BAD_VVALUE; | |
92 | ||
93 | DRM_DEBUG_DRIVER("Vertical parameters OK\n"); | |
94 | ||
bb43d40d MR |
95 | rounded_rate = clk_round_rate(tcon->dclk, rate); |
96 | if (rounded_rate < rate) | |
97 | return MODE_CLOCK_LOW; | |
98 | ||
99 | if (rounded_rate > rate) | |
100 | return MODE_CLOCK_HIGH; | |
101 | ||
102 | DRM_DEBUG_DRIVER("Clock rate OK\n"); | |
103 | ||
29e57fab MR |
104 | return MODE_OK; |
105 | } | |
106 | ||
107 | static struct drm_encoder * | |
108 | sun4i_rgb_best_encoder(struct drm_connector *connector) | |
109 | { | |
110 | struct sun4i_rgb *rgb = | |
111 | drm_connector_to_sun4i_rgb(connector); | |
112 | ||
113 | return &rgb->encoder; | |
114 | } | |
115 | ||
116 | static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { | |
117 | .get_modes = sun4i_rgb_get_modes, | |
118 | .mode_valid = sun4i_rgb_mode_valid, | |
119 | .best_encoder = sun4i_rgb_best_encoder, | |
120 | }; | |
121 | ||
122 | static enum drm_connector_status | |
123 | sun4i_rgb_connector_detect(struct drm_connector *connector, bool force) | |
124 | { | |
125 | return connector_status_connected; | |
126 | } | |
127 | ||
128 | static void | |
129 | sun4i_rgb_connector_destroy(struct drm_connector *connector) | |
130 | { | |
131 | struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); | |
132 | struct sun4i_drv *drv = rgb->drv; | |
133 | struct sun4i_tcon *tcon = drv->tcon; | |
134 | ||
135 | drm_panel_detach(tcon->panel); | |
136 | drm_connector_cleanup(connector); | |
137 | } | |
138 | ||
139 | static struct drm_connector_funcs sun4i_rgb_con_funcs = { | |
140 | .dpms = drm_atomic_helper_connector_dpms, | |
141 | .detect = sun4i_rgb_connector_detect, | |
142 | .fill_modes = drm_helper_probe_single_connector_modes, | |
143 | .destroy = sun4i_rgb_connector_destroy, | |
144 | .reset = drm_atomic_helper_connector_reset, | |
145 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | |
146 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | |
147 | }; | |
148 | ||
149 | static int sun4i_rgb_atomic_check(struct drm_encoder *encoder, | |
150 | struct drm_crtc_state *crtc_state, | |
151 | struct drm_connector_state *conn_state) | |
152 | { | |
153 | return 0; | |
154 | } | |
155 | ||
156 | static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) | |
157 | { | |
158 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); | |
159 | struct sun4i_drv *drv = rgb->drv; | |
160 | struct sun4i_tcon *tcon = drv->tcon; | |
161 | ||
162 | DRM_DEBUG_DRIVER("Enabling RGB output\n"); | |
163 | ||
164 | drm_panel_enable(tcon->panel); | |
165 | sun4i_tcon_channel_enable(tcon, 0); | |
166 | } | |
167 | ||
168 | static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) | |
169 | { | |
170 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); | |
171 | struct sun4i_drv *drv = rgb->drv; | |
172 | struct sun4i_tcon *tcon = drv->tcon; | |
173 | ||
174 | DRM_DEBUG_DRIVER("Disabling RGB output\n"); | |
175 | ||
176 | sun4i_tcon_channel_disable(tcon, 0); | |
177 | drm_panel_disable(tcon->panel); | |
178 | } | |
179 | ||
180 | static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, | |
181 | struct drm_display_mode *mode, | |
182 | struct drm_display_mode *adjusted_mode) | |
183 | { | |
184 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); | |
185 | struct sun4i_drv *drv = rgb->drv; | |
186 | struct sun4i_tcon *tcon = drv->tcon; | |
187 | ||
188 | sun4i_tcon0_mode_set(tcon, mode); | |
189 | ||
190 | clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); | |
191 | ||
192 | /* FIXME: This seems to be board specific */ | |
193 | clk_set_phase(tcon->dclk, 120); | |
194 | } | |
195 | ||
196 | static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { | |
197 | .atomic_check = sun4i_rgb_atomic_check, | |
198 | .mode_set = sun4i_rgb_encoder_mode_set, | |
199 | .disable = sun4i_rgb_encoder_disable, | |
200 | .enable = sun4i_rgb_encoder_enable, | |
201 | }; | |
202 | ||
203 | static void sun4i_rgb_enc_destroy(struct drm_encoder *encoder) | |
204 | { | |
205 | drm_encoder_cleanup(encoder); | |
206 | } | |
207 | ||
208 | static struct drm_encoder_funcs sun4i_rgb_enc_funcs = { | |
209 | .destroy = sun4i_rgb_enc_destroy, | |
210 | }; | |
211 | ||
212 | int sun4i_rgb_init(struct drm_device *drm) | |
213 | { | |
214 | struct sun4i_drv *drv = drm->dev_private; | |
215 | struct sun4i_tcon *tcon = drv->tcon; | |
216 | struct sun4i_rgb *rgb; | |
217 | int ret; | |
218 | ||
219 | /* If we don't have a panel, there's no point in going on */ | |
0de6e914 | 220 | if (IS_ERR(tcon->panel)) |
29e57fab MR |
221 | return -ENODEV; |
222 | ||
223 | rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL); | |
224 | if (!rgb) | |
225 | return -ENOMEM; | |
226 | rgb->drv = drv; | |
227 | ||
228 | drm_encoder_helper_add(&rgb->encoder, | |
229 | &sun4i_rgb_enc_helper_funcs); | |
230 | ret = drm_encoder_init(drm, | |
231 | &rgb->encoder, | |
232 | &sun4i_rgb_enc_funcs, | |
233 | DRM_MODE_ENCODER_NONE, | |
234 | NULL); | |
235 | if (ret) { | |
236 | dev_err(drm->dev, "Couldn't initialise the rgb encoder\n"); | |
237 | goto err_out; | |
238 | } | |
239 | ||
240 | /* The RGB encoder can only work with the TCON channel 0 */ | |
241 | rgb->encoder.possible_crtcs = BIT(0); | |
242 | ||
243 | drm_connector_helper_add(&rgb->connector, | |
244 | &sun4i_rgb_con_helper_funcs); | |
245 | ret = drm_connector_init(drm, &rgb->connector, | |
246 | &sun4i_rgb_con_funcs, | |
247 | DRM_MODE_CONNECTOR_Unknown); | |
248 | if (ret) { | |
249 | dev_err(drm->dev, "Couldn't initialise the rgb connector\n"); | |
250 | goto err_cleanup_connector; | |
251 | } | |
252 | ||
253 | drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder); | |
254 | ||
255 | drm_panel_attach(tcon->panel, &rgb->connector); | |
256 | ||
257 | return 0; | |
258 | ||
259 | err_cleanup_connector: | |
260 | drm_encoder_cleanup(&rgb->encoder); | |
261 | err_out: | |
262 | return ret; | |
263 | } | |
264 | EXPORT_SYMBOL(sun4i_rgb_init); |