Path: blob/master/drivers/gpu/drm/i2c/ch7006_drv.c
15112 views
/*1* Copyright (C) 2009 Francisco Jerez.2* All Rights Reserved.3*4* Permission is hereby granted, free of charge, to any person obtaining5* a copy of this software and associated documentation files (the6* "Software"), to deal in the Software without restriction, including7* without limitation the rights to use, copy, modify, merge, publish,8* distribute, sublicense, and/or sell copies of the Software, and to9* permit persons to whom the Software is furnished to do so, subject to10* the following conditions:11*12* The above copyright notice and this permission notice (including the13* next paragraph) shall be included in all copies or substantial14* portions of the Software.15*16* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,17* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF18* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.19* IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE20* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION21* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION22* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.23*24*/2526#include "ch7006_priv.h"2728/* DRM encoder functions */2930static void ch7006_encoder_set_config(struct drm_encoder *encoder,31void *params)32{33struct ch7006_priv *priv = to_ch7006_priv(encoder);3435priv->params = *(struct ch7006_encoder_params *)params;36}3738static void ch7006_encoder_destroy(struct drm_encoder *encoder)39{40struct ch7006_priv *priv = to_ch7006_priv(encoder);4142drm_property_destroy(encoder->dev, priv->scale_property);4344kfree(priv);45to_encoder_slave(encoder)->slave_priv = NULL;4647drm_i2c_encoder_destroy(encoder);48}4950static void ch7006_encoder_dpms(struct drm_encoder *encoder, int mode)51{52struct i2c_client *client = drm_i2c_encoder_get_client(encoder);53struct ch7006_priv *priv = to_ch7006_priv(encoder);54struct ch7006_state *state = &priv->state;5556ch7006_dbg(client, "\n");5758if (mode == priv->last_dpms)59return;60priv->last_dpms = mode;6162ch7006_setup_power_state(encoder);6364ch7006_load_reg(client, state, CH7006_POWER);65}6667static void ch7006_encoder_save(struct drm_encoder *encoder)68{69struct i2c_client *client = drm_i2c_encoder_get_client(encoder);70struct ch7006_priv *priv = to_ch7006_priv(encoder);7172ch7006_dbg(client, "\n");7374ch7006_state_save(client, &priv->saved_state);75}7677static void ch7006_encoder_restore(struct drm_encoder *encoder)78{79struct i2c_client *client = drm_i2c_encoder_get_client(encoder);80struct ch7006_priv *priv = to_ch7006_priv(encoder);8182ch7006_dbg(client, "\n");8384ch7006_state_load(client, &priv->saved_state);85}8687static bool ch7006_encoder_mode_fixup(struct drm_encoder *encoder,88struct drm_display_mode *mode,89struct drm_display_mode *adjusted_mode)90{91struct ch7006_priv *priv = to_ch7006_priv(encoder);9293/* The ch7006 is painfully picky with the input timings so no94* custom modes for now... */9596priv->mode = ch7006_lookup_mode(encoder, mode);9798return !!priv->mode;99}100101static int ch7006_encoder_mode_valid(struct drm_encoder *encoder,102struct drm_display_mode *mode)103{104if (ch7006_lookup_mode(encoder, mode))105return MODE_OK;106else107return MODE_BAD;108}109110static void ch7006_encoder_mode_set(struct drm_encoder *encoder,111struct drm_display_mode *drm_mode,112struct drm_display_mode *adjusted_mode)113{114struct i2c_client *client = drm_i2c_encoder_get_client(encoder);115struct ch7006_priv *priv = to_ch7006_priv(encoder);116struct ch7006_encoder_params *params = &priv->params;117struct ch7006_state *state = &priv->state;118uint8_t *regs = state->regs;119struct ch7006_mode *mode = priv->mode;120struct ch7006_tv_norm_info *norm = &ch7006_tv_norms[priv->norm];121int start_active;122123ch7006_dbg(client, "\n");124125regs[CH7006_DISPMODE] = norm->dispmode | mode->dispmode;126regs[CH7006_BWIDTH] = 0;127regs[CH7006_INPUT_FORMAT] = bitf(CH7006_INPUT_FORMAT_FORMAT,128params->input_format);129130regs[CH7006_CLKMODE] = CH7006_CLKMODE_SUBC_LOCK131| bitf(CH7006_CLKMODE_XCM, params->xcm)132| bitf(CH7006_CLKMODE_PCM, params->pcm);133if (params->clock_mode)134regs[CH7006_CLKMODE] |= CH7006_CLKMODE_MASTER;135if (params->clock_edge)136regs[CH7006_CLKMODE] |= CH7006_CLKMODE_POS_EDGE;137138start_active = (drm_mode->htotal & ~0x7) - (drm_mode->hsync_start & ~0x7);139regs[CH7006_POV] = bitf(CH7006_POV_START_ACTIVE_8, start_active);140regs[CH7006_START_ACTIVE] = bitf(CH7006_START_ACTIVE_0, start_active);141142regs[CH7006_INPUT_SYNC] = 0;143if (params->sync_direction)144regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_OUTPUT;145if (params->sync_encoding)146regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_EMBEDDED;147if (drm_mode->flags & DRM_MODE_FLAG_PVSYNC)148regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_PVSYNC;149if (drm_mode->flags & DRM_MODE_FLAG_PHSYNC)150regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_PHSYNC;151152regs[CH7006_DETECT] = 0;153regs[CH7006_BCLKOUT] = 0;154155regs[CH7006_SUBC_INC3] = 0;156if (params->pout_level)157regs[CH7006_SUBC_INC3] |= CH7006_SUBC_INC3_POUT_3_3V;158159regs[CH7006_SUBC_INC4] = 0;160if (params->active_detect)161regs[CH7006_SUBC_INC4] |= CH7006_SUBC_INC4_DS_INPUT;162163regs[CH7006_PLL_CONTROL] = priv->saved_state.regs[CH7006_PLL_CONTROL];164165ch7006_setup_levels(encoder);166ch7006_setup_subcarrier(encoder);167ch7006_setup_pll(encoder);168ch7006_setup_power_state(encoder);169ch7006_setup_properties(encoder);170171ch7006_state_load(client, state);172}173174static enum drm_connector_status ch7006_encoder_detect(struct drm_encoder *encoder,175struct drm_connector *connector)176{177struct i2c_client *client = drm_i2c_encoder_get_client(encoder);178struct ch7006_priv *priv = to_ch7006_priv(encoder);179struct ch7006_state *state = &priv->state;180int det;181182ch7006_dbg(client, "\n");183184ch7006_save_reg(client, state, CH7006_DETECT);185ch7006_save_reg(client, state, CH7006_POWER);186ch7006_save_reg(client, state, CH7006_CLKMODE);187188ch7006_write(client, CH7006_POWER, CH7006_POWER_RESET |189bitfs(CH7006_POWER_LEVEL, NORMAL));190ch7006_write(client, CH7006_CLKMODE, CH7006_CLKMODE_MASTER);191192ch7006_write(client, CH7006_DETECT, CH7006_DETECT_SENSE);193194ch7006_write(client, CH7006_DETECT, 0);195196det = ch7006_read(client, CH7006_DETECT);197198ch7006_load_reg(client, state, CH7006_CLKMODE);199ch7006_load_reg(client, state, CH7006_POWER);200ch7006_load_reg(client, state, CH7006_DETECT);201202if ((det & (CH7006_DETECT_SVIDEO_Y_TEST|203CH7006_DETECT_SVIDEO_C_TEST|204CH7006_DETECT_CVBS_TEST)) == 0)205priv->subconnector = DRM_MODE_SUBCONNECTOR_SCART;206else if ((det & (CH7006_DETECT_SVIDEO_Y_TEST|207CH7006_DETECT_SVIDEO_C_TEST)) == 0)208priv->subconnector = DRM_MODE_SUBCONNECTOR_SVIDEO;209else if ((det & CH7006_DETECT_CVBS_TEST) == 0)210priv->subconnector = DRM_MODE_SUBCONNECTOR_Composite;211else212priv->subconnector = DRM_MODE_SUBCONNECTOR_Unknown;213214drm_connector_property_set_value(connector,215encoder->dev->mode_config.tv_subconnector_property,216priv->subconnector);217218return priv->subconnector ? connector_status_connected :219connector_status_disconnected;220}221222static int ch7006_encoder_get_modes(struct drm_encoder *encoder,223struct drm_connector *connector)224{225struct ch7006_priv *priv = to_ch7006_priv(encoder);226struct ch7006_mode *mode;227int n = 0;228229for (mode = ch7006_modes; mode->mode.clock; mode++) {230if (~mode->valid_scales & 1<<priv->scale ||231~mode->valid_norms & 1<<priv->norm)232continue;233234drm_mode_probed_add(connector,235drm_mode_duplicate(encoder->dev, &mode->mode));236237n++;238}239240return n;241}242243static int ch7006_encoder_create_resources(struct drm_encoder *encoder,244struct drm_connector *connector)245{246struct ch7006_priv *priv = to_ch7006_priv(encoder);247struct drm_device *dev = encoder->dev;248struct drm_mode_config *conf = &dev->mode_config;249250drm_mode_create_tv_properties(dev, NUM_TV_NORMS, ch7006_tv_norm_names);251252priv->scale_property = drm_property_create(dev, DRM_MODE_PROP_RANGE,253"scale", 2);254priv->scale_property->values[0] = 0;255priv->scale_property->values[1] = 2;256257drm_connector_attach_property(connector, conf->tv_select_subconnector_property,258priv->select_subconnector);259drm_connector_attach_property(connector, conf->tv_subconnector_property,260priv->subconnector);261drm_connector_attach_property(connector, conf->tv_left_margin_property,262priv->hmargin);263drm_connector_attach_property(connector, conf->tv_bottom_margin_property,264priv->vmargin);265drm_connector_attach_property(connector, conf->tv_mode_property,266priv->norm);267drm_connector_attach_property(connector, conf->tv_brightness_property,268priv->brightness);269drm_connector_attach_property(connector, conf->tv_contrast_property,270priv->contrast);271drm_connector_attach_property(connector, conf->tv_flicker_reduction_property,272priv->flicker);273drm_connector_attach_property(connector, priv->scale_property,274priv->scale);275276return 0;277}278279static int ch7006_encoder_set_property(struct drm_encoder *encoder,280struct drm_connector *connector,281struct drm_property *property,282uint64_t val)283{284struct i2c_client *client = drm_i2c_encoder_get_client(encoder);285struct ch7006_priv *priv = to_ch7006_priv(encoder);286struct ch7006_state *state = &priv->state;287struct drm_mode_config *conf = &encoder->dev->mode_config;288struct drm_crtc *crtc = encoder->crtc;289bool modes_changed = false;290291ch7006_dbg(client, "\n");292293if (property == conf->tv_select_subconnector_property) {294priv->select_subconnector = val;295296ch7006_setup_power_state(encoder);297298ch7006_load_reg(client, state, CH7006_POWER);299300} else if (property == conf->tv_left_margin_property) {301priv->hmargin = val;302303ch7006_setup_properties(encoder);304305ch7006_load_reg(client, state, CH7006_POV);306ch7006_load_reg(client, state, CH7006_HPOS);307308} else if (property == conf->tv_bottom_margin_property) {309priv->vmargin = val;310311ch7006_setup_properties(encoder);312313ch7006_load_reg(client, state, CH7006_POV);314ch7006_load_reg(client, state, CH7006_VPOS);315316} else if (property == conf->tv_mode_property) {317if (connector->dpms != DRM_MODE_DPMS_OFF)318return -EINVAL;319320priv->norm = val;321322modes_changed = true;323324} else if (property == conf->tv_brightness_property) {325priv->brightness = val;326327ch7006_setup_levels(encoder);328329ch7006_load_reg(client, state, CH7006_BLACK_LEVEL);330331} else if (property == conf->tv_contrast_property) {332priv->contrast = val;333334ch7006_setup_properties(encoder);335336ch7006_load_reg(client, state, CH7006_CONTRAST);337338} else if (property == conf->tv_flicker_reduction_property) {339priv->flicker = val;340341ch7006_setup_properties(encoder);342343ch7006_load_reg(client, state, CH7006_FFILTER);344345} else if (property == priv->scale_property) {346if (connector->dpms != DRM_MODE_DPMS_OFF)347return -EINVAL;348349priv->scale = val;350351modes_changed = true;352353} else {354return -EINVAL;355}356357if (modes_changed) {358drm_helper_probe_single_connector_modes(connector, 0, 0);359360/* Disable the crtc to ensure a full modeset is361* performed whenever it's turned on again. */362if (crtc) {363struct drm_mode_set modeset = {364.crtc = crtc,365};366367crtc->funcs->set_config(&modeset);368}369}370371return 0;372}373374static struct drm_encoder_slave_funcs ch7006_encoder_funcs = {375.set_config = ch7006_encoder_set_config,376.destroy = ch7006_encoder_destroy,377.dpms = ch7006_encoder_dpms,378.save = ch7006_encoder_save,379.restore = ch7006_encoder_restore,380.mode_fixup = ch7006_encoder_mode_fixup,381.mode_valid = ch7006_encoder_mode_valid,382.mode_set = ch7006_encoder_mode_set,383.detect = ch7006_encoder_detect,384.get_modes = ch7006_encoder_get_modes,385.create_resources = ch7006_encoder_create_resources,386.set_property = ch7006_encoder_set_property,387};388389390/* I2C driver functions */391392static int ch7006_probe(struct i2c_client *client, const struct i2c_device_id *id)393{394uint8_t addr = CH7006_VERSION_ID;395uint8_t val;396int ret;397398ch7006_dbg(client, "\n");399400ret = i2c_master_send(client, &addr, sizeof(addr));401if (ret < 0)402goto fail;403404ret = i2c_master_recv(client, &val, sizeof(val));405if (ret < 0)406goto fail;407408ch7006_info(client, "Detected version ID: %x\n", val);409410/* I don't know what this is for, but otherwise I get no411* signal.412*/413ch7006_write(client, 0x3d, 0x0);414415return 0;416417fail:418ch7006_err(client, "Error %d reading version ID\n", ret);419420return -ENODEV;421}422423static int ch7006_remove(struct i2c_client *client)424{425ch7006_dbg(client, "\n");426427return 0;428}429430static int ch7006_suspend(struct i2c_client *client, pm_message_t mesg)431{432ch7006_dbg(client, "\n");433434return 0;435}436437static int ch7006_resume(struct i2c_client *client)438{439ch7006_dbg(client, "\n");440441ch7006_write(client, 0x3d, 0x0);442443return 0;444}445446static int ch7006_encoder_init(struct i2c_client *client,447struct drm_device *dev,448struct drm_encoder_slave *encoder)449{450struct ch7006_priv *priv;451int i;452453ch7006_dbg(client, "\n");454455priv = kzalloc(sizeof(*priv), GFP_KERNEL);456if (!priv)457return -ENOMEM;458459encoder->slave_priv = priv;460encoder->slave_funcs = &ch7006_encoder_funcs;461462priv->norm = TV_NORM_PAL;463priv->select_subconnector = DRM_MODE_SUBCONNECTOR_Automatic;464priv->subconnector = DRM_MODE_SUBCONNECTOR_Unknown;465priv->scale = 1;466priv->contrast = 50;467priv->brightness = 50;468priv->flicker = 50;469priv->hmargin = 50;470priv->vmargin = 50;471priv->last_dpms = -1;472priv->chip_version = ch7006_read(client, CH7006_VERSION_ID);473474if (ch7006_tv_norm) {475for (i = 0; i < NUM_TV_NORMS; i++) {476if (!strcmp(ch7006_tv_norm_names[i], ch7006_tv_norm)) {477priv->norm = i;478break;479}480}481482if (i == NUM_TV_NORMS)483ch7006_err(client, "Invalid TV norm setting \"%s\".\n",484ch7006_tv_norm);485}486487if (ch7006_scale >= 0 && ch7006_scale <= 2)488priv->scale = ch7006_scale;489else490ch7006_err(client, "Invalid scale setting \"%d\".\n",491ch7006_scale);492493return 0;494}495496static struct i2c_device_id ch7006_ids[] = {497{ "ch7006", 0 },498{ }499};500MODULE_DEVICE_TABLE(i2c, ch7006_ids);501502static struct drm_i2c_encoder_driver ch7006_driver = {503.i2c_driver = {504.probe = ch7006_probe,505.remove = ch7006_remove,506.suspend = ch7006_suspend,507.resume = ch7006_resume,508509.driver = {510.name = "ch7006",511},512513.id_table = ch7006_ids,514},515516.encoder_init = ch7006_encoder_init,517};518519520/* Module initialization */521522static int __init ch7006_init(void)523{524return drm_i2c_encoder_register(THIS_MODULE, &ch7006_driver);525}526527static void __exit ch7006_exit(void)528{529drm_i2c_encoder_unregister(&ch7006_driver);530}531532int ch7006_debug;533module_param_named(debug, ch7006_debug, int, 0600);534MODULE_PARM_DESC(debug, "Enable debug output.");535536char *ch7006_tv_norm;537module_param_named(tv_norm, ch7006_tv_norm, charp, 0600);538MODULE_PARM_DESC(tv_norm, "Default TV norm.\n"539"\t\tSupported: PAL, PAL-M, PAL-N, PAL-Nc, PAL-60, NTSC-M, NTSC-J.\n"540"\t\tDefault: PAL");541542int ch7006_scale = 1;543module_param_named(scale, ch7006_scale, int, 0600);544MODULE_PARM_DESC(scale, "Default scale.\n"545"\t\tSupported: 0 -> Select video modes with a higher blanking ratio.\n"546"\t\t\t1 -> Select default video modes.\n"547"\t\t\t2 -> Select video modes with a lower blanking ratio.");548549MODULE_AUTHOR("Francisco Jerez <[email protected]>");550MODULE_DESCRIPTION("Chrontel ch7006 TV encoder driver");551MODULE_LICENSE("GPL and additional rights");552553module_init(ch7006_init);554module_exit(ch7006_exit);555556557