diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nvkm/engine/disp')
27 files changed, 2722 insertions, 629 deletions
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild index e1aecd3fe9..e346e924fe 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild @@ -27,6 +27,9 @@ nvkm-y += nvkm/engine/disp/gp102.o nvkm-y += nvkm/engine/disp/gv100.o nvkm-y += nvkm/engine/disp/tu102.o nvkm-y += nvkm/engine/disp/ga102.o +nvkm-y += nvkm/engine/disp/ad102.o + +nvkm-y += nvkm/engine/disp/r535.o nvkm-y += nvkm/engine/disp/udisp.o nvkm-y += nvkm/engine/disp/uconn.o diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ad102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ad102.c new file mode 100644 index 0000000000..7f300a79aa --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ad102.c @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +#include "priv.h" +#include "chan.h" + +#include <subdev/gsp.h> + +#include <nvif/class.h> + +static const struct nvkm_disp_func +ad102_disp = { + .uevent = &gv100_disp_chan_uevent, + .ramht_size = 0x2000, + .root = { 0, 0,AD102_DISP }, + .user = { + {{-1,-1,GV100_DISP_CAPS }, gv100_disp_caps_new }, + {{ 0, 0,GA102_DISP_CURSOR }, nvkm_disp_chan_new, &gv100_disp_curs }, + {{ 0, 0,GA102_DISP_WINDOW_IMM_CHANNEL_DMA}, nvkm_disp_wndw_new, &gv100_disp_wimm }, + {{ 0, 0,AD102_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, &gv100_disp_core }, + {{ 0, 0,GA102_DISP_WINDOW_CHANNEL_DMA }, nvkm_disp_wndw_new, &gv100_disp_wndw }, + {} + }, +}; + +int +ad102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, + struct nvkm_disp **pdisp) +{ + if (nvkm_gsp_rm(device->gsp)) + return r535_disp_new(&ad102_disp, device, type, inst, pdisp); + + return -ENODEV; +} diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c index 73104b59f9..b24eb1e560 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/base.c @@ -23,15 +23,12 @@ */ #include "priv.h" #include "conn.h" -#include "dp.h" #include "head.h" #include "ior.h" #include "outp.h" #include <core/client.h> #include <core/ramht.h> -#include <subdev/bios.h> -#include <subdev/bios/dcb.h> #include <nvif/class.h> #include <nvif/cl0046.h> @@ -105,18 +102,14 @@ static int nvkm_disp_fini(struct nvkm_engine *engine, bool suspend) { struct nvkm_disp *disp = nvkm_disp(engine); - struct nvkm_conn *conn; struct nvkm_outp *outp; if (disp->func->fini) - disp->func->fini(disp); + disp->func->fini(disp, suspend); list_for_each_entry(outp, &disp->outps, head) { - nvkm_outp_fini(outp); - } - - list_for_each_entry(conn, &disp->conns, head) { - nvkm_conn_fini(conn); + if (outp->func->fini) + outp->func->fini(outp); } return 0; @@ -126,16 +119,12 @@ static int nvkm_disp_init(struct nvkm_engine *engine) { struct nvkm_disp *disp = nvkm_disp(engine); - struct nvkm_conn *conn; struct nvkm_outp *outp; struct nvkm_ior *ior; - list_for_each_entry(conn, &disp->conns, head) { - nvkm_conn_init(conn); - } - list_for_each_entry(outp, &disp->outps, head) { - nvkm_outp_init(outp); + if (outp->func->init) + outp->func->init(outp); } if (disp->func->init) { @@ -148,7 +137,8 @@ nvkm_disp_init(struct nvkm_engine *engine) * each output resource to 'fully enabled'. */ list_for_each_entry(ior, &disp->iors, head) { - ior->func->power(ior, true, true, true, true, true); + if (ior->func->power) + ior->func->power(ior, true, true, true, true, true); } return 0; @@ -159,142 +149,15 @@ nvkm_disp_oneinit(struct nvkm_engine *engine) { struct nvkm_disp *disp = nvkm_disp(engine); struct nvkm_subdev *subdev = &disp->engine.subdev; - struct nvkm_bios *bios = subdev->device->bios; - struct nvkm_outp *outp, *outt, *pair; - struct nvkm_conn *conn; struct nvkm_head *head; - struct nvkm_ior *ior; - struct nvbios_connE connE; - struct dcb_output dcbE; - u8 hpd = 0, ver, hdr; - u32 data; int ret, i; - /* Create output path objects for each VBIOS display path. */ - i = -1; - while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &dcbE))) { - if (ver < 0x40) /* No support for chipsets prior to NV50. */ - break; - if (dcbE.type == DCB_OUTPUT_UNUSED) - continue; - if (dcbE.type == DCB_OUTPUT_EOL) - break; - outp = NULL; - - switch (dcbE.type) { - case DCB_OUTPUT_ANALOG: - case DCB_OUTPUT_TV: - case DCB_OUTPUT_TMDS: - case DCB_OUTPUT_LVDS: - ret = nvkm_outp_new(disp, i, &dcbE, &outp); - break; - case DCB_OUTPUT_DP: - ret = nvkm_dp_new(disp, i, &dcbE, &outp); - break; - case DCB_OUTPUT_WFD: - /* No support for WFD yet. */ - ret = -ENODEV; - continue; - default: - nvkm_warn(subdev, "dcb %d type %d unknown\n", - i, dcbE.type); - continue; - } - - if (ret) { - if (outp) { - if (ret != -ENODEV) - OUTP_ERR(outp, "ctor failed: %d", ret); - else - OUTP_DBG(outp, "not supported"); - nvkm_outp_del(&outp); - continue; - } - nvkm_error(subdev, "failed to create outp %d\n", i); - continue; - } - - list_add_tail(&outp->head, &disp->outps); - hpd = max(hpd, (u8)(dcbE.connector + 1)); - } - - /* Create connector objects based on available output paths. */ - list_for_each_entry_safe(outp, outt, &disp->outps, head) { - /* VBIOS data *should* give us the most useful information. */ - data = nvbios_connEp(bios, outp->info.connector, &ver, &hdr, - &connE); - - /* No bios connector data... */ - if (!data) { - /* Heuristic: anything with the same ccb index is - * considered to be on the same connector, any - * output path without an associated ccb entry will - * be put on its own connector. - */ - int ccb_index = outp->info.i2c_index; - if (ccb_index != 0xf) { - list_for_each_entry(pair, &disp->outps, head) { - if (pair->info.i2c_index == ccb_index) { - outp->conn = pair->conn; - break; - } - } - } - - /* Connector shared with another output path. */ - if (outp->conn) - continue; - - memset(&connE, 0x00, sizeof(connE)); - connE.type = DCB_CONNECTOR_NONE; - i = -1; - } else { - i = outp->info.connector; - } - - /* Check that we haven't already created this connector. */ - list_for_each_entry(conn, &disp->conns, head) { - if (conn->index == outp->info.connector) { - outp->conn = conn; - break; - } - } - - if (outp->conn) - continue; - - /* Apparently we need to create a new one! */ - ret = nvkm_conn_new(disp, i, &connE, &outp->conn); - if (ret) { - nvkm_error(subdev, "failed to create outp %d conn: %d\n", outp->index, ret); - nvkm_conn_del(&outp->conn); - list_del(&outp->head); - nvkm_outp_del(&outp); - continue; - } - - list_add_tail(&outp->conn->head, &disp->conns); - } - if (disp->func->oneinit) { ret = disp->func->oneinit(disp); if (ret) return ret; } - /* Enforce identity-mapped SOR assignment for panels, which have - * certain bits (ie. backlight controls) wired to a specific SOR. - */ - list_for_each_entry(outp, &disp->outps, head) { - if (outp->conn->info.type == DCB_CONNECTOR_LVDS || - outp->conn->info.type == DCB_CONNECTOR_eDP) { - ior = nvkm_ior_find(disp, SOR, ffs(outp->info.or) - 1); - if (!WARN_ON(!ior)) - ior->identity = true; - outp->identity = true; - } - } - i = 0; list_for_each_entry(head, &disp->heads, head) i = max(i, head->id + 1); @@ -346,6 +209,9 @@ nvkm_disp_dtor(struct nvkm_engine *engine) nvkm_head_del(&head); } + if (disp->func && disp->func->dtor) + disp->func->dtor(disp); + return data; } @@ -377,8 +243,10 @@ nvkm_disp_new_(const struct nvkm_disp_func *func, struct nvkm_device *device, spin_lock_init(&disp->client.lock); ret = nvkm_engine_ctor(&nvkm_disp, device, type, inst, true, &disp->engine); - if (ret) + if (ret) { + disp->func = NULL; return ret; + } if (func->super) { disp->super.wq = create_singlethread_workqueue("nvkm-disp"); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/chan.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/chan.h index 398336ffb6..0202905101 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/chan.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/chan.h @@ -22,6 +22,10 @@ struct nvkm_disp_chan { u64 push; u32 suspend_put; + + struct { + struct nvkm_gsp_object object; + } rm; }; int nvkm_disp_core_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.c index fbdae11378..ff88a5a525 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.c @@ -30,16 +30,6 @@ #include <nvif/event.h> void -nvkm_conn_fini(struct nvkm_conn *conn) -{ -} - -void -nvkm_conn_init(struct nvkm_conn *conn) -{ -} - -void nvkm_conn_del(struct nvkm_conn **pconn) { struct nvkm_conn *conn = *pconn; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.h index a0600e72b0..01c3146c70 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/conn.h @@ -19,8 +19,6 @@ struct nvkm_conn { int nvkm_conn_new(struct nvkm_disp *, int index, struct nvbios_connE *, struct nvkm_conn **); void nvkm_conn_del(struct nvkm_conn **); -void nvkm_conn_init(struct nvkm_conn *); -void nvkm_conn_fini(struct nvkm_conn *); #define CONN_MSG(c,l,f,a...) do { \ struct nvkm_conn *_conn = (c); \ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c index b8ac66b4a2..a109348bd6 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c @@ -41,6 +41,40 @@ */ #define AMPERE_IED_HACK(disp) ((disp)->engine.subdev.device->card_type >= GA100) +static int +nvkm_dp_mst_id_put(struct nvkm_outp *outp, u32 id) +{ + return 0; +} + +static int +nvkm_dp_mst_id_get(struct nvkm_outp *outp, u32 *pid) +{ + *pid = BIT(outp->index); + return 0; +} + +static int +nvkm_dp_aux_xfer(struct nvkm_outp *outp, u8 type, u32 addr, u8 *data, u8 *size) +{ + int ret = nvkm_i2c_aux_acquire(outp->dp.aux); + + if (ret) + return ret; + + ret = nvkm_i2c_aux_xfer(outp->dp.aux, false, type, addr, data, size); + nvkm_i2c_aux_release(outp->dp.aux); + return ret; +} + +static int +nvkm_dp_aux_pwr(struct nvkm_outp *outp, bool pu) +{ + outp->dp.enabled = pu; + nvkm_dp_enable(outp, outp->dp.enabled); + return 0; +} + struct lt_state { struct nvkm_outp *outp; @@ -282,31 +316,20 @@ nvkm_dp_train_link(struct nvkm_outp *outp, int rate) struct lt_state lt = { .outp = outp, .pc2 = outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED, + .repeaters = outp->dp.lttprs, }; - u8 sink[2], data; + u8 sink[2]; int ret; OUTP_DBG(outp, "training %dx%02x", ior->dp.nr, ior->dp.bw); - /* Select LTTPR non-transparent mode if we have a valid configuration, - * use transparent mode otherwise. - */ - if (outp->dp.lttpr[0] >= 0x14) { - data = DPCD_LTTPR_MODE_TRANSPARENT; - nvkm_wraux(outp->dp.aux, DPCD_LTTPR_MODE, &data, sizeof(data)); - - if (outp->dp.lttprs) { - data = DPCD_LTTPR_MODE_NON_TRANSPARENT; - nvkm_wraux(outp->dp.aux, DPCD_LTTPR_MODE, &data, sizeof(data)); - lt.repeaters = outp->dp.lttprs; - } - } - /* Set desired link configuration on the sink. */ sink[0] = (outp->dp.rate[rate].dpcd < 0) ? ior->dp.bw : 0; sink[1] = ior->dp.nr; if (ior->dp.ef) sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN; + if (outp->dp.lt.post_adj) + sink[1] |= 0x20; ret = nvkm_wraux(outp->dp.aux, DPCD_LC00_LINK_BW_SET, sink, 2); if (ret) @@ -447,71 +470,58 @@ nvkm_dp_train_init(struct nvkm_outp *outp) } static int -nvkm_dp_train(struct nvkm_outp *outp, u32 dataKBps) +nvkm_dp_drive(struct nvkm_outp *outp, u8 lanes, u8 pe[4], u8 vs[4]) +{ + struct lt_state lt = { + .outp = outp, + .stat[4] = (pe[0] << 2) | (vs[0] << 0) | + (pe[1] << 6) | (vs[1] << 4), + .stat[5] = (pe[2] << 2) | (vs[2] << 0) | + (pe[3] << 6) | (vs[3] << 4), + }; + + return nvkm_dp_train_drive(<, false); +} + +static int +nvkm_dp_train(struct nvkm_outp *outp, bool retrain) { struct nvkm_ior *ior = outp->ior; - int ret = -EINVAL, nr, rate; - u8 pwr; + int ret, rate; - /* Retraining link? Skip source configuration, it can mess up the active modeset. */ - if (atomic_read(&outp->dp.lt.done)) { - for (rate = 0; rate < outp->dp.rates; rate++) { - if (outp->dp.rate[rate].rate == ior->dp.bw * 27000) - return nvkm_dp_train_link(outp, ret); - } - WARN_ON(1); - return -EINVAL; + for (rate = 0; rate < outp->dp.rates; rate++) { + if (outp->dp.rate[rate].rate == (retrain ? ior->dp.bw : outp->dp.lt.bw) * 27000) + break; } - /* Ensure sink is not in a low-power state. */ - if (!nvkm_rdaux(outp->dp.aux, DPCD_SC00, &pwr, 1)) { - if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) { - pwr &= ~DPCD_SC00_SET_POWER; - pwr |= DPCD_SC00_SET_POWER_D0; - nvkm_wraux(outp->dp.aux, DPCD_SC00, &pwr, 1); - } + if (WARN_ON(rate == outp->dp.rates)) + return -EINVAL; + + /* Retraining link? Skip source configuration, it can mess up the active modeset. */ + if (retrain) { + mutex_lock(&outp->dp.mutex); + ret = nvkm_dp_train_link(outp, rate); + mutex_unlock(&outp->dp.mutex); + return ret; } + mutex_lock(&outp->dp.mutex); + OUTP_DBG(outp, "training"); + ior->dp.mst = outp->dp.lt.mst; ior->dp.ef = outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP; - ior->dp.nr = 0; + ior->dp.bw = outp->dp.lt.bw; + ior->dp.nr = outp->dp.lt.nr; - /* Link training. */ - OUTP_DBG(outp, "training"); nvkm_dp_train_init(outp); - - /* Validate and train at configuration requested (if any) on ACQUIRE. */ - if (outp->dp.lt.nr) { - for (nr = outp->dp.links; ret < 0 && nr; nr >>= 1) { - for (rate = 0; nr == outp->dp.lt.nr && rate < outp->dp.rates; rate++) { - if (outp->dp.rate[rate].rate / 27000 == outp->dp.lt.bw) { - ior->dp.bw = outp->dp.rate[rate].rate / 27000; - ior->dp.nr = nr; - ret = nvkm_dp_train_links(outp, rate); - } - } - } - } - - /* Otherwise, loop through all valid link configurations that support the data rate. */ - for (nr = outp->dp.links; ret < 0 && nr; nr >>= 1) { - for (rate = 0; ret < 0 && rate < outp->dp.rates; rate++) { - if (outp->dp.rate[rate].rate * nr >= dataKBps || WARN_ON(!ior->dp.nr)) { - /* Program selected link configuration. */ - ior->dp.bw = outp->dp.rate[rate].rate / 27000; - ior->dp.nr = nr; - ret = nvkm_dp_train_links(outp, rate); - } - } - } - - /* Finish up. */ + ret = nvkm_dp_train_links(outp, rate); nvkm_dp_train_fini(outp); if (ret < 0) OUTP_ERR(outp, "training failed"); else OUTP_DBG(outp, "training done"); - atomic_set(&outp->dp.lt.done, 1); + + mutex_unlock(&outp->dp.mutex); return ret; } @@ -529,155 +539,10 @@ nvkm_dp_disable(struct nvkm_outp *outp, struct nvkm_ior *ior) static void nvkm_dp_release(struct nvkm_outp *outp) { - /* Prevent link from being retrained if sink sends an IRQ. */ - atomic_set(&outp->dp.lt.done, 0); outp->ior->dp.nr = 0; -} - -static int -nvkm_dp_acquire(struct nvkm_outp *outp) -{ - struct nvkm_ior *ior = outp->ior; - struct nvkm_head *head; - bool retrain = true; - u32 datakbps = 0; - u32 dataKBps; - u32 linkKBps; - u8 stat[3]; - int ret, i; - - mutex_lock(&outp->dp.mutex); - - /* Check that link configuration meets current requirements. */ - list_for_each_entry(head, &outp->disp->heads, head) { - if (ior->asy.head & (1 << head->id)) { - u32 khz = (head->asy.hz >> ior->asy.rgdiv) / 1000; - datakbps += khz * head->asy.or.depth; - } - } - - linkKBps = ior->dp.bw * 27000 * ior->dp.nr; - dataKBps = DIV_ROUND_UP(datakbps, 8); - OUTP_DBG(outp, "data %d KB/s link %d KB/s mst %d->%d", - dataKBps, linkKBps, ior->dp.mst, outp->dp.lt.mst); - if (linkKBps < dataKBps || ior->dp.mst != outp->dp.lt.mst) { - OUTP_DBG(outp, "link requirements changed"); - goto done; - } - - /* Check that link is still trained. */ - ret = nvkm_rdaux(outp->dp.aux, DPCD_LS02, stat, 3); - if (ret) { - OUTP_DBG(outp, "failed to read link status, assuming no sink"); - goto done; - } - - if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) { - for (i = 0; i < ior->dp.nr; i++) { - u8 lane = (stat[i >> 1] >> ((i & 1) * 4)) & 0x0f; - if (!(lane & DPCD_LS02_LANE0_CR_DONE) || - !(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) || - !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED)) { - OUTP_DBG(outp, "lane %d not equalised", lane); - goto done; - } - } - retrain = false; - } else { - OUTP_DBG(outp, "no inter-lane alignment"); - } - -done: - if (retrain || !atomic_read(&outp->dp.lt.done)) - ret = nvkm_dp_train(outp, dataKBps); - mutex_unlock(&outp->dp.mutex); - return ret; -} - -static bool -nvkm_dp_enable_supported_link_rates(struct nvkm_outp *outp) -{ - u8 sink_rates[DPCD_RC10_SUPPORTED_LINK_RATES__SIZE]; - int i, j, k; - - if (outp->conn->info.type != DCB_CONNECTOR_eDP || - outp->dp.dpcd[DPCD_RC00_DPCD_REV] < 0x13 || - nvkm_rdaux(outp->dp.aux, DPCD_RC10_SUPPORTED_LINK_RATES(0), - sink_rates, sizeof(sink_rates))) - return false; - - for (i = 0; i < ARRAY_SIZE(sink_rates); i += 2) { - const u32 rate = ((sink_rates[i + 1] << 8) | sink_rates[i]) * 200 / 10; - - if (!rate || WARN_ON(outp->dp.rates == ARRAY_SIZE(outp->dp.rate))) - break; - - if (rate > outp->info.dpconf.link_bw * 27000) { - OUTP_DBG(outp, "rate %d !outp", rate); - continue; - } - - for (j = 0; j < outp->dp.rates; j++) { - if (rate > outp->dp.rate[j].rate) { - for (k = outp->dp.rates; k > j; k--) - outp->dp.rate[k] = outp->dp.rate[k - 1]; - break; - } - } - - outp->dp.rate[j].dpcd = i / 2; - outp->dp.rate[j].rate = rate; - outp->dp.rates++; - } - - for (i = 0; i < outp->dp.rates; i++) - OUTP_DBG(outp, "link_rate[%d] = %d", outp->dp.rate[i].dpcd, outp->dp.rate[i].rate); + nvkm_dp_disable(outp, outp->ior); - return outp->dp.rates != 0; -} - -/* XXX: This is a big fat hack, and this is just drm_dp_read_dpcd_caps() - * converted to work inside nvkm. This is a temporary holdover until we start - * passing the drm_dp_aux device through NVKM - */ -static int -nvkm_dp_read_dpcd_caps(struct nvkm_outp *outp) -{ - struct nvkm_i2c_aux *aux = outp->dp.aux; - u8 dpcd_ext[DP_RECEIVER_CAP_SIZE]; - int ret; - - ret = nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, outp->dp.dpcd, DP_RECEIVER_CAP_SIZE); - if (ret < 0) - return ret; - - /* - * Prior to DP1.3 the bit represented by - * DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT was reserved. - * If it is set DP_DPCD_REV at 0000h could be at a value less than - * the true capability of the panel. The only way to check is to - * then compare 0000h and 2200h. - */ - if (!(outp->dp.dpcd[DP_TRAINING_AUX_RD_INTERVAL] & - DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)) - return 0; - - ret = nvkm_rdaux(aux, DP_DP13_DPCD_REV, dpcd_ext, sizeof(dpcd_ext)); - if (ret < 0) - return ret; - - if (outp->dp.dpcd[DP_DPCD_REV] > dpcd_ext[DP_DPCD_REV]) { - OUTP_DBG(outp, "Extended DPCD rev less than base DPCD rev (%d > %d)\n", - outp->dp.dpcd[DP_DPCD_REV], dpcd_ext[DP_DPCD_REV]); - return 0; - } - - if (!memcmp(outp->dp.dpcd, dpcd_ext, sizeof(dpcd_ext))) - return 0; - - memcpy(outp->dp.dpcd, dpcd_ext, sizeof(dpcd_ext)); - - return 0; + nvkm_outp_release(outp); } void @@ -711,66 +576,11 @@ nvkm_dp_enable(struct nvkm_outp *outp, bool auxpwr) OUTP_DBG(outp, "aux power -> always"); nvkm_i2c_aux_monitor(aux, true); outp->dp.aux_pwr = true; - - /* Detect any LTTPRs before reading DPCD receiver caps. */ - if (!nvkm_rdaux(aux, DPCD_LTTPR_REV, outp->dp.lttpr, sizeof(outp->dp.lttpr)) && - outp->dp.lttpr[0] >= 0x14 && outp->dp.lttpr[2]) { - switch (outp->dp.lttpr[2]) { - case 0x80: outp->dp.lttprs = 1; break; - case 0x40: outp->dp.lttprs = 2; break; - case 0x20: outp->dp.lttprs = 3; break; - case 0x10: outp->dp.lttprs = 4; break; - case 0x08: outp->dp.lttprs = 5; break; - case 0x04: outp->dp.lttprs = 6; break; - case 0x02: outp->dp.lttprs = 7; break; - case 0x01: outp->dp.lttprs = 8; break; - default: - /* Unknown LTTPR count, we'll switch to transparent mode. */ - WARN_ON(1); - outp->dp.lttprs = 0; - break; - } - } else { - /* No LTTPR support, or zero LTTPR count - don't touch it at all. */ - memset(outp->dp.lttpr, 0x00, sizeof(outp->dp.lttpr)); - } - - if (!nvkm_dp_read_dpcd_caps(outp)) { - const u8 rates[] = { 0x1e, 0x14, 0x0a, 0x06, 0 }; - const u8 *rate; - int rate_max; - - outp->dp.rates = 0; - outp->dp.links = outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_MAX_LANE_COUNT; - outp->dp.links = min(outp->dp.links, outp->info.dpconf.link_nr); - if (outp->dp.lttprs && outp->dp.lttpr[4]) - outp->dp.links = min_t(int, outp->dp.links, outp->dp.lttpr[4]); - - rate_max = outp->dp.dpcd[DPCD_RC01_MAX_LINK_RATE]; - rate_max = min(rate_max, outp->info.dpconf.link_bw); - if (outp->dp.lttprs && outp->dp.lttpr[1]) - rate_max = min_t(int, rate_max, outp->dp.lttpr[1]); - - if (!nvkm_dp_enable_supported_link_rates(outp)) { - for (rate = rates; *rate; rate++) { - if (*rate > rate_max) - continue; - - if (WARN_ON(outp->dp.rates == ARRAY_SIZE(outp->dp.rate))) - break; - - outp->dp.rate[outp->dp.rates].dpcd = -1; - outp->dp.rate[outp->dp.rates].rate = *rate * 27000; - outp->dp.rates++; - } - } - } } else if (!auxpwr && outp->dp.aux_pwr) { OUTP_DBG(outp, "aux power -> demand"); nvkm_i2c_aux_monitor(aux, false); outp->dp.aux_pwr = false; - atomic_set(&outp->dp.lt.done, 0); /* Restore eDP panel GPIO to its prior state if we changed it, as * it could potentially interfere with other outputs. @@ -793,6 +603,7 @@ nvkm_dp_fini(struct nvkm_outp *outp) static void nvkm_dp_init(struct nvkm_outp *outp) { + nvkm_outp_init(outp); nvkm_dp_enable(outp, outp->dp.enabled); } @@ -807,9 +618,18 @@ nvkm_dp_func = { .dtor = nvkm_dp_dtor, .init = nvkm_dp_init, .fini = nvkm_dp_fini, - .acquire = nvkm_dp_acquire, + .detect = nvkm_outp_detect, + .inherit = nvkm_outp_inherit, + .acquire = nvkm_outp_acquire, .release = nvkm_dp_release, - .disable = nvkm_dp_disable, + .bl.get = nvkm_outp_bl_get, + .bl.set = nvkm_outp_bl_set, + .dp.aux_pwr = nvkm_dp_aux_pwr, + .dp.aux_xfer = nvkm_dp_aux_xfer, + .dp.train = nvkm_dp_train, + .dp.drive = nvkm_dp_drive, + .dp.mst_id_get = nvkm_dp_mst_id_get, + .dp.mst_id_put = nvkm_dp_mst_id_put, }; int @@ -819,7 +639,7 @@ nvkm_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE, struct n struct nvkm_bios *bios = device->bios; struct nvkm_i2c *i2c = device->i2c; struct nvkm_outp *outp; - u8 hdr, cnt, len; + u8 ver, hdr, cnt, len; u32 data; int ret; @@ -847,7 +667,9 @@ nvkm_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE, struct n OUTP_DBG(outp, "bios dp %02x %02x %02x %02x", outp->dp.version, hdr, cnt, len); + data = nvbios_dp_table(bios, &ver, &hdr, &cnt, &len); + outp->dp.mst = data && ver >= 0x40 && (nvbios_rd08(bios, data + 0x08) & 0x04); + mutex_init(&outp->dp.mutex); - atomic_set(&outp->dp.lt.done, 0); return 0; } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c index 23ae451ba4..1be97a68a8 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c @@ -124,6 +124,7 @@ g84_sor = { .state = nv50_sor_state, .power = nv50_sor_power, .clock = nv50_sor_clock, + .bl = &nv50_sor_bl, .hdmi = &g84_sor_hdmi, }; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c index 67ef889a0c..843a2661ce 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c @@ -295,6 +295,7 @@ g94_sor = { .clock = nv50_sor_clock, .war_2 = g94_sor_war_2, .war_3 = g94_sor_war_3, + .bl = &nv50_sor_bl, .hdmi = &g84_sor_hdmi, .dp = &g94_sor_dp, }; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c index 52099b75f5..ab0a85c924 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c @@ -24,6 +24,7 @@ #include "head.h" #include "ior.h" +#include <subdev/gsp.h> #include <subdev/timer.h> #include <nvif/class.h> @@ -105,6 +106,7 @@ ga102_sor = { .state = gv100_sor_state, .power = nv50_sor_power, .clock = ga102_sor_clock, + .bl = >215_sor_bl, .hdmi = &gv100_sor_hdmi, .dp = &ga102_sor_dp, .hda = &gv100_sor_hda, @@ -146,5 +148,8 @@ int ga102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, struct nvkm_disp **pdisp) { + if (nvkm_gsp_rm(device->gsp)) + return r535_disp_new(&ga102_disp, device, type, inst, pdisp); + return nvkm_disp_new_(&ga102_disp, device, type, inst, pdisp); } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c index a48e9bdf4c..83a1323600 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c @@ -328,6 +328,7 @@ gf119_sor = { .state = gf119_sor_state, .power = nv50_sor_power, .clock = gf119_sor_clock, + .bl = >215_sor_bl, .hdmi = &gf119_sor_hdmi, .dp = &gf119_sor_dp, .hda = &gf119_sor_hda, @@ -1038,7 +1039,6 @@ gf119_disp_super(struct work_struct *work) continue; nv50_disp_super_2_0(disp, head); } - nvkm_outp_route(disp); list_for_each_entry(head, &disp->heads, head) { if (!(mask[head->id] & 0x00010000)) continue; @@ -1154,7 +1154,7 @@ gf119_disp_intr(struct nvkm_disp *disp) } void -gf119_disp_fini(struct nvkm_disp *disp) +gf119_disp_fini(struct nvkm_disp *disp, bool suspend) { struct nvkm_device *device = disp->engine.subdev.device; /* disable all interrupts */ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c index 876a21a0ce..a3e2fbadad 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c @@ -115,6 +115,7 @@ gk104_sor = { .state = gf119_sor_state, .power = nv50_sor_power, .clock = gf119_sor_clock, + .bl = >215_sor_bl, .hdmi = &gk104_sor_hdmi, .dp = &gf119_sor_dp, .hda = &gf119_sor_hda, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c index b4d8e86861..688e123ad4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c @@ -70,6 +70,7 @@ gm107_sor = { .state = gf119_sor_state, .power = nv50_sor_power, .clock = gf119_sor_clock, + .bl = >215_sor_bl, .hdmi = &gk104_sor_hdmi, .dp = &gm107_sor_dp, .hda = &gf119_sor_hda, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c index 562ebae57d..511e7831b2 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c @@ -68,15 +68,23 @@ gm200_sor_dp = { }; void -gm200_sor_hdmi_scdc(struct nvkm_ior *ior, u8 scdc) +gm200_sor_hdmi_scdc(struct nvkm_ior *ior, u32 khz, bool support, bool scrambling, + bool scrambling_low_rates) { struct nvkm_device *device = ior->disp->engine.subdev.device; const u32 soff = nv50_ior_base(ior); - const u32 ctrl = scdc & 0x3; + u32 ctrl = 0; - nvkm_mask(device, 0x61c5bc + soff, 0x00000003, ctrl); + ior->tmds.high_speed = khz > 340000; + + if (support && scrambling) { + if (ior->tmds.high_speed) + ctrl |= 0x00000002; + if (ior->tmds.high_speed || scrambling_low_rates) + ctrl |= 0x00000001; + } - ior->tmds.high_speed = !!(scdc & 0x2); + nvkm_mask(device, 0x61c5bc + soff, 0x00000003, ctrl); } const struct nvkm_ior_func_hdmi @@ -139,6 +147,7 @@ gm200_sor = { .state = gf119_sor_state, .power = nv50_sor_power, .clock = gf119_sor_clock, + .bl = >215_sor_bl, .hdmi = &gm200_sor_hdmi, .dp = &gm200_sor_dp, .hda = &gf119_sor_hda, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c index 7f1eb43320..4070447bd8 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c @@ -37,6 +37,7 @@ gp100_sor = { .state = gf119_sor_state, .power = nv50_sor_power, .clock = gf119_sor_clock, + .bl = >215_sor_bl, .hdmi = &gm200_sor_hdmi, .dp = &gm200_sor_dp, .hda = &gf119_sor_hda, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c index 506ffbe7b8..6318721b66 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c @@ -182,11 +182,49 @@ gt215_sor_hdmi = { .infoframe_vsi = gt215_sor_hdmi_infoframe_vsi, }; +static int +gt215_sor_bl_set(struct nvkm_ior *ior, int lvl) +{ + struct nvkm_device *device = ior->disp->engine.subdev.device; + const u32 soff = nv50_ior_base(ior); + u32 div, val; + + div = nvkm_rd32(device, 0x61c080 + soff); + val = (lvl * div) / 100; + if (div) + nvkm_wr32(device, 0x61c084 + soff, 0xc0000000 | val); + + return 0; +} + +static int +gt215_sor_bl_get(struct nvkm_ior *ior) +{ + struct nvkm_device *device = ior->disp->engine.subdev.device; + const u32 soff = nv50_ior_base(ior); + u32 div, val; + + div = nvkm_rd32(device, 0x61c080 + soff); + val = nvkm_rd32(device, 0x61c084 + soff); + val &= 0x00ffffff; + if (div && div >= val) + return ((val * 100) + (div / 2)) / div; + + return 100; +} + +const struct nvkm_ior_func_bl +gt215_sor_bl = { + .get = gt215_sor_bl_get, + .set = gt215_sor_bl_set, +}; + static const struct nvkm_ior_func gt215_sor = { .state = g94_sor_state, .power = nv50_sor_power, .clock = nv50_sor_clock, + .bl = >215_sor_bl, .hdmi = >215_sor_hdmi, .dp = >215_sor_dp, .hda = >215_sor_hda, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c index 4ebc030e40..cfa3698d3a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c @@ -96,7 +96,7 @@ gv100_sor_dp = { .watermark = gv100_sor_dp_watermark, }; -static void +void gv100_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size) { struct nvkm_device *device = ior->disp->engine.subdev.device; @@ -120,7 +120,7 @@ gv100_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 siz nvkm_mask(device, 0x6f0100 + hoff, 0x00000001, 0x00000001); } -static void +void gv100_sor_hdmi_infoframe_avi(struct nvkm_ior *ior, int head, void *data, u32 size) { struct nvkm_device *device = ior->disp->engine.subdev.device; @@ -212,6 +212,7 @@ gv100_sor = { .state = gv100_sor_state, .power = nv50_sor_power, .clock = gf119_sor_clock, + .bl = >215_sor_bl, .hdmi = &gv100_sor_hdmi, .dp = &gv100_sor_dp, .hda = &gv100_sor_hda, @@ -863,7 +864,6 @@ gv100_disp_super(struct work_struct *work) continue; nv50_disp_super_2_0(disp, head); } - nvkm_outp_route(disp); list_for_each_entry(head, &disp->heads, head) { if (!(mask[head->id] & 0x00010000)) continue; @@ -1115,7 +1115,7 @@ gv100_disp_intr(struct nvkm_disp *disp) } void -gv100_disp_fini(struct nvkm_disp *disp) +gv100_disp_fini(struct nvkm_disp *disp, bool suspend) { struct nvkm_device *device = disp->engine.subdev.device; nvkm_wr32(device, 0x611db0, 0x00000000); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h index da1b1a626e..3ba04bead2 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h @@ -63,11 +63,18 @@ struct nvkm_ior_func { void (*war_2)(struct nvkm_ior *); void (*war_3)(struct nvkm_ior *); + const struct nvkm_ior_func_bl { + int (*get)(struct nvkm_ior *); + int (*set)(struct nvkm_ior *, int lvl); + } *bl; + const struct nvkm_ior_func_hdmi { void (*ctrl)(struct nvkm_ior *, int head, bool enable, u8 max_ac_packet, u8 rekey); - void (*scdc)(struct nvkm_ior *, u8 scdc); + void (*scdc)(struct nvkm_ior *, u32 khz, bool support, bool scrambling, + bool scrambling_low_rates); void (*infoframe_avi)(struct nvkm_ior *, int head, void *data, u32 size); void (*infoframe_vsi)(struct nvkm_ior *, int head, void *data, u32 size); + void (*audio)(struct nvkm_ior *, int head, bool enable); } *hdmi; const struct nvkm_ior_func_dp { @@ -77,6 +84,8 @@ struct nvkm_ior_func { void (*pattern)(struct nvkm_ior *, int pattern); void (*drive)(struct nvkm_ior *, int ln, int pc, int dc, int pe, int tx_pu); + int (*sst)(struct nvkm_ior *, int head, bool ef, + u32 watermark, u32 hblanksym, u32 vblanksym); void (*vcpi)(struct nvkm_ior *, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned); void (*audio)(struct nvkm_ior *, int head, bool enable); @@ -122,6 +131,7 @@ int nv50_sor_cnt(struct nvkm_disp *, unsigned long *); void nv50_sor_state(struct nvkm_ior *, struct nvkm_ior_state *); void nv50_sor_power(struct nvkm_ior *, bool, bool, bool, bool, bool); void nv50_sor_clock(struct nvkm_ior *); +extern const struct nvkm_ior_func_bl nv50_sor_bl; int g84_sor_new(struct nvkm_disp *, int); extern const struct nvkm_ior_func_hdmi g84_sor_hdmi; @@ -138,6 +148,7 @@ void g94_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32); void g94_sor_dp_activesym(struct nvkm_ior *, int, u8, u8, u8, u8); void g94_sor_dp_watermark(struct nvkm_ior *, int, u8); +extern const struct nvkm_ior_func_bl gt215_sor_bl; extern const struct nvkm_ior_func_hdmi gt215_sor_hdmi; void gt215_sor_dp_audio(struct nvkm_ior *, int, bool); extern const struct nvkm_ior_func_hda gt215_sor_hda; @@ -167,7 +178,7 @@ void gm107_sor_dp_pattern(struct nvkm_ior *, int); void gm200_sor_route_set(struct nvkm_outp *, struct nvkm_ior *); int gm200_sor_route_get(struct nvkm_outp *, int *); extern const struct nvkm_ior_func_hdmi gm200_sor_hdmi; -void gm200_sor_hdmi_scdc(struct nvkm_ior *, u8); +void gm200_sor_hdmi_scdc(struct nvkm_ior *, u32, bool, bool, bool); extern const struct nvkm_ior_func_dp gm200_sor_dp; void gm200_sor_dp_drive(struct nvkm_ior *, int, int, int, int, int); @@ -176,6 +187,8 @@ int gp100_sor_new(struct nvkm_disp *, int); int gv100_sor_cnt(struct nvkm_disp *, unsigned long *); void gv100_sor_state(struct nvkm_ior *, struct nvkm_ior_state *); extern const struct nvkm_ior_func_hdmi gv100_sor_hdmi; +void gv100_sor_hdmi_infoframe_avi(struct nvkm_ior *, int, void *, u32); +void gv100_sor_hdmi_infoframe_vsi(struct nvkm_ior *, int, void *, u32); void gv100_sor_dp_audio(struct nvkm_ior *, int, bool); void gv100_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32); void gv100_sor_dp_watermark(struct nvkm_ior *, int, u8); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp89.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp89.c index f96ba47526..e0c5fb6df3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp89.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/mcp89.c @@ -44,6 +44,7 @@ mcp89_sor = { .state = g94_sor_state, .power = nv50_sor_power, .clock = nv50_sor_clock, + .bl = >215_sor_bl, .hdmi = >215_sor_hdmi, .dp = &mcp89_sor_dp, .hda = >215_sor_hda, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c index be81168029..03a5f88a4b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c @@ -23,7 +23,9 @@ */ #include "priv.h" #include "chan.h" +#include "conn.h" #include "head.h" +#include "dp.h" #include "ior.h" #include "outp.h" @@ -156,6 +158,37 @@ nv50_pior_cnt(struct nvkm_disp *disp, unsigned long *pmask) return 3; } +static int +nv50_sor_bl_set(struct nvkm_ior *ior, int lvl) +{ + struct nvkm_device *device = ior->disp->engine.subdev.device; + const u32 soff = nv50_ior_base(ior); + u32 div = 1025; + u32 val = (lvl * div) / 100; + + nvkm_wr32(device, 0x61c084 + soff, 0x80000000 | val); + return 0; +} + +static int +nv50_sor_bl_get(struct nvkm_ior *ior) +{ + struct nvkm_device *device = ior->disp->engine.subdev.device; + const u32 soff = nv50_ior_base(ior); + u32 div = 1025; + u32 val; + + val = nvkm_rd32(device, 0x61c084 + soff); + val &= 0x000007ff; + return ((val * 100) + (div / 2)) / div; +} + +const struct nvkm_ior_func_bl +nv50_sor_bl = { + .get = nv50_sor_bl_get, + .set = nv50_sor_bl_set, +}; + void nv50_sor_clock(struct nvkm_ior *sor) { @@ -220,6 +253,7 @@ nv50_sor = { .state = nv50_sor_state, .power = nv50_sor_power, .clock = nv50_sor_clock, + .bl = &nv50_sor_bl, }; static int @@ -1254,10 +1288,6 @@ nv50_disp_super_2_2(struct nvkm_disp *disp, struct nvkm_head *head) ior->asy.link = outp->lvds.dual ? 3 : 1; } - /* Handle any link training, etc. */ - if (outp && outp->func->acquire) - outp->func->acquire(outp); - /* Execute OnInt2 IED script. */ nv50_disp_super_ied_on(head, ior, 0, khz); @@ -1287,7 +1317,6 @@ nv50_disp_super_2_1(struct nvkm_disp *disp, struct nvkm_head *head) void nv50_disp_super_2_0(struct nvkm_disp *disp, struct nvkm_head *head) { - struct nvkm_outp *outp; struct nvkm_ior *ior; /* Determine which OR, if any, we're detaching from the head. */ @@ -1298,14 +1327,6 @@ nv50_disp_super_2_0(struct nvkm_disp *disp, struct nvkm_head *head) /* Execute OffInt2 IED script. */ nv50_disp_super_ied_off(head, ior, 2); - - /* If we're shutting down the OR's only active head, execute - * the output path's disable function. - */ - if (ior->arm.head == (1 << head->id)) { - if ((outp = ior->arm.outp) && outp->func->disable) - outp->func->disable(outp, ior); - } } void @@ -1371,7 +1392,6 @@ nv50_disp_super(struct work_struct *work) continue; nv50_disp_super_2_0(disp, head); } - nvkm_outp_route(disp); list_for_each_entry(head, &disp->heads, head) { if (!(super & (0x00000200 << head->id))) continue; @@ -1484,7 +1504,7 @@ nv50_disp_intr(struct nvkm_disp *disp) } void -nv50_disp_fini(struct nvkm_disp *disp) +nv50_disp_fini(struct nvkm_disp *disp, bool suspend) { struct nvkm_device *device = disp->engine.subdev.device; /* disable all interrupts */ @@ -1563,7 +1583,15 @@ nv50_disp_oneinit(struct nvkm_disp *disp) const struct nvkm_disp_func *func = disp->func; struct nvkm_subdev *subdev = &disp->engine.subdev; struct nvkm_device *device = subdev->device; + struct nvkm_bios *bios = device->bios; + struct nvkm_outp *outp, *outt, *pair; + struct nvkm_conn *conn; + struct nvkm_ior *ior; int ret, i; + u8 ver, hdr; + u32 data; + struct dcb_output dcbE; + struct nvbios_connE connE; if (func->wndw.cnt) { disp->wndw.nr = func->wndw.cnt(disp, &disp->wndw.mask); @@ -1610,8 +1638,130 @@ nv50_disp_oneinit(struct nvkm_disp *disp) if (ret) return ret; - return nvkm_ramht_new(device, func->ramht_size ? func->ramht_size : - 0x1000, 0, disp->inst, &disp->ramht); + ret = nvkm_ramht_new(device, func->ramht_size ? func->ramht_size : 0x1000, 0, disp->inst, + &disp->ramht); + if (ret) + return ret; + + /* Create output path objects for each VBIOS display path. */ + i = -1; + while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &dcbE))) { + if (WARN_ON((ver & 0xf0) != 0x40)) + return -EINVAL; + if (dcbE.type == DCB_OUTPUT_UNUSED) + continue; + if (dcbE.type == DCB_OUTPUT_EOL) + break; + outp = NULL; + + switch (dcbE.type) { + case DCB_OUTPUT_ANALOG: + case DCB_OUTPUT_TMDS: + case DCB_OUTPUT_LVDS: + ret = nvkm_outp_new(disp, i, &dcbE, &outp); + break; + case DCB_OUTPUT_DP: + ret = nvkm_dp_new(disp, i, &dcbE, &outp); + break; + case DCB_OUTPUT_TV: + case DCB_OUTPUT_WFD: + /* No support for WFD yet. */ + ret = -ENODEV; + continue; + default: + nvkm_warn(subdev, "dcb %d type %d unknown\n", + i, dcbE.type); + continue; + } + + if (ret) { + if (outp) { + if (ret != -ENODEV) + OUTP_ERR(outp, "ctor failed: %d", ret); + else + OUTP_DBG(outp, "not supported"); + nvkm_outp_del(&outp); + continue; + } + nvkm_error(subdev, "failed to create outp %d\n", i); + continue; + } + + list_add_tail(&outp->head, &disp->outps); + } + + /* Create connector objects based on available output paths. */ + list_for_each_entry_safe(outp, outt, &disp->outps, head) { + /* VBIOS data *should* give us the most useful information. */ + data = nvbios_connEp(bios, outp->info.connector, &ver, &hdr, + &connE); + + /* No bios connector data... */ + if (!data) { + /* Heuristic: anything with the same ccb index is + * considered to be on the same connector, any + * output path without an associated ccb entry will + * be put on its own connector. + */ + int ccb_index = outp->info.i2c_index; + if (ccb_index != 0xf) { + list_for_each_entry(pair, &disp->outps, head) { + if (pair->info.i2c_index == ccb_index) { + outp->conn = pair->conn; + break; + } + } + } + + /* Connector shared with another output path. */ + if (outp->conn) + continue; + + memset(&connE, 0x00, sizeof(connE)); + connE.type = DCB_CONNECTOR_NONE; + i = -1; + } else { + i = outp->info.connector; + } + + /* Check that we haven't already created this connector. */ + list_for_each_entry(conn, &disp->conns, head) { + if (conn->index == outp->info.connector) { + outp->conn = conn; + break; + } + } + + if (outp->conn) + continue; + + /* Apparently we need to create a new one! */ + ret = nvkm_conn_new(disp, i, &connE, &outp->conn); + if (ret) { + nvkm_error(subdev, "failed to create outp %d conn: %d\n", outp->index, ret); + nvkm_conn_del(&outp->conn); + list_del(&outp->head); + nvkm_outp_del(&outp); + continue; + } + + list_add_tail(&outp->conn->head, &disp->conns); + } + + /* Enforce identity-mapped SOR assignment for panels, which have + * certain bits (ie. backlight controls) wired to a specific SOR. + */ + list_for_each_entry(outp, &disp->outps, head) { + if (outp->conn->info.type == DCB_CONNECTOR_LVDS || + outp->conn->info.type == DCB_CONNECTOR_eDP) { + ior = nvkm_ior_find(disp, SOR, ffs(outp->info.or) - 1); + if (!WARN_ON(!ior)) + ior->identity = true; + outp->identity = true; + } + } + + return 0; } static const struct nvkm_disp_func diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c index 6094805fbd..28adc5a30f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c @@ -22,14 +22,16 @@ * Authors: Ben Skeggs */ #include "outp.h" +#include "conn.h" #include "dp.h" #include "ior.h" #include <subdev/bios.h> #include <subdev/bios/dcb.h> +#include <subdev/gpio.h> #include <subdev/i2c.h> -void +static void nvkm_outp_route(struct nvkm_disp *disp) { struct nvkm_outp *outp; @@ -46,8 +48,8 @@ nvkm_outp_route(struct nvkm_disp *disp) list_for_each_entry(ior, &disp->iors, head) { if ((outp = ior->asy.outp)) { - OUTP_DBG(outp, "acquire %s", ior->name); if (ior->asy.outp != ior->arm.outp) { + OUTP_DBG(outp, "acquire %s", ior->name); if (ior->func->route.set) ior->func->route.set(outp, ior); ior->arm.outp = ior->asy.outp; @@ -87,22 +89,20 @@ nvkm_outp_xlat(struct nvkm_outp *outp, enum nvkm_ior_type *type) } void -nvkm_outp_release(struct nvkm_outp *outp, u8 user) +nvkm_outp_release_or(struct nvkm_outp *outp, u8 user) { struct nvkm_ior *ior = outp->ior; OUTP_TRACE(outp, "release %02x &= %02x %p", outp->acquired, ~user, ior); if (ior) { outp->acquired &= ~user; if (!outp->acquired) { - if (outp->func->release && outp->ior) - outp->func->release(outp); outp->ior->asy.outp = NULL; outp->ior = NULL; } } } -static inline int +int nvkm_outp_acquire_ior(struct nvkm_outp *outp, u8 user, struct nvkm_ior *ior) { outp->ior = ior; @@ -140,7 +140,7 @@ nvkm_outp_acquire_hda(struct nvkm_outp *outp, enum nvkm_ior_type type, } int -nvkm_outp_acquire(struct nvkm_outp *outp, u8 user, bool hda) +nvkm_outp_acquire_or(struct nvkm_outp *outp, u8 user, bool hda) { struct nvkm_ior *ior = outp->ior; enum nvkm_ior_proto proto; @@ -207,39 +207,110 @@ nvkm_outp_acquire(struct nvkm_outp *outp, u8 user, bool hda) return nvkm_outp_acquire_hda(outp, type, user, false); } +int +nvkm_outp_bl_set(struct nvkm_outp *outp, int level) +{ + int ret; + + ret = nvkm_outp_acquire_or(outp, NVKM_OUTP_PRIV, false); + if (ret) + return ret; + + if (outp->ior->func->bl) + ret = outp->ior->func->bl->set(outp->ior, level); + else + ret = -EINVAL; + + nvkm_outp_release_or(outp, NVKM_OUTP_PRIV); + return ret; +} + +int +nvkm_outp_bl_get(struct nvkm_outp *outp) +{ + int ret; + + ret = nvkm_outp_acquire_or(outp, NVKM_OUTP_PRIV, false); + if (ret) + return ret; + + if (outp->ior->func->bl) + ret = outp->ior->func->bl->get(outp->ior); + else + ret = -EINVAL; + + nvkm_outp_release_or(outp, NVKM_OUTP_PRIV); + return ret; +} + +int +nvkm_outp_detect(struct nvkm_outp *outp) +{ + struct nvkm_gpio *gpio = outp->disp->engine.subdev.device->gpio; + int ret = -EINVAL; + + if (outp->conn->info.hpd != DCB_GPIO_UNUSED) { + ret = nvkm_gpio_get(gpio, 0, DCB_GPIO_UNUSED, outp->conn->info.hpd); + if (ret < 0) + return ret; + if (ret) + return 1; + + /*TODO: Look into returning NOT_PRESENT if !HPD on DVI/HDMI. + * + * It's uncertain whether this is accurate for all older chipsets, + * so we're returning UNKNOWN, and the DRM will probe DDC instead. + */ + if (outp->info.type == DCB_OUTPUT_DP) + return 0; + } + + return ret; +} + void -nvkm_outp_fini(struct nvkm_outp *outp) +nvkm_outp_release(struct nvkm_outp *outp) { - if (outp->func->fini) - outp->func->fini(outp); + nvkm_outp_release_or(outp, NVKM_OUTP_USER); + nvkm_outp_route(outp->disp); } -static void -nvkm_outp_init_route(struct nvkm_outp *outp) +int +nvkm_outp_acquire(struct nvkm_outp *outp, bool hda) +{ + int ret = nvkm_outp_acquire_or(outp, NVKM_OUTP_USER, hda); + + if (ret) + return ret; + + nvkm_outp_route(outp->disp); + return 0; +} + +struct nvkm_ior * +nvkm_outp_inherit(struct nvkm_outp *outp) { struct nvkm_disp *disp = outp->disp; + struct nvkm_ior *ior; enum nvkm_ior_proto proto; enum nvkm_ior_type type; - struct nvkm_ior *ior; int id, link; /* Find any OR from the class that is able to support this device. */ proto = nvkm_outp_xlat(outp, &type); if (proto == UNKNOWN) - return; + return NULL; ior = nvkm_ior_find(disp, type, -1); - if (!ior) { - WARN_ON(1); - return; - } + if (WARN_ON(!ior)) + return NULL; /* Determine the specific OR, if any, this device is attached to. */ if (ior->func->route.get) { id = ior->func->route.get(outp, &link); if (id < 0) { OUTP_DBG(outp, "no route"); - return; + return NULL; } } else { /* Prior to DCB 4.1, this is hardwired like so. */ @@ -248,10 +319,24 @@ nvkm_outp_init_route(struct nvkm_outp *outp) } ior = nvkm_ior_find(disp, type, id); - if (!ior) { - WARN_ON(1); + if (WARN_ON(!ior)) + return NULL; + + return ior; +} + +void +nvkm_outp_init(struct nvkm_outp *outp) +{ + enum nvkm_ior_proto proto; + enum nvkm_ior_type type; + struct nvkm_ior *ior; + + /* Find any OR from the class that is able to support this device. */ + proto = nvkm_outp_xlat(outp, &type); + ior = outp->func->inherit(outp); + if (!ior) return; - } /* Determine if the OR is already configured for this device. */ ior->func->state(ior, &ior->arm); @@ -274,14 +359,6 @@ nvkm_outp_init_route(struct nvkm_outp *outp) } void -nvkm_outp_init(struct nvkm_outp *outp) -{ - nvkm_outp_init_route(outp); - if (outp->func->init) - outp->func->init(outp); -} - -void nvkm_outp_del(struct nvkm_outp **poutp) { struct nvkm_outp *outp = *poutp; @@ -309,7 +386,8 @@ nvkm_outp_new_(const struct nvkm_outp_func *func, struct nvkm_disp *disp, outp->disp = disp; outp->index = index; outp->info = *dcbE; - outp->i2c = nvkm_i2c_bus_find(i2c, dcbE->i2c_index); + if (!disp->rm.client.gsp) + outp->i2c = nvkm_i2c_bus_find(i2c, dcbE->i2c_index); OUTP_DBG(outp, "type %02x loc %d or %d link %d con %x " "edid %x bus %d head %x", @@ -328,6 +406,13 @@ nvkm_outp_new_(const struct nvkm_outp_func *func, struct nvkm_disp *disp, static const struct nvkm_outp_func nvkm_outp = { + .init = nvkm_outp_init, + .detect = nvkm_outp_detect, + .inherit = nvkm_outp_inherit, + .acquire = nvkm_outp_acquire, + .release = nvkm_outp_release, + .bl.get = nvkm_outp_bl_get, + .bl.set = nvkm_outp_bl_set, }; int diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h index 4e7f873f66..ebd2f499b4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.h @@ -35,6 +35,8 @@ struct nvkm_outp { struct { struct nvbios_dpout info; u8 version; + bool mst; + bool increased_wm; struct nvkm_i2c_aux *aux; @@ -50,14 +52,13 @@ struct nvkm_outp { u32 rate; } rate[8]; int rates; - int links; struct mutex mutex; struct { - atomic_t done; u8 nr; u8 bw; bool mst; + bool post_adj; } lt; } dp; }; @@ -74,17 +75,45 @@ int nvkm_outp_new(struct nvkm_disp *, int index, struct dcb_output *, struct nvk void nvkm_outp_del(struct nvkm_outp **); void nvkm_outp_init(struct nvkm_outp *); void nvkm_outp_fini(struct nvkm_outp *); -int nvkm_outp_acquire(struct nvkm_outp *, u8 user, bool hda); -void nvkm_outp_release(struct nvkm_outp *, u8 user); -void nvkm_outp_route(struct nvkm_disp *); + +int nvkm_outp_detect(struct nvkm_outp *); + +struct nvkm_ior *nvkm_outp_inherit(struct nvkm_outp *); +int nvkm_outp_acquire(struct nvkm_outp *, bool hda); +int nvkm_outp_acquire_or(struct nvkm_outp *, u8 user, bool hda); +int nvkm_outp_acquire_ior(struct nvkm_outp *, u8 user, struct nvkm_ior *); +void nvkm_outp_release(struct nvkm_outp *); +void nvkm_outp_release_or(struct nvkm_outp *, u8 user); + +int nvkm_outp_bl_get(struct nvkm_outp *); +int nvkm_outp_bl_set(struct nvkm_outp *, int level); struct nvkm_outp_func { void *(*dtor)(struct nvkm_outp *); void (*init)(struct nvkm_outp *); void (*fini)(struct nvkm_outp *); - int (*acquire)(struct nvkm_outp *); + + int (*detect)(struct nvkm_outp *); + int (*edid_get)(struct nvkm_outp *, u8 *data, u16 *size); + + struct nvkm_ior *(*inherit)(struct nvkm_outp *); + int (*acquire)(struct nvkm_outp *, bool hda); void (*release)(struct nvkm_outp *); - void (*disable)(struct nvkm_outp *, struct nvkm_ior *); + + struct { + int (*get)(struct nvkm_outp *); + int (*set)(struct nvkm_outp *, int level); + } bl; + + struct { + int (*aux_pwr)(struct nvkm_outp *, bool pu); + int (*aux_xfer)(struct nvkm_outp *, u8 type, u32 addr, u8 *data, u8 *size); + int (*rates)(struct nvkm_outp *); + int (*train)(struct nvkm_outp *, bool retrain); + int (*drive)(struct nvkm_outp *, u8 lanes, u8 pe[4], u8 vs[4]); + int (*mst_id_get)(struct nvkm_outp *, u32 *id); + int (*mst_id_put)(struct nvkm_outp *, u32 id); + } dp; }; #define OUTP_MSG(o,l,f,a...) do { \ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h index ec5292a8f3..a3fd7cb7c4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h @@ -8,6 +8,9 @@ struct nvkm_head; struct nvkm_outp; struct dcb_output; +int r535_disp_new(const struct nvkm_disp_func *, struct nvkm_device *, enum nvkm_subdev_type, int, + struct nvkm_disp **); + int nvkm_disp_ctor(const struct nvkm_disp_func *, struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_disp *); int nvkm_disp_new_(const struct nvkm_disp_func *, struct nvkm_device *, enum nvkm_subdev_type, int, @@ -15,9 +18,10 @@ int nvkm_disp_new_(const struct nvkm_disp_func *, struct nvkm_device *, enum nvk void nvkm_disp_vblank(struct nvkm_disp *, int head); struct nvkm_disp_func { + void (*dtor)(struct nvkm_disp *); int (*oneinit)(struct nvkm_disp *); int (*init)(struct nvkm_disp *); - void (*fini)(struct nvkm_disp *); + void (*fini)(struct nvkm_disp *, bool suspend); void (*intr)(struct nvkm_disp *); void (*intr_error)(struct nvkm_disp *, int chid); @@ -32,7 +36,7 @@ struct nvkm_disp_func { u16 ramht_size; - const struct nvkm_sclass root; + struct nvkm_sclass root; struct nvkm_disp_user { struct nvkm_sclass base; @@ -44,7 +48,7 @@ struct nvkm_disp_func { int nv50_disp_oneinit(struct nvkm_disp *); int nv50_disp_init(struct nvkm_disp *); -void nv50_disp_fini(struct nvkm_disp *); +void nv50_disp_fini(struct nvkm_disp *, bool suspend); void nv50_disp_intr(struct nvkm_disp *); extern const struct nvkm_enum nv50_disp_intr_error_type[]; void nv50_disp_super(struct work_struct *); @@ -56,12 +60,12 @@ void nv50_disp_super_2_2(struct nvkm_disp *, struct nvkm_head *); void nv50_disp_super_3_0(struct nvkm_disp *, struct nvkm_head *); int gf119_disp_init(struct nvkm_disp *); -void gf119_disp_fini(struct nvkm_disp *); +void gf119_disp_fini(struct nvkm_disp *, bool suspend); void gf119_disp_intr(struct nvkm_disp *); void gf119_disp_super(struct work_struct *); void gf119_disp_intr_error(struct nvkm_disp *, int); -void gv100_disp_fini(struct nvkm_disp *); +void gv100_disp_fini(struct nvkm_disp *, bool suspend); void gv100_disp_intr(struct nvkm_disp *); void gv100_disp_super(struct work_struct *); int gv100_disp_wndw_cnt(struct nvkm_disp *, unsigned long *); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/r535.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/r535.c new file mode 100644 index 0000000000..6a0a4d3b89 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/r535.c @@ -0,0 +1,1714 @@ +/* + * Copyright 2023 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +#include "priv.h" +#include "chan.h" +#include "conn.h" +#include "dp.h" +#include "head.h" +#include "ior.h" +#include "outp.h" + +#include <core/ramht.h> +#include <subdev/bios.h> +#include <subdev/bios/conn.h> +#include <subdev/gsp.h> +#include <subdev/mmu.h> +#include <subdev/vfn.h> + +#include <nvhw/drf.h> + +#include <nvrm/nvtypes.h> +#include <nvrm/535.113.01/common/sdk/nvidia/inc/class/cl2080_notification.h> +#include <nvrm/535.113.01/common/sdk/nvidia/inc/ctrl/ctrl0073/ctrl0073dfp.h> +#include <nvrm/535.113.01/common/sdk/nvidia/inc/ctrl/ctrl0073/ctrl0073dp.h> +#include <nvrm/535.113.01/common/sdk/nvidia/inc/ctrl/ctrl0073/ctrl0073specific.h> +#include <nvrm/535.113.01/common/sdk/nvidia/inc/ctrl/ctrl0073/ctrl0073system.h> +#include <nvrm/535.113.01/common/sdk/nvidia/inc/ctrl/ctrl2080/ctrl2080internal.h> +#include <nvrm/535.113.01/common/sdk/nvidia/inc/nvos.h> +#include <nvrm/535.113.01/nvidia/generated/g_allclasses.h> +#include <nvrm/535.113.01/nvidia/generated/g_mem_desc_nvoc.h> +#include <nvrm/535.113.01/nvidia/inc/kernel/os/nv_memory_type.h> + +#include <linux/acpi.h> + +static u64 +r535_chan_user(struct nvkm_disp_chan *chan, u64 *psize) +{ + switch (chan->object.oclass & 0xff) { + case 0x7d: *psize = 0x10000; return 0x680000; + case 0x7e: *psize = 0x01000; return 0x690000 + (chan->head * *psize); + case 0x7b: *psize = 0x01000; return 0x6b0000 + (chan->head * *psize); + case 0x7a: *psize = 0x01000; return 0x6d8000 + (chan->head * *psize); + default: + BUG_ON(1); + break; + } + + return 0ULL; +} + +static void +r535_chan_intr(struct nvkm_disp_chan *chan, bool en) +{ +} + +static void +r535_chan_fini(struct nvkm_disp_chan *chan) +{ + nvkm_gsp_rm_free(&chan->rm.object); +} + +static int +r535_chan_push(struct nvkm_disp_chan *chan) +{ + struct nvkm_gsp *gsp = chan->disp->engine.subdev.device->gsp; + NV2080_CTRL_INTERNAL_DISPLAY_CHANNEL_PUSHBUFFER_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_DISPLAY_CHANNEL_PUSHBUFFER, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + if (chan->memory) { + switch (nvkm_memory_target(chan->memory)) { + case NVKM_MEM_TARGET_NCOH: + ctrl->addressSpace = ADDR_SYSMEM; + ctrl->cacheSnoop = 0; + break; + case NVKM_MEM_TARGET_HOST: + ctrl->addressSpace = ADDR_SYSMEM; + ctrl->cacheSnoop = 1; + break; + case NVKM_MEM_TARGET_VRAM: + ctrl->addressSpace = ADDR_FBMEM; + break; + default: + WARN_ON(1); + return -EINVAL; + } + + ctrl->physicalAddr = nvkm_memory_addr(chan->memory); + ctrl->limit = nvkm_memory_size(chan->memory) - 1; + } + + ctrl->hclass = chan->object.oclass; + ctrl->channelInstance = chan->head; + ctrl->valid = ((chan->object.oclass & 0xff) != 0x7a) ? 1 : 0; + + return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); +} + +static int +r535_curs_init(struct nvkm_disp_chan *chan) +{ + NV50VAIO_CHANNELPIO_ALLOCATION_PARAMETERS *args; + int ret; + + ret = r535_chan_push(chan); + if (ret) + return ret; + + args = nvkm_gsp_rm_alloc_get(&chan->disp->rm.object, + (chan->object.oclass << 16) | chan->head, + chan->object.oclass, sizeof(*args), &chan->rm.object); + if (IS_ERR(args)) + return PTR_ERR(args); + + args->channelInstance = chan->head; + + return nvkm_gsp_rm_alloc_wr(&chan->rm.object, args); +} + +static const struct nvkm_disp_chan_func +r535_curs_func = { + .init = r535_curs_init, + .fini = r535_chan_fini, + .intr = r535_chan_intr, + .user = r535_chan_user, +}; + +static const struct nvkm_disp_chan_user +r535_curs = { + .func = &r535_curs_func, + .user = 73, +}; + +static int +r535_dmac_bind(struct nvkm_disp_chan *chan, struct nvkm_object *object, u32 handle) +{ + return nvkm_ramht_insert(chan->disp->ramht, object, chan->chid.user, -9, handle, + chan->chid.user << 25 | + (chan->disp->rm.client.object.handle & 0x3fff)); +} + +static void +r535_dmac_fini(struct nvkm_disp_chan *chan) +{ + struct nvkm_device *device = chan->disp->engine.subdev.device; + const u32 uoff = (chan->chid.user - 1) * 0x1000; + + chan->suspend_put = nvkm_rd32(device, 0x690000 + uoff); + r535_chan_fini(chan); +} + +static int +r535_dmac_init(struct nvkm_disp_chan *chan) +{ + NV50VAIO_CHANNELDMA_ALLOCATION_PARAMETERS *args; + int ret; + + ret = r535_chan_push(chan); + if (ret) + return ret; + + args = nvkm_gsp_rm_alloc_get(&chan->disp->rm.object, + (chan->object.oclass << 16) | chan->head, + chan->object.oclass, sizeof(*args), &chan->rm.object); + if (IS_ERR(args)) + return PTR_ERR(args); + + args->channelInstance = chan->head; + args->offset = chan->suspend_put; + + return nvkm_gsp_rm_alloc_wr(&chan->rm.object, args); +} + +static int +r535_dmac_push(struct nvkm_disp_chan *chan, u64 memory) +{ + chan->memory = nvkm_umem_search(chan->object.client, memory); + if (IS_ERR(chan->memory)) + return PTR_ERR(chan->memory); + + return 0; +} + +static const struct nvkm_disp_chan_func +r535_dmac_func = { + .push = r535_dmac_push, + .init = r535_dmac_init, + .fini = r535_dmac_fini, + .intr = r535_chan_intr, + .user = r535_chan_user, + .bind = r535_dmac_bind, +}; + +static const struct nvkm_disp_chan_func +r535_wimm_func = { + .push = r535_dmac_push, + .init = r535_dmac_init, + .fini = r535_dmac_fini, + .intr = r535_chan_intr, + .user = r535_chan_user, +}; + +static const struct nvkm_disp_chan_user +r535_wimm = { + .func = &r535_wimm_func, + .user = 33, +}; + +static const struct nvkm_disp_chan_user +r535_wndw = { + .func = &r535_dmac_func, + .user = 1, +}; + +static void +r535_core_fini(struct nvkm_disp_chan *chan) +{ + struct nvkm_device *device = chan->disp->engine.subdev.device; + + chan->suspend_put = nvkm_rd32(device, 0x680000); + r535_chan_fini(chan); +} + +static const struct nvkm_disp_chan_func +r535_core_func = { + .push = r535_dmac_push, + .init = r535_dmac_init, + .fini = r535_core_fini, + .intr = r535_chan_intr, + .user = r535_chan_user, + .bind = r535_dmac_bind, +}; + +static const struct nvkm_disp_chan_user +r535_core = { + .func = &r535_core_func, + .user = 0, +}; + +static int +r535_sor_bl_set(struct nvkm_ior *sor, int lvl) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_SPECIFIC_BACKLIGHT_BRIGHTNESS_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_SET_BACKLIGHT_BRIGHTNESS, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->displayId = BIT(sor->asy.outp->index); + ctrl->brightness = lvl; + + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + +static int +r535_sor_bl_get(struct nvkm_ior *sor) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_SPECIFIC_BACKLIGHT_BRIGHTNESS_PARAMS *ctrl; + int ret, lvl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_GET_BACKLIGHT_BRIGHTNESS, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->displayId = BIT(sor->asy.outp->index); + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; + } + + lvl = ctrl->brightness; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return lvl; +} + +static const struct nvkm_ior_func_bl +r535_sor_bl = { + .get = r535_sor_bl_get, + .set = r535_sor_bl_set, +}; + +static void +r535_sor_hda_eld(struct nvkm_ior *sor, int head, u8 *data, u8 size) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_DFP_SET_ELD_AUDIO_CAP_PARAMS *ctrl; + + if (WARN_ON(size > sizeof(ctrl->bufferELD))) + return; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DFP_SET_ELD_AUDIO_CAPS, sizeof(*ctrl)); + if (WARN_ON(IS_ERR(ctrl))) + return; + + ctrl->displayId = BIT(sor->asy.outp->index); + ctrl->numELDSize = size; + memcpy(ctrl->bufferELD, data, size); + ctrl->maxFreqSupported = 0; //XXX + ctrl->ctrl = NVDEF(NV0073, CTRL_DFP_ELD_AUDIO_CAPS_CTRL, PD, TRUE); + ctrl->ctrl |= NVDEF(NV0073, CTRL_DFP_ELD_AUDIO_CAPS_CTRL, ELDV, TRUE); + ctrl->deviceEntry = head; + + WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); +} + +static void +r535_sor_hda_hpd(struct nvkm_ior *sor, int head, bool present) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_DFP_SET_ELD_AUDIO_CAP_PARAMS *ctrl; + + if (present) + return; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DFP_SET_ELD_AUDIO_CAPS, sizeof(*ctrl)); + if (WARN_ON(IS_ERR(ctrl))) + return; + + ctrl->displayId = BIT(sor->asy.outp->index); + ctrl->deviceEntry = head; + + WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); +} + +static const struct nvkm_ior_func_hda +r535_sor_hda = { + .hpd = r535_sor_hda_hpd, + .eld = r535_sor_hda_eld, +}; + +static void +r535_sor_dp_audio_mute(struct nvkm_ior *sor, bool mute) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_DP_SET_AUDIO_MUTESTREAM_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_SET_AUDIO_MUTESTREAM, sizeof(*ctrl)); + if (WARN_ON(IS_ERR(ctrl))) + return; + + ctrl->displayId = BIT(sor->asy.outp->index); + ctrl->mute = mute; + WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); +} + +static void +r535_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_DFP_SET_AUDIO_ENABLE_PARAMS *ctrl; + + if (!enable) + r535_sor_dp_audio_mute(sor, true); + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DFP_SET_AUDIO_ENABLE, sizeof(*ctrl)); + if (WARN_ON(IS_ERR(ctrl))) + return; + + ctrl->displayId = BIT(sor->asy.outp->index); + ctrl->enable = enable; + WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); + + if (enable) + r535_sor_dp_audio_mute(sor, false); +} + +static void +r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn) +{ + struct nvkm_disp *disp = sor->disp; + struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl)); + if (WARN_ON(IS_ERR(ctrl))) + return; + + ctrl->subDeviceInstance = 0; + ctrl->head = head; + ctrl->sorIndex = sor->id; + ctrl->dpLink = sor->asy.link == 2; + ctrl->bEnableOverride = 1; + ctrl->bMST = 1; + ctrl->hBlankSym = 0; + ctrl->vBlankSym = 0; + ctrl->colorFormat = 0; + ctrl->bEnableTwoHeadOneOr = 0; + ctrl->singleHeadMultistreamMode = 0; + ctrl->MST.slotStart = slot; + ctrl->MST.slotEnd = slot + slot_nr - 1; + ctrl->MST.PBN = pbn; + ctrl->MST.Timeslice = aligned_pbn; + ctrl->MST.sendACT = 0; + ctrl->MST.singleHeadMSTPipeline = 0; + ctrl->MST.bEnableAudioOverRightPanel = 0; + WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); +} + +static int +r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef, + u32 watermark, u32 hblanksym, u32 vblanksym) +{ + struct nvkm_disp *disp = sor->disp; + struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->head = head; + ctrl->sorIndex = sor->id; + ctrl->dpLink = sor->asy.link == 2; + ctrl->bEnableOverride = 1; + ctrl->bMST = 0; + ctrl->hBlankSym = hblanksym; + ctrl->vBlankSym = vblanksym; + ctrl->colorFormat = 0; + ctrl->bEnableTwoHeadOneOr = 0; + ctrl->SST.bEnhancedFraming = ef; + ctrl->SST.tuSize = 64; + ctrl->SST.waterMark = watermark; + ctrl->SST.bEnableAudioOverRightPanel = 0; + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + +static const struct nvkm_ior_func_dp +r535_sor_dp = { + .sst = r535_sor_dp_sst, + .vcpi = r535_sor_dp_vcpi, + .audio = r535_sor_dp_audio, +}; + +static void +r535_sor_hdmi_scdc(struct nvkm_ior *sor, u32 khz, bool support, bool scrambling, + bool scrambling_low_rates) +{ + struct nvkm_outp *outp = sor->asy.outp; + struct nvkm_disp *disp = outp->disp; + NV0073_CTRL_SPECIFIC_SET_HDMI_SINK_CAPS_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_SINK_CAPS, sizeof(*ctrl)); + if (WARN_ON(IS_ERR(ctrl))) + return; + + ctrl->displayId = BIT(outp->index); + ctrl->caps = 0; + if (support) + ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, SCDC_SUPPORTED, TRUE); + if (scrambling) + ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, GT_340MHZ_CLOCK_SUPPORTED, TRUE); + if (scrambling_low_rates) + ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, LTE_340MHZ_SCRAMBLING_SUPPORTED, TRUE); + + WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); +} + +static void +r535_sor_hdmi_ctrl_audio_mute(struct nvkm_outp *outp, bool mute) +{ + struct nvkm_disp *disp = outp->disp; + NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_AUDIO_MUTESTREAM_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_AUDIO_MUTESTREAM, sizeof(*ctrl)); + if (WARN_ON(IS_ERR(ctrl))) + return; + + ctrl->displayId = BIT(outp->index); + ctrl->mute = mute; + WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); +} + +static void +r535_sor_hdmi_ctrl_audio(struct nvkm_outp *outp, bool enable) +{ + struct nvkm_disp *disp = outp->disp; + NV0073_CTRL_SPECIFIC_SET_OD_PACKET_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_SET_OD_PACKET, sizeof(*ctrl)); + if (WARN_ON(IS_ERR(ctrl))) + return; + + ctrl->displayId = BIT(outp->index); + ctrl->transmitControl = + NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, ENABLE, YES) | + NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, OTHER_FRAME, DISABLE) | + NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, SINGLE_FRAME, DISABLE) | + NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, ON_HBLANK, DISABLE) | + NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, VIDEO_FMT, SW_CONTROLLED) | + NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, RESERVED_LEGACY_MODE, NO); + ctrl->packetSize = 10; + ctrl->aPacket[0] = 0x03; + ctrl->aPacket[1] = 0x00; + ctrl->aPacket[2] = 0x00; + ctrl->aPacket[3] = enable ? 0x10 : 0x01; + ctrl->aPacket[4] = 0x00; + ctrl->aPacket[5] = 0x00; + ctrl->aPacket[6] = 0x00; + ctrl->aPacket[7] = 0x00; + ctrl->aPacket[8] = 0x00; + ctrl->aPacket[9] = 0x00; + WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); +} + +static void +r535_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable) +{ + struct nvkm_device *device = sor->disp->engine.subdev.device; + const u32 hdmi = head * 0x400; + + r535_sor_hdmi_ctrl_audio(sor->asy.outp, enable); + r535_sor_hdmi_ctrl_audio_mute(sor->asy.outp, !enable); + + /* General Control (GCP). */ + nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000000); + nvkm_wr32(device, 0x6f00cc + hdmi, !enable ? 0x00000001 : 0x00000010); + nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001); +} + +static void +r535_sor_hdmi_ctrl(struct nvkm_ior *sor, int head, bool enable, u8 max_ac_packet, u8 rekey) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_SPECIFIC_SET_HDMI_ENABLE_PARAMS *ctrl; + + if (!enable) + return; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_ENABLE, sizeof(*ctrl)); + if (WARN_ON(IS_ERR(ctrl))) + return; + + ctrl->displayId = BIT(sor->asy.outp->index); + ctrl->enable = enable; + + WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); +} + +static const struct nvkm_ior_func_hdmi +r535_sor_hdmi = { + .ctrl = r535_sor_hdmi_ctrl, + .scdc = r535_sor_hdmi_scdc, + /*TODO: SF_USER -> KMS. */ + .infoframe_avi = gv100_sor_hdmi_infoframe_avi, + .infoframe_vsi = gv100_sor_hdmi_infoframe_vsi, + .audio = r535_sor_hdmi_audio, +}; + +static const struct nvkm_ior_func +r535_sor = { + .hdmi = &r535_sor_hdmi, + .dp = &r535_sor_dp, + .hda = &r535_sor_hda, + .bl = &r535_sor_bl, +}; + +static int +r535_sor_new(struct nvkm_disp *disp, int id) +{ + return nvkm_ior_new_(&r535_sor, disp, SOR, id, true/*XXX: hda cap*/); +} + +static int +r535_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask) +{ + *pmask = 0xf; + return 4; +} + +static void +r535_head_vblank_put(struct nvkm_head *head) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + + nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000); +} + +static void +r535_head_vblank_get(struct nvkm_head *head) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + + nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002); + nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002); +} + +static void +r535_head_state(struct nvkm_head *head, struct nvkm_head_state *state) +{ +} + +static const struct nvkm_head_func +r535_head = { + .state = r535_head_state, + .vblank_get = r535_head_vblank_get, + .vblank_put = r535_head_vblank_put, +}; + +static struct nvkm_conn * +r535_conn_new(struct nvkm_disp *disp, u32 id) +{ + NV0073_CTRL_SPECIFIC_GET_CONNECTOR_DATA_PARAMS *ctrl; + struct nvbios_connE dcbE = {}; + struct nvkm_conn *conn; + int ret, index; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_GET_CONNECTOR_DATA, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return (void *)ctrl; + + ctrl->subDeviceInstance = 0; + ctrl->displayId = BIT(id); + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ERR_PTR(ret); + } + + list_for_each_entry(conn, &disp->conns, head) { + if (conn->index == ctrl->data[0].index) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return conn; + } + } + + dcbE.type = ctrl->data[0].type; + index = ctrl->data[0].index; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + + ret = nvkm_conn_new(disp, index, &dcbE, &conn); + if (ret) + return ERR_PTR(ret); + + list_add_tail(&conn->head, &disp->conns); + return conn; +} + +static void +r535_outp_release(struct nvkm_outp *outp) +{ + outp->disp->rm.assigned_sors &= ~BIT(outp->ior->id); + outp->ior->asy.outp = NULL; + outp->ior = NULL; +} + +static int +r535_outp_acquire(struct nvkm_outp *outp, bool hda) +{ + struct nvkm_disp *disp = outp->disp; + struct nvkm_ior *ior; + NV0073_CTRL_DFP_ASSIGN_SOR_PARAMS *ctrl; + int ret, or; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DFP_ASSIGN_SOR, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->displayId = BIT(outp->index); + ctrl->sorExcludeMask = disp->rm.assigned_sors; + if (hda) + ctrl->flags |= NVDEF(NV0073_CTRL, DFP_ASSIGN_SOR_FLAGS, AUDIO, OPTIMAL); + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; + } + + for (or = 0; or < ARRAY_SIZE(ctrl->sorAssignListWithTag); or++) { + if (ctrl->sorAssignListWithTag[or].displayMask & BIT(outp->index)) { + disp->rm.assigned_sors |= BIT(or); + break; + } + } + + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + + if (WARN_ON(or == ARRAY_SIZE(ctrl->sorAssignListWithTag))) + return -EINVAL; + + ior = nvkm_ior_find(disp, SOR, or); + if (WARN_ON(!ior)) + return -EINVAL; + + nvkm_outp_acquire_ior(outp, NVKM_OUTP_USER, ior); + return 0; +} + +static int +r535_disp_head_displayid(struct nvkm_disp *disp, int head, u32 *displayid) +{ + NV0073_CTRL_SYSTEM_GET_ACTIVE_PARAMS *ctrl; + int ret; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SYSTEM_GET_ACTIVE, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->head = head; + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; + } + + *displayid = ctrl->displayId; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return 0; +} + +static struct nvkm_ior * +r535_outp_inherit(struct nvkm_outp *outp) +{ + struct nvkm_disp *disp = outp->disp; + struct nvkm_head *head; + u32 displayid; + int ret; + + list_for_each_entry(head, &disp->heads, head) { + ret = r535_disp_head_displayid(disp, head->id, &displayid); + if (WARN_ON(ret)) + return NULL; + + if (displayid == BIT(outp->index)) { + NV0073_CTRL_SPECIFIC_OR_GET_INFO_PARAMS *ctrl; + u32 id, proto; + struct nvkm_ior *ior; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_OR_GET_INFO, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return NULL; + + ctrl->subDeviceInstance = 0; + ctrl->displayId = displayid; + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return NULL; + } + + id = ctrl->index; + proto = ctrl->protocol; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + + ior = nvkm_ior_find(disp, SOR, id); + if (WARN_ON(!ior)) + return NULL; + + switch (proto) { + case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_SINGLE_TMDS_A: + ior->arm.proto = TMDS; + ior->arm.link = 1; + break; + case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_SINGLE_TMDS_B: + ior->arm.proto = TMDS; + ior->arm.link = 2; + break; + case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DUAL_TMDS: + ior->arm.proto = TMDS; + ior->arm.link = 3; + break; + case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DP_A: + ior->arm.proto = DP; + ior->arm.link = 1; + break; + case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DP_B: + ior->arm.proto = DP; + ior->arm.link = 2; + break; + default: + WARN_ON(1); + return NULL; + } + + ior->arm.proto_evo = proto; + ior->arm.head = BIT(head->id); + disp->rm.assigned_sors |= BIT(ior->id); + return ior; + } + } + + return NULL; +} + +static int +r535_outp_dfp_get_info(struct nvkm_outp *outp) +{ + NV0073_CTRL_DFP_GET_INFO_PARAMS *ctrl; + struct nvkm_disp *disp = outp->disp; + int ret; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, NV0073_CTRL_CMD_DFP_GET_INFO, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->displayId = BIT(outp->index); + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; + } + + nvkm_debug(&disp->engine.subdev, "DFP %08x: flags:%08x flags2:%08x\n", + ctrl->displayId, ctrl->flags, ctrl->flags2); + + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return 0; +} + +static int +r535_outp_detect(struct nvkm_outp *outp) +{ + NV0073_CTRL_SYSTEM_GET_CONNECT_STATE_PARAMS *ctrl; + struct nvkm_disp *disp = outp->disp; + int ret; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SYSTEM_GET_CONNECT_STATE, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->displayMask = BIT(outp->index); + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; + } + + if (ctrl->displayMask & BIT(outp->index)) { + ret = r535_outp_dfp_get_info(outp); + if (ret == 0) + ret = 1; + } else { + ret = 0; + } + + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; +} + +static int +r535_dp_mst_id_put(struct nvkm_outp *outp, u32 id) +{ + NV0073_CTRL_CMD_DP_TOPOLOGY_FREE_DISPLAYID_PARAMS *ctrl; + struct nvkm_disp *disp = outp->disp; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_TOPOLOGY_FREE_DISPLAYID, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->displayId = id; + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + +static int +r535_dp_mst_id_get(struct nvkm_outp *outp, u32 *pid) +{ + NV0073_CTRL_CMD_DP_TOPOLOGY_ALLOCATE_DISPLAYID_PARAMS *ctrl; + struct nvkm_disp *disp = outp->disp; + int ret; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_TOPOLOGY_ALLOCATE_DISPLAYID, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->displayId = BIT(outp->index); + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; + } + + *pid = ctrl->displayIdAssigned; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return 0; +} + +static int +r535_dp_drive(struct nvkm_outp *outp, u8 lanes, u8 pe[4], u8 vs[4]) +{ + NV0073_CTRL_DP_LANE_DATA_PARAMS *ctrl; + struct nvkm_disp *disp = outp->disp; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_SET_LANE_DATA, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->displayId = BIT(outp->index); + ctrl->numLanes = lanes; + for (int i = 0; i < lanes; i++) + ctrl->data[i] = NVVAL(NV0073_CTRL, DP_LANE_DATA, PREEMPHASIS, pe[i]) | + NVVAL(NV0073_CTRL, DP_LANE_DATA, DRIVECURRENT, vs[i]); + + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + +static int +r535_dp_train_target(struct nvkm_outp *outp, u8 target, bool mst, u8 link_nr, u8 link_bw) +{ + struct nvkm_disp *disp = outp->disp; + NV0073_CTRL_DP_CTRL_PARAMS *ctrl; + int ret, retries; + u32 cmd, data; + + cmd = NVDEF(NV0073_CTRL, DP_CMD, SET_LANE_COUNT, TRUE) | + NVDEF(NV0073_CTRL, DP_CMD, SET_LINK_BW, TRUE) | + NVDEF(NV0073_CTRL, DP_CMD, TRAIN_PHY_REPEATER, YES); + data = NVVAL(NV0073_CTRL, DP_DATA, SET_LANE_COUNT, link_nr) | + NVVAL(NV0073_CTRL, DP_DATA, SET_LINK_BW, link_bw) | + NVVAL(NV0073_CTRL, DP_DATA, TARGET, target); + + if (mst) + cmd |= NVDEF(NV0073_CTRL, DP_CMD, SET_FORMAT_MODE, MULTI_STREAM); + + if (outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP) + cmd |= NVDEF(NV0073_CTRL, DP_CMD, SET_ENHANCED_FRAMING, TRUE); + + if (target == 0 && + (outp->dp.dpcd[DPCD_RC02] & 0x20) && + !(outp->dp.dpcd[DPCD_RC03] & DPCD_RC03_TPS4_SUPPORTED)) + cmd |= NVDEF(NV0073_CTRL, DP_CMD, POST_LT_ADJ_REQ_GRANTED, YES); + + /* We should retry up to 3 times, but only if GSP asks politely */ + for (retries = 0; retries < 3; ++retries) { + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, NV0073_CTRL_CMD_DP_CTRL, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->displayId = BIT(outp->index); + ctrl->retryTimeMs = 0; + ctrl->cmd = cmd; + ctrl->data = data; + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret == -EAGAIN && ctrl->retryTimeMs) { + /* + * Device (likely an eDP panel) isn't ready yet, wait for the time specified + * by GSP before retrying again + */ + nvkm_debug(&disp->engine.subdev, + "Waiting %dms for GSP LT panel delay before retrying\n", + ctrl->retryTimeMs); + msleep(ctrl->retryTimeMs); + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + } else { + /* GSP didn't say to retry, or we were successful */ + if (ctrl->err) + ret = -EIO; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + break; + } + } + + return ret; +} + +static int +r535_dp_train(struct nvkm_outp *outp, bool retrain) +{ + for (int target = outp->dp.lttprs; target >= 0; target--) { + int ret = r535_dp_train_target(outp, target, outp->dp.lt.mst, + outp->dp.lt.nr, + outp->dp.lt.bw); + if (ret) + return ret; + } + + return 0; +} + +static int +r535_dp_rates(struct nvkm_outp *outp) +{ + NV0073_CTRL_CMD_DP_CONFIG_INDEXED_LINK_RATES_PARAMS *ctrl; + struct nvkm_disp *disp = outp->disp; + + if (outp->conn->info.type != DCB_CONNECTOR_eDP || + !outp->dp.rates || outp->dp.rate[0].dpcd < 0) + return 0; + + if (WARN_ON(outp->dp.rates > ARRAY_SIZE(ctrl->linkRateTbl))) + return -EINVAL; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_CONFIG_INDEXED_LINK_RATES, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->displayId = BIT(outp->index); + for (int i = 0; i < outp->dp.rates; i++) + ctrl->linkRateTbl[outp->dp.rate[i].dpcd] = outp->dp.rate[i].rate * 10 / 200; + + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + +static int +r535_dp_aux_xfer(struct nvkm_outp *outp, u8 type, u32 addr, u8 *data, u8 *psize) +{ + struct nvkm_disp *disp = outp->disp; + NV0073_CTRL_DP_AUXCH_CTRL_PARAMS *ctrl; + u8 size = *psize; + int ret; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, NV0073_CTRL_CMD_DP_AUXCH_CTRL, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->displayId = BIT(outp->index); + ctrl->bAddrOnly = !size; + ctrl->cmd = type; + if (ctrl->bAddrOnly) { + ctrl->cmd = NVDEF_SET(ctrl->cmd, NV0073_CTRL, DP_AUXCH_CMD, REQ_TYPE, WRITE); + ctrl->cmd = NVDEF_SET(ctrl->cmd, NV0073_CTRL, DP_AUXCH_CMD, I2C_MOT, FALSE); + } + ctrl->addr = addr; + ctrl->size = !ctrl->bAddrOnly ? (size - 1) : 0; + memcpy(ctrl->data, data, size); + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return PTR_ERR(ctrl); + } + + memcpy(data, ctrl->data, size); + *psize = ctrl->size; + ret = ctrl->replyType; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; +} + +static int +r535_dp_aux_pwr(struct nvkm_outp *outp, bool pu) +{ + return 0; +} + +static void +r535_dp_release(struct nvkm_outp *outp) +{ + if (!outp->dp.lt.bw) { + if (!WARN_ON(!outp->dp.rates)) + outp->dp.lt.bw = outp->dp.rate[0].rate / 27000; + else + outp->dp.lt.bw = 0x06; + } + + outp->dp.lt.nr = 0; + + r535_dp_train_target(outp, 0, outp->dp.lt.mst, outp->dp.lt.nr, outp->dp.lt.bw); + r535_outp_release(outp); +} + +static int +r535_dp_acquire(struct nvkm_outp *outp, bool hda) +{ + int ret; + + ret = r535_outp_acquire(outp, hda); + if (ret) + return ret; + + return 0; +} + +static const struct nvkm_outp_func +r535_dp = { + .detect = r535_outp_detect, + .inherit = r535_outp_inherit, + .acquire = r535_dp_acquire, + .release = r535_dp_release, + .dp.aux_pwr = r535_dp_aux_pwr, + .dp.aux_xfer = r535_dp_aux_xfer, + .dp.mst_id_get = r535_dp_mst_id_get, + .dp.mst_id_put = r535_dp_mst_id_put, + .dp.rates = r535_dp_rates, + .dp.train = r535_dp_train, + .dp.drive = r535_dp_drive, +}; + +static int +r535_tmds_edid_get(struct nvkm_outp *outp, u8 *data, u16 *psize) +{ + NV0073_CTRL_SPECIFIC_GET_EDID_V2_PARAMS *ctrl; + struct nvkm_disp *disp = outp->disp; + int ret = -E2BIG; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_GET_EDID_V2, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->displayId = BIT(outp->index); + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; + } + + ret = -E2BIG; + if (ctrl->bufferSize <= *psize) { + memcpy(data, ctrl->edidBuffer, ctrl->bufferSize); + *psize = ctrl->bufferSize; + ret = 0; + } + + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; +} + +static const struct nvkm_outp_func +r535_tmds = { + .detect = r535_outp_detect, + .inherit = r535_outp_inherit, + .acquire = r535_outp_acquire, + .release = r535_outp_release, + .edid_get = r535_tmds_edid_get, +}; + +static int +r535_outp_new(struct nvkm_disp *disp, u32 id) +{ + NV0073_CTRL_SPECIFIC_OR_GET_INFO_PARAMS *ctrl; + enum nvkm_ior_proto proto; + struct dcb_output dcbE = {}; + struct nvkm_conn *conn; + struct nvkm_outp *outp; + u8 locn, link = 0; + int ret; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_OR_GET_INFO, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->displayId = BIT(id); + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; + } + + switch (ctrl->type) { + case NV0073_CTRL_SPECIFIC_OR_TYPE_NONE: + return 0; + case NV0073_CTRL_SPECIFIC_OR_TYPE_SOR: + switch (ctrl->protocol) { + case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_SINGLE_TMDS_A: + proto = TMDS; + link = 1; + break; + case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_SINGLE_TMDS_B: + proto = TMDS; + link = 2; + break; + case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DUAL_TMDS: + proto = TMDS; + link = 3; + break; + case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DP_A: + proto = DP; + link = 1; + break; + case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DP_B: + proto = DP; + link = 2; + break; + default: + WARN_ON(1); + return -EINVAL; + } + + break; + default: + WARN_ON(1); + return -EINVAL; + } + + locn = ctrl->location; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + + conn = r535_conn_new(disp, id); + if (IS_ERR(conn)) + return PTR_ERR(conn); + + switch (proto) { + case TMDS: dcbE.type = DCB_OUTPUT_TMDS; break; + case DP: dcbE.type = DCB_OUTPUT_DP; break; + default: + WARN_ON(1); + return -EINVAL; + } + + dcbE.location = locn; + dcbE.connector = conn->index; + dcbE.heads = disp->head.mask; + dcbE.i2c_index = 0xff; + dcbE.link = dcbE.sorconf.link = link; + + if (proto == TMDS) { + ret = nvkm_outp_new_(&r535_tmds, disp, id, &dcbE, &outp); + if (ret) + return ret; + } else { + NV0073_CTRL_CMD_DP_GET_CAPS_PARAMS *ctrl; + bool mst, wm; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_GET_CAPS, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->sorIndex = ~0; + + ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl)); + if (ret) { + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + return ret; + } + + switch (NVVAL_GET(ctrl->maxLinkRate, NV0073_CTRL_CMD, DP_GET_CAPS, MAX_LINK_RATE)) { + case NV0073_CTRL_CMD_DP_GET_CAPS_MAX_LINK_RATE_1_62: + dcbE.dpconf.link_bw = 0x06; + break; + case NV0073_CTRL_CMD_DP_GET_CAPS_MAX_LINK_RATE_2_70: + dcbE.dpconf.link_bw = 0x0a; + break; + case NV0073_CTRL_CMD_DP_GET_CAPS_MAX_LINK_RATE_5_40: + dcbE.dpconf.link_bw = 0x14; + break; + case NV0073_CTRL_CMD_DP_GET_CAPS_MAX_LINK_RATE_8_10: + dcbE.dpconf.link_bw = 0x1e; + break; + default: + dcbE.dpconf.link_bw = 0x00; + break; + } + + mst = ctrl->bIsMultistreamSupported; + wm = ctrl->bHasIncreasedWatermarkLimits; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + + if (WARN_ON(!dcbE.dpconf.link_bw)) + return -EINVAL; + + dcbE.dpconf.link_nr = 4; + + ret = nvkm_outp_new_(&r535_dp, disp, id, &dcbE, &outp); + if (ret) + return ret; + + outp->dp.mst = mst; + outp->dp.increased_wm = wm; + } + + + outp->conn = conn; + list_add_tail(&outp->head, &disp->outps); + return 0; +} + +static void +r535_disp_irq(struct nvkm_gsp_event *event, void *repv, u32 repc) +{ + struct nvkm_disp *disp = container_of(event, typeof(*disp), rm.irq); + Nv2080DpIrqNotification *irq = repv; + + if (WARN_ON(repc < sizeof(*irq))) + return; + + nvkm_debug(&disp->engine.subdev, "event: dp irq displayId %08x\n", irq->displayId); + + if (irq->displayId) + nvkm_event_ntfy(&disp->rm.event, fls(irq->displayId) - 1, NVKM_DPYID_IRQ); +} + +static void +r535_disp_hpd(struct nvkm_gsp_event *event, void *repv, u32 repc) +{ + struct nvkm_disp *disp = container_of(event, typeof(*disp), rm.hpd); + Nv2080HotplugNotification *hpd = repv; + + if (WARN_ON(repc < sizeof(*hpd))) + return; + + nvkm_debug(&disp->engine.subdev, "event: hpd plug %08x unplug %08x\n", + hpd->plugDisplayMask, hpd->unplugDisplayMask); + + for (int i = 0; i < 31; i++) { + u32 mask = 0; + + if (hpd->plugDisplayMask & BIT(i)) + mask |= NVKM_DPYID_PLUG; + if (hpd->unplugDisplayMask & BIT(i)) + mask |= NVKM_DPYID_UNPLUG; + + if (mask) + nvkm_event_ntfy(&disp->rm.event, i, mask); + } +} + +static const struct nvkm_event_func +r535_disp_event = { +}; + +static void +r535_disp_intr_head_timing(struct nvkm_disp *disp, int head) +{ + struct nvkm_subdev *subdev = &disp->engine.subdev; + struct nvkm_device *device = subdev->device; + u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04)); + + if (stat & 0x00000002) { + nvkm_disp_vblank(disp, head); + + nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002); + } +} + +static irqreturn_t +r535_disp_intr(struct nvkm_inth *inth) +{ + struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth); + struct nvkm_subdev *subdev = &disp->engine.subdev; + struct nvkm_device *device = subdev->device; + unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff; + int head; + + for_each_set_bit(head, &mask, 8) + r535_disp_intr_head_timing(disp, head); + + return IRQ_HANDLED; +} + +static void +r535_disp_fini(struct nvkm_disp *disp, bool suspend) +{ + if (!disp->engine.subdev.use.enabled) + return; + + nvkm_gsp_rm_free(&disp->rm.object); + + if (!suspend) { + nvkm_gsp_event_dtor(&disp->rm.irq); + nvkm_gsp_event_dtor(&disp->rm.hpd); + nvkm_event_fini(&disp->rm.event); + + nvkm_gsp_rm_free(&disp->rm.objcom); + nvkm_gsp_device_dtor(&disp->rm.device); + nvkm_gsp_client_dtor(&disp->rm.client); + } +} + +static int +r535_disp_init(struct nvkm_disp *disp) +{ + int ret; + + ret = nvkm_gsp_rm_alloc(&disp->rm.device.object, disp->func->root.oclass << 16, + disp->func->root.oclass, 0, &disp->rm.object); + if (ret) + return ret; + + return 0; +} + +static int +r535_disp_oneinit(struct nvkm_disp *disp) +{ + struct nvkm_device *device = disp->engine.subdev.device; + struct nvkm_gsp *gsp = device->gsp; + NV2080_CTRL_INTERNAL_DISPLAY_WRITE_INST_MEM_PARAMS *ctrl; + int ret, i; + + /* RAMIN. */ + ret = nvkm_gpuobj_new(device, 0x10000, 0x10000, false, NULL, &disp->inst); + if (ret) + return ret; + + if (WARN_ON(nvkm_memory_target(disp->inst->memory) != NVKM_MEM_TARGET_VRAM)) + return -EINVAL; + + ctrl = nvkm_gsp_rm_ctrl_get(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_DISPLAY_WRITE_INST_MEM, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->instMemPhysAddr = nvkm_memory_addr(disp->inst->memory); + ctrl->instMemSize = nvkm_memory_size(disp->inst->memory); + ctrl->instMemAddrSpace = ADDR_FBMEM; + ctrl->instMemCpuCacheAttr = NV_MEMORY_WRITECOMBINED; + + ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); + if (ret) + return ret; + + /* OBJs. */ + ret = nvkm_gsp_client_device_ctor(gsp, &disp->rm.client, &disp->rm.device); + if (ret) + return ret; + + ret = nvkm_gsp_rm_alloc(&disp->rm.device.object, 0x00730000, NV04_DISPLAY_COMMON, 0, + &disp->rm.objcom); + if (ret) + return ret; + + { + NV2080_CTRL_INTERNAL_DISPLAY_GET_STATIC_INFO_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_DISPLAY_GET_STATIC_INFO, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + disp->wndw.mask = ctrl->windowPresentMask; + disp->wndw.nr = fls(disp->wndw.mask); + nvkm_gsp_rm_ctrl_done(&gsp->internal.device.subdevice, ctrl); + } + + /* */ + { +#if defined(CONFIG_ACPI) && defined(CONFIG_X86) + NV2080_CTRL_INTERNAL_INIT_BRIGHTC_STATE_LOAD_PARAMS *ctrl; + struct nvkm_gsp_object *subdevice = &disp->rm.client.gsp->internal.device.subdevice; + + ctrl = nvkm_gsp_rm_ctrl_get(subdevice, + NV2080_CTRL_CMD_INTERNAL_INIT_BRIGHTC_STATE_LOAD, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->status = 0x56; /* NV_ERR_NOT_SUPPORTED */ + + { + const guid_t NBCI_DSM_GUID = + GUID_INIT(0xD4A50B75, 0x65C7, 0x46F7, + 0xBF, 0xB7, 0x41, 0x51, 0x4C, 0xEA, 0x02, 0x44); + u64 NBCI_DSM_REV = 0x00000102; + const guid_t NVHG_DSM_GUID = + GUID_INIT(0x9D95A0A0, 0x0060, 0x4D48, + 0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4); + u64 NVHG_DSM_REV = 0x00000102; + acpi_handle handle = ACPI_HANDLE(device->dev); + + if (handle && acpi_has_method(handle, "_DSM")) { + bool nbci = acpi_check_dsm(handle, &NBCI_DSM_GUID, NBCI_DSM_REV, + 1ULL << 0x00000014); + bool nvhg = acpi_check_dsm(handle, &NVHG_DSM_GUID, NVHG_DSM_REV, + 1ULL << 0x00000014); + + if (nbci || nvhg) { + union acpi_object argv4 = { + .buffer.type = ACPI_TYPE_BUFFER, + .buffer.length = sizeof(ctrl->backLightData), + .buffer.pointer = kmalloc(argv4.buffer.length, GFP_KERNEL), + }, *obj; + + obj = acpi_evaluate_dsm(handle, nbci ? &NBCI_DSM_GUID : &NVHG_DSM_GUID, + 0x00000102, 0x14, &argv4); + if (!obj) { + acpi_handle_info(handle, "failed to evaluate _DSM\n"); + } else { + for (int i = 0; i < obj->package.count; i++) { + union acpi_object *elt = &obj->package.elements[i]; + u32 size; + + if (elt->integer.value & ~0xffffffffULL) + size = 8; + else + size = 4; + + memcpy(&ctrl->backLightData[ctrl->backLightDataSize], &elt->integer.value, size); + ctrl->backLightDataSize += size; + } + + ctrl->status = 0; + ACPI_FREE(obj); + } + + kfree(argv4.buffer.pointer); + } + } + } + + ret = nvkm_gsp_rm_ctrl_wr(subdevice, ctrl); + if (ret) + return ret; +#endif + } + + /* */ + { + NV0073_CTRL_CMD_DP_SET_MANUAL_DISPLAYPORT_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_SET_MANUAL_DISPLAYPORT, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ret = nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); + if (ret) + return ret; + } + + /* */ + { + NV0073_CTRL_SYSTEM_GET_NUM_HEADS_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_rd(&disp->rm.objcom, + NV0073_CTRL_CMD_SYSTEM_GET_NUM_HEADS, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + disp->head.nr = ctrl->numHeads; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + } + + /* */ + { + NV0073_CTRL_SPECIFIC_GET_ALL_HEAD_MASK_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_rd(&disp->rm.objcom, + NV0073_CTRL_CMD_SPECIFIC_GET_ALL_HEAD_MASK, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + disp->head.mask = ctrl->headMask; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + + for_each_set_bit(i, &disp->head.mask, disp->head.nr) { + ret = nvkm_head_new_(&r535_head, disp, i); + if (ret) + return ret; + } + } + + disp->sor.nr = disp->func->sor.cnt(disp, &disp->sor.mask); + nvkm_debug(&disp->engine.subdev, " SOR(s): %d (%02lx)\n", disp->sor.nr, disp->sor.mask); + for_each_set_bit(i, &disp->sor.mask, disp->sor.nr) { + ret = disp->func->sor.new(disp, i); + if (ret) + return ret; + } + + /* */ + { + NV0073_CTRL_SYSTEM_GET_SUPPORTED_PARAMS *ctrl; + unsigned long mask; + int i; + + ctrl = nvkm_gsp_rm_ctrl_rd(&disp->rm.objcom, + NV0073_CTRL_CMD_SYSTEM_GET_SUPPORTED, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + mask = ctrl->displayMask; + nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); + + for_each_set_bit(i, &mask, 32) { + ret = r535_outp_new(disp, i); + if (ret) + return ret; + } + } + + ret = nvkm_event_init(&r535_disp_event, &gsp->subdev, 3, 32, &disp->rm.event); + if (WARN_ON(ret)) + return ret; + + ret = nvkm_gsp_device_event_ctor(&disp->rm.device, 0x007e0000, NV2080_NOTIFIERS_HOTPLUG, + r535_disp_hpd, &disp->rm.hpd); + if (ret) + return ret; + + ret = nvkm_gsp_device_event_ctor(&disp->rm.device, 0x007e0001, NV2080_NOTIFIERS_DP_IRQ, + r535_disp_irq, &disp->rm.irq); + if (ret) + return ret; + + /* RAMHT. */ + ret = nvkm_ramht_new(device, disp->func->ramht_size ? disp->func->ramht_size : + 0x1000, 0, disp->inst, &disp->ramht); + if (ret) + return ret; + + ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, disp->engine.subdev.inst); + if (ret < 0) + return ret; + + ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev, + r535_disp_intr, &disp->engine.subdev.inth); + if (ret) + return ret; + + nvkm_inth_allow(&disp->engine.subdev.inth); + return 0; +} + +static void +r535_disp_dtor(struct nvkm_disp *disp) +{ + kfree(disp->func); +} + +int +r535_disp_new(const struct nvkm_disp_func *hw, struct nvkm_device *device, + enum nvkm_subdev_type type, int inst, struct nvkm_disp **pdisp) +{ + struct nvkm_disp_func *rm; + int ret; + + if (!(rm = kzalloc(sizeof(*rm) + 6 * sizeof(rm->user[0]), GFP_KERNEL))) + return -ENOMEM; + + rm->dtor = r535_disp_dtor; + rm->oneinit = r535_disp_oneinit; + rm->init = r535_disp_init; + rm->fini = r535_disp_fini; + rm->uevent = hw->uevent; + rm->sor.cnt = r535_sor_cnt; + rm->sor.new = r535_sor_new; + rm->ramht_size = hw->ramht_size; + + rm->root = hw->root; + + for (int i = 0; hw->user[i].ctor; i++) { + switch (hw->user[i].base.oclass & 0xff) { + case 0x73: rm->user[i] = hw->user[i]; break; + case 0x7d: rm->user[i] = hw->user[i]; rm->user[i].chan = &r535_core; break; + case 0x7e: rm->user[i] = hw->user[i]; rm->user[i].chan = &r535_wndw; break; + case 0x7b: rm->user[i] = hw->user[i]; rm->user[i].chan = &r535_wimm; break; + case 0x7a: rm->user[i] = hw->user[i]; rm->user[i].chan = &r535_curs; break; + default: + WARN_ON(1); + continue; + } + } + + ret = nvkm_disp_new_(rm, device, type, inst, pdisp); + if (ret) + kfree(rm); + + mutex_init(&(*pdisp)->super.mutex); //XXX + return ret; +} diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c index f5242a6722..dcb9f8ba37 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c @@ -25,6 +25,7 @@ #include "ior.h" #include <core/gpuobj.h> +#include <subdev/gsp.h> #include <subdev/timer.h> #include <nvif/class.h> @@ -88,6 +89,7 @@ tu102_sor = { .state = gv100_sor_state, .power = nv50_sor_power, .clock = gf119_sor_clock, + .bl = >215_sor_bl, .hdmi = &gv100_sor_hdmi, .dp = &tu102_sor_dp, .hda = &gv100_sor_hda, @@ -232,5 +234,8 @@ int tu102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, struct nvkm_disp **pdisp) { + if (nvkm_gsp_rm(device->gsp)) + return r535_disp_new(&tu102_disp, device, type, inst, pdisp); + return nvkm_disp_new_(&tu102_disp, device, type, inst, pdisp); } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c index 3249e5c1c8..2dab6612c4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uconn.c @@ -31,6 +31,23 @@ #include <nvif/if0011.h> static int +nvkm_uconn_uevent_gsp(struct nvkm_object *object, u64 token, u32 bits) +{ + union nvif_conn_event_args args; + + args.v0.version = 0; + args.v0.types = 0; + if (bits & NVKM_DPYID_PLUG) + args.v0.types |= NVIF_CONN_EVENT_V0_PLUG; + if (bits & NVKM_DPYID_UNPLUG) + args.v0.types |= NVIF_CONN_EVENT_V0_UNPLUG; + if (bits & NVKM_DPYID_IRQ) + args.v0.types |= NVIF_CONN_EVENT_V0_IRQ; + + return object->client->event(token, &args, sizeof(args.v0)); +} + +static int nvkm_uconn_uevent_aux(struct nvkm_object *object, u64 token, u32 bits) { union nvif_conn_event_args args; @@ -78,13 +95,14 @@ static int nvkm_uconn_uevent(struct nvkm_object *object, void *argv, u32 argc, struct nvkm_uevent *uevent) { struct nvkm_conn *conn = nvkm_uconn(object); - struct nvkm_device *device = conn->disp->engine.subdev.device; + struct nvkm_disp *disp = conn->disp; + struct nvkm_device *device = disp->engine.subdev.device; struct nvkm_outp *outp; union nvif_conn_event_args *args = argv; u64 bits = 0; if (!uevent) { - if (conn->info.hpd == DCB_GPIO_UNUSED) + if (!disp->rm.client.gsp && conn->info.hpd == DCB_GPIO_UNUSED) return -ENOSYS; return 0; } @@ -100,6 +118,15 @@ nvkm_uconn_uevent(struct nvkm_object *object, void *argv, u32 argc, struct nvkm_ if (&outp->head == &conn->disp->outps) return -EINVAL; + if (disp->rm.client.gsp) { + if (args->v0.types & NVIF_CONN_EVENT_V0_PLUG ) bits |= NVKM_DPYID_PLUG; + if (args->v0.types & NVIF_CONN_EVENT_V0_UNPLUG) bits |= NVKM_DPYID_UNPLUG; + if (args->v0.types & NVIF_CONN_EVENT_V0_IRQ ) bits |= NVKM_DPYID_IRQ; + + return nvkm_uevent_add(uevent, &disp->rm.event, outp->index, bits, + nvkm_uconn_uevent_gsp); + } + if (outp->dp.aux && !outp->info.location) { if (args->v0.types & NVIF_CONN_EVENT_V0_PLUG ) bits |= NVKM_I2C_PLUG; if (args->v0.types & NVIF_CONN_EVENT_V0_UNPLUG) bits |= NVKM_I2C_UNPLUG; @@ -121,46 +148,6 @@ nvkm_uconn_uevent(struct nvkm_object *object, void *argv, u32 argc, struct nvkm_ nvkm_uconn_uevent_gpio); } -static int -nvkm_uconn_mthd_hpd_status(struct nvkm_conn *conn, void *argv, u32 argc) -{ - struct nvkm_gpio *gpio = conn->disp->engine.subdev.device->gpio; - union nvif_conn_hpd_status_args *args = argv; - - if (argc != sizeof(args->v0) || args->v0.version != 0) - return -ENOSYS; - - args->v0.support = gpio && conn->info.hpd != DCB_GPIO_UNUSED; - args->v0.present = 0; - - if (args->v0.support) { - int ret = nvkm_gpio_get(gpio, 0, DCB_GPIO_UNUSED, conn->info.hpd); - - if (WARN_ON(ret < 0)) { - args->v0.support = false; - return 0; - } - - args->v0.present = ret; - } - - return 0; -} - -static int -nvkm_uconn_mthd(struct nvkm_object *object, u32 mthd, void *argv, u32 argc) -{ - struct nvkm_conn *conn = nvkm_uconn(object); - - switch (mthd) { - case NVIF_CONN_V0_HPD_STATUS: return nvkm_uconn_mthd_hpd_status(conn, argv, argc); - default: - break; - } - - return -EINVAL; -} - static void * nvkm_uconn_dtor(struct nvkm_object *object) { @@ -176,7 +163,6 @@ nvkm_uconn_dtor(struct nvkm_object *object) static const struct nvkm_object_func nvkm_uconn = { .dtor = nvkm_uconn_dtor, - .mthd = nvkm_uconn_mthd, .uevent = nvkm_uconn_uevent, }; @@ -204,6 +190,32 @@ nvkm_uconn_new(const struct nvkm_oclass *oclass, void *argv, u32 argc, struct nv ret = -EBUSY; spin_lock(&disp->client.lock); if (!conn->object.func) { + switch (conn->info.type) { + case DCB_CONNECTOR_VGA : args->v0.type = NVIF_CONN_V0_VGA; break; + case DCB_CONNECTOR_TV_0 : + case DCB_CONNECTOR_TV_1 : + case DCB_CONNECTOR_TV_3 : args->v0.type = NVIF_CONN_V0_TV; break; + case DCB_CONNECTOR_DMS59_0 : + case DCB_CONNECTOR_DMS59_1 : + case DCB_CONNECTOR_DVI_I : args->v0.type = NVIF_CONN_V0_DVI_I; break; + case DCB_CONNECTOR_DVI_D : args->v0.type = NVIF_CONN_V0_DVI_D; break; + case DCB_CONNECTOR_LVDS : args->v0.type = NVIF_CONN_V0_LVDS; break; + case DCB_CONNECTOR_LVDS_SPWG: args->v0.type = NVIF_CONN_V0_LVDS_SPWG; break; + case DCB_CONNECTOR_DMS59_DP0: + case DCB_CONNECTOR_DMS59_DP1: + case DCB_CONNECTOR_DP : + case DCB_CONNECTOR_mDP : + case DCB_CONNECTOR_USB_C : args->v0.type = NVIF_CONN_V0_DP; break; + case DCB_CONNECTOR_eDP : args->v0.type = NVIF_CONN_V0_EDP; break; + case DCB_CONNECTOR_HDMI_0 : + case DCB_CONNECTOR_HDMI_1 : + case DCB_CONNECTOR_HDMI_C : args->v0.type = NVIF_CONN_V0_HDMI; break; + default: + WARN_ON(1); + ret = -EINVAL; + break; + } + nvkm_object_ctor(&nvkm_uconn, oclass, &conn->object); *pobject = &conn->object; ret = 0; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c index fc283a4a15..377d0e0cef 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c @@ -25,6 +25,8 @@ #include "head.h" #include "ior.h" +#include <subdev/i2c.h> + #include <nvif/if0012.h> static int @@ -44,17 +46,121 @@ nvkm_uoutp_mthd_dp_mst_vcpi(struct nvkm_outp *outp, void *argv, u32 argc) } static int -nvkm_uoutp_mthd_dp_retrain(struct nvkm_outp *outp, void *argv, u32 argc) +nvkm_uoutp_mthd_dp_mst_id_put(struct nvkm_outp *outp, void *argv, u32 argc) { - union nvif_outp_dp_retrain_args *args = argv; + union nvif_outp_dp_mst_id_put_args *args = argv; - if (argc != sizeof(args->vn)) + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; + if (!outp->func->dp.mst_id_put) + return -EINVAL; + + return outp->func->dp.mst_id_put(outp, args->v0.id); +} + +static int +nvkm_uoutp_mthd_dp_mst_id_get(struct nvkm_outp *outp, void *argv, u32 argc) +{ + union nvif_outp_dp_mst_id_get_args *args = argv; + + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; + if (!outp->func->dp.mst_id_get) + return -EINVAL; + + return outp->func->dp.mst_id_get(outp, &args->v0.id); +} + +static int +nvkm_uoutp_mthd_dp_sst(struct nvkm_outp *outp, void *argv, u32 argc) +{ + union nvif_outp_dp_sst_args *args = argv; + struct nvkm_disp *disp = outp->disp; + struct nvkm_ior *ior = outp->ior; + + if (argc != sizeof(args->v0) || args->v0.version != 0) return -ENOSYS; - if (!atomic_read(&outp->dp.lt.done)) + if (!ior->func->dp || !nvkm_head_find(disp, args->v0.head)) + return -EINVAL; + if (!ior->func->dp->sst) return 0; - return outp->func->acquire(outp); + return ior->func->dp->sst(ior, args->v0.head, + outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP, + args->v0.watermark, args->v0.hblanksym, args->v0.vblanksym); +} + +static int +nvkm_uoutp_mthd_dp_drive(struct nvkm_outp *outp, void *argv, u32 argc) +{ + union nvif_outp_dp_drive_args *args = argv; + + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; + if (!outp->func->dp.drive) + return -EINVAL; + + return outp->func->dp.drive(outp, args->v0.lanes, args->v0.pe, args->v0.vs); +} + +static int +nvkm_uoutp_mthd_dp_train(struct nvkm_outp *outp, void *argv, u32 argc) +{ + union nvif_outp_dp_train_args *args = argv; + + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; + if (!outp->func->dp.train) + return -EINVAL; + + if (!args->v0.retrain) { + memcpy(outp->dp.dpcd, args->v0.dpcd, sizeof(outp->dp.dpcd)); + outp->dp.lttprs = args->v0.lttprs; + outp->dp.lt.nr = args->v0.link_nr; + outp->dp.lt.bw = args->v0.link_bw / 27000; + outp->dp.lt.mst = args->v0.mst; + outp->dp.lt.post_adj = args->v0.post_lt_adj; + } + + return outp->func->dp.train(outp, args->v0.retrain); +} + +static int +nvkm_uoutp_mthd_dp_rates(struct nvkm_outp *outp, void *argv, u32 argc) +{ + union nvif_outp_dp_rates_args *args = argv; + + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; + if (args->v0.rates > ARRAY_SIZE(outp->dp.rate)) + return -EINVAL; + + for (int i = 0; i < args->v0.rates; i++) { + outp->dp.rate[i].dpcd = args->v0.rate[i].dpcd; + outp->dp.rate[i].rate = args->v0.rate[i].rate; + } + + outp->dp.rates = args->v0.rates; + + if (outp->func->dp.rates) + outp->func->dp.rates(outp); + + return 0; +} + +static int +nvkm_uoutp_mthd_dp_aux_xfer(struct nvkm_outp *outp, void *argv, u32 argc) +{ + union nvif_outp_dp_aux_xfer_args *args = argv; + + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; + if (!outp->func->dp.aux_xfer) + return -EINVAL; + + return outp->func->dp.aux_xfer(outp, args->v0.type, args->v0.addr, + args->v0.data, &args->v0.size); } static int @@ -64,10 +170,10 @@ nvkm_uoutp_mthd_dp_aux_pwr(struct nvkm_outp *outp, void *argv, u32 argc) if (argc != sizeof(args->v0) || args->v0.version != 0) return -ENOSYS; + if (!outp->func->dp.aux_pwr) + return -EINVAL; - outp->dp.enabled = !!args->v0.state; - nvkm_dp_enable(outp, outp->dp.enabled); - return 0; + return outp->func->dp.aux_pwr(outp, !!args->v0.state); } static int @@ -88,12 +194,20 @@ nvkm_uoutp_mthd_hda_eld(struct nvkm_outp *outp, void *argv, u32 argc) if (argc && args->v0.data[0]) { if (outp->info.type == DCB_OUTPUT_DP) ior->func->dp->audio(ior, args->v0.head, true); + else + if (ior->func->hdmi->audio) + ior->func->hdmi->audio(ior, args->v0.head, true); + ior->func->hda->hpd(ior, args->v0.head, true); ior->func->hda->eld(ior, args->v0.head, args->v0.data, argc); } else { + ior->func->hda->hpd(ior, args->v0.head, false); + if (outp->info.type == DCB_OUTPUT_DP) ior->func->dp->audio(ior, args->v0.head, false); - ior->func->hda->hpd(ior, args->v0.head, false); + else + if (ior->func->hdmi->audio) + ior->func->hdmi->audio(ior, args->v0.head, false); } return 0; @@ -126,84 +240,105 @@ nvkm_uoutp_mthd_infoframe(struct nvkm_outp *outp, void *argv, u32 argc) } static int -nvkm_uoutp_mthd_release(struct nvkm_outp *outp, void *argv, u32 argc) +nvkm_uoutp_mthd_hdmi(struct nvkm_outp *outp, void *argv, u32 argc) { - struct nvkm_head *head = outp->asy.head; + union nvif_outp_hdmi_args *args = argv; struct nvkm_ior *ior = outp->ior; - union nvif_outp_release_args *args = argv; - if (argc != sizeof(args->vn)) + if (argc != sizeof(args->v0) || args->v0.version != 0) return -ENOSYS; - if (ior->func->hdmi && head) { - ior->func->hdmi->infoframe_avi(ior, head->id, NULL, 0); - ior->func->hdmi->infoframe_vsi(ior, head->id, NULL, 0); - ior->func->hdmi->ctrl(ior, head->id, false, 0, 0); + if (!(outp->asy.head = nvkm_head_find(outp->disp, args->v0.head))) + return -EINVAL; + + if (!ior->func->hdmi || + args->v0.max_ac_packet > 0x1f || + args->v0.rekey > 0x7f || + (args->v0.scdc && !ior->func->hdmi->scdc)) + return -EINVAL; + + if (!args->v0.enable) { + ior->func->hdmi->infoframe_avi(ior, args->v0.head, NULL, 0); + ior->func->hdmi->infoframe_vsi(ior, args->v0.head, NULL, 0); + ior->func->hdmi->ctrl(ior, args->v0.head, false, 0, 0); + return 0; } - nvkm_outp_release(outp, NVKM_OUTP_USER); + ior->func->hdmi->ctrl(ior, args->v0.head, args->v0.enable, + args->v0.max_ac_packet, args->v0.rekey); + if (ior->func->hdmi->scdc) + ior->func->hdmi->scdc(ior, args->v0.khz, args->v0.scdc, args->v0.scdc_scrambling, + args->v0.scdc_low_rates); + return 0; } static int -nvkm_uoutp_mthd_acquire_dp(struct nvkm_outp *outp, u8 dpcd[DP_RECEIVER_CAP_SIZE], - u8 link_nr, u8 link_bw, bool hda, bool mst) +nvkm_uoutp_mthd_lvds(struct nvkm_outp *outp, void *argv, u32 argc) { - int ret; + union nvif_outp_lvds_args *args = argv; - ret = nvkm_outp_acquire(outp, NVKM_OUTP_USER, hda); - if (ret) - return ret; + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; + if (outp->info.type != DCB_OUTPUT_LVDS) + return -EINVAL; - memcpy(outp->dp.dpcd, dpcd, sizeof(outp->dp.dpcd)); - outp->dp.lt.nr = link_nr; - outp->dp.lt.bw = link_bw; - outp->dp.lt.mst = mst; + outp->lvds.dual = !!args->v0.dual; + outp->lvds.bpc8 = !!args->v0.bpc8; return 0; } static int -nvkm_uoutp_mthd_acquire_tmds(struct nvkm_outp *outp, u8 head, u8 hdmi, u8 hdmi_max_ac_packet, - u8 hdmi_rekey, u8 hdmi_scdc, u8 hdmi_hda) +nvkm_uoutp_mthd_bl_set(struct nvkm_outp *outp, void *argv, u32 argc) { - struct nvkm_ior *ior; + union nvif_outp_bl_get_args *args = argv; int ret; - if (!(outp->asy.head = nvkm_head_find(outp->disp, head))) - return -EINVAL; + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; - ret = nvkm_outp_acquire(outp, NVKM_OUTP_USER, hdmi && hdmi_hda); - if (ret) - return ret; + if (outp->func->bl.set) + ret = outp->func->bl.set(outp, args->v0.level); + else + ret = -EINVAL; - ior = outp->ior; + return ret; +} - if (hdmi) { - if (!ior->func->hdmi || - hdmi_max_ac_packet > 0x1f || hdmi_rekey > 0x7f || - (hdmi_scdc && !ior->func->hdmi->scdc)) { - nvkm_outp_release(outp, NVKM_OUTP_USER); - return -EINVAL; - } +static int +nvkm_uoutp_mthd_bl_get(struct nvkm_outp *outp, void *argv, u32 argc) +{ + union nvif_outp_bl_get_args *args = argv; + int ret; + + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; - ior->func->hdmi->ctrl(ior, head, hdmi, hdmi_max_ac_packet, hdmi_rekey); - if (ior->func->hdmi->scdc) - ior->func->hdmi->scdc(ior, hdmi_scdc); + if (outp->func->bl.get) { + ret = outp->func->bl.get(outp); + if (ret >= 0) { + args->v0.level = ret; + ret = 0; + } + } else { + ret = -EINVAL; } - return 0; + return ret; } static int -nvkm_uoutp_mthd_acquire_lvds(struct nvkm_outp *outp, bool dual, bool bpc8) +nvkm_uoutp_mthd_release(struct nvkm_outp *outp, void *argv, u32 argc) { - if (outp->info.type != DCB_OUTPUT_LVDS) - return -EINVAL; + union nvif_outp_release_args *args = argv; - outp->lvds.dual = dual; - outp->lvds.bpc8 = bpc8; + if (argc != sizeof(args->vn)) + return -ENOSYS; + if (!outp->ior) + return -EINVAL; - return nvkm_outp_acquire(outp, NVKM_OUTP_USER, false); + outp->func->release(outp); + return 0; } static int @@ -214,30 +349,16 @@ nvkm_uoutp_mthd_acquire(struct nvkm_outp *outp, void *argv, u32 argc) if (argc != sizeof(args->v0) || args->v0.version != 0) return -ENOSYS; - if (outp->ior) + if (outp->ior && args->v0.type <= NVIF_OUTP_ACQUIRE_V0_PIOR) return -EBUSY; - switch (args->v0.proto) { - case NVIF_OUTP_ACQUIRE_V0_RGB_CRT: - ret = nvkm_outp_acquire(outp, NVKM_OUTP_USER, false); + switch (args->v0.type) { + case NVIF_OUTP_ACQUIRE_V0_DAC: + case NVIF_OUTP_ACQUIRE_V0_PIOR: + ret = outp->func->acquire(outp, false); break; - case NVIF_OUTP_ACQUIRE_V0_TMDS: - ret = nvkm_uoutp_mthd_acquire_tmds(outp, args->v0.tmds.head, - args->v0.tmds.hdmi, - args->v0.tmds.hdmi_max_ac_packet, - args->v0.tmds.hdmi_rekey, - args->v0.tmds.hdmi_scdc, - args->v0.tmds.hdmi_hda); - break; - case NVIF_OUTP_ACQUIRE_V0_LVDS: - ret = nvkm_uoutp_mthd_acquire_lvds(outp, args->v0.lvds.dual, args->v0.lvds.bpc8); - break; - case NVIF_OUTP_ACQUIRE_V0_DP: - ret = nvkm_uoutp_mthd_acquire_dp(outp, args->v0.dp.dpcd, - args->v0.dp.link_nr, - args->v0.dp.link_bw, - args->v0.dp.hda != 0, - args->v0.dp.mst != 0); + case NVIF_OUTP_ACQUIRE_V0_SOR: + ret = outp->func->acquire(outp, args->v0.sor.hda); break; default: ret = -EINVAL; @@ -253,6 +374,69 @@ nvkm_uoutp_mthd_acquire(struct nvkm_outp *outp, void *argv, u32 argc) } static int +nvkm_uoutp_mthd_inherit(struct nvkm_outp *outp, void *argv, u32 argc) +{ + union nvif_outp_inherit_args *args = argv; + struct nvkm_ior *ior; + int ret = 0; + + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; + + /* Ensure an ior is hooked up to this outp already */ + ior = outp->func->inherit(outp); + if (!ior || !ior->arm.head) + return -ENODEV; + + /* With iors, there will be a separate output path for each type of connector - and all of + * them will appear to be hooked up. Figure out which one is actually the one we're using + * based on the protocol we were given over nvif + */ + switch (args->v0.proto) { + case NVIF_OUTP_INHERIT_V0_TMDS: + if (ior->arm.proto != TMDS) + return -ENODEV; + break; + case NVIF_OUTP_INHERIT_V0_DP: + if (ior->arm.proto != DP) + return -ENODEV; + break; + case NVIF_OUTP_INHERIT_V0_LVDS: + if (ior->arm.proto != LVDS) + return -ENODEV; + break; + case NVIF_OUTP_INHERIT_V0_TV: + if (ior->arm.proto != TV) + return -ENODEV; + break; + case NVIF_OUTP_INHERIT_V0_RGB_CRT: + if (ior->arm.proto != CRT) + return -ENODEV; + break; + default: + ret = -EINVAL; + break; + } + + /* Make sure that userspace hasn't already acquired this */ + if (outp->acquired) { + OUTP_ERR(outp, "cannot inherit an already acquired (%02x) outp", outp->acquired); + return -EBUSY; + } + + /* Mark the outp acquired by userspace now that we've confirmed it's already active */ + OUTP_TRACE(outp, "inherit %02x |= %02x %p", outp->acquired, NVKM_OUTP_USER, ior); + nvkm_outp_acquire_ior(outp, NVKM_OUTP_USER, ior); + + args->v0.or = ior->id; + args->v0.link = ior->arm.link; + args->v0.head = ffs(ior->arm.head) - 1; + args->v0.proto = ior->arm.proto_evo; + + return ret; +} + +static int nvkm_uoutp_mthd_load_detect(struct nvkm_outp *outp, void *argv, u32 argc) { union nvif_outp_load_detect_args *args = argv; @@ -261,7 +445,7 @@ nvkm_uoutp_mthd_load_detect(struct nvkm_outp *outp, void *argv, u32 argc) if (argc != sizeof(args->v0) || args->v0.version != 0) return -ENOSYS; - ret = nvkm_outp_acquire(outp, NVKM_OUTP_PRIV, false); + ret = nvkm_outp_acquire_or(outp, NVKM_OUTP_PRIV, false); if (ret == 0) { if (outp->ior->func->sense) { ret = outp->ior->func->sense(outp->ior, args->v0.data); @@ -269,21 +453,64 @@ nvkm_uoutp_mthd_load_detect(struct nvkm_outp *outp, void *argv, u32 argc) } else { ret = -EINVAL; } - nvkm_outp_release(outp, NVKM_OUTP_PRIV); + nvkm_outp_release_or(outp, NVKM_OUTP_PRIV); } return ret; } static int +nvkm_uoutp_mthd_edid_get(struct nvkm_outp *outp, void *argv, u32 argc) +{ + union nvif_outp_edid_get_args *args = argv; + + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; + if (!outp->func->edid_get) + return -EINVAL; + + args->v0.size = ARRAY_SIZE(args->v0.data); + return outp->func->edid_get(outp, args->v0.data, &args->v0.size); +} + +static int +nvkm_uoutp_mthd_detect(struct nvkm_outp *outp, void *argv, u32 argc) +{ + union nvif_outp_detect_args *args = argv; + int ret; + + if (argc != sizeof(args->v0) || args->v0.version != 0) + return -ENOSYS; + if (!outp->func->detect) + return -EINVAL; + + ret = outp->func->detect(outp); + switch (ret) { + case 0: args->v0.status = NVIF_OUTP_DETECT_V0_NOT_PRESENT; break; + case 1: args->v0.status = NVIF_OUTP_DETECT_V0_PRESENT; break; + default: + args->v0.status = NVIF_OUTP_DETECT_V0_UNKNOWN; + break; + } + + return 0; +} + +static int nvkm_uoutp_mthd_acquired(struct nvkm_outp *outp, u32 mthd, void *argv, u32 argc) { switch (mthd) { - case NVIF_OUTP_V0_RELEASE : return nvkm_uoutp_mthd_release (outp, argv, argc); - case NVIF_OUTP_V0_INFOFRAME : return nvkm_uoutp_mthd_infoframe (outp, argv, argc); - case NVIF_OUTP_V0_HDA_ELD : return nvkm_uoutp_mthd_hda_eld (outp, argv, argc); - case NVIF_OUTP_V0_DP_RETRAIN : return nvkm_uoutp_mthd_dp_retrain (outp, argv, argc); - case NVIF_OUTP_V0_DP_MST_VCPI: return nvkm_uoutp_mthd_dp_mst_vcpi(outp, argv, argc); + case NVIF_OUTP_V0_RELEASE : return nvkm_uoutp_mthd_release (outp, argv, argc); + case NVIF_OUTP_V0_LVDS : return nvkm_uoutp_mthd_lvds (outp, argv, argc); + case NVIF_OUTP_V0_HDMI : return nvkm_uoutp_mthd_hdmi (outp, argv, argc); + case NVIF_OUTP_V0_INFOFRAME : return nvkm_uoutp_mthd_infoframe (outp, argv, argc); + case NVIF_OUTP_V0_HDA_ELD : return nvkm_uoutp_mthd_hda_eld (outp, argv, argc); + case NVIF_OUTP_V0_DP_TRAIN : return nvkm_uoutp_mthd_dp_train (outp, argv, argc); + case NVIF_OUTP_V0_DP_DRIVE : return nvkm_uoutp_mthd_dp_drive (outp, argv, argc); + case NVIF_OUTP_V0_DP_SST : return nvkm_uoutp_mthd_dp_sst (outp, argv, argc); + case NVIF_OUTP_V0_DP_MST_ID_GET: return nvkm_uoutp_mthd_dp_mst_id_get(outp, argv, argc); + case NVIF_OUTP_V0_DP_MST_ID_PUT: return nvkm_uoutp_mthd_dp_mst_id_put(outp, argv, argc); + case NVIF_OUTP_V0_DP_MST_VCPI : return nvkm_uoutp_mthd_dp_mst_vcpi (outp, argv, argc); default: break; } @@ -292,17 +519,25 @@ nvkm_uoutp_mthd_acquired(struct nvkm_outp *outp, u32 mthd, void *argv, u32 argc) } static int -nvkm_uoutp_mthd_noacquire(struct nvkm_outp *outp, u32 mthd, void *argv, u32 argc) +nvkm_uoutp_mthd_noacquire(struct nvkm_outp *outp, u32 mthd, void *argv, u32 argc, bool *invalid) { switch (mthd) { - case NVIF_OUTP_V0_LOAD_DETECT: return nvkm_uoutp_mthd_load_detect(outp, argv, argc); + case NVIF_OUTP_V0_DETECT : return nvkm_uoutp_mthd_detect (outp, argv, argc); + case NVIF_OUTP_V0_EDID_GET : return nvkm_uoutp_mthd_edid_get (outp, argv, argc); + case NVIF_OUTP_V0_INHERIT : return nvkm_uoutp_mthd_inherit (outp, argv, argc); case NVIF_OUTP_V0_ACQUIRE : return nvkm_uoutp_mthd_acquire (outp, argv, argc); + case NVIF_OUTP_V0_LOAD_DETECT: return nvkm_uoutp_mthd_load_detect(outp, argv, argc); + case NVIF_OUTP_V0_BL_GET : return nvkm_uoutp_mthd_bl_get (outp, argv, argc); + case NVIF_OUTP_V0_BL_SET : return nvkm_uoutp_mthd_bl_set (outp, argv, argc); case NVIF_OUTP_V0_DP_AUX_PWR : return nvkm_uoutp_mthd_dp_aux_pwr (outp, argv, argc); + case NVIF_OUTP_V0_DP_AUX_XFER: return nvkm_uoutp_mthd_dp_aux_xfer(outp, argv, argc); + case NVIF_OUTP_V0_DP_RATES : return nvkm_uoutp_mthd_dp_rates (outp, argv, argc); default: break; } - return 1; + *invalid = true; + return 0; } static int @@ -310,12 +545,13 @@ nvkm_uoutp_mthd(struct nvkm_object *object, u32 mthd, void *argv, u32 argc) { struct nvkm_outp *outp = nvkm_uoutp(object); struct nvkm_disp *disp = outp->disp; + bool invalid = false; int ret; mutex_lock(&disp->super.mutex); - ret = nvkm_uoutp_mthd_noacquire(outp, mthd, argv, argc); - if (ret <= 0) + ret = nvkm_uoutp_mthd_noacquire(outp, mthd, argv, argc, &invalid); + if (!invalid) goto done; if (outp->ior) @@ -370,10 +606,60 @@ nvkm_uoutp_new(const struct nvkm_oclass *oclass, void *argv, u32 argc, struct nv ret = -EBUSY; spin_lock(&disp->client.lock); if (!outp->object.func) { + switch (outp->info.type) { + case DCB_OUTPUT_ANALOG: + args->v0.type = NVIF_OUTP_V0_TYPE_DAC; + args->v0.proto = NVIF_OUTP_V0_PROTO_RGB_CRT; + args->v0.rgb_crt.freq_max = outp->info.crtconf.maxfreq; + break; + case DCB_OUTPUT_TMDS: + if (!outp->info.location) { + args->v0.type = NVIF_OUTP_V0_TYPE_SOR; + args->v0.tmds.dual = (outp->info.tmdsconf.sor.link == 3); + } else { + args->v0.type = NVIF_OUTP_V0_TYPE_PIOR; + args->v0.tmds.dual = 0; + } + args->v0.proto = NVIF_OUTP_V0_PROTO_TMDS; + break; + case DCB_OUTPUT_LVDS: + args->v0.type = NVIF_OUTP_V0_TYPE_SOR; + args->v0.proto = NVIF_OUTP_V0_PROTO_LVDS; + args->v0.lvds.acpi_edid = outp->info.lvdsconf.use_acpi_for_edid; + break; + case DCB_OUTPUT_DP: + if (!outp->info.location) { + args->v0.type = NVIF_OUTP_V0_TYPE_SOR; + args->v0.dp.aux = outp->info.i2c_index; + } else { + args->v0.type = NVIF_OUTP_V0_TYPE_PIOR; + args->v0.dp.aux = NVKM_I2C_AUX_EXT(outp->info.extdev); + } + args->v0.proto = NVIF_OUTP_V0_PROTO_DP; + args->v0.dp.mst = outp->dp.mst; + args->v0.dp.increased_wm = outp->dp.increased_wm; + args->v0.dp.link_nr = outp->info.dpconf.link_nr; + args->v0.dp.link_bw = outp->info.dpconf.link_bw * 27000; + break; + default: + WARN_ON(1); + ret = -EINVAL; + goto done; + } + + if (outp->info.location) + args->v0.ddc = NVKM_I2C_BUS_EXT(outp->info.extdev); + else + args->v0.ddc = outp->info.i2c_index; + args->v0.heads = outp->info.heads; + args->v0.conn = outp->info.connector; + nvkm_object_ctor(&nvkm_uoutp, oclass, &outp->object); *pobject = &outp->object; ret = 0; } + +done: spin_unlock(&disp->client.lock); return ret; } |