]>
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> | |
ebc94461 | 18 | #include <drm/drm_of.h> |
29e57fab MR |
19 | #include <drm/drm_panel.h> |
20 | ||
a5154a4d | 21 | #include "sun4i_crtc.h" |
29e57fab | 22 | #include "sun4i_tcon.h" |
0c3ff44c | 23 | #include "sun4i_rgb.h" |
29e57fab MR |
24 | |
25 | struct sun4i_rgb { | |
26 | struct drm_connector connector; | |
27 | struct drm_encoder encoder; | |
28 | ||
b9c8506c | 29 | struct sun4i_tcon *tcon; |
29e57fab MR |
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 int sun4i_rgb_get_modes(struct drm_connector *connector) | |
47 | { | |
48 | struct sun4i_rgb *rgb = | |
49 | drm_connector_to_sun4i_rgb(connector); | |
b9c8506c | 50 | struct sun4i_tcon *tcon = rgb->tcon; |
29e57fab MR |
51 | |
52 | return drm_panel_get_modes(tcon->panel); | |
53 | } | |
54 | ||
55 | static int sun4i_rgb_mode_valid(struct drm_connector *connector, | |
56 | struct drm_display_mode *mode) | |
57 | { | |
bb43d40d | 58 | struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); |
b9c8506c | 59 | struct sun4i_tcon *tcon = rgb->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 | ||
5af894bd MR |
95 | tcon->dclk_min_div = 6; |
96 | tcon->dclk_max_div = 127; | |
bb43d40d MR |
97 | rounded_rate = clk_round_rate(tcon->dclk, rate); |
98 | if (rounded_rate < rate) | |
99 | return MODE_CLOCK_LOW; | |
100 | ||
101 | if (rounded_rate > rate) | |
102 | return MODE_CLOCK_HIGH; | |
103 | ||
104 | DRM_DEBUG_DRIVER("Clock rate OK\n"); | |
105 | ||
29e57fab MR |
106 | return MODE_OK; |
107 | } | |
108 | ||
29e57fab MR |
109 | static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { |
110 | .get_modes = sun4i_rgb_get_modes, | |
111 | .mode_valid = sun4i_rgb_mode_valid, | |
29e57fab MR |
112 | }; |
113 | ||
29e57fab MR |
114 | static void |
115 | sun4i_rgb_connector_destroy(struct drm_connector *connector) | |
116 | { | |
117 | struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); | |
b9c8506c | 118 | struct sun4i_tcon *tcon = rgb->tcon; |
29e57fab MR |
119 | |
120 | drm_panel_detach(tcon->panel); | |
121 | drm_connector_cleanup(connector); | |
122 | } | |
123 | ||
32b4d575 | 124 | static const struct drm_connector_funcs sun4i_rgb_con_funcs = { |
29e57fab MR |
125 | .fill_modes = drm_helper_probe_single_connector_modes, |
126 | .destroy = sun4i_rgb_connector_destroy, | |
127 | .reset = drm_atomic_helper_connector_reset, | |
128 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | |
129 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | |
130 | }; | |
131 | ||
29e57fab MR |
132 | static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) |
133 | { | |
134 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); | |
b9c8506c | 135 | struct sun4i_tcon *tcon = rgb->tcon; |
29e57fab MR |
136 | |
137 | DRM_DEBUG_DRIVER("Enabling RGB output\n"); | |
138 | ||
45e88f99 | 139 | if (!IS_ERR(tcon->panel)) { |
4b309502 | 140 | drm_panel_prepare(tcon->panel); |
5647b25c | 141 | drm_panel_enable(tcon->panel); |
45e88f99 | 142 | } |
29e57fab MR |
143 | } |
144 | ||
145 | static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) | |
146 | { | |
147 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); | |
b9c8506c | 148 | struct sun4i_tcon *tcon = rgb->tcon; |
29e57fab MR |
149 | |
150 | DRM_DEBUG_DRIVER("Disabling RGB output\n"); | |
151 | ||
45e88f99 | 152 | if (!IS_ERR(tcon->panel)) { |
5647b25c | 153 | drm_panel_disable(tcon->panel); |
4b309502 | 154 | drm_panel_unprepare(tcon->panel); |
45e88f99 | 155 | } |
29e57fab MR |
156 | } |
157 | ||
29e57fab | 158 | static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { |
29e57fab MR |
159 | .disable = sun4i_rgb_encoder_disable, |
160 | .enable = sun4i_rgb_encoder_enable, | |
161 | }; | |
162 | ||
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 | ||
b9c8506c | 172 | int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon) |
29e57fab | 173 | { |
894f5a9f | 174 | struct drm_encoder *encoder; |
3bb80f24 | 175 | struct drm_bridge *bridge; |
29e57fab MR |
176 | struct sun4i_rgb *rgb; |
177 | int ret; | |
178 | ||
29e57fab MR |
179 | rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL); |
180 | if (!rgb) | |
181 | return -ENOMEM; | |
b9c8506c | 182 | rgb->tcon = tcon; |
894f5a9f | 183 | encoder = &rgb->encoder; |
29e57fab | 184 | |
ebc94461 RH |
185 | ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0, |
186 | &tcon->panel, &bridge); | |
187 | if (ret) { | |
894f5a9f | 188 | dev_info(drm->dev, "No panel or bridge found... RGB output disabled\n"); |
a8444c7e MR |
189 | return 0; |
190 | } | |
191 | ||
29e57fab MR |
192 | drm_encoder_helper_add(&rgb->encoder, |
193 | &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); | |
199 | if (ret) { | |
200 | dev_err(drm->dev, "Couldn't initialise the rgb encoder\n"); | |
201 | goto err_out; | |
202 | } | |
203 | ||
204 | /* The RGB encoder can only work with the TCON channel 0 */ | |
a5154a4d | 205 | rgb->encoder.possible_crtcs = BIT(drm_crtc_index(&tcon->crtc->crtc)); |
29e57fab | 206 | |
ebc94461 | 207 | if (tcon->panel) { |
894f5a9f MR |
208 | drm_connector_helper_add(&rgb->connector, |
209 | &sun4i_rgb_con_helper_funcs); | |
210 | ret = drm_connector_init(drm, &rgb->connector, | |
211 | &sun4i_rgb_con_funcs, | |
212 | DRM_MODE_CONNECTOR_Unknown); | |
213 | if (ret) { | |
214 | dev_err(drm->dev, "Couldn't initialise the rgb connector\n"); | |
215 | goto err_cleanup_connector; | |
216 | } | |
217 | ||
218 | drm_mode_connector_attach_encoder(&rgb->connector, | |
219 | &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 | } | |
29e57fab MR |
226 | } |
227 | ||
ebc94461 | 228 | if (bridge) { |
3bb80f24 | 229 | ret = drm_bridge_attach(encoder, bridge, NULL); |
894f5a9f MR |
230 | if (ret) { |
231 | dev_err(drm->dev, "Couldn't attach our bridge\n"); | |
232 | goto err_cleanup_connector; | |
233 | } | |
234 | } | |
29e57fab MR |
235 | |
236 | return 0; | |
237 | ||
238 | err_cleanup_connector: | |
239 | drm_encoder_cleanup(&rgb->encoder); | |
240 | err_out: | |
241 | return ret; | |
242 | } | |
243 | EXPORT_SYMBOL(sun4i_rgb_init); |