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
38struct nv50_mxm_priv {
39 struct nouveau_mxm base;
40};
41
42struct context {
43 u32 *outp;
44 struct mxms_odev desc;
45};
46
47static bool
48mxm_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
60static bool
61mxm_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
105static int
106mxm_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
188static bool
189mxm_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
197static void
198mxm_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
212static int
213nv50_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
230struct nouveau_oclass
231nv50_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