1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2023 Loongson Technology Corporation Limited |
4 | */ |
5 | |
6 | #include <linux/delay.h> |
7 | |
8 | #include <drm/drm_atomic_helper.h> |
9 | #include <drm/drm_debugfs.h> |
10 | #include <drm/drm_edid.h> |
11 | #include <drm/drm_probe_helper.h> |
12 | |
13 | #include "lsdc_drv.h" |
14 | #include "lsdc_output.h" |
15 | |
16 | /* |
17 | * The display controller in LS7A2000 has two display pipes |
18 | * Display pipe 0 is attached with a built-in transparent VGA encoder and |
19 | * a built-in HDMI encoder. |
20 | * Display pipe 1 has only one built-in HDMI encoder connected. |
21 | * ______________________ _____________ |
22 | * | +-----+ | | | |
23 | * | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA Monitor |<---+ |
24 | * | | +-----+ | |_____________| | |
25 | * | | | ______________ | |
26 | * | | +------+ | | | | |
27 | * | +--> | HDMI | ----> HDMI Connector --> | HDMI Monitor |<--+ |
28 | * | +------+ | |______________| | |
29 | * | +------+ | | |
30 | * | | i2c6 | <-------------------------------------------+ |
31 | * | +------+ | |
32 | * | | |
33 | * | DC in LS7A2000 | |
34 | * | | |
35 | * | +------+ | |
36 | * | | i2c7 | <--------------------------------+ |
37 | * | +------+ | | |
38 | * | | ______|_______ |
39 | * | +------+ | | | |
40 | * | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI Monitor | |
41 | * | +------+ | |______________| |
42 | * |______________________| |
43 | */ |
44 | |
45 | static int ls7a2000_connector_get_modes(struct drm_connector *connector) |
46 | { |
47 | unsigned int num = 0; |
48 | struct edid *edid; |
49 | |
50 | if (connector->ddc) { |
51 | edid = drm_get_edid(connector, adapter: connector->ddc); |
52 | if (edid) { |
53 | drm_connector_update_edid_property(connector, edid); |
54 | num = drm_add_edid_modes(connector, edid); |
55 | kfree(objp: edid); |
56 | } |
57 | |
58 | return num; |
59 | } |
60 | |
61 | num = drm_add_modes_noedid(connector, hdisplay: 1920, vdisplay: 1200); |
62 | |
63 | drm_set_preferred_mode(connector, hpref: 1024, vpref: 768); |
64 | |
65 | return num; |
66 | } |
67 | |
68 | static struct drm_encoder * |
69 | ls7a2000_connector_get_best_encoder(struct drm_connector *connector, |
70 | struct drm_atomic_state *state) |
71 | { |
72 | struct lsdc_output *output = connector_to_lsdc_output(connector); |
73 | |
74 | return &output->encoder; |
75 | } |
76 | |
77 | static const struct drm_connector_helper_funcs ls7a2000_connector_helpers = { |
78 | .atomic_best_encoder = ls7a2000_connector_get_best_encoder, |
79 | .get_modes = ls7a2000_connector_get_modes, |
80 | }; |
81 | |
82 | /* debugfs */ |
83 | |
84 | #define LSDC_HDMI_REG(i, reg) { \ |
85 | .name = __stringify_1(LSDC_HDMI##i##_##reg##_REG), \ |
86 | .offset = LSDC_HDMI##i##_##reg##_REG, \ |
87 | } |
88 | |
89 | static const struct lsdc_reg32 ls7a2000_hdmi0_encoder_regs[] = { |
90 | LSDC_HDMI_REG(0, ZONE), |
91 | LSDC_HDMI_REG(0, INTF_CTRL), |
92 | LSDC_HDMI_REG(0, PHY_CTRL), |
93 | LSDC_HDMI_REG(0, PHY_PLL), |
94 | LSDC_HDMI_REG(0, AVI_INFO_CRTL), |
95 | LSDC_HDMI_REG(0, PHY_CAL), |
96 | LSDC_HDMI_REG(0, AUDIO_PLL_LO), |
97 | LSDC_HDMI_REG(0, AUDIO_PLL_HI), |
98 | {NULL, 0}, /* MUST be {NULL, 0} terminated */ |
99 | }; |
100 | |
101 | static const struct lsdc_reg32 ls7a2000_hdmi1_encoder_regs[] = { |
102 | LSDC_HDMI_REG(1, ZONE), |
103 | LSDC_HDMI_REG(1, INTF_CTRL), |
104 | LSDC_HDMI_REG(1, PHY_CTRL), |
105 | LSDC_HDMI_REG(1, PHY_PLL), |
106 | LSDC_HDMI_REG(1, AVI_INFO_CRTL), |
107 | LSDC_HDMI_REG(1, PHY_CAL), |
108 | LSDC_HDMI_REG(1, AUDIO_PLL_LO), |
109 | LSDC_HDMI_REG(1, AUDIO_PLL_HI), |
110 | {NULL, 0}, /* MUST be {NULL, 0} terminated */ |
111 | }; |
112 | |
113 | static int ls7a2000_hdmi_encoder_regs_show(struct seq_file *m, void *data) |
114 | { |
115 | struct drm_info_node *node = (struct drm_info_node *)m->private; |
116 | struct drm_device *ddev = node->minor->dev; |
117 | struct lsdc_device *ldev = to_lsdc(ddev); |
118 | const struct lsdc_reg32 *preg; |
119 | |
120 | preg = (const struct lsdc_reg32 *)node->info_ent->data; |
121 | |
122 | while (preg->name) { |
123 | u32 offset = preg->offset; |
124 | |
125 | seq_printf(m, fmt: "%s (0x%04x): 0x%08x\n" , |
126 | preg->name, offset, lsdc_rreg32(ldev, offset)); |
127 | ++preg; |
128 | } |
129 | |
130 | return 0; |
131 | } |
132 | |
133 | static const struct drm_info_list ls7a2000_hdmi0_debugfs_files[] = { |
134 | { "regs" , ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi0_encoder_regs }, |
135 | }; |
136 | |
137 | static const struct drm_info_list ls7a2000_hdmi1_debugfs_files[] = { |
138 | { "regs" , ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi1_encoder_regs }, |
139 | }; |
140 | |
141 | static void ls7a2000_hdmi0_late_register(struct drm_connector *connector, |
142 | struct dentry *root) |
143 | { |
144 | struct drm_device *ddev = connector->dev; |
145 | struct drm_minor *minor = ddev->primary; |
146 | |
147 | drm_debugfs_create_files(files: ls7a2000_hdmi0_debugfs_files, |
148 | ARRAY_SIZE(ls7a2000_hdmi0_debugfs_files), |
149 | root, minor); |
150 | } |
151 | |
152 | static void ls7a2000_hdmi1_late_register(struct drm_connector *connector, |
153 | struct dentry *root) |
154 | { |
155 | struct drm_device *ddev = connector->dev; |
156 | struct drm_minor *minor = ddev->primary; |
157 | |
158 | drm_debugfs_create_files(files: ls7a2000_hdmi1_debugfs_files, |
159 | ARRAY_SIZE(ls7a2000_hdmi1_debugfs_files), |
160 | root, minor); |
161 | } |
162 | |
163 | /* monitor present detection */ |
164 | |
165 | static enum drm_connector_status |
166 | ls7a2000_hdmi0_vga_connector_detect(struct drm_connector *connector, bool force) |
167 | { |
168 | struct drm_device *ddev = connector->dev; |
169 | struct lsdc_device *ldev = to_lsdc(ddev); |
170 | u32 val; |
171 | |
172 | val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); |
173 | |
174 | if (val & HDMI0_HPD_FLAG) |
175 | return connector_status_connected; |
176 | |
177 | if (connector->ddc) { |
178 | if (drm_probe_ddc(adapter: connector->ddc)) |
179 | return connector_status_connected; |
180 | |
181 | return connector_status_disconnected; |
182 | } |
183 | |
184 | return connector_status_unknown; |
185 | } |
186 | |
187 | static enum drm_connector_status |
188 | ls7a2000_hdmi1_connector_detect(struct drm_connector *connector, bool force) |
189 | { |
190 | struct lsdc_device *ldev = to_lsdc(ddev: connector->dev); |
191 | u32 val; |
192 | |
193 | val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); |
194 | |
195 | if (val & HDMI1_HPD_FLAG) |
196 | return connector_status_connected; |
197 | |
198 | return connector_status_disconnected; |
199 | } |
200 | |
201 | static const struct drm_connector_funcs ls7a2000_hdmi_connector_funcs[2] = { |
202 | { |
203 | .detect = ls7a2000_hdmi0_vga_connector_detect, |
204 | .fill_modes = drm_helper_probe_single_connector_modes, |
205 | .destroy = drm_connector_cleanup, |
206 | .reset = drm_atomic_helper_connector_reset, |
207 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
208 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
209 | .debugfs_init = ls7a2000_hdmi0_late_register, |
210 | }, |
211 | { |
212 | .detect = ls7a2000_hdmi1_connector_detect, |
213 | .fill_modes = drm_helper_probe_single_connector_modes, |
214 | .destroy = drm_connector_cleanup, |
215 | .reset = drm_atomic_helper_connector_reset, |
216 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
217 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
218 | .debugfs_init = ls7a2000_hdmi1_late_register, |
219 | }, |
220 | }; |
221 | |
222 | /* Even though some board has only one hdmi on display pipe 1, |
223 | * We still need hook lsdc_encoder_funcs up on display pipe 0, |
224 | * This is because we need its reset() callback get called, to |
225 | * set the LSDC_HDMIx_CTRL_REG using software gpio emulated i2c. |
226 | * Otherwise, the firmware may set LSDC_HDMIx_CTRL_REG blindly. |
227 | */ |
228 | static void ls7a2000_hdmi0_encoder_reset(struct drm_encoder *encoder) |
229 | { |
230 | struct drm_device *ddev = encoder->dev; |
231 | struct lsdc_device *ldev = to_lsdc(ddev); |
232 | u32 val; |
233 | |
234 | val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; |
235 | lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, val); |
236 | |
237 | /* using software gpio emulated i2c */ |
238 | val = lsdc_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG); |
239 | val &= ~HW_I2C_EN; |
240 | lsdc_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, val); |
241 | |
242 | /* help the hdmi phy to get out of reset state */ |
243 | lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_RESET_N); |
244 | |
245 | mdelay(20); |
246 | |
247 | drm_dbg(ddev, "HDMI-0 Reset\n" ); |
248 | } |
249 | |
250 | static void ls7a2000_hdmi1_encoder_reset(struct drm_encoder *encoder) |
251 | { |
252 | struct drm_device *ddev = encoder->dev; |
253 | struct lsdc_device *ldev = to_lsdc(ddev); |
254 | u32 val; |
255 | |
256 | val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; |
257 | lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val); |
258 | |
259 | /* using software gpio emulated i2c */ |
260 | val = lsdc_rreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG); |
261 | val &= ~HW_I2C_EN; |
262 | lsdc_wreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG, val); |
263 | |
264 | /* help the hdmi phy to get out of reset state */ |
265 | lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_RESET_N); |
266 | |
267 | mdelay(20); |
268 | |
269 | drm_dbg(ddev, "HDMI-1 Reset\n" ); |
270 | } |
271 | |
272 | static const struct drm_encoder_funcs ls7a2000_encoder_funcs[2] = { |
273 | { |
274 | .reset = ls7a2000_hdmi0_encoder_reset, |
275 | .destroy = drm_encoder_cleanup, |
276 | }, |
277 | { |
278 | .reset = ls7a2000_hdmi1_encoder_reset, |
279 | .destroy = drm_encoder_cleanup, |
280 | }, |
281 | }; |
282 | |
283 | static int ls7a2000_hdmi_set_avi_infoframe(struct drm_encoder *encoder, |
284 | struct drm_display_mode *mode) |
285 | { |
286 | struct lsdc_output *output = encoder_to_lsdc_output(encoder); |
287 | struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); |
288 | unsigned int index = dispipe->index; |
289 | struct drm_device *ddev = encoder->dev; |
290 | struct lsdc_device *ldev = to_lsdc(ddev); |
291 | struct hdmi_avi_infoframe infoframe; |
292 | u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; |
293 | unsigned char *ptr = &buffer[HDMI_INFOFRAME_HEADER_SIZE]; |
294 | unsigned int content0, content1, content2, content3; |
295 | int err; |
296 | |
297 | err = drm_hdmi_avi_infoframe_from_display_mode(frame: &infoframe, |
298 | connector: &output->connector, |
299 | mode); |
300 | if (err < 0) { |
301 | drm_err(ddev, "failed to setup AVI infoframe: %d\n" , err); |
302 | return err; |
303 | } |
304 | |
305 | /* Fixed infoframe configuration not linked to the mode */ |
306 | infoframe.colorspace = HDMI_COLORSPACE_RGB; |
307 | infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; |
308 | infoframe.colorimetry = HDMI_COLORIMETRY_NONE; |
309 | |
310 | err = hdmi_avi_infoframe_pack(frame: &infoframe, buffer, size: sizeof(buffer)); |
311 | if (err < 0) { |
312 | drm_err(ddev, "failed to pack AVI infoframe: %d\n" , err); |
313 | return err; |
314 | } |
315 | |
316 | content0 = *(unsigned int *)ptr; |
317 | content1 = *(ptr + 4); |
318 | content2 = *(unsigned int *)(ptr + 5); |
319 | content3 = *(unsigned int *)(ptr + 9); |
320 | |
321 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT0, pipe: index, val: content0); |
322 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT1, pipe: index, val: content1); |
323 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT2, pipe: index, val: content2); |
324 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT3, pipe: index, val: content3); |
325 | |
326 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_INFO_CRTL_REG, pipe: index, |
327 | AVI_PKT_ENABLE | AVI_PKT_UPDATE); |
328 | |
329 | drm_dbg(ddev, "Update HDMI-%u avi infoframe\n" , index); |
330 | |
331 | return 0; |
332 | } |
333 | |
334 | static void ls7a2000_hdmi_atomic_disable(struct drm_encoder *encoder, |
335 | struct drm_atomic_state *state) |
336 | { |
337 | struct lsdc_output *output = encoder_to_lsdc_output(encoder); |
338 | struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); |
339 | unsigned int index = dispipe->index; |
340 | struct drm_device *ddev = encoder->dev; |
341 | struct lsdc_device *ldev = to_lsdc(ddev); |
342 | u32 val; |
343 | |
344 | /* Disable the hdmi phy */ |
345 | val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, pipe: index); |
346 | val &= ~HDMI_PHY_EN; |
347 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, pipe: index, val); |
348 | |
349 | /* Disable the hdmi interface */ |
350 | val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, pipe: index); |
351 | val &= ~HDMI_INTERFACE_EN; |
352 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, pipe: index, val); |
353 | |
354 | drm_dbg(ddev, "HDMI-%u disabled\n" , index); |
355 | } |
356 | |
357 | static void ls7a2000_hdmi_atomic_enable(struct drm_encoder *encoder, |
358 | struct drm_atomic_state *state) |
359 | { |
360 | struct drm_device *ddev = encoder->dev; |
361 | struct lsdc_device *ldev = to_lsdc(ddev); |
362 | struct lsdc_output *output = encoder_to_lsdc_output(encoder); |
363 | struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); |
364 | unsigned int index = dispipe->index; |
365 | u32 val; |
366 | |
367 | /* datasheet say it should larger than 48 */ |
368 | val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT; |
369 | |
370 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_ZONE_REG, pipe: index, val); |
371 | |
372 | val = HDMI_PHY_TERM_STATUS | |
373 | HDMI_PHY_TERM_DET_EN | |
374 | HDMI_PHY_TERM_H_EN | |
375 | HDMI_PHY_TERM_L_EN | |
376 | HDMI_PHY_RESET_N | |
377 | HDMI_PHY_EN; |
378 | |
379 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, pipe: index, val); |
380 | |
381 | udelay(2); |
382 | |
383 | val = HDMI_CTL_PERIOD_MODE | |
384 | HDMI_AUDIO_EN | |
385 | HDMI_PACKET_EN | |
386 | HDMI_INTERFACE_EN | |
387 | (8 << HDMI_VIDEO_PREAMBLE_SHIFT); |
388 | |
389 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, pipe: index, val); |
390 | |
391 | drm_dbg(ddev, "HDMI-%u enabled\n" , index); |
392 | } |
393 | |
394 | /* |
395 | * Fout = M * Fin |
396 | * |
397 | * M = (4 * LF) / (IDF * ODF) |
398 | * |
399 | * IDF: Input Division Factor |
400 | * ODF: Output Division Factor |
401 | * LF: Loop Factor |
402 | * M: Required Mult |
403 | * |
404 | * +--------------------------------------------------------+ |
405 | * | Fin (kHZ) | M | IDF | LF | ODF | Fout(Mhz) | |
406 | * |-------------------+----+-----+----+-----+--------------| |
407 | * | 170000 ~ 340000 | 10 | 16 | 40 | 1 | 1700 ~ 3400 | |
408 | * | 85000 ~ 170000 | 10 | 8 | 40 | 2 | 850 ~ 1700 | |
409 | * | 42500 ~ 85000 | 10 | 4 | 40 | 4 | 425 ~ 850 | |
410 | * | 21250 ~ 42500 | 10 | 2 | 40 | 8 | 212.5 ~ 425 | |
411 | * | 20000 ~ 21250 | 10 | 1 | 40 | 16 | 200 ~ 212.5 | |
412 | * +--------------------------------------------------------+ |
413 | */ |
414 | static void ls7a2000_hdmi_phy_pll_config(struct lsdc_device *ldev, |
415 | int fin, |
416 | unsigned int index) |
417 | { |
418 | struct drm_device *ddev = &ldev->base; |
419 | int count = 0; |
420 | u32 val; |
421 | |
422 | /* Firstly, disable phy pll */ |
423 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe: index, val: 0x0); |
424 | |
425 | /* |
426 | * Most of time, loongson HDMI require M = 10 |
427 | * for example, 10 = (4 * 40) / (8 * 2) |
428 | * here, write "1" to the ODF will get "2" |
429 | */ |
430 | |
431 | if (fin >= 170000) |
432 | val = (16 << HDMI_PLL_IDF_SHIFT) | |
433 | (40 << HDMI_PLL_LF_SHIFT) | |
434 | (0 << HDMI_PLL_ODF_SHIFT); |
435 | else if (fin >= 85000) |
436 | val = (8 << HDMI_PLL_IDF_SHIFT) | |
437 | (40 << HDMI_PLL_LF_SHIFT) | |
438 | (1 << HDMI_PLL_ODF_SHIFT); |
439 | else if (fin >= 42500) |
440 | val = (4 << HDMI_PLL_IDF_SHIFT) | |
441 | (40 << HDMI_PLL_LF_SHIFT) | |
442 | (2 << HDMI_PLL_ODF_SHIFT); |
443 | else if (fin >= 21250) |
444 | val = (2 << HDMI_PLL_IDF_SHIFT) | |
445 | (40 << HDMI_PLL_LF_SHIFT) | |
446 | (3 << HDMI_PLL_ODF_SHIFT); |
447 | else |
448 | val = (1 << HDMI_PLL_IDF_SHIFT) | |
449 | (40 << HDMI_PLL_LF_SHIFT) | |
450 | (4 << HDMI_PLL_ODF_SHIFT); |
451 | |
452 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe: index, val); |
453 | |
454 | val |= HDMI_PLL_ENABLE; |
455 | |
456 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe: index, val); |
457 | |
458 | udelay(2); |
459 | |
460 | drm_dbg(ddev, "Fin of HDMI-%u: %d kHz\n" , index, fin); |
461 | |
462 | /* Wait hdmi phy pll lock */ |
463 | do { |
464 | val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe: index); |
465 | |
466 | if (val & HDMI_PLL_LOCKED) { |
467 | drm_dbg(ddev, "Setting HDMI-%u PLL take %d cycles\n" , |
468 | index, count); |
469 | break; |
470 | } |
471 | ++count; |
472 | } while (count < 1000); |
473 | |
474 | lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CAL_REG, pipe: index, val: 0x0f000ff0); |
475 | |
476 | if (count >= 1000) |
477 | drm_err(ddev, "Setting HDMI-%u PLL failed\n" , index); |
478 | } |
479 | |
480 | static void ls7a2000_hdmi_atomic_mode_set(struct drm_encoder *encoder, |
481 | struct drm_crtc_state *crtc_state, |
482 | struct drm_connector_state *conn_state) |
483 | { |
484 | struct lsdc_output *output = encoder_to_lsdc_output(encoder); |
485 | struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); |
486 | unsigned int index = dispipe->index; |
487 | struct drm_device *ddev = encoder->dev; |
488 | struct lsdc_device *ldev = to_lsdc(ddev); |
489 | struct drm_display_mode *mode = &crtc_state->mode; |
490 | |
491 | ls7a2000_hdmi_phy_pll_config(ldev, fin: mode->clock, index); |
492 | |
493 | ls7a2000_hdmi_set_avi_infoframe(encoder, mode); |
494 | |
495 | drm_dbg(ddev, "%s modeset finished\n" , encoder->name); |
496 | } |
497 | |
498 | static const struct drm_encoder_helper_funcs ls7a2000_encoder_helper_funcs = { |
499 | .atomic_disable = ls7a2000_hdmi_atomic_disable, |
500 | .atomic_enable = ls7a2000_hdmi_atomic_enable, |
501 | .atomic_mode_set = ls7a2000_hdmi_atomic_mode_set, |
502 | }; |
503 | |
504 | /* |
505 | * For LS7A2000: |
506 | * |
507 | * 1) Most of board export one vga + hdmi output interface. |
508 | * 2) Yet, Some boards export double hdmi output interface. |
509 | * 3) Still have boards export three output(2 hdmi + 1 vga). |
510 | * |
511 | * So let's hook hdmi helper funcs to all display pipe, don't miss. |
512 | * writing hdmi register do no harms. |
513 | */ |
514 | int ls7a2000_output_init(struct drm_device *ddev, |
515 | struct lsdc_display_pipe *dispipe, |
516 | struct i2c_adapter *ddc, |
517 | unsigned int pipe) |
518 | { |
519 | struct lsdc_output *output = &dispipe->output; |
520 | struct drm_encoder *encoder = &output->encoder; |
521 | struct drm_connector *connector = &output->connector; |
522 | int ret; |
523 | |
524 | ret = drm_encoder_init(dev: ddev, encoder, funcs: &ls7a2000_encoder_funcs[pipe], |
525 | DRM_MODE_ENCODER_TMDS, name: "encoder-%u" , pipe); |
526 | if (ret) |
527 | return ret; |
528 | |
529 | encoder->possible_crtcs = BIT(pipe); |
530 | |
531 | drm_encoder_helper_add(encoder, funcs: &ls7a2000_encoder_helper_funcs); |
532 | |
533 | ret = drm_connector_init_with_ddc(dev: ddev, connector, |
534 | funcs: &ls7a2000_hdmi_connector_funcs[pipe], |
535 | DRM_MODE_CONNECTOR_HDMIA, ddc); |
536 | if (ret) |
537 | return ret; |
538 | |
539 | drm_info(ddev, "display pipe-%u has HDMI %s\n" , pipe, pipe ? "" : "and/or VGA" ); |
540 | |
541 | drm_connector_helper_add(connector, funcs: &ls7a2000_connector_helpers); |
542 | |
543 | drm_connector_attach_encoder(connector, encoder); |
544 | |
545 | connector->polled = DRM_CONNECTOR_POLL_CONNECT | |
546 | DRM_CONNECTOR_POLL_DISCONNECT; |
547 | |
548 | connector->interlace_allowed = 0; |
549 | connector->doublescan_allowed = 0; |
550 | |
551 | return 0; |
552 | } |
553 | |