Commit b4c26818 authored by Ben Skeggs's avatar Ben Skeggs

drm/nouveau/mxm: initial implementation of dcb sanitisation

The DCB table provided by the VBIOS on most MXM chips has a number of
entries which either need to be disabled, or modified according to the
MXM-SIS Output Device Descriptors.

The x86 vbios code usually takes care of this for us, however, with the
large number of laptops now with switchable graphics or optimus, a lot
of the time nouveau is responsible for POSTing the card instead - leaving
some fun situations like, plugging in a monitor and having nouveau decide
3 connectors actually just got plugged in..

No MXM-SIS fetching methods implemented yet.
Signed-off-by: default avatarBen Skeggs <bskeggs@redhat.com>
parent befb51e9
......@@ -11,7 +11,7 @@ nouveau-y := nouveau_drv.o nouveau_state.o nouveau_channel.o nouveau_mem.o \
nouveau_display.o nouveau_connector.o nouveau_fbcon.o \
nouveau_hdmi.o nouveau_dp.o nouveau_ramht.o \
nouveau_pm.o nouveau_volt.o nouveau_perf.o nouveau_temp.o \
nouveau_mm.o nouveau_vm.o \
nouveau_mm.o nouveau_vm.o nouveau_mxm.o \
nv04_timer.o \
nv04_mc.o nv40_mc.o nv50_mc.o \
nv04_fb.o nv10_fb.o nv30_fb.o nv40_fb.o nv50_fb.o nvc0_fb.o \
......
......@@ -5382,6 +5382,9 @@ bit_table(struct drm_device *dev, u8 id, struct bit_entry *bit)
struct nvbios *bios = &dev_priv->vbios;
u8 entries, *entry;
if (bios->type != NVBIOS_BIT)
return -ENODEV;
entries = bios->data[bios->offset + 10];
entry = &bios->data[bios->offset + 12];
while (entries--) {
......@@ -5832,7 +5835,7 @@ dcb_table(struct drm_device *dev)
return NULL;
}
u8 *
void *
dcb_outp(struct drm_device *dev, u8 idx)
{
u8 *dcb = dcb_table(dev);
......@@ -6663,6 +6666,10 @@ nouveau_bios_init(struct drm_device *dev)
if (ret)
return ret;
ret = nouveau_mxm_init(dev);
if (ret)
return ret;
ret = parse_dcb_table(dev, bios);
if (ret)
return ret;
......@@ -6703,5 +6710,6 @@ nouveau_bios_init(struct drm_device *dev)
void
nouveau_bios_takedown(struct drm_device *dev)
{
nouveau_mxm_fini(dev);
nouveau_i2c_fini(dev);
}
......@@ -315,7 +315,7 @@ struct nvbios {
};
void *dcb_table(struct drm_device *);
u8 *dcb_outp(struct drm_device *, u8 idx);
void *dcb_outp(struct drm_device *, u8 idx);
int dcb_outp_foreach(struct drm_device *, void *data,
int (*)(struct drm_device *, void *, int idx, u8 *outp));
u8 *dcb_conntab(struct drm_device *);
......
......@@ -124,6 +124,10 @@ MODULE_PARM_DESC(ctxfw, "Use external HUB/GPC ucode (fermi)\n");
int nouveau_ctxfw;
module_param_named(ctxfw, nouveau_ctxfw, int, 0400);
MODULE_PARM_DESC(ctxfw, "Santise DCB table according to MXM-SIS\n");
int nouveau_mxmdcb = 1;
module_param_named(mxmdcb, nouveau_mxmdcb, int, 0400);
int nouveau_fbpercrtc;
#if 0
module_param_named(fbpercrtc, nouveau_fbpercrtc, int, 0400);
......
......@@ -793,6 +793,7 @@ struct drm_nouveau_private {
struct nouveau_vm *chan_vm;
struct nvbios vbios;
u8 *mxms;
struct list_head i2c_ports;
struct nv04_mode_state mode_reg;
......@@ -864,6 +865,7 @@ extern char *nouveau_perflvl;
extern int nouveau_perflvl_wr;
extern int nouveau_msi;
extern int nouveau_ctxfw;
extern int nouveau_mxmdcb;
extern int nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state);
extern int nouveau_pci_resume(struct pci_dev *pdev);
......@@ -1108,6 +1110,10 @@ extern int call_lvds_script(struct drm_device *, struct dcb_entry *, int head,
enum LVDS_script, int pxclk);
bool bios_encoder_match(struct dcb_entry *, u32 hash);
/* nouveau_mxm.c */
int nouveau_mxm_init(struct drm_device *dev);
void nouveau_mxm_fini(struct drm_device *dev);
/* nouveau_ttm.c */
int nouveau_ttm_global_init(struct drm_nouveau_private *);
void nouveau_ttm_global_release(struct drm_nouveau_private *);
......
/*
* 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 "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(struct drm_device *dev)
{
return false;
}
static bool
mxm_shadow_dsm(struct drm_device *dev)
{
return false;
}
struct mxm_shadow_h {
const char *name;
bool (*exec)(struct drm_device *);
} _mxm_shadow[] = {
{ "ROM", mxm_shadow_rom },
{ "DSM", mxm_shadow_dsm },
{}
};
static int
mxm_shadow(struct drm_device *dev)
{
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)) {
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_MSG(dev, "failed to locate valid SIS\n");
return -EINVAL;
}
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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment