1 | /* |
2 | * Copyright (C) 2009 Francisco Jerez. |
3 | * All Rights Reserved. |
4 | * |
5 | * Permission is hereby granted, free of charge, to any person obtaining |
6 | * a copy of this software and associated documentation files (the |
7 | * "Software"), to deal in the Software without restriction, including |
8 | * without limitation the rights to use, copy, modify, merge, publish, |
9 | * distribute, sublicense, and/or sell copies of the Software, and to |
10 | * permit persons to whom the Software is furnished to do so, subject to |
11 | * the following conditions: |
12 | * |
13 | * The above copyright notice and this permission notice (including the |
14 | * next paragraph) shall be included in all copies or substantial |
15 | * portions of the Software. |
16 | * |
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
20 | * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE |
21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
24 | * |
25 | */ |
26 | |
27 | #include <linux/module.h> |
28 | |
29 | #include <drm/drm_crtc_helper.h> |
30 | |
31 | #include "ch7006_priv.h" |
32 | |
33 | /* DRM encoder functions */ |
34 | |
35 | static void ch7006_encoder_set_config(struct drm_encoder *encoder, |
36 | void *params) |
37 | { |
38 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
39 | |
40 | priv->params = *(struct ch7006_encoder_params *)params; |
41 | } |
42 | |
43 | static void ch7006_encoder_destroy(struct drm_encoder *encoder) |
44 | { |
45 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
46 | |
47 | drm_property_destroy(dev: encoder->dev, property: priv->scale_property); |
48 | |
49 | kfree(objp: priv); |
50 | to_encoder_slave(encoder)->slave_priv = NULL; |
51 | |
52 | drm_i2c_encoder_destroy(encoder); |
53 | } |
54 | |
55 | static void ch7006_encoder_dpms(struct drm_encoder *encoder, int mode) |
56 | { |
57 | struct i2c_client *client = drm_i2c_encoder_get_client(encoder); |
58 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
59 | struct ch7006_state *state = &priv->state; |
60 | |
61 | ch7006_dbg(client, "\n" ); |
62 | |
63 | if (mode == priv->last_dpms) |
64 | return; |
65 | priv->last_dpms = mode; |
66 | |
67 | ch7006_setup_power_state(encoder); |
68 | |
69 | ch7006_load_reg(client, state, CH7006_POWER); |
70 | } |
71 | |
72 | static void ch7006_encoder_save(struct drm_encoder *encoder) |
73 | { |
74 | struct i2c_client *client = drm_i2c_encoder_get_client(encoder); |
75 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
76 | |
77 | ch7006_dbg(client, "\n" ); |
78 | |
79 | ch7006_state_save(client, state: &priv->saved_state); |
80 | } |
81 | |
82 | static void ch7006_encoder_restore(struct drm_encoder *encoder) |
83 | { |
84 | struct i2c_client *client = drm_i2c_encoder_get_client(encoder); |
85 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
86 | |
87 | ch7006_dbg(client, "\n" ); |
88 | |
89 | ch7006_state_load(client, state: &priv->saved_state); |
90 | } |
91 | |
92 | static bool ch7006_encoder_mode_fixup(struct drm_encoder *encoder, |
93 | const struct drm_display_mode *mode, |
94 | struct drm_display_mode *adjusted_mode) |
95 | { |
96 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
97 | |
98 | /* The ch7006 is painfully picky with the input timings so no |
99 | * custom modes for now... */ |
100 | |
101 | priv->mode = ch7006_lookup_mode(encoder, drm_mode: mode); |
102 | |
103 | return !!priv->mode; |
104 | } |
105 | |
106 | static int ch7006_encoder_mode_valid(struct drm_encoder *encoder, |
107 | struct drm_display_mode *mode) |
108 | { |
109 | if (ch7006_lookup_mode(encoder, drm_mode: mode)) |
110 | return MODE_OK; |
111 | else |
112 | return MODE_BAD; |
113 | } |
114 | |
115 | static void ch7006_encoder_mode_set(struct drm_encoder *encoder, |
116 | struct drm_display_mode *drm_mode, |
117 | struct drm_display_mode *adjusted_mode) |
118 | { |
119 | struct i2c_client *client = drm_i2c_encoder_get_client(encoder); |
120 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
121 | struct ch7006_encoder_params *params = &priv->params; |
122 | struct ch7006_state *state = &priv->state; |
123 | uint8_t *regs = state->regs; |
124 | const struct ch7006_mode *mode = priv->mode; |
125 | const struct ch7006_tv_norm_info *norm = &ch7006_tv_norms[priv->norm]; |
126 | int start_active; |
127 | |
128 | ch7006_dbg(client, "\n" ); |
129 | |
130 | regs[CH7006_DISPMODE] = norm->dispmode | mode->dispmode; |
131 | regs[CH7006_BWIDTH] = 0; |
132 | regs[CH7006_INPUT_FORMAT] = bitf(CH7006_INPUT_FORMAT_FORMAT, |
133 | params->input_format); |
134 | |
135 | regs[CH7006_CLKMODE] = CH7006_CLKMODE_SUBC_LOCK |
136 | | bitf(CH7006_CLKMODE_XCM, params->xcm) |
137 | | bitf(CH7006_CLKMODE_PCM, params->pcm); |
138 | if (params->clock_mode) |
139 | regs[CH7006_CLKMODE] |= CH7006_CLKMODE_MASTER; |
140 | if (params->clock_edge) |
141 | regs[CH7006_CLKMODE] |= CH7006_CLKMODE_POS_EDGE; |
142 | |
143 | start_active = (drm_mode->htotal & ~0x7) - (drm_mode->hsync_start & ~0x7); |
144 | regs[CH7006_POV] = bitf(CH7006_POV_START_ACTIVE_8, start_active); |
145 | regs[CH7006_START_ACTIVE] = bitf(CH7006_START_ACTIVE_0, start_active); |
146 | |
147 | regs[CH7006_INPUT_SYNC] = 0; |
148 | if (params->sync_direction) |
149 | regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_OUTPUT; |
150 | if (params->sync_encoding) |
151 | regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_EMBEDDED; |
152 | if (drm_mode->flags & DRM_MODE_FLAG_PVSYNC) |
153 | regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_PVSYNC; |
154 | if (drm_mode->flags & DRM_MODE_FLAG_PHSYNC) |
155 | regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_PHSYNC; |
156 | |
157 | regs[CH7006_DETECT] = 0; |
158 | regs[CH7006_BCLKOUT] = 0; |
159 | |
160 | regs[CH7006_SUBC_INC3] = 0; |
161 | if (params->pout_level) |
162 | regs[CH7006_SUBC_INC3] |= CH7006_SUBC_INC3_POUT_3_3V; |
163 | |
164 | regs[CH7006_SUBC_INC4] = 0; |
165 | if (params->active_detect) |
166 | regs[CH7006_SUBC_INC4] |= CH7006_SUBC_INC4_DS_INPUT; |
167 | |
168 | regs[CH7006_PLL_CONTROL] = priv->saved_state.regs[CH7006_PLL_CONTROL]; |
169 | |
170 | ch7006_setup_levels(encoder); |
171 | ch7006_setup_subcarrier(encoder); |
172 | ch7006_setup_pll(encoder); |
173 | ch7006_setup_power_state(encoder); |
174 | ch7006_setup_properties(encoder); |
175 | |
176 | ch7006_state_load(client, state); |
177 | } |
178 | |
179 | static enum drm_connector_status ch7006_encoder_detect(struct drm_encoder *encoder, |
180 | struct drm_connector *connector) |
181 | { |
182 | struct i2c_client *client = drm_i2c_encoder_get_client(encoder); |
183 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
184 | struct ch7006_state *state = &priv->state; |
185 | int det; |
186 | |
187 | ch7006_dbg(client, "\n" ); |
188 | |
189 | ch7006_save_reg(client, state, CH7006_DETECT); |
190 | ch7006_save_reg(client, state, CH7006_POWER); |
191 | ch7006_save_reg(client, state, CH7006_CLKMODE); |
192 | |
193 | ch7006_write(client, CH7006_POWER, CH7006_POWER_RESET | |
194 | bitfs(CH7006_POWER_LEVEL, NORMAL)); |
195 | ch7006_write(client, CH7006_CLKMODE, CH7006_CLKMODE_MASTER); |
196 | |
197 | ch7006_write(client, CH7006_DETECT, CH7006_DETECT_SENSE); |
198 | |
199 | ch7006_write(client, CH7006_DETECT, val: 0); |
200 | |
201 | det = ch7006_read(client, CH7006_DETECT); |
202 | |
203 | ch7006_load_reg(client, state, CH7006_CLKMODE); |
204 | ch7006_load_reg(client, state, CH7006_POWER); |
205 | ch7006_load_reg(client, state, CH7006_DETECT); |
206 | |
207 | if ((det & (CH7006_DETECT_SVIDEO_Y_TEST| |
208 | CH7006_DETECT_SVIDEO_C_TEST| |
209 | CH7006_DETECT_CVBS_TEST)) == 0) |
210 | priv->subconnector = DRM_MODE_SUBCONNECTOR_SCART; |
211 | else if ((det & (CH7006_DETECT_SVIDEO_Y_TEST| |
212 | CH7006_DETECT_SVIDEO_C_TEST)) == 0) |
213 | priv->subconnector = DRM_MODE_SUBCONNECTOR_SVIDEO; |
214 | else if ((det & CH7006_DETECT_CVBS_TEST) == 0) |
215 | priv->subconnector = DRM_MODE_SUBCONNECTOR_Composite; |
216 | else |
217 | priv->subconnector = DRM_MODE_SUBCONNECTOR_Unknown; |
218 | |
219 | drm_object_property_set_value(obj: &connector->base, |
220 | property: encoder->dev->mode_config.tv_subconnector_property, |
221 | val: priv->subconnector); |
222 | |
223 | return priv->subconnector ? connector_status_connected : |
224 | connector_status_disconnected; |
225 | } |
226 | |
227 | static int ch7006_encoder_get_modes(struct drm_encoder *encoder, |
228 | struct drm_connector *connector) |
229 | { |
230 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
231 | const struct ch7006_mode *mode; |
232 | int n = 0; |
233 | |
234 | for (mode = ch7006_modes; mode->mode.clock; mode++) { |
235 | if (~mode->valid_scales & 1<<priv->scale || |
236 | ~mode->valid_norms & 1<<priv->norm) |
237 | continue; |
238 | |
239 | drm_mode_probed_add(connector, |
240 | mode: drm_mode_duplicate(dev: encoder->dev, mode: &mode->mode)); |
241 | |
242 | n++; |
243 | } |
244 | |
245 | return n; |
246 | } |
247 | |
248 | static int ch7006_encoder_create_resources(struct drm_encoder *encoder, |
249 | struct drm_connector *connector) |
250 | { |
251 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
252 | struct drm_device *dev = encoder->dev; |
253 | struct drm_mode_config *conf = &dev->mode_config; |
254 | |
255 | drm_mode_create_tv_properties_legacy(dev, num_modes: NUM_TV_NORMS, modes: ch7006_tv_norm_names); |
256 | |
257 | priv->scale_property = drm_property_create_range(dev, flags: 0, name: "scale" , min: 0, max: 2); |
258 | if (!priv->scale_property) |
259 | return -ENOMEM; |
260 | |
261 | drm_object_attach_property(obj: &connector->base, property: conf->tv_select_subconnector_property, |
262 | init_val: priv->select_subconnector); |
263 | drm_object_attach_property(obj: &connector->base, property: conf->tv_subconnector_property, |
264 | init_val: priv->subconnector); |
265 | drm_object_attach_property(obj: &connector->base, property: conf->tv_left_margin_property, |
266 | init_val: priv->hmargin); |
267 | drm_object_attach_property(obj: &connector->base, property: conf->tv_bottom_margin_property, |
268 | init_val: priv->vmargin); |
269 | drm_object_attach_property(obj: &connector->base, property: conf->legacy_tv_mode_property, |
270 | init_val: priv->norm); |
271 | drm_object_attach_property(obj: &connector->base, property: conf->tv_brightness_property, |
272 | init_val: priv->brightness); |
273 | drm_object_attach_property(obj: &connector->base, property: conf->tv_contrast_property, |
274 | init_val: priv->contrast); |
275 | drm_object_attach_property(obj: &connector->base, property: conf->tv_flicker_reduction_property, |
276 | init_val: priv->flicker); |
277 | drm_object_attach_property(obj: &connector->base, property: priv->scale_property, |
278 | init_val: priv->scale); |
279 | |
280 | return 0; |
281 | } |
282 | |
283 | static int ch7006_encoder_set_property(struct drm_encoder *encoder, |
284 | struct drm_connector *connector, |
285 | struct drm_property *property, |
286 | uint64_t val) |
287 | { |
288 | struct i2c_client *client = drm_i2c_encoder_get_client(encoder); |
289 | struct ch7006_priv *priv = to_ch7006_priv(encoder); |
290 | struct ch7006_state *state = &priv->state; |
291 | struct drm_mode_config *conf = &encoder->dev->mode_config; |
292 | struct drm_crtc *crtc = encoder->crtc; |
293 | bool modes_changed = false; |
294 | |
295 | ch7006_dbg(client, "\n" ); |
296 | |
297 | if (property == conf->tv_select_subconnector_property) { |
298 | priv->select_subconnector = val; |
299 | |
300 | ch7006_setup_power_state(encoder); |
301 | |
302 | ch7006_load_reg(client, state, CH7006_POWER); |
303 | |
304 | } else if (property == conf->tv_left_margin_property) { |
305 | priv->hmargin = val; |
306 | |
307 | ch7006_setup_properties(encoder); |
308 | |
309 | ch7006_load_reg(client, state, CH7006_POV); |
310 | ch7006_load_reg(client, state, CH7006_HPOS); |
311 | |
312 | } else if (property == conf->tv_bottom_margin_property) { |
313 | priv->vmargin = val; |
314 | |
315 | ch7006_setup_properties(encoder); |
316 | |
317 | ch7006_load_reg(client, state, CH7006_POV); |
318 | ch7006_load_reg(client, state, CH7006_VPOS); |
319 | |
320 | } else if (property == conf->legacy_tv_mode_property) { |
321 | if (connector->dpms != DRM_MODE_DPMS_OFF) |
322 | return -EINVAL; |
323 | |
324 | priv->norm = val; |
325 | |
326 | modes_changed = true; |
327 | |
328 | } else if (property == conf->tv_brightness_property) { |
329 | priv->brightness = val; |
330 | |
331 | ch7006_setup_levels(encoder); |
332 | |
333 | ch7006_load_reg(client, state, CH7006_BLACK_LEVEL); |
334 | |
335 | } else if (property == conf->tv_contrast_property) { |
336 | priv->contrast = val; |
337 | |
338 | ch7006_setup_properties(encoder); |
339 | |
340 | ch7006_load_reg(client, state, CH7006_CONTRAST); |
341 | |
342 | } else if (property == conf->tv_flicker_reduction_property) { |
343 | priv->flicker = val; |
344 | |
345 | ch7006_setup_properties(encoder); |
346 | |
347 | ch7006_load_reg(client, state, CH7006_FFILTER); |
348 | |
349 | } else if (property == priv->scale_property) { |
350 | if (connector->dpms != DRM_MODE_DPMS_OFF) |
351 | return -EINVAL; |
352 | |
353 | priv->scale = val; |
354 | |
355 | modes_changed = true; |
356 | |
357 | } else { |
358 | return -EINVAL; |
359 | } |
360 | |
361 | if (modes_changed) { |
362 | drm_helper_probe_single_connector_modes(connector, maxX: 0, maxY: 0); |
363 | |
364 | if (crtc) |
365 | drm_crtc_helper_set_mode(crtc, mode: &crtc->mode, |
366 | x: crtc->x, y: crtc->y, |
367 | old_fb: crtc->primary->fb); |
368 | } |
369 | |
370 | return 0; |
371 | } |
372 | |
373 | static const struct drm_encoder_slave_funcs ch7006_encoder_funcs = { |
374 | .set_config = ch7006_encoder_set_config, |
375 | .destroy = ch7006_encoder_destroy, |
376 | .dpms = ch7006_encoder_dpms, |
377 | .save = ch7006_encoder_save, |
378 | .restore = ch7006_encoder_restore, |
379 | .mode_fixup = ch7006_encoder_mode_fixup, |
380 | .mode_valid = ch7006_encoder_mode_valid, |
381 | .mode_set = ch7006_encoder_mode_set, |
382 | .detect = ch7006_encoder_detect, |
383 | .get_modes = ch7006_encoder_get_modes, |
384 | .create_resources = ch7006_encoder_create_resources, |
385 | .set_property = ch7006_encoder_set_property, |
386 | }; |
387 | |
388 | |
389 | /* I2C driver functions */ |
390 | |
391 | static int ch7006_probe(struct i2c_client *client) |
392 | { |
393 | uint8_t addr = CH7006_VERSION_ID; |
394 | uint8_t val; |
395 | int ret; |
396 | |
397 | ch7006_dbg(client, "\n" ); |
398 | |
399 | ret = i2c_master_send(client, buf: &addr, count: sizeof(addr)); |
400 | if (ret < 0) |
401 | goto fail; |
402 | |
403 | ret = i2c_master_recv(client, buf: &val, count: sizeof(val)); |
404 | if (ret < 0) |
405 | goto fail; |
406 | |
407 | ch7006_info(client, "Detected version ID: %x\n" , val); |
408 | |
409 | /* I don't know what this is for, but otherwise I get no |
410 | * signal. |
411 | */ |
412 | ch7006_write(client, addr: 0x3d, val: 0x0); |
413 | |
414 | return 0; |
415 | |
416 | fail: |
417 | ch7006_err(client, "Error %d reading version ID\n" , ret); |
418 | |
419 | return -ENODEV; |
420 | } |
421 | |
422 | static void ch7006_remove(struct i2c_client *client) |
423 | { |
424 | ch7006_dbg(client, "\n" ); |
425 | } |
426 | |
427 | static int ch7006_resume(struct device *dev) |
428 | { |
429 | struct i2c_client *client = to_i2c_client(dev); |
430 | |
431 | ch7006_dbg(client, "\n" ); |
432 | |
433 | ch7006_write(client, addr: 0x3d, val: 0x0); |
434 | |
435 | return 0; |
436 | } |
437 | |
438 | static int ch7006_encoder_init(struct i2c_client *client, |
439 | struct drm_device *dev, |
440 | struct drm_encoder_slave *encoder) |
441 | { |
442 | struct ch7006_priv *priv; |
443 | int i; |
444 | |
445 | ch7006_dbg(client, "\n" ); |
446 | |
447 | priv = kzalloc(size: sizeof(*priv), GFP_KERNEL); |
448 | if (!priv) |
449 | return -ENOMEM; |
450 | |
451 | encoder->slave_priv = priv; |
452 | encoder->slave_funcs = &ch7006_encoder_funcs; |
453 | |
454 | priv->norm = TV_NORM_PAL; |
455 | priv->select_subconnector = DRM_MODE_SUBCONNECTOR_Automatic; |
456 | priv->subconnector = DRM_MODE_SUBCONNECTOR_Unknown; |
457 | priv->scale = 1; |
458 | priv->contrast = 50; |
459 | priv->brightness = 50; |
460 | priv->flicker = 50; |
461 | priv->hmargin = 50; |
462 | priv->vmargin = 50; |
463 | priv->last_dpms = -1; |
464 | priv->chip_version = ch7006_read(client, CH7006_VERSION_ID); |
465 | |
466 | if (ch7006_tv_norm) { |
467 | for (i = 0; i < NUM_TV_NORMS; i++) { |
468 | if (!strcmp(ch7006_tv_norm_names[i], ch7006_tv_norm)) { |
469 | priv->norm = i; |
470 | break; |
471 | } |
472 | } |
473 | |
474 | if (i == NUM_TV_NORMS) |
475 | ch7006_err(client, "Invalid TV norm setting \"%s\".\n" , |
476 | ch7006_tv_norm); |
477 | } |
478 | |
479 | if (ch7006_scale >= 0 && ch7006_scale <= 2) |
480 | priv->scale = ch7006_scale; |
481 | else |
482 | ch7006_err(client, "Invalid scale setting \"%d\".\n" , |
483 | ch7006_scale); |
484 | |
485 | return 0; |
486 | } |
487 | |
488 | static const struct i2c_device_id ch7006_ids[] = { |
489 | { "ch7006" , 0 }, |
490 | { } |
491 | }; |
492 | MODULE_DEVICE_TABLE(i2c, ch7006_ids); |
493 | |
494 | static const struct dev_pm_ops ch7006_pm_ops = { |
495 | .resume = ch7006_resume, |
496 | }; |
497 | |
498 | static struct drm_i2c_encoder_driver ch7006_driver = { |
499 | .i2c_driver = { |
500 | .probe = ch7006_probe, |
501 | .remove = ch7006_remove, |
502 | |
503 | .driver = { |
504 | .name = "ch7006" , |
505 | .pm = &ch7006_pm_ops, |
506 | }, |
507 | |
508 | .id_table = ch7006_ids, |
509 | }, |
510 | |
511 | .encoder_init = ch7006_encoder_init, |
512 | }; |
513 | |
514 | |
515 | /* Module initialization */ |
516 | |
517 | static int __init ch7006_init(void) |
518 | { |
519 | return drm_i2c_encoder_register(THIS_MODULE, driver: &ch7006_driver); |
520 | } |
521 | |
522 | static void __exit ch7006_exit(void) |
523 | { |
524 | drm_i2c_encoder_unregister(driver: &ch7006_driver); |
525 | } |
526 | |
527 | int ch7006_debug; |
528 | module_param_named(debug, ch7006_debug, int, 0600); |
529 | MODULE_PARM_DESC(debug, "Enable debug output." ); |
530 | |
531 | char *ch7006_tv_norm; |
532 | module_param_named(tv_norm, ch7006_tv_norm, charp, 0600); |
533 | MODULE_PARM_DESC(tv_norm, "Default TV norm.\n" |
534 | "\t\tSupported: PAL, PAL-M, PAL-N, PAL-Nc, PAL-60, NTSC-M, NTSC-J.\n" |
535 | "\t\tDefault: PAL" ); |
536 | |
537 | int ch7006_scale = 1; |
538 | module_param_named(scale, ch7006_scale, int, 0600); |
539 | MODULE_PARM_DESC(scale, "Default scale.\n" |
540 | "\t\tSupported: 0 -> Select video modes with a higher blanking ratio.\n" |
541 | "\t\t\t1 -> Select default video modes.\n" |
542 | "\t\t\t2 -> Select video modes with a lower blanking ratio." ); |
543 | |
544 | MODULE_AUTHOR("Francisco Jerez <currojerez@riseup.net>" ); |
545 | MODULE_DESCRIPTION("Chrontel ch7006 TV encoder driver" ); |
546 | MODULE_LICENSE("GPL and additional rights" ); |
547 | |
548 | module_init(ch7006_init); |
549 | module_exit(ch7006_exit); |
550 | |