diff options
Diffstat (limited to 'drivers/gpu/drm/rockchip/inno_hdmi.c')
-rw-r--r-- | drivers/gpu/drm/rockchip/inno_hdmi.c | 938 |
1 files changed, 938 insertions, 0 deletions
diff --git a/drivers/gpu/drm/rockchip/inno_hdmi.c b/drivers/gpu/drm/rockchip/inno_hdmi.c new file mode 100644 index 000000000000..10d62fff22f1 --- /dev/null +++ b/drivers/gpu/drm/rockchip/inno_hdmi.c | |||
@@ -0,0 +1,938 @@ | |||
1 | /* | ||
2 | * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd | ||
3 | * Zheng Yang <zhengyang@rock-chips.com> | ||
4 | * Yakir Yang <ykk@rock-chips.com> | ||
5 | * | ||
6 | * This software is licensed under the terms of the GNU General Public | ||
7 | * License version 2, as published by the Free Software Foundation, and | ||
8 | * may be copied, distributed, and modified under those terms. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | */ | ||
15 | |||
16 | #include <linux/irq.h> | ||
17 | #include <linux/clk.h> | ||
18 | #include <linux/delay.h> | ||
19 | #include <linux/err.h> | ||
20 | #include <linux/hdmi.h> | ||
21 | #include <linux/mfd/syscon.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/mutex.h> | ||
24 | #include <linux/of_device.h> | ||
25 | |||
26 | #include <drm/drm_of.h> | ||
27 | #include <drm/drmP.h> | ||
28 | #include <drm/drm_atomic_helper.h> | ||
29 | #include <drm/drm_crtc_helper.h> | ||
30 | #include <drm/drm_edid.h> | ||
31 | |||
32 | #include "rockchip_drm_drv.h" | ||
33 | #include "rockchip_drm_vop.h" | ||
34 | |||
35 | #include "inno_hdmi.h" | ||
36 | |||
37 | #define to_inno_hdmi(x) container_of(x, struct inno_hdmi, x) | ||
38 | |||
39 | struct hdmi_data_info { | ||
40 | int vic; | ||
41 | bool sink_is_hdmi; | ||
42 | bool sink_has_audio; | ||
43 | unsigned int enc_in_format; | ||
44 | unsigned int enc_out_format; | ||
45 | unsigned int colorimetry; | ||
46 | }; | ||
47 | |||
48 | struct inno_hdmi_i2c { | ||
49 | struct i2c_adapter adap; | ||
50 | |||
51 | u8 ddc_addr; | ||
52 | u8 segment_addr; | ||
53 | |||
54 | struct mutex lock; | ||
55 | struct completion cmp; | ||
56 | }; | ||
57 | |||
58 | struct inno_hdmi { | ||
59 | struct device *dev; | ||
60 | struct drm_device *drm_dev; | ||
61 | |||
62 | int irq; | ||
63 | struct clk *pclk; | ||
64 | void __iomem *regs; | ||
65 | |||
66 | struct drm_connector connector; | ||
67 | struct drm_encoder encoder; | ||
68 | |||
69 | struct inno_hdmi_i2c *i2c; | ||
70 | struct i2c_adapter *ddc; | ||
71 | |||
72 | unsigned int tmds_rate; | ||
73 | |||
74 | struct hdmi_data_info hdmi_data; | ||
75 | struct drm_display_mode previous_mode; | ||
76 | }; | ||
77 | |||
78 | enum { | ||
79 | CSC_ITU601_16_235_TO_RGB_0_255_8BIT, | ||
80 | CSC_ITU601_0_255_TO_RGB_0_255_8BIT, | ||
81 | CSC_ITU709_16_235_TO_RGB_0_255_8BIT, | ||
82 | CSC_RGB_0_255_TO_ITU601_16_235_8BIT, | ||
83 | CSC_RGB_0_255_TO_ITU709_16_235_8BIT, | ||
84 | CSC_RGB_0_255_TO_RGB_16_235_8BIT, | ||
85 | }; | ||
86 | |||
87 | static const char coeff_csc[][24] = { | ||
88 | /* | ||
89 | * YUV2RGB:601 SD mode(Y[16:235], UV[16:240], RGB[0:255]): | ||
90 | * R = 1.164*Y + 1.596*V - 204 | ||
91 | * G = 1.164*Y - 0.391*U - 0.813*V + 154 | ||
92 | * B = 1.164*Y + 2.018*U - 258 | ||
93 | */ | ||
94 | { | ||
95 | 0x04, 0xa7, 0x00, 0x00, 0x06, 0x62, 0x02, 0xcc, | ||
96 | 0x04, 0xa7, 0x11, 0x90, 0x13, 0x40, 0x00, 0x9a, | ||
97 | 0x04, 0xa7, 0x08, 0x12, 0x00, 0x00, 0x03, 0x02 | ||
98 | }, | ||
99 | /* | ||
100 | * YUV2RGB:601 SD mode(YUV[0:255],RGB[0:255]): | ||
101 | * R = Y + 1.402*V - 248 | ||
102 | * G = Y - 0.344*U - 0.714*V + 135 | ||
103 | * B = Y + 1.772*U - 227 | ||
104 | */ | ||
105 | { | ||
106 | 0x04, 0x00, 0x00, 0x00, 0x05, 0x9b, 0x02, 0xf8, | ||
107 | 0x04, 0x00, 0x11, 0x60, 0x12, 0xdb, 0x00, 0x87, | ||
108 | 0x04, 0x00, 0x07, 0x16, 0x00, 0x00, 0x02, 0xe3 | ||
109 | }, | ||
110 | /* | ||
111 | * YUV2RGB:709 HD mode(Y[16:235],UV[16:240],RGB[0:255]): | ||
112 | * R = 1.164*Y + 1.793*V - 248 | ||
113 | * G = 1.164*Y - 0.213*U - 0.534*V + 77 | ||
114 | * B = 1.164*Y + 2.115*U - 289 | ||
115 | */ | ||
116 | { | ||
117 | 0x04, 0xa7, 0x00, 0x00, 0x07, 0x2c, 0x02, 0xf8, | ||
118 | 0x04, 0xa7, 0x10, 0xda, 0x12, 0x22, 0x00, 0x4d, | ||
119 | 0x04, 0xa7, 0x08, 0x74, 0x00, 0x00, 0x03, 0x21 | ||
120 | }, | ||
121 | |||
122 | /* | ||
123 | * RGB2YUV:601 SD mode: | ||
124 | * Cb = -0.291G - 0.148R + 0.439B + 128 | ||
125 | * Y = 0.504G + 0.257R + 0.098B + 16 | ||
126 | * Cr = -0.368G + 0.439R - 0.071B + 128 | ||
127 | */ | ||
128 | { | ||
129 | 0x11, 0x5f, 0x01, 0x82, 0x10, 0x23, 0x00, 0x80, | ||
130 | 0x02, 0x1c, 0x00, 0xa1, 0x00, 0x36, 0x00, 0x1e, | ||
131 | 0x11, 0x29, 0x10, 0x59, 0x01, 0x82, 0x00, 0x80 | ||
132 | }, | ||
133 | /* | ||
134 | * RGB2YUV:709 HD mode: | ||
135 | * Cb = - 0.338G - 0.101R + 0.439B + 128 | ||
136 | * Y = 0.614G + 0.183R + 0.062B + 16 | ||
137 | * Cr = - 0.399G + 0.439R - 0.040B + 128 | ||
138 | */ | ||
139 | { | ||
140 | 0x11, 0x98, 0x01, 0xc1, 0x10, 0x28, 0x00, 0x80, | ||
141 | 0x02, 0x74, 0x00, 0xbb, 0x00, 0x3f, 0x00, 0x10, | ||
142 | 0x11, 0x5a, 0x10, 0x67, 0x01, 0xc1, 0x00, 0x80 | ||
143 | }, | ||
144 | /* | ||
145 | * RGB[0:255]2RGB[16:235]: | ||
146 | * R' = R x (235-16)/255 + 16; | ||
147 | * G' = G x (235-16)/255 + 16; | ||
148 | * B' = B x (235-16)/255 + 16; | ||
149 | */ | ||
150 | { | ||
151 | 0x00, 0x00, 0x03, 0x6F, 0x00, 0x00, 0x00, 0x10, | ||
152 | 0x03, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, | ||
153 | 0x00, 0x00, 0x00, 0x00, 0x03, 0x6F, 0x00, 0x10 | ||
154 | }, | ||
155 | }; | ||
156 | |||
157 | static inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset) | ||
158 | { | ||
159 | return readl_relaxed(hdmi->regs + (offset) * 0x04); | ||
160 | } | ||
161 | |||
162 | static inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val) | ||
163 | { | ||
164 | writel_relaxed(val, hdmi->regs + (offset) * 0x04); | ||
165 | } | ||
166 | |||
167 | static inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, | ||
168 | u32 msk, u32 val) | ||
169 | { | ||
170 | u8 temp = hdmi_readb(hdmi, offset) & ~msk; | ||
171 | |||
172 | temp |= val & msk; | ||
173 | hdmi_writeb(hdmi, offset, temp); | ||
174 | } | ||
175 | |||
176 | static void inno_hdmi_i2c_init(struct inno_hdmi *hdmi) | ||
177 | { | ||
178 | int ddc_bus_freq; | ||
179 | |||
180 | ddc_bus_freq = (hdmi->tmds_rate >> 2) / HDMI_SCL_RATE; | ||
181 | |||
182 | hdmi_writeb(hdmi, DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF); | ||
183 | hdmi_writeb(hdmi, DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF); | ||
184 | |||
185 | /* Clear the EDID interrupt flag and mute the interrupt */ | ||
186 | hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0); | ||
187 | hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); | ||
188 | } | ||
189 | |||
190 | static void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable) | ||
191 | { | ||
192 | if (enable) | ||
193 | hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON); | ||
194 | else | ||
195 | hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF); | ||
196 | } | ||
197 | |||
198 | static void inno_hdmi_set_pwr_mode(struct inno_hdmi *hdmi, int mode) | ||
199 | { | ||
200 | switch (mode) { | ||
201 | case NORMAL: | ||
202 | inno_hdmi_sys_power(hdmi, false); | ||
203 | |||
204 | hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x6f); | ||
205 | hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0xbb); | ||
206 | |||
207 | hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15); | ||
208 | hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x14); | ||
209 | hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x10); | ||
210 | hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x0f); | ||
211 | hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x00); | ||
212 | hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x01); | ||
213 | |||
214 | inno_hdmi_sys_power(hdmi, true); | ||
215 | break; | ||
216 | |||
217 | case LOWER_PWR: | ||
218 | inno_hdmi_sys_power(hdmi, false); | ||
219 | hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0x00); | ||
220 | hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x00); | ||
221 | hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x00); | ||
222 | hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15); | ||
223 | |||
224 | break; | ||
225 | |||
226 | default: | ||
227 | dev_err(hdmi->dev, "Unknown power mode %d\n", mode); | ||
228 | } | ||
229 | } | ||
230 | |||
231 | static void inno_hdmi_reset(struct inno_hdmi *hdmi) | ||
232 | { | ||
233 | u32 val; | ||
234 | u32 msk; | ||
235 | |||
236 | hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_DIGITAL, v_NOT_RST_DIGITAL); | ||
237 | udelay(100); | ||
238 | |||
239 | hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_ANALOG, v_NOT_RST_ANALOG); | ||
240 | udelay(100); | ||
241 | |||
242 | msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL; | ||
243 | val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH; | ||
244 | hdmi_modb(hdmi, HDMI_SYS_CTRL, msk, val); | ||
245 | |||
246 | inno_hdmi_set_pwr_mode(hdmi, NORMAL); | ||
247 | } | ||
248 | |||
249 | static int inno_hdmi_upload_frame(struct inno_hdmi *hdmi, int setup_rc, | ||
250 | union hdmi_infoframe *frame, u32 frame_index, | ||
251 | u32 mask, u32 disable, u32 enable) | ||
252 | { | ||
253 | if (mask) | ||
254 | hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, disable); | ||
255 | |||
256 | hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, frame_index); | ||
257 | |||
258 | if (setup_rc >= 0) { | ||
259 | u8 packed_frame[HDMI_MAXIMUM_INFO_FRAME_SIZE]; | ||
260 | ssize_t rc, i; | ||
261 | |||
262 | rc = hdmi_infoframe_pack(frame, packed_frame, | ||
263 | sizeof(packed_frame)); | ||
264 | if (rc < 0) | ||
265 | return rc; | ||
266 | |||
267 | for (i = 0; i < rc; i++) | ||
268 | hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i, | ||
269 | packed_frame[i]); | ||
270 | |||
271 | if (mask) | ||
272 | hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, enable); | ||
273 | } | ||
274 | |||
275 | return setup_rc; | ||
276 | } | ||
277 | |||
278 | static int inno_hdmi_config_video_vsi(struct inno_hdmi *hdmi, | ||
279 | struct drm_display_mode *mode) | ||
280 | { | ||
281 | union hdmi_infoframe frame; | ||
282 | int rc; | ||
283 | |||
284 | rc = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi, | ||
285 | mode); | ||
286 | |||
287 | return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_VSI, | ||
288 | m_PACKET_VSI_EN, v_PACKET_VSI_EN(0), v_PACKET_VSI_EN(1)); | ||
289 | } | ||
290 | |||
291 | static int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi, | ||
292 | struct drm_display_mode *mode) | ||
293 | { | ||
294 | union hdmi_infoframe frame; | ||
295 | int rc; | ||
296 | |||
297 | rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode); | ||
298 | |||
299 | if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV444) | ||
300 | frame.avi.colorspace = HDMI_COLORSPACE_YUV444; | ||
301 | else if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV422) | ||
302 | frame.avi.colorspace = HDMI_COLORSPACE_YUV422; | ||
303 | else | ||
304 | frame.avi.colorspace = HDMI_COLORSPACE_RGB; | ||
305 | |||
306 | return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_AVI, 0, 0, 0); | ||
307 | } | ||
308 | |||
309 | static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi) | ||
310 | { | ||
311 | struct hdmi_data_info *data = &hdmi->hdmi_data; | ||
312 | int c0_c2_change = 0; | ||
313 | int csc_enable = 0; | ||
314 | int csc_mode = 0; | ||
315 | int auto_csc = 0; | ||
316 | int value; | ||
317 | int i; | ||
318 | |||
319 | /* Input video mode is SDR RGB24bit, data enable signal from external */ | ||
320 | hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL | | ||
321 | v_VIDEO_INPUT_FORMAT(VIDEO_INPUT_SDR_RGB444)); | ||
322 | |||
323 | /* Input color hardcode to RGB, and output color hardcode to RGB888 */ | ||
324 | value = v_VIDEO_INPUT_BITS(VIDEO_INPUT_8BITS) | | ||
325 | v_VIDEO_OUTPUT_COLOR(0) | | ||
326 | v_VIDEO_INPUT_CSP(0); | ||
327 | hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, value); | ||
328 | |||
329 | if (data->enc_in_format == data->enc_out_format) { | ||
330 | if ((data->enc_in_format == HDMI_COLORSPACE_RGB) || | ||
331 | (data->enc_in_format >= HDMI_COLORSPACE_YUV444)) { | ||
332 | value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED(1); | ||
333 | hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value); | ||
334 | |||
335 | hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, | ||
336 | m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP, | ||
337 | v_VIDEO_AUTO_CSC(AUTO_CSC_DISABLE) | | ||
338 | v_VIDEO_C0_C2_SWAP(C0_C2_CHANGE_DISABLE)); | ||
339 | return 0; | ||
340 | } | ||
341 | } | ||
342 | |||
343 | if (data->colorimetry == HDMI_COLORIMETRY_ITU_601) { | ||
344 | if ((data->enc_in_format == HDMI_COLORSPACE_RGB) && | ||
345 | (data->enc_out_format == HDMI_COLORSPACE_YUV444)) { | ||
346 | csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT; | ||
347 | auto_csc = AUTO_CSC_DISABLE; | ||
348 | c0_c2_change = C0_C2_CHANGE_DISABLE; | ||
349 | csc_enable = v_CSC_ENABLE; | ||
350 | } else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) && | ||
351 | (data->enc_out_format == HDMI_COLORSPACE_RGB)) { | ||
352 | csc_mode = CSC_ITU601_16_235_TO_RGB_0_255_8BIT; | ||
353 | auto_csc = AUTO_CSC_ENABLE; | ||
354 | c0_c2_change = C0_C2_CHANGE_DISABLE; | ||
355 | csc_enable = v_CSC_DISABLE; | ||
356 | } | ||
357 | } else { | ||
358 | if ((data->enc_in_format == HDMI_COLORSPACE_RGB) && | ||
359 | (data->enc_out_format == HDMI_COLORSPACE_YUV444)) { | ||
360 | csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT; | ||
361 | auto_csc = AUTO_CSC_DISABLE; | ||
362 | c0_c2_change = C0_C2_CHANGE_DISABLE; | ||
363 | csc_enable = v_CSC_ENABLE; | ||
364 | } else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) && | ||
365 | (data->enc_out_format == HDMI_COLORSPACE_RGB)) { | ||
366 | csc_mode = CSC_ITU709_16_235_TO_RGB_0_255_8BIT; | ||
367 | auto_csc = AUTO_CSC_ENABLE; | ||
368 | c0_c2_change = C0_C2_CHANGE_DISABLE; | ||
369 | csc_enable = v_CSC_DISABLE; | ||
370 | } | ||
371 | } | ||
372 | |||
373 | for (i = 0; i < 24; i++) | ||
374 | hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i, | ||
375 | coeff_csc[csc_mode][i]); | ||
376 | |||
377 | value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1); | ||
378 | hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value); | ||
379 | hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, m_VIDEO_AUTO_CSC | | ||
380 | m_VIDEO_C0_C2_SWAP, v_VIDEO_AUTO_CSC(auto_csc) | | ||
381 | v_VIDEO_C0_C2_SWAP(c0_c2_change)); | ||
382 | |||
383 | return 0; | ||
384 | } | ||
385 | |||
386 | static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi, | ||
387 | struct drm_display_mode *mode) | ||
388 | { | ||
389 | int value; | ||
390 | |||
391 | /* Set detail external video timing polarity and interlace mode */ | ||
392 | value = v_EXTERANL_VIDEO(1); | ||
393 | value |= mode->flags & DRM_MODE_FLAG_PHSYNC ? | ||
394 | v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0); | ||
395 | value |= mode->flags & DRM_MODE_FLAG_PVSYNC ? | ||
396 | v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0); | ||
397 | value |= mode->flags & DRM_MODE_FLAG_INTERLACE ? | ||
398 | v_INETLACE(1) : v_INETLACE(0); | ||
399 | hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value); | ||
400 | |||
401 | /* Set detail external video timing */ | ||
402 | value = mode->htotal; | ||
403 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF); | ||
404 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF); | ||
405 | |||
406 | value = mode->htotal - mode->hdisplay; | ||
407 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF); | ||
408 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF); | ||
409 | |||
410 | value = mode->hsync_start - mode->hdisplay; | ||
411 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF); | ||
412 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF); | ||
413 | |||
414 | value = mode->hsync_end - mode->hsync_start; | ||
415 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF); | ||
416 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF); | ||
417 | |||
418 | value = mode->vtotal; | ||
419 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF); | ||
420 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF); | ||
421 | |||
422 | value = mode->vtotal - mode->vdisplay; | ||
423 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF); | ||
424 | |||
425 | value = mode->vsync_start - mode->vdisplay; | ||
426 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF); | ||
427 | |||
428 | value = mode->vsync_end - mode->vsync_start; | ||
429 | hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF); | ||
430 | |||
431 | hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, 0x1e); | ||
432 | hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, 0x2c); | ||
433 | hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, 0x01); | ||
434 | |||
435 | return 0; | ||
436 | } | ||
437 | |||
438 | static int inno_hdmi_setup(struct inno_hdmi *hdmi, | ||
439 | struct drm_display_mode *mode) | ||
440 | { | ||
441 | hdmi->hdmi_data.vic = drm_match_cea_mode(mode); | ||
442 | |||
443 | hdmi->hdmi_data.enc_in_format = HDMI_COLORSPACE_RGB; | ||
444 | hdmi->hdmi_data.enc_out_format = HDMI_COLORSPACE_RGB; | ||
445 | |||
446 | if ((hdmi->hdmi_data.vic == 6) || (hdmi->hdmi_data.vic == 7) || | ||
447 | (hdmi->hdmi_data.vic == 21) || (hdmi->hdmi_data.vic == 22) || | ||
448 | (hdmi->hdmi_data.vic == 2) || (hdmi->hdmi_data.vic == 3) || | ||
449 | (hdmi->hdmi_data.vic == 17) || (hdmi->hdmi_data.vic == 18)) | ||
450 | hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_601; | ||
451 | else | ||
452 | hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709; | ||
453 | |||
454 | /* Mute video and audio output */ | ||
455 | hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, | ||
456 | v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1)); | ||
457 | |||
458 | /* Set HDMI Mode */ | ||
459 | hdmi_writeb(hdmi, HDMI_HDCP_CTRL, | ||
460 | v_HDMI_DVI(hdmi->hdmi_data.sink_is_hdmi)); | ||
461 | |||
462 | inno_hdmi_config_video_timing(hdmi, mode); | ||
463 | |||
464 | inno_hdmi_config_video_csc(hdmi); | ||
465 | |||
466 | if (hdmi->hdmi_data.sink_is_hdmi) { | ||
467 | inno_hdmi_config_video_avi(hdmi, mode); | ||
468 | inno_hdmi_config_video_vsi(hdmi, mode); | ||
469 | } | ||
470 | |||
471 | /* | ||
472 | * When IP controller have configured to an accurate video | ||
473 | * timing, then the TMDS clock source would be switched to | ||
474 | * DCLK_LCDC, so we need to init the TMDS rate to mode pixel | ||
475 | * clock rate, and reconfigure the DDC clock. | ||
476 | */ | ||
477 | hdmi->tmds_rate = mode->clock * 1000; | ||
478 | inno_hdmi_i2c_init(hdmi); | ||
479 | |||
480 | /* Unmute video and audio output */ | ||
481 | hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, | ||
482 | v_AUDIO_MUTE(0) | v_VIDEO_MUTE(0)); | ||
483 | |||
484 | return 0; | ||
485 | } | ||
486 | |||
487 | static void inno_hdmi_encoder_mode_set(struct drm_encoder *encoder, | ||
488 | struct drm_display_mode *mode, | ||
489 | struct drm_display_mode *adj_mode) | ||
490 | { | ||
491 | struct inno_hdmi *hdmi = to_inno_hdmi(encoder); | ||
492 | |||
493 | inno_hdmi_setup(hdmi, adj_mode); | ||
494 | |||
495 | /* Store the display mode for plugin/DPMS poweron events */ | ||
496 | memcpy(&hdmi->previous_mode, adj_mode, sizeof(hdmi->previous_mode)); | ||
497 | } | ||
498 | |||
499 | static void inno_hdmi_encoder_enable(struct drm_encoder *encoder) | ||
500 | { | ||
501 | struct inno_hdmi *hdmi = to_inno_hdmi(encoder); | ||
502 | |||
503 | rockchip_drm_crtc_mode_config(encoder->crtc, DRM_MODE_CONNECTOR_HDMIA, | ||
504 | ROCKCHIP_OUT_MODE_P888); | ||
505 | |||
506 | inno_hdmi_set_pwr_mode(hdmi, NORMAL); | ||
507 | } | ||
508 | |||
509 | static void inno_hdmi_encoder_disable(struct drm_encoder *encoder) | ||
510 | { | ||
511 | struct inno_hdmi *hdmi = to_inno_hdmi(encoder); | ||
512 | |||
513 | inno_hdmi_set_pwr_mode(hdmi, LOWER_PWR); | ||
514 | } | ||
515 | |||
516 | static bool inno_hdmi_encoder_mode_fixup(struct drm_encoder *encoder, | ||
517 | const struct drm_display_mode *mode, | ||
518 | struct drm_display_mode *adj_mode) | ||
519 | { | ||
520 | return true; | ||
521 | } | ||
522 | |||
523 | static struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = { | ||
524 | .enable = inno_hdmi_encoder_enable, | ||
525 | .disable = inno_hdmi_encoder_disable, | ||
526 | .mode_fixup = inno_hdmi_encoder_mode_fixup, | ||
527 | .mode_set = inno_hdmi_encoder_mode_set, | ||
528 | }; | ||
529 | |||
530 | static struct drm_encoder_funcs inno_hdmi_encoder_funcs = { | ||
531 | .destroy = drm_encoder_cleanup, | ||
532 | }; | ||
533 | |||
534 | static enum drm_connector_status | ||
535 | inno_hdmi_connector_detect(struct drm_connector *connector, bool force) | ||
536 | { | ||
537 | struct inno_hdmi *hdmi = to_inno_hdmi(connector); | ||
538 | |||
539 | return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ? | ||
540 | connector_status_connected : connector_status_disconnected; | ||
541 | } | ||
542 | |||
543 | static int inno_hdmi_connector_get_modes(struct drm_connector *connector) | ||
544 | { | ||
545 | struct inno_hdmi *hdmi = to_inno_hdmi(connector); | ||
546 | struct edid *edid; | ||
547 | int ret = 0; | ||
548 | |||
549 | if (!hdmi->ddc) | ||
550 | return 0; | ||
551 | |||
552 | edid = drm_get_edid(connector, hdmi->ddc); | ||
553 | if (edid) { | ||
554 | hdmi->hdmi_data.sink_is_hdmi = drm_detect_hdmi_monitor(edid); | ||
555 | hdmi->hdmi_data.sink_has_audio = drm_detect_monitor_audio(edid); | ||
556 | drm_mode_connector_update_edid_property(connector, edid); | ||
557 | ret = drm_add_edid_modes(connector, edid); | ||
558 | kfree(edid); | ||
559 | } | ||
560 | |||
561 | return ret; | ||
562 | } | ||
563 | |||
564 | static enum drm_mode_status | ||
565 | inno_hdmi_connector_mode_valid(struct drm_connector *connector, | ||
566 | struct drm_display_mode *mode) | ||
567 | { | ||
568 | return MODE_OK; | ||
569 | } | ||
570 | |||
571 | static struct drm_encoder * | ||
572 | inno_hdmi_connector_best_encoder(struct drm_connector *connector) | ||
573 | { | ||
574 | struct inno_hdmi *hdmi = to_inno_hdmi(connector); | ||
575 | |||
576 | return &hdmi->encoder; | ||
577 | } | ||
578 | |||
579 | static int | ||
580 | inno_hdmi_probe_single_connector_modes(struct drm_connector *connector, | ||
581 | uint32_t maxX, uint32_t maxY) | ||
582 | { | ||
583 | return drm_helper_probe_single_connector_modes(connector, 1920, 1080); | ||
584 | } | ||
585 | |||
586 | static void inno_hdmi_connector_destroy(struct drm_connector *connector) | ||
587 | { | ||
588 | drm_connector_unregister(connector); | ||
589 | drm_connector_cleanup(connector); | ||
590 | } | ||
591 | |||
592 | static struct drm_connector_funcs inno_hdmi_connector_funcs = { | ||
593 | .dpms = drm_atomic_helper_connector_dpms, | ||
594 | .fill_modes = inno_hdmi_probe_single_connector_modes, | ||
595 | .detect = inno_hdmi_connector_detect, | ||
596 | .destroy = inno_hdmi_connector_destroy, | ||
597 | .reset = drm_atomic_helper_connector_reset, | ||
598 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | ||
599 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | ||
600 | }; | ||
601 | |||
602 | static struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = { | ||
603 | .get_modes = inno_hdmi_connector_get_modes, | ||
604 | .mode_valid = inno_hdmi_connector_mode_valid, | ||
605 | .best_encoder = inno_hdmi_connector_best_encoder, | ||
606 | }; | ||
607 | |||
608 | static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi) | ||
609 | { | ||
610 | struct drm_encoder *encoder = &hdmi->encoder; | ||
611 | struct device *dev = hdmi->dev; | ||
612 | |||
613 | encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); | ||
614 | |||
615 | /* | ||
616 | * If we failed to find the CRTC(s) which this encoder is | ||
617 | * supposed to be connected to, it's because the CRTC has | ||
618 | * not been registered yet. Defer probing, and hope that | ||
619 | * the required CRTC is added later. | ||
620 | */ | ||
621 | if (encoder->possible_crtcs == 0) | ||
622 | return -EPROBE_DEFER; | ||
623 | |||
624 | drm_encoder_helper_add(encoder, &inno_hdmi_encoder_helper_funcs); | ||
625 | drm_encoder_init(drm, encoder, &inno_hdmi_encoder_funcs, | ||
626 | DRM_MODE_ENCODER_TMDS, NULL); | ||
627 | |||
628 | hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD; | ||
629 | |||
630 | drm_connector_helper_add(&hdmi->connector, | ||
631 | &inno_hdmi_connector_helper_funcs); | ||
632 | drm_connector_init(drm, &hdmi->connector, &inno_hdmi_connector_funcs, | ||
633 | DRM_MODE_CONNECTOR_HDMIA); | ||
634 | |||
635 | drm_mode_connector_attach_encoder(&hdmi->connector, encoder); | ||
636 | |||
637 | return 0; | ||
638 | } | ||
639 | |||
640 | static irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi) | ||
641 | { | ||
642 | struct inno_hdmi_i2c *i2c = hdmi->i2c; | ||
643 | u8 stat; | ||
644 | |||
645 | stat = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1); | ||
646 | if (!(stat & m_INT_EDID_READY)) | ||
647 | return IRQ_NONE; | ||
648 | |||
649 | /* Clear HDMI EDID interrupt flag */ | ||
650 | hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); | ||
651 | |||
652 | complete(&i2c->cmp); | ||
653 | |||
654 | return IRQ_HANDLED; | ||
655 | } | ||
656 | |||
657 | static irqreturn_t inno_hdmi_hardirq(int irq, void *dev_id) | ||
658 | { | ||
659 | struct inno_hdmi *hdmi = dev_id; | ||
660 | irqreturn_t ret = IRQ_NONE; | ||
661 | u8 interrupt; | ||
662 | |||
663 | if (hdmi->i2c) | ||
664 | ret = inno_hdmi_i2c_irq(hdmi); | ||
665 | |||
666 | interrupt = hdmi_readb(hdmi, HDMI_STATUS); | ||
667 | if (interrupt & m_INT_HOTPLUG) { | ||
668 | hdmi_modb(hdmi, HDMI_STATUS, m_INT_HOTPLUG, m_INT_HOTPLUG); | ||
669 | ret = IRQ_WAKE_THREAD; | ||
670 | } | ||
671 | |||
672 | return ret; | ||
673 | } | ||
674 | |||
675 | static irqreturn_t inno_hdmi_irq(int irq, void *dev_id) | ||
676 | { | ||
677 | struct inno_hdmi *hdmi = dev_id; | ||
678 | |||
679 | drm_helper_hpd_irq_event(hdmi->connector.dev); | ||
680 | |||
681 | return IRQ_HANDLED; | ||
682 | } | ||
683 | |||
684 | static int inno_hdmi_i2c_read(struct inno_hdmi *hdmi, struct i2c_msg *msgs) | ||
685 | { | ||
686 | int length = msgs->len; | ||
687 | u8 *buf = msgs->buf; | ||
688 | int ret; | ||
689 | |||
690 | ret = wait_for_completion_timeout(&hdmi->i2c->cmp, HZ / 10); | ||
691 | if (!ret) | ||
692 | return -EAGAIN; | ||
693 | |||
694 | while (length--) | ||
695 | *buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR); | ||
696 | |||
697 | return 0; | ||
698 | } | ||
699 | |||
700 | static int inno_hdmi_i2c_write(struct inno_hdmi *hdmi, struct i2c_msg *msgs) | ||
701 | { | ||
702 | /* | ||
703 | * The DDC module only support read EDID message, so | ||
704 | * we assume that each word write to this i2c adapter | ||
705 | * should be the offset of EDID word address. | ||
706 | */ | ||
707 | if ((msgs->len != 1) || | ||
708 | ((msgs->addr != DDC_ADDR) && (msgs->addr != DDC_SEGMENT_ADDR))) | ||
709 | return -EINVAL; | ||
710 | |||
711 | reinit_completion(&hdmi->i2c->cmp); | ||
712 | |||
713 | if (msgs->addr == DDC_SEGMENT_ADDR) | ||
714 | hdmi->i2c->segment_addr = msgs->buf[0]; | ||
715 | if (msgs->addr == DDC_ADDR) | ||
716 | hdmi->i2c->ddc_addr = msgs->buf[0]; | ||
717 | |||
718 | /* Set edid fifo first addr */ | ||
719 | hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, 0x00); | ||
720 | |||
721 | /* Set edid word address 0x00/0x80 */ | ||
722 | hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr); | ||
723 | |||
724 | /* Set edid segment pointer */ | ||
725 | hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr); | ||
726 | |||
727 | return 0; | ||
728 | } | ||
729 | |||
730 | static int inno_hdmi_i2c_xfer(struct i2c_adapter *adap, | ||
731 | struct i2c_msg *msgs, int num) | ||
732 | { | ||
733 | struct inno_hdmi *hdmi = i2c_get_adapdata(adap); | ||
734 | struct inno_hdmi_i2c *i2c = hdmi->i2c; | ||
735 | int i, ret = 0; | ||
736 | |||
737 | mutex_lock(&i2c->lock); | ||
738 | |||
739 | /* Clear the EDID interrupt flag and unmute the interrupt */ | ||
740 | hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY); | ||
741 | hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); | ||
742 | |||
743 | for (i = 0; i < num; i++) { | ||
744 | dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n", | ||
745 | i + 1, num, msgs[i].len, msgs[i].flags); | ||
746 | |||
747 | if (msgs[i].flags & I2C_M_RD) | ||
748 | ret = inno_hdmi_i2c_read(hdmi, &msgs[i]); | ||
749 | else | ||
750 | ret = inno_hdmi_i2c_write(hdmi, &msgs[i]); | ||
751 | |||
752 | if (ret < 0) | ||
753 | break; | ||
754 | } | ||
755 | |||
756 | if (!ret) | ||
757 | ret = num; | ||
758 | |||
759 | /* Mute HDMI EDID interrupt */ | ||
760 | hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0); | ||
761 | |||
762 | mutex_unlock(&i2c->lock); | ||
763 | |||
764 | return ret; | ||
765 | } | ||
766 | |||
767 | static u32 inno_hdmi_i2c_func(struct i2c_adapter *adapter) | ||
768 | { | ||
769 | return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; | ||
770 | } | ||
771 | |||
772 | static const struct i2c_algorithm inno_hdmi_algorithm = { | ||
773 | .master_xfer = inno_hdmi_i2c_xfer, | ||
774 | .functionality = inno_hdmi_i2c_func, | ||
775 | }; | ||
776 | |||
777 | static struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi) | ||
778 | { | ||
779 | struct i2c_adapter *adap; | ||
780 | struct inno_hdmi_i2c *i2c; | ||
781 | int ret; | ||
782 | |||
783 | i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL); | ||
784 | if (!i2c) | ||
785 | return ERR_PTR(-ENOMEM); | ||
786 | |||
787 | mutex_init(&i2c->lock); | ||
788 | init_completion(&i2c->cmp); | ||
789 | |||
790 | adap = &i2c->adap; | ||
791 | adap->class = I2C_CLASS_DDC; | ||
792 | adap->owner = THIS_MODULE; | ||
793 | adap->dev.parent = hdmi->dev; | ||
794 | adap->dev.of_node = hdmi->dev->of_node; | ||
795 | adap->algo = &inno_hdmi_algorithm; | ||
796 | strlcpy(adap->name, "Inno HDMI", sizeof(adap->name)); | ||
797 | i2c_set_adapdata(adap, hdmi); | ||
798 | |||
799 | ret = i2c_add_adapter(adap); | ||
800 | if (ret) { | ||
801 | dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name); | ||
802 | devm_kfree(hdmi->dev, i2c); | ||
803 | return ERR_PTR(ret); | ||
804 | } | ||
805 | |||
806 | hdmi->i2c = i2c; | ||
807 | |||
808 | dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name); | ||
809 | |||
810 | return adap; | ||
811 | } | ||
812 | |||
813 | static int inno_hdmi_bind(struct device *dev, struct device *master, | ||
814 | void *data) | ||
815 | { | ||
816 | struct platform_device *pdev = to_platform_device(dev); | ||
817 | struct drm_device *drm = data; | ||
818 | struct inno_hdmi *hdmi; | ||
819 | struct resource *iores; | ||
820 | int irq; | ||
821 | int ret; | ||
822 | |||
823 | hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); | ||
824 | if (!hdmi) | ||
825 | return -ENOMEM; | ||
826 | |||
827 | hdmi->dev = dev; | ||
828 | hdmi->drm_dev = drm; | ||
829 | |||
830 | iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
831 | if (!iores) | ||
832 | return -ENXIO; | ||
833 | |||
834 | hdmi->regs = devm_ioremap_resource(dev, iores); | ||
835 | if (IS_ERR(hdmi->regs)) | ||
836 | return PTR_ERR(hdmi->regs); | ||
837 | |||
838 | hdmi->pclk = devm_clk_get(hdmi->dev, "pclk"); | ||
839 | if (IS_ERR(hdmi->pclk)) { | ||
840 | dev_err(hdmi->dev, "Unable to get HDMI pclk clk\n"); | ||
841 | return PTR_ERR(hdmi->pclk); | ||
842 | } | ||
843 | |||
844 | ret = clk_prepare_enable(hdmi->pclk); | ||
845 | if (ret) { | ||
846 | dev_err(hdmi->dev, "Cannot enable HDMI pclk clock: %d\n", ret); | ||
847 | return ret; | ||
848 | } | ||
849 | |||
850 | irq = platform_get_irq(pdev, 0); | ||
851 | if (irq < 0) | ||
852 | return irq; | ||
853 | |||
854 | inno_hdmi_reset(hdmi); | ||
855 | |||
856 | hdmi->ddc = inno_hdmi_i2c_adapter(hdmi); | ||
857 | if (IS_ERR(hdmi->ddc)) { | ||
858 | hdmi->ddc = NULL; | ||
859 | return PTR_ERR(hdmi->ddc); | ||
860 | } | ||
861 | |||
862 | /* | ||
863 | * When IP controller haven't configured to an accurate video | ||
864 | * timing, then the TMDS clock source would be switched to | ||
865 | * PCLK_HDMI, so we need to init the TMDS rate to PCLK rate, | ||
866 | * and reconfigure the DDC clock. | ||
867 | */ | ||
868 | hdmi->tmds_rate = clk_get_rate(hdmi->pclk); | ||
869 | inno_hdmi_i2c_init(hdmi); | ||
870 | |||
871 | ret = inno_hdmi_register(drm, hdmi); | ||
872 | if (ret) | ||
873 | return ret; | ||
874 | |||
875 | dev_set_drvdata(dev, hdmi); | ||
876 | |||
877 | /* Unmute hotplug interrupt */ | ||
878 | hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1)); | ||
879 | |||
880 | ret = devm_request_threaded_irq(dev, irq, inno_hdmi_hardirq, | ||
881 | inno_hdmi_irq, IRQF_SHARED, | ||
882 | dev_name(dev), hdmi); | ||
883 | |||
884 | return ret; | ||
885 | } | ||
886 | |||
887 | static void inno_hdmi_unbind(struct device *dev, struct device *master, | ||
888 | void *data) | ||
889 | { | ||
890 | struct inno_hdmi *hdmi = dev_get_drvdata(dev); | ||
891 | |||
892 | hdmi->connector.funcs->destroy(&hdmi->connector); | ||
893 | hdmi->encoder.funcs->destroy(&hdmi->encoder); | ||
894 | |||
895 | clk_disable_unprepare(hdmi->pclk); | ||
896 | i2c_put_adapter(hdmi->ddc); | ||
897 | } | ||
898 | |||
899 | static const struct component_ops inno_hdmi_ops = { | ||
900 | .bind = inno_hdmi_bind, | ||
901 | .unbind = inno_hdmi_unbind, | ||
902 | }; | ||
903 | |||
904 | static int inno_hdmi_probe(struct platform_device *pdev) | ||
905 | { | ||
906 | return component_add(&pdev->dev, &inno_hdmi_ops); | ||
907 | } | ||
908 | |||
909 | static int inno_hdmi_remove(struct platform_device *pdev) | ||
910 | { | ||
911 | component_del(&pdev->dev, &inno_hdmi_ops); | ||
912 | |||
913 | return 0; | ||
914 | } | ||
915 | |||
916 | static const struct of_device_id inno_hdmi_dt_ids[] = { | ||
917 | { .compatible = "rockchip,rk3036-inno-hdmi", | ||
918 | }, | ||
919 | {}, | ||
920 | }; | ||
921 | MODULE_DEVICE_TABLE(of, inno_hdmi_dt_ids); | ||
922 | |||
923 | static struct platform_driver inno_hdmi_driver = { | ||
924 | .probe = inno_hdmi_probe, | ||
925 | .remove = inno_hdmi_remove, | ||
926 | .driver = { | ||
927 | .name = "innohdmi-rockchip", | ||
928 | .of_match_table = inno_hdmi_dt_ids, | ||
929 | }, | ||
930 | }; | ||
931 | |||
932 | module_platform_driver(inno_hdmi_driver); | ||
933 | |||
934 | MODULE_AUTHOR("Zheng Yang <zhengyang@rock-chips.com>"); | ||
935 | MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>"); | ||
936 | MODULE_DESCRIPTION("Rockchip Specific INNO-HDMI Driver"); | ||
937 | MODULE_LICENSE("GPL v2"); | ||
938 | MODULE_ALIAS("platform:innohdmi-rockchip"); | ||