Commit | Line | Data |
---|---|---|
e7f12054 LR |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Chrontel CH7033 Video Encoder Driver | |
4 | * | |
5 | * Copyright (C) 2019,2020 Lubomir Rintel | |
6 | */ | |
7 | ||
8 | #include <linux/gpio/consumer.h> | |
a204f974 | 9 | #include <linux/i2c.h> |
e7f12054 LR |
10 | #include <linux/module.h> |
11 | #include <linux/regmap.h> | |
12 | ||
13 | #include <drm/drm_atomic_helper.h> | |
14 | #include <drm/drm_bridge.h> | |
15 | #include <drm/drm_edid.h> | |
16 | #include <drm/drm_of.h> | |
17 | #include <drm/drm_print.h> | |
18 | #include <drm/drm_probe_helper.h> | |
19 | ||
20 | /* Page 0, Register 0x07 */ | |
21 | enum { | |
22 | DRI_PD = BIT(3), | |
23 | IO_PD = BIT(5), | |
24 | }; | |
25 | ||
26 | /* Page 0, Register 0x08 */ | |
27 | enum { | |
28 | DRI_PDDRI = GENMASK(7, 4), | |
29 | PDDAC = GENMASK(3, 1), | |
30 | PANEN = BIT(0), | |
31 | }; | |
32 | ||
33 | /* Page 0, Register 0x09 */ | |
34 | enum { | |
35 | DPD = BIT(7), | |
36 | GCKOFF = BIT(6), | |
37 | TV_BP = BIT(5), | |
38 | SCLPD = BIT(4), | |
39 | SDPD = BIT(3), | |
40 | VGA_PD = BIT(2), | |
41 | HDBKPD = BIT(1), | |
42 | HDMI_PD = BIT(0), | |
43 | }; | |
44 | ||
45 | /* Page 0, Register 0x0a */ | |
46 | enum { | |
47 | MEMINIT = BIT(7), | |
48 | MEMIDLE = BIT(6), | |
49 | MEMPD = BIT(5), | |
50 | STOP = BIT(4), | |
51 | LVDS_PD = BIT(3), | |
52 | HD_DVIB = BIT(2), | |
53 | HDCP_PD = BIT(1), | |
54 | MCU_PD = BIT(0), | |
55 | }; | |
56 | ||
57 | /* Page 0, Register 0x18 */ | |
58 | enum { | |
59 | IDF = GENMASK(7, 4), | |
60 | INTEN = BIT(3), | |
61 | SWAP = GENMASK(2, 0), | |
62 | }; | |
63 | ||
64 | enum { | |
65 | BYTE_SWAP_RGB = 0, | |
66 | BYTE_SWAP_RBG = 1, | |
67 | BYTE_SWAP_GRB = 2, | |
68 | BYTE_SWAP_GBR = 3, | |
69 | BYTE_SWAP_BRG = 4, | |
70 | BYTE_SWAP_BGR = 5, | |
71 | }; | |
72 | ||
73 | /* Page 0, Register 0x19 */ | |
74 | enum { | |
75 | HPO_I = BIT(5), | |
76 | VPO_I = BIT(4), | |
77 | DEPO_I = BIT(3), | |
78 | CRYS_EN = BIT(2), | |
79 | GCLKFREQ = GENMASK(2, 0), | |
80 | }; | |
81 | ||
82 | /* Page 0, Register 0x2e */ | |
83 | enum { | |
84 | HFLIP = BIT(7), | |
85 | VFLIP = BIT(6), | |
86 | DEPO_O = BIT(5), | |
87 | HPO_O = BIT(4), | |
88 | VPO_O = BIT(3), | |
89 | TE = GENMASK(2, 0), | |
90 | }; | |
91 | ||
92 | /* Page 0, Register 0x2b */ | |
93 | enum { | |
94 | SWAPS = GENMASK(7, 4), | |
95 | VFMT = GENMASK(3, 0), | |
96 | }; | |
97 | ||
98 | /* Page 0, Register 0x54 */ | |
99 | enum { | |
100 | COMP_BP = BIT(7), | |
101 | DAC_EN_T = BIT(6), | |
102 | HWO_HDMI_HI = GENMASK(5, 3), | |
103 | HOO_HDMI_HI = GENMASK(2, 0), | |
104 | }; | |
105 | ||
106 | /* Page 0, Register 0x57 */ | |
107 | enum { | |
108 | FLDSEN = BIT(7), | |
109 | VWO_HDMI_HI = GENMASK(5, 3), | |
110 | VOO_HDMI_HI = GENMASK(2, 0), | |
111 | }; | |
112 | ||
113 | /* Page 0, Register 0x7e */ | |
114 | enum { | |
115 | HDMI_LVDS_SEL = BIT(7), | |
116 | DE_GEN = BIT(6), | |
117 | PWM_INDEX_HI = BIT(5), | |
118 | USE_DE = BIT(4), | |
119 | R_INT = GENMASK(3, 0), | |
120 | }; | |
121 | ||
122 | /* Page 1, Register 0x07 */ | |
123 | enum { | |
124 | BPCKSEL = BIT(7), | |
125 | DRI_CMFB_EN = BIT(6), | |
126 | CEC_PUEN = BIT(5), | |
127 | CEC_T = BIT(3), | |
128 | CKINV = BIT(2), | |
129 | CK_TVINV = BIT(1), | |
130 | DRI_CKS2 = BIT(0), | |
131 | }; | |
132 | ||
133 | /* Page 1, Register 0x08 */ | |
134 | enum { | |
135 | DACG = BIT(6), | |
136 | DACKTST = BIT(5), | |
137 | DEDGEB = BIT(4), | |
138 | SYO = BIT(3), | |
139 | DRI_IT_LVDS = GENMASK(2, 1), | |
140 | DISPON = BIT(0), | |
141 | }; | |
142 | ||
143 | /* Page 1, Register 0x0c */ | |
144 | enum { | |
145 | DRI_PLL_CP = GENMASK(7, 6), | |
146 | DRI_PLL_DIVSEL = BIT(5), | |
147 | DRI_PLL_N1_1 = BIT(4), | |
148 | DRI_PLL_N1_0 = BIT(3), | |
149 | DRI_PLL_N3_1 = BIT(2), | |
150 | DRI_PLL_N3_0 = BIT(1), | |
151 | DRI_PLL_CKTSTEN = BIT(0), | |
152 | }; | |
153 | ||
154 | /* Page 1, Register 0x6b */ | |
155 | enum { | |
156 | VCO3CS = GENMASK(7, 6), | |
157 | ICPGBK2_0 = GENMASK(5, 3), | |
158 | DRI_VCO357SC = BIT(2), | |
159 | PDPLL2 = BIT(1), | |
160 | DRI_PD_SER = BIT(0), | |
161 | }; | |
162 | ||
163 | /* Page 1, Register 0x6c */ | |
164 | enum { | |
165 | PLL2N11 = GENMASK(7, 4), | |
166 | PLL2N5_4 = BIT(3), | |
167 | PLL2N5_TOP = BIT(2), | |
168 | DRI_PLL_PD = BIT(1), | |
169 | PD_I2CM = BIT(0), | |
170 | }; | |
171 | ||
172 | /* Page 3, Register 0x28 */ | |
173 | enum { | |
174 | DIFF_EN = GENMASK(7, 6), | |
175 | CORREC_EN = GENMASK(5, 4), | |
176 | VGACLK_BP = BIT(3), | |
177 | HM_LV_SEL = BIT(2), | |
178 | HD_VGA_SEL = BIT(1), | |
179 | }; | |
180 | ||
181 | /* Page 3, Register 0x2a */ | |
182 | enum { | |
183 | LVDSCLK_BP = BIT(7), | |
184 | HDTVCLK_BP = BIT(6), | |
185 | HDMICLK_BP = BIT(5), | |
186 | HDTV_BP = BIT(4), | |
187 | HDMI_BP = BIT(3), | |
188 | THRWL = GENMASK(2, 0), | |
189 | }; | |
190 | ||
191 | /* Page 4, Register 0x52 */ | |
192 | enum { | |
193 | PGM_ARSTB = BIT(7), | |
194 | MCU_ARSTB = BIT(6), | |
195 | MCU_RETB = BIT(2), | |
196 | RESETIB = BIT(1), | |
197 | RESETDB = BIT(0), | |
198 | }; | |
199 | ||
200 | struct ch7033_priv { | |
201 | struct regmap *regmap; | |
202 | struct drm_bridge *next_bridge; | |
203 | struct drm_bridge bridge; | |
204 | struct drm_connector connector; | |
205 | }; | |
206 | ||
207 | #define conn_to_ch7033_priv(x) \ | |
208 | container_of(x, struct ch7033_priv, connector) | |
209 | #define bridge_to_ch7033_priv(x) \ | |
210 | container_of(x, struct ch7033_priv, bridge) | |
211 | ||
212 | ||
213 | static enum drm_connector_status ch7033_connector_detect( | |
214 | struct drm_connector *connector, bool force) | |
215 | { | |
216 | struct ch7033_priv *priv = conn_to_ch7033_priv(connector); | |
217 | ||
218 | return drm_bridge_detect(priv->next_bridge); | |
219 | } | |
220 | ||
221 | static const struct drm_connector_funcs ch7033_connector_funcs = { | |
222 | .reset = drm_atomic_helper_connector_reset, | |
223 | .fill_modes = drm_helper_probe_single_connector_modes, | |
224 | .detect = ch7033_connector_detect, | |
225 | .destroy = drm_connector_cleanup, | |
226 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | |
227 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | |
228 | }; | |
229 | ||
230 | static int ch7033_connector_get_modes(struct drm_connector *connector) | |
231 | { | |
232 | struct ch7033_priv *priv = conn_to_ch7033_priv(connector); | |
233 | struct edid *edid; | |
234 | int ret; | |
235 | ||
236 | edid = drm_bridge_get_edid(priv->next_bridge, connector); | |
237 | drm_connector_update_edid_property(connector, edid); | |
238 | if (edid) { | |
239 | ret = drm_add_edid_modes(connector, edid); | |
240 | kfree(edid); | |
241 | } else { | |
242 | ret = drm_add_modes_noedid(connector, 1920, 1080); | |
243 | drm_set_preferred_mode(connector, 1024, 768); | |
244 | } | |
245 | ||
246 | return ret; | |
247 | } | |
248 | ||
249 | static struct drm_encoder *ch7033_connector_best_encoder( | |
250 | struct drm_connector *connector) | |
251 | { | |
252 | struct ch7033_priv *priv = conn_to_ch7033_priv(connector); | |
253 | ||
254 | return priv->bridge.encoder; | |
255 | } | |
256 | ||
257 | static const struct drm_connector_helper_funcs ch7033_connector_helper_funcs = { | |
258 | .get_modes = ch7033_connector_get_modes, | |
259 | .best_encoder = ch7033_connector_best_encoder, | |
260 | }; | |
261 | ||
262 | static void ch7033_hpd_event(void *arg, enum drm_connector_status status) | |
263 | { | |
264 | struct ch7033_priv *priv = arg; | |
265 | ||
266 | if (priv->bridge.dev) | |
267 | drm_helper_hpd_irq_event(priv->connector.dev); | |
268 | } | |
269 | ||
270 | static int ch7033_bridge_attach(struct drm_bridge *bridge, | |
271 | enum drm_bridge_attach_flags flags) | |
272 | { | |
273 | struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); | |
274 | struct drm_connector *connector = &priv->connector; | |
275 | int ret; | |
276 | ||
277 | ret = drm_bridge_attach(bridge->encoder, priv->next_bridge, bridge, | |
278 | DRM_BRIDGE_ATTACH_NO_CONNECTOR); | |
279 | if (ret) | |
280 | return ret; | |
281 | ||
282 | if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) | |
283 | return 0; | |
284 | ||
285 | if (priv->next_bridge->ops & DRM_BRIDGE_OP_DETECT) { | |
286 | connector->polled = DRM_CONNECTOR_POLL_HPD; | |
287 | } else { | |
288 | connector->polled = DRM_CONNECTOR_POLL_CONNECT | | |
289 | DRM_CONNECTOR_POLL_DISCONNECT; | |
290 | } | |
291 | ||
292 | if (priv->next_bridge->ops & DRM_BRIDGE_OP_HPD) { | |
293 | drm_bridge_hpd_enable(priv->next_bridge, ch7033_hpd_event, | |
294 | priv); | |
295 | } | |
296 | ||
297 | drm_connector_helper_add(connector, | |
298 | &ch7033_connector_helper_funcs); | |
299 | ret = drm_connector_init_with_ddc(bridge->dev, &priv->connector, | |
300 | &ch7033_connector_funcs, | |
301 | priv->next_bridge->type, | |
302 | priv->next_bridge->ddc); | |
303 | if (ret) { | |
304 | DRM_ERROR("Failed to initialize connector\n"); | |
305 | return ret; | |
306 | } | |
307 | ||
308 | return drm_connector_attach_encoder(&priv->connector, bridge->encoder); | |
309 | } | |
310 | ||
311 | static void ch7033_bridge_detach(struct drm_bridge *bridge) | |
312 | { | |
313 | struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); | |
314 | ||
315 | if (priv->next_bridge->ops & DRM_BRIDGE_OP_HPD) | |
316 | drm_bridge_hpd_disable(priv->next_bridge); | |
317 | drm_connector_cleanup(&priv->connector); | |
318 | } | |
319 | ||
320 | static enum drm_mode_status ch7033_bridge_mode_valid(struct drm_bridge *bridge, | |
12c683e1 | 321 | const struct drm_display_info *info, |
e7f12054 LR |
322 | const struct drm_display_mode *mode) |
323 | { | |
324 | if (mode->clock > 165000) | |
325 | return MODE_CLOCK_HIGH; | |
326 | if (mode->hdisplay >= 1920) | |
327 | return MODE_BAD_HVALUE; | |
328 | if (mode->vdisplay >= 1080) | |
329 | return MODE_BAD_VVALUE; | |
330 | return MODE_OK; | |
331 | } | |
332 | ||
333 | static void ch7033_bridge_disable(struct drm_bridge *bridge) | |
334 | { | |
335 | struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); | |
336 | ||
337 | regmap_write(priv->regmap, 0x03, 0x04); | |
338 | regmap_update_bits(priv->regmap, 0x52, RESETDB, 0x00); | |
339 | } | |
340 | ||
341 | static void ch7033_bridge_enable(struct drm_bridge *bridge) | |
342 | { | |
343 | struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); | |
344 | ||
345 | regmap_write(priv->regmap, 0x03, 0x04); | |
346 | regmap_update_bits(priv->regmap, 0x52, RESETDB, RESETDB); | |
347 | } | |
348 | ||
349 | static void ch7033_bridge_mode_set(struct drm_bridge *bridge, | |
350 | const struct drm_display_mode *mode, | |
351 | const struct drm_display_mode *adjusted_mode) | |
352 | { | |
353 | struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); | |
354 | int hbporch = mode->hsync_start - mode->hdisplay; | |
355 | int hsynclen = mode->hsync_end - mode->hsync_start; | |
356 | int vbporch = mode->vsync_start - mode->vdisplay; | |
357 | int vsynclen = mode->vsync_end - mode->vsync_start; | |
358 | ||
359 | /* | |
360 | * Page 4 | |
361 | */ | |
362 | regmap_write(priv->regmap, 0x03, 0x04); | |
363 | ||
364 | /* Turn everything off to set all the registers to their defaults. */ | |
365 | regmap_write(priv->regmap, 0x52, 0x00); | |
366 | /* Bring I/O block up. */ | |
367 | regmap_write(priv->regmap, 0x52, RESETIB); | |
368 | ||
369 | /* | |
370 | * Page 0 | |
371 | */ | |
372 | regmap_write(priv->regmap, 0x03, 0x00); | |
373 | ||
374 | /* Bring up parts we need from the power down. */ | |
375 | regmap_update_bits(priv->regmap, 0x07, DRI_PD | IO_PD, 0); | |
376 | regmap_update_bits(priv->regmap, 0x08, DRI_PDDRI | PDDAC | PANEN, 0); | |
377 | regmap_update_bits(priv->regmap, 0x09, DPD | GCKOFF | | |
378 | HDMI_PD | VGA_PD, 0); | |
379 | regmap_update_bits(priv->regmap, 0x0a, HD_DVIB, 0); | |
380 | ||
381 | /* Horizontal input timing. */ | |
382 | regmap_write(priv->regmap, 0x0b, (mode->htotal >> 8) << 3 | | |
383 | (mode->hdisplay >> 8)); | |
384 | regmap_write(priv->regmap, 0x0c, mode->hdisplay); | |
385 | regmap_write(priv->regmap, 0x0d, mode->htotal); | |
386 | regmap_write(priv->regmap, 0x0e, (hsynclen >> 8) << 3 | | |
387 | (hbporch >> 8)); | |
388 | regmap_write(priv->regmap, 0x0f, hbporch); | |
389 | regmap_write(priv->regmap, 0x10, hsynclen); | |
390 | ||
391 | /* Vertical input timing. */ | |
392 | regmap_write(priv->regmap, 0x11, (mode->vtotal >> 8) << 3 | | |
393 | (mode->vdisplay >> 8)); | |
394 | regmap_write(priv->regmap, 0x12, mode->vdisplay); | |
395 | regmap_write(priv->regmap, 0x13, mode->vtotal); | |
396 | regmap_write(priv->regmap, 0x14, ((vsynclen >> 8) << 3) | | |
397 | (vbporch >> 8)); | |
398 | regmap_write(priv->regmap, 0x15, vbporch); | |
399 | regmap_write(priv->regmap, 0x16, vsynclen); | |
400 | ||
401 | /* Input color swap. */ | |
402 | regmap_update_bits(priv->regmap, 0x18, SWAP, BYTE_SWAP_BGR); | |
403 | ||
404 | /* Input clock and sync polarity. */ | |
405 | regmap_update_bits(priv->regmap, 0x19, 0x1, mode->clock >> 16); | |
406 | regmap_update_bits(priv->regmap, 0x19, HPO_I | VPO_I | GCLKFREQ, | |
407 | (mode->flags & DRM_MODE_FLAG_PHSYNC) ? HPO_I : 0 | | |
408 | (mode->flags & DRM_MODE_FLAG_PVSYNC) ? VPO_I : 0 | | |
409 | mode->clock >> 16); | |
410 | regmap_write(priv->regmap, 0x1a, mode->clock >> 8); | |
411 | regmap_write(priv->regmap, 0x1b, mode->clock); | |
412 | ||
413 | /* Horizontal output timing. */ | |
414 | regmap_write(priv->regmap, 0x1f, (mode->htotal >> 8) << 3 | | |
415 | (mode->hdisplay >> 8)); | |
416 | regmap_write(priv->regmap, 0x20, mode->hdisplay); | |
417 | regmap_write(priv->regmap, 0x21, mode->htotal); | |
418 | ||
419 | /* Vertical output timing. */ | |
420 | regmap_write(priv->regmap, 0x25, (mode->vtotal >> 8) << 3 | | |
421 | (mode->vdisplay >> 8)); | |
422 | regmap_write(priv->regmap, 0x26, mode->vdisplay); | |
423 | regmap_write(priv->regmap, 0x27, mode->vtotal); | |
424 | ||
425 | /* VGA channel bypass */ | |
426 | regmap_update_bits(priv->regmap, 0x2b, VFMT, 9); | |
427 | ||
428 | /* Output sync polarity. */ | |
429 | regmap_update_bits(priv->regmap, 0x2e, HPO_O | VPO_O, | |
430 | (mode->flags & DRM_MODE_FLAG_PHSYNC) ? HPO_O : 0 | | |
431 | (mode->flags & DRM_MODE_FLAG_PVSYNC) ? VPO_O : 0); | |
432 | ||
433 | /* HDMI horizontal output timing. */ | |
434 | regmap_update_bits(priv->regmap, 0x54, HWO_HDMI_HI | HOO_HDMI_HI, | |
435 | (hsynclen >> 8) << 3 | | |
436 | (hbporch >> 8)); | |
437 | regmap_write(priv->regmap, 0x55, hbporch); | |
438 | regmap_write(priv->regmap, 0x56, hsynclen); | |
439 | ||
440 | /* HDMI vertical output timing. */ | |
441 | regmap_update_bits(priv->regmap, 0x57, VWO_HDMI_HI | VOO_HDMI_HI, | |
442 | (vsynclen >> 8) << 3 | | |
443 | (vbporch >> 8)); | |
444 | regmap_write(priv->regmap, 0x58, vbporch); | |
445 | regmap_write(priv->regmap, 0x59, vsynclen); | |
446 | ||
447 | /* Pick HDMI, not LVDS. */ | |
448 | regmap_update_bits(priv->regmap, 0x7e, HDMI_LVDS_SEL, HDMI_LVDS_SEL); | |
449 | ||
450 | /* | |
451 | * Page 1 | |
452 | */ | |
453 | regmap_write(priv->regmap, 0x03, 0x01); | |
454 | ||
455 | /* No idea what these do, but VGA is wobbly and blinky without them. */ | |
456 | regmap_update_bits(priv->regmap, 0x07, CKINV, CKINV); | |
457 | regmap_update_bits(priv->regmap, 0x08, DISPON, DISPON); | |
458 | ||
459 | /* DRI PLL */ | |
460 | regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_DIVSEL, DRI_PLL_DIVSEL); | |
461 | if (mode->clock <= 40000) { | |
462 | regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | | |
463 | DRI_PLL_N1_0 | | |
464 | DRI_PLL_N3_1 | | |
465 | DRI_PLL_N3_0, | |
466 | 0); | |
467 | } else if (mode->clock < 80000) { | |
468 | regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | | |
469 | DRI_PLL_N1_0 | | |
470 | DRI_PLL_N3_1 | | |
471 | DRI_PLL_N3_0, | |
472 | DRI_PLL_N3_0 | | |
473 | DRI_PLL_N1_0); | |
474 | } else { | |
475 | regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | | |
476 | DRI_PLL_N1_0 | | |
477 | DRI_PLL_N3_1 | | |
478 | DRI_PLL_N3_0, | |
479 | DRI_PLL_N3_1 | | |
480 | DRI_PLL_N1_1); | |
481 | } | |
482 | ||
483 | /* This seems to be color calibration for VGA. */ | |
484 | regmap_write(priv->regmap, 0x64, 0x29); /* LSB Blue */ | |
485 | regmap_write(priv->regmap, 0x65, 0x29); /* LSB Green */ | |
486 | regmap_write(priv->regmap, 0x66, 0x29); /* LSB Red */ | |
487 | regmap_write(priv->regmap, 0x67, 0x00); /* MSB Blue */ | |
488 | regmap_write(priv->regmap, 0x68, 0x00); /* MSB Green */ | |
489 | regmap_write(priv->regmap, 0x69, 0x00); /* MSB Red */ | |
490 | ||
491 | regmap_update_bits(priv->regmap, 0x6b, DRI_PD_SER, 0x00); | |
492 | regmap_update_bits(priv->regmap, 0x6c, DRI_PLL_PD, 0x00); | |
493 | ||
494 | /* | |
495 | * Page 3 | |
496 | */ | |
497 | regmap_write(priv->regmap, 0x03, 0x03); | |
498 | ||
499 | /* More bypasses and apparently another HDMI/LVDS selector. */ | |
500 | regmap_update_bits(priv->regmap, 0x28, VGACLK_BP | HM_LV_SEL, | |
501 | VGACLK_BP | HM_LV_SEL); | |
502 | regmap_update_bits(priv->regmap, 0x2a, HDMICLK_BP | HDMI_BP, | |
503 | HDMICLK_BP | HDMI_BP); | |
504 | ||
505 | /* | |
506 | * Page 4 | |
507 | */ | |
508 | regmap_write(priv->regmap, 0x03, 0x04); | |
509 | ||
510 | /* Output clock. */ | |
511 | regmap_write(priv->regmap, 0x10, mode->clock >> 16); | |
512 | regmap_write(priv->regmap, 0x11, mode->clock >> 8); | |
513 | regmap_write(priv->regmap, 0x12, mode->clock); | |
514 | } | |
515 | ||
516 | static const struct drm_bridge_funcs ch7033_bridge_funcs = { | |
517 | .attach = ch7033_bridge_attach, | |
518 | .detach = ch7033_bridge_detach, | |
519 | .mode_valid = ch7033_bridge_mode_valid, | |
520 | .disable = ch7033_bridge_disable, | |
521 | .enable = ch7033_bridge_enable, | |
522 | .mode_set = ch7033_bridge_mode_set, | |
523 | }; | |
524 | ||
525 | static const struct regmap_config ch7033_regmap_config = { | |
526 | .reg_bits = 8, | |
527 | .val_bits = 8, | |
528 | .max_register = 0x7f, | |
529 | }; | |
530 | ||
8dc6de28 | 531 | static int ch7033_probe(struct i2c_client *client) |
e7f12054 LR |
532 | { |
533 | struct device *dev = &client->dev; | |
534 | struct ch7033_priv *priv; | |
535 | unsigned int val; | |
536 | int ret; | |
537 | ||
538 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
539 | if (!priv) | |
540 | return -ENOMEM; | |
541 | ||
542 | dev_set_drvdata(dev, priv); | |
543 | ||
544 | ret = drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, | |
545 | &priv->next_bridge); | |
546 | if (ret) | |
547 | return ret; | |
548 | ||
549 | priv->regmap = devm_regmap_init_i2c(client, &ch7033_regmap_config); | |
550 | if (IS_ERR(priv->regmap)) { | |
551 | dev_err(&client->dev, "regmap init failed\n"); | |
552 | return PTR_ERR(priv->regmap); | |
553 | } | |
554 | ||
555 | ret = regmap_read(priv->regmap, 0x00, &val); | |
556 | if (ret < 0) { | |
557 | dev_err(&client->dev, "error reading the model id: %d\n", ret); | |
558 | return ret; | |
559 | } | |
560 | if ((val & 0xf7) != 0x56) { | |
561 | dev_err(&client->dev, "the device is not a ch7033\n"); | |
562 | return -ENODEV; | |
563 | } | |
564 | ||
565 | regmap_write(priv->regmap, 0x03, 0x04); | |
566 | ret = regmap_read(priv->regmap, 0x51, &val); | |
567 | if (ret < 0) { | |
568 | dev_err(&client->dev, "error reading the model id: %d\n", ret); | |
569 | return ret; | |
570 | } | |
571 | if ((val & 0x0f) != 3) { | |
572 | dev_err(&client->dev, "unknown revision %u\n", val); | |
573 | return -ENODEV; | |
574 | } | |
575 | ||
576 | INIT_LIST_HEAD(&priv->bridge.list); | |
577 | priv->bridge.funcs = &ch7033_bridge_funcs; | |
578 | priv->bridge.of_node = dev->of_node; | |
579 | drm_bridge_add(&priv->bridge); | |
580 | ||
581 | dev_info(dev, "Chrontel CH7033 Video Encoder\n"); | |
582 | return 0; | |
583 | } | |
584 | ||
ed5c2f5f | 585 | static void ch7033_remove(struct i2c_client *client) |
e7f12054 LR |
586 | { |
587 | struct device *dev = &client->dev; | |
588 | struct ch7033_priv *priv = dev_get_drvdata(dev); | |
589 | ||
590 | drm_bridge_remove(&priv->bridge); | |
e7f12054 LR |
591 | } |
592 | ||
593 | static const struct of_device_id ch7033_dt_ids[] = { | |
594 | { .compatible = "chrontel,ch7033", }, | |
595 | { } | |
596 | }; | |
597 | MODULE_DEVICE_TABLE(of, ch7033_dt_ids); | |
598 | ||
599 | static const struct i2c_device_id ch7033_ids[] = { | |
600 | { "ch7033", 0 }, | |
601 | { } | |
602 | }; | |
603 | MODULE_DEVICE_TABLE(i2c, ch7033_ids); | |
604 | ||
605 | static struct i2c_driver ch7033_driver = { | |
8dc6de28 | 606 | .probe_new = ch7033_probe, |
e7f12054 LR |
607 | .remove = ch7033_remove, |
608 | .driver = { | |
609 | .name = "ch7033", | |
610 | .of_match_table = of_match_ptr(ch7033_dt_ids), | |
611 | }, | |
612 | .id_table = ch7033_ids, | |
613 | }; | |
614 | ||
615 | module_i2c_driver(ch7033_driver); | |
616 | ||
617 | MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); | |
618 | MODULE_DESCRIPTION("Chrontel CH7033 Video Encoder Driver"); | |
619 | MODULE_LICENSE("GPL v2"); |