1/*
2 * Copyright 2016 Kevin Brace
3 * Copyright 2016 The OpenChrome Project
4 *                [https://www.freedesktop.org/wiki/Openchrome]
5 * Copyright 2014 SHS SERVICES GmbH
6 * Copyright 2006-2009 Luc Verhaegen.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sub license,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice (including the
16 * next paragraph) shall be included in all copies or substantial portions
17 * of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
22 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 * DEALINGS IN THE SOFTWARE.
26 */
27
28#ifdef HAVE_CONFIG_H
29#include "config.h"
30#endif
31
32#include "via_driver.h"
33#include "via_vt1632.h"
34
35static void
36viaVT1632DumpRegisters(ScrnInfoPtr pScrn, I2CDevPtr pDev)
37{
38    int i;
39    CARD8 tmp;
40
41    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
42                        "Entered viaVT1632DumpRegisters.\n"));
43
44    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Dumping VT1632(A) registers.\n"));
45    for (i = 0; i <= 0x0f; i++) {
46        xf86I2CReadByte(pDev, i, &tmp);
47        DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "0x%02x: 0x%02x\n", i, tmp));
48    }
49
50    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
51                        "Exiting viaVT1632DumpRegisters.\n"));
52}
53
54static void
55viaVT1632InitRegisters(ScrnInfoPtr pScrn, I2CDevPtr pDev)
56{
57    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
58                        "Entered viaVT1632InitRegisters.\n"));
59
60    /* For Wyse C00X VX855 chipset DVP1 (Digital Video Port 1), use
61     * 12-bit mode with dual edge transfer, along with rising edge
62     * data capture first mode. This is likely true for CX700, VX700,
63     * VX800, and VX900 chipsets as well. */
64    xf86I2CWriteByte(pDev, 0x08,
65                        VIA_VT1632_VEN | VIA_VT1632_HEN |
66                        VIA_VT1632_DSEL |
67                        VIA_VT1632_EDGE | VIA_VT1632_PDB);
68
69    /* Route receiver detect bit (Offset 0x09[2]) as the output of
70     * MSEN pin. */
71    xf86I2CWriteByte(pDev, 0x09, 0x20);
72
73    /* Turning on deskew feature caused screen display issues.
74     * This was observed with Wyse C00X. */
75    xf86I2CWriteByte(pDev, 0x0A, 0x00);
76
77    /* While VIA Technologies VT1632A datasheet insists on setting this
78     * register to 0x89 as the recommended setting, in practice, this
79     * leads to a blank screen on the display with Wyse C00X. According to
80     * Silicon Image SiI 164 datasheet (VT1632A is a pin and mostly
81     * register compatible chip), offset 0x0C is for PLL filter enable,
82     * PLL filter setting, and continuous SYNC enable bits. All of these are
83     * turned off for proper operation. */
84    xf86I2CWriteByte(pDev, 0x0C, 0x00);
85
86    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
87                        "Exiting viaVT1632InitRegisters.\n"));
88}
89
90/*
91 * Returns TMDS receiver detection state for VIA Technologies VT1632
92 * external TMDS transmitter.
93 */
94static Bool
95viaVT1632Sense(ScrnInfoPtr pScrn, I2CDevPtr pDev)
96{
97    CARD8 tmp;
98    Bool receiverDetected = FALSE;
99
100    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
101                        "Entered viaVT1632Sense.\n"));
102
103    xf86I2CReadByte(pDev, 0x09, &tmp);
104    if (tmp & 0x04) {
105        receiverDetected = TRUE;
106    }
107
108    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
109                "VT1632 %s a TMDS receiver.\n",
110                receiverDetected ? "detected" : "did not detect");
111
112    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
113                        "Exiting viaVT1632Sense.\n"));
114    return receiverDetected;
115}
116
117static void
118viaVT1632Power(ScrnInfoPtr pScrn, I2CDevPtr pDev, Bool powerState)
119{
120    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
121                        "Entered viaVT1632Power.\n"));
122
123    xf86I2CMaskByte(pDev, 0x08, powerState ? 0x01 : 0x00, 0x01);
124    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "VT1632 (DVI) Power: %s\n",
125                powerState ? "On" : "Off");
126
127    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
128                        "Exiting viaVT1632Power.\n"));
129}
130
131static void
132viaVT1632SaveRegisters(ScrnInfoPtr pScrn, I2CDevPtr pDev,
133                        viaVT1632RecPtr pVIAVT1632Rec)
134{
135    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
136                        "Entered viaVT1632SaveRegisters.\n"));
137
138    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
139                "Saving VT1632 registers.\n");
140    xf86I2CReadByte(pDev, 0x08, &pVIAVT1632Rec->Register08);
141    xf86I2CReadByte(pDev, 0x09, &pVIAVT1632Rec->Register09);
142    xf86I2CReadByte(pDev, 0x0A, &pVIAVT1632Rec->Register0A);
143    xf86I2CReadByte(pDev, 0x0C, &pVIAVT1632Rec->Register0C);
144
145    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
146                        "Exiting viaVT1632SaveRegisters.\n"));
147}
148
149static void
150viaVT1632RestoreRegisters(ScrnInfoPtr pScrn, I2CDevPtr pDev,
151                            viaVT1632RecPtr pVIAVT1632Rec)
152{
153    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
154                        "Entered viaVT1632RestoreRegisters.\n"));
155
156    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
157                        "Restoring VT1632 registers.\n"));
158    xf86I2CWriteByte(pDev, 0x08, pVIAVT1632Rec->Register08);
159    xf86I2CWriteByte(pDev, 0x09, pVIAVT1632Rec->Register09);
160    xf86I2CWriteByte(pDev, 0x0A, pVIAVT1632Rec->Register0A);
161    xf86I2CWriteByte(pDev, 0x0C, pVIAVT1632Rec->Register0C);
162
163    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
164                        "Exiting viaVT1632RestoreRegisters.\n"));
165}
166
167static int
168viaVT1632CheckModeValidity(xf86OutputPtr output, DisplayModePtr pMode)
169{
170    ScrnInfoPtr pScrn = output->scrn;
171    viaVT1632RecPtr pVIAVT1632Rec = output->driver_private;
172    int status = MODE_OK;
173
174    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
175                        "Entered viaVT1632CheckModeValidity.\n"));
176
177    if (pMode->Clock < pVIAVT1632Rec->DotclockMin) {
178        status = MODE_CLOCK_LOW;
179        goto exit;
180    }
181
182    if (pMode->Clock > pVIAVT1632Rec->DotclockMax) {
183        status = MODE_CLOCK_HIGH;
184    }
185
186exit:
187    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
188                        "Exiting viaVT1632CheckModeValidity.\n"));
189    return status;
190}
191
192static void
193via_vt1632_create_resources(xf86OutputPtr output)
194{
195}
196
197static void
198via_vt1632_dpms(xf86OutputPtr output, int mode)
199{
200    ScrnInfoPtr pScrn = output->scrn;
201    viaVT1632RecPtr pVIAVT1632Rec = output->driver_private;
202
203    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
204                        "Entered via_vt1632_dpms.\n"));
205
206    switch (mode) {
207    case DPMSModeOn:
208        viaVT1632Power(pScrn, pVIAVT1632Rec->VT1632I2CDev, TRUE);
209        break;
210    case DPMSModeStandby:
211    case DPMSModeSuspend:
212    case DPMSModeOff:
213        viaVT1632Power(pScrn, pVIAVT1632Rec->VT1632I2CDev, FALSE);
214        break;
215    default:
216        break;
217    }
218
219    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
220                        "Exiting via_vt1632_dpms.\n"));
221}
222
223static void
224via_vt1632_save(xf86OutputPtr output)
225{
226    ScrnInfoPtr pScrn = output->scrn;
227    viaVT1632RecPtr pVIAVT1632Rec = output->driver_private;
228
229    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
230                        "Entered via_vt1632_save.\n"));
231
232    viaVT1632SaveRegisters(pScrn, pVIAVT1632Rec->VT1632I2CDev, pVIAVT1632Rec);
233
234    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
235                        "Exiting via_vt1632_save.\n"));
236}
237
238static void
239via_vt1632_restore(xf86OutputPtr output)
240{
241    ScrnInfoPtr pScrn = output->scrn;
242    viaVT1632RecPtr pVIAVT1632Rec = output->driver_private;
243
244    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
245                        "Entered via_vt1632_restore.\n"));
246
247    viaVT1632RestoreRegisters(pScrn, pVIAVT1632Rec->VT1632I2CDev,
248                                pVIAVT1632Rec);
249
250    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
251                        "Exiting via_vt1632_restore.\n"));
252}
253
254static int
255via_vt1632_mode_valid(xf86OutputPtr output, DisplayModePtr pMode)
256{
257    return viaVT1632CheckModeValidity(output, pMode);
258}
259
260static Bool
261via_vt1632_mode_fixup(xf86OutputPtr output, DisplayModePtr mode,
262                   DisplayModePtr adjusted_mode)
263{
264    return TRUE;
265}
266
267static void
268via_vt1632_prepare(xf86OutputPtr output)
269{
270}
271
272static void
273via_vt1632_commit(xf86OutputPtr output)
274{
275}
276
277static void
278via_vt1632_mode_set(xf86OutputPtr output, DisplayModePtr mode,
279                    DisplayModePtr adjusted_mode)
280{
281    ScrnInfoPtr pScrn = output->scrn;
282    drmmode_crtc_private_ptr iga = output->crtc->driver_private;
283    viaVT1632RecPtr pVIAVT1632Rec = output->driver_private;
284
285    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
286                        "Entered via_vt1632_mode_set.\n"));
287
288    if (output->crtc) {
289        viaExtTMDSSetClockDriveStrength(pScrn, 0x03);
290        viaExtTMDSSetDataDriveStrength(pScrn, 0x03);
291        viaExtTMDSEnableIOPads(pScrn, 0x03);
292
293        viaVT1632DumpRegisters(pScrn, pVIAVT1632Rec->VT1632I2CDev);
294        viaVT1632InitRegisters(pScrn, pVIAVT1632Rec->VT1632I2CDev);
295        viaVT1632DumpRegisters(pScrn, pVIAVT1632Rec->VT1632I2CDev);
296
297        viaExtTMDSSetDisplaySource(pScrn, iga->index ? 0x01 : 0x00);
298    }
299
300    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
301                "Exiting via_vt1632_mode_set.\n"));
302}
303
304static xf86OutputStatus
305via_vt1632_detect(xf86OutputPtr output)
306{
307    xf86MonPtr mon;
308    xf86OutputStatus status = XF86OutputStatusDisconnected;
309    ScrnInfoPtr pScrn = output->scrn;
310    viaVT1632RecPtr pVIAVT1632Rec = output->driver_private;
311
312    /* Check for the DVI presence via VT1632 first before accessing
313     * I2C bus. */
314    if (viaVT1632Sense(pScrn, pVIAVT1632Rec->VT1632I2CDev)) {
315
316        /* Since DVI presence was established, access the I2C bus
317         * assigned to DVI. */
318        mon = xf86OutputGetEDID(output, pVIAVT1632Rec->VT1632I2CDev->pI2CBus);
319
320        /* Is the interface type digital? */
321        if (mon && DIGITAL(mon->features.input_type)) {
322            status = XF86OutputStatusConnected;
323            xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
324                        "Detected a monitor connected to DVI.\n");
325            xf86OutputSetEDID(output, mon);
326        } else {
327            xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
328                        "Could not obtain EDID from a monitor "
329                        "connected to DVI.\n");
330        }
331    }
332
333    return status;
334}
335
336#ifdef RANDR_12_INTERFACE
337static Bool
338via_vt1632_set_property(xf86OutputPtr output, Atom property,
339                     RRPropertyValuePtr value)
340{
341    return TRUE;
342}
343
344static Bool
345via_vt1632_get_property(xf86OutputPtr output, Atom property)
346{
347    return FALSE;
348}
349#endif
350
351static void
352via_vt1632_destroy(xf86OutputPtr output)
353{
354}
355
356const xf86OutputFuncsRec via_vt1632_funcs = {
357    .create_resources   = via_vt1632_create_resources,
358    .dpms               = via_vt1632_dpms,
359    .save               = via_vt1632_save,
360    .restore            = via_vt1632_restore,
361    .mode_valid         = via_vt1632_mode_valid,
362    .mode_fixup         = via_vt1632_mode_fixup,
363    .prepare            = via_vt1632_prepare,
364    .commit             = via_vt1632_commit,
365    .mode_set           = via_vt1632_mode_set,
366    .detect             = via_vt1632_detect,
367    .get_modes          = xf86OutputGetEDIDModes,
368#ifdef RANDR_12_INTERFACE
369    .set_property       = via_vt1632_set_property,
370#endif
371#ifdef RANDR_13_INTERFACE
372    .get_property       = via_vt1632_get_property,
373#endif
374    .destroy            = via_vt1632_destroy,
375};
376
377Bool
378viaVT1632Init(ScrnInfoPtr pScrn, I2CBusPtr pI2CBus)
379{
380    xf86OutputPtr output;
381    VIAPtr pVia = VIAPTR(pScrn);
382    viaVT1632RecPtr pVIAVT1632Rec = NULL;
383    I2CDevPtr pI2CDevice = NULL;
384    I2CSlaveAddr i2cAddr = 0x10;
385    CARD8 buf;
386    CARD16 vendorID, deviceID;
387    Bool status = FALSE;
388    char outputNameBuffer[32];
389
390    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
391                        "Entered viaVT1632Init.\n"));
392
393    if (!xf86I2CProbeAddress(pI2CBus, i2cAddr)) {
394        xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
395                    "I2C device not found.\n");
396        goto exit;
397    }
398
399    pI2CDevice = xf86CreateI2CDevRec();
400    if (!pI2CDevice) {
401        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
402                    "Failed to create an I2C bus device record.\n");
403        goto exit;
404    }
405
406    pI2CDevice->DevName = "VT1632";
407    pI2CDevice->SlaveAddr = i2cAddr;
408    pI2CDevice->pI2CBus = pI2CBus;
409    if (!xf86I2CDevInit(pI2CDevice)) {
410        xf86DestroyI2CDevRec(pI2CDevice, TRUE);
411        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
412                    "Failed to initialize a device on I2C bus.\n");
413        goto exit;
414    }
415
416    xf86I2CReadByte(pI2CDevice, 0, &buf);
417    vendorID = buf;
418    xf86I2CReadByte(pI2CDevice, 1, &buf);
419    vendorID |= buf << 8;
420    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
421                        "Vendor ID: 0x%04x\n", vendorID));
422
423    xf86I2CReadByte(pI2CDevice, 2, &buf);
424    deviceID = buf;
425    xf86I2CReadByte(pI2CDevice, 3, &buf);
426    deviceID |= buf << 8;
427    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
428                        "Device ID: 0x%04x\n", deviceID));
429
430    if ((vendorID != 0x1106) || (deviceID != 0x3192)) {
431        xf86DestroyI2CDevRec(pI2CDevice, TRUE);
432        xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
433                    "VT1632 external TMDS transmitter not detected.\n");
434        goto exit;
435    }
436
437    xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
438                "VT1632 external TMDS transmitter detected.\n");
439
440    pVIAVT1632Rec = xnfcalloc(1, sizeof(viaVT1632Rec));
441    if (!pVIAVT1632Rec) {
442        xf86DestroyI2CDevRec(pI2CDevice, TRUE);
443        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
444                    "Failed to allocate working storage for VT1632.\n");
445        goto exit;
446    }
447
448    // Remembering which I2C bus is used for VT1632.
449    pVIAVT1632Rec->VT1632I2CDev = pI2CDevice;
450
451    xf86I2CReadByte(pI2CDevice, 0x06, &buf);
452    pVIAVT1632Rec->DotclockMin = buf * 1000;
453
454    xf86I2CReadByte(pI2CDevice, 0x07, &buf);
455    pVIAVT1632Rec->DotclockMax = (buf + 65) * 1000;
456
457    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Supported VT1632 Dot Clock Range: "
458                "%d to %d MHz\n",
459                pVIAVT1632Rec->DotclockMin / 1000,
460                pVIAVT1632Rec->DotclockMax / 1000);
461
462    /* The code to dynamically designate the particular DVI (i.e., DVI-1,
463     * DVI-2, etc.) for xrandr was borrowed from xf86-video-r128 DDX. */
464    sprintf(outputNameBuffer, "DVI-%d", (pVia->numberDVI + 1));
465    output = xf86OutputCreate(pScrn, &via_vt1632_funcs, outputNameBuffer);
466    if (!output) {
467        free(pVIAVT1632Rec);
468        xf86DestroyI2CDevRec(pI2CDevice, TRUE);
469        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
470                    "Failed to allocate X Server display output record for "
471                    "VT1632.\n");
472        goto exit;
473    }
474
475    output->driver_private = pVIAVT1632Rec;
476
477    /* Since there are two (2) display controllers registered with the
478     * X.Org Server and both IGA1 and IGA2 can handle DVI without any
479     * limitations, possible_crtcs should be set to 0x3 (0b11) so that
480     * either display controller can get assigned to handle DVI. */
481    output->possible_crtcs = (1 << 1) | (1 << 0);
482
483    output->possible_clones = 0;
484    output->interlaceAllowed = FALSE;
485    output->doubleScanAllowed = FALSE;
486
487    viaVT1632DumpRegisters(pScrn, pI2CDevice);
488
489    pVia->numberDVI++;
490    status = TRUE;
491exit:
492    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
493                        "Exiting viaVT1632Init.\n"));
494    return status;
495}
496