diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c')
| -rw-r--r-- | drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c new file mode 100644 index 000000000000..667a9070e006 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c | |||
| @@ -0,0 +1,302 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2014 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 | |||
| 25 | #include <core/os.h> | ||
| 26 | #include <nvif/event.h> | ||
| 27 | |||
| 28 | #include <subdev/i2c.h> | ||
| 29 | |||
| 30 | #include "outpdp.h" | ||
| 31 | #include "conn.h" | ||
| 32 | #include "dport.h" | ||
| 33 | |||
| 34 | int | ||
| 35 | nvkm_output_dp_train(struct nvkm_output *base, u32 datarate, bool wait) | ||
| 36 | { | ||
| 37 | struct nvkm_output_dp *outp = (void *)base; | ||
| 38 | bool retrain = true; | ||
| 39 | u8 link[2], stat[3]; | ||
| 40 | u32 linkrate; | ||
| 41 | int ret, i; | ||
| 42 | |||
| 43 | /* check that the link is trained at a high enough rate */ | ||
| 44 | ret = nv_rdaux(outp->base.edid, DPCD_LC00_LINK_BW_SET, link, 2); | ||
| 45 | if (ret) { | ||
| 46 | DBG("failed to read link config, assuming no sink\n"); | ||
| 47 | goto done; | ||
| 48 | } | ||
| 49 | |||
| 50 | linkrate = link[0] * 27000 * (link[1] & DPCD_LC01_LANE_COUNT_SET); | ||
| 51 | linkrate = (linkrate * 8) / 10; /* 8B/10B coding overhead */ | ||
| 52 | datarate = (datarate + 9) / 10; /* -> decakilobits */ | ||
| 53 | if (linkrate < datarate) { | ||
| 54 | DBG("link not trained at sufficient rate\n"); | ||
| 55 | goto done; | ||
| 56 | } | ||
| 57 | |||
| 58 | /* check that link is still trained */ | ||
| 59 | ret = nv_rdaux(outp->base.edid, DPCD_LS02, stat, 3); | ||
| 60 | if (ret) { | ||
| 61 | DBG("failed to read link status, assuming no sink\n"); | ||
| 62 | goto done; | ||
| 63 | } | ||
| 64 | |||
| 65 | if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) { | ||
| 66 | for (i = 0; i < (link[1] & DPCD_LC01_LANE_COUNT_SET); i++) { | ||
| 67 | u8 lane = (stat[i >> 1] >> ((i & 1) * 4)) & 0x0f; | ||
| 68 | if (!(lane & DPCD_LS02_LANE0_CR_DONE) || | ||
| 69 | !(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) || | ||
| 70 | !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED)) { | ||
| 71 | DBG("lane %d not equalised\n", lane); | ||
| 72 | goto done; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | retrain = false; | ||
| 76 | } else { | ||
| 77 | DBG("no inter-lane alignment\n"); | ||
| 78 | } | ||
| 79 | |||
| 80 | done: | ||
| 81 | if (retrain || !atomic_read(&outp->lt.done)) { | ||
| 82 | /* no sink, but still need to configure source */ | ||
| 83 | if (outp->dpcd[DPCD_RC00_DPCD_REV] == 0x00) { | ||
| 84 | outp->dpcd[DPCD_RC01_MAX_LINK_RATE] = | ||
| 85 | outp->base.info.dpconf.link_bw; | ||
| 86 | outp->dpcd[DPCD_RC02] = | ||
| 87 | outp->base.info.dpconf.link_nr; | ||
| 88 | } | ||
| 89 | atomic_set(&outp->lt.done, 0); | ||
| 90 | schedule_work(&outp->lt.work); | ||
| 91 | } else { | ||
| 92 | nvkm_notify_get(&outp->irq); | ||
| 93 | } | ||
| 94 | |||
| 95 | if (wait) { | ||
| 96 | if (!wait_event_timeout(outp->lt.wait, | ||
| 97 | atomic_read(&outp->lt.done), | ||
| 98 | msecs_to_jiffies(2000))) | ||
| 99 | ret = -ETIMEDOUT; | ||
| 100 | } | ||
| 101 | |||
| 102 | return ret; | ||
| 103 | } | ||
| 104 | |||
| 105 | static void | ||
| 106 | nvkm_output_dp_enable(struct nvkm_output_dp *outp, bool present) | ||
| 107 | { | ||
| 108 | struct nouveau_i2c_port *port = outp->base.edid; | ||
| 109 | if (present) { | ||
| 110 | if (!outp->present) { | ||
| 111 | nouveau_i2c(port)->acquire_pad(port, 0); | ||
| 112 | DBG("aux power -> always\n"); | ||
| 113 | outp->present = true; | ||
| 114 | } | ||
| 115 | nvkm_output_dp_train(&outp->base, 0, true); | ||
| 116 | } else { | ||
| 117 | if (outp->present) { | ||
| 118 | nouveau_i2c(port)->release_pad(port); | ||
| 119 | DBG("aux power -> demand\n"); | ||
| 120 | outp->present = false; | ||
| 121 | } | ||
| 122 | atomic_set(&outp->lt.done, 0); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | static void | ||
| 127 | nvkm_output_dp_detect(struct nvkm_output_dp *outp) | ||
| 128 | { | ||
| 129 | struct nouveau_i2c_port *port = outp->base.edid; | ||
| 130 | int ret = nouveau_i2c(port)->acquire_pad(port, 0); | ||
| 131 | if (ret == 0) { | ||
| 132 | ret = nv_rdaux(outp->base.edid, DPCD_RC00_DPCD_REV, | ||
| 133 | outp->dpcd, sizeof(outp->dpcd)); | ||
| 134 | nvkm_output_dp_enable(outp, ret == 0); | ||
| 135 | nouveau_i2c(port)->release_pad(port); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | static int | ||
| 140 | nvkm_output_dp_hpd(struct nvkm_notify *notify) | ||
| 141 | { | ||
| 142 | struct nvkm_connector *conn = container_of(notify, typeof(*conn), hpd); | ||
| 143 | struct nvkm_output_dp *outp; | ||
| 144 | struct nouveau_disp *disp = nouveau_disp(conn); | ||
| 145 | const struct nvkm_i2c_ntfy_rep *line = notify->data; | ||
| 146 | struct nvif_notify_conn_rep_v0 rep = {}; | ||
| 147 | |||
| 148 | list_for_each_entry(outp, &disp->outp, base.head) { | ||
| 149 | if (outp->base.conn == conn && | ||
| 150 | outp->info.type == DCB_OUTPUT_DP) { | ||
| 151 | DBG("HPD: %d\n", line->mask); | ||
| 152 | nvkm_output_dp_detect(outp); | ||
| 153 | |||
| 154 | if (line->mask & NVKM_I2C_UNPLUG) | ||
| 155 | rep.mask |= NVIF_NOTIFY_CONN_V0_UNPLUG; | ||
| 156 | if (line->mask & NVKM_I2C_PLUG) | ||
| 157 | rep.mask |= NVIF_NOTIFY_CONN_V0_PLUG; | ||
| 158 | |||
| 159 | nvkm_event_send(&disp->hpd, rep.mask, conn->index, | ||
| 160 | &rep, sizeof(rep)); | ||
| 161 | return NVKM_NOTIFY_KEEP; | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | WARN_ON(1); | ||
| 166 | return NVKM_NOTIFY_DROP; | ||
| 167 | } | ||
| 168 | |||
| 169 | static int | ||
| 170 | nvkm_output_dp_irq(struct nvkm_notify *notify) | ||
| 171 | { | ||
| 172 | struct nvkm_output_dp *outp = container_of(notify, typeof(*outp), irq); | ||
| 173 | struct nouveau_disp *disp = nouveau_disp(outp); | ||
| 174 | const struct nvkm_i2c_ntfy_rep *line = notify->data; | ||
| 175 | struct nvif_notify_conn_rep_v0 rep = { | ||
| 176 | .mask = NVIF_NOTIFY_CONN_V0_IRQ, | ||
| 177 | }; | ||
| 178 | int index = outp->base.info.connector; | ||
| 179 | |||
| 180 | DBG("IRQ: %d\n", line->mask); | ||
| 181 | nvkm_output_dp_train(&outp->base, 0, true); | ||
| 182 | |||
| 183 | nvkm_event_send(&disp->hpd, rep.mask, index, &rep, sizeof(rep)); | ||
| 184 | return NVKM_NOTIFY_DROP; | ||
| 185 | } | ||
| 186 | |||
| 187 | int | ||
| 188 | _nvkm_output_dp_fini(struct nouveau_object *object, bool suspend) | ||
| 189 | { | ||
| 190 | struct nvkm_output_dp *outp = (void *)object; | ||
| 191 | nvkm_notify_put(&outp->irq); | ||
| 192 | nvkm_output_dp_enable(outp, false); | ||
| 193 | return nvkm_output_fini(&outp->base, suspend); | ||
| 194 | } | ||
| 195 | |||
| 196 | int | ||
| 197 | _nvkm_output_dp_init(struct nouveau_object *object) | ||
| 198 | { | ||
| 199 | struct nvkm_output_dp *outp = (void *)object; | ||
| 200 | nvkm_output_dp_detect(outp); | ||
| 201 | return nvkm_output_init(&outp->base); | ||
| 202 | } | ||
| 203 | |||
| 204 | void | ||
| 205 | _nvkm_output_dp_dtor(struct nouveau_object *object) | ||
| 206 | { | ||
| 207 | struct nvkm_output_dp *outp = (void *)object; | ||
| 208 | nvkm_notify_fini(&outp->irq); | ||
| 209 | nvkm_output_destroy(&outp->base); | ||
| 210 | } | ||
| 211 | |||
| 212 | int | ||
| 213 | nvkm_output_dp_create_(struct nouveau_object *parent, | ||
| 214 | struct nouveau_object *engine, | ||
| 215 | struct nouveau_oclass *oclass, | ||
| 216 | struct dcb_output *info, int index, | ||
| 217 | int length, void **pobject) | ||
| 218 | { | ||
| 219 | struct nouveau_bios *bios = nouveau_bios(parent); | ||
| 220 | struct nouveau_i2c *i2c = nouveau_i2c(parent); | ||
| 221 | struct nvkm_output_dp *outp; | ||
| 222 | u8 hdr, cnt, len; | ||
| 223 | u32 data; | ||
| 224 | int ret; | ||
| 225 | |||
| 226 | ret = nvkm_output_create_(parent, engine, oclass, info, index, | ||
| 227 | length, pobject); | ||
| 228 | outp = *pobject; | ||
| 229 | if (ret) | ||
| 230 | return ret; | ||
| 231 | |||
| 232 | nvkm_notify_fini(&outp->base.conn->hpd); | ||
| 233 | |||
| 234 | /* access to the aux channel is not optional... */ | ||
| 235 | if (!outp->base.edid) { | ||
| 236 | ERR("aux channel not found\n"); | ||
| 237 | return -ENODEV; | ||
| 238 | } | ||
| 239 | |||
| 240 | /* nor is the bios data for this output... */ | ||
| 241 | data = nvbios_dpout_match(bios, outp->base.info.hasht, | ||
| 242 | outp->base.info.hashm, &outp->version, | ||
| 243 | &hdr, &cnt, &len, &outp->info); | ||
| 244 | if (!data) { | ||
| 245 | ERR("no bios dp data\n"); | ||
| 246 | return -ENODEV; | ||
| 247 | } | ||
| 248 | |||
| 249 | DBG("bios dp %02x %02x %02x %02x\n", outp->version, hdr, cnt, len); | ||
| 250 | |||
| 251 | /* link training */ | ||
| 252 | INIT_WORK(&outp->lt.work, nouveau_dp_train); | ||
| 253 | init_waitqueue_head(&outp->lt.wait); | ||
| 254 | atomic_set(&outp->lt.done, 0); | ||
| 255 | |||
| 256 | /* link maintenance */ | ||
| 257 | ret = nvkm_notify_init(NULL, &i2c->event, nvkm_output_dp_irq, true, | ||
| 258 | &(struct nvkm_i2c_ntfy_req) { | ||
| 259 | .mask = NVKM_I2C_IRQ, | ||
| 260 | .port = outp->base.edid->index, | ||
| 261 | }, | ||
| 262 | sizeof(struct nvkm_i2c_ntfy_req), | ||
| 263 | sizeof(struct nvkm_i2c_ntfy_rep), | ||
| 264 | &outp->irq); | ||
| 265 | if (ret) { | ||
| 266 | ERR("error monitoring aux irq event: %d\n", ret); | ||
| 267 | return ret; | ||
| 268 | } | ||
| 269 | |||
| 270 | /* hotplug detect, replaces gpio-based mechanism with aux events */ | ||
| 271 | ret = nvkm_notify_init(NULL, &i2c->event, nvkm_output_dp_hpd, true, | ||
| 272 | &(struct nvkm_i2c_ntfy_req) { | ||
| 273 | .mask = NVKM_I2C_PLUG | NVKM_I2C_UNPLUG, | ||
| 274 | .port = outp->base.edid->index, | ||
| 275 | }, | ||
| 276 | sizeof(struct nvkm_i2c_ntfy_req), | ||
| 277 | sizeof(struct nvkm_i2c_ntfy_rep), | ||
| 278 | &outp->base.conn->hpd); | ||
| 279 | if (ret) { | ||
| 280 | ERR("error monitoring aux hpd events: %d\n", ret); | ||
| 281 | return ret; | ||
| 282 | } | ||
| 283 | |||
| 284 | return 0; | ||
| 285 | } | ||
| 286 | |||
| 287 | int | ||
| 288 | _nvkm_output_dp_ctor(struct nouveau_object *parent, | ||
| 289 | struct nouveau_object *engine, | ||
| 290 | struct nouveau_oclass *oclass, void *info, u32 index, | ||
| 291 | struct nouveau_object **pobject) | ||
| 292 | { | ||
| 293 | struct nvkm_output_dp *outp; | ||
| 294 | int ret; | ||
| 295 | |||
| 296 | ret = nvkm_output_dp_create(parent, engine, oclass, info, index, &outp); | ||
| 297 | *pobject = nv_object(outp); | ||
| 298 | if (ret) | ||
| 299 | return ret; | ||
| 300 | |||
| 301 | return 0; | ||
| 302 | } | ||
