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