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 | ||
0ce41e3c | 28 | #include <core/client.h> |
878da15a | 29 | #include <core/notify.h> |
0ce41e3c | 30 | #include <core/oproxy.h> |
878da15a BS |
31 | #include <subdev/bios.h> |
32 | #include <subdev/bios/dcb.h> | |
1d7c71a3 | 33 | |
80bc340b | 34 | #include <nvif/class.h> |
7568b106 | 35 | #include <nvif/cl0046.h> |
79ca2770 | 36 | #include <nvif/event.h> |
878da15a | 37 | #include <nvif/unpack.h> |
7a014a87 | 38 | |
70aa8670 BS |
39 | static void |
40 | nvkm_disp_vblank_fini(struct nvkm_event *event, int type, int head) | |
41 | { | |
42 | struct nvkm_disp *disp = container_of(event, typeof(*disp), vblank); | |
43 | disp->func->head.vblank_fini(disp, head); | |
44 | } | |
45 | ||
46 | static void | |
47 | nvkm_disp_vblank_init(struct nvkm_event *event, int type, int head) | |
48 | { | |
49 | struct nvkm_disp *disp = container_of(event, typeof(*disp), vblank); | |
50 | disp->func->head.vblank_init(disp, head); | |
51 | } | |
52 | ||
53 | static int | |
878da15a BS |
54 | nvkm_disp_vblank_ctor(struct nvkm_object *object, void *data, u32 size, |
55 | struct nvkm_notify *notify) | |
79ca2770 | 56 | { |
878da15a | 57 | struct nvkm_disp *disp = |
79ca2770 BS |
58 | container_of(notify->event, typeof(*disp), vblank); |
59 | union { | |
60 | struct nvif_notify_head_req_v0 v0; | |
61 | } *req = data; | |
62 | int ret; | |
63 | ||
64 | if (nvif_unpack(req->v0, 0, 0, false)) { | |
65 | notify->size = sizeof(struct nvif_notify_head_rep_v0); | |
66 | if (ret = -ENXIO, req->v0.head <= disp->vblank.index_nr) { | |
67 | notify->types = 1; | |
68 | notify->index = req->v0.head; | |
69 | return 0; | |
70 | } | |
71 | } | |
72 | ||
73 | return ret; | |
74 | } | |
75 | ||
70aa8670 BS |
76 | static const struct nvkm_event_func |
77 | nvkm_disp_vblank_func = { | |
78 | .ctor = nvkm_disp_vblank_ctor, | |
79 | .init = nvkm_disp_vblank_init, | |
80 | .fini = nvkm_disp_vblank_fini, | |
81 | }; | |
82 | ||
79ca2770 | 83 | void |
878da15a | 84 | nvkm_disp_vblank(struct nvkm_disp *disp, int head) |
79ca2770 BS |
85 | { |
86 | struct nvif_notify_head_rep_v0 rep = {}; | |
87 | nvkm_event_send(&disp->vblank, 1, head, &rep, sizeof(rep)); | |
88 | } | |
89 | ||
7a014a87 | 90 | static int |
878da15a BS |
91 | nvkm_disp_hpd_ctor(struct nvkm_object *object, void *data, u32 size, |
92 | struct nvkm_notify *notify) | |
7a014a87 | 93 | { |
878da15a | 94 | struct nvkm_disp *disp = |
79ca2770 BS |
95 | container_of(notify->event, typeof(*disp), hpd); |
96 | union { | |
97 | struct nvif_notify_conn_req_v0 v0; | |
98 | } *req = data; | |
7a014a87 | 99 | struct nvkm_output *outp; |
79ca2770 BS |
100 | int ret; |
101 | ||
102 | if (nvif_unpack(req->v0, 0, 0, false)) { | |
103 | notify->size = sizeof(struct nvif_notify_conn_rep_v0); | |
104 | list_for_each_entry(outp, &disp->outp, head) { | |
105 | if (ret = -ENXIO, outp->conn->index == req->v0.conn) { | |
106 | if (ret = -ENODEV, outp->conn->hpd.event) { | |
107 | notify->types = req->v0.mask; | |
108 | notify->index = req->v0.conn; | |
109 | ret = 0; | |
110 | } | |
111 | break; | |
112 | } | |
7a014a87 BS |
113 | } |
114 | } | |
79ca2770 BS |
115 | |
116 | return ret; | |
7a014a87 | 117 | } |
377b1f16 | 118 | |
79ca2770 | 119 | static const struct nvkm_event_func |
878da15a BS |
120 | nvkm_disp_hpd_func = { |
121 | .ctor = nvkm_disp_hpd_ctor | |
79ca2770 BS |
122 | }; |
123 | ||
80bc340b | 124 | int |
878da15a | 125 | nvkm_disp_ntfy(struct nvkm_object *object, u32 type, struct nvkm_event **event) |
80bc340b | 126 | { |
70aa8670 | 127 | struct nvkm_disp *disp = nvkm_disp(object->engine); |
80bc340b BS |
128 | switch (type) { |
129 | case NV04_DISP_NTFY_VBLANK: | |
130 | *event = &disp->vblank; | |
131 | return 0; | |
132 | case NV04_DISP_NTFY_CONN: | |
133 | *event = &disp->hpd; | |
134 | return 0; | |
135 | default: | |
136 | break; | |
137 | } | |
138 | return -EINVAL; | |
139 | } | |
140 | ||
0ce41e3c BS |
141 | static void |
142 | nvkm_disp_class_del(struct nvkm_oproxy *oproxy) | |
143 | { | |
144 | struct nvkm_disp *disp = nvkm_disp(oproxy->base.engine); | |
145 | mutex_lock(&disp->engine.subdev.mutex); | |
146 | if (disp->client == oproxy) | |
147 | disp->client = NULL; | |
148 | mutex_unlock(&disp->engine.subdev.mutex); | |
149 | } | |
150 | ||
151 | static const struct nvkm_oproxy_func | |
152 | nvkm_disp_class = { | |
153 | .dtor[1] = nvkm_disp_class_del, | |
154 | }; | |
155 | ||
156 | static int | |
157 | nvkm_disp_class_new(struct nvkm_device *device, | |
158 | const struct nvkm_oclass *oclass, void *data, u32 size, | |
159 | struct nvkm_object **pobject) | |
160 | { | |
161 | const struct nvkm_disp_oclass *sclass = oclass->engn; | |
162 | struct nvkm_disp *disp = nvkm_disp(oclass->engine); | |
163 | struct nvkm_oproxy *oproxy; | |
164 | int ret; | |
165 | ||
166 | ret = nvkm_oproxy_new_(&nvkm_disp_class, oclass, &oproxy); | |
167 | if (ret) | |
168 | return ret; | |
169 | *pobject = &oproxy->base; | |
170 | ||
171 | mutex_lock(&disp->engine.subdev.mutex); | |
172 | if (disp->client) { | |
173 | mutex_unlock(&disp->engine.subdev.mutex); | |
174 | return -EBUSY; | |
175 | } | |
176 | disp->client = oproxy; | |
177 | mutex_unlock(&disp->engine.subdev.mutex); | |
178 | ||
179 | return sclass->ctor(disp, oclass, data, size, &oproxy->object); | |
180 | } | |
181 | ||
182 | static const struct nvkm_device_oclass | |
183 | nvkm_disp_sclass = { | |
184 | .ctor = nvkm_disp_class_new, | |
185 | }; | |
186 | ||
187 | static int | |
188 | nvkm_disp_class_get(struct nvkm_oclass *oclass, int index, | |
189 | const struct nvkm_device_oclass **class) | |
190 | { | |
191 | struct nvkm_disp *disp = nvkm_disp(oclass->engine); | |
192 | if (index == 0) { | |
70aa8670 BS |
193 | const struct nvkm_disp_oclass *root = disp->func->root(disp); |
194 | oclass->base = root->base; | |
195 | oclass->engn = root; | |
0ce41e3c BS |
196 | *class = &nvkm_disp_sclass; |
197 | return 0; | |
198 | } | |
199 | return 1; | |
200 | } | |
201 | ||
70aa8670 BS |
202 | static void |
203 | nvkm_disp_intr(struct nvkm_engine *engine) | |
204 | { | |
205 | struct nvkm_disp *disp = nvkm_disp(engine); | |
206 | disp->func->intr(disp); | |
207 | } | |
208 | ||
209 | static int | |
210 | nvkm_disp_fini(struct nvkm_engine *engine, bool suspend) | |
377b1f16 | 211 | { |
70aa8670 | 212 | struct nvkm_disp *disp = nvkm_disp(engine); |
f2c906fc | 213 | struct nvkm_connector *conn; |
7a014a87 | 214 | struct nvkm_output *outp; |
7a014a87 BS |
215 | |
216 | list_for_each_entry(outp, &disp->outp, head) { | |
f2c906fc | 217 | nvkm_output_fini(outp); |
7a014a87 BS |
218 | } |
219 | ||
f2c906fc BS |
220 | list_for_each_entry(conn, &disp->conn, head) { |
221 | nvkm_connector_fini(conn); | |
7a014a87 BS |
222 | } |
223 | ||
70aa8670 | 224 | return 0; |
377b1f16 BS |
225 | } |
226 | ||
70aa8670 BS |
227 | static int |
228 | nvkm_disp_init(struct nvkm_engine *engine) | |
377b1f16 | 229 | { |
70aa8670 | 230 | struct nvkm_disp *disp = nvkm_disp(engine); |
f2c906fc | 231 | struct nvkm_connector *conn; |
7a014a87 | 232 | struct nvkm_output *outp; |
7a014a87 | 233 | |
f2c906fc BS |
234 | list_for_each_entry(conn, &disp->conn, head) { |
235 | nvkm_connector_init(conn); | |
7a014a87 BS |
236 | } |
237 | ||
f2c906fc BS |
238 | list_for_each_entry(outp, &disp->outp, head) { |
239 | nvkm_output_init(outp); | |
7a014a87 BS |
240 | } |
241 | ||
70aa8670 | 242 | return 0; |
377b1f16 | 243 | } |
1d7c71a3 | 244 | |
70aa8670 BS |
245 | static void * |
246 | nvkm_disp_dtor(struct nvkm_engine *engine) | |
1d7c71a3 | 247 | { |
70aa8670 | 248 | struct nvkm_disp *disp = nvkm_disp(engine); |
f2c906fc BS |
249 | struct nvkm_connector *conn; |
250 | struct nvkm_output *outp; | |
70aa8670 BS |
251 | void *data = disp; |
252 | ||
253 | if (disp->func->dtor) | |
254 | data = disp->func->dtor(disp); | |
7a014a87 | 255 | |
79ca2770 BS |
256 | nvkm_event_fini(&disp->vblank); |
257 | nvkm_event_fini(&disp->hpd); | |
7a014a87 | 258 | |
f2c906fc BS |
259 | while (!list_empty(&disp->outp)) { |
260 | outp = list_first_entry(&disp->outp, typeof(*outp), head); | |
261 | list_del(&outp->head); | |
262 | nvkm_output_del(&outp); | |
263 | } | |
264 | ||
265 | while (!list_empty(&disp->conn)) { | |
266 | conn = list_first_entry(&disp->conn, typeof(*conn), head); | |
267 | list_del(&conn->head); | |
268 | nvkm_connector_del(&conn); | |
7a014a87 BS |
269 | } |
270 | ||
70aa8670 | 271 | return data; |
1d7c71a3 BS |
272 | } |
273 | ||
0ce41e3c BS |
274 | static const struct nvkm_engine_func |
275 | nvkm_disp = { | |
70aa8670 BS |
276 | .dtor = nvkm_disp_dtor, |
277 | .init = nvkm_disp_init, | |
278 | .fini = nvkm_disp_fini, | |
279 | .intr = nvkm_disp_intr, | |
0ce41e3c BS |
280 | .base.sclass = nvkm_disp_class_get, |
281 | }; | |
282 | ||
1d7c71a3 | 283 | int |
70aa8670 BS |
284 | nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device, |
285 | int index, int heads, struct nvkm_disp *disp) | |
1d7c71a3 | 286 | { |
f2c906fc | 287 | struct nvkm_bios *bios = device->bios; |
f2c906fc | 288 | struct nvkm_output *outp, *outt, *pair; |
70aa8670 | 289 | struct nvkm_connector *conn; |
f2c906fc | 290 | struct nvbios_connE connE; |
7a014a87 BS |
291 | struct dcb_output dcbE; |
292 | u8 hpd = 0, ver, hdr; | |
293 | u32 data; | |
294 | int ret, i; | |
1d7c71a3 | 295 | |
7a014a87 | 296 | INIT_LIST_HEAD(&disp->outp); |
f2c906fc | 297 | INIT_LIST_HEAD(&disp->conn); |
70aa8670 BS |
298 | disp->func = func; |
299 | disp->head.nr = heads; | |
300 | ||
301 | ret = nvkm_engine_ctor(&nvkm_disp, device, index, 0, | |
302 | true, &disp->engine); | |
303 | if (ret) | |
304 | return ret; | |
7a014a87 BS |
305 | |
306 | /* create output objects for each display path in the vbios */ | |
307 | i = -1; | |
308 | while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &dcbE))) { | |
f2c906fc BS |
309 | const struct nvkm_disp_func_outp *outps; |
310 | int (*ctor)(struct nvkm_disp *, int, struct dcb_output *, | |
311 | struct nvkm_output **); | |
312 | ||
7a014a87 BS |
313 | if (dcbE.type == DCB_OUTPUT_UNUSED) |
314 | continue; | |
315 | if (dcbE.type == DCB_OUTPUT_EOL) | |
316 | break; | |
f2c906fc BS |
317 | outp = NULL; |
318 | ||
319 | switch (dcbE.location) { | |
70aa8670 BS |
320 | case 0: outps = &disp->func->outp.internal; break; |
321 | case 1: outps = &disp->func->outp.external; break; | |
f2c906fc BS |
322 | default: |
323 | nvkm_warn(&disp->engine.subdev, | |
324 | "dcb %d locn %d unknown\n", i, dcbE.location); | |
325 | continue; | |
326 | } | |
7a014a87 | 327 | |
f2c906fc BS |
328 | switch (dcbE.type) { |
329 | case DCB_OUTPUT_ANALOG: ctor = outps->crt ; break; | |
330 | case DCB_OUTPUT_TV : ctor = outps->tv ; break; | |
331 | case DCB_OUTPUT_TMDS : ctor = outps->tmds; break; | |
332 | case DCB_OUTPUT_LVDS : ctor = outps->lvds; break; | |
333 | case DCB_OUTPUT_DP : ctor = outps->dp ; break; | |
334 | default: | |
335 | nvkm_warn(&disp->engine.subdev, | |
336 | "dcb %d type %d unknown\n", i, dcbE.type); | |
337 | continue; | |
338 | } | |
339 | ||
340 | if (ctor) | |
341 | ret = ctor(disp, i, &dcbE, &outp); | |
342 | else | |
343 | ret = -ENODEV; | |
344 | ||
345 | if (ret) { | |
346 | if (ret == -ENODEV) { | |
347 | nvkm_debug(&disp->engine.subdev, | |
348 | "dcb %d %d/%d not supported\n", | |
349 | i, dcbE.location, dcbE.type); | |
350 | continue; | |
7a014a87 | 351 | } |
f2c906fc BS |
352 | nvkm_error(&disp->engine.subdev, |
353 | "failed to create output %d\n", i); | |
354 | nvkm_output_del(&outp); | |
355 | continue; | |
7a014a87 BS |
356 | } |
357 | ||
f2c906fc | 358 | list_add_tail(&outp->head, &disp->outp); |
7a014a87 BS |
359 | hpd = max(hpd, (u8)(dcbE.connector + 1)); |
360 | } | |
361 | ||
f2c906fc BS |
362 | /* create connector objects based on the outputs we support */ |
363 | list_for_each_entry_safe(outp, outt, &disp->outp, head) { | |
364 | /* bios data *should* give us the most useful information */ | |
365 | data = nvbios_connEp(bios, outp->info.connector, &ver, &hdr, | |
366 | &connE); | |
367 | ||
368 | /* no bios connector data... */ | |
369 | if (!data) { | |
370 | /* heuristic: anything with the same ccb index is | |
371 | * considered to be on the same connector, any | |
372 | * output path without an associated ccb entry will | |
373 | * be put on its own connector | |
374 | */ | |
375 | int ccb_index = outp->info.i2c_index; | |
376 | if (ccb_index != 0xf) { | |
377 | list_for_each_entry(pair, &disp->outp, head) { | |
378 | if (pair->info.i2c_index == ccb_index) { | |
379 | outp->conn = pair->conn; | |
380 | break; | |
381 | } | |
382 | } | |
383 | } | |
384 | ||
385 | /* connector shared with another output path */ | |
386 | if (outp->conn) | |
387 | continue; | |
388 | ||
389 | memset(&connE, 0x00, sizeof(connE)); | |
390 | connE.type = DCB_CONNECTOR_NONE; | |
391 | i = -1; | |
392 | } else { | |
393 | i = outp->info.connector; | |
394 | } | |
395 | ||
396 | /* check that we haven't already created this connector */ | |
397 | list_for_each_entry(conn, &disp->conn, head) { | |
398 | if (conn->index == outp->info.connector) { | |
399 | outp->conn = conn; | |
400 | break; | |
401 | } | |
402 | } | |
403 | ||
404 | if (outp->conn) | |
405 | continue; | |
406 | ||
407 | /* apparently we need to create a new one! */ | |
408 | ret = nvkm_connector_new(disp, i, &connE, &outp->conn); | |
409 | if (ret) { | |
410 | nvkm_error(&disp->engine.subdev, | |
411 | "failed to create output %d conn: %d\n", | |
412 | outp->index, ret); | |
413 | nvkm_connector_del(&outp->conn); | |
414 | list_del(&outp->head); | |
415 | nvkm_output_del(&outp); | |
416 | continue; | |
417 | } | |
418 | ||
419 | list_add_tail(&outp->conn->head, &disp->conn); | |
420 | } | |
421 | ||
878da15a | 422 | ret = nvkm_event_init(&nvkm_disp_hpd_func, 3, hpd, &disp->hpd); |
7a014a87 BS |
423 | if (ret) |
424 | return ret; | |
425 | ||
70aa8670 | 426 | ret = nvkm_event_init(&nvkm_disp_vblank_func, 1, heads, &disp->vblank); |
377b1f16 BS |
427 | if (ret) |
428 | return ret; | |
429 | ||
430 | return 0; | |
1d7c71a3 | 431 | } |
70aa8670 BS |
432 | |
433 | int | |
434 | nvkm_disp_new_(const struct nvkm_disp_func *func, struct nvkm_device *device, | |
435 | int index, int heads, struct nvkm_disp **pdisp) | |
436 | { | |
437 | if (!(*pdisp = kzalloc(sizeof(**pdisp), GFP_KERNEL))) | |
438 | return -ENOMEM; | |
439 | return nvkm_disp_ctor(func, device, index, heads, *pdisp); | |
440 | } |