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_sii164.h"
34
35static void
36viaSiI164DumpRegisters(ScrnInfoPtr pScrn, I2CDevPtr pDev)
37{
38    int i;
39    CARD8 tmp;
40
41    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
42                        "Entered viaSiI164DumpRegisters.\n"));
43
44    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "SiI 164: dumping registers:\n"));
45    for (i = 0; i <= 0x0f; i++) {
46        xf86I2CReadByte(pDev, i, &tmp);
47        DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO, "SiI 164: 0x%02x: 0x%02x\n", i, tmp));
48    }
49
50    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
51                        "Exiting viaSiI164DumpRegisters.\n"));
52}
53
54static void
55viaSiI164InitRegisters(ScrnInfoPtr pScrn, I2CDevPtr pDev)
56{
57    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
58                        "Entered viaSiI164InitRegisters.\n"));
59
60    xf86I2CWriteByte(pDev, 0x08,
61                        VIA_SII164_VEN | VIA_SII164_HEN |
62                        VIA_SII164_DSEL | VIA_SII164_EDGE | VIA_SII164_PDB);
63
64    /* Route receiver detect bit (Offset 0x09[2]) as the output of
65     * MSEN pin. */
66    xf86I2CWriteByte(pDev, 0x09, 0x20);
67
68    xf86I2CWriteByte(pDev, 0x0A, 0x90);
69
70    xf86I2CWriteByte(pDev, 0x0C, 0x89);
71
72    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
73                        "Exiting viaSiI164InitRegisters.\n"));
74}
75
76/*
77 * Returns TMDS receiver detection state for Silicon Image SiI 164
78 * external TMDS transmitter.
79 */
80static Bool
81viaSiI164Sense(ScrnInfoPtr pScrn, I2CDevPtr pDev)
82{
83    CARD8 tmp;
84    Bool receiverDetected = FALSE;
85
86    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
87                        "Entered viaSiI164Sense.\n"));
88
89    xf86I2CReadByte(pDev, 0x09, &tmp);
90    if (tmp & 0x04) {
91        receiverDetected = TRUE;
92    }
93
94    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
95                "SiI 164 %s a TMDS receiver.\n",
96                receiverDetected ? "detected" : "did not detect");
97
98    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
99                        "Exiting viaSiI164Sense.\n"));
100    return receiverDetected;
101}
102
103static void
104viaSiI164Power(ScrnInfoPtr pScrn, I2CDevPtr pDev, Bool powerState)
105{
106    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
107                        "Entered viaSiI164Power.\n"));
108
109    xf86I2CMaskByte(pDev, 0x08, powerState ? 0x01 : 0x00, 0x01);
110    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "SiI 164 (DVI) Power: %s\n",
111                powerState ? "On" : "Off");
112
113    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
114                        "Exiting viaSiI164Power.\n"));
115}
116
117static void
118viaSiI164SaveRegisters(ScrnInfoPtr pScrn, I2CDevPtr pDev,
119                        viaSiI164RecPtr pSiI164Rec)
120{
121    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
122                        "Entered viaSiI164SaveRegisters.\n"));
123
124    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
125                "Saving SiI 164 registers.\n");
126    xf86I2CReadByte(pDev, 0x08, &pSiI164Rec->Register08);
127    xf86I2CReadByte(pDev, 0x09, &pSiI164Rec->Register09);
128    xf86I2CReadByte(pDev, 0x0A, &pSiI164Rec->Register0A);
129    xf86I2CReadByte(pDev, 0x0C, &pSiI164Rec->Register0C);
130
131    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
132                        "Exiting viaSiI164SaveRegisters.\n"));
133}
134
135static void
136viaSiI164RestoreRegisters(ScrnInfoPtr pScrn, I2CDevPtr pDev,
137                            viaSiI164RecPtr pSiI164Rec)
138{
139    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
140                        "Entered viaSiI164RestoreRegisters.\n"));
141
142    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
143                        "Restoring SiI 164 registers.\n"));
144    xf86I2CWriteByte(pDev, 0x08, pSiI164Rec->Register08);
145    xf86I2CWriteByte(pDev, 0x09, pSiI164Rec->Register09);
146    xf86I2CWriteByte(pDev, 0x0A, pSiI164Rec->Register0A);
147    xf86I2CWriteByte(pDev, 0x0C, pSiI164Rec->Register0C);
148
149    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
150                        "Exiting viaSiI164RestoreRegisters.\n"));
151}
152
153static int
154viaSiI164CheckModeValidity(xf86OutputPtr output, DisplayModePtr pMode)
155{
156    ScrnInfoPtr pScrn = output->scrn;
157    viaSiI164RecPtr pSiI164Rec = output->driver_private;
158    int status = MODE_OK;
159
160    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
161                        "Entered viaSiI164CheckModeValidity.\n"));
162
163    if (pMode->Clock < pSiI164Rec->DotclockMin) {
164        status = MODE_CLOCK_LOW;
165        goto exit;
166    }
167
168    if (pMode->Clock > pSiI164Rec->DotclockMax) {
169        status = MODE_CLOCK_HIGH;
170    }
171
172exit:
173    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
174                        "Exiting viaSiI164CheckModeValidity.\n"));
175    return status;
176}
177
178static void
179via_sii164_create_resources(xf86OutputPtr output)
180{
181}
182
183static void
184via_sii164_dpms(xf86OutputPtr output, int mode)
185{
186    ScrnInfoPtr pScrn = output->scrn;
187    viaSiI164RecPtr pSiI164Rec = output->driver_private;
188
189    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
190                        "Entered via_sii164_dpms.\n"));
191
192    switch (mode) {
193    case DPMSModeOn:
194        viaSiI164Power(pScrn, pSiI164Rec->SiI164I2CDev, TRUE);
195        break;
196    case DPMSModeStandby:
197    case DPMSModeSuspend:
198    case DPMSModeOff:
199        viaSiI164Power(pScrn, pSiI164Rec->SiI164I2CDev, FALSE);
200        break;
201    default:
202        break;
203    }
204
205    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
206                        "Exiting via_sii164_dpms.\n"));
207}
208
209static void
210via_sii164_save(xf86OutputPtr output)
211{
212    ScrnInfoPtr pScrn = output->scrn;
213    viaSiI164RecPtr pSiI164Rec = output->driver_private;
214
215    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
216                        "Entered via_sii164_save.\n"));
217
218    viaSiI164SaveRegisters(pScrn, pSiI164Rec->SiI164I2CDev, pSiI164Rec);
219
220    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
221                        "Exiting via_sii164_save.\n"));
222}
223
224static void
225via_sii164_restore(xf86OutputPtr output)
226{
227    ScrnInfoPtr pScrn = output->scrn;
228    viaSiI164RecPtr pSiI164Rec = output->driver_private;
229
230    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
231                        "Entered via_sii164_restore.\n"));
232
233    viaSiI164RestoreRegisters(pScrn, pSiI164Rec->SiI164I2CDev,
234                                pSiI164Rec);
235
236    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
237                        "Exiting via_sii164_restore.\n"));
238}
239
240static int
241via_sii164_mode_valid(xf86OutputPtr output, DisplayModePtr pMode)
242{
243    return viaSiI164CheckModeValidity(output, pMode);
244}
245
246static Bool
247via_sii164_mode_fixup(xf86OutputPtr output, DisplayModePtr mode,
248                   DisplayModePtr adjusted_mode)
249{
250    return TRUE;
251}
252
253static void
254via_sii164_prepare(xf86OutputPtr output)
255{
256}
257
258static void
259via_sii164_commit(xf86OutputPtr output)
260{
261}
262
263static void
264via_sii164_mode_set(xf86OutputPtr output, DisplayModePtr mode,
265                    DisplayModePtr adjusted_mode)
266{
267    ScrnInfoPtr pScrn = output->scrn;
268    drmmode_crtc_private_ptr iga = output->crtc->driver_private;
269    viaSiI164RecPtr pSiI164Rec = output->driver_private;
270
271    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
272                        "Entered via_sii164_mode_set.\n"));
273
274    if (output->crtc) {
275        viaExtTMDSSetClockDriveStrength(pScrn, 0x03);
276        viaExtTMDSSetDataDriveStrength(pScrn, 0x03);
277        viaExtTMDSEnableIOPads(pScrn, 0x03);
278
279        viaSiI164DumpRegisters(pScrn, pSiI164Rec->SiI164I2CDev);
280        viaSiI164InitRegisters(pScrn, pSiI164Rec->SiI164I2CDev);
281        viaSiI164DumpRegisters(pScrn, pSiI164Rec->SiI164I2CDev);
282
283        viaExtTMDSSetDisplaySource(pScrn, iga->index ? 0x01 : 0x00);
284    }
285
286    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
287                "Exiting via_sii164_mode_set.\n"));
288}
289
290static xf86OutputStatus
291via_sii164_detect(xf86OutputPtr output)
292{
293    xf86MonPtr mon;
294    xf86OutputStatus status = XF86OutputStatusDisconnected;
295    ScrnInfoPtr pScrn = output->scrn;
296    viaSiI164RecPtr pSiI164Rec = output->driver_private;
297
298    /* Check for the DVI presence via SiI 164 first before accessing
299     * I2C bus. */
300    if (viaSiI164Sense(pScrn, pSiI164Rec->SiI164I2CDev)) {
301
302        /* Since DVI presence was established, access the I2C bus
303         * assigned to DVI. */
304        mon = xf86OutputGetEDID(output, pSiI164Rec->SiI164I2CDev->pI2CBus);
305
306        /* Is the interface type digital? */
307        if (mon && DIGITAL(mon->features.input_type)) {
308            status = XF86OutputStatusConnected;
309            xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
310                        "Detected a monitor connected to DVI.\n");
311            xf86OutputSetEDID(output, mon);
312        } else {
313            xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
314                        "Could not obtain EDID from a monitor "
315                        "connected to DVI.\n");
316        }
317    }
318
319    return status;
320}
321
322#ifdef RANDR_12_INTERFACE
323static Bool
324via_sii164_set_property(xf86OutputPtr output, Atom property,
325                     RRPropertyValuePtr value)
326{
327    return TRUE;
328}
329#endif
330
331#ifdef RANDR_13_INTERFACE
332static Bool
333via_sii164_get_property(xf86OutputPtr output, Atom property)
334{
335    return FALSE;
336}
337#endif
338
339static void
340via_sii164_destroy(xf86OutputPtr output)
341{
342}
343
344const xf86OutputFuncsRec via_sii164_funcs = {
345    .create_resources   = via_sii164_create_resources,
346    .dpms               = via_sii164_dpms,
347    .save               = via_sii164_save,
348    .restore            = via_sii164_restore,
349    .mode_valid         = via_sii164_mode_valid,
350    .mode_fixup         = via_sii164_mode_fixup,
351    .prepare            = via_sii164_prepare,
352    .commit             = via_sii164_commit,
353    .mode_set           = via_sii164_mode_set,
354    .detect             = via_sii164_detect,
355    .get_modes          = xf86OutputGetEDIDModes,
356#ifdef RANDR_12_INTERFACE
357    .set_property       = via_sii164_set_property,
358#endif
359#ifdef RANDR_13_INTERFACE
360    .get_property       = via_sii164_get_property,
361#endif
362    .destroy            = via_sii164_destroy,
363};
364
365Bool
366viaSiI164Init(ScrnInfoPtr pScrn, I2CBusPtr pI2CBus)
367{
368    xf86OutputPtr output;
369    VIAPtr pVia = VIAPTR(pScrn);
370    viaSiI164RecPtr pSiI164Rec = NULL;
371    I2CDevPtr pI2CDevice = NULL;
372    I2CSlaveAddr i2cAddr = 0x70;
373    CARD8 buf;
374    CARD16 vendorID, deviceID;
375    Bool status = FALSE;
376    char outputNameBuffer[32];
377
378    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
379                        "Entered viaSiI164Init.\n"));
380
381    if (!xf86I2CProbeAddress(pI2CBus, i2cAddr)) {
382        xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
383                    "I2C device not found.\n");
384        goto exit;
385    }
386
387    pI2CDevice = xf86CreateI2CDevRec();
388    if (!pI2CDevice) {
389        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
390                    "Failed to create an I2C bus device record.\n");
391        goto exit;
392    }
393
394    pI2CDevice->DevName = "SiI 164";
395    pI2CDevice->SlaveAddr = i2cAddr;
396    pI2CDevice->pI2CBus = pI2CBus;
397    if (!xf86I2CDevInit(pI2CDevice)) {
398        xf86DestroyI2CDevRec(pI2CDevice, TRUE);
399        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
400                    "Failed to initialize a device on I2C bus.\n");
401        goto exit;
402    }
403
404    xf86I2CReadByte(pI2CDevice, 0, &buf);
405    vendorID = buf;
406    xf86I2CReadByte(pI2CDevice, 1, &buf);
407    vendorID |= buf << 8;
408    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
409                        "Vendor ID: 0x%04x\n", vendorID));
410
411    xf86I2CReadByte(pI2CDevice, 2, &buf);
412    deviceID = buf;
413    xf86I2CReadByte(pI2CDevice, 3, &buf);
414    deviceID |= buf << 8;
415    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
416                        "Device ID: 0x%04x\n", deviceID));
417
418    if ((vendorID != 0x0001) || (deviceID != 0x0006)) {
419        xf86DestroyI2CDevRec(pI2CDevice, TRUE);
420        xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
421                    "SiI 164 external TMDS transmitter not detected.\n");
422        goto exit;
423    }
424
425    xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
426                "SiI 164 external TMDS transmitter detected.\n");
427
428    pSiI164Rec = xnfcalloc(1, sizeof(viaSiI164Rec));
429    if (!pSiI164Rec) {
430        xf86DestroyI2CDevRec(pI2CDevice, TRUE);
431        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
432                    "Failed to allocate working storage for SiI 164.\n");
433        goto exit;
434    }
435
436    // Remembering which I2C bus is used for SiI 164.
437    pSiI164Rec->SiI164I2CDev = pI2CDevice;
438
439    xf86I2CReadByte(pI2CDevice, 0x06, &buf);
440    pSiI164Rec->DotclockMin = buf * 1000;
441
442    xf86I2CReadByte(pI2CDevice, 0x07, &buf);
443    pSiI164Rec->DotclockMax = (buf + 65) * 1000;
444
445    xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Supported SiI 164 Dot Clock Range: "
446                "%d to %d MHz\n",
447                pSiI164Rec->DotclockMin / 1000,
448                pSiI164Rec->DotclockMax / 1000);
449
450    /* The code to dynamically designate the particular DVI (i.e., DVI-1,
451     * DVI-2, etc.) for xrandr was borrowed from xf86-video-r128 DDX. */
452    sprintf(outputNameBuffer, "DVI-%d", (pVia->numberDVI + 1));
453    output = xf86OutputCreate(pScrn, &via_sii164_funcs, outputNameBuffer);
454    if (!output) {
455        free(pSiI164Rec);
456        xf86DestroyI2CDevRec(pI2CDevice, TRUE);
457        xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
458                    "Failed to allocate X Server display output record for "
459                    "SiI 164.\n");
460        goto exit;
461    }
462
463    output->driver_private = pSiI164Rec;
464
465    /* Since there are two (2) display controllers registered with the
466     * X.Org Server and both IGA1 and IGA2 can handle DVI without any
467     * limitations, possible_crtcs should be set to 0x3 (0b11) so that
468     * either display controller can get assigned to handle DVI. */
469    output->possible_crtcs = (1 << 1) | (1 << 0);
470
471    output->possible_clones = 0;
472    output->interlaceAllowed = FALSE;
473    output->doubleScanAllowed = FALSE;
474
475    viaSiI164DumpRegisters(pScrn, pI2CDevice);
476
477    pVia->numberDVI++;
478    status = TRUE;
479exit:
480    DEBUG(xf86DrvMsg(pScrn->scrnIndex, X_INFO,
481                        "Exiting viaSiI164Init.\n"));
482    return status;
483}
484