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 <drm/drm_crtc_helper.h> 2862306a36Sopenharmony_ci#include <drm/drm_modeset_helper_vtables.h> 2962306a36Sopenharmony_ci#include <drm/drm_probe_helper.h> 3062306a36Sopenharmony_ci#include "nouveau_drv.h" 3162306a36Sopenharmony_ci#include "nouveau_reg.h" 3262306a36Sopenharmony_ci#include "nouveau_encoder.h" 3362306a36Sopenharmony_ci#include "nouveau_connector.h" 3462306a36Sopenharmony_ci#include "nouveau_crtc.h" 3562306a36Sopenharmony_ci#include "hw.h" 3662306a36Sopenharmony_ci#include "tvnv17.h" 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ciMODULE_PARM_DESC(tv_norm, "Default TV norm.\n" 3962306a36Sopenharmony_ci "\t\tSupported: PAL, PAL-M, PAL-N, PAL-Nc, NTSC-M, NTSC-J,\n" 4062306a36Sopenharmony_ci "\t\t\thd480i, hd480p, hd576i, hd576p, hd720p, hd1080i.\n" 4162306a36Sopenharmony_ci "\t\tDefault: PAL\n" 4262306a36Sopenharmony_ci "\t\t*NOTE* Ignored for cards with external TV encoders."); 4362306a36Sopenharmony_cistatic char *nouveau_tv_norm; 4462306a36Sopenharmony_cimodule_param_named(tv_norm, nouveau_tv_norm, charp, 0400); 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic uint32_t nv42_tv_sample_load(struct drm_encoder *encoder) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 4962306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 5062306a36Sopenharmony_ci struct nvkm_gpio *gpio = nvxx_gpio(&drm->client.device); 5162306a36Sopenharmony_ci uint32_t testval, regoffset = nv04_dac_output_offset(encoder); 5262306a36Sopenharmony_ci uint32_t gpio0, gpio1, fp_htotal, fp_hsync_start, fp_hsync_end, 5362306a36Sopenharmony_ci fp_control, test_ctrl, dacclk, ctv_14, ctv_1c, ctv_6c; 5462306a36Sopenharmony_ci uint32_t sample = 0; 5562306a36Sopenharmony_ci int head; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci#define RGB_TEST_DATA(r, g, b) (r << 0 | g << 10 | b << 20) 5862306a36Sopenharmony_ci testval = RGB_TEST_DATA(0x82, 0xeb, 0x82); 5962306a36Sopenharmony_ci if (drm->vbios.tvdactestval) 6062306a36Sopenharmony_ci testval = drm->vbios.tvdactestval; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci dacclk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset); 6362306a36Sopenharmony_ci head = (dacclk & 0x100) >> 8; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci /* Save the previous state. */ 6662306a36Sopenharmony_ci gpio1 = nvkm_gpio_get(gpio, 0, DCB_GPIO_TVDAC1, 0xff); 6762306a36Sopenharmony_ci gpio0 = nvkm_gpio_get(gpio, 0, DCB_GPIO_TVDAC0, 0xff); 6862306a36Sopenharmony_ci fp_htotal = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_HTOTAL); 6962306a36Sopenharmony_ci fp_hsync_start = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_START); 7062306a36Sopenharmony_ci fp_hsync_end = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_END); 7162306a36Sopenharmony_ci fp_control = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL); 7262306a36Sopenharmony_ci test_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset); 7362306a36Sopenharmony_ci ctv_1c = NVReadRAMDAC(dev, head, 0x680c1c); 7462306a36Sopenharmony_ci ctv_14 = NVReadRAMDAC(dev, head, 0x680c14); 7562306a36Sopenharmony_ci ctv_6c = NVReadRAMDAC(dev, head, 0x680c6c); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci /* Prepare the DAC for load detection. */ 7862306a36Sopenharmony_ci nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, true); 7962306a36Sopenharmony_ci nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC0, 0xff, true); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HTOTAL, 1343); 8262306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_START, 1047); 8362306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_END, 1183); 8462306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL, 8562306a36Sopenharmony_ci NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS | 8662306a36Sopenharmony_ci NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12 | 8762306a36Sopenharmony_ci NV_PRAMDAC_FP_TG_CONTROL_READ_PROG | 8862306a36Sopenharmony_ci NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS | 8962306a36Sopenharmony_ci NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset, 0); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, 9462306a36Sopenharmony_ci (dacclk & ~0xff) | 0x22); 9562306a36Sopenharmony_ci msleep(1); 9662306a36Sopenharmony_ci NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, 9762306a36Sopenharmony_ci (dacclk & ~0xff) | 0x21); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, 0x680c1c, 1 << 20); 10062306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, 0x680c14, 4 << 16); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci /* Sample pin 0x4 (usually S-video luma). */ 10362306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, 0x680c6c, testval >> 10 & 0x3ff); 10462306a36Sopenharmony_ci msleep(20); 10562306a36Sopenharmony_ci sample |= NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset) 10662306a36Sopenharmony_ci & 0x4 << 28; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci /* Sample the remaining pins. */ 10962306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, 0x680c6c, testval & 0x3ff); 11062306a36Sopenharmony_ci msleep(20); 11162306a36Sopenharmony_ci sample |= NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset) 11262306a36Sopenharmony_ci & 0xa << 28; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci /* Restore the previous state. */ 11562306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, 0x680c1c, ctv_1c); 11662306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, 0x680c14, ctv_14); 11762306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, 0x680c6c, ctv_6c); 11862306a36Sopenharmony_ci NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, dacclk); 11962306a36Sopenharmony_ci NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset, test_ctrl); 12062306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL, fp_control); 12162306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_END, fp_hsync_end); 12262306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_START, fp_hsync_start); 12362306a36Sopenharmony_ci NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HTOTAL, fp_htotal); 12462306a36Sopenharmony_ci nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, gpio1); 12562306a36Sopenharmony_ci nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC0, 0xff, gpio0); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci return sample; 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic bool 13162306a36Sopenharmony_ciget_tv_detect_quirks(struct drm_device *dev, uint32_t *pin_mask) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 13462306a36Sopenharmony_ci struct nvkm_device *device = nvxx_device(&drm->client.device); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (device->quirk && device->quirk->tv_pin_mask) { 13762306a36Sopenharmony_ci *pin_mask = device->quirk->tv_pin_mask; 13862306a36Sopenharmony_ci return false; 13962306a36Sopenharmony_ci } 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci return true; 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic enum drm_connector_status 14562306a36Sopenharmony_cinv17_tv_detect(struct drm_encoder *encoder, struct drm_connector *connector) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 14862306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 14962306a36Sopenharmony_ci struct drm_mode_config *conf = &dev->mode_config; 15062306a36Sopenharmony_ci struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); 15162306a36Sopenharmony_ci struct dcb_output *dcb = tv_enc->base.dcb; 15262306a36Sopenharmony_ci bool reliable = get_tv_detect_quirks(dev, &tv_enc->pin_mask); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci if (nv04_dac_in_use(encoder)) 15562306a36Sopenharmony_ci return connector_status_disconnected; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci if (reliable) { 15862306a36Sopenharmony_ci if (drm->client.device.info.chipset == 0x42 || 15962306a36Sopenharmony_ci drm->client.device.info.chipset == 0x43) 16062306a36Sopenharmony_ci tv_enc->pin_mask = 16162306a36Sopenharmony_ci nv42_tv_sample_load(encoder) >> 28 & 0xe; 16262306a36Sopenharmony_ci else 16362306a36Sopenharmony_ci tv_enc->pin_mask = 16462306a36Sopenharmony_ci nv17_dac_sample_load(encoder) >> 28 & 0xe; 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci switch (tv_enc->pin_mask) { 16862306a36Sopenharmony_ci case 0x2: 16962306a36Sopenharmony_ci case 0x4: 17062306a36Sopenharmony_ci tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Composite; 17162306a36Sopenharmony_ci break; 17262306a36Sopenharmony_ci case 0xc: 17362306a36Sopenharmony_ci tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_SVIDEO; 17462306a36Sopenharmony_ci break; 17562306a36Sopenharmony_ci case 0xe: 17662306a36Sopenharmony_ci if (dcb->tvconf.has_component_output) 17762306a36Sopenharmony_ci tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Component; 17862306a36Sopenharmony_ci else 17962306a36Sopenharmony_ci tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_SCART; 18062306a36Sopenharmony_ci break; 18162306a36Sopenharmony_ci default: 18262306a36Sopenharmony_ci tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Unknown; 18362306a36Sopenharmony_ci break; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci drm_object_property_set_value(&connector->base, 18762306a36Sopenharmony_ci conf->tv_subconnector_property, 18862306a36Sopenharmony_ci tv_enc->subconnector); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if (!reliable) { 19162306a36Sopenharmony_ci return connector_status_unknown; 19262306a36Sopenharmony_ci } else if (tv_enc->subconnector) { 19362306a36Sopenharmony_ci NV_INFO(drm, "Load detected on output %c\n", 19462306a36Sopenharmony_ci '@' + ffs(dcb->or)); 19562306a36Sopenharmony_ci return connector_status_connected; 19662306a36Sopenharmony_ci } else { 19762306a36Sopenharmony_ci return connector_status_disconnected; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic int nv17_tv_get_ld_modes(struct drm_encoder *encoder, 20262306a36Sopenharmony_ci struct drm_connector *connector) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); 20562306a36Sopenharmony_ci const struct drm_display_mode *tv_mode; 20662306a36Sopenharmony_ci int n = 0; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci for (tv_mode = nv17_tv_modes; tv_mode->hdisplay; tv_mode++) { 20962306a36Sopenharmony_ci struct drm_display_mode *mode; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci mode = drm_mode_duplicate(encoder->dev, tv_mode); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci mode->clock = tv_norm->tv_enc_mode.vrefresh * 21462306a36Sopenharmony_ci mode->htotal / 1000 * 21562306a36Sopenharmony_ci mode->vtotal / 1000; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci if (mode->flags & DRM_MODE_FLAG_DBLSCAN) 21862306a36Sopenharmony_ci mode->clock *= 2; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci if (mode->hdisplay == tv_norm->tv_enc_mode.hdisplay && 22162306a36Sopenharmony_ci mode->vdisplay == tv_norm->tv_enc_mode.vdisplay) 22262306a36Sopenharmony_ci mode->type |= DRM_MODE_TYPE_PREFERRED; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci drm_mode_probed_add(connector, mode); 22562306a36Sopenharmony_ci n++; 22662306a36Sopenharmony_ci } 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci return n; 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic int nv17_tv_get_hd_modes(struct drm_encoder *encoder, 23262306a36Sopenharmony_ci struct drm_connector *connector) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); 23562306a36Sopenharmony_ci struct drm_display_mode *output_mode = &tv_norm->ctv_enc_mode.mode; 23662306a36Sopenharmony_ci struct drm_display_mode *mode; 23762306a36Sopenharmony_ci const struct { 23862306a36Sopenharmony_ci int hdisplay; 23962306a36Sopenharmony_ci int vdisplay; 24062306a36Sopenharmony_ci } modes[] = { 24162306a36Sopenharmony_ci { 640, 400 }, 24262306a36Sopenharmony_ci { 640, 480 }, 24362306a36Sopenharmony_ci { 720, 480 }, 24462306a36Sopenharmony_ci { 720, 576 }, 24562306a36Sopenharmony_ci { 800, 600 }, 24662306a36Sopenharmony_ci { 1024, 768 }, 24762306a36Sopenharmony_ci { 1280, 720 }, 24862306a36Sopenharmony_ci { 1280, 1024 }, 24962306a36Sopenharmony_ci { 1920, 1080 } 25062306a36Sopenharmony_ci }; 25162306a36Sopenharmony_ci int i, n = 0; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(modes); i++) { 25462306a36Sopenharmony_ci if (modes[i].hdisplay > output_mode->hdisplay || 25562306a36Sopenharmony_ci modes[i].vdisplay > output_mode->vdisplay) 25662306a36Sopenharmony_ci continue; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (modes[i].hdisplay == output_mode->hdisplay && 25962306a36Sopenharmony_ci modes[i].vdisplay == output_mode->vdisplay) { 26062306a36Sopenharmony_ci mode = drm_mode_duplicate(encoder->dev, output_mode); 26162306a36Sopenharmony_ci mode->type |= DRM_MODE_TYPE_PREFERRED; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci } else { 26462306a36Sopenharmony_ci mode = drm_cvt_mode(encoder->dev, modes[i].hdisplay, 26562306a36Sopenharmony_ci modes[i].vdisplay, 60, false, 26662306a36Sopenharmony_ci (output_mode->flags & 26762306a36Sopenharmony_ci DRM_MODE_FLAG_INTERLACE), false); 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci /* CVT modes are sometimes unsuitable... */ 27162306a36Sopenharmony_ci if (output_mode->hdisplay <= 720 27262306a36Sopenharmony_ci || output_mode->hdisplay >= 1920) { 27362306a36Sopenharmony_ci mode->htotal = output_mode->htotal; 27462306a36Sopenharmony_ci mode->hsync_start = (mode->hdisplay + (mode->htotal 27562306a36Sopenharmony_ci - mode->hdisplay) * 9 / 10) & ~7; 27662306a36Sopenharmony_ci mode->hsync_end = mode->hsync_start + 8; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (output_mode->vdisplay >= 1024) { 28062306a36Sopenharmony_ci mode->vtotal = output_mode->vtotal; 28162306a36Sopenharmony_ci mode->vsync_start = output_mode->vsync_start; 28262306a36Sopenharmony_ci mode->vsync_end = output_mode->vsync_end; 28362306a36Sopenharmony_ci } 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci mode->type |= DRM_MODE_TYPE_DRIVER; 28662306a36Sopenharmony_ci drm_mode_probed_add(connector, mode); 28762306a36Sopenharmony_ci n++; 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci return n; 29162306a36Sopenharmony_ci} 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_cistatic int nv17_tv_get_modes(struct drm_encoder *encoder, 29462306a36Sopenharmony_ci struct drm_connector *connector) 29562306a36Sopenharmony_ci{ 29662306a36Sopenharmony_ci struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci if (tv_norm->kind == CTV_ENC_MODE) 29962306a36Sopenharmony_ci return nv17_tv_get_hd_modes(encoder, connector); 30062306a36Sopenharmony_ci else 30162306a36Sopenharmony_ci return nv17_tv_get_ld_modes(encoder, connector); 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_cistatic int nv17_tv_mode_valid(struct drm_encoder *encoder, 30562306a36Sopenharmony_ci struct drm_display_mode *mode) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (tv_norm->kind == CTV_ENC_MODE) { 31062306a36Sopenharmony_ci struct drm_display_mode *output_mode = 31162306a36Sopenharmony_ci &tv_norm->ctv_enc_mode.mode; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci if (mode->clock > 400000) 31462306a36Sopenharmony_ci return MODE_CLOCK_HIGH; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci if (mode->hdisplay > output_mode->hdisplay || 31762306a36Sopenharmony_ci mode->vdisplay > output_mode->vdisplay) 31862306a36Sopenharmony_ci return MODE_BAD; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci if ((mode->flags & DRM_MODE_FLAG_INTERLACE) != 32162306a36Sopenharmony_ci (output_mode->flags & DRM_MODE_FLAG_INTERLACE)) 32262306a36Sopenharmony_ci return MODE_NO_INTERLACE; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci if (mode->flags & DRM_MODE_FLAG_DBLSCAN) 32562306a36Sopenharmony_ci return MODE_NO_DBLESCAN; 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci } else { 32862306a36Sopenharmony_ci const int vsync_tolerance = 600; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci if (mode->clock > 70000) 33162306a36Sopenharmony_ci return MODE_CLOCK_HIGH; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (abs(drm_mode_vrefresh(mode) * 1000 - 33462306a36Sopenharmony_ci tv_norm->tv_enc_mode.vrefresh) > vsync_tolerance) 33562306a36Sopenharmony_ci return MODE_VSYNC; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci /* The encoder takes care of the actual interlacing */ 33862306a36Sopenharmony_ci if (mode->flags & DRM_MODE_FLAG_INTERLACE) 33962306a36Sopenharmony_ci return MODE_NO_INTERLACE; 34062306a36Sopenharmony_ci } 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci return MODE_OK; 34362306a36Sopenharmony_ci} 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_cistatic bool nv17_tv_mode_fixup(struct drm_encoder *encoder, 34662306a36Sopenharmony_ci const struct drm_display_mode *mode, 34762306a36Sopenharmony_ci struct drm_display_mode *adjusted_mode) 34862306a36Sopenharmony_ci{ 34962306a36Sopenharmony_ci struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci if (nv04_dac_in_use(encoder)) 35262306a36Sopenharmony_ci return false; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci if (tv_norm->kind == CTV_ENC_MODE) 35562306a36Sopenharmony_ci adjusted_mode->clock = tv_norm->ctv_enc_mode.mode.clock; 35662306a36Sopenharmony_ci else 35762306a36Sopenharmony_ci adjusted_mode->clock = 90000; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci return true; 36062306a36Sopenharmony_ci} 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_cistatic void nv17_tv_dpms(struct drm_encoder *encoder, int mode) 36362306a36Sopenharmony_ci{ 36462306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 36562306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 36662306a36Sopenharmony_ci struct nvkm_gpio *gpio = nvxx_gpio(&drm->client.device); 36762306a36Sopenharmony_ci struct nv17_tv_state *regs = &to_tv_enc(encoder)->state; 36862306a36Sopenharmony_ci struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci if (nouveau_encoder(encoder)->last_dpms == mode) 37162306a36Sopenharmony_ci return; 37262306a36Sopenharmony_ci nouveau_encoder(encoder)->last_dpms = mode; 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci NV_INFO(drm, "Setting dpms mode %d on TV encoder (output %d)\n", 37562306a36Sopenharmony_ci mode, nouveau_encoder(encoder)->dcb->index); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci regs->ptv_200 &= ~1; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci if (tv_norm->kind == CTV_ENC_MODE) { 38062306a36Sopenharmony_ci nv04_dfp_update_fp_control(encoder, mode); 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci } else { 38362306a36Sopenharmony_ci nv04_dfp_update_fp_control(encoder, DRM_MODE_DPMS_OFF); 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci if (mode == DRM_MODE_DPMS_ON) 38662306a36Sopenharmony_ci regs->ptv_200 |= 1; 38762306a36Sopenharmony_ci } 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci nv_load_ptv(dev, regs, 200); 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, mode == DRM_MODE_DPMS_ON); 39262306a36Sopenharmony_ci nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC0, 0xff, mode == DRM_MODE_DPMS_ON); 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci nv04_dac_update_dacclk(encoder, mode == DRM_MODE_DPMS_ON); 39562306a36Sopenharmony_ci} 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_cistatic void nv17_tv_prepare(struct drm_encoder *encoder) 39862306a36Sopenharmony_ci{ 39962306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 40062306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 40162306a36Sopenharmony_ci const struct drm_encoder_helper_funcs *helper = encoder->helper_private; 40262306a36Sopenharmony_ci struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); 40362306a36Sopenharmony_ci int head = nouveau_crtc(encoder->crtc)->index; 40462306a36Sopenharmony_ci uint8_t *cr_lcd = &nv04_display(dev)->mode_reg.crtc_reg[head].CRTC[ 40562306a36Sopenharmony_ci NV_CIO_CRE_LCD__INDEX]; 40662306a36Sopenharmony_ci uint32_t dacclk_off = NV_PRAMDAC_DACCLK + 40762306a36Sopenharmony_ci nv04_dac_output_offset(encoder); 40862306a36Sopenharmony_ci uint32_t dacclk; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci helper->dpms(encoder, DRM_MODE_DPMS_OFF); 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci nv04_dfp_disable(dev, head); 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci /* Unbind any FP encoders from this head if we need the FP 41562306a36Sopenharmony_ci * stuff enabled. */ 41662306a36Sopenharmony_ci if (tv_norm->kind == CTV_ENC_MODE) { 41762306a36Sopenharmony_ci struct drm_encoder *enc; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci list_for_each_entry(enc, &dev->mode_config.encoder_list, head) { 42062306a36Sopenharmony_ci struct dcb_output *dcb = nouveau_encoder(enc)->dcb; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci if ((dcb->type == DCB_OUTPUT_TMDS || 42362306a36Sopenharmony_ci dcb->type == DCB_OUTPUT_LVDS) && 42462306a36Sopenharmony_ci !enc->crtc && 42562306a36Sopenharmony_ci nv04_dfp_get_bound_head(dev, dcb) == head) { 42662306a36Sopenharmony_ci nv04_dfp_bind_head(dev, dcb, head ^ 1, 42762306a36Sopenharmony_ci drm->vbios.fp.dual_link); 42862306a36Sopenharmony_ci } 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci if (tv_norm->kind == CTV_ENC_MODE) 43462306a36Sopenharmony_ci *cr_lcd |= 0x1 | (head ? 0x0 : 0x8); 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci /* Set the DACCLK register */ 43762306a36Sopenharmony_ci dacclk = (NVReadRAMDAC(dev, 0, dacclk_off) & ~0x30) | 0x1; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE) 44062306a36Sopenharmony_ci dacclk |= 0x1a << 16; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci if (tv_norm->kind == CTV_ENC_MODE) { 44362306a36Sopenharmony_ci dacclk |= 0x20; 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci if (head) 44662306a36Sopenharmony_ci dacclk |= 0x100; 44762306a36Sopenharmony_ci else 44862306a36Sopenharmony_ci dacclk &= ~0x100; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci } else { 45162306a36Sopenharmony_ci dacclk |= 0x10; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci } 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci NVWriteRAMDAC(dev, 0, dacclk_off, dacclk); 45662306a36Sopenharmony_ci} 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_cistatic void nv17_tv_mode_set(struct drm_encoder *encoder, 45962306a36Sopenharmony_ci struct drm_display_mode *drm_mode, 46062306a36Sopenharmony_ci struct drm_display_mode *adjusted_mode) 46162306a36Sopenharmony_ci{ 46262306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 46362306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 46462306a36Sopenharmony_ci int head = nouveau_crtc(encoder->crtc)->index; 46562306a36Sopenharmony_ci struct nv04_crtc_reg *regs = &nv04_display(dev)->mode_reg.crtc_reg[head]; 46662306a36Sopenharmony_ci struct nv17_tv_state *tv_regs = &to_tv_enc(encoder)->state; 46762306a36Sopenharmony_ci struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); 46862306a36Sopenharmony_ci int i; 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci regs->CRTC[NV_CIO_CRE_53] = 0x40; /* FP_HTIMING */ 47162306a36Sopenharmony_ci regs->CRTC[NV_CIO_CRE_54] = 0; /* FP_VTIMING */ 47262306a36Sopenharmony_ci regs->ramdac_630 = 0x2; /* turn off green mode (tv test pattern?) */ 47362306a36Sopenharmony_ci regs->tv_setup = 1; 47462306a36Sopenharmony_ci regs->ramdac_8c0 = 0x0; 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci if (tv_norm->kind == TV_ENC_MODE) { 47762306a36Sopenharmony_ci tv_regs->ptv_200 = 0x13111100; 47862306a36Sopenharmony_ci if (head) 47962306a36Sopenharmony_ci tv_regs->ptv_200 |= 0x10; 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci tv_regs->ptv_20c = 0x808010; 48262306a36Sopenharmony_ci tv_regs->ptv_304 = 0x2d00000; 48362306a36Sopenharmony_ci tv_regs->ptv_600 = 0x0; 48462306a36Sopenharmony_ci tv_regs->ptv_60c = 0x0; 48562306a36Sopenharmony_ci tv_regs->ptv_610 = 0x1e00000; 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci if (tv_norm->tv_enc_mode.vdisplay == 576) { 48862306a36Sopenharmony_ci tv_regs->ptv_508 = 0x1200000; 48962306a36Sopenharmony_ci tv_regs->ptv_614 = 0x33; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci } else if (tv_norm->tv_enc_mode.vdisplay == 480) { 49262306a36Sopenharmony_ci tv_regs->ptv_508 = 0xf00000; 49362306a36Sopenharmony_ci tv_regs->ptv_614 = 0x13; 49462306a36Sopenharmony_ci } 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_RANKINE) { 49762306a36Sopenharmony_ci tv_regs->ptv_500 = 0xe8e0; 49862306a36Sopenharmony_ci tv_regs->ptv_504 = 0x1710; 49962306a36Sopenharmony_ci tv_regs->ptv_604 = 0x0; 50062306a36Sopenharmony_ci tv_regs->ptv_608 = 0x0; 50162306a36Sopenharmony_ci } else { 50262306a36Sopenharmony_ci if (tv_norm->tv_enc_mode.vdisplay == 576) { 50362306a36Sopenharmony_ci tv_regs->ptv_604 = 0x20; 50462306a36Sopenharmony_ci tv_regs->ptv_608 = 0x10; 50562306a36Sopenharmony_ci tv_regs->ptv_500 = 0x19710; 50662306a36Sopenharmony_ci tv_regs->ptv_504 = 0x68f0; 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci } else if (tv_norm->tv_enc_mode.vdisplay == 480) { 50962306a36Sopenharmony_ci tv_regs->ptv_604 = 0x10; 51062306a36Sopenharmony_ci tv_regs->ptv_608 = 0x20; 51162306a36Sopenharmony_ci tv_regs->ptv_500 = 0x4b90; 51262306a36Sopenharmony_ci tv_regs->ptv_504 = 0x1b480; 51362306a36Sopenharmony_ci } 51462306a36Sopenharmony_ci } 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci for (i = 0; i < 0x40; i++) 51762306a36Sopenharmony_ci tv_regs->tv_enc[i] = tv_norm->tv_enc_mode.tv_enc[i]; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci } else { 52062306a36Sopenharmony_ci struct drm_display_mode *output_mode = 52162306a36Sopenharmony_ci &tv_norm->ctv_enc_mode.mode; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci /* The registers in PRAMDAC+0xc00 control some timings and CSC 52462306a36Sopenharmony_ci * parameters for the CTV encoder (It's only used for "HD" TV 52562306a36Sopenharmony_ci * modes, I don't think I have enough working to guess what 52662306a36Sopenharmony_ci * they exactly mean...), it's probably connected at the 52762306a36Sopenharmony_ci * output of the FP encoder, but it also needs the analog 52862306a36Sopenharmony_ci * encoder in its OR enabled and routed to the head it's 52962306a36Sopenharmony_ci * using. It's enabled with the DACCLK register, bits [5:4]. 53062306a36Sopenharmony_ci */ 53162306a36Sopenharmony_ci for (i = 0; i < 38; i++) 53262306a36Sopenharmony_ci regs->ctv_regs[i] = tv_norm->ctv_enc_mode.ctv_regs[i]; 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci regs->fp_horiz_regs[FP_DISPLAY_END] = output_mode->hdisplay - 1; 53562306a36Sopenharmony_ci regs->fp_horiz_regs[FP_TOTAL] = output_mode->htotal - 1; 53662306a36Sopenharmony_ci regs->fp_horiz_regs[FP_SYNC_START] = 53762306a36Sopenharmony_ci output_mode->hsync_start - 1; 53862306a36Sopenharmony_ci regs->fp_horiz_regs[FP_SYNC_END] = output_mode->hsync_end - 1; 53962306a36Sopenharmony_ci regs->fp_horiz_regs[FP_CRTC] = output_mode->hdisplay + 54062306a36Sopenharmony_ci max((output_mode->hdisplay-600)/40 - 1, 1); 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci regs->fp_vert_regs[FP_DISPLAY_END] = output_mode->vdisplay - 1; 54362306a36Sopenharmony_ci regs->fp_vert_regs[FP_TOTAL] = output_mode->vtotal - 1; 54462306a36Sopenharmony_ci regs->fp_vert_regs[FP_SYNC_START] = 54562306a36Sopenharmony_ci output_mode->vsync_start - 1; 54662306a36Sopenharmony_ci regs->fp_vert_regs[FP_SYNC_END] = output_mode->vsync_end - 1; 54762306a36Sopenharmony_ci regs->fp_vert_regs[FP_CRTC] = output_mode->vdisplay - 1; 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci regs->fp_control = NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS | 55062306a36Sopenharmony_ci NV_PRAMDAC_FP_TG_CONTROL_READ_PROG | 55162306a36Sopenharmony_ci NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci if (output_mode->flags & DRM_MODE_FLAG_PVSYNC) 55462306a36Sopenharmony_ci regs->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS; 55562306a36Sopenharmony_ci if (output_mode->flags & DRM_MODE_FLAG_PHSYNC) 55662306a36Sopenharmony_ci regs->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS; 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci regs->fp_debug_0 = NV_PRAMDAC_FP_DEBUG_0_YWEIGHT_ROUND | 55962306a36Sopenharmony_ci NV_PRAMDAC_FP_DEBUG_0_XWEIGHT_ROUND | 56062306a36Sopenharmony_ci NV_PRAMDAC_FP_DEBUG_0_YINTERP_BILINEAR | 56162306a36Sopenharmony_ci NV_PRAMDAC_FP_DEBUG_0_XINTERP_BILINEAR | 56262306a36Sopenharmony_ci NV_RAMDAC_FP_DEBUG_0_TMDS_ENABLED | 56362306a36Sopenharmony_ci NV_PRAMDAC_FP_DEBUG_0_YSCALE_ENABLE | 56462306a36Sopenharmony_ci NV_PRAMDAC_FP_DEBUG_0_XSCALE_ENABLE; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci regs->fp_debug_2 = 0; 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci regs->fp_margin_color = 0x801080; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci} 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_cistatic void nv17_tv_commit(struct drm_encoder *encoder) 57462306a36Sopenharmony_ci{ 57562306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 57662306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 57762306a36Sopenharmony_ci struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); 57862306a36Sopenharmony_ci struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); 57962306a36Sopenharmony_ci const struct drm_encoder_helper_funcs *helper = encoder->helper_private; 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci if (get_tv_norm(encoder)->kind == TV_ENC_MODE) { 58262306a36Sopenharmony_ci nv17_tv_update_rescaler(encoder); 58362306a36Sopenharmony_ci nv17_tv_update_properties(encoder); 58462306a36Sopenharmony_ci } else { 58562306a36Sopenharmony_ci nv17_ctv_update_rescaler(encoder); 58662306a36Sopenharmony_ci } 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci nv17_tv_state_load(dev, &to_tv_enc(encoder)->state); 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci /* This could use refinement for flatpanels, but it should work */ 59162306a36Sopenharmony_ci if (drm->client.device.info.chipset < 0x44) 59262306a36Sopenharmony_ci NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + 59362306a36Sopenharmony_ci nv04_dac_output_offset(encoder), 59462306a36Sopenharmony_ci 0xf0000000); 59562306a36Sopenharmony_ci else 59662306a36Sopenharmony_ci NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + 59762306a36Sopenharmony_ci nv04_dac_output_offset(encoder), 59862306a36Sopenharmony_ci 0x00100000); 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci helper->dpms(encoder, DRM_MODE_DPMS_ON); 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci NV_INFO(drm, "Output %s is running on CRTC %d using output %c\n", 60362306a36Sopenharmony_ci nv04_encoder_get_connector(nv_encoder)->base.name, 60462306a36Sopenharmony_ci nv_crtc->index, '@' + ffs(nv_encoder->dcb->or)); 60562306a36Sopenharmony_ci} 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_cistatic void nv17_tv_save(struct drm_encoder *encoder) 60862306a36Sopenharmony_ci{ 60962306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 61062306a36Sopenharmony_ci struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci nouveau_encoder(encoder)->restore.output = 61362306a36Sopenharmony_ci NVReadRAMDAC(dev, 0, 61462306a36Sopenharmony_ci NV_PRAMDAC_DACCLK + 61562306a36Sopenharmony_ci nv04_dac_output_offset(encoder)); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci nv17_tv_state_save(dev, &tv_enc->saved_state); 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci tv_enc->state.ptv_200 = tv_enc->saved_state.ptv_200; 62062306a36Sopenharmony_ci} 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_cistatic void nv17_tv_restore(struct drm_encoder *encoder) 62362306a36Sopenharmony_ci{ 62462306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + 62762306a36Sopenharmony_ci nv04_dac_output_offset(encoder), 62862306a36Sopenharmony_ci nouveau_encoder(encoder)->restore.output); 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci nv17_tv_state_load(dev, &to_tv_enc(encoder)->saved_state); 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci nouveau_encoder(encoder)->last_dpms = NV_DPMS_CLEARED; 63362306a36Sopenharmony_ci} 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_cistatic int nv17_tv_create_resources(struct drm_encoder *encoder, 63662306a36Sopenharmony_ci struct drm_connector *connector) 63762306a36Sopenharmony_ci{ 63862306a36Sopenharmony_ci struct drm_device *dev = encoder->dev; 63962306a36Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 64062306a36Sopenharmony_ci struct drm_mode_config *conf = &dev->mode_config; 64162306a36Sopenharmony_ci struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); 64262306a36Sopenharmony_ci struct dcb_output *dcb = nouveau_encoder(encoder)->dcb; 64362306a36Sopenharmony_ci int num_tv_norms = dcb->tvconf.has_component_output ? NUM_TV_NORMS : 64462306a36Sopenharmony_ci NUM_LD_TV_NORMS; 64562306a36Sopenharmony_ci int i; 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci if (nouveau_tv_norm) { 64862306a36Sopenharmony_ci i = match_string(nv17_tv_norm_names, num_tv_norms, 64962306a36Sopenharmony_ci nouveau_tv_norm); 65062306a36Sopenharmony_ci if (i < 0) 65162306a36Sopenharmony_ci NV_WARN(drm, "Invalid TV norm setting \"%s\"\n", 65262306a36Sopenharmony_ci nouveau_tv_norm); 65362306a36Sopenharmony_ci else 65462306a36Sopenharmony_ci tv_enc->tv_norm = i; 65562306a36Sopenharmony_ci } 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci drm_mode_create_tv_properties_legacy(dev, num_tv_norms, nv17_tv_norm_names); 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci drm_object_attach_property(&connector->base, 66062306a36Sopenharmony_ci conf->tv_select_subconnector_property, 66162306a36Sopenharmony_ci tv_enc->select_subconnector); 66262306a36Sopenharmony_ci drm_object_attach_property(&connector->base, 66362306a36Sopenharmony_ci conf->tv_subconnector_property, 66462306a36Sopenharmony_ci tv_enc->subconnector); 66562306a36Sopenharmony_ci drm_object_attach_property(&connector->base, 66662306a36Sopenharmony_ci conf->legacy_tv_mode_property, 66762306a36Sopenharmony_ci tv_enc->tv_norm); 66862306a36Sopenharmony_ci drm_object_attach_property(&connector->base, 66962306a36Sopenharmony_ci conf->tv_flicker_reduction_property, 67062306a36Sopenharmony_ci tv_enc->flicker); 67162306a36Sopenharmony_ci drm_object_attach_property(&connector->base, 67262306a36Sopenharmony_ci conf->tv_saturation_property, 67362306a36Sopenharmony_ci tv_enc->saturation); 67462306a36Sopenharmony_ci drm_object_attach_property(&connector->base, 67562306a36Sopenharmony_ci conf->tv_hue_property, 67662306a36Sopenharmony_ci tv_enc->hue); 67762306a36Sopenharmony_ci drm_object_attach_property(&connector->base, 67862306a36Sopenharmony_ci conf->tv_overscan_property, 67962306a36Sopenharmony_ci tv_enc->overscan); 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci return 0; 68262306a36Sopenharmony_ci} 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_cistatic int nv17_tv_set_property(struct drm_encoder *encoder, 68562306a36Sopenharmony_ci struct drm_connector *connector, 68662306a36Sopenharmony_ci struct drm_property *property, 68762306a36Sopenharmony_ci uint64_t val) 68862306a36Sopenharmony_ci{ 68962306a36Sopenharmony_ci struct drm_mode_config *conf = &encoder->dev->mode_config; 69062306a36Sopenharmony_ci struct drm_crtc *crtc = encoder->crtc; 69162306a36Sopenharmony_ci struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); 69262306a36Sopenharmony_ci struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); 69362306a36Sopenharmony_ci bool modes_changed = false; 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci if (property == conf->tv_overscan_property) { 69662306a36Sopenharmony_ci tv_enc->overscan = val; 69762306a36Sopenharmony_ci if (encoder->crtc) { 69862306a36Sopenharmony_ci if (tv_norm->kind == CTV_ENC_MODE) 69962306a36Sopenharmony_ci nv17_ctv_update_rescaler(encoder); 70062306a36Sopenharmony_ci else 70162306a36Sopenharmony_ci nv17_tv_update_rescaler(encoder); 70262306a36Sopenharmony_ci } 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci } else if (property == conf->tv_saturation_property) { 70562306a36Sopenharmony_ci if (tv_norm->kind != TV_ENC_MODE) 70662306a36Sopenharmony_ci return -EINVAL; 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci tv_enc->saturation = val; 70962306a36Sopenharmony_ci nv17_tv_update_properties(encoder); 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci } else if (property == conf->tv_hue_property) { 71262306a36Sopenharmony_ci if (tv_norm->kind != TV_ENC_MODE) 71362306a36Sopenharmony_ci return -EINVAL; 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci tv_enc->hue = val; 71662306a36Sopenharmony_ci nv17_tv_update_properties(encoder); 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci } else if (property == conf->tv_flicker_reduction_property) { 71962306a36Sopenharmony_ci if (tv_norm->kind != TV_ENC_MODE) 72062306a36Sopenharmony_ci return -EINVAL; 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci tv_enc->flicker = val; 72362306a36Sopenharmony_ci if (encoder->crtc) 72462306a36Sopenharmony_ci nv17_tv_update_rescaler(encoder); 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci } else if (property == conf->legacy_tv_mode_property) { 72762306a36Sopenharmony_ci if (connector->dpms != DRM_MODE_DPMS_OFF) 72862306a36Sopenharmony_ci return -EINVAL; 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci tv_enc->tv_norm = val; 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci modes_changed = true; 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_ci } else if (property == conf->tv_select_subconnector_property) { 73562306a36Sopenharmony_ci if (tv_norm->kind != TV_ENC_MODE) 73662306a36Sopenharmony_ci return -EINVAL; 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci tv_enc->select_subconnector = val; 73962306a36Sopenharmony_ci nv17_tv_update_properties(encoder); 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_ci } else { 74262306a36Sopenharmony_ci return -EINVAL; 74362306a36Sopenharmony_ci } 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci if (modes_changed) { 74662306a36Sopenharmony_ci drm_helper_probe_single_connector_modes(connector, 0, 0); 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_ci /* Disable the crtc to ensure a full modeset is 74962306a36Sopenharmony_ci * performed whenever it's turned on again. */ 75062306a36Sopenharmony_ci if (crtc) 75162306a36Sopenharmony_ci drm_crtc_helper_set_mode(crtc, &crtc->mode, 75262306a36Sopenharmony_ci crtc->x, crtc->y, 75362306a36Sopenharmony_ci crtc->primary->fb); 75462306a36Sopenharmony_ci } 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_ci return 0; 75762306a36Sopenharmony_ci} 75862306a36Sopenharmony_ci 75962306a36Sopenharmony_cistatic void nv17_tv_destroy(struct drm_encoder *encoder) 76062306a36Sopenharmony_ci{ 76162306a36Sopenharmony_ci struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_ci drm_encoder_cleanup(encoder); 76462306a36Sopenharmony_ci kfree(tv_enc); 76562306a36Sopenharmony_ci} 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_cistatic const struct drm_encoder_helper_funcs nv17_tv_helper_funcs = { 76862306a36Sopenharmony_ci .dpms = nv17_tv_dpms, 76962306a36Sopenharmony_ci .mode_fixup = nv17_tv_mode_fixup, 77062306a36Sopenharmony_ci .prepare = nv17_tv_prepare, 77162306a36Sopenharmony_ci .commit = nv17_tv_commit, 77262306a36Sopenharmony_ci .mode_set = nv17_tv_mode_set, 77362306a36Sopenharmony_ci .detect = nv17_tv_detect, 77462306a36Sopenharmony_ci}; 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_cistatic const struct drm_encoder_slave_funcs nv17_tv_slave_funcs = { 77762306a36Sopenharmony_ci .get_modes = nv17_tv_get_modes, 77862306a36Sopenharmony_ci .mode_valid = nv17_tv_mode_valid, 77962306a36Sopenharmony_ci .create_resources = nv17_tv_create_resources, 78062306a36Sopenharmony_ci .set_property = nv17_tv_set_property, 78162306a36Sopenharmony_ci}; 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_cistatic const struct drm_encoder_funcs nv17_tv_funcs = { 78462306a36Sopenharmony_ci .destroy = nv17_tv_destroy, 78562306a36Sopenharmony_ci}; 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ciint 78862306a36Sopenharmony_cinv17_tv_create(struct drm_connector *connector, struct dcb_output *entry) 78962306a36Sopenharmony_ci{ 79062306a36Sopenharmony_ci struct drm_device *dev = connector->dev; 79162306a36Sopenharmony_ci struct drm_encoder *encoder; 79262306a36Sopenharmony_ci struct nv17_tv_encoder *tv_enc = NULL; 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci tv_enc = kzalloc(sizeof(*tv_enc), GFP_KERNEL); 79562306a36Sopenharmony_ci if (!tv_enc) 79662306a36Sopenharmony_ci return -ENOMEM; 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_ci tv_enc->overscan = 50; 79962306a36Sopenharmony_ci tv_enc->flicker = 50; 80062306a36Sopenharmony_ci tv_enc->saturation = 50; 80162306a36Sopenharmony_ci tv_enc->hue = 0; 80262306a36Sopenharmony_ci tv_enc->tv_norm = TV_NORM_PAL; 80362306a36Sopenharmony_ci tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Unknown; 80462306a36Sopenharmony_ci tv_enc->select_subconnector = DRM_MODE_SUBCONNECTOR_Automatic; 80562306a36Sopenharmony_ci tv_enc->pin_mask = 0; 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci encoder = to_drm_encoder(&tv_enc->base); 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci tv_enc->base.dcb = entry; 81062306a36Sopenharmony_ci tv_enc->base.or = ffs(entry->or) - 1; 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci drm_encoder_init(dev, encoder, &nv17_tv_funcs, DRM_MODE_ENCODER_TVDAC, 81362306a36Sopenharmony_ci NULL); 81462306a36Sopenharmony_ci drm_encoder_helper_add(encoder, &nv17_tv_helper_funcs); 81562306a36Sopenharmony_ci to_encoder_slave(encoder)->slave_funcs = &nv17_tv_slave_funcs; 81662306a36Sopenharmony_ci 81762306a36Sopenharmony_ci tv_enc->base.enc_save = nv17_tv_save; 81862306a36Sopenharmony_ci tv_enc->base.enc_restore = nv17_tv_restore; 81962306a36Sopenharmony_ci 82062306a36Sopenharmony_ci encoder->possible_crtcs = entry->heads; 82162306a36Sopenharmony_ci encoder->possible_clones = 0; 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci nv17_tv_create_resources(encoder, connector); 82462306a36Sopenharmony_ci drm_connector_attach_encoder(connector, encoder); 82562306a36Sopenharmony_ci return 0; 82662306a36Sopenharmony_ci} 827