1/* 2 * Copyright © 2006 Intel Corporation 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice (including the next 12 * paragraph) shall be included in all copies or substantial portions of the 13 * Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 * 23 * Authors: 24 * Eric Anholt <eric@anholt.net> 25 * 26 */ 27 28#ifdef HAVE_CONFIG_H 29#include "config.h" 30#endif 31 32#include "xf86.h" 33#include "xf86_OSproc.h" 34#include "compiler.h" 35#include "miscstruct.h" 36#include "xf86i2c.h" 37#include "xf86Crtc.h" 38#ifdef HAVE_XEXTPROTO_71 39#include <X11/extensions/dpmsconst.h> 40#else 41#define DPMS_SERVER 42#include <X11/extensions/dpms.h> 43#endif 44 45#include <unistd.h> 46 47#include "../i2c_vid.h" 48#include "../i830_bios.h" 49#include "ivch_reg.h" 50 51struct ivch_priv { 52 I2CDevRec d; 53 54 xf86OutputPtr output; 55 Bool quiet; 56 57 uint16_t width, height; 58 59 uint16_t save_VR01; 60 uint16_t save_VR40; 61}; 62 63struct vch_capabilities { 64 struct aimdb_block aimdb_block; 65 uint8_t panel_type; 66 uint8_t set_panel_type; 67 uint8_t slave_address; 68 uint8_t capabilities; 69#define VCH_PANEL_FITTING_SUPPORT (0x3 << 0) 70#define VCH_PANEL_FITTING_TEXT (1 << 2) 71#define VCH_PANEL_FITTING_GRAPHICS (1 << 3) 72#define VCH_PANEL_FITTING_RATIO (1 << 4) 73#define VCH_DITHERING (1 << 5) 74 uint8_t backlight_gpio; 75 uint8_t set_panel_type_us_gpios; 76} __attribute__ ((packed)); 77 78static void 79ivch_dump_regs(I2CDevPtr d); 80 81/** 82 * Reads a register on the ivch. 83 * 84 * Each of the 256 registers are 16 bits long. 85 */ 86static Bool 87ivch_read(struct ivch_priv *priv, int addr, uint16_t *data) 88{ 89 I2CBusPtr b = priv->d.pI2CBus; 90 I2CByte *p = (I2CByte *) data; 91 92 if (!b->I2CStart(b, priv->d.StartTimeout)) 93 goto fail; 94 95 if (!b->I2CPutByte(&priv->d, priv->d.SlaveAddr | 1)) 96 goto fail; 97 98 if (!b->I2CPutByte(&priv->d, addr)) 99 goto fail; 100 101 if (!b->I2CGetByte(&priv->d, p++, FALSE)) 102 goto fail; 103 104 if (!b->I2CGetByte(&priv->d, p++, TRUE)) 105 goto fail; 106 107 b->I2CStop(&priv->d); 108 109 return TRUE; 110 111 fail: 112 if (!priv->quiet) { 113 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_ERROR, 114 "ivch: Unable to read register 0x%02x from %s:%02x.\n", 115 addr, priv->d.pI2CBus->BusName, priv->d.SlaveAddr); 116 } 117 b->I2CStop(&priv->d); 118 119 return FALSE; 120} 121 122/** Writes a 16-bit register on the ivch */ 123static Bool 124ivch_write(struct ivch_priv *priv, int addr, uint16_t data) 125{ 126 I2CBusPtr b = priv->d.pI2CBus; 127 128 if (!b->I2CStart(b, priv->d.StartTimeout)) 129 goto fail; 130 131 if (!b->I2CPutByte(&priv->d, priv->d.SlaveAddr)) 132 goto fail; 133 134 if (!b->I2CPutByte(&priv->d, addr)) 135 goto fail; 136 137 if (!b->I2CPutByte(&priv->d, data & 0xff)) 138 goto fail; 139 140 if (!b->I2CPutByte(&priv->d, data >> 8)) 141 goto fail; 142 143 b->I2CStop(&priv->d); 144 145 return TRUE; 146 147 fail: 148 b->I2CStop(&priv->d); 149 150 if (!priv->quiet) { 151 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_ERROR, 152 "Unable to write register 0x%02x to %s:%d.\n", 153 addr, priv->d.pI2CBus->BusName, priv->d.SlaveAddr); 154 } 155 156 return FALSE; 157} 158 159/** Probes the given bus and slave address for an ivch */ 160static void * 161ivch_init(I2CBusPtr b, I2CSlaveAddr addr) 162{ 163 struct ivch_priv *priv; 164 uint16_t temp; 165 166 priv = xcalloc(1, sizeof(struct ivch_priv)); 167 if (priv == NULL) 168 return NULL; 169 170 priv->output = NULL; 171 priv->d.DevName = "i82807aa \"ivch\" LVDS/CMOS panel controller"; 172 priv->d.SlaveAddr = addr; 173 priv->d.pI2CBus = b; 174 priv->d.StartTimeout = b->StartTimeout; 175 priv->d.BitTimeout = b->BitTimeout; 176 priv->d.AcknTimeout = b->AcknTimeout; 177 priv->d.ByteTimeout = b->ByteTimeout; 178 priv->d.DriverPrivate.ptr = priv; 179 priv->quiet = TRUE; 180 181 if (!ivch_read(priv, VR00, &temp)) 182 goto out; 183 priv->quiet = FALSE; 184 185 /* Since the identification bits are probably zeroes, which doesn't seem 186 * very unique, check that the value in the base address field matches 187 * the address it's responding on. 188 */ 189 if ((temp & VR00_BASE_ADDRESS_MASK) != (priv->d.SlaveAddr >> 1)) { 190 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_ERROR, 191 "ivch detect failed due to address mismatch " 192 "(%d vs %d)\n", 193 (temp & VR00_BASE_ADDRESS_MASK), priv->d.SlaveAddr >> 1); 194 goto out; 195 } 196 197 if (!xf86I2CDevInit(&priv->d)) { 198 goto out; 199 } 200 201 ivch_read(priv, VR20, &priv->width); 202 ivch_read(priv, VR21, &priv->height); 203 204 return priv; 205 206out: 207 xfree(priv); 208 return NULL; 209} 210 211static xf86OutputStatus 212ivch_detect(I2CDevPtr d) 213{ 214 return XF86OutputStatusConnected; 215} 216 217static ModeStatus 218ivch_mode_valid(I2CDevPtr d, DisplayModePtr mode) 219{ 220 if (mode->Clock > 112000) 221 return MODE_CLOCK_HIGH; 222 223 return MODE_OK; 224} 225 226/** Sets the power state of the panel connected to the ivch */ 227static void 228ivch_dpms(I2CDevPtr d, int mode) 229{ 230 struct ivch_priv *priv = d->DriverPrivate.ptr; 231 int i; 232 uint16_t vr01, vr30, backlight; 233 234 /* Set the new power state of the panel. */ 235 if (!ivch_read(priv, VR01, &vr01)) 236 return; 237 238 if (mode == DPMSModeOn) 239 backlight = 1; 240 else 241 backlight = 0; 242 ivch_write(priv, VR80, backlight); 243 244 if (mode == DPMSModeOn) 245 vr01 |= VR01_LCD_ENABLE | VR01_DVO_ENABLE; 246 else 247 vr01 &= ~(VR01_LCD_ENABLE | VR01_DVO_ENABLE); 248 249 ivch_write(priv, VR01, vr01); 250 251 /* Wait for the panel to make its state transition */ 252 for (i = 0; i < 100; i++) { 253 if (!ivch_read(priv, VR30, &vr30)) 254 break; 255 256 if (((vr30 & VR30_PANEL_ON) != 0) == (mode == DPMSModeOn)) 257 break; 258 usleep (1000); 259 } 260 /* And wait some more; without this, the vch fails to resync sometimes */ 261 usleep (16 * 1000); 262} 263 264static void 265ivch_mode_set(I2CDevPtr d, DisplayModePtr mode, DisplayModePtr adjusted_mode) 266{ 267 struct ivch_priv *priv = d->DriverPrivate.ptr; 268 uint16_t vr40 = 0; 269 uint16_t vr01; 270 271 vr01 = 0; 272 vr40 = (VR40_STALL_ENABLE | 273 VR40_VERTICAL_INTERP_ENABLE | 274 VR40_HORIZONTAL_INTERP_ENABLE); 275 276 if (mode->HDisplay != adjusted_mode->HDisplay || 277 mode->VDisplay != adjusted_mode->VDisplay) 278 { 279 uint16_t x_ratio, y_ratio; 280 281 vr01 |= VR01_PANEL_FIT_ENABLE; 282 vr40 |= VR40_CLOCK_GATING_ENABLE; 283 x_ratio = (((mode->HDisplay - 1) << 16) / (adjusted_mode->HDisplay - 1)) >> 2; 284 y_ratio = (((mode->VDisplay - 1) << 16) / (adjusted_mode->VDisplay - 1)) >> 2; 285 ivch_write (priv, VR42, x_ratio); 286 ivch_write (priv, VR41, y_ratio); 287 } 288 else 289 { 290 vr01 &= ~VR01_PANEL_FIT_ENABLE; 291 vr40 &= ~VR40_CLOCK_GATING_ENABLE; 292 } 293 vr40 &= ~VR40_AUTO_RATIO_ENABLE; 294 295 ivch_write(priv, VR01, vr01); 296 ivch_write(priv, VR40, vr40); 297 298 ivch_dump_regs(d); 299} 300 301static void 302ivch_dump_regs(I2CDevPtr d) 303{ 304 struct ivch_priv *priv = d->DriverPrivate.ptr; 305 uint16_t val; 306 307 ivch_read(priv, VR00, &val); 308 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR00: 0x%04x\n", val); 309 ivch_read(priv, VR01, &val); 310 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR01: 0x%04x\n", val); 311 ivch_read(priv, VR30, &val); 312 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR30: 0x%04x\n", val); 313 ivch_read(priv, VR40, &val); 314 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR40: 0x%04x\n", val); 315 316 /* GPIO registers */ 317 ivch_read(priv, VR80, &val); 318 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR80: 0x%04x\n", val); 319 ivch_read(priv, VR81, &val); 320 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR81: 0x%04x\n", val); 321 ivch_read(priv, VR82, &val); 322 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR82: 0x%04x\n", val); 323 ivch_read(priv, VR83, &val); 324 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR83: 0x%04x\n", val); 325 ivch_read(priv, VR84, &val); 326 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR84: 0x%04x\n", val); 327 ivch_read(priv, VR85, &val); 328 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR85: 0x%04x\n", val); 329 ivch_read(priv, VR86, &val); 330 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR86: 0x%04x\n", val); 331 ivch_read(priv, VR87, &val); 332 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR87: 0x%04x\n", val); 333 ivch_read(priv, VR88, &val); 334 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR88: 0x%04x\n", val); 335 336 /* Scratch register 0 - AIM Panel type */ 337 ivch_read(priv, VR8E, &val); 338 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR8E: 0x%04x\n", val); 339 340 /* Scratch register 1 - Status register */ 341 ivch_read(priv, VR8F, &val); 342 xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO, "VR8F: 0x%04x\n", val); 343} 344 345static void 346ivch_save(I2CDevPtr d) 347{ 348 struct ivch_priv *priv = d->DriverPrivate.ptr; 349 350 ivch_read(priv, VR01, &priv->save_VR01); 351 ivch_read(priv, VR40, &priv->save_VR40); 352} 353 354static void 355ivch_restore(I2CDevPtr d) 356{ 357 struct ivch_priv *priv = d->DriverPrivate.ptr; 358 359 ivch_write(priv, VR01, priv->save_VR01); 360 ivch_write(priv, VR40, priv->save_VR40); 361} 362 363 364_X_EXPORT I830I2CVidOutputRec ivch_methods = { 365 .init = ivch_init, 366 .dpms = ivch_dpms, 367 .save = ivch_save, 368 .restore = ivch_restore, 369 .mode_valid = ivch_mode_valid, 370 .mode_set = ivch_mode_set, 371 .detect = ivch_detect, 372 .dump_regs = ivch_dump_regs, 373}; 374