ch7017.c revision fa225cbc
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 <stdint.h>
33#include <unistd.h>
34
35#include "xf86.h"
36#include "xf86_OSproc.h"
37#include "compiler.h"
38#include "miscstruct.h"
39#include "xf86i2c.h"
40#include "xf86Crtc.h"
41#ifdef HAVE_XEXTPROTO_71
42#include <X11/extensions/dpmsconst.h>
43#else
44#define DPMS_SERVER
45#include <X11/extensions/dpms.h>
46#endif
47
48
49#include "../i2c_vid.h"
50#include "ch7017_reg.h"
51
52struct ch7017_priv {
53    I2CDevRec d;
54
55    uint8_t save_hapi;
56    uint8_t save_vali;
57    uint8_t save_valo;
58    uint8_t save_ailo;
59    uint8_t save_lvds_pll_vco;
60    uint8_t save_feedback_div;
61    uint8_t save_lvds_control_2;
62    uint8_t save_outputs_enable;
63    uint8_t save_lvds_power_down;
64    uint8_t save_power_management;
65};
66
67static void
68ch7017_dump_regs(I2CDevPtr d);
69static void
70ch7017_dpms(I2CDevPtr d, int mode);
71
72static Bool
73ch7017_read(struct ch7017_priv *priv, int addr, uint8_t *val)
74{
75    if (!xf86I2CReadByte(&priv->d, addr, val)) {
76	xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_ERROR,
77		   "Unable to read from %s Slave %d.\n",
78		   priv->d.pI2CBus->BusName, priv->d.SlaveAddr);
79	return FALSE;
80    }
81    return TRUE;
82}
83
84static Bool
85ch7017_write(struct ch7017_priv *priv, int addr, uint8_t val)
86{
87    if (!xf86I2CWriteByte(&priv->d, addr, val)) {
88	xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_ERROR,
89		   "Unable to write to %s Slave %d.\n",
90		   priv->d.pI2CBus->BusName, priv->d.SlaveAddr);
91	return FALSE;
92    }
93    return TRUE;
94}
95
96/** Probes for a CH7017 on the given bus and slave address. */
97static void *
98ch7017_init(I2CBusPtr b, I2CSlaveAddr addr)
99{
100    struct ch7017_priv *priv;
101    uint8_t val;
102
103    priv = xcalloc(1, sizeof(struct ch7017_priv));
104    if (priv == NULL)
105	return NULL;
106
107    priv->d.DevName = "CH7017/7018/7019 LVDS Controller";
108    priv->d.SlaveAddr = addr;
109    priv->d.pI2CBus = b;
110    priv->d.StartTimeout = b->StartTimeout;
111    priv->d.BitTimeout = b->BitTimeout;
112    priv->d.AcknTimeout = b->AcknTimeout;
113    priv->d.ByteTimeout = b->ByteTimeout;
114    priv->d.DriverPrivate.ptr = priv;
115
116    if (!xf86I2CReadByte(&priv->d, CH7017_DEVICE_ID, &val))
117	goto fail;
118
119    if (val != CH7017_DEVICE_ID_VALUE &&
120	val != CH7018_DEVICE_ID_VALUE &&
121	val != CH7019_DEVICE_ID_VALUE) {
122	xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_ERROR,
123		   "ch701x not detected, got %d: from %s Slave %d.\n",
124		   val, priv->d.pI2CBus->BusName, priv->d.SlaveAddr);
125	goto fail;
126    }
127
128    if (!xf86I2CDevInit(&(priv->d)))
129	goto fail;
130
131    return priv;
132
133fail:
134    xfree(priv);
135    return NULL;
136}
137
138static xf86OutputStatus
139ch7017_detect(I2CDevPtr d)
140{
141    return XF86OutputStatusUnknown;
142}
143
144static ModeStatus
145ch7017_mode_valid(I2CDevPtr d, DisplayModePtr mode)
146{
147    if (mode->Clock > 160000)
148	return MODE_CLOCK_HIGH;
149
150    return MODE_OK;
151}
152
153static void
154ch7017_mode_set(I2CDevPtr d, DisplayModePtr mode, DisplayModePtr adjusted_mode)
155{
156    struct ch7017_priv *priv = d->DriverPrivate.ptr;
157    uint8_t lvds_pll_feedback_div, lvds_pll_vco_control;
158    uint8_t outputs_enable, lvds_control_2, lvds_power_down;
159    uint8_t horizontal_active_pixel_input;
160    uint8_t horizontal_active_pixel_output, vertical_active_line_output;
161    uint8_t active_input_line_output;
162
163    xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO,
164	       "Registers before mode setting\n");
165    ch7017_dump_regs(d);
166
167    /* LVDS PLL settings from page 75 of 7017-7017ds.pdf*/
168    if (mode->Clock < 100000) {
169	outputs_enable = CH7017_LVDS_CHANNEL_A | CH7017_CHARGE_PUMP_LOW;
170	lvds_pll_feedback_div = CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED |
171	    (2 << CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT) |
172	    (13 << CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT);
173	lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED |
174	    (2 << CH7017_LVDS_PLL_VCO_SHIFT) |
175	    (3 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT);
176	lvds_control_2 = (1 << CH7017_LOOP_FILTER_SHIFT) |
177	    (0 << CH7017_PHASE_DETECTOR_SHIFT);
178    } else {
179	outputs_enable = CH7017_LVDS_CHANNEL_A | CH7017_CHARGE_PUMP_HIGH;
180	lvds_pll_feedback_div = CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED |
181	    (2 << CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT) |
182	    (3 << CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT);
183	lvds_pll_feedback_div = 35;
184	lvds_control_2 = (3 << CH7017_LOOP_FILTER_SHIFT) |
185	    (0 << CH7017_PHASE_DETECTOR_SHIFT);
186	if (1) { /* XXX: dual channel panel detection.  Assume yes for now. */
187	    outputs_enable |= CH7017_LVDS_CHANNEL_B;
188	    lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED |
189		(2 << CH7017_LVDS_PLL_VCO_SHIFT) |
190		(13 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT);
191	} else {
192	    lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED |
193		(1 << CH7017_LVDS_PLL_VCO_SHIFT) |
194		(13 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT);
195	}
196    }
197
198    horizontal_active_pixel_input = mode->HDisplay & 0x00ff;
199
200    vertical_active_line_output = mode->VDisplay & 0x00ff;
201    horizontal_active_pixel_output = mode->HDisplay & 0x00ff;
202
203    active_input_line_output = ((mode->HDisplay & 0x0700) >> 8) |
204	(((mode->VDisplay & 0x0700) >> 8) << 3);
205
206    lvds_power_down = CH7017_LVDS_POWER_DOWN_DEFAULT_RESERVED |
207	(mode->HDisplay & 0x0700) >> 8;
208
209    ch7017_dpms(d, DPMSModeOff);
210    ch7017_write(priv, CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT,
211		    horizontal_active_pixel_input);
212    ch7017_write(priv, CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT,
213		    horizontal_active_pixel_output);
214    ch7017_write(priv, CH7017_VERTICAL_ACTIVE_LINE_OUTPUT,
215		    vertical_active_line_output);
216    ch7017_write(priv, CH7017_ACTIVE_INPUT_LINE_OUTPUT,
217		 active_input_line_output);
218    ch7017_write(priv, CH7017_LVDS_PLL_VCO_CONTROL, lvds_pll_vco_control);
219    ch7017_write(priv, CH7017_LVDS_PLL_FEEDBACK_DIV, lvds_pll_feedback_div);
220    ch7017_write(priv, CH7017_LVDS_CONTROL_2, lvds_control_2);
221    ch7017_write(priv, CH7017_OUTPUTS_ENABLE, outputs_enable);
222
223    /* Turn the LVDS back on with new settings. */
224    ch7017_write(priv, CH7017_LVDS_POWER_DOWN, lvds_power_down);
225
226    xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO,
227	       "Registers after mode setting\n");
228    ch7017_dump_regs(d);
229}
230
231/* set the CH7017 power state */
232static void
233ch7017_dpms(I2CDevPtr d, int mode)
234{
235    struct ch7017_priv *priv = d->DriverPrivate.ptr;
236    uint8_t val;
237
238    ch7017_read(priv, CH7017_LVDS_POWER_DOWN, &val);
239
240    /* Turn off TV/VGA, and never turn it on since we don't support it. */
241    ch7017_write(priv, CH7017_POWER_MANAGEMENT,
242		 CH7017_DAC0_POWER_DOWN |
243		 CH7017_DAC1_POWER_DOWN |
244		 CH7017_DAC2_POWER_DOWN |
245		 CH7017_DAC3_POWER_DOWN |
246		 CH7017_TV_POWER_DOWN_EN);
247
248    if (mode == DPMSModeOn) {
249	/* Turn on the LVDS */
250	ch7017_write(priv, CH7017_LVDS_POWER_DOWN,
251			val & ~CH7017_LVDS_POWER_DOWN_EN);
252    } else {
253	/* Turn off the LVDS */
254	ch7017_write(priv, CH7017_LVDS_POWER_DOWN,
255			val | CH7017_LVDS_POWER_DOWN_EN);
256    }
257
258    /* XXX: Should actually wait for update power status somehow */
259    usleep(50000);
260}
261
262static void
263ch7017_dump_regs(I2CDevPtr d)
264{
265    struct ch7017_priv *priv = d->DriverPrivate.ptr;
266    uint8_t val;
267
268#define DUMP(reg)					\
269do {							\
270	ch7017_read(priv, reg, &val);			\
271	xf86DrvMsg(priv->d.pI2CBus->scrnIndex, X_INFO,	\
272		   #reg ": %02x\n", val);		\
273} while (0)
274
275    DUMP(CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT);
276    DUMP(CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT);
277    DUMP(CH7017_VERTICAL_ACTIVE_LINE_OUTPUT);
278    DUMP(CH7017_ACTIVE_INPUT_LINE_OUTPUT);
279    DUMP(CH7017_LVDS_PLL_VCO_CONTROL);
280    DUMP(CH7017_LVDS_PLL_FEEDBACK_DIV);
281    DUMP(CH7017_LVDS_CONTROL_2);
282    DUMP(CH7017_OUTPUTS_ENABLE);
283    DUMP(CH7017_LVDS_POWER_DOWN);
284}
285
286static void
287ch7017_save(I2CDevPtr d)
288{
289    struct ch7017_priv *priv = d->DriverPrivate.ptr;
290
291    ch7017_read(priv, CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT, &priv->save_hapi);
292    ch7017_read(priv, CH7017_VERTICAL_ACTIVE_LINE_OUTPUT, &priv->save_valo);
293    ch7017_read(priv, CH7017_ACTIVE_INPUT_LINE_OUTPUT, &priv->save_ailo);
294    ch7017_read(priv, CH7017_LVDS_PLL_VCO_CONTROL, &priv->save_lvds_pll_vco);
295    ch7017_read(priv, CH7017_LVDS_PLL_FEEDBACK_DIV, &priv->save_feedback_div);
296    ch7017_read(priv, CH7017_LVDS_CONTROL_2, &priv->save_lvds_control_2);
297    ch7017_read(priv, CH7017_OUTPUTS_ENABLE, &priv->save_outputs_enable);
298    ch7017_read(priv, CH7017_LVDS_POWER_DOWN, &priv->save_lvds_power_down);
299    ch7017_read(priv, CH7017_POWER_MANAGEMENT, &priv->save_power_management);
300}
301
302static void
303ch7017_restore(I2CDevPtr d)
304{
305    struct ch7017_priv *priv = d->DriverPrivate.ptr;
306
307    /* Power down before changing mode */
308    ch7017_dpms(d, DPMSModeOff);
309
310    ch7017_write(priv, CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT, priv->save_hapi);
311    ch7017_write(priv, CH7017_VERTICAL_ACTIVE_LINE_OUTPUT, priv->save_valo);
312    ch7017_write(priv, CH7017_ACTIVE_INPUT_LINE_OUTPUT, priv->save_ailo);
313    ch7017_write(priv, CH7017_LVDS_PLL_VCO_CONTROL, priv->save_lvds_pll_vco);
314    ch7017_write(priv, CH7017_LVDS_PLL_FEEDBACK_DIV, priv->save_feedback_div);
315    ch7017_write(priv, CH7017_LVDS_CONTROL_2, priv->save_lvds_control_2);
316    ch7017_write(priv, CH7017_OUTPUTS_ENABLE, priv->save_outputs_enable);
317    ch7017_write(priv, CH7017_LVDS_POWER_DOWN, priv->save_lvds_power_down);
318    ch7017_write(priv, CH7017_POWER_MANAGEMENT, priv->save_power_management);
319}
320
321_X_EXPORT I830I2CVidOutputRec ch7017_methods = {
322    .init = ch7017_init,
323    .detect = ch7017_detect,
324    .mode_valid = ch7017_mode_valid,
325    .mode_set = ch7017_mode_set,
326    .dpms = ch7017_dpms,
327    .dump_regs = ch7017_dump_regs,
328    .save = ch7017_save,
329    .restore = ch7017_restore,
330};
331