/* * Copyright 2011 Red Hat Inc. * * 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 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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: Ben Skeggs */ #include #include "drmP.h" #include "nouveau_drv.h" #define MXM_DBG(dev, fmt, args...) NV_DEBUG((dev), "MXM: " fmt, ##args) #define MXM_MSG(dev, fmt, args...) NV_INFO((dev), "MXM: " fmt, ##args) static u8 * mxms_data(struct drm_device *dev) { struct drm_nouveau_private *dev_priv = dev->dev_private; return dev_priv->mxms; } static u16 mxms_version(struct drm_device *dev) { u8 *mxms = mxms_data(dev); u16 version = (mxms[4] << 8) | mxms[5]; switch (version ) { case 0x0200: case 0x0201: case 0x0300: return version; default: break; } MXM_DBG(dev, "unknown version %d.%d\n", mxms[4], mxms[5]); return 0x0000; } static u16 mxms_headerlen(struct drm_device *dev) { return 8; } static u16 mxms_structlen(struct drm_device *dev) { return *(u16 *)&mxms_data(dev)[6]; } static bool mxms_checksum(struct drm_device *dev) { u16 size = mxms_headerlen(dev) + mxms_structlen(dev); u8 *mxms = mxms_data(dev), sum = 0; while (size--) sum += *mxms++; if (sum) { MXM_DBG(dev, "checksum invalid\n"); return false; } return true; } static bool mxms_valid(struct drm_device *dev) { u8 *mxms = mxms_data(dev); if (*(u32 *)mxms != 0x5f4d584d) { MXM_DBG(dev, "signature invalid\n"); return false; } if (!mxms_version(dev) || !mxms_checksum(dev)) return false; return true; } static bool mxms_foreach(struct drm_device *dev, u8 types, bool (*exec)(struct drm_device *, u8 *, void *), void *info) { u8 *mxms = mxms_data(dev); u8 *desc = mxms + mxms_headerlen(dev); u8 *fini = desc + mxms_structlen(dev) - 1; while (desc < fini) { u8 type = desc[0] & 0x0f; u8 headerlen = 0; u8 recordlen = 0; u8 entries = 0; switch (type) { case 0: /* Output Device Structure */ if (mxms_version(dev) >= 0x0300) headerlen = 8; else headerlen = 6; break; case 1: /* System Cooling Capability Structure */ case 2: /* Thermal Structure */ case 3: /* Input Power Structure */ headerlen = 4; break; case 4: /* GPIO Device Structure */ headerlen = 4; recordlen = 2; entries = (ROM32(desc[0]) & 0x01f00000) >> 20; break; case 5: /* Vendor Specific Structure */ headerlen = 8; break; case 6: /* Backlight Control Structure */ if (mxms_version(dev) >= 0x0300) { headerlen = 4; recordlen = 8; entries = (desc[1] & 0xf0) >> 4; } else { headerlen = 8; } break; case 7: /* Fan Control Structure */ headerlen = 8; recordlen = 4; entries = desc[1] & 0x07; break; default: MXM_DBG(dev, "unknown descriptor type %d\n", type); return false; } if ((drm_debug & DRM_UT_DRIVER) && (exec == NULL)) { static const char * mxms_desc_name[] = { "ODS", "SCCS", "TS", "IPS", "GSD", "VSS", "BCS", "FCS", }; u8 *dump = desc; int i, j; MXM_DBG(dev, "%4s: ", mxms_desc_name[type]); for (j = headerlen - 1; j >= 0; j--) printk("%02x", dump[j]); printk("\n"); dump += headerlen; for (i = 0; i < entries; i++, dump += recordlen) { MXM_DBG(dev, " "); for (j = recordlen - 1; j >= 0; j--) printk("%02x", dump[j]); printk("\n"); } } if (types & (1 << type)) { if (!exec(dev, desc, info)) return false; } desc += headerlen + (entries * recordlen); } return true; } static u8 * mxm_table(struct drm_device *dev, u8 *size) { struct bit_entry x; if (bit_table(dev, 'x', &x)) { MXM_DBG(dev, "BIT 'x' table not present\n"); return NULL; } if (x.version != 1 || x.length < 3) { MXM_MSG(dev, "BIT x table %d/%d unknown\n", x.version, x.length); return NULL; } *size = x.length; return x.data; } /* These map MXM v2.x digital connection values to the appropriate SOR/link, * hopefully they're correct for all boards within the same chipset... * * MXM v3.x VBIOS are nicer and provide pointers to these tables. */ static u8 nv84_sor_map[16] = { 0x00, 0x12, 0x22, 0x11, 0x32, 0x31, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static u8 nv92_sor_map[16] = { 0x00, 0x12, 0x22, 0x11, 0x32, 0x31, 0x11, 0x31, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static u8 nv94_sor_map[16] = { 0x00, 0x14, 0x24, 0x11, 0x34, 0x31, 0x11, 0x31, 0x11, 0x31, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00 }; static u8 nv96_sor_map[16] = { 0x00, 0x14, 0x24, 0x00, 0x34, 0x00, 0x11, 0x31, 0x11, 0x31, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00 }; static u8 nv98_sor_map[16] = { 0x00, 0x14, 0x12, 0x11, 0x00, 0x31, 0x11, 0x31, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static u8 mxm_sor_map(struct drm_device *dev, u8 conn) { struct drm_nouveau_private *dev_priv = dev->dev_private; u8 len, *mxm = mxm_table(dev, &len); if (mxm && len >= 6) { u8 *map = ROMPTR(dev, mxm[4]); if (map) { if (map[0] == 0x10) { if (conn < map[3]) return map[map[1] + conn]; return 0x00; } MXM_MSG(dev, "unknown sor map 0x%02x\n", map[0]); } } if (dev_priv->chipset == 0x84 || dev_priv->chipset == 0x86) return nv84_sor_map[conn]; if (dev_priv->chipset == 0x92) return nv92_sor_map[conn]; if (dev_priv->chipset == 0x94) return nv94_sor_map[conn]; if (dev_priv->chipset == 0x96) return nv96_sor_map[conn]; if (dev_priv->chipset == 0x98) return nv98_sor_map[conn]; MXM_MSG(dev, "missing sor map\n"); return 0x00; } static u8 mxm_ddc_map(struct drm_device *dev, u8 port) { u8 len, *mxm = mxm_table(dev, &len); if (mxm && len >= 8) { u8 *map = ROMPTR(dev, mxm[6]); if (map) { if (map[0] == 0x10) { if (port < map[3]) return map[map[1] + port]; return 0x00; } MXM_MSG(dev, "unknown ddc map 0x%02x\n", map[0]); } } /* v2.x: directly write port as dcb i2cidx */ return (port << 4) | port; } struct mxms_odev { u8 outp_type; u8 conn_type; u8 ddc_port; u8 dig_conn; }; static void mxms_output_device(struct drm_device *dev, u8 *pdata, struct mxms_odev *desc) { u64 data = ROM32(pdata[0]); if (mxms_version(dev) >= 0x0300) data |= (u64)ROM16(pdata[4]) << 32; desc->outp_type = (data & 0x00000000000000f0ULL) >> 4; desc->ddc_port = (data & 0x0000000000000f00ULL) >> 8; desc->conn_type = (data & 0x000000000001f000ULL) >> 12; desc->dig_conn = (data & 0x0000000000780000ULL) >> 19; } struct context { u32 *outp; struct mxms_odev desc; }; static bool mxm_match_tmds_partner(struct drm_device *dev, u8 *data, void *info) { struct context *ctx = info; struct mxms_odev desc; mxms_output_device(dev, data, &desc); if (desc.outp_type == 2 && desc.dig_conn == ctx->desc.dig_conn) return false; return true; } static bool mxm_match_dcb(struct drm_device *dev, u8 *data, void *info) { struct context *ctx = info; u64 desc = *(u64 *)data; mxms_output_device(dev, data, &ctx->desc); /* match dcb encoder type to mxm-ods device type */ if ((ctx->outp[0] & 0x0000000f) != ctx->desc.outp_type) return true; /* digital output, have some extra stuff to match here, there's a * table in the vbios that provides a mapping from the mxm digital * connection enum values to SOR/link */ if ((desc & 0x00000000000000f0) >= 0x20) { /* check against sor index */ u8 link = mxm_sor_map(dev, ctx->desc.dig_conn); if ((ctx->outp[0] & 0x0f000000) != (link & 0x0f) << 24) return true; /* check dcb entry has a compatible link field */ link = (link & 0x30) >> 4; if ((link & ((ctx->outp[1] & 0x00000030) >> 4)) != link) return true; } /* mark this descriptor accounted for by setting invalid device type, * except of course some manufactures don't follow specs properly and * we need to avoid killing off the TMDS function on DP connectors * if MXM-SIS is missing an entry for it. */ data[0] &= ~0xf0; if (ctx->desc.outp_type == 6 && ctx->desc.conn_type == 6 && mxms_foreach(dev, 0x01, mxm_match_tmds_partner, ctx)) { data[0] |= 0x20; /* modify descriptor to match TMDS now */ } else { data[0] |= 0xf0; } return false; } static int mxm_dcb_sanitise_entry(struct drm_device *dev, void *data, int idx, u8 *dcbe) { struct context ctx = { .outp = (u32 *)dcbe }; u8 type, i2cidx, link; u8 *conn; /* look for an output device structure that matches this dcb entry. * if one isn't found, disable it. */ if (mxms_foreach(dev, 0x01, mxm_match_dcb, &ctx)) { MXM_DBG(dev, "disable %d: 0x%08x 0x%08x\n", idx, ctx.outp[0], ctx.outp[1]); ctx.outp[0] |= 0x0000000f; return 0; } /* modify the output's ddc/aux port, there's a pointer to a table * with the mapping from mxm ddc/aux port to dcb i2c_index in the * vbios mxm table */ i2cidx = mxm_ddc_map(dev, ctx.desc.ddc_port); if ((ctx.outp[0] & 0x0000000f) != OUTPUT_DP) i2cidx = (i2cidx & 0x0f) << 4; else i2cidx = (i2cidx & 0xf0); if (i2cidx != 0xf0) { ctx.outp[0] &= ~0x000000f0; ctx.outp[0] |= i2cidx; } /* override dcb sorconf.link, based on what mxm data says */ switch (ctx.desc.outp_type) { case 0x00: /* Analog CRT */ case 0x01: /* Analog TV/HDTV */ break; default: link = mxm_sor_map(dev, ctx.desc.dig_conn) & 0x30; ctx.outp[1] &= ~0x00000030; ctx.outp[1] |= link; break; } /* we may need to fixup various other vbios tables based on what * the descriptor says the connector type should be. * * in a lot of cases, the vbios tables will claim DVI-I is possible, * and the mxm data says the connector is really HDMI. another * common example is DP->eDP. */ conn = dcb_conn(dev, (ctx.outp[0] & 0x0000f000) >> 12); type = conn[0]; switch (ctx.desc.conn_type) { case 0x01: /* LVDS */ ctx.outp[1] |= 0x00000004; /* use_power_scripts */ /* XXX: modify default link width in LVDS table */ break; case 0x02: /* HDMI */ type = DCB_CONNECTOR_HDMI_1; break; case 0x03: /* DVI-D */ type = DCB_CONNECTOR_DVI_D; break; case 0x0e: /* eDP, falls through to DPint */ ctx.outp[1] |= 0x00010000; case 0x07: /* DP internal, wtf is this?? HP8670w */ ctx.outp[1] |= 0x00000004; /* use_power_scripts? */ type = DCB_CONNECTOR_eDP; break; default: break; } if (mxms_version(dev) >= 0x0300) conn[0] = type; return 0; } static bool mxm_show_unmatched(struct drm_device *dev, u8 *data, void *info) { u64 desc = *(u64 *)data; if ((desc & 0xf0) != 0xf0) MXM_MSG(dev, "unmatched output device 0x%016llx\n", desc); return true; } static void mxm_dcb_sanitise(struct drm_device *dev) { u8 *dcb = dcb_table(dev); if (!dcb || dcb[0] != 0x40) { MXM_DBG(dev, "unsupported DCB version\n"); return; } dcb_outp_foreach(dev, NULL, mxm_dcb_sanitise_entry); mxms_foreach(dev, 0x01, mxm_show_unmatched, NULL); } static bool mxm_shadow_rom_fetch(struct nouveau_i2c_chan *i2c, u8 addr, u8 offset, u8 size, u8 *data) { struct i2c_msg msgs[] = { { .addr = addr, .flags = 0, .len = 1, .buf = &offset }, { .addr = addr, .flags = I2C_M_RD, .len = size, .buf = data, }, }; return i2c_transfer(&i2c->adapter, msgs, 2) == 2; } static bool mxm_shadow_rom(struct drm_device *dev, u8 version) { struct drm_nouveau_private *dev_priv = dev->dev_private; struct nouveau_i2c_chan *i2c = NULL; u8 i2cidx, mxms[6], addr, size; i2cidx = mxm_ddc_map(dev, 1 /* LVDS_DDC */) & 0x0f; if (i2cidx < 0x0f) i2c = nouveau_i2c_find(dev, i2cidx); if (!i2c) return false; addr = 0x54; if (!mxm_shadow_rom_fetch(i2c, addr, 0, 6, mxms)) { addr = 0x56; if (!mxm_shadow_rom_fetch(i2c, addr, 0, 6, mxms)) return false; } dev_priv->mxms = mxms; size = mxms_headerlen(dev) + mxms_structlen(dev); dev_priv->mxms = kmalloc(size, GFP_KERNEL); if (dev_priv->mxms && mxm_shadow_rom_fetch(i2c, addr, 0, size, dev_priv->mxms)) return true; kfree(dev_priv->mxms); dev_priv->mxms = NULL; return false; } #if defined(CONFIG_ACPI) static bool mxm_shadow_dsm(struct drm_device *dev, u8 version) { struct drm_nouveau_private *dev_priv = dev->dev_private; static char muid[] = { 0x00, 0xA4, 0x04, 0x40, 0x7D, 0x91, 0xF2, 0x4C, 0xB8, 0x9C, 0x79, 0xB6, 0x2F, 0xD5, 0x56, 0x65 }; u32 mxms_args[] = { 0x00000000 }; union acpi_object args[4] = { /* _DSM MUID */ { .buffer.type = 3, .buffer.length = sizeof(muid), .buffer.pointer = muid, }, /* spec says this can be zero to mean "highest revision", but * of course there's at least one bios out there which fails * unless you pass in exactly the version it supports.. */ { .integer.type = ACPI_TYPE_INTEGER, .integer.value = (version & 0xf0) << 4 | (version & 0x0f), }, /* MXMS function */ { .integer.type = ACPI_TYPE_INTEGER, .integer.value = 0x00000010, }, /* Pointer to MXMS arguments */ { .buffer.type = ACPI_TYPE_BUFFER, .buffer.length = sizeof(mxms_args), .buffer.pointer = (char *)mxms_args, }, }; struct acpi_object_list list = { ARRAY_SIZE(args), args }; struct acpi_buffer retn = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; acpi_handle handle; int ret; handle = DEVICE_ACPI_HANDLE(&dev->pdev->dev); if (!handle) return false; ret = acpi_evaluate_object(handle, "_DSM", &list, &retn); if (ret) { MXM_DBG(dev, "DSM MXMS failed: %d\n", ret); return false; } obj = retn.pointer; if (obj->type == ACPI_TYPE_BUFFER) { dev_priv->mxms = kmemdup(obj->buffer.pointer, obj->buffer.length, GFP_KERNEL); } else if (obj->type == ACPI_TYPE_INTEGER) { MXM_DBG(dev, "DSM MXMS returned 0x%llx\n", obj->integer.value); } kfree(obj); return dev_priv->mxms != NULL; } #endif #if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE) #define WMI_WMMX_GUID "F6CB5C3C-9CAE-4EBD-B577-931EA32A2CC0" static u8 wmi_wmmx_mxmi(struct drm_device *dev, u8 version) { u32 mxmi_args[] = { 0x494D584D /* MXMI */, version, 0 }; struct acpi_buffer args = { sizeof(mxmi_args), mxmi_args }; struct acpi_buffer retn = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; acpi_status status; status = wmi_evaluate_method(WMI_WMMX_GUID, 0, 0, &args, &retn); if (ACPI_FAILURE(status)) { MXM_DBG(dev, "WMMX MXMI returned %d\n", status); return 0x00; } obj = retn.pointer; if (obj->type == ACPI_TYPE_INTEGER) { version = obj->integer.value; MXM_DBG(dev, "WMMX MXMI version %d.%d\n", (version >> 4), version & 0x0f); } else { version = 0; MXM_DBG(dev, "WMMX MXMI returned non-integer\n"); } kfree(obj); return version; } static bool mxm_shadow_wmi(struct drm_device *dev, u8 version) { struct drm_nouveau_private *dev_priv = dev->dev_private; u32 mxms_args[] = { 0x534D584D /* MXMS */, version, 0 }; struct acpi_buffer args = { sizeof(mxms_args), mxms_args }; struct acpi_buffer retn = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; acpi_status status; if (!wmi_has_guid(WMI_WMMX_GUID)) { MXM_DBG(dev, "WMMX GUID not found\n"); return false; } mxms_args[1] = wmi_wmmx_mxmi(dev, 0x00); if (!mxms_args[1]) mxms_args[1] = wmi_wmmx_mxmi(dev, version); if (!mxms_args[1]) return false; status = wmi_evaluate_method(WMI_WMMX_GUID, 0, 0, &args, &retn); if (ACPI_FAILURE(status)) { MXM_DBG(dev, "WMMX MXMS returned %d\n", status); return false; } obj = retn.pointer; if (obj->type == ACPI_TYPE_BUFFER) { dev_priv->mxms = kmemdup(obj->buffer.pointer, obj->buffer.length, GFP_KERNEL); } kfree(obj); return dev_priv->mxms != NULL; } #endif struct mxm_shadow_h { const char *name; bool (*exec)(struct drm_device *, u8 version); } _mxm_shadow[] = { { "ROM", mxm_shadow_rom }, #if defined(CONFIG_ACPI) { "DSM", mxm_shadow_dsm }, #endif #if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE) { "WMI", mxm_shadow_wmi }, #endif {} }; static int mxm_shadow(struct drm_device *dev, u8 version) { struct drm_nouveau_private *dev_priv = dev->dev_private; struct mxm_shadow_h *shadow = _mxm_shadow; do { MXM_DBG(dev, "checking %s\n", shadow->name); if (shadow->exec(dev, version)) { if (mxms_valid(dev)) return 0; kfree(dev_priv->mxms); dev_priv->mxms = NULL; } } while ((++shadow)->name); return -ENOENT; } int nouveau_mxm_init(struct drm_device *dev) { u8 mxm_size, *mxm = mxm_table(dev, &mxm_size); if (!mxm || !mxm[0]) { MXM_MSG(dev, "no VBIOS data, nothing to do\n"); return 0; } MXM_MSG(dev, "BIOS version %d.%d\n", mxm[0] >> 4, mxm[0] & 0x0f); if (mxm_shadow(dev, mxm[0])) { MXM_MSG(dev, "failed to locate valid SIS\n"); #if 0 /* we should, perhaps, fall back to some kind of limited * mode here if the x86 vbios hasn't already done the * work for us (so we prevent loading with completely * whacked vbios tables). */ return -EINVAL; #else return 0; #endif } MXM_MSG(dev, "MXMS Version %d.%d\n", mxms_version(dev) >> 8, mxms_version(dev) & 0xff); mxms_foreach(dev, 0, NULL, NULL); if (nouveau_mxmdcb) mxm_dcb_sanitise(dev); return 0; } void nouveau_mxm_fini(struct drm_device *dev) { struct drm_nouveau_private *dev_priv = dev->dev_private; kfree(dev_priv->mxms); dev_priv->mxms = NULL; }