| /* |
| * 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; |
| } |