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> |
9 | #include <linux/i2c.h> |
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(bridge: 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(bridge: 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(objp: edid); |
241 | } else { |
242 | ret = drm_add_modes_noedid(connector, hdisplay: 1920, vdisplay: 1080); |
243 | drm_set_preferred_mode(connector, hpref: 1024, vpref: 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(dev: 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(encoder: bridge->encoder, bridge: priv->next_bridge, previous: bridge, |
278 | flags: 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(bridge: priv->next_bridge, cb: ch7033_hpd_event, |
294 | data: priv); |
295 | } |
296 | |
297 | drm_connector_helper_add(connector, |
298 | funcs: &ch7033_connector_helper_funcs); |
299 | ret = drm_connector_init_with_ddc(dev: bridge->dev, connector: &priv->connector, |
300 | funcs: &ch7033_connector_funcs, |
301 | connector_type: priv->next_bridge->type, |
302 | ddc: 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(connector: &priv->connector, encoder: 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(bridge: priv->next_bridge); |
317 | drm_connector_cleanup(connector: &priv->connector); |
318 | } |
319 | |
320 | static enum drm_mode_status ch7033_bridge_mode_valid(struct drm_bridge *bridge, |
321 | const struct drm_display_info *info, |
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(map: priv->regmap, reg: 0x03, val: 0x04); |
338 | regmap_update_bits(map: priv->regmap, reg: 0x52, mask: RESETDB, val: 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(map: priv->regmap, reg: 0x03, val: 0x04); |
346 | regmap_update_bits(map: priv->regmap, reg: 0x52, mask: RESETDB, val: 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(map: priv->regmap, reg: 0x03, val: 0x04); |
363 | |
364 | /* Turn everything off to set all the registers to their defaults. */ |
365 | regmap_write(map: priv->regmap, reg: 0x52, val: 0x00); |
366 | /* Bring I/O block up. */ |
367 | regmap_write(map: priv->regmap, reg: 0x52, val: RESETIB); |
368 | |
369 | /* |
370 | * Page 0 |
371 | */ |
372 | regmap_write(map: priv->regmap, reg: 0x03, val: 0x00); |
373 | |
374 | /* Bring up parts we need from the power down. */ |
375 | regmap_update_bits(map: priv->regmap, reg: 0x07, mask: DRI_PD | IO_PD, val: 0); |
376 | regmap_update_bits(map: priv->regmap, reg: 0x08, mask: DRI_PDDRI | PDDAC | PANEN, val: 0); |
377 | regmap_update_bits(map: priv->regmap, reg: 0x09, mask: DPD | GCKOFF | |
378 | HDMI_PD | VGA_PD, val: 0); |
379 | regmap_update_bits(map: priv->regmap, reg: 0x0a, mask: HD_DVIB, val: 0); |
380 | |
381 | /* Horizontal input timing. */ |
382 | regmap_write(map: priv->regmap, reg: 0x0b, val: (mode->htotal >> 8) << 3 | |
383 | (mode->hdisplay >> 8)); |
384 | regmap_write(map: priv->regmap, reg: 0x0c, val: mode->hdisplay); |
385 | regmap_write(map: priv->regmap, reg: 0x0d, val: mode->htotal); |
386 | regmap_write(map: priv->regmap, reg: 0x0e, val: (hsynclen >> 8) << 3 | |
387 | (hbporch >> 8)); |
388 | regmap_write(map: priv->regmap, reg: 0x0f, val: hbporch); |
389 | regmap_write(map: priv->regmap, reg: 0x10, val: hsynclen); |
390 | |
391 | /* Vertical input timing. */ |
392 | regmap_write(map: priv->regmap, reg: 0x11, val: (mode->vtotal >> 8) << 3 | |
393 | (mode->vdisplay >> 8)); |
394 | regmap_write(map: priv->regmap, reg: 0x12, val: mode->vdisplay); |
395 | regmap_write(map: priv->regmap, reg: 0x13, val: mode->vtotal); |
396 | regmap_write(map: priv->regmap, reg: 0x14, val: ((vsynclen >> 8) << 3) | |
397 | (vbporch >> 8)); |
398 | regmap_write(map: priv->regmap, reg: 0x15, val: vbporch); |
399 | regmap_write(map: priv->regmap, reg: 0x16, val: vsynclen); |
400 | |
401 | /* Input color swap. */ |
402 | regmap_update_bits(map: priv->regmap, reg: 0x18, mask: SWAP, val: BYTE_SWAP_BGR); |
403 | |
404 | /* Input clock and sync polarity. */ |
405 | regmap_update_bits(map: priv->regmap, reg: 0x19, mask: 0x1, val: mode->clock >> 16); |
406 | regmap_update_bits(map: priv->regmap, reg: 0x19, mask: HPO_I | VPO_I | GCLKFREQ, |
407 | val: (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(map: priv->regmap, reg: 0x1a, val: mode->clock >> 8); |
411 | regmap_write(map: priv->regmap, reg: 0x1b, val: mode->clock); |
412 | |
413 | /* Horizontal output timing. */ |
414 | regmap_write(map: priv->regmap, reg: 0x1f, val: (mode->htotal >> 8) << 3 | |
415 | (mode->hdisplay >> 8)); |
416 | regmap_write(map: priv->regmap, reg: 0x20, val: mode->hdisplay); |
417 | regmap_write(map: priv->regmap, reg: 0x21, val: mode->htotal); |
418 | |
419 | /* Vertical output timing. */ |
420 | regmap_write(map: priv->regmap, reg: 0x25, val: (mode->vtotal >> 8) << 3 | |
421 | (mode->vdisplay >> 8)); |
422 | regmap_write(map: priv->regmap, reg: 0x26, val: mode->vdisplay); |
423 | regmap_write(map: priv->regmap, reg: 0x27, val: mode->vtotal); |
424 | |
425 | /* VGA channel bypass */ |
426 | regmap_update_bits(map: priv->regmap, reg: 0x2b, mask: VFMT, val: 9); |
427 | |
428 | /* Output sync polarity. */ |
429 | regmap_update_bits(map: priv->regmap, reg: 0x2e, mask: HPO_O | VPO_O, |
430 | val: (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(map: priv->regmap, reg: 0x54, mask: HWO_HDMI_HI | HOO_HDMI_HI, |
435 | val: (hsynclen >> 8) << 3 | |
436 | (hbporch >> 8)); |
437 | regmap_write(map: priv->regmap, reg: 0x55, val: hbporch); |
438 | regmap_write(map: priv->regmap, reg: 0x56, val: hsynclen); |
439 | |
440 | /* HDMI vertical output timing. */ |
441 | regmap_update_bits(map: priv->regmap, reg: 0x57, mask: VWO_HDMI_HI | VOO_HDMI_HI, |
442 | val: (vsynclen >> 8) << 3 | |
443 | (vbporch >> 8)); |
444 | regmap_write(map: priv->regmap, reg: 0x58, val: vbporch); |
445 | regmap_write(map: priv->regmap, reg: 0x59, val: vsynclen); |
446 | |
447 | /* Pick HDMI, not LVDS. */ |
448 | regmap_update_bits(map: priv->regmap, reg: 0x7e, mask: HDMI_LVDS_SEL, val: HDMI_LVDS_SEL); |
449 | |
450 | /* |
451 | * Page 1 |
452 | */ |
453 | regmap_write(map: priv->regmap, reg: 0x03, val: 0x01); |
454 | |
455 | /* No idea what these do, but VGA is wobbly and blinky without them. */ |
456 | regmap_update_bits(map: priv->regmap, reg: 0x07, mask: CKINV, val: CKINV); |
457 | regmap_update_bits(map: priv->regmap, reg: 0x08, mask: DISPON, val: DISPON); |
458 | |
459 | /* DRI PLL */ |
460 | regmap_update_bits(map: priv->regmap, reg: 0x0c, mask: DRI_PLL_DIVSEL, val: DRI_PLL_DIVSEL); |
461 | if (mode->clock <= 40000) { |
462 | regmap_update_bits(map: priv->regmap, reg: 0x0c, mask: DRI_PLL_N1_1 | |
463 | DRI_PLL_N1_0 | |
464 | DRI_PLL_N3_1 | |
465 | DRI_PLL_N3_0, |
466 | val: 0); |
467 | } else if (mode->clock < 80000) { |
468 | regmap_update_bits(map: priv->regmap, reg: 0x0c, mask: DRI_PLL_N1_1 | |
469 | DRI_PLL_N1_0 | |
470 | DRI_PLL_N3_1 | |
471 | DRI_PLL_N3_0, |
472 | val: DRI_PLL_N3_0 | |
473 | DRI_PLL_N1_0); |
474 | } else { |
475 | regmap_update_bits(map: priv->regmap, reg: 0x0c, mask: DRI_PLL_N1_1 | |
476 | DRI_PLL_N1_0 | |
477 | DRI_PLL_N3_1 | |
478 | DRI_PLL_N3_0, |
479 | val: DRI_PLL_N3_1 | |
480 | DRI_PLL_N1_1); |
481 | } |
482 | |
483 | /* This seems to be color calibration for VGA. */ |
484 | regmap_write(map: priv->regmap, reg: 0x64, val: 0x29); /* LSB Blue */ |
485 | regmap_write(map: priv->regmap, reg: 0x65, val: 0x29); /* LSB Green */ |
486 | regmap_write(map: priv->regmap, reg: 0x66, val: 0x29); /* LSB Red */ |
487 | regmap_write(map: priv->regmap, reg: 0x67, val: 0x00); /* MSB Blue */ |
488 | regmap_write(map: priv->regmap, reg: 0x68, val: 0x00); /* MSB Green */ |
489 | regmap_write(map: priv->regmap, reg: 0x69, val: 0x00); /* MSB Red */ |
490 | |
491 | regmap_update_bits(map: priv->regmap, reg: 0x6b, mask: DRI_PD_SER, val: 0x00); |
492 | regmap_update_bits(map: priv->regmap, reg: 0x6c, mask: DRI_PLL_PD, val: 0x00); |
493 | |
494 | /* |
495 | * Page 3 |
496 | */ |
497 | regmap_write(map: priv->regmap, reg: 0x03, val: 0x03); |
498 | |
499 | /* More bypasses and apparently another HDMI/LVDS selector. */ |
500 | regmap_update_bits(map: priv->regmap, reg: 0x28, mask: VGACLK_BP | HM_LV_SEL, |
501 | val: VGACLK_BP | HM_LV_SEL); |
502 | regmap_update_bits(map: priv->regmap, reg: 0x2a, mask: HDMICLK_BP | HDMI_BP, |
503 | val: HDMICLK_BP | HDMI_BP); |
504 | |
505 | /* |
506 | * Page 4 |
507 | */ |
508 | regmap_write(map: priv->regmap, reg: 0x03, val: 0x04); |
509 | |
510 | /* Output clock. */ |
511 | regmap_write(map: priv->regmap, reg: 0x10, val: mode->clock >> 16); |
512 | regmap_write(map: priv->regmap, reg: 0x11, val: mode->clock >> 8); |
513 | regmap_write(map: priv->regmap, reg: 0x12, val: 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 | |
531 | static int ch7033_probe(struct i2c_client *client) |
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, size: sizeof(*priv), GFP_KERNEL); |
539 | if (!priv) |
540 | return -ENOMEM; |
541 | |
542 | dev_set_drvdata(dev, data: priv); |
543 | |
544 | ret = drm_of_find_panel_or_bridge(np: dev->of_node, port: 1, endpoint: -1, NULL, |
545 | bridge: &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(ptr: priv->regmap)) { |
551 | dev_err(&client->dev, "regmap init failed\n" ); |
552 | return PTR_ERR(ptr: priv->regmap); |
553 | } |
554 | |
555 | ret = regmap_read(map: priv->regmap, reg: 0x00, val: &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(map: priv->regmap, reg: 0x03, val: 0x04); |
566 | ret = regmap_read(map: priv->regmap, reg: 0x51, val: &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(list: &priv->bridge.list); |
577 | priv->bridge.funcs = &ch7033_bridge_funcs; |
578 | priv->bridge.of_node = dev->of_node; |
579 | drm_bridge_add(bridge: &priv->bridge); |
580 | |
581 | dev_info(dev, "Chrontel CH7033 Video Encoder\n" ); |
582 | return 0; |
583 | } |
584 | |
585 | static void ch7033_remove(struct i2c_client *client) |
586 | { |
587 | struct device *dev = &client->dev; |
588 | struct ch7033_priv *priv = dev_get_drvdata(dev); |
589 | |
590 | drm_bridge_remove(bridge: &priv->bridge); |
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 = { |
606 | .probe = ch7033_probe, |
607 | .remove = ch7033_remove, |
608 | .driver = { |
609 | .name = "ch7033" , |
610 | .of_match_table = 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" ); |
620 | |