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