1 /* $NetBSD: nouveau_nvkm_subdev_mxm_nv50.c,v 1.4 2021/12/18 23:45:41 riastradh Exp $ */
2
3 /*
4 * Copyright 2011 Red Hat Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 * OTHER DEALINGS IN THE SOFTWARE.
23 *
24 * Authors: Ben Skeggs
25 */
26 #include <sys/cdefs.h>
27 __KERNEL_RCSID(0, "$NetBSD: nouveau_nvkm_subdev_mxm_nv50.c,v 1.4 2021/12/18 23:45:41 riastradh Exp $");
28
29 #include "mxms.h"
30
31 #include <subdev/bios.h>
32 #include <subdev/bios/conn.h>
33 #include <subdev/bios/dcb.h>
34 #include <subdev/bios/mxm.h>
35
36 struct context {
37 u32 *outp;
38 struct mxms_odev desc;
39 };
40
41 static bool
mxm_match_tmds_partner(struct nvkm_mxm * mxm,u8 * data,void * info)42 mxm_match_tmds_partner(struct nvkm_mxm *mxm, u8 *data, void *info)
43 {
44 struct context *ctx = info;
45 struct mxms_odev desc;
46
47 mxms_output_device(mxm, data, &desc);
48 if (desc.outp_type == 2 &&
49 desc.dig_conn == ctx->desc.dig_conn)
50 return false;
51 return true;
52 }
53
54 static bool
mxm_match_dcb(struct nvkm_mxm * mxm,u8 * data,void * info)55 mxm_match_dcb(struct nvkm_mxm *mxm, u8 *data, void *info)
56 {
57 struct nvkm_bios *bios = mxm->subdev.device->bios;
58 struct context *ctx = info;
59 u64 desc = *(u64 *)data;
60
61 mxms_output_device(mxm, data, &ctx->desc);
62
63 /* match dcb encoder type to mxm-ods device type */
64 if ((ctx->outp[0] & 0x0000000f) != ctx->desc.outp_type)
65 return true;
66
67 /* digital output, have some extra stuff to match here, there's a
68 * table in the vbios that provides a mapping from the mxm digital
69 * connection enum values to SOR/link
70 */
71 if ((desc & 0x00000000000000f0) >= 0x20) {
72 /* check against sor index */
73 u8 link = mxm_sor_map(bios, ctx->desc.dig_conn);
74 if ((ctx->outp[0] & 0x0f000000) != (link & 0x0f) << 24)
75 return true;
76
77 /* check dcb entry has a compatible link field */
78 link = (link & 0x30) >> 4;
79 if ((link & ((ctx->outp[1] & 0x00000030) >> 4)) != link)
80 return true;
81 }
82
83 /* mark this descriptor accounted for by setting invalid device type,
84 * except of course some manufactures don't follow specs properly and
85 * we need to avoid killing off the TMDS function on DP connectors
86 * if MXM-SIS is missing an entry for it.
87 */
88 data[0] &= ~0xf0;
89 if (ctx->desc.outp_type == 6 && ctx->desc.conn_type == 6 &&
90 mxms_foreach(mxm, 0x01, mxm_match_tmds_partner, ctx)) {
91 data[0] |= 0x20; /* modify descriptor to match TMDS now */
92 } else {
93 data[0] |= 0xf0;
94 }
95
96 return false;
97 }
98
99 static int
mxm_dcb_sanitise_entry(struct nvkm_bios * bios,void * data,int idx,u16 pdcb)100 mxm_dcb_sanitise_entry(struct nvkm_bios *bios, void *data, int idx, u16 pdcb)
101 {
102 struct nvkm_mxm *mxm = data;
103 struct context ctx = { .outp = (u32 *)(bios->data + pdcb) };
104 u8 type, i2cidx, link, ver, len;
105 u8 *conn;
106
107 /* look for an output device structure that matches this dcb entry.
108 * if one isn't found, disable it.
109 */
110 if (mxms_foreach(mxm, 0x01, mxm_match_dcb, &ctx)) {
111 nvkm_debug(&mxm->subdev, "disable %d: %08x %08x\n",
112 idx, ctx.outp[0], ctx.outp[1]);
113 ctx.outp[0] |= 0x0000000f;
114 return 0;
115 }
116
117 /* modify the output's ddc/aux port, there's a pointer to a table
118 * with the mapping from mxm ddc/aux port to dcb i2c_index in the
119 * vbios mxm table
120 */
121 i2cidx = mxm_ddc_map(bios, ctx.desc.ddc_port);
122 if ((ctx.outp[0] & 0x0000000f) != DCB_OUTPUT_DP)
123 i2cidx = (i2cidx & 0x0f) << 4;
124 else
125 i2cidx = (i2cidx & 0xf0);
126
127 if (i2cidx != 0xf0) {
128 ctx.outp[0] &= ~0x000000f0;
129 ctx.outp[0] |= i2cidx;
130 }
131
132 /* override dcb sorconf.link, based on what mxm data says */
133 switch (ctx.desc.outp_type) {
134 case 0x00: /* Analog CRT */
135 case 0x01: /* Analog TV/HDTV */
136 break;
137 default:
138 link = mxm_sor_map(bios, ctx.desc.dig_conn) & 0x30;
139 ctx.outp[1] &= ~0x00000030;
140 ctx.outp[1] |= link;
141 break;
142 }
143
144 /* we may need to fixup various other vbios tables based on what
145 * the descriptor says the connector type should be.
146 *
147 * in a lot of cases, the vbios tables will claim DVI-I is possible,
148 * and the mxm data says the connector is really HDMI. another
149 * common example is DP->eDP.
150 */
151 conn = bios->data;
152 conn += nvbios_connEe(bios, (ctx.outp[0] & 0x0000f000) >> 12, &ver, &len);
153 type = conn[0];
154 switch (ctx.desc.conn_type) {
155 case 0x01: /* LVDS */
156 ctx.outp[1] |= 0x00000004; /* use_power_scripts */
157 /* XXX: modify default link width in LVDS table */
158 break;
159 case 0x02: /* HDMI */
160 type = DCB_CONNECTOR_HDMI_1;
161 break;
162 case 0x03: /* DVI-D */
163 type = DCB_CONNECTOR_DVI_D;
164 break;
165 case 0x0e: /* eDP, falls through to DPint */
166 ctx.outp[1] |= 0x00010000;
167 /* fall through */
168 case 0x07: /* DP internal, wtf is this?? HP8670w */
169 ctx.outp[1] |= 0x00000004; /* use_power_scripts? */
170 type = DCB_CONNECTOR_eDP;
171 break;
172 default:
173 break;
174 }
175
176 if (mxms_version(mxm) >= 0x0300)
177 conn[0] = type;
178
179 return 0;
180 }
181
182 static bool
mxm_show_unmatched(struct nvkm_mxm * mxm,u8 * data,void * info)183 mxm_show_unmatched(struct nvkm_mxm *mxm, u8 *data, void *info)
184 {
185 struct nvkm_subdev *subdev = &mxm->subdev;
186 u64 desc = *(u64 *)data;
187 if ((desc & 0xf0) != 0xf0)
188 nvkm_info(subdev, "unmatched output device %016"PRIx64"\n", desc);
189 return true;
190 }
191
192 static void
mxm_dcb_sanitise(struct nvkm_mxm * mxm)193 mxm_dcb_sanitise(struct nvkm_mxm *mxm)
194 {
195 struct nvkm_subdev *subdev = &mxm->subdev;
196 struct nvkm_bios *bios = subdev->device->bios;
197 u8 ver, hdr, cnt, len;
198 u16 dcb = dcb_table(bios, &ver, &hdr, &cnt, &len);
199 if (dcb == 0x0000 || (ver != 0x40 && ver != 0x41)) {
200 nvkm_warn(subdev, "unsupported DCB version\n");
201 return;
202 }
203
204 dcb_outp_foreach(bios, mxm, mxm_dcb_sanitise_entry);
205 mxms_foreach(mxm, 0x01, mxm_show_unmatched, NULL);
206 }
207
208 int
nv50_mxm_new(struct nvkm_device * device,int index,struct nvkm_subdev ** pmxm)209 nv50_mxm_new(struct nvkm_device *device, int index, struct nvkm_subdev **pmxm)
210 {
211 struct nvkm_mxm *mxm;
212 int ret;
213
214 ret = nvkm_mxm_new_(device, index, &mxm);
215 if (mxm)
216 *pmxm = &mxm->subdev;
217 if (ret)
218 return ret;
219
220 if (mxm->action & MXM_SANITISE_DCB)
221 mxm_dcb_sanitise(mxm);
222
223 return 0;
224 }
225