| /* | |
| * Copyright 2012 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 <subdev/bios.h> | |
| #include <subdev/bios/dcb.h> | |
| u16 | |
| dcb_table(struct nvkm_bios *bios, u8 *ver, u8 *hdr, u8 *cnt, u8 *len) | |
| { | |
| struct nvkm_subdev *subdev = &bios->subdev; | |
| struct nvkm_device *device = subdev->device; | |
| u16 dcb = 0x0000; | |
| if (device->card_type > NV_04) | |
| dcb = nvbios_rd16(bios, 0x36); | |
| if (!dcb) { | |
| nvkm_warn(subdev, "DCB table not found\n"); | |
| return dcb; | |
| } | |
| *ver = nvbios_rd08(bios, dcb); | |
| if (*ver >= 0x42) { | |
| nvkm_warn(subdev, "DCB version 0x%02x unknown\n", *ver); | |
| return 0x0000; | |
| } else | |
| if (*ver >= 0x30) { | |
| if (nvbios_rd32(bios, dcb + 6) == 0x4edcbdcb) { | |
| *hdr = nvbios_rd08(bios, dcb + 1); | |
| *cnt = nvbios_rd08(bios, dcb + 2); | |
| *len = nvbios_rd08(bios, dcb + 3); | |
| return dcb; | |
| } | |
| } else | |
| if (*ver >= 0x20) { | |
| if (nvbios_rd32(bios, dcb + 4) == 0x4edcbdcb) { | |
| u16 i2c = nvbios_rd16(bios, dcb + 2); | |
| *hdr = 8; | |
| *cnt = (i2c - dcb) / 8; | |
| *len = 8; | |
| return dcb; | |
| } | |
| } else | |
| if (*ver >= 0x15) { | |
| if (!nvbios_memcmp(bios, dcb - 7, "DEV_REC", 7)) { | |
| u16 i2c = nvbios_rd16(bios, dcb + 2); | |
| *hdr = 4; | |
| *cnt = (i2c - dcb) / 10; | |
| *len = 10; | |
| return dcb; | |
| } | |
| } else { | |
| /* | |
| * v1.4 (some NV15/16, NV11+) seems the same as v1.5, but | |
| * always has the same single (crt) entry, even when tv-out | |
| * present, so the conclusion is this version cannot really | |
| * be used. | |
| * | |
| * v1.2 tables (some NV6/10, and NV15+) normally have the | |
| * same 5 entries, which are not specific to the card and so | |
| * no use. | |
| * | |
| * v1.2 does have an I2C table that read_dcb_i2c_table can | |
| * handle, but cards exist (nv11 in #14821) with a bad i2c | |
| * table pointer, so use the indices parsed in | |
| * parse_bmp_structure. | |
| * | |
| * v1.1 (NV5+, maybe some NV4) is entirely unhelpful | |
| */ | |
| nvkm_debug(subdev, "DCB contains no useful data\n"); | |
| return 0x0000; | |
| } | |
| nvkm_warn(subdev, "DCB header validation failed\n"); | |
| return 0x0000; | |
| } | |
| u16 | |
| dcb_outp(struct nvkm_bios *bios, u8 idx, u8 *ver, u8 *len) | |
| { | |
| u8 hdr, cnt; | |
| u16 dcb = dcb_table(bios, ver, &hdr, &cnt, len); | |
| if (dcb && idx < cnt) | |
| return dcb + hdr + (idx * *len); | |
| return 0x0000; | |
| } | |
| static inline u16 | |
| dcb_outp_hasht(struct dcb_output *outp) | |
| { | |
| return (outp->extdev << 8) | (outp->location << 4) | outp->type; | |
| } | |
| static inline u16 | |
| dcb_outp_hashm(struct dcb_output *outp) | |
| { | |
| return (outp->heads << 8) | (outp->link << 6) | outp->or; | |
| } | |
| u16 | |
| dcb_outp_parse(struct nvkm_bios *bios, u8 idx, u8 *ver, u8 *len, | |
| struct dcb_output *outp) | |
| { | |
| u16 dcb = dcb_outp(bios, idx, ver, len); | |
| memset(outp, 0x00, sizeof(*outp)); | |
| if (dcb) { | |
| if (*ver >= 0x20) { | |
| u32 conn = nvbios_rd32(bios, dcb + 0x00); | |
| outp->or = (conn & 0x0f000000) >> 24; | |
| outp->location = (conn & 0x00300000) >> 20; | |
| outp->bus = (conn & 0x000f0000) >> 16; | |
| outp->connector = (conn & 0x0000f000) >> 12; | |
| outp->heads = (conn & 0x00000f00) >> 8; | |
| outp->i2c_index = (conn & 0x000000f0) >> 4; | |
| outp->type = (conn & 0x0000000f); | |
| outp->link = 0; | |
| } else { | |
| dcb = 0x0000; | |
| } | |
| if (*ver >= 0x40) { | |
| u32 conf = nvbios_rd32(bios, dcb + 0x04); | |
| switch (outp->type) { | |
| case DCB_OUTPUT_DP: | |
| switch (conf & 0x00e00000) { | |
| case 0x00000000: /* 1.62 */ | |
| outp->dpconf.link_bw = 0x06; | |
| break; | |
| case 0x00200000: /* 2.7 */ | |
| outp->dpconf.link_bw = 0x0a; | |
| break; | |
| case 0x00400000: /* 5.4 */ | |
| outp->dpconf.link_bw = 0x14; | |
| break; | |
| case 0x00600000: /* 8.1 */ | |
| default: | |
| outp->dpconf.link_bw = 0x1e; | |
| break; | |
| } | |
| switch ((conf & 0x0f000000) >> 24) { | |
| case 0xf: | |
| case 0x4: | |
| outp->dpconf.link_nr = 4; | |
| break; | |
| case 0x3: | |
| case 0x2: | |
| outp->dpconf.link_nr = 2; | |
| break; | |
| case 0x1: | |
| default: | |
| outp->dpconf.link_nr = 1; | |
| break; | |
| } | |
| /* fall-through... */ | |
| case DCB_OUTPUT_TMDS: | |
| case DCB_OUTPUT_LVDS: | |
| outp->link = (conf & 0x00000030) >> 4; | |
| outp->sorconf.link = outp->link; /*XXX*/ | |
| outp->extdev = 0x00; | |
| if (outp->location != 0) | |
| outp->extdev = (conf & 0x0000ff00) >> 8; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| outp->hasht = dcb_outp_hasht(outp); | |
| outp->hashm = dcb_outp_hashm(outp); | |
| } | |
| return dcb; | |
| } | |
| u16 | |
| dcb_outp_match(struct nvkm_bios *bios, u16 type, u16 mask, | |
| u8 *ver, u8 *len, struct dcb_output *outp) | |
| { | |
| u16 dcb, idx = 0; | |
| while ((dcb = dcb_outp_parse(bios, idx++, ver, len, outp))) { | |
| if ((dcb_outp_hasht(outp) & 0x00ff) == (type & 0x00ff)) { | |
| if ((dcb_outp_hashm(outp) & mask) == mask) | |
| break; | |
| } | |
| } | |
| return dcb; | |
| } | |
| int | |
| dcb_outp_foreach(struct nvkm_bios *bios, void *data, | |
| int (*exec)(struct nvkm_bios *, void *, int, u16)) | |
| { | |
| int ret, idx = -1; | |
| u8 ver, len; | |
| u16 outp; | |
| while ((outp = dcb_outp(bios, ++idx, &ver, &len))) { | |
| if (nvbios_rd32(bios, outp) == 0x00000000) | |
| break; /* seen on an NV11 with DCB v1.5 */ | |
| if (nvbios_rd32(bios, outp) == 0xffffffff) | |
| break; /* seen on an NV17 with DCB v2.0 */ | |
| if (nvbios_rd08(bios, outp) == DCB_OUTPUT_UNUSED) | |
| continue; | |
| if (nvbios_rd08(bios, outp) == DCB_OUTPUT_EOL) | |
| break; | |
| ret = exec(bios, data, idx, outp); | |
| if (ret) | |
| return ret; | |
| } | |
| return 0; | |
| } |