/*
 * Copyright © 2007 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Authors:
 *    Zhenyu Wang <zhenyu.z.wang@intel.com>
 *
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "i830.h"

#define SUBSYS_ANY (~0)

#define DMIID_DIR "/sys/class/dmi/id/"
#define DMIID_FILE(x) (DMIID_DIR # x)

typedef struct {
    int chipType;
    int subsysVendor;
    int subsysCard;
    void (*hook)(I830Ptr);
} i830_quirk, *i830_quirk_ptr;

enum i830_dmi_data_t {
    bios_vendor,
    bios_version,
    bios_date,
    sys_vendor,
    product_name,
    product_version,
    product_serial,
    product_uuid,
    board_vendor,
    board_name,
    board_version,
    board_serial,
    board_asset_tag,
    chassis_vendor,
    chassis_type,
    chassis_version,
    chassis_serial,
    chassis_asset_tag,
    dmi_data_max,
};

static char *i830_dmi_data[dmi_data_max];

#define I830_DMI_FIELD_FUNC(field) \
static void i830_dmi_store_##field(void) \
{\
    FILE *f = NULL;\
    f = fopen(DMIID_FILE(field), "r");\
    if (f == NULL) {\
	xfree(i830_dmi_data[field]); i830_dmi_data[field] = NULL;\
	return;\
    }\
    fread(i830_dmi_data[field], 64, 1, f);\
    fclose(f);\
}

I830_DMI_FIELD_FUNC(bios_vendor);
I830_DMI_FIELD_FUNC(bios_version);
I830_DMI_FIELD_FUNC(bios_date);
I830_DMI_FIELD_FUNC(sys_vendor);
I830_DMI_FIELD_FUNC(product_name);
I830_DMI_FIELD_FUNC(product_version);
I830_DMI_FIELD_FUNC(product_serial);
I830_DMI_FIELD_FUNC(product_uuid);
I830_DMI_FIELD_FUNC(board_vendor);
I830_DMI_FIELD_FUNC(board_name);
I830_DMI_FIELD_FUNC(board_version);
I830_DMI_FIELD_FUNC(board_serial);
I830_DMI_FIELD_FUNC(board_asset_tag);
I830_DMI_FIELD_FUNC(chassis_vendor);
I830_DMI_FIELD_FUNC(chassis_type);
I830_DMI_FIELD_FUNC(chassis_version);
I830_DMI_FIELD_FUNC(chassis_serial);
I830_DMI_FIELD_FUNC(chassis_asset_tag);

static void i830_dmi_scan(void)
{
    int i;

    for (i = 0; i < dmi_data_max; i++) {
	i830_dmi_data[i] = xcalloc(64, sizeof(char));
	if (!i830_dmi_data[i]) {
	    int j;
	    for (j = 0; j < i; j++) {
		xfree(i830_dmi_data[j]);
		i830_dmi_data[i] = NULL;
	    }
	    return;
	}
    }

    i830_dmi_store_bios_vendor();
    i830_dmi_store_bios_version();
    i830_dmi_store_bios_date();
    i830_dmi_store_sys_vendor();
    i830_dmi_store_product_name();
    i830_dmi_store_product_version();
    i830_dmi_store_product_serial();
    i830_dmi_store_product_uuid();
    i830_dmi_store_board_vendor();
    i830_dmi_store_board_name();
    i830_dmi_store_board_version();
    i830_dmi_store_board_serial();
    i830_dmi_store_board_asset_tag();
    i830_dmi_store_chassis_vendor();
    i830_dmi_store_chassis_type();
    i830_dmi_store_chassis_version();
    i830_dmi_store_chassis_serial();
    i830_dmi_store_chassis_asset_tag();
}

#define DMIID_DUMP(field) \
    ErrorF("\t" # field ": %s", i830_dmi_data[field] ?\
	    i830_dmi_data[field] : "unknown")

static void i830_dmi_dump(void)
{
    ErrorF("i830_dmi_dump:\n");
    DMIID_DUMP(bios_vendor);
    DMIID_DUMP(bios_version);
    DMIID_DUMP(bios_date);
    DMIID_DUMP(sys_vendor);
    DMIID_DUMP(product_name);
    DMIID_DUMP(product_version);
    DMIID_DUMP(product_serial);
    DMIID_DUMP(product_uuid);
    DMIID_DUMP(board_vendor);
    DMIID_DUMP(board_name);
    DMIID_DUMP(board_version);
    DMIID_DUMP(board_serial);
    DMIID_DUMP(board_asset_tag);
    DMIID_DUMP(chassis_vendor);
    DMIID_DUMP(chassis_type);
    DMIID_DUMP(chassis_version);
    DMIID_DUMP(chassis_serial);
    DMIID_DUMP(chassis_asset_tag);
}

/*
 * Old chips have undocumented panel fitting registers.  Some of them actually
 * work; this quirk indicates that.
 */
static void quirk_pfit_safe (I830Ptr pI830)
{
    pI830->quirk_flag |= QUIRK_PFIT_SAFE;
}

/*
 * Some machines hose the display regs regardless of the ACPI DOS
 * setting, so we need to reset modes at ACPI event time.
 */
static void quirk_reset_modes (I830Ptr pI830)
{
    pI830->quirk_flag |= QUIRK_RESET_MODES;
}

static void quirk_pipea_force (I830Ptr pI830)
{
    pI830->quirk_flag |= QUIRK_PIPEA_FORCE;
}

static void quirk_ignore_tv (I830Ptr pI830)
{
    pI830->quirk_flag |= QUIRK_IGNORE_TV;
}

static void quirk_ignore_lvds (I830Ptr pI830)
{
    pI830->quirk_flag |= QUIRK_IGNORE_LVDS;
}

static void quirk_ignore_crt (I830Ptr pI830)
{
    pI830->quirk_flag |= QUIRK_IGNORE_CRT;
}

static void quirk_mac_mini (I830Ptr pI830)
{
    pI830->quirk_flag |= QUIRK_IGNORE_MACMINI_LVDS;
}

static void quirk_lenovo_tv_dmi (I830Ptr pI830)
{
    /* X60, X60s has no TV output.
     * Z61 has S-video TV output.
     * And they have same subsys ids...
     *
     * http://www-307.ibm.com/pc/support/site.wss/MIGR-45120.html
     * http://www.thinkwiki.org/wiki/List_of_DMI_IDs
     */
    if (!i830_dmi_data[bios_version]) {
	ErrorF("Failed to load DMI info, X60 TV quirk not applied.\n");
	return;
    }
    if (!strncmp(i830_dmi_data[bios_version], "7B", 2) || /* X60, X60s */
	    !strncmp(i830_dmi_data[bios_version], "7E", 2)) /* R60e */
	pI830->quirk_flag |= QUIRK_IGNORE_TV;
}

static void quirk_msi_lvds_dmi (I830Ptr pI830)
{
   /* MSI IM-945GSE-A has no TV output, nor a LVDS connection.
    */
   if (!i830_dmi_data[board_name]) {
       ErrorF("Failed to load DMI info, MSI LVDS quirk not applied.\n");
       return;
   }
   if (!strncmp(i830_dmi_data[board_name],"A9830IMS",8)) {
       pI830->quirk_flag |= QUIRK_IGNORE_LVDS;
       pI830->quirk_flag |= QUIRK_IGNORE_TV;
   }
}

static void quirk_ivch_dvob (I830Ptr pI830)
{
	pI830->quirk_flag |= QUIRK_IVCH_NEED_DVOB;
}

/* keep this list sorted by OEM, then by chip ID */
static i830_quirk i830_quirk_list[] = {
    /* Aopen mini pc */
    { PCI_CHIP_I915_GM, 0xa0a0, SUBSYS_ANY, quirk_ignore_lvds },
    { PCI_CHIP_I945_GM, 0xa0a0, SUBSYS_ANY, quirk_ignore_lvds },
    { PCI_CHIP_I965_GM, 0xa0a0, SUBSYS_ANY, quirk_ignore_lvds },
    { PCI_CHIP_GM45_GM, 0xa0a0, SUBSYS_ANY, quirk_ignore_lvds },

    { PCI_CHIP_I965_GM, 0x8086, 0x1999, quirk_ignore_lvds },

    /* Apple Mac mini has no lvds, but macbook pro does */
    { PCI_CHIP_I945_GM, 0x8086, 0x7270, quirk_mac_mini },

    /* Transtec Senyo 610 mini pc */
    { PCI_CHIP_I965_GM, 0x1509, 0x2f15, quirk_ignore_lvds },

    /* Clevo M720R has no tv output */
    { PCI_CHIP_I965_GM, 0x1558, 0x0721, quirk_ignore_tv },

    /* Dell Latitude X1 */
    { PCI_CHIP_I915_GM, 0x1028, 0x01a3, quirk_ignore_tv },
    /* Dell Latitude X1 / D630 (LP: #197740) */
    { PCI_CHIP_I915_GM, 0x1028, 0x01f9, quirk_ignore_tv },
    /* Dell XPS 1330 */
    { PCI_CHIP_I965_GM, 0x1028, 0x0209, quirk_ignore_tv },
    /* Dell Inspiron 1535 */
    { PCI_CHIP_I965_GM, 0x1028, 0x0254, quirk_ignore_tv },
    /* Dell Inspiron 1735 */
    { PCI_CHIP_I965_GM, 0x1028, 0x0256, quirk_ignore_tv },
    /* Dell Inspiron 1318 */
    { PCI_CHIP_I965_GM, 0x1028, 0x0286, quirk_ignore_tv },
    /* Dell Vostro A840 (LP: #235155) */
    { PCI_CHIP_I965_GM, 0x1028, 0x0298, quirk_ignore_tv },
    /* Dell Studio Hybrid */
    { PCI_CHIP_I965_GM, 0x1028, 0x0279, quirk_ignore_lvds },

    /* Lenovo Napa TV (use dmi)*/
    { PCI_CHIP_I945_GM, 0x17aa, SUBSYS_ANY, quirk_lenovo_tv_dmi },
    /* Lenovo 3000 v200 */
    { PCI_CHIP_I965_GM, 0x17aa, 0x3c18, quirk_ignore_tv },

    /* MSI IM-945GSE-A has no LVDS or TV (use dmi) */
    { PCI_CHIP_I945_GME, 0x8086, 0x27ae, quirk_msi_lvds_dmi },

    /* Panasonic Toughbook CF-Y4 has no TV output */
    { PCI_CHIP_I915_GM, 0x10f7, 0x8338, quirk_ignore_tv },
    /* Panasonic Toughbook CF-Y7 has no TV output */
    { PCI_CHIP_I965_GM, 0x10f7, 0x8338, quirk_ignore_tv },

    /* Toshiba Satellite U300 has no TV output */
    { PCI_CHIP_I965_GM, 0x1179, 0xff50, quirk_ignore_tv },
    /* Toshiba i830M laptop (fix bug 11148) */
    { PCI_CHIP_I830_M, 0x1179, 0xff00, quirk_ivch_dvob },

    /* Motion Computing M1200 reported on irc */
    { PCI_CHIP_I830_M, 0x14c0, 0x0012, quirk_ivch_dvob },

    /* Samsung Q35 has no TV output */
    { PCI_CHIP_I945_GM, 0x144d, 0xc504, quirk_ignore_tv },
    /* Samsung Q45 has no TV output */
    { PCI_CHIP_I965_GM, 0x144d, 0xc510, quirk_ignore_tv },

    /* HP Compaq nx6110 has no TV output */
    { PCI_CHIP_I915_GM, 0x103c, 0x099c, quirk_ignore_tv },
    /* HP Compaq nx6310 has no TV output */
    { PCI_CHIP_I945_GM, 0x103c, 0x30aa, quirk_ignore_tv },
    /* HP Compaq 6730s has no TV output */
    { PCI_CHIP_GM45_GM, 0x103c, 0x30e8, quirk_ignore_tv },
    /* HP Compaq 2730p needs pipe A force quirk (LP: #291555) */
    { PCI_CHIP_GM45_GM, 0x103c, 0x30eb, quirk_pipea_force },

    /* Thinkpad R31 needs pipe A force quirk */
    { PCI_CHIP_I830_M, 0x1014, 0x0505, quirk_pipea_force },
    /* Dell Latitude D400 needs pipe A force quirk (LP: #228519) */
    { PCI_CHIP_I855_GM, 0x1028, 0x0139, quirk_pipea_force },
    /* Dell Latitude D500 needs pipe A force quirk */
    { PCI_CHIP_I855_GM, 0x1028, 0x0152, quirk_pipea_force },
    /* Dell Latitude D505 needs pipe A force quirk (LP: #235643) */
    { PCI_CHIP_I855_GM, 0x1028, 0x0163, quirk_pipea_force },
    /* Dell Latitude X300 needs pipe A force quirk */
    { PCI_CHIP_I855_GM, 0x1028, 0x014f, quirk_pipea_force },
    /* Dell Inspiron 510m needs pipe A force quirk */
    { PCI_CHIP_I855_GM, 0x1028, 0x0164, quirk_pipea_force },
    /* Toshiba Satellite A30 needs pipe A force quirk */
    { PCI_CHIP_I855_GM, 0x1179, 0xff00 , quirk_pipea_force },
    /* Toshiba Protege R-205, S-209 needs pipe A force quirk */
    { PCI_CHIP_I915_GM, 0x1179, 0x0001, quirk_pipea_force },
    /* Intel 855GM hardware (See LP: #216490) */
    { PCI_CHIP_I855_GM, 0x1028, 0x00c8, quirk_pipea_force },
    /* Intel 855GM hardware (See Novell Bugzilla #406123) */
    { PCI_CHIP_I855_GM, 0x10cf, 0x1215, quirk_pipea_force },
    /* HP Pavilion ze4944ea needs pipe A force quirk (See LP: #242389) */
    { PCI_CHIP_I855_GM, 0x103c, 0x3084, quirk_pipea_force },

    { PCI_CHIP_I855_GM, 0x161f, 0x2030, quirk_pfit_safe },

    /* ThinkPad X40 needs pipe A force quirk */
    { PCI_CHIP_I855_GM, 0x1014, 0x0557, quirk_pipea_force },

    /* ThinkPad T60 needs pipe A force quirk (bug #16494) */
    { PCI_CHIP_I945_GM, 0x17aa, 0x201a, quirk_pipea_force },

    /* Sony vaio PCG-r600HFP (fix bug 13722) */
    { PCI_CHIP_I830_M, 0x104d, 0x8100, quirk_ivch_dvob },
    /* Sony vaio VGN-SZ4MN (See LP: #212163) */
    { PCI_CHIP_I830_M, 0x104d, 0x81e6, quirk_pipea_force },
    /* Sony VGC-LT71DB has no VGA output (bug #17395) */
    { PCI_CHIP_I965_GM, 0x104d, 0x9018, quirk_ignore_crt },

    /* Quanta Gigabyte W251U (See LP: #244242) */
    { PCI_CHIP_I945_GM, 0x152d, 0x0755, quirk_pipea_force },

    /* Ordi Enduro UW31 (See LP: #152416) */
    { PCI_CHIP_I945_GM, 0x1584, 0x9900, quirk_ignore_tv },

    /* Dell Latitude D500 needs reset modes quirk */
    { PCI_CHIP_I855_GM, 0x1028, 0x0152, quirk_reset_modes },

    /* Littlebit Sepia X35 (rebranded Asus Z37E) (See LP: #201257) */
    { PCI_CHIP_I965_GM, 0x1043, 0x8265, quirk_ignore_tv },

    /* 855 & before need to leave pipe A & dpll A up */
    { PCI_CHIP_I855_GM, SUBSYS_ANY, SUBSYS_ANY, quirk_pipea_force },
    { PCI_CHIP_845_G, SUBSYS_ANY, SUBSYS_ANY, quirk_pipea_force },

    /* Asus Eee Box has no LVDS */
    { PCI_CHIP_I945_GME, 0x1043, 0x1252, quirk_ignore_lvds },

    { 0, 0, 0, NULL },
};

void i830_fixup_devices(ScrnInfoPtr scrn)
{
    I830Ptr pI830 = I830PTR(scrn);
    i830_quirk_ptr p = i830_quirk_list;
    int i;

    i830_dmi_scan();

    if (0)
	i830_dmi_dump();

    while (p && p->chipType != 0) {
	if (DEVICE_ID(pI830->PciInfo) == p->chipType &&
	    (SUBVENDOR_ID(pI830->PciInfo) == p->subsysVendor ||
	     p->subsysVendor == SUBSYS_ANY) &&
	    (SUBSYS_ID(pI830->PciInfo) == p->subsysCard ||
	     p->subsysCard == SUBSYS_ANY))
	    p->hook(pI830);
	++p;
    }

    for (i = 0; i < dmi_data_max; i++)
	if (i830_dmi_data[i])
	    xfree(i830_dmi_data[i]);
}