]>
Commit | Line | Data |
---|---|---|
d8f4a9ed TR |
1 | /* |
2 | * Copyright (C) 2012 Avionic Design GmbH | |
3 | * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | */ | |
9 | ||
10 | #include <linux/clk.h> | |
d8f4a9ed | 11 | |
4aa3df71 | 12 | #include <drm/drm_atomic_helper.h> |
3b0e5855 TR |
13 | #include <drm/drm_panel.h> |
14 | ||
d8f4a9ed TR |
15 | #include "drm.h" |
16 | #include "dc.h" | |
17 | ||
18 | struct tegra_rgb { | |
19 | struct tegra_output output; | |
7602fa1d TR |
20 | struct tegra_dc *dc; |
21 | ||
d8f4a9ed TR |
22 | struct clk *clk_parent; |
23 | struct clk *clk; | |
24 | }; | |
25 | ||
26 | static inline struct tegra_rgb *to_rgb(struct tegra_output *output) | |
27 | { | |
28 | return container_of(output, struct tegra_rgb, output); | |
29 | } | |
30 | ||
31 | struct reg_entry { | |
32 | unsigned long offset; | |
33 | unsigned long value; | |
34 | }; | |
35 | ||
36 | static const struct reg_entry rgb_enable[] = { | |
37 | { DC_COM_PIN_OUTPUT_ENABLE(0), 0x00000000 }, | |
38 | { DC_COM_PIN_OUTPUT_ENABLE(1), 0x00000000 }, | |
39 | { DC_COM_PIN_OUTPUT_ENABLE(2), 0x00000000 }, | |
40 | { DC_COM_PIN_OUTPUT_ENABLE(3), 0x00000000 }, | |
41 | { DC_COM_PIN_OUTPUT_POLARITY(0), 0x00000000 }, | |
42 | { DC_COM_PIN_OUTPUT_POLARITY(1), 0x01000000 }, | |
43 | { DC_COM_PIN_OUTPUT_POLARITY(2), 0x00000000 }, | |
44 | { DC_COM_PIN_OUTPUT_POLARITY(3), 0x00000000 }, | |
45 | { DC_COM_PIN_OUTPUT_DATA(0), 0x00000000 }, | |
46 | { DC_COM_PIN_OUTPUT_DATA(1), 0x00000000 }, | |
47 | { DC_COM_PIN_OUTPUT_DATA(2), 0x00000000 }, | |
48 | { DC_COM_PIN_OUTPUT_DATA(3), 0x00000000 }, | |
49 | { DC_COM_PIN_OUTPUT_SELECT(0), 0x00000000 }, | |
50 | { DC_COM_PIN_OUTPUT_SELECT(1), 0x00000000 }, | |
51 | { DC_COM_PIN_OUTPUT_SELECT(2), 0x00000000 }, | |
52 | { DC_COM_PIN_OUTPUT_SELECT(3), 0x00000000 }, | |
53 | { DC_COM_PIN_OUTPUT_SELECT(4), 0x00210222 }, | |
54 | { DC_COM_PIN_OUTPUT_SELECT(5), 0x00002200 }, | |
55 | { DC_COM_PIN_OUTPUT_SELECT(6), 0x00020000 }, | |
56 | }; | |
57 | ||
58 | static const struct reg_entry rgb_disable[] = { | |
59 | { DC_COM_PIN_OUTPUT_SELECT(6), 0x00000000 }, | |
60 | { DC_COM_PIN_OUTPUT_SELECT(5), 0x00000000 }, | |
61 | { DC_COM_PIN_OUTPUT_SELECT(4), 0x00000000 }, | |
62 | { DC_COM_PIN_OUTPUT_SELECT(3), 0x00000000 }, | |
63 | { DC_COM_PIN_OUTPUT_SELECT(2), 0x00000000 }, | |
64 | { DC_COM_PIN_OUTPUT_SELECT(1), 0x00000000 }, | |
65 | { DC_COM_PIN_OUTPUT_SELECT(0), 0x00000000 }, | |
66 | { DC_COM_PIN_OUTPUT_DATA(3), 0xaaaaaaaa }, | |
67 | { DC_COM_PIN_OUTPUT_DATA(2), 0xaaaaaaaa }, | |
68 | { DC_COM_PIN_OUTPUT_DATA(1), 0xaaaaaaaa }, | |
69 | { DC_COM_PIN_OUTPUT_DATA(0), 0xaaaaaaaa }, | |
70 | { DC_COM_PIN_OUTPUT_POLARITY(3), 0x00000000 }, | |
71 | { DC_COM_PIN_OUTPUT_POLARITY(2), 0x00000000 }, | |
72 | { DC_COM_PIN_OUTPUT_POLARITY(1), 0x00000000 }, | |
73 | { DC_COM_PIN_OUTPUT_POLARITY(0), 0x00000000 }, | |
74 | { DC_COM_PIN_OUTPUT_ENABLE(3), 0x55555555 }, | |
75 | { DC_COM_PIN_OUTPUT_ENABLE(2), 0x55555555 }, | |
76 | { DC_COM_PIN_OUTPUT_ENABLE(1), 0x55150005 }, | |
77 | { DC_COM_PIN_OUTPUT_ENABLE(0), 0x55555555 }, | |
78 | }; | |
79 | ||
80 | static void tegra_dc_write_regs(struct tegra_dc *dc, | |
81 | const struct reg_entry *table, | |
82 | unsigned int num) | |
83 | { | |
84 | unsigned int i; | |
85 | ||
86 | for (i = 0; i < num; i++) | |
87 | tegra_dc_writel(dc, table[i].value, table[i].offset); | |
88 | } | |
89 | ||
3b0e5855 | 90 | static const struct drm_connector_funcs tegra_rgb_connector_funcs = { |
9d44189f | 91 | .reset = drm_atomic_helper_connector_reset, |
3b0e5855 TR |
92 | .detect = tegra_output_connector_detect, |
93 | .fill_modes = drm_helper_probe_single_connector_modes, | |
94 | .destroy = tegra_output_connector_destroy, | |
9d44189f | 95 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
4aa3df71 | 96 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
3b0e5855 TR |
97 | }; |
98 | ||
99 | static enum drm_mode_status | |
100 | tegra_rgb_connector_mode_valid(struct drm_connector *connector, | |
101 | struct drm_display_mode *mode) | |
102 | { | |
103 | /* | |
104 | * FIXME: For now, always assume that the mode is okay. There are | |
105 | * unresolved issues with clk_round_rate(), which doesn't always | |
106 | * reliably report whether a frequency can be set or not. | |
107 | */ | |
108 | return MODE_OK; | |
109 | } | |
110 | ||
111 | static const struct drm_connector_helper_funcs tegra_rgb_connector_helper_funcs = { | |
112 | .get_modes = tegra_output_connector_get_modes, | |
113 | .mode_valid = tegra_rgb_connector_mode_valid, | |
3b0e5855 TR |
114 | }; |
115 | ||
116 | static const struct drm_encoder_funcs tegra_rgb_encoder_funcs = { | |
117 | .destroy = tegra_output_encoder_destroy, | |
118 | }; | |
119 | ||
32c3dee1 | 120 | static void tegra_rgb_encoder_disable(struct drm_encoder *encoder) |
3b0e5855 | 121 | { |
32c3dee1 TR |
122 | struct tegra_output *output = encoder_to_output(encoder); |
123 | struct tegra_rgb *rgb = to_rgb(output); | |
3b0e5855 | 124 | |
32c3dee1 TR |
125 | if (output->panel) |
126 | drm_panel_disable(output->panel); | |
3b0e5855 | 127 | |
32c3dee1 TR |
128 | tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable)); |
129 | tegra_dc_commit(rgb->dc); | |
130 | ||
131 | if (output->panel) | |
132 | drm_panel_unprepare(output->panel); | |
3b0e5855 TR |
133 | } |
134 | ||
32c3dee1 | 135 | static void tegra_rgb_encoder_enable(struct drm_encoder *encoder) |
3b0e5855 TR |
136 | { |
137 | struct tegra_output *output = encoder_to_output(encoder); | |
138 | struct tegra_rgb *rgb = to_rgb(output); | |
139 | u32 value; | |
140 | ||
141 | if (output->panel) | |
142 | drm_panel_prepare(output->panel); | |
b1891537 | 143 | |
7602fa1d | 144 | tegra_dc_write_regs(rgb->dc, rgb_enable, ARRAY_SIZE(rgb_enable)); |
d8f4a9ed | 145 | |
72d30286 TR |
146 | value = DE_SELECT_ACTIVE | DE_CONTROL_NORMAL; |
147 | tegra_dc_writel(rgb->dc, value, DC_DISP_DATA_ENABLE_OPTIONS); | |
148 | ||
149 | /* XXX: parameterize? */ | |
150 | value = tegra_dc_readl(rgb->dc, DC_COM_PIN_OUTPUT_POLARITY(1)); | |
151 | value &= ~LVS_OUTPUT_POLARITY_LOW; | |
152 | value &= ~LHS_OUTPUT_POLARITY_LOW; | |
153 | tegra_dc_writel(rgb->dc, value, DC_COM_PIN_OUTPUT_POLARITY(1)); | |
154 | ||
155 | /* XXX: parameterize? */ | |
156 | value = DISP_DATA_FORMAT_DF1P1C | DISP_ALIGNMENT_MSB | | |
157 | DISP_ORDER_RED_BLUE; | |
158 | tegra_dc_writel(rgb->dc, value, DC_DISP_DISP_INTERFACE_CONTROL); | |
159 | ||
160 | /* XXX: parameterize? */ | |
161 | value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE; | |
162 | tegra_dc_writel(rgb->dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS); | |
163 | ||
62b9e063 | 164 | tegra_dc_commit(rgb->dc); |
72d30286 | 165 | |
3b0e5855 TR |
166 | if (output->panel) |
167 | drm_panel_enable(output->panel); | |
d8f4a9ed TR |
168 | } |
169 | ||
3cebae67 TR |
170 | static int |
171 | tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder, | |
172 | struct drm_crtc_state *crtc_state, | |
173 | struct drm_connector_state *conn_state) | |
174 | { | |
175 | struct tegra_output *output = encoder_to_output(encoder); | |
176 | struct tegra_dc *dc = to_tegra_dc(conn_state->crtc); | |
177 | unsigned long pclk = crtc_state->mode.clock * 1000; | |
178 | struct tegra_rgb *rgb = to_rgb(output); | |
179 | unsigned int div; | |
180 | int err; | |
181 | ||
182 | /* | |
183 | * We may not want to change the frequency of the parent clock, since | |
184 | * it may be a parent for other peripherals. This is due to the fact | |
185 | * that on Tegra20 there's only a single clock dedicated to display | |
186 | * (pll_d_out0), whereas later generations have a second one that can | |
187 | * be used to independently drive a second output (pll_d2_out0). | |
188 | * | |
189 | * As a way to support multiple outputs on Tegra20 as well, pll_p is | |
190 | * typically used as the parent clock for the display controllers. | |
191 | * But this comes at a cost: pll_p is the parent of several other | |
192 | * peripherals, so its frequency shouldn't change out of the blue. | |
193 | * | |
194 | * The best we can do at this point is to use the shift clock divider | |
195 | * and hope that the desired frequency can be matched (or at least | |
196 | * matched sufficiently close that the panel will still work). | |
197 | */ | |
198 | div = ((clk_get_rate(rgb->clk) * 2) / pclk) - 2; | |
199 | pclk = 0; | |
200 | ||
201 | err = tegra_dc_state_setup_clock(dc, crtc_state, rgb->clk_parent, | |
202 | pclk, div); | |
203 | if (err < 0) { | |
204 | dev_err(output->dev, "failed to setup CRTC state: %d\n", err); | |
205 | return err; | |
206 | } | |
207 | ||
208 | return err; | |
209 | } | |
210 | ||
3b0e5855 | 211 | static const struct drm_encoder_helper_funcs tegra_rgb_encoder_helper_funcs = { |
3b0e5855 | 212 | .disable = tegra_rgb_encoder_disable, |
32c3dee1 | 213 | .enable = tegra_rgb_encoder_enable, |
3cebae67 | 214 | .atomic_check = tegra_rgb_encoder_atomic_check, |
d8f4a9ed TR |
215 | }; |
216 | ||
217 | int tegra_dc_rgb_probe(struct tegra_dc *dc) | |
218 | { | |
219 | struct device_node *np; | |
220 | struct tegra_rgb *rgb; | |
221 | int err; | |
222 | ||
223 | np = of_get_child_by_name(dc->dev->of_node, "rgb"); | |
224 | if (!np || !of_device_is_available(np)) | |
225 | return -ENODEV; | |
226 | ||
227 | rgb = devm_kzalloc(dc->dev, sizeof(*rgb), GFP_KERNEL); | |
228 | if (!rgb) | |
229 | return -ENOMEM; | |
230 | ||
03da0e7b TR |
231 | rgb->output.dev = dc->dev; |
232 | rgb->output.of_node = np; | |
7602fa1d | 233 | rgb->dc = dc; |
03da0e7b | 234 | |
59d29c0e | 235 | err = tegra_output_probe(&rgb->output); |
03da0e7b TR |
236 | if (err < 0) |
237 | return err; | |
238 | ||
d8f4a9ed TR |
239 | rgb->clk = devm_clk_get(dc->dev, NULL); |
240 | if (IS_ERR(rgb->clk)) { | |
241 | dev_err(dc->dev, "failed to get clock\n"); | |
242 | return PTR_ERR(rgb->clk); | |
243 | } | |
244 | ||
245 | rgb->clk_parent = devm_clk_get(dc->dev, "parent"); | |
246 | if (IS_ERR(rgb->clk_parent)) { | |
247 | dev_err(dc->dev, "failed to get parent clock\n"); | |
248 | return PTR_ERR(rgb->clk_parent); | |
249 | } | |
250 | ||
251 | err = clk_set_parent(rgb->clk, rgb->clk_parent); | |
252 | if (err < 0) { | |
253 | dev_err(dc->dev, "failed to set parent clock: %d\n", err); | |
254 | return err; | |
255 | } | |
256 | ||
d8f4a9ed TR |
257 | dc->rgb = &rgb->output; |
258 | ||
259 | return 0; | |
260 | } | |
261 | ||
59d29c0e TR |
262 | int tegra_dc_rgb_remove(struct tegra_dc *dc) |
263 | { | |
59d29c0e TR |
264 | if (!dc->rgb) |
265 | return 0; | |
266 | ||
328ec69e | 267 | tegra_output_remove(dc->rgb); |
3b0e5855 TR |
268 | dc->rgb = NULL; |
269 | ||
59d29c0e TR |
270 | return 0; |
271 | } | |
272 | ||
d8f4a9ed TR |
273 | int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc) |
274 | { | |
3b0e5855 | 275 | struct tegra_output *output = dc->rgb; |
d8f4a9ed TR |
276 | int err; |
277 | ||
278 | if (!dc->rgb) | |
279 | return -ENODEV; | |
280 | ||
3b0e5855 TR |
281 | drm_connector_init(drm, &output->connector, &tegra_rgb_connector_funcs, |
282 | DRM_MODE_CONNECTOR_LVDS); | |
283 | drm_connector_helper_add(&output->connector, | |
284 | &tegra_rgb_connector_helper_funcs); | |
285 | output->connector.dpms = DRM_MODE_DPMS_OFF; | |
286 | ||
3b0e5855 | 287 | drm_encoder_init(drm, &output->encoder, &tegra_rgb_encoder_funcs, |
13a3d91f | 288 | DRM_MODE_ENCODER_LVDS, NULL); |
3b0e5855 TR |
289 | drm_encoder_helper_add(&output->encoder, |
290 | &tegra_rgb_encoder_helper_funcs); | |
291 | ||
cde4c44d | 292 | drm_connector_attach_encoder(&output->connector, |
3b0e5855 TR |
293 | &output->encoder); |
294 | drm_connector_register(&output->connector); | |
295 | ||
ea130b24 TR |
296 | err = tegra_output_init(drm, output); |
297 | if (err < 0) { | |
298 | dev_err(output->dev, "failed to initialize output: %d\n", err); | |
299 | return err; | |
300 | } | |
301 | ||
d8f4a9ed | 302 | /* |
3b0e5855 TR |
303 | * Other outputs can be attached to either display controller. The RGB |
304 | * outputs are an exception and work only with their parent display | |
305 | * controller. | |
d8f4a9ed | 306 | */ |
3b0e5855 | 307 | output->encoder.possible_crtcs = drm_crtc_mask(&dc->base); |
d8f4a9ed TR |
308 | |
309 | return 0; | |
310 | } | |
311 | ||
312 | int tegra_dc_rgb_exit(struct tegra_dc *dc) | |
313 | { | |
328ec69e TR |
314 | if (dc->rgb) |
315 | tegra_output_exit(dc->rgb); | |
d8f4a9ed | 316 | |
328ec69e | 317 | return 0; |
d8f4a9ed | 318 | } |