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 */ 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> 31 32 #include <nvif/class.h> 33 #include <nvif/event.h> 34 #include <nvif/unpack.h> 35 36 int 37 nvkm_disp_vblank_ctor(struct nvkm_object *object, void *data, u32 size, 38 struct nvkm_notify *notify) 39 { 40 struct nvkm_disp *disp = 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 60 nvkm_disp_vblank(struct nvkm_disp *disp, int head) 61 { 62 struct nvif_notify_head_rep_v0 rep = {}; 63 nvkm_event_send(&disp->vblank, 1, head, &rep, sizeof(rep)); 64 } 65 66 static int 67 nvkm_disp_hpd_ctor(struct nvkm_object *object, void *data, u32 size, 68 struct nvkm_notify *notify) 69 { 70 struct nvkm_disp *disp = 71 container_of(notify->event, typeof(*disp), hpd); 72 union { 73 struct nvif_notify_conn_req_v0 v0; 74 } *req = data; 75 struct nvkm_output *outp; 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 } 89 } 90 } 91 92 return ret; 93 } 94 95 static const struct nvkm_event_func 96 nvkm_disp_hpd_func = { 97 .ctor = nvkm_disp_hpd_ctor 98 }; 99 100 int 101 nvkm_disp_ntfy(struct nvkm_object *object, u32 type, struct nvkm_event **event) 102 { 103 struct nvkm_disp *disp = (void *)object->engine; 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 117 int 118 _nvkm_disp_fini(struct nvkm_object *object, bool suspend) 119 { 120 struct nvkm_disp *disp = (void *)object; 121 struct nvkm_connector *conn; 122 struct nvkm_output *outp; 123 124 list_for_each_entry(outp, &disp->outp, head) { 125 nvkm_output_fini(outp); 126 } 127 128 list_for_each_entry(conn, &disp->conn, head) { 129 nvkm_connector_fini(conn); 130 } 131 132 return nvkm_engine_fini_old(&disp->engine, suspend); 133 } 134 135 int 136 _nvkm_disp_init(struct nvkm_object *object) 137 { 138 struct nvkm_disp *disp = (void *)object; 139 struct nvkm_connector *conn; 140 struct nvkm_output *outp; 141 int ret; 142 143 ret = nvkm_engine_init_old(&disp->engine); 144 if (ret) 145 return ret; 146 147 list_for_each_entry(conn, &disp->conn, head) { 148 nvkm_connector_init(conn); 149 } 150 151 list_for_each_entry(outp, &disp->outp, head) { 152 nvkm_output_init(outp); 153 } 154 155 return ret; 156 } 157 158 void 159 _nvkm_disp_dtor(struct nvkm_object *object) 160 { 161 struct nvkm_disp *disp = (void *)object; 162 struct nvkm_connector *conn; 163 struct nvkm_output *outp; 164 165 nvkm_event_fini(&disp->vblank); 166 nvkm_event_fini(&disp->hpd); 167 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); 178 } 179 180 nvkm_engine_destroy(&disp->engine); 181 } 182 183 int 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) 187 { 188 struct nvkm_disp_impl *impl = (void *)oclass; 189 struct nvkm_device *device = (void *)parent; 190 struct nvkm_bios *bios = device->bios; 191 struct nvkm_disp *disp; 192 struct nvkm_connector *conn; 193 struct nvkm_output *outp, *outt, *pair; 194 struct nvbios_connE connE; 195 struct dcb_output dcbE; 196 u8 hpd = 0, ver, hdr; 197 u32 data; 198 int ret, i; 199 200 ret = nvkm_engine_create_(parent, engine, oclass, true, intname, 201 extname, length, pobject); 202 disp = *pobject; 203 if (ret) 204 return ret; 205 206 INIT_LIST_HEAD(&disp->outp); 207 INIT_LIST_HEAD(&disp->conn); 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))) { 212 const struct nvkm_disp_func_outp *outps; 213 int (*ctor)(struct nvkm_disp *, int, struct dcb_output *, 214 struct nvkm_output **); 215 216 if (dcbE.type == DCB_OUTPUT_UNUSED) 217 continue; 218 if (dcbE.type == DCB_OUTPUT_EOL) 219 break; 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 } 230 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; 254 } 255 nvkm_error(&disp->engine.subdev, 256 "failed to create output %d\n", i); 257 nvkm_output_del(&outp); 258 continue; 259 } 260 261 list_add_tail(&outp->head, &disp->outp); 262 hpd = max(hpd, (u8)(dcbE.connector + 1)); 263 } 264 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 325 ret = nvkm_event_init(&nvkm_disp_hpd_func, 3, hpd, &disp->hpd); 326 if (ret) 327 return ret; 328 329 ret = nvkm_event_init(impl->vblank, 1, heads, &disp->vblank); 330 if (ret) 331 return ret; 332 333 return 0; 334 } 335