Commit | Line | Data |
---|---|---|
1d7c71a3 BS |
1 | /* |
2 | * Copyright 2013 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 | */ | |
878da15a BS |
24 | #include "priv.h" |
25 | #include "conn.h" | |
26 | #include "outp.h" | |
27 | ||
28 | #include <core/notify.h> | |
29 | #include <subdev/bios.h> | |
30 | #include <subdev/bios/dcb.h> | |
1d7c71a3 | 31 | |
80bc340b | 32 | #include <nvif/class.h> |
79ca2770 | 33 | #include <nvif/event.h> |
878da15a | 34 | #include <nvif/unpack.h> |
7a014a87 | 35 | |
79ca2770 | 36 | int |
878da15a BS |
37 | nvkm_disp_vblank_ctor(struct nvkm_object *object, void *data, u32 size, |
38 | struct nvkm_notify *notify) | |
79ca2770 | 39 | { |
878da15a | 40 | struct nvkm_disp *disp = |
79ca2770 BS |
41 | container_of(notify->event, typeof(*disp), vblank); |
42 | union { | |
43 | struct nvif_notify_head_req_v0 v0; | |
44 | } *req = data; | |
45 | int ret; | |
46 | ||
47 | if (nvif_unpack(req->v0, 0, 0, false)) { | |
48 | notify->size = sizeof(struct nvif_notify_head_rep_v0); | |
49 | if (ret = -ENXIO, req->v0.head <= disp->vblank.index_nr) { | |
50 | notify->types = 1; | |
51 | notify->index = req->v0.head; | |
52 | return 0; | |
53 | } | |
54 | } | |
55 | ||
56 | return ret; | |
57 | } | |
58 | ||
59 | void | |
878da15a | 60 | nvkm_disp_vblank(struct nvkm_disp *disp, int head) |
79ca2770 BS |
61 | { |
62 | struct nvif_notify_head_rep_v0 rep = {}; | |
63 | nvkm_event_send(&disp->vblank, 1, head, &rep, sizeof(rep)); | |
64 | } | |
65 | ||
7a014a87 | 66 | static int |
878da15a BS |
67 | nvkm_disp_hpd_ctor(struct nvkm_object *object, void *data, u32 size, |
68 | struct nvkm_notify *notify) | |
7a014a87 | 69 | { |
878da15a | 70 | struct nvkm_disp *disp = |
79ca2770 BS |
71 | container_of(notify->event, typeof(*disp), hpd); |
72 | union { | |
73 | struct nvif_notify_conn_req_v0 v0; | |
74 | } *req = data; | |
7a014a87 | 75 | struct nvkm_output *outp; |
79ca2770 BS |
76 | int ret; |
77 | ||
78 | if (nvif_unpack(req->v0, 0, 0, false)) { | |
79 | notify->size = sizeof(struct nvif_notify_conn_rep_v0); | |
80 | list_for_each_entry(outp, &disp->outp, head) { | |
81 | if (ret = -ENXIO, outp->conn->index == req->v0.conn) { | |
82 | if (ret = -ENODEV, outp->conn->hpd.event) { | |
83 | notify->types = req->v0.mask; | |
84 | notify->index = req->v0.conn; | |
85 | ret = 0; | |
86 | } | |
87 | break; | |
88 | } | |
7a014a87 BS |
89 | } |
90 | } | |
79ca2770 BS |
91 | |
92 | return ret; | |
7a014a87 | 93 | } |
377b1f16 | 94 | |
79ca2770 | 95 | static const struct nvkm_event_func |
878da15a BS |
96 | nvkm_disp_hpd_func = { |
97 | .ctor = nvkm_disp_hpd_ctor | |
79ca2770 BS |
98 | }; |
99 | ||
80bc340b | 100 | int |
878da15a | 101 | nvkm_disp_ntfy(struct nvkm_object *object, u32 type, struct nvkm_event **event) |
80bc340b | 102 | { |
878da15a | 103 | struct nvkm_disp *disp = (void *)object->engine; |
80bc340b BS |
104 | switch (type) { |
105 | case NV04_DISP_NTFY_VBLANK: | |
106 | *event = &disp->vblank; | |
107 | return 0; | |
108 | case NV04_DISP_NTFY_CONN: | |
109 | *event = &disp->hpd; | |
110 | return 0; | |
111 | default: | |
112 | break; | |
113 | } | |
114 | return -EINVAL; | |
115 | } | |
116 | ||
377b1f16 | 117 | int |
878da15a | 118 | _nvkm_disp_fini(struct nvkm_object *object, bool suspend) |
377b1f16 | 119 | { |
878da15a | 120 | struct nvkm_disp *disp = (void *)object; |
f2c906fc | 121 | struct nvkm_connector *conn; |
7a014a87 | 122 | struct nvkm_output *outp; |
7a014a87 BS |
123 | |
124 | list_for_each_entry(outp, &disp->outp, head) { | |
f2c906fc | 125 | nvkm_output_fini(outp); |
7a014a87 BS |
126 | } |
127 | ||
f2c906fc BS |
128 | list_for_each_entry(conn, &disp->conn, head) { |
129 | nvkm_connector_fini(conn); | |
7a014a87 BS |
130 | } |
131 | ||
89c651e2 | 132 | return nvkm_engine_fini_old(&disp->engine, suspend); |
377b1f16 BS |
133 | } |
134 | ||
135 | int | |
878da15a | 136 | _nvkm_disp_init(struct nvkm_object *object) |
377b1f16 | 137 | { |
878da15a | 138 | struct nvkm_disp *disp = (void *)object; |
f2c906fc | 139 | struct nvkm_connector *conn; |
7a014a87 BS |
140 | struct nvkm_output *outp; |
141 | int ret; | |
142 | ||
89c651e2 | 143 | ret = nvkm_engine_init_old(&disp->engine); |
7a014a87 BS |
144 | if (ret) |
145 | return ret; | |
146 | ||
f2c906fc BS |
147 | list_for_each_entry(conn, &disp->conn, head) { |
148 | nvkm_connector_init(conn); | |
7a014a87 BS |
149 | } |
150 | ||
f2c906fc BS |
151 | list_for_each_entry(outp, &disp->outp, head) { |
152 | nvkm_output_init(outp); | |
7a014a87 BS |
153 | } |
154 | ||
155 | return ret; | |
377b1f16 | 156 | } |
1d7c71a3 BS |
157 | |
158 | void | |
878da15a | 159 | _nvkm_disp_dtor(struct nvkm_object *object) |
1d7c71a3 | 160 | { |
878da15a | 161 | struct nvkm_disp *disp = (void *)object; |
f2c906fc BS |
162 | struct nvkm_connector *conn; |
163 | struct nvkm_output *outp; | |
7a014a87 | 164 | |
79ca2770 BS |
165 | nvkm_event_fini(&disp->vblank); |
166 | nvkm_event_fini(&disp->hpd); | |
7a014a87 | 167 | |
f2c906fc BS |
168 | while (!list_empty(&disp->outp)) { |
169 | outp = list_first_entry(&disp->outp, typeof(*outp), head); | |
170 | list_del(&outp->head); | |
171 | nvkm_output_del(&outp); | |
172 | } | |
173 | ||
174 | while (!list_empty(&disp->conn)) { | |
175 | conn = list_first_entry(&disp->conn, typeof(*conn), head); | |
176 | list_del(&conn->head); | |
177 | nvkm_connector_del(&conn); | |
7a014a87 BS |
178 | } |
179 | ||
fd166a18 | 180 | nvkm_engine_destroy(&disp->engine); |
1d7c71a3 BS |
181 | } |
182 | ||
183 | int | |
878da15a BS |
184 | nvkm_disp_create_(struct nvkm_object *parent, struct nvkm_object *engine, |
185 | struct nvkm_oclass *oclass, int heads, const char *intname, | |
186 | const char *extname, int length, void **pobject) | |
1d7c71a3 | 187 | { |
878da15a | 188 | struct nvkm_disp_impl *impl = (void *)oclass; |
f2c906fc BS |
189 | struct nvkm_device *device = (void *)parent; |
190 | struct nvkm_bios *bios = device->bios; | |
878da15a | 191 | struct nvkm_disp *disp; |
f2c906fc BS |
192 | struct nvkm_connector *conn; |
193 | struct nvkm_output *outp, *outt, *pair; | |
194 | struct nvbios_connE connE; | |
7a014a87 BS |
195 | struct dcb_output dcbE; |
196 | u8 hpd = 0, ver, hdr; | |
197 | u32 data; | |
198 | int ret, i; | |
1d7c71a3 | 199 | |
878da15a BS |
200 | ret = nvkm_engine_create_(parent, engine, oclass, true, intname, |
201 | extname, length, pobject); | |
1d7c71a3 BS |
202 | disp = *pobject; |
203 | if (ret) | |
204 | return ret; | |
205 | ||
7a014a87 | 206 | INIT_LIST_HEAD(&disp->outp); |
f2c906fc | 207 | INIT_LIST_HEAD(&disp->conn); |
7a014a87 BS |
208 | |
209 | /* create output objects for each display path in the vbios */ | |
210 | i = -1; | |
211 | while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &dcbE))) { | |
f2c906fc BS |
212 | const struct nvkm_disp_func_outp *outps; |
213 | int (*ctor)(struct nvkm_disp *, int, struct dcb_output *, | |
214 | struct nvkm_output **); | |
215 | ||
7a014a87 BS |
216 | if (dcbE.type == DCB_OUTPUT_UNUSED) |
217 | continue; | |
218 | if (dcbE.type == DCB_OUTPUT_EOL) | |
219 | break; | |
f2c906fc BS |
220 | outp = NULL; |
221 | ||
222 | switch (dcbE.location) { | |
223 | case 0: outps = &impl->outp.internal; break; | |
224 | case 1: outps = &impl->outp.external; break; | |
225 | default: | |
226 | nvkm_warn(&disp->engine.subdev, | |
227 | "dcb %d locn %d unknown\n", i, dcbE.location); | |
228 | continue; | |
229 | } | |
7a014a87 | 230 | |
f2c906fc BS |
231 | switch (dcbE.type) { |
232 | case DCB_OUTPUT_ANALOG: ctor = outps->crt ; break; | |
233 | case DCB_OUTPUT_TV : ctor = outps->tv ; break; | |
234 | case DCB_OUTPUT_TMDS : ctor = outps->tmds; break; | |
235 | case DCB_OUTPUT_LVDS : ctor = outps->lvds; break; | |
236 | case DCB_OUTPUT_DP : ctor = outps->dp ; break; | |
237 | default: | |
238 | nvkm_warn(&disp->engine.subdev, | |
239 | "dcb %d type %d unknown\n", i, dcbE.type); | |
240 | continue; | |
241 | } | |
242 | ||
243 | if (ctor) | |
244 | ret = ctor(disp, i, &dcbE, &outp); | |
245 | else | |
246 | ret = -ENODEV; | |
247 | ||
248 | if (ret) { | |
249 | if (ret == -ENODEV) { | |
250 | nvkm_debug(&disp->engine.subdev, | |
251 | "dcb %d %d/%d not supported\n", | |
252 | i, dcbE.location, dcbE.type); | |
253 | continue; | |
7a014a87 | 254 | } |
f2c906fc BS |
255 | nvkm_error(&disp->engine.subdev, |
256 | "failed to create output %d\n", i); | |
257 | nvkm_output_del(&outp); | |
258 | continue; | |
7a014a87 BS |
259 | } |
260 | ||
f2c906fc | 261 | list_add_tail(&outp->head, &disp->outp); |
7a014a87 BS |
262 | hpd = max(hpd, (u8)(dcbE.connector + 1)); |
263 | } | |
264 | ||
f2c906fc BS |
265 | /* create connector objects based on the outputs we support */ |
266 | list_for_each_entry_safe(outp, outt, &disp->outp, head) { | |
267 | /* bios data *should* give us the most useful information */ | |
268 | data = nvbios_connEp(bios, outp->info.connector, &ver, &hdr, | |
269 | &connE); | |
270 | ||
271 | /* no bios connector data... */ | |
272 | if (!data) { | |
273 | /* heuristic: anything with the same ccb index is | |
274 | * considered to be on the same connector, any | |
275 | * output path without an associated ccb entry will | |
276 | * be put on its own connector | |
277 | */ | |
278 | int ccb_index = outp->info.i2c_index; | |
279 | if (ccb_index != 0xf) { | |
280 | list_for_each_entry(pair, &disp->outp, head) { | |
281 | if (pair->info.i2c_index == ccb_index) { | |
282 | outp->conn = pair->conn; | |
283 | break; | |
284 | } | |
285 | } | |
286 | } | |
287 | ||
288 | /* connector shared with another output path */ | |
289 | if (outp->conn) | |
290 | continue; | |
291 | ||
292 | memset(&connE, 0x00, sizeof(connE)); | |
293 | connE.type = DCB_CONNECTOR_NONE; | |
294 | i = -1; | |
295 | } else { | |
296 | i = outp->info.connector; | |
297 | } | |
298 | ||
299 | /* check that we haven't already created this connector */ | |
300 | list_for_each_entry(conn, &disp->conn, head) { | |
301 | if (conn->index == outp->info.connector) { | |
302 | outp->conn = conn; | |
303 | break; | |
304 | } | |
305 | } | |
306 | ||
307 | if (outp->conn) | |
308 | continue; | |
309 | ||
310 | /* apparently we need to create a new one! */ | |
311 | ret = nvkm_connector_new(disp, i, &connE, &outp->conn); | |
312 | if (ret) { | |
313 | nvkm_error(&disp->engine.subdev, | |
314 | "failed to create output %d conn: %d\n", | |
315 | outp->index, ret); | |
316 | nvkm_connector_del(&outp->conn); | |
317 | list_del(&outp->head); | |
318 | nvkm_output_del(&outp); | |
319 | continue; | |
320 | } | |
321 | ||
322 | list_add_tail(&outp->conn->head, &disp->conn); | |
323 | } | |
324 | ||
878da15a | 325 | ret = nvkm_event_init(&nvkm_disp_hpd_func, 3, hpd, &disp->hpd); |
7a014a87 BS |
326 | if (ret) |
327 | return ret; | |
328 | ||
79ca2770 | 329 | ret = nvkm_event_init(impl->vblank, 1, heads, &disp->vblank); |
377b1f16 BS |
330 | if (ret) |
331 | return ret; | |
332 | ||
333 | return 0; | |
1d7c71a3 | 334 | } |