1 | /* $NetBSD: nouveau_subdev_mxm_nv50.c,v 1.3 2016/04/22 20:19:30 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 | |
27 | #include <sys/cdefs.h> |
28 | __KERNEL_RCSID(0, "$NetBSD: nouveau_subdev_mxm_nv50.c,v 1.3 2016/04/22 20:19:30 riastradh Exp $" ); |
29 | |
30 | #include <subdev/mxm.h> |
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 | #include "mxms.h" |
37 | |
38 | struct nv50_mxm_priv { |
39 | struct nouveau_mxm base; |
40 | }; |
41 | |
42 | struct context { |
43 | u32 *outp; |
44 | struct mxms_odev desc; |
45 | }; |
46 | |
47 | static bool |
48 | mxm_match_tmds_partner(struct nouveau_mxm *mxm, u8 *data, void *info) |
49 | { |
50 | struct context *ctx = info; |
51 | struct mxms_odev desc; |
52 | |
53 | mxms_output_device(mxm, data, &desc); |
54 | if (desc.outp_type == 2 && |
55 | desc.dig_conn == ctx->desc.dig_conn) |
56 | return false; |
57 | return true; |
58 | } |
59 | |
60 | static bool |
61 | mxm_match_dcb(struct nouveau_mxm *mxm, u8 *data, void *info) |
62 | { |
63 | struct nouveau_bios *bios = nouveau_bios(mxm); |
64 | struct context *ctx = info; |
65 | u64 desc = *(u64 *)data; |
66 | |
67 | mxms_output_device(mxm, data, &ctx->desc); |
68 | |
69 | /* match dcb encoder type to mxm-ods device type */ |
70 | if ((ctx->outp[0] & 0x0000000f) != ctx->desc.outp_type) |
71 | return true; |
72 | |
73 | /* digital output, have some extra stuff to match here, there's a |
74 | * table in the vbios that provides a mapping from the mxm digital |
75 | * connection enum values to SOR/link |
76 | */ |
77 | if ((desc & 0x00000000000000f0) >= 0x20) { |
78 | /* check against sor index */ |
79 | u8 link = mxm_sor_map(bios, ctx->desc.dig_conn); |
80 | if ((ctx->outp[0] & 0x0f000000) != (link & 0x0f) << 24) |
81 | return true; |
82 | |
83 | /* check dcb entry has a compatible link field */ |
84 | link = (link & 0x30) >> 4; |
85 | if ((link & ((ctx->outp[1] & 0x00000030) >> 4)) != link) |
86 | return true; |
87 | } |
88 | |
89 | /* mark this descriptor accounted for by setting invalid device type, |
90 | * except of course some manufactures don't follow specs properly and |
91 | * we need to avoid killing off the TMDS function on DP connectors |
92 | * if MXM-SIS is missing an entry for it. |
93 | */ |
94 | data[0] &= ~0xf0; |
95 | if (ctx->desc.outp_type == 6 && ctx->desc.conn_type == 6 && |
96 | mxms_foreach(mxm, 0x01, mxm_match_tmds_partner, ctx)) { |
97 | data[0] |= 0x20; /* modify descriptor to match TMDS now */ |
98 | } else { |
99 | data[0] |= 0xf0; |
100 | } |
101 | |
102 | return false; |
103 | } |
104 | |
105 | static int |
106 | mxm_dcb_sanitise_entry(struct nouveau_bios *bios, void *data, int idx, u16 pdcb) |
107 | { |
108 | struct nouveau_mxm *mxm = data; |
109 | struct context ctx = { .outp = (u32 *)(bios->data + pdcb) }; |
110 | u8 type, i2cidx, link, ver, len; |
111 | u8 *conn; |
112 | |
113 | /* look for an output device structure that matches this dcb entry. |
114 | * if one isn't found, disable it. |
115 | */ |
116 | if (mxms_foreach(mxm, 0x01, mxm_match_dcb, &ctx)) { |
117 | nv_debug(mxm, "disable %d: 0x%08x 0x%08x\n" , |
118 | idx, ctx.outp[0], ctx.outp[1]); |
119 | ctx.outp[0] |= 0x0000000f; |
120 | return 0; |
121 | } |
122 | |
123 | /* modify the output's ddc/aux port, there's a pointer to a table |
124 | * with the mapping from mxm ddc/aux port to dcb i2c_index in the |
125 | * vbios mxm table |
126 | */ |
127 | i2cidx = mxm_ddc_map(bios, ctx.desc.ddc_port); |
128 | if ((ctx.outp[0] & 0x0000000f) != DCB_OUTPUT_DP) |
129 | i2cidx = (i2cidx & 0x0f) << 4; |
130 | else |
131 | i2cidx = (i2cidx & 0xf0); |
132 | |
133 | if (i2cidx != 0xf0) { |
134 | ctx.outp[0] &= ~0x000000f0; |
135 | ctx.outp[0] |= i2cidx; |
136 | } |
137 | |
138 | /* override dcb sorconf.link, based on what mxm data says */ |
139 | switch (ctx.desc.outp_type) { |
140 | case 0x00: /* Analog CRT */ |
141 | case 0x01: /* Analog TV/HDTV */ |
142 | break; |
143 | default: |
144 | link = mxm_sor_map(bios, ctx.desc.dig_conn) & 0x30; |
145 | ctx.outp[1] &= ~0x00000030; |
146 | ctx.outp[1] |= link; |
147 | break; |
148 | } |
149 | |
150 | /* we may need to fixup various other vbios tables based on what |
151 | * the descriptor says the connector type should be. |
152 | * |
153 | * in a lot of cases, the vbios tables will claim DVI-I is possible, |
154 | * and the mxm data says the connector is really HDMI. another |
155 | * common example is DP->eDP. |
156 | */ |
157 | conn = bios->data; |
158 | conn += dcb_conn(bios, (ctx.outp[0] & 0x0000f000) >> 12, &ver, &len); |
159 | type = conn[0]; |
160 | switch (ctx.desc.conn_type) { |
161 | case 0x01: /* LVDS */ |
162 | ctx.outp[1] |= 0x00000004; /* use_power_scripts */ |
163 | /* XXX: modify default link width in LVDS table */ |
164 | break; |
165 | case 0x02: /* HDMI */ |
166 | type = DCB_CONNECTOR_HDMI_1; |
167 | break; |
168 | case 0x03: /* DVI-D */ |
169 | type = DCB_CONNECTOR_DVI_D; |
170 | break; |
171 | case 0x0e: /* eDP, falls through to DPint */ |
172 | ctx.outp[1] |= 0x00010000; |
173 | /*FALLTHROUGH*/ |
174 | case 0x07: /* DP internal, wtf is this?? HP8670w */ |
175 | ctx.outp[1] |= 0x00000004; /* use_power_scripts? */ |
176 | type = DCB_CONNECTOR_eDP; |
177 | break; |
178 | default: |
179 | break; |
180 | } |
181 | |
182 | if (mxms_version(mxm) >= 0x0300) |
183 | conn[0] = type; |
184 | |
185 | return 0; |
186 | } |
187 | |
188 | static bool |
189 | mxm_show_unmatched(struct nouveau_mxm *mxm, u8 *data, void *info) |
190 | { |
191 | u64 desc = *(u64 *)data; |
192 | if ((desc & 0xf0) != 0xf0) |
193 | nv_info(mxm, "unmatched output device 0x%016" PRIx64"\n" , desc); |
194 | return true; |
195 | } |
196 | |
197 | static void |
198 | mxm_dcb_sanitise(struct nouveau_mxm *mxm) |
199 | { |
200 | struct nouveau_bios *bios = nouveau_bios(mxm); |
201 | u8 ver, hdr, cnt, len; |
202 | u16 dcb = dcb_table(bios, &ver, &hdr, &cnt, &len); |
203 | if (dcb == 0x0000 || ver != 0x40) { |
204 | nv_debug(mxm, "unsupported DCB version\n" ); |
205 | return; |
206 | } |
207 | |
208 | dcb_outp_foreach(bios, mxm, mxm_dcb_sanitise_entry); |
209 | mxms_foreach(mxm, 0x01, mxm_show_unmatched, NULL); |
210 | } |
211 | |
212 | static int |
213 | nv50_mxm_ctor(struct nouveau_object *parent, struct nouveau_object *engine, |
214 | struct nouveau_oclass *oclass, void *data, u32 size, |
215 | struct nouveau_object **pobject) |
216 | { |
217 | struct nv50_mxm_priv *priv; |
218 | int ret; |
219 | |
220 | ret = nouveau_mxm_create(parent, engine, oclass, &priv); |
221 | *pobject = nv_object(priv); |
222 | if (ret) |
223 | return ret; |
224 | |
225 | if (priv->base.action & MXM_SANITISE_DCB) |
226 | mxm_dcb_sanitise(&priv->base); |
227 | return 0; |
228 | } |
229 | |
230 | struct nouveau_oclass |
231 | nv50_mxm_oclass = { |
232 | .handle = NV_SUBDEV(MXM, 0x50), |
233 | .ofuncs = &(struct nouveau_ofuncs) { |
234 | .ctor = nv50_mxm_ctor, |
235 | .dtor = _nouveau_mxm_dtor, |
236 | .init = _nouveau_mxm_init, |
237 | .fini = _nouveau_mxm_fini, |
238 | }, |
239 | }; |
240 | |