1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd |
4 | * Zheng Yang <zhengyang@rock-chips.com> |
5 | * Yakir Yang <ykk@rock-chips.com> |
6 | */ |
7 | |
8 | #include <linux/irq.h> |
9 | #include <linux/clk.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/err.h> |
12 | #include <linux/hdmi.h> |
13 | #include <linux/mod_devicetable.h> |
14 | #include <linux/module.h> |
15 | #include <linux/mutex.h> |
16 | #include <linux/platform_device.h> |
17 | |
18 | #include <drm/drm_atomic.h> |
19 | #include <drm/drm_atomic_helper.h> |
20 | #include <drm/drm_edid.h> |
21 | #include <drm/drm_of.h> |
22 | #include <drm/drm_probe_helper.h> |
23 | #include <drm/drm_simple_kms_helper.h> |
24 | |
25 | #include "rockchip_drm_drv.h" |
26 | |
27 | #include "inno_hdmi.h" |
28 | |
29 | #define INNO_HDMI_MIN_TMDS_CLOCK 25000000U |
30 | |
31 | struct inno_hdmi_phy_config { |
32 | unsigned long pixelclock; |
33 | u8 pre_emphasis; |
34 | u8 voltage_level_control; |
35 | }; |
36 | |
37 | struct inno_hdmi_variant { |
38 | struct inno_hdmi_phy_config *phy_configs; |
39 | struct inno_hdmi_phy_config *default_phy_config; |
40 | }; |
41 | |
42 | struct inno_hdmi_i2c { |
43 | struct i2c_adapter adap; |
44 | |
45 | u8 ddc_addr; |
46 | u8 segment_addr; |
47 | |
48 | struct mutex lock; |
49 | struct completion cmp; |
50 | }; |
51 | |
52 | struct inno_hdmi { |
53 | struct device *dev; |
54 | |
55 | struct clk *pclk; |
56 | struct clk *refclk; |
57 | void __iomem *regs; |
58 | |
59 | struct drm_connector connector; |
60 | struct rockchip_encoder encoder; |
61 | |
62 | struct inno_hdmi_i2c *i2c; |
63 | struct i2c_adapter *ddc; |
64 | |
65 | const struct inno_hdmi_variant *variant; |
66 | }; |
67 | |
68 | struct inno_hdmi_connector_state { |
69 | struct drm_connector_state base; |
70 | unsigned int enc_out_format; |
71 | unsigned int colorimetry; |
72 | bool rgb_limited_range; |
73 | }; |
74 | |
75 | static struct inno_hdmi *encoder_to_inno_hdmi(struct drm_encoder *encoder) |
76 | { |
77 | struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); |
78 | |
79 | return container_of(rkencoder, struct inno_hdmi, encoder); |
80 | } |
81 | |
82 | static struct inno_hdmi *connector_to_inno_hdmi(struct drm_connector *connector) |
83 | { |
84 | return container_of(connector, struct inno_hdmi, connector); |
85 | } |
86 | |
87 | #define to_inno_hdmi_conn_state(conn_state) \ |
88 | container_of_const(conn_state, struct inno_hdmi_connector_state, base) |
89 | |
90 | enum { |
91 | CSC_RGB_0_255_TO_ITU601_16_235_8BIT, |
92 | CSC_RGB_0_255_TO_ITU709_16_235_8BIT, |
93 | CSC_RGB_0_255_TO_RGB_16_235_8BIT, |
94 | }; |
95 | |
96 | static const char coeff_csc[][24] = { |
97 | /* |
98 | * RGB2YUV:601 SD mode: |
99 | * Cb = -0.291G - 0.148R + 0.439B + 128 |
100 | * Y = 0.504G + 0.257R + 0.098B + 16 |
101 | * Cr = -0.368G + 0.439R - 0.071B + 128 |
102 | */ |
103 | { |
104 | 0x11, 0x5f, 0x01, 0x82, 0x10, 0x23, 0x00, 0x80, |
105 | 0x02, 0x1c, 0x00, 0xa1, 0x00, 0x36, 0x00, 0x1e, |
106 | 0x11, 0x29, 0x10, 0x59, 0x01, 0x82, 0x00, 0x80 |
107 | }, |
108 | /* |
109 | * RGB2YUV:709 HD mode: |
110 | * Cb = - 0.338G - 0.101R + 0.439B + 128 |
111 | * Y = 0.614G + 0.183R + 0.062B + 16 |
112 | * Cr = - 0.399G + 0.439R - 0.040B + 128 |
113 | */ |
114 | { |
115 | 0x11, 0x98, 0x01, 0xc1, 0x10, 0x28, 0x00, 0x80, |
116 | 0x02, 0x74, 0x00, 0xbb, 0x00, 0x3f, 0x00, 0x10, |
117 | 0x11, 0x5a, 0x10, 0x67, 0x01, 0xc1, 0x00, 0x80 |
118 | }, |
119 | /* |
120 | * RGB[0:255]2RGB[16:235]: |
121 | * R' = R x (235-16)/255 + 16; |
122 | * G' = G x (235-16)/255 + 16; |
123 | * B' = B x (235-16)/255 + 16; |
124 | */ |
125 | { |
126 | 0x00, 0x00, 0x03, 0x6F, 0x00, 0x00, 0x00, 0x10, |
127 | 0x03, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, |
128 | 0x00, 0x00, 0x00, 0x00, 0x03, 0x6F, 0x00, 0x10 |
129 | }, |
130 | }; |
131 | |
132 | static struct inno_hdmi_phy_config rk3036_hdmi_phy_configs[] = { |
133 | { 74250000, 0x3f, 0xbb }, |
134 | { 165000000, 0x6f, 0xbb }, |
135 | { ~0UL, 0x00, 0x00 } |
136 | }; |
137 | |
138 | static struct inno_hdmi_phy_config rk3128_hdmi_phy_configs[] = { |
139 | { 74250000, 0x3f, 0xaa }, |
140 | { 165000000, 0x5f, 0xaa }, |
141 | { ~0UL, 0x00, 0x00 } |
142 | }; |
143 | |
144 | static int inno_hdmi_find_phy_config(struct inno_hdmi *hdmi, |
145 | unsigned long pixelclk) |
146 | { |
147 | const struct inno_hdmi_phy_config *phy_configs = |
148 | hdmi->variant->phy_configs; |
149 | int i; |
150 | |
151 | for (i = 0; phy_configs[i].pixelclock != ~0UL; i++) { |
152 | if (pixelclk <= phy_configs[i].pixelclock) |
153 | return i; |
154 | } |
155 | |
156 | DRM_DEV_DEBUG(hdmi->dev, "No phy configuration for pixelclock %lu\n" , |
157 | pixelclk); |
158 | |
159 | return -EINVAL; |
160 | } |
161 | |
162 | static inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset) |
163 | { |
164 | return readl_relaxed(hdmi->regs + (offset) * 0x04); |
165 | } |
166 | |
167 | static inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val) |
168 | { |
169 | writel_relaxed(val, hdmi->regs + (offset) * 0x04); |
170 | } |
171 | |
172 | static inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, |
173 | u32 msk, u32 val) |
174 | { |
175 | u8 temp = hdmi_readb(hdmi, offset) & ~msk; |
176 | |
177 | temp |= val & msk; |
178 | hdmi_writeb(hdmi, offset, val: temp); |
179 | } |
180 | |
181 | static void inno_hdmi_i2c_init(struct inno_hdmi *hdmi, unsigned long long rate) |
182 | { |
183 | unsigned long long ddc_bus_freq = rate >> 2; |
184 | |
185 | do_div(ddc_bus_freq, HDMI_SCL_RATE); |
186 | |
187 | hdmi_writeb(hdmi, DDC_BUS_FREQ_L, val: ddc_bus_freq & 0xFF); |
188 | hdmi_writeb(hdmi, DDC_BUS_FREQ_H, val: (ddc_bus_freq >> 8) & 0xFF); |
189 | |
190 | /* Clear the EDID interrupt flag and mute the interrupt */ |
191 | hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, val: 0); |
192 | hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); |
193 | } |
194 | |
195 | static void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable) |
196 | { |
197 | if (enable) |
198 | hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON); |
199 | else |
200 | hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF); |
201 | } |
202 | |
203 | static void inno_hdmi_standby(struct inno_hdmi *hdmi) |
204 | { |
205 | inno_hdmi_sys_power(hdmi, enable: false); |
206 | |
207 | hdmi_writeb(hdmi, HDMI_PHY_DRIVER, val: 0x00); |
208 | hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, val: 0x00); |
209 | hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, val: 0x00); |
210 | hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, val: 0x15); |
211 | }; |
212 | |
213 | static void inno_hdmi_power_up(struct inno_hdmi *hdmi, |
214 | unsigned long mpixelclock) |
215 | { |
216 | struct inno_hdmi_phy_config *phy_config; |
217 | int ret = inno_hdmi_find_phy_config(hdmi, pixelclk: mpixelclock); |
218 | |
219 | if (ret < 0) { |
220 | phy_config = hdmi->variant->default_phy_config; |
221 | DRM_DEV_ERROR(hdmi->dev, |
222 | "Using default phy configuration for TMDS rate %lu" , |
223 | mpixelclock); |
224 | } else { |
225 | phy_config = &hdmi->variant->phy_configs[ret]; |
226 | } |
227 | |
228 | inno_hdmi_sys_power(hdmi, enable: false); |
229 | |
230 | hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, val: phy_config->pre_emphasis); |
231 | hdmi_writeb(hdmi, HDMI_PHY_DRIVER, val: phy_config->voltage_level_control); |
232 | hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, val: 0x15); |
233 | hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, val: 0x14); |
234 | hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, val: 0x10); |
235 | hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, val: 0x0f); |
236 | hdmi_writeb(hdmi, HDMI_PHY_SYNC, val: 0x00); |
237 | hdmi_writeb(hdmi, HDMI_PHY_SYNC, val: 0x01); |
238 | |
239 | inno_hdmi_sys_power(hdmi, enable: true); |
240 | }; |
241 | |
242 | static void inno_hdmi_reset(struct inno_hdmi *hdmi) |
243 | { |
244 | u32 val; |
245 | u32 msk; |
246 | |
247 | hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_DIGITAL, v_NOT_RST_DIGITAL); |
248 | udelay(100); |
249 | |
250 | hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_ANALOG, v_NOT_RST_ANALOG); |
251 | udelay(100); |
252 | |
253 | msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL; |
254 | val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH; |
255 | hdmi_modb(hdmi, HDMI_SYS_CTRL, msk, val); |
256 | |
257 | inno_hdmi_standby(hdmi); |
258 | } |
259 | |
260 | static void inno_hdmi_disable_frame(struct inno_hdmi *hdmi, |
261 | enum hdmi_infoframe_type type) |
262 | { |
263 | struct drm_connector *connector = &hdmi->connector; |
264 | |
265 | if (type != HDMI_INFOFRAME_TYPE_AVI) { |
266 | drm_err(connector->dev, |
267 | "Unsupported infoframe type: %u\n" , type); |
268 | return; |
269 | } |
270 | |
271 | hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, val: INFOFRAME_AVI); |
272 | } |
273 | |
274 | static int inno_hdmi_upload_frame(struct inno_hdmi *hdmi, |
275 | union hdmi_infoframe *frame, enum hdmi_infoframe_type type) |
276 | { |
277 | struct drm_connector *connector = &hdmi->connector; |
278 | u8 packed_frame[HDMI_MAXIMUM_INFO_FRAME_SIZE]; |
279 | ssize_t rc, i; |
280 | |
281 | if (type != HDMI_INFOFRAME_TYPE_AVI) { |
282 | drm_err(connector->dev, |
283 | "Unsupported infoframe type: %u\n" , type); |
284 | return 0; |
285 | } |
286 | |
287 | inno_hdmi_disable_frame(hdmi, type); |
288 | |
289 | rc = hdmi_infoframe_pack(frame, buffer: packed_frame, |
290 | size: sizeof(packed_frame)); |
291 | if (rc < 0) |
292 | return rc; |
293 | |
294 | for (i = 0; i < rc; i++) |
295 | hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i, |
296 | val: packed_frame[i]); |
297 | |
298 | return 0; |
299 | } |
300 | |
301 | static int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi, |
302 | struct drm_display_mode *mode) |
303 | { |
304 | struct drm_connector *connector = &hdmi->connector; |
305 | struct drm_connector_state *conn_state = connector->state; |
306 | struct inno_hdmi_connector_state *inno_conn_state = |
307 | to_inno_hdmi_conn_state(conn_state); |
308 | union hdmi_infoframe frame; |
309 | int rc; |
310 | |
311 | rc = drm_hdmi_avi_infoframe_from_display_mode(frame: &frame.avi, |
312 | connector: &hdmi->connector, |
313 | mode); |
314 | if (rc) { |
315 | inno_hdmi_disable_frame(hdmi, type: HDMI_INFOFRAME_TYPE_AVI); |
316 | return rc; |
317 | } |
318 | |
319 | if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_YUV444) |
320 | frame.avi.colorspace = HDMI_COLORSPACE_YUV444; |
321 | else if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_YUV422) |
322 | frame.avi.colorspace = HDMI_COLORSPACE_YUV422; |
323 | else |
324 | frame.avi.colorspace = HDMI_COLORSPACE_RGB; |
325 | |
326 | if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_RGB) { |
327 | drm_hdmi_avi_infoframe_quant_range(frame: &frame.avi, |
328 | connector, mode, |
329 | rgb_quant_range: inno_conn_state->rgb_limited_range ? |
330 | HDMI_QUANTIZATION_RANGE_LIMITED : |
331 | HDMI_QUANTIZATION_RANGE_FULL); |
332 | } else { |
333 | frame.avi.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; |
334 | frame.avi.ycc_quantization_range = |
335 | HDMI_YCC_QUANTIZATION_RANGE_LIMITED; |
336 | } |
337 | |
338 | return inno_hdmi_upload_frame(hdmi, frame: &frame, type: HDMI_INFOFRAME_TYPE_AVI); |
339 | } |
340 | |
341 | static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi) |
342 | { |
343 | struct drm_connector *connector = &hdmi->connector; |
344 | struct drm_connector_state *conn_state = connector->state; |
345 | struct inno_hdmi_connector_state *inno_conn_state = |
346 | to_inno_hdmi_conn_state(conn_state); |
347 | int c0_c2_change = 0; |
348 | int csc_enable = 0; |
349 | int csc_mode = 0; |
350 | int auto_csc = 0; |
351 | int value; |
352 | int i; |
353 | |
354 | /* Input video mode is SDR RGB24bit, data enable signal from external */ |
355 | hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL | |
356 | v_VIDEO_INPUT_FORMAT(VIDEO_INPUT_SDR_RGB444)); |
357 | |
358 | /* Input color hardcode to RGB, and output color hardcode to RGB888 */ |
359 | value = v_VIDEO_INPUT_BITS(VIDEO_INPUT_8BITS) | |
360 | v_VIDEO_OUTPUT_COLOR(0) | |
361 | v_VIDEO_INPUT_CSP(0); |
362 | hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, val: value); |
363 | |
364 | if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_RGB) { |
365 | if (inno_conn_state->rgb_limited_range) { |
366 | csc_mode = CSC_RGB_0_255_TO_RGB_16_235_8BIT; |
367 | auto_csc = AUTO_CSC_DISABLE; |
368 | c0_c2_change = C0_C2_CHANGE_DISABLE; |
369 | csc_enable = v_CSC_ENABLE; |
370 | |
371 | } else { |
372 | value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED(1); |
373 | hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, val: value); |
374 | |
375 | hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, |
376 | m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP, |
377 | v_VIDEO_AUTO_CSC(AUTO_CSC_DISABLE) | |
378 | v_VIDEO_C0_C2_SWAP(C0_C2_CHANGE_DISABLE)); |
379 | return 0; |
380 | } |
381 | } else { |
382 | if (inno_conn_state->colorimetry == HDMI_COLORIMETRY_ITU_601) { |
383 | if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_YUV444) { |
384 | csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT; |
385 | auto_csc = AUTO_CSC_DISABLE; |
386 | c0_c2_change = C0_C2_CHANGE_DISABLE; |
387 | csc_enable = v_CSC_ENABLE; |
388 | } |
389 | } else { |
390 | if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_YUV444) { |
391 | csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT; |
392 | auto_csc = AUTO_CSC_DISABLE; |
393 | c0_c2_change = C0_C2_CHANGE_DISABLE; |
394 | csc_enable = v_CSC_ENABLE; |
395 | } |
396 | } |
397 | } |
398 | |
399 | for (i = 0; i < 24; i++) |
400 | hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i, |
401 | val: coeff_csc[csc_mode][i]); |
402 | |
403 | value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1); |
404 | hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, val: value); |
405 | hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, m_VIDEO_AUTO_CSC | |
406 | m_VIDEO_C0_C2_SWAP, v_VIDEO_AUTO_CSC(auto_csc) | |
407 | v_VIDEO_C0_C2_SWAP(c0_c2_change)); |
408 | |
409 | return 0; |
410 | } |
411 | |
412 | static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi, |
413 | struct drm_display_mode *mode) |
414 | { |
415 | int value; |
416 | |
417 | /* Set detail external video timing polarity and interlace mode */ |
418 | value = v_EXTERANL_VIDEO(1); |
419 | value |= mode->flags & DRM_MODE_FLAG_PHSYNC ? |
420 | v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0); |
421 | value |= mode->flags & DRM_MODE_FLAG_PVSYNC ? |
422 | v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0); |
423 | value |= mode->flags & DRM_MODE_FLAG_INTERLACE ? |
424 | v_INETLACE(1) : v_INETLACE(0); |
425 | hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, val: value); |
426 | |
427 | /* Set detail external video timing */ |
428 | value = mode->htotal; |
429 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, val: value & 0xFF); |
430 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, val: (value >> 8) & 0xFF); |
431 | |
432 | value = mode->htotal - mode->hdisplay; |
433 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, val: value & 0xFF); |
434 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, val: (value >> 8) & 0xFF); |
435 | |
436 | value = mode->htotal - mode->hsync_start; |
437 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, val: value & 0xFF); |
438 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, val: (value >> 8) & 0xFF); |
439 | |
440 | value = mode->hsync_end - mode->hsync_start; |
441 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, val: value & 0xFF); |
442 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, val: (value >> 8) & 0xFF); |
443 | |
444 | value = mode->vtotal; |
445 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, val: value & 0xFF); |
446 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, val: (value >> 8) & 0xFF); |
447 | |
448 | value = mode->vtotal - mode->vdisplay; |
449 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, val: value & 0xFF); |
450 | |
451 | value = mode->vtotal - mode->vsync_start; |
452 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, val: value & 0xFF); |
453 | |
454 | value = mode->vsync_end - mode->vsync_start; |
455 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, val: value & 0xFF); |
456 | |
457 | hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, val: 0x1e); |
458 | hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, val: 0x2c); |
459 | hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, val: 0x01); |
460 | |
461 | return 0; |
462 | } |
463 | |
464 | static int inno_hdmi_setup(struct inno_hdmi *hdmi, |
465 | struct drm_display_mode *mode) |
466 | { |
467 | struct drm_display_info *display = &hdmi->connector.display_info; |
468 | unsigned long mpixelclock = mode->clock * 1000; |
469 | |
470 | /* Mute video and audio output */ |
471 | hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, |
472 | v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1)); |
473 | |
474 | /* Set HDMI Mode */ |
475 | hdmi_writeb(hdmi, HDMI_HDCP_CTRL, |
476 | v_HDMI_DVI(display->is_hdmi)); |
477 | |
478 | inno_hdmi_config_video_timing(hdmi, mode); |
479 | |
480 | inno_hdmi_config_video_csc(hdmi); |
481 | |
482 | if (display->is_hdmi) |
483 | inno_hdmi_config_video_avi(hdmi, mode); |
484 | |
485 | /* |
486 | * When IP controller have configured to an accurate video |
487 | * timing, then the TMDS clock source would be switched to |
488 | * DCLK_LCDC, so we need to init the TMDS rate to mode pixel |
489 | * clock rate, and reconfigure the DDC clock. |
490 | */ |
491 | inno_hdmi_i2c_init(hdmi, rate: mpixelclock); |
492 | |
493 | /* Unmute video and audio output */ |
494 | hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, |
495 | v_AUDIO_MUTE(0) | v_VIDEO_MUTE(0)); |
496 | |
497 | inno_hdmi_power_up(hdmi, mpixelclock); |
498 | |
499 | return 0; |
500 | } |
501 | |
502 | static enum drm_mode_status inno_hdmi_display_mode_valid(struct inno_hdmi *hdmi, |
503 | struct drm_display_mode *mode) |
504 | { |
505 | unsigned long mpixelclk, max_tolerance; |
506 | long rounded_refclk; |
507 | |
508 | /* No support for double-clock modes */ |
509 | if (mode->flags & DRM_MODE_FLAG_DBLCLK) |
510 | return MODE_BAD; |
511 | |
512 | mpixelclk = mode->clock * 1000; |
513 | |
514 | if (mpixelclk < INNO_HDMI_MIN_TMDS_CLOCK) |
515 | return MODE_CLOCK_LOW; |
516 | |
517 | if (inno_hdmi_find_phy_config(hdmi, pixelclk: mpixelclk) < 0) |
518 | return MODE_CLOCK_HIGH; |
519 | |
520 | if (hdmi->refclk) { |
521 | rounded_refclk = clk_round_rate(clk: hdmi->refclk, rate: mpixelclk); |
522 | if (rounded_refclk < 0) |
523 | return MODE_BAD; |
524 | |
525 | /* Vesa DMT standard mentions +/- 0.5% max tolerance */ |
526 | max_tolerance = mpixelclk / 200; |
527 | if (abs_diff((unsigned long)rounded_refclk, mpixelclk) > max_tolerance) |
528 | return MODE_NOCLOCK; |
529 | } |
530 | |
531 | return MODE_OK; |
532 | } |
533 | |
534 | static void inno_hdmi_encoder_enable(struct drm_encoder *encoder, |
535 | struct drm_atomic_state *state) |
536 | { |
537 | struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); |
538 | struct drm_connector_state *conn_state; |
539 | struct drm_crtc_state *crtc_state; |
540 | |
541 | conn_state = drm_atomic_get_new_connector_state(state, connector: &hdmi->connector); |
542 | if (WARN_ON(!conn_state)) |
543 | return; |
544 | |
545 | crtc_state = drm_atomic_get_new_crtc_state(state, crtc: conn_state->crtc); |
546 | if (WARN_ON(!crtc_state)) |
547 | return; |
548 | |
549 | inno_hdmi_setup(hdmi, mode: &crtc_state->adjusted_mode); |
550 | } |
551 | |
552 | static void inno_hdmi_encoder_disable(struct drm_encoder *encoder, |
553 | struct drm_atomic_state *state) |
554 | { |
555 | struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); |
556 | |
557 | inno_hdmi_standby(hdmi); |
558 | } |
559 | |
560 | static int |
561 | inno_hdmi_encoder_atomic_check(struct drm_encoder *encoder, |
562 | struct drm_crtc_state *crtc_state, |
563 | struct drm_connector_state *conn_state) |
564 | { |
565 | struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); |
566 | struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); |
567 | struct drm_display_mode *mode = &crtc_state->adjusted_mode; |
568 | u8 vic = drm_match_cea_mode(to_match: mode); |
569 | struct inno_hdmi_connector_state *inno_conn_state = |
570 | to_inno_hdmi_conn_state(conn_state); |
571 | |
572 | s->output_mode = ROCKCHIP_OUT_MODE_P888; |
573 | s->output_type = DRM_MODE_CONNECTOR_HDMIA; |
574 | |
575 | if (vic == 6 || vic == 7 || |
576 | vic == 21 || vic == 22 || |
577 | vic == 2 || vic == 3 || |
578 | vic == 17 || vic == 18) |
579 | inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_601; |
580 | else |
581 | inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_709; |
582 | |
583 | inno_conn_state->enc_out_format = HDMI_COLORSPACE_RGB; |
584 | inno_conn_state->rgb_limited_range = |
585 | drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED; |
586 | |
587 | return inno_hdmi_display_mode_valid(hdmi, |
588 | mode: &crtc_state->adjusted_mode) == MODE_OK ? 0 : -EINVAL; |
589 | } |
590 | |
591 | static struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = { |
592 | .atomic_check = inno_hdmi_encoder_atomic_check, |
593 | .atomic_enable = inno_hdmi_encoder_enable, |
594 | .atomic_disable = inno_hdmi_encoder_disable, |
595 | }; |
596 | |
597 | static enum drm_connector_status |
598 | inno_hdmi_connector_detect(struct drm_connector *connector, bool force) |
599 | { |
600 | struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); |
601 | |
602 | return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ? |
603 | connector_status_connected : connector_status_disconnected; |
604 | } |
605 | |
606 | static int inno_hdmi_connector_get_modes(struct drm_connector *connector) |
607 | { |
608 | struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); |
609 | struct edid *edid; |
610 | int ret = 0; |
611 | |
612 | if (!hdmi->ddc) |
613 | return 0; |
614 | |
615 | edid = drm_get_edid(connector, adapter: hdmi->ddc); |
616 | if (edid) { |
617 | drm_connector_update_edid_property(connector, edid); |
618 | ret = drm_add_edid_modes(connector, edid); |
619 | kfree(objp: edid); |
620 | } |
621 | |
622 | return ret; |
623 | } |
624 | |
625 | static enum drm_mode_status |
626 | inno_hdmi_connector_mode_valid(struct drm_connector *connector, |
627 | struct drm_display_mode *mode) |
628 | { |
629 | struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); |
630 | |
631 | return inno_hdmi_display_mode_valid(hdmi, mode); |
632 | } |
633 | |
634 | static void inno_hdmi_connector_destroy(struct drm_connector *connector) |
635 | { |
636 | drm_connector_unregister(connector); |
637 | drm_connector_cleanup(connector); |
638 | } |
639 | |
640 | static void |
641 | inno_hdmi_connector_destroy_state(struct drm_connector *connector, |
642 | struct drm_connector_state *state) |
643 | { |
644 | struct inno_hdmi_connector_state *inno_conn_state = |
645 | to_inno_hdmi_conn_state(state); |
646 | |
647 | __drm_atomic_helper_connector_destroy_state(state: &inno_conn_state->base); |
648 | kfree(objp: inno_conn_state); |
649 | } |
650 | |
651 | static void inno_hdmi_connector_reset(struct drm_connector *connector) |
652 | { |
653 | struct inno_hdmi_connector_state *inno_conn_state; |
654 | |
655 | if (connector->state) { |
656 | inno_hdmi_connector_destroy_state(connector, state: connector->state); |
657 | connector->state = NULL; |
658 | } |
659 | |
660 | inno_conn_state = kzalloc(size: sizeof(*inno_conn_state), GFP_KERNEL); |
661 | if (!inno_conn_state) |
662 | return; |
663 | |
664 | __drm_atomic_helper_connector_reset(connector, conn_state: &inno_conn_state->base); |
665 | |
666 | inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_709; |
667 | inno_conn_state->enc_out_format = HDMI_COLORSPACE_RGB; |
668 | inno_conn_state->rgb_limited_range = false; |
669 | } |
670 | |
671 | static struct drm_connector_state * |
672 | inno_hdmi_connector_duplicate_state(struct drm_connector *connector) |
673 | { |
674 | struct inno_hdmi_connector_state *inno_conn_state; |
675 | |
676 | if (WARN_ON(!connector->state)) |
677 | return NULL; |
678 | |
679 | inno_conn_state = kmemdup(to_inno_hdmi_conn_state(connector->state), |
680 | size: sizeof(*inno_conn_state), GFP_KERNEL); |
681 | |
682 | if (!inno_conn_state) |
683 | return NULL; |
684 | |
685 | __drm_atomic_helper_connector_duplicate_state(connector, |
686 | state: &inno_conn_state->base); |
687 | |
688 | return &inno_conn_state->base; |
689 | } |
690 | |
691 | static const struct drm_connector_funcs inno_hdmi_connector_funcs = { |
692 | .fill_modes = drm_helper_probe_single_connector_modes, |
693 | .detect = inno_hdmi_connector_detect, |
694 | .destroy = inno_hdmi_connector_destroy, |
695 | .reset = inno_hdmi_connector_reset, |
696 | .atomic_duplicate_state = inno_hdmi_connector_duplicate_state, |
697 | .atomic_destroy_state = inno_hdmi_connector_destroy_state, |
698 | }; |
699 | |
700 | static struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = { |
701 | .get_modes = inno_hdmi_connector_get_modes, |
702 | .mode_valid = inno_hdmi_connector_mode_valid, |
703 | }; |
704 | |
705 | static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi) |
706 | { |
707 | struct drm_encoder *encoder = &hdmi->encoder.encoder; |
708 | struct device *dev = hdmi->dev; |
709 | |
710 | encoder->possible_crtcs = drm_of_find_possible_crtcs(dev: drm, port: dev->of_node); |
711 | |
712 | /* |
713 | * If we failed to find the CRTC(s) which this encoder is |
714 | * supposed to be connected to, it's because the CRTC has |
715 | * not been registered yet. Defer probing, and hope that |
716 | * the required CRTC is added later. |
717 | */ |
718 | if (encoder->possible_crtcs == 0) |
719 | return -EPROBE_DEFER; |
720 | |
721 | drm_encoder_helper_add(encoder, funcs: &inno_hdmi_encoder_helper_funcs); |
722 | drm_simple_encoder_init(dev: drm, encoder, DRM_MODE_ENCODER_TMDS); |
723 | |
724 | hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD; |
725 | |
726 | drm_connector_helper_add(connector: &hdmi->connector, |
727 | funcs: &inno_hdmi_connector_helper_funcs); |
728 | drm_connector_init_with_ddc(dev: drm, connector: &hdmi->connector, |
729 | funcs: &inno_hdmi_connector_funcs, |
730 | DRM_MODE_CONNECTOR_HDMIA, |
731 | ddc: hdmi->ddc); |
732 | |
733 | drm_connector_attach_encoder(connector: &hdmi->connector, encoder); |
734 | |
735 | return 0; |
736 | } |
737 | |
738 | static irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi) |
739 | { |
740 | struct inno_hdmi_i2c *i2c = hdmi->i2c; |
741 | u8 stat; |
742 | |
743 | stat = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1); |
744 | if (!(stat & m_INT_EDID_READY)) |
745 | return IRQ_NONE; |
746 | |
747 | /* Clear HDMI EDID interrupt flag */ |
748 | hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); |
749 | |
750 | complete(&i2c->cmp); |
751 | |
752 | return IRQ_HANDLED; |
753 | } |
754 | |
755 | static irqreturn_t inno_hdmi_hardirq(int irq, void *dev_id) |
756 | { |
757 | struct inno_hdmi *hdmi = dev_id; |
758 | irqreturn_t ret = IRQ_NONE; |
759 | u8 interrupt; |
760 | |
761 | if (hdmi->i2c) |
762 | ret = inno_hdmi_i2c_irq(hdmi); |
763 | |
764 | interrupt = hdmi_readb(hdmi, HDMI_STATUS); |
765 | if (interrupt & m_INT_HOTPLUG) { |
766 | hdmi_modb(hdmi, HDMI_STATUS, m_INT_HOTPLUG, m_INT_HOTPLUG); |
767 | ret = IRQ_WAKE_THREAD; |
768 | } |
769 | |
770 | return ret; |
771 | } |
772 | |
773 | static irqreturn_t inno_hdmi_irq(int irq, void *dev_id) |
774 | { |
775 | struct inno_hdmi *hdmi = dev_id; |
776 | |
777 | drm_helper_hpd_irq_event(dev: hdmi->connector.dev); |
778 | |
779 | return IRQ_HANDLED; |
780 | } |
781 | |
782 | static int inno_hdmi_i2c_read(struct inno_hdmi *hdmi, struct i2c_msg *msgs) |
783 | { |
784 | int length = msgs->len; |
785 | u8 *buf = msgs->buf; |
786 | int ret; |
787 | |
788 | ret = wait_for_completion_timeout(x: &hdmi->i2c->cmp, HZ / 10); |
789 | if (!ret) |
790 | return -EAGAIN; |
791 | |
792 | while (length--) |
793 | *buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR); |
794 | |
795 | return 0; |
796 | } |
797 | |
798 | static int inno_hdmi_i2c_write(struct inno_hdmi *hdmi, struct i2c_msg *msgs) |
799 | { |
800 | /* |
801 | * The DDC module only support read EDID message, so |
802 | * we assume that each word write to this i2c adapter |
803 | * should be the offset of EDID word address. |
804 | */ |
805 | if ((msgs->len != 1) || |
806 | ((msgs->addr != DDC_ADDR) && (msgs->addr != DDC_SEGMENT_ADDR))) |
807 | return -EINVAL; |
808 | |
809 | reinit_completion(x: &hdmi->i2c->cmp); |
810 | |
811 | if (msgs->addr == DDC_SEGMENT_ADDR) |
812 | hdmi->i2c->segment_addr = msgs->buf[0]; |
813 | if (msgs->addr == DDC_ADDR) |
814 | hdmi->i2c->ddc_addr = msgs->buf[0]; |
815 | |
816 | /* Set edid fifo first addr */ |
817 | hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, val: 0x00); |
818 | |
819 | /* Set edid word address 0x00/0x80 */ |
820 | hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, val: hdmi->i2c->ddc_addr); |
821 | |
822 | /* Set edid segment pointer */ |
823 | hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, val: hdmi->i2c->segment_addr); |
824 | |
825 | return 0; |
826 | } |
827 | |
828 | static int inno_hdmi_i2c_xfer(struct i2c_adapter *adap, |
829 | struct i2c_msg *msgs, int num) |
830 | { |
831 | struct inno_hdmi *hdmi = i2c_get_adapdata(adap); |
832 | struct inno_hdmi_i2c *i2c = hdmi->i2c; |
833 | int i, ret = 0; |
834 | |
835 | mutex_lock(&i2c->lock); |
836 | |
837 | /* Clear the EDID interrupt flag and unmute the interrupt */ |
838 | hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY); |
839 | hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); |
840 | |
841 | for (i = 0; i < num; i++) { |
842 | DRM_DEV_DEBUG(hdmi->dev, |
843 | "xfer: num: %d/%d, len: %d, flags: %#x\n" , |
844 | i + 1, num, msgs[i].len, msgs[i].flags); |
845 | |
846 | if (msgs[i].flags & I2C_M_RD) |
847 | ret = inno_hdmi_i2c_read(hdmi, msgs: &msgs[i]); |
848 | else |
849 | ret = inno_hdmi_i2c_write(hdmi, msgs: &msgs[i]); |
850 | |
851 | if (ret < 0) |
852 | break; |
853 | } |
854 | |
855 | if (!ret) |
856 | ret = num; |
857 | |
858 | /* Mute HDMI EDID interrupt */ |
859 | hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, val: 0); |
860 | |
861 | mutex_unlock(lock: &i2c->lock); |
862 | |
863 | return ret; |
864 | } |
865 | |
866 | static u32 inno_hdmi_i2c_func(struct i2c_adapter *adapter) |
867 | { |
868 | return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; |
869 | } |
870 | |
871 | static const struct i2c_algorithm inno_hdmi_algorithm = { |
872 | .master_xfer = inno_hdmi_i2c_xfer, |
873 | .functionality = inno_hdmi_i2c_func, |
874 | }; |
875 | |
876 | static struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi) |
877 | { |
878 | struct i2c_adapter *adap; |
879 | struct inno_hdmi_i2c *i2c; |
880 | int ret; |
881 | |
882 | i2c = devm_kzalloc(dev: hdmi->dev, size: sizeof(*i2c), GFP_KERNEL); |
883 | if (!i2c) |
884 | return ERR_PTR(error: -ENOMEM); |
885 | |
886 | mutex_init(&i2c->lock); |
887 | init_completion(x: &i2c->cmp); |
888 | |
889 | adap = &i2c->adap; |
890 | adap->owner = THIS_MODULE; |
891 | adap->dev.parent = hdmi->dev; |
892 | adap->dev.of_node = hdmi->dev->of_node; |
893 | adap->algo = &inno_hdmi_algorithm; |
894 | strscpy(adap->name, "Inno HDMI" , sizeof(adap->name)); |
895 | i2c_set_adapdata(adap, data: hdmi); |
896 | |
897 | ret = i2c_add_adapter(adap); |
898 | if (ret) { |
899 | dev_warn(hdmi->dev, "cannot add %s I2C adapter\n" , adap->name); |
900 | devm_kfree(dev: hdmi->dev, p: i2c); |
901 | return ERR_PTR(error: ret); |
902 | } |
903 | |
904 | hdmi->i2c = i2c; |
905 | |
906 | DRM_DEV_INFO(hdmi->dev, "registered %s I2C bus driver\n" , adap->name); |
907 | |
908 | return adap; |
909 | } |
910 | |
911 | static int inno_hdmi_bind(struct device *dev, struct device *master, |
912 | void *data) |
913 | { |
914 | struct platform_device *pdev = to_platform_device(dev); |
915 | struct drm_device *drm = data; |
916 | struct inno_hdmi *hdmi; |
917 | const struct inno_hdmi_variant *variant; |
918 | int irq; |
919 | int ret; |
920 | |
921 | hdmi = devm_kzalloc(dev, size: sizeof(*hdmi), GFP_KERNEL); |
922 | if (!hdmi) |
923 | return -ENOMEM; |
924 | |
925 | hdmi->dev = dev; |
926 | |
927 | variant = of_device_get_match_data(dev: hdmi->dev); |
928 | if (!variant) |
929 | return -EINVAL; |
930 | |
931 | hdmi->variant = variant; |
932 | |
933 | hdmi->regs = devm_platform_ioremap_resource(pdev, index: 0); |
934 | if (IS_ERR(ptr: hdmi->regs)) |
935 | return PTR_ERR(ptr: hdmi->regs); |
936 | |
937 | hdmi->pclk = devm_clk_get(dev: hdmi->dev, id: "pclk" ); |
938 | if (IS_ERR(ptr: hdmi->pclk)) { |
939 | DRM_DEV_ERROR(hdmi->dev, "Unable to get HDMI pclk clk\n" ); |
940 | return PTR_ERR(ptr: hdmi->pclk); |
941 | } |
942 | |
943 | ret = clk_prepare_enable(clk: hdmi->pclk); |
944 | if (ret) { |
945 | DRM_DEV_ERROR(hdmi->dev, |
946 | "Cannot enable HDMI pclk clock: %d\n" , ret); |
947 | return ret; |
948 | } |
949 | |
950 | hdmi->refclk = devm_clk_get_optional(dev: hdmi->dev, id: "ref" ); |
951 | if (IS_ERR(ptr: hdmi->refclk)) { |
952 | DRM_DEV_ERROR(hdmi->dev, "Unable to get HDMI reference clock\n" ); |
953 | ret = PTR_ERR(ptr: hdmi->refclk); |
954 | goto err_disable_pclk; |
955 | } |
956 | |
957 | ret = clk_prepare_enable(clk: hdmi->refclk); |
958 | if (ret) { |
959 | DRM_DEV_ERROR(hdmi->dev, |
960 | "Cannot enable HDMI reference clock: %d\n" , ret); |
961 | goto err_disable_pclk; |
962 | } |
963 | |
964 | irq = platform_get_irq(pdev, 0); |
965 | if (irq < 0) { |
966 | ret = irq; |
967 | goto err_disable_clk; |
968 | } |
969 | |
970 | inno_hdmi_reset(hdmi); |
971 | |
972 | hdmi->ddc = inno_hdmi_i2c_adapter(hdmi); |
973 | if (IS_ERR(ptr: hdmi->ddc)) { |
974 | ret = PTR_ERR(ptr: hdmi->ddc); |
975 | hdmi->ddc = NULL; |
976 | goto err_disable_clk; |
977 | } |
978 | |
979 | /* |
980 | * When the controller isn't configured to an accurate |
981 | * video timing and there is no reference clock available, |
982 | * then the TMDS clock source would be switched to PCLK_HDMI, |
983 | * so we need to init the TMDS rate to PCLK rate, and |
984 | * reconfigure the DDC clock. |
985 | */ |
986 | if (hdmi->refclk) |
987 | inno_hdmi_i2c_init(hdmi, rate: clk_get_rate(clk: hdmi->refclk)); |
988 | else |
989 | inno_hdmi_i2c_init(hdmi, rate: clk_get_rate(clk: hdmi->pclk)); |
990 | |
991 | ret = inno_hdmi_register(drm, hdmi); |
992 | if (ret) |
993 | goto err_put_adapter; |
994 | |
995 | dev_set_drvdata(dev, data: hdmi); |
996 | |
997 | /* Unmute hotplug interrupt */ |
998 | hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1)); |
999 | |
1000 | ret = devm_request_threaded_irq(dev, irq, handler: inno_hdmi_hardirq, |
1001 | thread_fn: inno_hdmi_irq, IRQF_SHARED, |
1002 | devname: dev_name(dev), dev_id: hdmi); |
1003 | if (ret < 0) |
1004 | goto err_cleanup_hdmi; |
1005 | |
1006 | return 0; |
1007 | err_cleanup_hdmi: |
1008 | hdmi->connector.funcs->destroy(&hdmi->connector); |
1009 | hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); |
1010 | err_put_adapter: |
1011 | i2c_put_adapter(adap: hdmi->ddc); |
1012 | err_disable_clk: |
1013 | clk_disable_unprepare(clk: hdmi->refclk); |
1014 | err_disable_pclk: |
1015 | clk_disable_unprepare(clk: hdmi->pclk); |
1016 | return ret; |
1017 | } |
1018 | |
1019 | static void inno_hdmi_unbind(struct device *dev, struct device *master, |
1020 | void *data) |
1021 | { |
1022 | struct inno_hdmi *hdmi = dev_get_drvdata(dev); |
1023 | |
1024 | hdmi->connector.funcs->destroy(&hdmi->connector); |
1025 | hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); |
1026 | |
1027 | i2c_put_adapter(adap: hdmi->ddc); |
1028 | clk_disable_unprepare(clk: hdmi->refclk); |
1029 | clk_disable_unprepare(clk: hdmi->pclk); |
1030 | } |
1031 | |
1032 | static const struct component_ops inno_hdmi_ops = { |
1033 | .bind = inno_hdmi_bind, |
1034 | .unbind = inno_hdmi_unbind, |
1035 | }; |
1036 | |
1037 | static int inno_hdmi_probe(struct platform_device *pdev) |
1038 | { |
1039 | return component_add(&pdev->dev, &inno_hdmi_ops); |
1040 | } |
1041 | |
1042 | static void inno_hdmi_remove(struct platform_device *pdev) |
1043 | { |
1044 | component_del(&pdev->dev, &inno_hdmi_ops); |
1045 | } |
1046 | |
1047 | static const struct inno_hdmi_variant rk3036_inno_hdmi_variant = { |
1048 | .phy_configs = rk3036_hdmi_phy_configs, |
1049 | .default_phy_config = &rk3036_hdmi_phy_configs[1], |
1050 | }; |
1051 | |
1052 | static const struct inno_hdmi_variant rk3128_inno_hdmi_variant = { |
1053 | .phy_configs = rk3128_hdmi_phy_configs, |
1054 | .default_phy_config = &rk3128_hdmi_phy_configs[1], |
1055 | }; |
1056 | |
1057 | static const struct of_device_id inno_hdmi_dt_ids[] = { |
1058 | { .compatible = "rockchip,rk3036-inno-hdmi" , |
1059 | .data = &rk3036_inno_hdmi_variant, |
1060 | }, |
1061 | { .compatible = "rockchip,rk3128-inno-hdmi" , |
1062 | .data = &rk3128_inno_hdmi_variant, |
1063 | }, |
1064 | {}, |
1065 | }; |
1066 | MODULE_DEVICE_TABLE(of, inno_hdmi_dt_ids); |
1067 | |
1068 | struct platform_driver inno_hdmi_driver = { |
1069 | .probe = inno_hdmi_probe, |
1070 | .remove_new = inno_hdmi_remove, |
1071 | .driver = { |
1072 | .name = "innohdmi-rockchip" , |
1073 | .of_match_table = inno_hdmi_dt_ids, |
1074 | }, |
1075 | }; |
1076 | |