162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Copyright (C) 2009 Francisco Jerez. 362306a36Sopenharmony_ci * All Rights Reserved. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining 662306a36Sopenharmony_ci * a copy of this software and associated documentation files (the 762306a36Sopenharmony_ci * "Software"), to deal in the Software without restriction, including 862306a36Sopenharmony_ci * without limitation the rights to use, copy, modify, merge, publish, 962306a36Sopenharmony_ci * distribute, sublicense, and/or sell copies of the Software, and to 1062306a36Sopenharmony_ci * permit persons to whom the Software is furnished to do so, subject to 1162306a36Sopenharmony_ci * the following conditions: 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * The above copyright notice and this permission notice (including the 1462306a36Sopenharmony_ci * next paragraph) shall be included in all copies or substantial 1562306a36Sopenharmony_ci * portions of the Software. 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 1862306a36Sopenharmony_ci * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 1962306a36Sopenharmony_ci * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 2062306a36Sopenharmony_ci * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE 2162306a36Sopenharmony_ci * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 2262306a36Sopenharmony_ci * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 2362306a36Sopenharmony_ci * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci */ 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#include "nouveau_drv.h" 2862306a36Sopenharmony_ci#include "nouveau_reg.h" 2962306a36Sopenharmony_ci#include "nouveau_encoder.h" 3062306a36Sopenharmony_ci#include "nouveau_connector.h" 3162306a36Sopenharmony_ci#include "nouveau_crtc.h" 3262306a36Sopenharmony_ci#include "hw.h" 3362306a36Sopenharmony_ci#include <drm/drm_modeset_helper_vtables.h> 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#include <drm/i2c/ch7006.h> 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic struct nvkm_i2c_bus_probe nv04_tv_encoder_info[] = { 3862306a36Sopenharmony_ci { 3962306a36Sopenharmony_ci { 4062306a36Sopenharmony_ci I2C_BOARD_INFO("ch7006", 0x75), 4162306a36Sopenharmony_ci .platform_data = &(struct ch7006_encoder_params) { 4262306a36Sopenharmony_ci CH7006_FORMAT_RGB24m12I, CH7006_CLOCK_MASTER, 4362306a36Sopenharmony_ci 0, 0, 0, 4462306a36Sopenharmony_ci CH7006_SYNC_SLAVE, CH7006_SYNC_SEPARATED, 4562306a36Sopenharmony_ci CH7006_POUT_3_3V, CH7006_ACTIVE_HSYNC 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci }, 4862306a36Sopenharmony_ci 0 4962306a36Sopenharmony_ci }, 5062306a36Sopenharmony_ci { } 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ciint nv04_tv_identify(struct drm_device *dev, int i2c_index) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 5662306a36Sopenharmony_ci struct nvkm_i2c *i2c = nvxx_i2c(&drm->client.device); 5762306a36Sopenharmony_ci struct nvkm_i2c_bus *bus = nvkm_i2c_bus_find(i2c, i2c_index); 5862306a36Sopenharmony_ci if (bus) { 5962306a36Sopenharmony_ci return nvkm_i2c_bus_probe(bus, "TV encoder", 6062306a36Sopenharmony_ci nv04_tv_encoder_info, 6162306a36Sopenharmony_ci NULL, NULL); 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci return -ENODEV; 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci#define PLLSEL_TV_CRTC1_MASK \ 6862306a36Sopenharmony_ci (NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK1 \ 6962306a36Sopenharmony_ci | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK1) 7062306a36Sopenharmony_ci#define PLLSEL_TV_CRTC2_MASK \ 7162306a36Sopenharmony_ci (NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK2 \ 7262306a36Sopenharmony_ci | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK2) 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic void nv04_tv_dpms(struct drm_encoder *encoder, int mode) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 7762306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 7862306a36Sopenharmony_ci struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); 7962306a36Sopenharmony_ci struct nv04_mode_state *state = &nv04_display(dev)->mode_reg; 8062306a36Sopenharmony_ci uint8_t crtc1A; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci NV_DEBUG(drm, "Setting dpms mode %d on TV encoder (output %d)\n", 8362306a36Sopenharmony_ci mode, nv_encoder->dcb->index); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci state->pllsel &= ~(PLLSEL_TV_CRTC1_MASK | PLLSEL_TV_CRTC2_MASK); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (mode == DRM_MODE_DPMS_ON) { 8862306a36Sopenharmony_ci int head = nouveau_crtc(encoder->crtc)->index; 8962306a36Sopenharmony_ci crtc1A = NVReadVgaCrtc(dev, head, NV_CIO_CRE_RPC1_INDEX); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci state->pllsel |= head ? PLLSEL_TV_CRTC2_MASK : 9262306a36Sopenharmony_ci PLLSEL_TV_CRTC1_MASK; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci /* Inhibit hsync */ 9562306a36Sopenharmony_ci crtc1A |= 0x80; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci NVWriteVgaCrtc(dev, head, NV_CIO_CRE_RPC1_INDEX, crtc1A); 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci NVWriteRAMDAC(dev, 0, NV_PRAMDAC_PLL_COEFF_SELECT, state->pllsel); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci get_slave_funcs(encoder)->dpms(encoder, mode); 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic void nv04_tv_bind(struct drm_device *dev, int head, bool bind) 10662306a36Sopenharmony_ci{ 10762306a36Sopenharmony_ci struct nv04_crtc_reg *state = &nv04_display(dev)->mode_reg.crtc_reg[head]; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci state->tv_setup = 0; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci if (bind) 11262306a36Sopenharmony_ci state->CRTC[NV_CIO_CRE_49] |= 0x10; 11362306a36Sopenharmony_ci else 11462306a36Sopenharmony_ci state->CRTC[NV_CIO_CRE_49] &= ~0x10; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci NVWriteVgaCrtc(dev, head, NV_CIO_CRE_LCD__INDEX, 11762306a36Sopenharmony_ci state->CRTC[NV_CIO_CRE_LCD__INDEX]); 11862306a36Sopenharmony_ci NVWriteVgaCrtc(dev, head, NV_CIO_CRE_49, 11962306a36Sopenharmony_ci state->CRTC[NV_CIO_CRE_49]); 12062306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_SETUP, 12162306a36Sopenharmony_ci state->tv_setup); 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic void nv04_tv_prepare(struct drm_encoder *encoder) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 12762306a36Sopenharmony_ci int head = nouveau_crtc(encoder->crtc)->index; 12862306a36Sopenharmony_ci const struct drm_encoder_helper_funcs *helper = encoder->helper_private; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci helper->dpms(encoder, DRM_MODE_DPMS_OFF); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci nv04_dfp_disable(dev, head); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci if (nv_two_heads(dev)) 13562306a36Sopenharmony_ci nv04_tv_bind(dev, head ^ 1, false); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci nv04_tv_bind(dev, head, true); 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic void nv04_tv_mode_set(struct drm_encoder *encoder, 14162306a36Sopenharmony_ci struct drm_display_mode *mode, 14262306a36Sopenharmony_ci struct drm_display_mode *adjusted_mode) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 14562306a36Sopenharmony_ci struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); 14662306a36Sopenharmony_ci struct nv04_crtc_reg *regp = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index]; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci regp->tv_htotal = adjusted_mode->htotal; 14962306a36Sopenharmony_ci regp->tv_vtotal = adjusted_mode->vtotal; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci /* These delay the TV signals with respect to the VGA port, 15262306a36Sopenharmony_ci * they might be useful if we ever allow a CRTC to drive 15362306a36Sopenharmony_ci * multiple outputs. 15462306a36Sopenharmony_ci */ 15562306a36Sopenharmony_ci regp->tv_hskew = 1; 15662306a36Sopenharmony_ci regp->tv_hsync_delay = 1; 15762306a36Sopenharmony_ci regp->tv_hsync_delay2 = 64; 15862306a36Sopenharmony_ci regp->tv_vskew = 1; 15962306a36Sopenharmony_ci regp->tv_vsync_delay = 1; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci get_slave_funcs(encoder)->mode_set(encoder, mode, adjusted_mode); 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic void nv04_tv_commit(struct drm_encoder *encoder) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); 16762306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 16862306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 16962306a36Sopenharmony_ci struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); 17062306a36Sopenharmony_ci const struct drm_encoder_helper_funcs *helper = encoder->helper_private; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci helper->dpms(encoder, DRM_MODE_DPMS_ON); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci NV_DEBUG(drm, "Output %s is running on CRTC %d using output %c\n", 17562306a36Sopenharmony_ci nv04_encoder_get_connector(nv_encoder)->base.name, 17662306a36Sopenharmony_ci nv_crtc->index, '@' + ffs(nv_encoder->dcb->or)); 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic void nv04_tv_destroy(struct drm_encoder *encoder) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci get_slave_funcs(encoder)->destroy(encoder); 18262306a36Sopenharmony_ci drm_encoder_cleanup(encoder); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci kfree(encoder->helper_private); 18562306a36Sopenharmony_ci kfree(nouveau_encoder(encoder)); 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic const struct drm_encoder_funcs nv04_tv_funcs = { 18962306a36Sopenharmony_ci .destroy = nv04_tv_destroy, 19062306a36Sopenharmony_ci}; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic const struct drm_encoder_helper_funcs nv04_tv_helper_funcs = { 19362306a36Sopenharmony_ci .dpms = nv04_tv_dpms, 19462306a36Sopenharmony_ci .mode_fixup = drm_i2c_encoder_mode_fixup, 19562306a36Sopenharmony_ci .prepare = nv04_tv_prepare, 19662306a36Sopenharmony_ci .commit = nv04_tv_commit, 19762306a36Sopenharmony_ci .mode_set = nv04_tv_mode_set, 19862306a36Sopenharmony_ci .detect = drm_i2c_encoder_detect, 19962306a36Sopenharmony_ci}; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ciint 20262306a36Sopenharmony_cinv04_tv_create(struct drm_connector *connector, struct dcb_output *entry) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci struct nouveau_encoder *nv_encoder; 20562306a36Sopenharmony_ci struct drm_encoder *encoder; 20662306a36Sopenharmony_ci struct drm_device *dev = connector->dev; 20762306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 20862306a36Sopenharmony_ci struct nvkm_i2c *i2c = nvxx_i2c(&drm->client.device); 20962306a36Sopenharmony_ci struct nvkm_i2c_bus *bus = nvkm_i2c_bus_find(i2c, entry->i2c_index); 21062306a36Sopenharmony_ci int type, ret; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci /* Ensure that we can talk to this encoder */ 21362306a36Sopenharmony_ci type = nv04_tv_identify(dev, entry->i2c_index); 21462306a36Sopenharmony_ci if (type < 0) 21562306a36Sopenharmony_ci return type; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci /* Allocate the necessary memory */ 21862306a36Sopenharmony_ci nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); 21962306a36Sopenharmony_ci if (!nv_encoder) 22062306a36Sopenharmony_ci return -ENOMEM; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci /* Initialize the common members */ 22362306a36Sopenharmony_ci encoder = to_drm_encoder(nv_encoder); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci drm_encoder_init(dev, encoder, &nv04_tv_funcs, DRM_MODE_ENCODER_TVDAC, 22662306a36Sopenharmony_ci NULL); 22762306a36Sopenharmony_ci drm_encoder_helper_add(encoder, &nv04_tv_helper_funcs); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci nv_encoder->enc_save = drm_i2c_encoder_save; 23062306a36Sopenharmony_ci nv_encoder->enc_restore = drm_i2c_encoder_restore; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci encoder->possible_crtcs = entry->heads; 23362306a36Sopenharmony_ci encoder->possible_clones = 0; 23462306a36Sopenharmony_ci nv_encoder->dcb = entry; 23562306a36Sopenharmony_ci nv_encoder->or = ffs(entry->or) - 1; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci /* Run the slave-specific initialization */ 23862306a36Sopenharmony_ci ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), 23962306a36Sopenharmony_ci &bus->i2c, 24062306a36Sopenharmony_ci &nv04_tv_encoder_info[type].dev); 24162306a36Sopenharmony_ci if (ret < 0) 24262306a36Sopenharmony_ci goto fail_cleanup; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci /* Attach it to the specified connector. */ 24562306a36Sopenharmony_ci get_slave_funcs(encoder)->create_resources(encoder, connector); 24662306a36Sopenharmony_ci drm_connector_attach_encoder(connector, encoder); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci return 0; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cifail_cleanup: 25162306a36Sopenharmony_ci drm_encoder_cleanup(encoder); 25262306a36Sopenharmony_ci kfree(nv_encoder); 25362306a36Sopenharmony_ci return ret; 25462306a36Sopenharmony_ci} 255