Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,34 @@
# SPDX-License-Identifier: MIT
nvkm-y += nvkm/engine/disp/base.o
nvkm-y += nvkm/engine/disp/chan.o
nvkm-y += nvkm/engine/disp/conn.o
nvkm-y += nvkm/engine/disp/dp.o
nvkm-y += nvkm/engine/disp/hdmi.o
nvkm-y += nvkm/engine/disp/head.o
nvkm-y += nvkm/engine/disp/ior.o
nvkm-y += nvkm/engine/disp/outp.o
nvkm-y += nvkm/engine/disp/vga.o
nvkm-y += nvkm/engine/disp/nv04.o
nvkm-y += nvkm/engine/disp/nv50.o
nvkm-y += nvkm/engine/disp/g84.o
nvkm-y += nvkm/engine/disp/g94.o
nvkm-y += nvkm/engine/disp/gt200.o
nvkm-y += nvkm/engine/disp/mcp77.o
nvkm-y += nvkm/engine/disp/gt215.o
nvkm-y += nvkm/engine/disp/mcp89.o
nvkm-y += nvkm/engine/disp/gf119.o
nvkm-y += nvkm/engine/disp/gk104.o
nvkm-y += nvkm/engine/disp/gk110.o
nvkm-y += nvkm/engine/disp/gm107.o
nvkm-y += nvkm/engine/disp/gm200.o
nvkm-y += nvkm/engine/disp/gp100.o
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/udisp.o
nvkm-y += nvkm/engine/disp/uconn.o
nvkm-y += nvkm/engine/disp/uoutp.o
nvkm-y += nvkm/engine/disp/uhead.o
@@ -0,0 +1,262 @@
/*
* Copyright 2013 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.
*
* Authors: Ben Skeggs
*/
#include "priv.h"
#include "conn.h"
#include "head.h"
#include "ior.h"
#include "outp.h"
#include <core/client.h>
#include <core/ramht.h>
#include <nvif/class.h>
#include <nvif/cl0046.h>
#include <nvif/event.h>
#include <nvif/unpack.h>
static void
nvkm_disp_vblank_fini(struct nvkm_event *event, int type, int id)
{
struct nvkm_disp *disp = container_of(event, typeof(*disp), vblank);
struct nvkm_head *head = nvkm_head_find(disp, id);
if (head)
head->func->vblank_put(head);
}
static void
nvkm_disp_vblank_init(struct nvkm_event *event, int type, int id)
{
struct nvkm_disp *disp = container_of(event, typeof(*disp), vblank);
struct nvkm_head *head = nvkm_head_find(disp, id);
if (head)
head->func->vblank_get(head);
}
static const struct nvkm_event_func
nvkm_disp_vblank_func = {
.init = nvkm_disp_vblank_init,
.fini = nvkm_disp_vblank_fini,
};
void
nvkm_disp_vblank(struct nvkm_disp *disp, int head)
{
nvkm_event_ntfy(&disp->vblank, head, NVKM_DISP_HEAD_EVENT_VBLANK);
}
static int
nvkm_disp_class_new(struct nvkm_device *device,
const struct nvkm_oclass *oclass, void *data, u32 size,
struct nvkm_object **pobject)
{
return nvkm_udisp_new(oclass, data, size, pobject);
}
static const struct nvkm_device_oclass
nvkm_disp_sclass = {
.ctor = nvkm_disp_class_new,
};
static int
nvkm_disp_class_get(struct nvkm_oclass *oclass, int index,
const struct nvkm_device_oclass **class)
{
struct nvkm_disp *disp = nvkm_disp(oclass->engine);
if (index == 0) {
oclass->base = disp->func->root;
*class = &nvkm_disp_sclass;
return 0;
}
return 1;
}
static void
nvkm_disp_intr(struct nvkm_engine *engine)
{
struct nvkm_disp *disp = nvkm_disp(engine);
disp->func->intr(disp);
}
static int
nvkm_disp_fini(struct nvkm_engine *engine, enum nvkm_suspend_state suspend)
{
struct nvkm_disp *disp = nvkm_disp(engine);
struct nvkm_outp *outp;
if (disp->func->fini)
disp->func->fini(disp, suspend != NVKM_POWEROFF);
list_for_each_entry(outp, &disp->outps, head) {
if (outp->func->fini)
outp->func->fini(outp);
}
return 0;
}
static int
nvkm_disp_init(struct nvkm_engine *engine)
{
struct nvkm_disp *disp = nvkm_disp(engine);
struct nvkm_outp *outp;
struct nvkm_ior *ior;
list_for_each_entry(outp, &disp->outps, head) {
if (outp->func->init)
outp->func->init(outp);
}
if (disp->func->init) {
int ret = disp->func->init(disp);
if (ret)
return ret;
}
/* Set 'normal' (ie. when it's attached to a head) state for
* each output resource to 'fully enabled'.
*/
list_for_each_entry(ior, &disp->iors, head) {
if (ior->func->power)
ior->func->power(ior, true, true, true, true, true);
}
return 0;
}
static int
nvkm_disp_oneinit(struct nvkm_engine *engine)
{
struct nvkm_disp *disp = nvkm_disp(engine);
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_head *head;
int ret, i;
if (disp->func->oneinit) {
ret = disp->func->oneinit(disp);
if (ret)
return ret;
}
i = 0;
list_for_each_entry(head, &disp->heads, head)
i = max(i, head->id + 1);
return nvkm_event_init(&nvkm_disp_vblank_func, subdev, 1, i, &disp->vblank);
}
static void *
nvkm_disp_dtor(struct nvkm_engine *engine)
{
struct nvkm_disp *disp = nvkm_disp(engine);
struct nvkm_conn *conn;
struct nvkm_outp *outp;
struct nvkm_ior *ior;
struct nvkm_head *head;
void *data = disp;
nvkm_ramht_del(&disp->ramht);
nvkm_gpuobj_del(&disp->inst);
nvkm_event_fini(&disp->uevent);
if (disp->super.wq) {
destroy_workqueue(disp->super.wq);
mutex_destroy(&disp->super.mutex);
}
nvkm_event_fini(&disp->vblank);
while (!list_empty(&disp->conns)) {
conn = list_first_entry(&disp->conns, typeof(*conn), head);
list_del(&conn->head);
nvkm_conn_del(&conn);
}
while (!list_empty(&disp->outps)) {
outp = list_first_entry(&disp->outps, typeof(*outp), head);
list_del(&outp->head);
nvkm_outp_del(&outp);
}
while (!list_empty(&disp->iors)) {
ior = list_first_entry(&disp->iors, typeof(*ior), head);
nvkm_ior_del(&ior);
}
while (!list_empty(&disp->heads)) {
head = list_first_entry(&disp->heads, typeof(*head), head);
nvkm_head_del(&head);
}
if (disp->func && disp->func->dtor)
disp->func->dtor(disp);
return data;
}
static const struct nvkm_engine_func
nvkm_disp = {
.dtor = nvkm_disp_dtor,
.oneinit = nvkm_disp_oneinit,
.init = nvkm_disp_init,
.fini = nvkm_disp_fini,
.intr = nvkm_disp_intr,
.base.sclass = nvkm_disp_class_get,
};
int
nvkm_disp_new_(const struct nvkm_disp_func *func, struct nvkm_device *device,
enum nvkm_subdev_type type, int inst, struct nvkm_disp **pdisp)
{
struct nvkm_disp *disp;
int ret;
if (!(disp = *pdisp = kzalloc_obj(**pdisp)))
return -ENOMEM;
disp->func = func;
INIT_LIST_HEAD(&disp->heads);
INIT_LIST_HEAD(&disp->iors);
INIT_LIST_HEAD(&disp->outps);
INIT_LIST_HEAD(&disp->conns);
spin_lock_init(&disp->client.lock);
ret = nvkm_engine_ctor(&nvkm_disp, device, type, inst, true, &disp->engine);
if (ret) {
disp->func = NULL;
return ret;
}
if (func->super) {
disp->super.wq = create_singlethread_workqueue("nvkm-disp");
if (!disp->super.wq)
return -ENOMEM;
INIT_WORK(&disp->super.work, func->super);
mutex_init(&disp->super.mutex);
}
return nvkm_event_init(func->uevent, &disp->engine.subdev, 1, ARRAY_SIZE(disp->chan),
&disp->uevent);
}
@@ -0,0 +1,251 @@
/*
* Copyright 2021 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 "chan.h"
#include <core/oproxy.h>
#include <core/ramht.h>
#include <nvif/if0014.h>
static int
nvkm_disp_chan_ntfy(struct nvkm_object *object, u32 type, struct nvkm_event **pevent)
{
struct nvkm_disp_chan *chan = nvkm_disp_chan(object);
struct nvkm_disp *disp = chan->disp;
switch (type) {
case 0:
*pevent = &disp->uevent;
return 0;
default:
break;
}
return -EINVAL;
}
static int
nvkm_disp_chan_map(struct nvkm_object *object, void *argv, u32 argc,
enum nvkm_object_map *type, u64 *addr, u64 *size)
{
struct nvkm_disp_chan *chan = nvkm_disp_chan(object);
struct nvkm_device *device = chan->disp->engine.subdev.device;
const u64 base = device->func->resource_addr(device, NVKM_BAR0_PRI);
*type = NVKM_OBJECT_MAP_IO;
*addr = base + chan->func->user(chan, size);
return 0;
}
struct nvkm_disp_chan_object {
struct nvkm_oproxy oproxy;
struct nvkm_disp *disp;
int hash;
};
static void
nvkm_disp_chan_child_del_(struct nvkm_oproxy *base)
{
struct nvkm_disp_chan_object *object = container_of(base, typeof(*object), oproxy);
nvkm_ramht_remove(object->disp->ramht, object->hash);
}
static const struct nvkm_oproxy_func
nvkm_disp_chan_child_func_ = {
.dtor[0] = nvkm_disp_chan_child_del_,
};
static int
nvkm_disp_chan_child_new(const struct nvkm_oclass *oclass, void *argv, u32 argc,
struct nvkm_object **pobject)
{
struct nvkm_disp_chan *chan = nvkm_disp_chan(oclass->parent);
struct nvkm_disp *disp = chan->disp;
struct nvkm_device *device = disp->engine.subdev.device;
const struct nvkm_device_oclass *sclass = oclass->priv;
struct nvkm_disp_chan_object *object;
int ret;
if (!(object = kzalloc_obj(*object)))
return -ENOMEM;
nvkm_oproxy_ctor(&nvkm_disp_chan_child_func_, oclass, &object->oproxy);
object->disp = disp;
*pobject = &object->oproxy.base;
ret = sclass->ctor(device, oclass, argv, argc, &object->oproxy.object);
if (ret)
return ret;
object->hash = chan->func->bind(chan, object->oproxy.object, oclass->handle);
if (object->hash < 0)
return object->hash;
return 0;
}
static int
nvkm_disp_chan_child_get(struct nvkm_object *object, int index, struct nvkm_oclass *sclass)
{
struct nvkm_disp_chan *chan = nvkm_disp_chan(object);
struct nvkm_device *device = chan->disp->engine.subdev.device;
const struct nvkm_device_oclass *oclass = NULL;
if (chan->func->bind)
sclass->engine = nvkm_device_engine(device, NVKM_ENGINE_DMAOBJ, 0);
else
sclass->engine = NULL;
if (sclass->engine && sclass->engine->func->base.sclass) {
sclass->engine->func->base.sclass(sclass, index, &oclass);
if (oclass) {
sclass->ctor = nvkm_disp_chan_child_new;
sclass->priv = oclass;
return 0;
}
}
return -EINVAL;
}
static int
nvkm_disp_chan_fini(struct nvkm_object *object, enum nvkm_suspend_state suspend)
{
struct nvkm_disp_chan *chan = nvkm_disp_chan(object);
chan->func->fini(chan);
chan->func->intr(chan, false);
return 0;
}
static int
nvkm_disp_chan_init(struct nvkm_object *object)
{
struct nvkm_disp_chan *chan = nvkm_disp_chan(object);
chan->func->intr(chan, true);
return chan->func->init(chan);
}
static void *
nvkm_disp_chan_dtor(struct nvkm_object *object)
{
struct nvkm_disp_chan *chan = nvkm_disp_chan(object);
struct nvkm_disp *disp = chan->disp;
spin_lock(&disp->client.lock);
if (disp->chan[chan->chid.user] == chan)
disp->chan[chan->chid.user] = NULL;
spin_unlock(&disp->client.lock);
nvkm_memory_unref(&chan->memory);
return chan;
}
static const struct nvkm_object_func
nvkm_disp_chan = {
.dtor = nvkm_disp_chan_dtor,
.init = nvkm_disp_chan_init,
.fini = nvkm_disp_chan_fini,
.ntfy = nvkm_disp_chan_ntfy,
.map = nvkm_disp_chan_map,
.sclass = nvkm_disp_chan_child_get,
};
static int
nvkm_disp_chan_new_(struct nvkm_disp *disp, int nr, const struct nvkm_oclass *oclass,
void *argv, u32 argc, struct nvkm_object **pobject)
{
const struct nvkm_disp_chan_user *user = NULL;
struct nvkm_disp_chan *chan;
union nvif_disp_chan_args *args = argv;
int ret, i;
for (i = 0; disp->func->user[i].ctor; i++) {
if (disp->func->user[i].base.oclass == oclass->base.oclass) {
user = disp->func->user[i].chan;
break;
}
}
if (WARN_ON(!user))
return -EINVAL;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
if (args->v0.id >= nr || !args->v0.pushbuf != !user->func->push)
return -EINVAL;
if (!(chan = kzalloc_obj(*chan)))
return -ENOMEM;
*pobject = &chan->object;
nvkm_object_ctor(&nvkm_disp_chan, oclass, &chan->object);
chan->func = user->func;
chan->mthd = user->mthd;
chan->disp = disp;
chan->chid.ctrl = user->ctrl + args->v0.id;
chan->chid.user = user->user + args->v0.id;
chan->head = args->v0.id;
if (chan->func->push) {
ret = chan->func->push(chan, args->v0.pushbuf);
if (ret)
return ret;
}
spin_lock(&disp->client.lock);
if (disp->chan[chan->chid.user]) {
spin_unlock(&disp->client.lock);
return -EBUSY;
}
disp->chan[chan->chid.user] = chan;
spin_unlock(&disp->client.lock);
return 0;
}
int
nvkm_disp_wndw_new(const struct nvkm_oclass *oclass, void *argv, u32 argc,
struct nvkm_object **pobject)
{
struct nvkm_disp *disp = nvkm_udisp(oclass->parent);
return nvkm_disp_chan_new_(disp, disp->wndw.nr, oclass, argv, argc, pobject);
}
int
nvkm_disp_chan_new(const struct nvkm_oclass *oclass, void *argv, u32 argc,
struct nvkm_object **pobject)
{
struct nvkm_disp *disp = nvkm_udisp(oclass->parent);
return nvkm_disp_chan_new_(disp, disp->head.nr, oclass, argv, argc, pobject);
}
int
nvkm_disp_core_new(const struct nvkm_oclass *oclass, void *argv, u32 argc,
struct nvkm_object **pobject)
{
struct nvkm_disp *disp = nvkm_udisp(oclass->parent);
return nvkm_disp_chan_new_(disp, 1, oclass, argv, argc, pobject);
}
@@ -0,0 +1,139 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_DISP_CHAN_H__
#define __NVKM_DISP_CHAN_H__
#define nvkm_disp_chan(p) container_of((p), struct nvkm_disp_chan, object)
#include <core/object.h>
#include "priv.h"
struct nvkm_disp_chan {
const struct nvkm_disp_chan_func *func;
const struct nvkm_disp_chan_mthd *mthd;
struct nvkm_disp *disp;
struct {
int ctrl;
int user;
} chid;
int head;
struct nvkm_object object;
struct nvkm_memory *memory;
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 **);
int nvkm_disp_chan_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **);
int nvkm_disp_wndw_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **);
struct nvkm_disp_chan_func {
int (*push)(struct nvkm_disp_chan *, u64 object);
int (*init)(struct nvkm_disp_chan *);
void (*fini)(struct nvkm_disp_chan *);
void (*intr)(struct nvkm_disp_chan *, bool en);
u64 (*user)(struct nvkm_disp_chan *, u64 *size);
int (*bind)(struct nvkm_disp_chan *, struct nvkm_object *, u32 handle);
};
void nv50_disp_chan_intr(struct nvkm_disp_chan *, bool);
u64 nv50_disp_chan_user(struct nvkm_disp_chan *, u64 *);
extern const struct nvkm_disp_chan_func nv50_disp_pioc_func;
extern const struct nvkm_disp_chan_func nv50_disp_dmac_func;
int nv50_disp_dmac_push(struct nvkm_disp_chan *, u64);
int nv50_disp_dmac_bind(struct nvkm_disp_chan *, struct nvkm_object *, u32);
extern const struct nvkm_disp_chan_func nv50_disp_core_func;
void gf119_disp_chan_intr(struct nvkm_disp_chan *, bool);
extern const struct nvkm_disp_chan_func gf119_disp_pioc_func;
extern const struct nvkm_disp_chan_func gf119_disp_dmac_func;
void gf119_disp_dmac_fini(struct nvkm_disp_chan *);
int gf119_disp_dmac_bind(struct nvkm_disp_chan *, struct nvkm_object *, u32);
extern const struct nvkm_disp_chan_func gf119_disp_core_func;
void gf119_disp_core_fini(struct nvkm_disp_chan *);
extern const struct nvkm_disp_chan_func gp102_disp_dmac_func;
u64 gv100_disp_chan_user(struct nvkm_disp_chan *, u64 *);
int gv100_disp_dmac_init(struct nvkm_disp_chan *);
void gv100_disp_dmac_fini(struct nvkm_disp_chan *);
int gv100_disp_dmac_bind(struct nvkm_disp_chan *, struct nvkm_object *, u32);
struct nvkm_disp_chan_user {
const struct nvkm_disp_chan_func *func;
int ctrl;
int user;
const struct nvkm_disp_chan_mthd *mthd;
};
extern const struct nvkm_disp_chan_user nv50_disp_oimm;
extern const struct nvkm_disp_chan_user nv50_disp_curs;
extern const struct nvkm_disp_chan_user g84_disp_core;
extern const struct nvkm_disp_chan_user g84_disp_base;
extern const struct nvkm_disp_chan_user g84_disp_ovly;
extern const struct nvkm_disp_chan_user g94_disp_core;
extern const struct nvkm_disp_chan_user gt200_disp_ovly;
extern const struct nvkm_disp_chan_user gf119_disp_base;
extern const struct nvkm_disp_chan_user gf119_disp_oimm;
extern const struct nvkm_disp_chan_user gf119_disp_curs;
extern const struct nvkm_disp_chan_user gk104_disp_core;
extern const struct nvkm_disp_chan_user gk104_disp_ovly;
extern const struct nvkm_disp_chan_user gv100_disp_core;
extern const struct nvkm_disp_chan_user gv100_disp_curs;
extern const struct nvkm_disp_chan_user gv100_disp_wndw;
extern const struct nvkm_disp_chan_user gv100_disp_wimm;
struct nvkm_disp_mthd_list {
u32 mthd;
u32 addr;
struct {
u32 mthd;
u32 addr;
const char *name;
} data[];
};
struct nvkm_disp_chan_mthd {
const char *name;
u32 addr;
s32 prev;
struct {
const char *name;
int nr;
const struct nvkm_disp_mthd_list *mthd;
} data[];
};
void nv50_disp_chan_mthd(struct nvkm_disp_chan *, int debug);
extern const struct nvkm_disp_mthd_list nv50_disp_core_mthd_base;
extern const struct nvkm_disp_mthd_list nv50_disp_core_mthd_sor;
extern const struct nvkm_disp_mthd_list nv50_disp_core_mthd_pior;
extern const struct nvkm_disp_mthd_list nv50_disp_base_mthd_image;
extern const struct nvkm_disp_chan_mthd g84_disp_core_mthd;
extern const struct nvkm_disp_mthd_list g84_disp_core_mthd_dac;
extern const struct nvkm_disp_mthd_list g84_disp_core_mthd_head;
extern const struct nvkm_disp_chan_mthd g94_disp_core_mthd;
extern const struct nvkm_disp_mthd_list gf119_disp_core_mthd_base;
extern const struct nvkm_disp_mthd_list gf119_disp_core_mthd_dac;
extern const struct nvkm_disp_mthd_list gf119_disp_core_mthd_sor;
extern const struct nvkm_disp_mthd_list gf119_disp_core_mthd_pior;
extern const struct nvkm_disp_chan_mthd gf119_disp_base_mthd;
extern const struct nvkm_disp_chan_mthd gk104_disp_core_mthd;
extern const struct nvkm_disp_chan_mthd gk104_disp_ovly_mthd;
#endif
@@ -0,0 +1,85 @@
/*
* Copyright 2014 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.
*
* Authors: Ben Skeggs
*/
#include "conn.h"
#include "outp.h"
#include "priv.h"
#include <subdev/gpio.h>
#include <nvif/event.h>
void
nvkm_conn_del(struct nvkm_conn **pconn)
{
struct nvkm_conn *conn = *pconn;
if (conn) {
kfree(*pconn);
*pconn = NULL;
}
}
static void
nvkm_conn_ctor(struct nvkm_disp *disp, int index, struct nvbios_connE *info,
struct nvkm_conn *conn)
{
static const u8 hpd[] = { 0x07, 0x08, 0x51, 0x52, 0x5e, 0x5f, 0x60 };
struct nvkm_gpio *gpio = disp->engine.subdev.device->gpio;
struct dcb_gpio_func func;
int ret;
conn->disp = disp;
conn->index = index;
conn->info = *info;
conn->info.hpd = DCB_GPIO_UNUSED;
CONN_DBG(conn, "type %02x loc %d hpd %02x dp %x di %x sr %x lcdid %x",
info->type, info->location, info->hpd, info->dp,
info->di, info->sr, info->lcdid);
if ((info->hpd = ffs(info->hpd))) {
if (--info->hpd >= ARRAY_SIZE(hpd)) {
CONN_ERR(conn, "hpd %02x unknown", info->hpd);
return;
}
info->hpd = hpd[info->hpd];
ret = nvkm_gpio_find(gpio, 0, info->hpd, DCB_GPIO_UNUSED, &func);
if (ret) {
CONN_ERR(conn, "func %02x lookup failed, %d", info->hpd, ret);
return;
}
conn->info.hpd = func.line;
}
}
int
nvkm_conn_new(struct nvkm_disp *disp, int index, struct nvbios_connE *info,
struct nvkm_conn **pconn)
{
if (!(*pconn = kzalloc_obj(**pconn)))
return -ENOMEM;
nvkm_conn_ctor(disp, index, info, *pconn);
return 0;
}
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_DISP_CONN_H__
#define __NVKM_DISP_CONN_H__
#include "priv.h"
#include <subdev/bios.h>
#include <subdev/bios/conn.h>
struct nvkm_conn {
struct nvkm_disp *disp;
int index;
struct nvbios_connE info;
struct list_head head;
struct nvkm_object object;
};
int nvkm_conn_new(struct nvkm_disp *, int index, struct nvbios_connE *,
struct nvkm_conn **);
void nvkm_conn_del(struct nvkm_conn **);
#define CONN_MSG(c,l,f,a...) do { \
struct nvkm_conn *_conn = (c); \
nvkm_##l(&_conn->disp->engine.subdev, "conn %02x:%02x%02x: "f"\n", \
_conn->index, _conn->info.location, _conn->info.type, ##a); \
} while(0)
#define CONN_ERR(c,f,a...) CONN_MSG((c), error, f, ##a)
#define CONN_DBG(c,f,a...) CONN_MSG((c), debug, f, ##a)
#define CONN_TRACE(c,f,a...) CONN_MSG((c), trace, f, ##a)
#endif
@@ -0,0 +1,675 @@
/*
* Copyright 2014 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.
*
* Authors: Ben Skeggs
*/
#include "dp.h"
#include "conn.h"
#include "head.h"
#include "ior.h"
#include <drm/display/drm_dp.h>
#include <subdev/bios.h>
#include <subdev/bios/init.h>
#include <subdev/gpio.h>
#include <subdev/i2c.h>
#include <nvif/event.h>
/* IED scripts are no longer used by UEFI/RM from Ampere, but have been updated for
* the x86 option ROM. However, the relevant VBIOS table versions weren't modified,
* so we're unable to detect this in a nice way.
*/
#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;
int repeaters;
int repeater;
u8 stat[6];
u8 conf[4];
bool pc2;
u8 pc2stat;
u8 pc2conf[2];
};
static int
nvkm_dp_train_sense(struct lt_state *lt, bool pc, u32 delay)
{
struct nvkm_outp *outp = lt->outp;
u32 addr;
int ret;
usleep_range(delay, delay * 2);
if (lt->repeater)
addr = DPCD_LTTPR_LANE0_1_STATUS(lt->repeater);
else
addr = DPCD_LS02;
ret = nvkm_rdaux(outp->dp.aux, addr, &lt->stat[0], 3);
if (ret)
return ret;
if (lt->repeater)
addr = DPCD_LTTPR_LANE0_1_ADJUST(lt->repeater);
else
addr = DPCD_LS06;
ret = nvkm_rdaux(outp->dp.aux, addr, &lt->stat[4], 2);
if (ret)
return ret;
if (pc) {
ret = nvkm_rdaux(outp->dp.aux, DPCD_LS0C, &lt->pc2stat, 1);
if (ret)
lt->pc2stat = 0x00;
OUTP_TRACE(outp, "status %6ph pc2 %02x", lt->stat, lt->pc2stat);
} else {
OUTP_TRACE(outp, "status %6ph", lt->stat);
}
return 0;
}
static int
nvkm_dp_train_drive(struct lt_state *lt, bool pc)
{
struct nvkm_outp *outp = lt->outp;
struct nvkm_ior *ior = outp->ior;
struct nvkm_bios *bios = ior->disp->engine.subdev.device->bios;
struct nvbios_dpout info;
struct nvbios_dpcfg ocfg;
u8 ver, hdr, cnt, len;
u32 addr;
u32 data;
int ret, i;
for (i = 0; i < ior->dp.nr; i++) {
u8 lane = (lt->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf;
u8 lpc2 = (lt->pc2stat >> (i * 2)) & 0x3;
u8 lpre = (lane & 0x0c) >> 2;
u8 lvsw = (lane & 0x03) >> 0;
u8 hivs = 3 - lpre;
u8 hipe = 3;
u8 hipc = 3;
if (lpc2 >= hipc)
lpc2 = hipc | DPCD_LC0F_LANE0_MAX_POST_CURSOR2_REACHED;
if (lpre >= hipe) {
lpre = hipe | DPCD_LC03_MAX_SWING_REACHED; /* yes. */
lvsw = hivs = 3 - (lpre & 3);
} else
if (lvsw >= hivs) {
lvsw = hivs | DPCD_LC03_MAX_SWING_REACHED;
}
lt->conf[i] = (lpre << 3) | lvsw;
lt->pc2conf[i >> 1] |= lpc2 << ((i & 1) * 4);
OUTP_TRACE(outp, "config lane %d %02x %02x", i, lt->conf[i], lpc2);
if (lt->repeater != lt->repeaters)
continue;
data = nvbios_dpout_match(bios, outp->info.hasht, outp->info.hashm,
&ver, &hdr, &cnt, &len, &info);
if (!data)
continue;
data = nvbios_dpcfg_match(bios, data, lpc2 & 3, lvsw & 3, lpre & 3,
&ver, &hdr, &cnt, &len, &ocfg);
if (!data)
continue;
ior->func->dp->drive(ior, i, ocfg.pc, ocfg.dc, ocfg.pe, ocfg.tx_pu);
}
if (lt->repeater)
addr = DPCD_LTTPR_LANE0_SET(lt->repeater);
else
addr = DPCD_LC03(0);
ret = nvkm_wraux(outp->dp.aux, addr, lt->conf, 4);
if (ret)
return ret;
if (pc) {
ret = nvkm_wraux(outp->dp.aux, DPCD_LC0F, lt->pc2conf, 2);
if (ret)
return ret;
}
return 0;
}
static void
nvkm_dp_train_pattern(struct lt_state *lt, u8 pattern)
{
struct nvkm_outp *outp = lt->outp;
u32 addr;
u8 sink_tp;
OUTP_TRACE(outp, "training pattern %d", pattern);
outp->ior->func->dp->pattern(outp->ior, pattern);
if (lt->repeater)
addr = DPCD_LTTPR_PATTERN_SET(lt->repeater);
else
addr = DPCD_LC02;
nvkm_rdaux(outp->dp.aux, addr, &sink_tp, 1);
sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET;
sink_tp |= (pattern != 4) ? pattern : 7;
if (pattern != 0)
sink_tp |= DPCD_LC02_SCRAMBLING_DISABLE;
else
sink_tp &= ~DPCD_LC02_SCRAMBLING_DISABLE;
nvkm_wraux(outp->dp.aux, addr, &sink_tp, 1);
}
static int
nvkm_dp_train_eq(struct lt_state *lt)
{
struct nvkm_i2c_aux *aux = lt->outp->dp.aux;
bool eq_done = false, cr_done = true;
int tries = 0, usec = 0, i;
u8 data;
if (lt->repeater) {
if (!nvkm_rdaux(aux, DPCD_LTTPR_AUX_RD_INTERVAL(lt->repeater), &data, sizeof(data)))
usec = (data & DPCD_RC0E_AUX_RD_INTERVAL) * 4000;
nvkm_dp_train_pattern(lt, 4);
} else {
if (lt->outp->dp.dpcd[DPCD_RC00_DPCD_REV] >= 0x14 &&
lt->outp->dp.dpcd[DPCD_RC03] & DPCD_RC03_TPS4_SUPPORTED)
nvkm_dp_train_pattern(lt, 4);
else
if (lt->outp->dp.dpcd[DPCD_RC00_DPCD_REV] >= 0x12 &&
lt->outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED)
nvkm_dp_train_pattern(lt, 3);
else
nvkm_dp_train_pattern(lt, 2);
usec = (lt->outp->dp.dpcd[DPCD_RC0E] & DPCD_RC0E_AUX_RD_INTERVAL) * 4000;
}
do {
if ((tries &&
nvkm_dp_train_drive(lt, lt->pc2)) ||
nvkm_dp_train_sense(lt, lt->pc2, usec ? usec : 400))
break;
eq_done = !!(lt->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE);
for (i = 0; i < lt->outp->ior->dp.nr && eq_done; i++) {
u8 lane = (lt->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
if (!(lane & DPCD_LS02_LANE0_CR_DONE))
cr_done = false;
if (!(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) ||
!(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED))
eq_done = false;
}
} while (!eq_done && cr_done && ++tries <= 5);
return eq_done ? 0 : -1;
}
static int
nvkm_dp_train_cr(struct lt_state *lt)
{
bool cr_done = false, abort = false;
int voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
int tries = 0, usec = 0, i;
nvkm_dp_train_pattern(lt, 1);
if (lt->outp->dp.dpcd[DPCD_RC00_DPCD_REV] < 0x14 && !lt->repeater)
usec = (lt->outp->dp.dpcd[DPCD_RC0E] & DPCD_RC0E_AUX_RD_INTERVAL) * 4000;
do {
if (nvkm_dp_train_drive(lt, false) ||
nvkm_dp_train_sense(lt, false, usec ? usec : 100))
break;
cr_done = true;
for (i = 0; i < lt->outp->ior->dp.nr; i++) {
u8 lane = (lt->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
if (!(lane & DPCD_LS02_LANE0_CR_DONE)) {
cr_done = false;
if (lt->conf[i] & DPCD_LC03_MAX_SWING_REACHED)
abort = true;
break;
}
}
if ((lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET) != voltage) {
voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
tries = 0;
}
} while (!cr_done && !abort && ++tries < 5);
return cr_done ? 0 : -1;
}
static int
nvkm_dp_train_link(struct nvkm_outp *outp, int rate)
{
struct nvkm_ior *ior = outp->ior;
struct lt_state lt = {
.outp = outp,
.pc2 = outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED,
.repeaters = outp->dp.lttprs,
};
u8 sink[2];
int ret;
OUTP_DBG(outp, "training %dx%02x", ior->dp.nr, ior->dp.bw);
/* 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)
return ret;
if (outp->dp.rate[rate].dpcd >= 0) {
ret = nvkm_rdaux(outp->dp.aux, DPCD_LC15_LINK_RATE_SET, &sink[0], sizeof(sink[0]));
if (ret)
return ret;
sink[0] &= ~DPCD_LC15_LINK_RATE_SET_MASK;
sink[0] |= outp->dp.rate[rate].dpcd;
ret = nvkm_wraux(outp->dp.aux, DPCD_LC15_LINK_RATE_SET, &sink[0], sizeof(sink[0]));
if (ret)
return ret;
}
/* Attempt to train the link in this configuration. */
for (lt.repeater = lt.repeaters; lt.repeater >= 0; lt.repeater--) {
if (lt.repeater)
OUTP_DBG(outp, "training LTTPR%d", lt.repeater);
else
OUTP_DBG(outp, "training sink");
memset(lt.stat, 0x00, sizeof(lt.stat));
ret = nvkm_dp_train_cr(&lt);
if (ret == 0)
ret = nvkm_dp_train_eq(&lt);
nvkm_dp_train_pattern(&lt, 0);
}
return ret;
}
static int
nvkm_dp_train_links(struct nvkm_outp *outp, int rate)
{
struct nvkm_ior *ior = outp->ior;
struct nvkm_disp *disp = outp->disp;
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_bios *bios = subdev->device->bios;
u32 lnkcmp;
int ret;
OUTP_DBG(outp, "programming link for %dx%02x", ior->dp.nr, ior->dp.bw);
/* Intersect misc. capabilities of the OR and sink. */
if (disp->engine.subdev.device->chipset < 0x110)
outp->dp.dpcd[DPCD_RC03] &= ~DPCD_RC03_TPS4_SUPPORTED;
if (disp->engine.subdev.device->chipset < 0xd0)
outp->dp.dpcd[DPCD_RC02] &= ~DPCD_RC02_TPS3_SUPPORTED;
if (AMPERE_IED_HACK(disp) && (lnkcmp = outp->dp.info.script[0])) {
/* Execute BeforeLinkTraining script from DP Info table. */
while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
lnkcmp += 3;
lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
nvbios_init(&outp->disp->engine.subdev, lnkcmp,
init.outp = &outp->info;
init.or = ior->id;
init.link = ior->asy.link;
);
}
/* Set desired link configuration on the source. */
if ((lnkcmp = outp->dp.info.lnkcmp)) {
if (outp->dp.version < 0x30) {
while ((ior->dp.bw * 2700) < nvbios_rd16(bios, lnkcmp))
lnkcmp += 4;
lnkcmp = nvbios_rd16(bios, lnkcmp + 2);
} else {
while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
lnkcmp += 3;
lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
}
nvbios_init(subdev, lnkcmp,
init.outp = &outp->info;
init.or = ior->id;
init.link = ior->asy.link;
);
}
ret = ior->func->dp->links(ior, outp->dp.aux);
if (ret) {
if (ret < 0) {
OUTP_ERR(outp, "train failed with %d", ret);
return ret;
}
return 0;
}
ior->func->dp->power(ior, ior->dp.nr);
/* Attempt to train the link in this configuration. */
return nvkm_dp_train_link(outp, rate);
}
static void
nvkm_dp_train_fini(struct nvkm_outp *outp)
{
/* Execute AfterLinkTraining script from DP Info table. */
nvbios_init(&outp->disp->engine.subdev, outp->dp.info.script[1],
init.outp = &outp->info;
init.or = outp->ior->id;
init.link = outp->ior->asy.link;
);
}
static void
nvkm_dp_train_init(struct nvkm_outp *outp)
{
/* Execute EnableSpread/DisableSpread script from DP Info table. */
if (outp->dp.dpcd[DPCD_RC03] & DPCD_RC03_MAX_DOWNSPREAD) {
nvbios_init(&outp->disp->engine.subdev, outp->dp.info.script[2],
init.outp = &outp->info;
init.or = outp->ior->id;
init.link = outp->ior->asy.link;
);
} else {
nvbios_init(&outp->disp->engine.subdev, outp->dp.info.script[3],
init.outp = &outp->info;
init.or = outp->ior->id;
init.link = outp->ior->asy.link;
);
}
if (!AMPERE_IED_HACK(outp->disp)) {
/* Execute BeforeLinkTraining script from DP Info table. */
nvbios_init(&outp->disp->engine.subdev, outp->dp.info.script[0],
init.outp = &outp->info;
init.or = outp->ior->id;
init.link = outp->ior->asy.link;
);
}
}
static int
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(&lt, false);
}
static int
nvkm_dp_train(struct nvkm_outp *outp, bool retrain)
{
struct nvkm_ior *ior = outp->ior;
int ret, rate;
for (rate = 0; rate < outp->dp.rates; rate++) {
if (outp->dp.rate[rate].rate == (retrain ? ior->dp.bw : outp->dp.lt.bw) * 27000)
break;
}
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.bw = outp->dp.lt.bw;
ior->dp.nr = outp->dp.lt.nr;
nvkm_dp_train_init(outp);
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");
mutex_unlock(&outp->dp.mutex);
return ret;
}
void
nvkm_dp_disable(struct nvkm_outp *outp, struct nvkm_ior *ior)
{
/* Execute DisableLT script from DP Info Table. */
nvbios_init(&ior->disp->engine.subdev, outp->dp.info.script[4],
init.outp = &outp->info;
init.or = ior->id;
init.link = ior->arm.link;
);
}
static void
nvkm_dp_release(struct nvkm_outp *outp)
{
outp->ior->dp.nr = 0;
nvkm_dp_disable(outp, outp->ior);
nvkm_outp_release(outp);
}
void
nvkm_dp_enable(struct nvkm_outp *outp, bool auxpwr)
{
struct nvkm_gpio *gpio = outp->disp->engine.subdev.device->gpio;
struct nvkm_i2c_aux *aux = outp->dp.aux;
if (auxpwr && !outp->dp.aux_pwr) {
/* eDP panels need powering on by us (if the VBIOS doesn't default it
* to on) before doing any AUX channel transactions. LVDS panel power
* is handled by the SOR itself, and not required for LVDS DDC.
*/
if (outp->conn->info.type == DCB_CONNECTOR_eDP) {
int power = nvkm_gpio_get(gpio, 0, DCB_GPIO_PANEL_POWER, 0xff);
if (power == 0) {
nvkm_gpio_set(gpio, 0, DCB_GPIO_PANEL_POWER, 0xff, 1);
outp->dp.aux_pwr_pu = true;
}
/* We delay here unconditionally, even if already powered,
* because some laptop panels having a significant resume
* delay before the panel begins responding.
*
* This is likely a bit of a hack, but no better idea for
* handling this at the moment.
*/
msleep(300);
}
OUTP_DBG(outp, "aux power -> always");
nvkm_i2c_aux_monitor(aux, true);
outp->dp.aux_pwr = true;
} else
if (!auxpwr && outp->dp.aux_pwr) {
OUTP_DBG(outp, "aux power -> demand");
nvkm_i2c_aux_monitor(aux, false);
outp->dp.aux_pwr = false;
/* Restore eDP panel GPIO to its prior state if we changed it, as
* it could potentially interfere with other outputs.
*/
if (outp->conn->info.type == DCB_CONNECTOR_eDP) {
if (outp->dp.aux_pwr_pu) {
nvkm_gpio_set(gpio, 0, DCB_GPIO_PANEL_POWER, 0xff, 0);
outp->dp.aux_pwr_pu = false;
}
}
}
}
static void
nvkm_dp_fini(struct nvkm_outp *outp)
{
nvkm_dp_enable(outp, false);
}
static void
nvkm_dp_init(struct nvkm_outp *outp)
{
nvkm_outp_init(outp);
nvkm_dp_enable(outp, outp->dp.enabled);
}
static void *
nvkm_dp_dtor(struct nvkm_outp *outp)
{
return outp;
}
static const struct nvkm_outp_func
nvkm_dp_func = {
.dtor = nvkm_dp_dtor,
.init = nvkm_dp_init,
.fini = nvkm_dp_fini,
.detect = nvkm_outp_detect,
.inherit = nvkm_outp_inherit,
.acquire = nvkm_outp_acquire,
.release = nvkm_dp_release,
.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
nvkm_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE, struct nvkm_outp **poutp)
{
struct nvkm_device *device = disp->engine.subdev.device;
struct nvkm_bios *bios = device->bios;
struct nvkm_i2c *i2c = device->i2c;
struct nvkm_outp *outp;
u8 ver, hdr, cnt, len;
u32 data;
int ret;
ret = nvkm_outp_new_(&nvkm_dp_func, disp, index, dcbE, poutp);
outp = *poutp;
if (ret)
return ret;
if (dcbE->location == 0)
outp->dp.aux = nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_CCB(dcbE->i2c_index));
else
outp->dp.aux = nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_EXT(dcbE->extdev));
if (!outp->dp.aux) {
OUTP_ERR(outp, "no aux");
return -EINVAL;
}
/* bios data is not optional */
data = nvbios_dpout_match(bios, outp->info.hasht, outp->info.hashm,
&outp->dp.version, &hdr, &cnt, &len, &outp->dp.info);
if (!data) {
OUTP_ERR(outp, "no bios dp data");
return -EINVAL;
}
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);
return 0;
}
@@ -0,0 +1,102 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_DISP_DP_H__
#define __NVKM_DISP_DP_H__
#include "outp.h"
int nvkm_dp_new(struct nvkm_disp *, int index, struct dcb_output *,
struct nvkm_outp **);
void nvkm_dp_disable(struct nvkm_outp *, struct nvkm_ior *);
void nvkm_dp_enable(struct nvkm_outp *, bool auxpwr);
/* DPCD Receiver Capabilities */
#define DPCD_RC00_DPCD_REV 0x00000
#define DPCD_RC01_MAX_LINK_RATE 0x00001
#define DPCD_RC02 0x00002
#define DPCD_RC02_ENHANCED_FRAME_CAP 0x80
#define DPCD_RC02_TPS3_SUPPORTED 0x40
#define DPCD_RC02_MAX_LANE_COUNT 0x1f
#define DPCD_RC03 0x00003
#define DPCD_RC03_TPS4_SUPPORTED 0x80
#define DPCD_RC03_MAX_DOWNSPREAD 0x01
#define DPCD_RC0E 0x0000e
#define DPCD_RC0E_AUX_RD_INTERVAL 0x7f
#define DPCD_RC10_SUPPORTED_LINK_RATES(i) 0x00010
#define DPCD_RC10_SUPPORTED_LINK_RATES__SIZE 16
/* DPCD Link Configuration */
#define DPCD_LC00_LINK_BW_SET 0x00100
#define DPCD_LC01 0x00101
#define DPCD_LC01_ENHANCED_FRAME_EN 0x80
#define DPCD_LC01_LANE_COUNT_SET 0x1f
#define DPCD_LC02 0x00102
#define DPCD_LC02_TRAINING_PATTERN_SET 0x0f
#define DPCD_LC02_SCRAMBLING_DISABLE 0x20
#define DPCD_LC03(l) ((l) + 0x00103)
#define DPCD_LC03_MAX_PRE_EMPHASIS_REACHED 0x20
#define DPCD_LC03_PRE_EMPHASIS_SET 0x18
#define DPCD_LC03_MAX_SWING_REACHED 0x04
#define DPCD_LC03_VOLTAGE_SWING_SET 0x03
#define DPCD_LC0F 0x0010f
#define DPCD_LC0F_LANE1_MAX_POST_CURSOR2_REACHED 0x40
#define DPCD_LC0F_LANE1_POST_CURSOR2_SET 0x30
#define DPCD_LC0F_LANE0_MAX_POST_CURSOR2_REACHED 0x04
#define DPCD_LC0F_LANE0_POST_CURSOR2_SET 0x03
#define DPCD_LC10 0x00110
#define DPCD_LC10_LANE3_MAX_POST_CURSOR2_REACHED 0x40
#define DPCD_LC10_LANE3_POST_CURSOR2_SET 0x30
#define DPCD_LC10_LANE2_MAX_POST_CURSOR2_REACHED 0x04
#define DPCD_LC10_LANE2_POST_CURSOR2_SET 0x03
#define DPCD_LC15_LINK_RATE_SET 0x00115
#define DPCD_LC15_LINK_RATE_SET_MASK 0x07
/* DPCD Link/Sink Status */
#define DPCD_LS02 0x00202
#define DPCD_LS02_LANE1_SYMBOL_LOCKED 0x40
#define DPCD_LS02_LANE1_CHANNEL_EQ_DONE 0x20
#define DPCD_LS02_LANE1_CR_DONE 0x10
#define DPCD_LS02_LANE0_SYMBOL_LOCKED 0x04
#define DPCD_LS02_LANE0_CHANNEL_EQ_DONE 0x02
#define DPCD_LS02_LANE0_CR_DONE 0x01
#define DPCD_LS03 0x00203
#define DPCD_LS03_LANE3_SYMBOL_LOCKED 0x40
#define DPCD_LS03_LANE3_CHANNEL_EQ_DONE 0x20
#define DPCD_LS03_LANE3_CR_DONE 0x10
#define DPCD_LS03_LANE2_SYMBOL_LOCKED 0x04
#define DPCD_LS03_LANE2_CHANNEL_EQ_DONE 0x02
#define DPCD_LS03_LANE2_CR_DONE 0x01
#define DPCD_LS04 0x00204
#define DPCD_LS04_LINK_STATUS_UPDATED 0x80
#define DPCD_LS04_DOWNSTREAM_PORT_STATUS_CHANGED 0x40
#define DPCD_LS04_INTERLANE_ALIGN_DONE 0x01
#define DPCD_LS06 0x00206
#define DPCD_LS06_LANE1_PRE_EMPHASIS 0xc0
#define DPCD_LS06_LANE1_VOLTAGE_SWING 0x30
#define DPCD_LS06_LANE0_PRE_EMPHASIS 0x0c
#define DPCD_LS06_LANE0_VOLTAGE_SWING 0x03
#define DPCD_LS07 0x00207
#define DPCD_LS07_LANE3_PRE_EMPHASIS 0xc0
#define DPCD_LS07_LANE3_VOLTAGE_SWING 0x30
#define DPCD_LS07_LANE2_PRE_EMPHASIS 0x0c
#define DPCD_LS07_LANE2_VOLTAGE_SWING 0x03
#define DPCD_LS0C 0x0020c
#define DPCD_LS0C_LANE3_POST_CURSOR2 0xc0
#define DPCD_LS0C_LANE2_POST_CURSOR2 0x30
#define DPCD_LS0C_LANE1_POST_CURSOR2 0x0c
#define DPCD_LS0C_LANE0_POST_CURSOR2 0x03
/* DPCD Sink Control */
#define DPCD_SC00 0x00600
#define DPCD_SC00_SET_POWER 0x03
#define DPCD_SC00_SET_POWER_D0 0x01
#define DPCD_SC00_SET_POWER_D3 0x03
#define DPCD_LTTPR_REV 0xf0000
#define DPCD_LTTPR_MODE 0xf0003
#define DPCD_LTTPR_MODE_TRANSPARENT 0x55
#define DPCD_LTTPR_MODE_NON_TRANSPARENT 0xaa
#define DPCD_LTTPR_PATTERN_SET(i) ((i - 1) * 0x50 + 0xf0010)
#define DPCD_LTTPR_LANE0_SET(i) ((i - 1) * 0x50 + 0xf0011)
#define DPCD_LTTPR_AUX_RD_INTERVAL(i) ((i - 1) * 0x50 + 0xf0020)
#define DPCD_LTTPR_LANE0_1_STATUS(i) ((i - 1) * 0x50 + 0xf0030)
#define DPCD_LTTPR_LANE0_1_ADJUST(i) ((i - 1) * 0x50 + 0xf0033)
#endif
@@ -0,0 +1,349 @@
/*
* Copyright 2012 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.
*
* Authors: Ben Skeggs
*/
#include "priv.h"
#include "chan.h"
#include "hdmi.h"
#include "head.h"
#include "ior.h"
#include <nvif/class.h>
static void
g84_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
struct packed_hdmi_infoframe vsi;
const u32 hoff = head * 0x800;
nvkm_mask(device, 0x61653c + hoff, 0x00010001, 0x00010000);
if (!size)
return;
pack_hdmi_infoframe(&vsi, data, size);
nvkm_wr32(device, 0x616544 + hoff, vsi.header);
nvkm_wr32(device, 0x616548 + hoff, vsi.subpack0_low);
nvkm_wr32(device, 0x61654c + hoff, vsi.subpack0_high);
/* Is there a second (or up to fourth?) set of subpack registers here? */
/* nvkm_wr32(device, 0x616550 + hoff, vsi.subpack1_low); */
/* nvkm_wr32(device, 0x616554 + hoff, vsi.subpack1_high); */
nvkm_mask(device, 0x61653c + hoff, 0x00010001, 0x00010001);
}
static void
g84_sor_hdmi_infoframe_avi(struct nvkm_ior *ior, int head, void *data, u32 size)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
struct packed_hdmi_infoframe avi;
const u32 hoff = head * 0x800;
pack_hdmi_infoframe(&avi, data, size);
nvkm_mask(device, 0x616520 + hoff, 0x00000001, 0x00000000);
if (!size)
return;
nvkm_wr32(device, 0x616528 + hoff, avi.header);
nvkm_wr32(device, 0x61652c + hoff, avi.subpack0_low);
nvkm_wr32(device, 0x616530 + hoff, avi.subpack0_high);
nvkm_wr32(device, 0x616534 + hoff, avi.subpack1_low);
nvkm_wr32(device, 0x616538 + hoff, avi.subpack1_high);
nvkm_mask(device, 0x616520 + hoff, 0x00000001, 0x00000001);
}
static void
g84_sor_hdmi_ctrl(struct nvkm_ior *ior, int head, bool enable, u8 max_ac_packet, u8 rekey)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 ctrl = 0x40000000 * enable |
0x1f000000 /* ??? */ |
max_ac_packet << 16 |
rekey;
const u32 hoff = head * 0x800;
if (!(ctrl & 0x40000000)) {
nvkm_mask(device, 0x6165a4 + hoff, 0x40000000, 0x00000000);
nvkm_mask(device, 0x616500 + hoff, 0x00000001, 0x00000000);
return;
}
/* Audio InfoFrame */
nvkm_mask(device, 0x616500 + hoff, 0x00000001, 0x00000000);
nvkm_wr32(device, 0x616508 + hoff, 0x000a0184);
nvkm_wr32(device, 0x61650c + hoff, 0x00000071);
nvkm_wr32(device, 0x616510 + hoff, 0x00000000);
nvkm_mask(device, 0x616500 + hoff, 0x00000001, 0x00000001);
nvkm_mask(device, 0x6165d0 + hoff, 0x00070001, 0x00010001); /* SPARE, HW_CTS */
nvkm_mask(device, 0x616568 + hoff, 0x00010101, 0x00000000); /* ACR_CTRL, ?? */
nvkm_mask(device, 0x616578 + hoff, 0x80000000, 0x80000000); /* ACR_0441_ENABLE */
/* ??? */
nvkm_mask(device, 0x61733c, 0x00100000, 0x00100000); /* RESETF */
nvkm_mask(device, 0x61733c, 0x10000000, 0x10000000); /* LOOKUP_EN */
nvkm_mask(device, 0x61733c, 0x00100000, 0x00000000); /* !RESETF */
/* HDMI_CTRL */
nvkm_mask(device, 0x6165a4 + hoff, 0x5f1f007f, ctrl);
}
const struct nvkm_ior_func_hdmi
g84_sor_hdmi = {
.ctrl = g84_sor_hdmi_ctrl,
.infoframe_avi = g84_sor_hdmi_infoframe_avi,
.infoframe_vsi = g84_sor_hdmi_infoframe_vsi,
};
static const struct nvkm_ior_func
g84_sor = {
.state = nv50_sor_state,
.power = nv50_sor_power,
.clock = nv50_sor_clock,
.bl = &nv50_sor_bl,
.hdmi = &g84_sor_hdmi,
};
int
g84_sor_new(struct nvkm_disp *disp, int id)
{
return nvkm_ior_new_(&g84_sor, disp, SOR, id, false);
}
static const struct nvkm_disp_mthd_list
g84_disp_ovly_mthd_base = {
.mthd = 0x0000,
.addr = 0x000000,
.data = {
{ 0x0080, 0x000000 },
{ 0x0084, 0x6109a0 },
{ 0x0088, 0x6109c0 },
{ 0x008c, 0x6109c8 },
{ 0x0090, 0x6109b4 },
{ 0x0094, 0x610970 },
{ 0x00a0, 0x610998 },
{ 0x00a4, 0x610964 },
{ 0x00c0, 0x610958 },
{ 0x00e0, 0x6109a8 },
{ 0x00e4, 0x6109d0 },
{ 0x00e8, 0x6109d8 },
{ 0x0100, 0x61094c },
{ 0x0104, 0x610984 },
{ 0x0108, 0x61098c },
{ 0x0800, 0x6109f8 },
{ 0x0808, 0x610a08 },
{ 0x080c, 0x610a10 },
{ 0x0810, 0x610a00 },
{}
}
};
static const struct nvkm_disp_chan_mthd
g84_disp_ovly_mthd = {
.name = "Overlay",
.addr = 0x000540,
.prev = 0x000004,
.data = {
{ "Global", 1, &g84_disp_ovly_mthd_base },
{}
}
};
const struct nvkm_disp_chan_user
g84_disp_ovly = {
.func = &nv50_disp_dmac_func,
.ctrl = 3,
.user = 3,
.mthd = &g84_disp_ovly_mthd,
};
static const struct nvkm_disp_mthd_list
g84_disp_base_mthd_base = {
.mthd = 0x0000,
.addr = 0x000000,
.data = {
{ 0x0080, 0x000000 },
{ 0x0084, 0x0008c4 },
{ 0x0088, 0x0008d0 },
{ 0x008c, 0x0008dc },
{ 0x0090, 0x0008e4 },
{ 0x0094, 0x610884 },
{ 0x00a0, 0x6108a0 },
{ 0x00a4, 0x610878 },
{ 0x00c0, 0x61086c },
{ 0x00c4, 0x610800 },
{ 0x00c8, 0x61080c },
{ 0x00cc, 0x610818 },
{ 0x00e0, 0x610858 },
{ 0x00e4, 0x610860 },
{ 0x00e8, 0x6108ac },
{ 0x00ec, 0x6108b4 },
{ 0x00fc, 0x610824 },
{ 0x0100, 0x610894 },
{ 0x0104, 0x61082c },
{ 0x0110, 0x6108bc },
{ 0x0114, 0x61088c },
{}
}
};
static const struct nvkm_disp_chan_mthd
g84_disp_base_mthd = {
.name = "Base",
.addr = 0x000540,
.prev = 0x000004,
.data = {
{ "Global", 1, &g84_disp_base_mthd_base },
{ "Image", 2, &nv50_disp_base_mthd_image },
{}
}
};
const struct nvkm_disp_chan_user
g84_disp_base = {
.func = &nv50_disp_dmac_func,
.ctrl = 1,
.user = 1,
.mthd = &g84_disp_base_mthd,
};
const struct nvkm_disp_mthd_list
g84_disp_core_mthd_dac = {
.mthd = 0x0080,
.addr = 0x000008,
.data = {
{ 0x0400, 0x610b58 },
{ 0x0404, 0x610bdc },
{ 0x0420, 0x610bc4 },
{}
}
};
const struct nvkm_disp_mthd_list
g84_disp_core_mthd_head = {
.mthd = 0x0400,
.addr = 0x000540,
.data = {
{ 0x0800, 0x610ad8 },
{ 0x0804, 0x610ad0 },
{ 0x0808, 0x610a48 },
{ 0x080c, 0x610a78 },
{ 0x0810, 0x610ac0 },
{ 0x0814, 0x610af8 },
{ 0x0818, 0x610b00 },
{ 0x081c, 0x610ae8 },
{ 0x0820, 0x610af0 },
{ 0x0824, 0x610b08 },
{ 0x0828, 0x610b10 },
{ 0x082c, 0x610a68 },
{ 0x0830, 0x610a60 },
{ 0x0834, 0x000000 },
{ 0x0838, 0x610a40 },
{ 0x0840, 0x610a24 },
{ 0x0844, 0x610a2c },
{ 0x0848, 0x610aa8 },
{ 0x084c, 0x610ab0 },
{ 0x085c, 0x610c5c },
{ 0x0860, 0x610a84 },
{ 0x0864, 0x610a90 },
{ 0x0868, 0x610b18 },
{ 0x086c, 0x610b20 },
{ 0x0870, 0x610ac8 },
{ 0x0874, 0x610a38 },
{ 0x0878, 0x610c50 },
{ 0x0880, 0x610a58 },
{ 0x0884, 0x610a9c },
{ 0x089c, 0x610c68 },
{ 0x08a0, 0x610a70 },
{ 0x08a4, 0x610a50 },
{ 0x08a8, 0x610ae0 },
{ 0x08c0, 0x610b28 },
{ 0x08c4, 0x610b30 },
{ 0x08c8, 0x610b40 },
{ 0x08d4, 0x610b38 },
{ 0x08d8, 0x610b48 },
{ 0x08dc, 0x610b50 },
{ 0x0900, 0x610a18 },
{ 0x0904, 0x610ab8 },
{ 0x0910, 0x610c70 },
{ 0x0914, 0x610c78 },
{}
}
};
const struct nvkm_disp_chan_mthd
g84_disp_core_mthd = {
.name = "Core",
.addr = 0x000000,
.prev = 0x000004,
.data = {
{ "Global", 1, &nv50_disp_core_mthd_base },
{ "DAC", 3, &g84_disp_core_mthd_dac },
{ "SOR", 2, &nv50_disp_core_mthd_sor },
{ "PIOR", 3, &nv50_disp_core_mthd_pior },
{ "HEAD", 2, &g84_disp_core_mthd_head },
{}
}
};
const struct nvkm_disp_chan_user
g84_disp_core = {
.func = &nv50_disp_core_func,
.ctrl = 0,
.user = 0,
.mthd = &g84_disp_core_mthd,
};
static const struct nvkm_disp_func
g84_disp = {
.oneinit = nv50_disp_oneinit,
.init = nv50_disp_init,
.fini = nv50_disp_fini,
.intr = nv50_disp_intr,
.super = nv50_disp_super,
.uevent = &nv50_disp_chan_uevent,
.head = { .cnt = nv50_head_cnt, .new = nv50_head_new },
.dac = { .cnt = nv50_dac_cnt, .new = nv50_dac_new },
.sor = { .cnt = nv50_sor_cnt, .new = g84_sor_new },
.pior = { .cnt = nv50_pior_cnt, .new = nv50_pior_new },
.root = { 0,0,G82_DISP },
.user = {
{{0,0,G82_DISP_CURSOR }, nvkm_disp_chan_new, &nv50_disp_curs },
{{0,0,G82_DISP_OVERLAY }, nvkm_disp_chan_new, &nv50_disp_oimm },
{{0,0,G82_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, & g84_disp_base },
{{0,0,G82_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, & g84_disp_core },
{{0,0,G82_DISP_OVERLAY_CHANNEL_DMA}, nvkm_disp_chan_new, & g84_disp_ovly },
{}
},
};
int
g84_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&g84_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,379 @@
/*
* Copyright 2012 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.
*
* Authors: Ben Skeggs
*/
#include "priv.h"
#include "chan.h"
#include "head.h"
#include "ior.h"
#include <subdev/timer.h>
#include <nvif/class.h>
void
g94_sor_dp_watermark(struct nvkm_ior *sor, int head, u8 watermark)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 loff = nv50_sor_link(sor);
nvkm_mask(device, 0x61c128 + loff, 0x0000003f, watermark);
}
void
g94_sor_dp_activesym(struct nvkm_ior *sor, int head,
u8 TU, u8 VTUa, u8 VTUf, u8 VTUi)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 loff = nv50_sor_link(sor);
nvkm_mask(device, 0x61c10c + loff, 0x000001fc, TU << 2);
nvkm_mask(device, 0x61c128 + loff, 0x010f7f00, VTUa << 24 | VTUf << 16 | VTUi << 8);
}
void
g94_sor_dp_audio_sym(struct nvkm_ior *sor, int head, u16 h, u32 v)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
nvkm_mask(device, 0x61c1e8 + soff, 0x0000ffff, h);
nvkm_mask(device, 0x61c1ec + soff, 0x00ffffff, v);
}
void
g94_sor_dp_drive(struct nvkm_ior *sor, int ln, int pc, int dc, int pe, int pu)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 loff = nv50_sor_link(sor);
const u32 shift = sor->func->dp->lanes[ln] * 8;
u32 data[3];
data[0] = nvkm_rd32(device, 0x61c118 + loff) & ~(0x000000ff << shift);
data[1] = nvkm_rd32(device, 0x61c120 + loff) & ~(0x000000ff << shift);
data[2] = nvkm_rd32(device, 0x61c130 + loff);
if ((data[2] & 0x0000ff00) < (pu << 8) || ln == 0)
data[2] = (data[2] & ~0x0000ff00) | (pu << 8);
nvkm_wr32(device, 0x61c118 + loff, data[0] | (dc << shift));
nvkm_wr32(device, 0x61c120 + loff, data[1] | (pe << shift));
nvkm_wr32(device, 0x61c130 + loff, data[2]);
}
void
g94_sor_dp_pattern(struct nvkm_ior *sor, int pattern)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 loff = nv50_sor_link(sor);
u32 data;
switch (pattern) {
case 0: data = 0x00001000; break;
case 1: data = 0x01000000; break;
case 2: data = 0x02000000; break;
default:
WARN_ON(1);
return;
}
nvkm_mask(device, 0x61c10c + loff, 0x0f001000, data);
}
void
g94_sor_dp_power(struct nvkm_ior *sor, int nr)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
const u32 loff = nv50_sor_link(sor);
u32 mask = 0, i;
for (i = 0; i < nr; i++)
mask |= 1 << sor->func->dp->lanes[i];
nvkm_mask(device, 0x61c130 + loff, 0x0000000f, mask);
nvkm_mask(device, 0x61c034 + soff, 0x80000000, 0x80000000);
nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x61c034 + soff) & 0x80000000))
break;
);
}
int
g94_sor_dp_links(struct nvkm_ior *sor, struct nvkm_i2c_aux *aux)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
const u32 loff = nv50_sor_link(sor);
u32 dpctrl = 0x00000000;
u32 clksor = 0x00000000;
dpctrl |= ((1 << sor->dp.nr) - 1) << 16;
if (sor->dp.ef)
dpctrl |= 0x00004000;
if (sor->dp.bw > 0x06)
clksor |= 0x00040000;
nvkm_mask(device, 0x614300 + soff, 0x000c0000, clksor);
nvkm_mask(device, 0x61c10c + loff, 0x001f4000, dpctrl);
return 0;
}
const struct nvkm_ior_func_dp
g94_sor_dp = {
.lanes = { 2, 1, 0, 3},
.links = g94_sor_dp_links,
.power = g94_sor_dp_power,
.pattern = g94_sor_dp_pattern,
.drive = g94_sor_dp_drive,
.audio_sym = g94_sor_dp_audio_sym,
.activesym = g94_sor_dp_activesym,
.watermark = g94_sor_dp_watermark,
};
static bool
g94_sor_war_needed(struct nvkm_ior *sor)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
if (sor->asy.proto == TMDS) {
switch (nvkm_rd32(device, 0x614300 + soff) & 0x00030000) {
case 0x00000000:
case 0x00030000:
return true;
default:
break;
}
}
return false;
}
static void
g94_sor_war_update_sppll1(struct nvkm_disp *disp)
{
struct nvkm_device *device = disp->engine.subdev.device;
struct nvkm_ior *ior;
bool used = false;
u32 clksor;
list_for_each_entry(ior, &disp->iors, head) {
if (ior->type != SOR)
continue;
clksor = nvkm_rd32(device, 0x614300 + nv50_ior_base(ior));
switch (clksor & 0x03000000) {
case 0x02000000:
case 0x03000000:
used = true;
break;
default:
break;
}
}
if (used)
return;
nvkm_mask(device, 0x00e840, 0x80000000, 0x00000000);
}
static void
g94_sor_war_3(struct nvkm_ior *sor)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
u32 sorpwr;
if (!g94_sor_war_needed(sor))
return;
sorpwr = nvkm_rd32(device, 0x61c004 + soff);
if (sorpwr & 0x00000001) {
u32 seqctl = nvkm_rd32(device, 0x61c030 + soff);
u32 pd_pc = (seqctl & 0x00000f00) >> 8;
u32 pu_pc = seqctl & 0x0000000f;
nvkm_wr32(device, 0x61c040 + soff + pd_pc * 4, 0x1f008000);
nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000))
break;
);
nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000000);
nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000))
break;
);
nvkm_wr32(device, 0x61c040 + soff + pd_pc * 4, 0x00002000);
nvkm_wr32(device, 0x61c040 + soff + pu_pc * 4, 0x1f000000);
}
nvkm_mask(device, 0x61c10c + soff, 0x00000001, 0x00000000);
nvkm_mask(device, 0x614300 + soff, 0x03000000, 0x00000000);
if (sorpwr & 0x00000001)
nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000001);
g94_sor_war_update_sppll1(sor->disp);
}
static void
g94_sor_war_2(struct nvkm_ior *sor)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
if (!g94_sor_war_needed(sor))
return;
nvkm_mask(device, 0x00e840, 0x80000000, 0x80000000);
nvkm_mask(device, 0x614300 + soff, 0x03000000, 0x03000000);
nvkm_mask(device, 0x61c10c + soff, 0x00000001, 0x00000001);
nvkm_mask(device, 0x61c00c + soff, 0x0f000000, 0x00000000);
nvkm_mask(device, 0x61c008 + soff, 0xff000000, 0x14000000);
nvkm_usec(device, 400, NVKM_DELAY);
nvkm_mask(device, 0x61c008 + soff, 0xff000000, 0x00000000);
nvkm_mask(device, 0x61c00c + soff, 0x0f000000, 0x01000000);
if (nvkm_rd32(device, 0x61c004 + soff) & 0x00000001) {
u32 seqctl = nvkm_rd32(device, 0x61c030 + soff);
u32 pu_pc = seqctl & 0x0000000f;
nvkm_wr32(device, 0x61c040 + soff + pu_pc * 4, 0x1f008000);
}
}
void
g94_sor_state(struct nvkm_ior *sor, struct nvkm_ior_state *state)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 coff = sor->id * 8 + (state == &sor->arm) * 4;
u32 ctrl = nvkm_rd32(device, 0x610794 + coff);
state->proto_evo = (ctrl & 0x00000f00) >> 8;
switch (state->proto_evo) {
case 0: state->proto = LVDS; state->link = 1; break;
case 1: state->proto = TMDS; state->link = 1; break;
case 2: state->proto = TMDS; state->link = 2; break;
case 5: state->proto = TMDS; state->link = 3; break;
case 8: state->proto = DP; state->link = 1; break;
case 9: state->proto = DP; state->link = 2; break;
default:
state->proto = UNKNOWN;
break;
}
state->head = ctrl & 0x00000003;
nv50_pior_depth(sor, state, ctrl);
}
static const struct nvkm_ior_func
g94_sor = {
.state = g94_sor_state,
.power = nv50_sor_power,
.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,
};
static int
g94_sor_new(struct nvkm_disp *disp, int id)
{
return nvkm_ior_new_(&g94_sor, disp, SOR, id, false);
}
int
g94_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask)
{
struct nvkm_device *device = disp->engine.subdev.device;
*pmask = (nvkm_rd32(device, 0x610184) & 0x0f000000) >> 24;
return 4;
}
static const struct nvkm_disp_mthd_list
g94_disp_core_mthd_sor = {
.mthd = 0x0040,
.addr = 0x000008,
.data = {
{ 0x0600, 0x610794 },
{}
}
};
const struct nvkm_disp_chan_mthd
g94_disp_core_mthd = {
.name = "Core",
.addr = 0x000000,
.prev = 0x000004,
.data = {
{ "Global", 1, &nv50_disp_core_mthd_base },
{ "DAC", 3, &g84_disp_core_mthd_dac },
{ "SOR", 4, &g94_disp_core_mthd_sor },
{ "PIOR", 3, &nv50_disp_core_mthd_pior },
{ "HEAD", 2, &g84_disp_core_mthd_head },
{}
}
};
const struct nvkm_disp_chan_user
g94_disp_core = {
.func = &nv50_disp_core_func,
.ctrl = 0,
.user = 0,
.mthd = &g94_disp_core_mthd,
};
static const struct nvkm_disp_func
g94_disp = {
.oneinit = nv50_disp_oneinit,
.init = nv50_disp_init,
.fini = nv50_disp_fini,
.intr = nv50_disp_intr,
.super = nv50_disp_super,
.uevent = &nv50_disp_chan_uevent,
.head = { .cnt = nv50_head_cnt, .new = nv50_head_new },
.dac = { .cnt = nv50_dac_cnt, .new = nv50_dac_new },
.sor = { .cnt = g94_sor_cnt, .new = g94_sor_new },
.pior = { .cnt = nv50_pior_cnt, .new = nv50_pior_new },
.root = { 0,0,GT206_DISP },
.user = {
{{0,0, G82_DISP_CURSOR }, nvkm_disp_chan_new, & nv50_disp_curs },
{{0,0, G82_DISP_OVERLAY }, nvkm_disp_chan_new, & nv50_disp_oimm },
{{0,0,GT200_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, & g84_disp_base },
{{0,0,GT206_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, & g94_disp_core },
{{0,0,GT200_DISP_OVERLAY_CHANNEL_DMA}, nvkm_disp_chan_new, &gt200_disp_ovly },
{}
},
};
int
g94_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&g94_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,155 @@
/*
* Copyright 2021 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 "head.h"
#include "ior.h"
#include <subdev/gsp.h>
#include <subdev/timer.h>
#include <nvif/class.h>
static int
ga102_sor_dp_links(struct nvkm_ior *sor, struct nvkm_i2c_aux *aux)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
const u32 loff = nv50_sor_link(sor);
u32 dpctrl = 0x00000000;
u32 clksor = 0x00000000;
switch (sor->dp.bw) {
case 0x06: clksor |= 0x00000000; break;
case 0x0a: clksor |= 0x00040000; break;
case 0x14: clksor |= 0x00080000; break;
case 0x1e: clksor |= 0x000c0000; break;
case 0x08: clksor |= 0x00100000; break;
case 0x09: clksor |= 0x00140000; break;
case 0x0c: clksor |= 0x00180000; break;
case 0x10: clksor |= 0x001c0000; break;
default:
WARN_ON(1);
return -EINVAL;
}
dpctrl |= ((1 << sor->dp.nr) - 1) << 16;
if (sor->dp.mst)
dpctrl |= 0x40000000;
if (sor->dp.ef)
dpctrl |= 0x00004000;
nvkm_mask(device, 0x612300 + soff, 0x007c0000, clksor);
/*XXX*/
nvkm_msec(device, 40, NVKM_DELAY);
nvkm_mask(device, 0x612300 + soff, 0x00030000, 0x00010000);
nvkm_mask(device, 0x61c10c + loff, 0x00000003, 0x00000001);
nvkm_mask(device, 0x61c10c + loff, 0x401f4000, dpctrl);
return 0;
}
static const struct nvkm_ior_func_dp
ga102_sor_dp = {
.lanes = { 0, 1, 2, 3 },
.links = ga102_sor_dp_links,
.power = g94_sor_dp_power,
.pattern = gm107_sor_dp_pattern,
.drive = gm200_sor_dp_drive,
.vcpi = tu102_sor_dp_vcpi,
.audio = gv100_sor_dp_audio,
.audio_sym = gv100_sor_dp_audio_sym,
.watermark = gv100_sor_dp_watermark,
};
static void
ga102_sor_clock(struct nvkm_ior *sor)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
u32 div2 = 0;
if (sor->asy.proto == TMDS) {
if (sor->tmds.high_speed)
div2 = 1;
}
nvkm_wr32(device, 0x00ec08 + (sor->id * 0x10), 0x00000000);
nvkm_wr32(device, 0x00ec04 + (sor->id * 0x10), div2);
}
static const struct nvkm_ior_func
ga102_sor = {
.route = {
.get = gm200_sor_route_get,
.set = gm200_sor_route_set,
},
.state = gv100_sor_state,
.power = nv50_sor_power,
.clock = ga102_sor_clock,
.bl = &gt215_sor_bl,
.hdmi = &gv100_sor_hdmi,
.dp = &ga102_sor_dp,
.hda = &gv100_sor_hda,
};
static int
ga102_sor_new(struct nvkm_disp *disp, int id)
{
struct nvkm_device *device = disp->engine.subdev.device;
u32 hda = nvkm_rd32(device, 0x08a15c);
return nvkm_ior_new_(&ga102_sor, disp, SOR, id, hda & BIT(id));
}
static const struct nvkm_disp_func
ga102_disp = {
.oneinit = nv50_disp_oneinit,
.init = tu102_disp_init,
.fini = gv100_disp_fini,
.intr = gv100_disp_intr,
.super = gv100_disp_super,
.uevent = &gv100_disp_chan_uevent,
.wndw = { .cnt = gv100_disp_wndw_cnt },
.head = { .cnt = gv100_head_cnt, .new = gv100_head_new },
.sor = { .cnt = gv100_sor_cnt, .new = ga102_sor_new },
.ramht_size = 0x2000,
.root = { 0, 0,GA102_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,GA102_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
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);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,333 @@
/*
* Copyright 2012 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.
*
* Authors: Ben Skeggs
*/
#include "priv.h"
#include "chan.h"
#include "hdmi.h"
#include "head.h"
#include "ior.h"
#include <nvif/class.h>
void
gk104_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
struct packed_hdmi_infoframe vsi;
const u32 hoff = head * 0x400;
pack_hdmi_infoframe(&vsi, data, size);
/* GENERIC(?) / Vendor InfoFrame? */
nvkm_mask(device, 0x690100 + hoff, 0x00010001, 0x00000000);
if (!size)
return;
nvkm_wr32(device, 0x690108 + hoff, vsi.header);
nvkm_wr32(device, 0x69010c + hoff, vsi.subpack0_low);
nvkm_wr32(device, 0x690110 + hoff, vsi.subpack0_high);
/* Is there a second (or further?) set of subpack registers here? */
nvkm_mask(device, 0x690100 + hoff, 0x00000001, 0x00000001);
}
void
gk104_sor_hdmi_infoframe_avi(struct nvkm_ior *ior, int head, void *data, u32 size)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
struct packed_hdmi_infoframe avi;
const u32 hoff = head * 0x400;
pack_hdmi_infoframe(&avi, data, size);
/* AVI InfoFrame */
nvkm_mask(device, 0x690000 + hoff, 0x00000001, 0x00000000);
if (!size)
return;
nvkm_wr32(device, 0x690008 + hoff, avi.header);
nvkm_wr32(device, 0x69000c + hoff, avi.subpack0_low);
nvkm_wr32(device, 0x690010 + hoff, avi.subpack0_high);
nvkm_wr32(device, 0x690014 + hoff, avi.subpack1_low);
nvkm_wr32(device, 0x690018 + hoff, avi.subpack1_high);
nvkm_mask(device, 0x690000 + hoff, 0x00000001, 0x00000001);
}
void
gk104_sor_hdmi_ctrl(struct nvkm_ior *ior, int head, bool enable, u8 max_ac_packet, u8 rekey)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 ctrl = 0x40000000 * enable |
max_ac_packet << 16 |
rekey;
const u32 hoff = head * 0x800;
const u32 hdmi = head * 0x400;
if (!(ctrl & 0x40000000)) {
nvkm_mask(device, 0x616798 + hoff, 0x40000000, 0x00000000);
nvkm_mask(device, 0x690100 + hdmi, 0x00000001, 0x00000000);
nvkm_mask(device, 0x6900c0 + hdmi, 0x00000001, 0x00000000);
nvkm_mask(device, 0x690000 + hdmi, 0x00000001, 0x00000000);
return;
}
/* ??? InfoFrame? */
nvkm_mask(device, 0x6900c0 + hdmi, 0x00000001, 0x00000000);
nvkm_wr32(device, 0x6900cc + hdmi, 0x00000010);
nvkm_mask(device, 0x6900c0 + hdmi, 0x00000001, 0x00000001);
/* ??? */
nvkm_wr32(device, 0x690080 + hdmi, 0x82000000);
/* HDMI_CTRL */
nvkm_mask(device, 0x616798 + hoff, 0x401f007f, ctrl);
}
const struct nvkm_ior_func_hdmi
gk104_sor_hdmi = {
.ctrl = gk104_sor_hdmi_ctrl,
.infoframe_avi = gk104_sor_hdmi_infoframe_avi,
.infoframe_vsi = gk104_sor_hdmi_infoframe_vsi,
};
static const struct nvkm_ior_func
gk104_sor = {
.state = gf119_sor_state,
.power = nv50_sor_power,
.clock = gf119_sor_clock,
.bl = &gt215_sor_bl,
.hdmi = &gk104_sor_hdmi,
.dp = &gf119_sor_dp,
.hda = &gf119_sor_hda,
};
int
gk104_sor_new(struct nvkm_disp *disp, int id)
{
return nvkm_ior_new_(&gk104_sor, disp, SOR, id, true);
}
static const struct nvkm_disp_mthd_list
gk104_disp_ovly_mthd_base = {
.mthd = 0x0000,
.data = {
{ 0x0080, 0x665080 },
{ 0x0084, 0x665084 },
{ 0x0088, 0x665088 },
{ 0x008c, 0x66508c },
{ 0x0090, 0x665090 },
{ 0x0094, 0x665094 },
{ 0x00a0, 0x6650a0 },
{ 0x00a4, 0x6650a4 },
{ 0x00b0, 0x6650b0 },
{ 0x00b4, 0x6650b4 },
{ 0x00b8, 0x6650b8 },
{ 0x00c0, 0x6650c0 },
{ 0x00c4, 0x6650c4 },
{ 0x00e0, 0x6650e0 },
{ 0x00e4, 0x6650e4 },
{ 0x00e8, 0x6650e8 },
{ 0x0100, 0x665100 },
{ 0x0104, 0x665104 },
{ 0x0108, 0x665108 },
{ 0x010c, 0x66510c },
{ 0x0110, 0x665110 },
{ 0x0118, 0x665118 },
{ 0x011c, 0x66511c },
{ 0x0120, 0x665120 },
{ 0x0124, 0x665124 },
{ 0x0130, 0x665130 },
{ 0x0134, 0x665134 },
{ 0x0138, 0x665138 },
{ 0x013c, 0x66513c },
{ 0x0140, 0x665140 },
{ 0x0144, 0x665144 },
{ 0x0148, 0x665148 },
{ 0x014c, 0x66514c },
{ 0x0150, 0x665150 },
{ 0x0154, 0x665154 },
{ 0x0158, 0x665158 },
{ 0x015c, 0x66515c },
{ 0x0160, 0x665160 },
{ 0x0164, 0x665164 },
{ 0x0168, 0x665168 },
{ 0x016c, 0x66516c },
{ 0x0400, 0x665400 },
{ 0x0404, 0x665404 },
{ 0x0408, 0x665408 },
{ 0x040c, 0x66540c },
{ 0x0410, 0x665410 },
{}
}
};
const struct nvkm_disp_chan_mthd
gk104_disp_ovly_mthd = {
.name = "Overlay",
.addr = 0x001000,
.prev = -0x020000,
.data = {
{ "Global", 1, &gk104_disp_ovly_mthd_base },
{}
}
};
const struct nvkm_disp_chan_user
gk104_disp_ovly = {
.func = &gf119_disp_dmac_func,
.ctrl = 5,
.user = 5,
.mthd = &gk104_disp_ovly_mthd,
};
static const struct nvkm_disp_mthd_list
gk104_disp_core_mthd_head = {
.mthd = 0x0300,
.addr = 0x000300,
.data = {
{ 0x0400, 0x660400 },
{ 0x0404, 0x660404 },
{ 0x0408, 0x660408 },
{ 0x040c, 0x66040c },
{ 0x0410, 0x660410 },
{ 0x0414, 0x660414 },
{ 0x0418, 0x660418 },
{ 0x041c, 0x66041c },
{ 0x0420, 0x660420 },
{ 0x0424, 0x660424 },
{ 0x0428, 0x660428 },
{ 0x042c, 0x66042c },
{ 0x0430, 0x660430 },
{ 0x0434, 0x660434 },
{ 0x0438, 0x660438 },
{ 0x0440, 0x660440 },
{ 0x0444, 0x660444 },
{ 0x0448, 0x660448 },
{ 0x044c, 0x66044c },
{ 0x0450, 0x660450 },
{ 0x0454, 0x660454 },
{ 0x0458, 0x660458 },
{ 0x045c, 0x66045c },
{ 0x0460, 0x660460 },
{ 0x0468, 0x660468 },
{ 0x046c, 0x66046c },
{ 0x0470, 0x660470 },
{ 0x0474, 0x660474 },
{ 0x047c, 0x66047c },
{ 0x0480, 0x660480 },
{ 0x0484, 0x660484 },
{ 0x0488, 0x660488 },
{ 0x048c, 0x66048c },
{ 0x0490, 0x660490 },
{ 0x0494, 0x660494 },
{ 0x0498, 0x660498 },
{ 0x04a0, 0x6604a0 },
{ 0x04b0, 0x6604b0 },
{ 0x04b8, 0x6604b8 },
{ 0x04bc, 0x6604bc },
{ 0x04c0, 0x6604c0 },
{ 0x04c4, 0x6604c4 },
{ 0x04c8, 0x6604c8 },
{ 0x04d0, 0x6604d0 },
{ 0x04d4, 0x6604d4 },
{ 0x04e0, 0x6604e0 },
{ 0x04e4, 0x6604e4 },
{ 0x04e8, 0x6604e8 },
{ 0x04ec, 0x6604ec },
{ 0x04f0, 0x6604f0 },
{ 0x04f4, 0x6604f4 },
{ 0x04f8, 0x6604f8 },
{ 0x04fc, 0x6604fc },
{ 0x0500, 0x660500 },
{ 0x0504, 0x660504 },
{ 0x0508, 0x660508 },
{ 0x050c, 0x66050c },
{ 0x0510, 0x660510 },
{ 0x0514, 0x660514 },
{ 0x0518, 0x660518 },
{ 0x051c, 0x66051c },
{ 0x0520, 0x660520 },
{ 0x0524, 0x660524 },
{ 0x052c, 0x66052c },
{ 0x0530, 0x660530 },
{ 0x054c, 0x66054c },
{ 0x0550, 0x660550 },
{ 0x0554, 0x660554 },
{ 0x0558, 0x660558 },
{ 0x055c, 0x66055c },
{}
}
};
const struct nvkm_disp_chan_mthd
gk104_disp_core_mthd = {
.name = "Core",
.addr = 0x000000,
.prev = -0x020000,
.data = {
{ "Global", 1, &gf119_disp_core_mthd_base },
{ "DAC", 3, &gf119_disp_core_mthd_dac },
{ "SOR", 8, &gf119_disp_core_mthd_sor },
{ "PIOR", 4, &gf119_disp_core_mthd_pior },
{ "HEAD", 4, &gk104_disp_core_mthd_head },
{}
}
};
const struct nvkm_disp_chan_user
gk104_disp_core = {
.func = &gf119_disp_core_func,
.ctrl = 0,
.user = 0,
.mthd = &gk104_disp_core_mthd,
};
static const struct nvkm_disp_func
gk104_disp = {
.oneinit = nv50_disp_oneinit,
.init = gf119_disp_init,
.fini = gf119_disp_fini,
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.super = gf119_disp_super,
.uevent = &gf119_disp_chan_uevent,
.head = { .cnt = gf119_head_cnt, .new = gf119_head_new },
.dac = { .cnt = gf119_dac_cnt, .new = gf119_dac_new },
.sor = { .cnt = gf119_sor_cnt, .new = gk104_sor_new },
.root = { 0,0,GK104_DISP },
.user = {
{{0,0,GK104_DISP_CURSOR }, nvkm_disp_chan_new, &gf119_disp_curs },
{{0,0,GK104_DISP_OVERLAY }, nvkm_disp_chan_new, &gf119_disp_oimm },
{{0,0,GK104_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, &gf119_disp_base },
{{0,0,GK104_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, &gk104_disp_core },
{{0,0,GK104_DISP_OVERLAY_CONTROL_DMA}, nvkm_disp_chan_new, &gk104_disp_ovly },
{}
},
};
int
gk104_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&gk104_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,59 @@
/*
* Copyright 2012 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.
*
* Authors: Ben Skeggs
*/
#include "priv.h"
#include "chan.h"
#include "head.h"
#include "ior.h"
#include <nvif/class.h>
static const struct nvkm_disp_func
gk110_disp = {
.oneinit = nv50_disp_oneinit,
.init = gf119_disp_init,
.fini = gf119_disp_fini,
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.super = gf119_disp_super,
.uevent = &gf119_disp_chan_uevent,
.head = { .cnt = gf119_head_cnt, .new = gf119_head_new },
.dac = { .cnt = gf119_dac_cnt, .new = gf119_dac_new },
.sor = { .cnt = gf119_sor_cnt, .new = gk104_sor_new },
.root = { 0,0,GK110_DISP },
.user = {
{{0,0,GK104_DISP_CURSOR }, nvkm_disp_chan_new, &gf119_disp_curs },
{{0,0,GK104_DISP_OVERLAY }, nvkm_disp_chan_new, &gf119_disp_oimm },
{{0,0,GK110_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, &gf119_disp_base },
{{0,0,GK110_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, &gk104_disp_core },
{{0,0,GK104_DISP_OVERLAY_CONTROL_DMA}, nvkm_disp_chan_new, &gk104_disp_ovly },
{}
},
};
int
gk110_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&gk110_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,113 @@
/*
* Copyright 2012 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.
*
* Authors: Ben Skeggs
*/
#include "priv.h"
#include "chan.h"
#include "head.h"
#include "ior.h"
#include <nvif/class.h>
void
gm107_sor_dp_pattern(struct nvkm_ior *sor, int pattern)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
u32 mask = 0x1f1f1f1f, data;
switch (pattern) {
case 0: data = 0x10101010; break;
case 1: data = 0x01010101; break;
case 2: data = 0x02020202; break;
case 3: data = 0x03030303; break;
case 4: data = 0x1b1b1b1b; break;
default:
WARN_ON(1);
return;
}
if (sor->asy.link & 1)
nvkm_mask(device, 0x61c110 + soff, mask, data);
else
nvkm_mask(device, 0x61c12c + soff, mask, data);
}
static const struct nvkm_ior_func_dp
gm107_sor_dp = {
.lanes = { 0, 1, 2, 3 },
.links = gf119_sor_dp_links,
.power = g94_sor_dp_power,
.pattern = gm107_sor_dp_pattern,
.drive = gf119_sor_dp_drive,
.vcpi = gf119_sor_dp_vcpi,
.audio = gf119_sor_dp_audio,
.audio_sym = gf119_sor_dp_audio_sym,
.watermark = gf119_sor_dp_watermark,
};
static const struct nvkm_ior_func
gm107_sor = {
.state = gf119_sor_state,
.power = nv50_sor_power,
.clock = gf119_sor_clock,
.bl = &gt215_sor_bl,
.hdmi = &gk104_sor_hdmi,
.dp = &gm107_sor_dp,
.hda = &gf119_sor_hda,
};
static int
gm107_sor_new(struct nvkm_disp *disp, int id)
{
return nvkm_ior_new_(&gm107_sor, disp, SOR, id, true);
}
static const struct nvkm_disp_func
gm107_disp = {
.oneinit = nv50_disp_oneinit,
.init = gf119_disp_init,
.fini = gf119_disp_fini,
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.super = gf119_disp_super,
.uevent = &gf119_disp_chan_uevent,
.head = { .cnt = gf119_head_cnt, .new = gf119_head_new },
.dac = { .cnt = gf119_dac_cnt, .new = gf119_dac_new },
.sor = { .cnt = gf119_sor_cnt, .new = gm107_sor_new },
.root = { 0,0,GM107_DISP },
.user = {
{{0,0,GK104_DISP_CURSOR }, nvkm_disp_chan_new, &gf119_disp_curs },
{{0,0,GK104_DISP_OVERLAY }, nvkm_disp_chan_new, &gf119_disp_oimm },
{{0,0,GK110_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, &gf119_disp_base },
{{0,0,GM107_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, &gk104_disp_core },
{{0,0,GK104_DISP_OVERLAY_CONTROL_DMA}, nvkm_disp_chan_new, &gk104_disp_ovly },
{}
},
};
int
gm107_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&gm107_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,196 @@
/*
* Copyright 2012 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.
*
* Authors: Ben Skeggs
*/
#include "priv.h"
#include "chan.h"
#include "hdmi.h"
#include "head.h"
#include "ior.h"
#include "outp.h"
#include <nvif/class.h>
void
gm200_sor_dp_drive(struct nvkm_ior *sor, int ln, int pc, int dc, int pe, int pu)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 loff = nv50_sor_link(sor);
const u32 shift = sor->func->dp->lanes[ln] * 8;
u32 data[4];
pu &= 0x0f;
data[0] = nvkm_rd32(device, 0x61c118 + loff) & ~(0x000000ff << shift);
data[1] = nvkm_rd32(device, 0x61c120 + loff) & ~(0x000000ff << shift);
data[2] = nvkm_rd32(device, 0x61c130 + loff);
if ((data[2] & 0x00000f00) < (pu << 8) || ln == 0)
data[2] = (data[2] & ~0x00000f00) | (pu << 8);
nvkm_wr32(device, 0x61c118 + loff, data[0] | (dc << shift));
nvkm_wr32(device, 0x61c120 + loff, data[1] | (pe << shift));
nvkm_wr32(device, 0x61c130 + loff, data[2]);
data[3] = nvkm_rd32(device, 0x61c13c + loff) & ~(0x000000ff << shift);
nvkm_wr32(device, 0x61c13c + loff, data[3] | (pc << shift));
}
const struct nvkm_ior_func_dp
gm200_sor_dp = {
.lanes = { 0, 1, 2, 3 },
.links = gf119_sor_dp_links,
.power = g94_sor_dp_power,
.pattern = gm107_sor_dp_pattern,
.drive = gm200_sor_dp_drive,
.vcpi = gf119_sor_dp_vcpi,
.audio = gf119_sor_dp_audio,
.audio_sym = gf119_sor_dp_audio_sym,
.watermark = gf119_sor_dp_watermark,
};
void
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);
u32 ctrl = 0;
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;
}
nvkm_mask(device, 0x61c5bc + soff, 0x00000003, ctrl);
}
const struct nvkm_ior_func_hdmi
gm200_sor_hdmi = {
.ctrl = gk104_sor_hdmi_ctrl,
.scdc = gm200_sor_hdmi_scdc,
.infoframe_avi = gk104_sor_hdmi_infoframe_avi,
.infoframe_vsi = gk104_sor_hdmi_infoframe_vsi,
};
void
gm200_sor_route_set(struct nvkm_outp *outp, struct nvkm_ior *ior)
{
struct nvkm_device *device = outp->disp->engine.subdev.device;
const u32 moff = __ffs(outp->info.or) * 0x100;
const u32 sor = ior ? ior->id + 1 : 0;
u32 link = ior ? (ior->asy.link == 2) : 0;
if (outp->info.sorconf.link & 1) {
nvkm_mask(device, 0x612308 + moff, 0x0000001f, link << 4 | sor);
link++;
}
if (outp->info.sorconf.link & 2)
nvkm_mask(device, 0x612388 + moff, 0x0000001f, link << 4 | sor);
}
int
gm200_sor_route_get(struct nvkm_outp *outp, int *link)
{
struct nvkm_device *device = outp->disp->engine.subdev.device;
const int sublinks = outp->info.sorconf.link;
int lnk[2], sor[2], m, s;
for (*link = 0, m = __ffs(outp->info.or) * 2, s = 0; s < 2; m++, s++) {
if (sublinks & BIT(s)) {
u32 data = nvkm_rd32(device, 0x612308 + (m * 0x80));
lnk[s] = (data & 0x00000010) >> 4;
sor[s] = (data & 0x0000000f);
if (!sor[s])
return -1;
*link |= lnk[s];
}
}
if (sublinks == 3) {
if (sor[0] != sor[1] || WARN_ON(lnk[0] || !lnk[1]))
return -1;
}
return ((sublinks & 1) ? sor[0] : sor[1]) - 1;
}
static const struct nvkm_ior_func
gm200_sor = {
.route = {
.get = gm200_sor_route_get,
.set = gm200_sor_route_set,
},
.state = gf119_sor_state,
.power = nv50_sor_power,
.clock = gf119_sor_clock,
.bl = &gt215_sor_bl,
.hdmi = &gm200_sor_hdmi,
.dp = &gm200_sor_dp,
.hda = &gf119_sor_hda,
};
static int
gm200_sor_new(struct nvkm_disp *disp, int id)
{
struct nvkm_device *device = disp->engine.subdev.device;
u32 hda;
if (!((hda = nvkm_rd32(device, 0x08a15c)) & 0x40000000))
hda = nvkm_rd32(device, 0x101034);
return nvkm_ior_new_(&gm200_sor, disp, SOR, id, hda & BIT(id));
}
static const struct nvkm_disp_func
gm200_disp = {
.oneinit = nv50_disp_oneinit,
.init = gf119_disp_init,
.fini = gf119_disp_fini,
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.super = gf119_disp_super,
.uevent = &gf119_disp_chan_uevent,
.head = { .cnt = gf119_head_cnt, .new = gf119_head_new },
.dac = { .cnt = gf119_dac_cnt, .new = gf119_dac_new },
.sor = { .cnt = gf119_sor_cnt, .new = gm200_sor_new },
.root = { 0,0,GM200_DISP },
.user = {
{{0,0,GK104_DISP_CURSOR }, nvkm_disp_chan_new, &gf119_disp_curs },
{{0,0,GK104_DISP_OVERLAY }, nvkm_disp_chan_new, &gf119_disp_oimm },
{{0,0,GK110_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, &gf119_disp_base },
{{0,0,GM200_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, &gk104_disp_core },
{{0,0,GK104_DISP_OVERLAY_CONTROL_DMA}, nvkm_disp_chan_new, &gk104_disp_ovly },
{}
},
};
int
gm200_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&gm200_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,85 @@
/*
* Copyright 2015 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.
*
* Authors: Ben Skeggs <bskeggs@redhat.com>
*/
#include "priv.h"
#include "chan.h"
#include "head.h"
#include "ior.h"
#include <nvif/class.h>
static const struct nvkm_ior_func
gp100_sor = {
.route = {
.get = gm200_sor_route_get,
.set = gm200_sor_route_set,
},
.state = gf119_sor_state,
.power = nv50_sor_power,
.clock = gf119_sor_clock,
.bl = &gt215_sor_bl,
.hdmi = &gm200_sor_hdmi,
.dp = &gm200_sor_dp,
.hda = &gf119_sor_hda,
};
int
gp100_sor_new(struct nvkm_disp *disp, int id)
{
struct nvkm_device *device = disp->engine.subdev.device;
u32 hda;
if (!((hda = nvkm_rd32(device, 0x08a15c)) & 0x40000000))
hda = nvkm_rd32(device, 0x10ebb0) >> 8;
return nvkm_ior_new_(&gp100_sor, disp, SOR, id, hda & BIT(id));
}
static const struct nvkm_disp_func
gp100_disp = {
.oneinit = nv50_disp_oneinit,
.init = gf119_disp_init,
.fini = gf119_disp_fini,
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.super = gf119_disp_super,
.uevent = &gf119_disp_chan_uevent,
.head = { .cnt = gf119_head_cnt, .new = gf119_head_new },
.sor = { .cnt = gf119_sor_cnt, .new = gp100_sor_new },
.root = { 0,0,GP100_DISP },
.user = {
{{0,0,GK104_DISP_CURSOR }, nvkm_disp_chan_new, &gf119_disp_curs },
{{0,0,GK104_DISP_OVERLAY }, nvkm_disp_chan_new, &gf119_disp_oimm },
{{0,0,GK110_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, &gf119_disp_base },
{{0,0,GP100_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, &gk104_disp_core },
{{0,0,GK104_DISP_OVERLAY_CONTROL_DMA}, nvkm_disp_chan_new, &gk104_disp_ovly },
{}
},
};
int
gp100_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&gp100_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,200 @@
/*
* Copyright 2016 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.
*
* Authors: Ben Skeggs <bskeggs@redhat.com>
*/
#include "priv.h"
#include "chan.h"
#include "head.h"
#include "ior.h"
#include <subdev/timer.h>
#include <nvif/class.h>
static int
gp102_disp_dmac_init(struct nvkm_disp_chan *chan)
{
struct nvkm_subdev *subdev = &chan->disp->engine.subdev;
struct nvkm_device *device = subdev->device;
int ctrl = chan->chid.ctrl;
int user = chan->chid.user;
/* initialise channel for dma command submission */
nvkm_wr32(device, 0x611494 + (ctrl * 0x0010), chan->push);
nvkm_wr32(device, 0x611498 + (ctrl * 0x0010), 0x00010000);
nvkm_wr32(device, 0x61149c + (ctrl * 0x0010), 0x00000001);
nvkm_mask(device, 0x610490 + (ctrl * 0x0010), 0x00000010, 0x00000010);
nvkm_wr32(device, 0x640000 + (ctrl * 0x1000), chan->suspend_put);
nvkm_wr32(device, 0x610490 + (ctrl * 0x0010), 0x00000013);
/* wait for it to go inactive */
if (nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x610490 + (ctrl * 0x10)) & 0x80000000))
break;
) < 0) {
nvkm_error(subdev, "ch %d init: %08x\n", user,
nvkm_rd32(device, 0x610490 + (ctrl * 0x10)));
return -EBUSY;
}
return 0;
}
const struct nvkm_disp_chan_func
gp102_disp_dmac_func = {
.push = nv50_disp_dmac_push,
.init = gp102_disp_dmac_init,
.fini = gf119_disp_dmac_fini,
.intr = gf119_disp_chan_intr,
.user = nv50_disp_chan_user,
.bind = gf119_disp_dmac_bind,
};
static const struct nvkm_disp_chan_user
gp102_disp_curs = {
.func = &gf119_disp_pioc_func,
.ctrl = 13,
.user = 17,
};
static const struct nvkm_disp_chan_user
gp102_disp_oimm = {
.func = &gf119_disp_pioc_func,
.ctrl = 9,
.user = 13,
};
static const struct nvkm_disp_chan_user
gp102_disp_ovly = {
.func = &gp102_disp_dmac_func,
.ctrl = 5,
.user = 5,
.mthd = &gk104_disp_ovly_mthd,
};
static const struct nvkm_disp_chan_user
gp102_disp_base = {
.func = &gp102_disp_dmac_func,
.ctrl = 1,
.user = 1,
.mthd = &gf119_disp_base_mthd,
};
static int
gp102_disp_core_init(struct nvkm_disp_chan *chan)
{
struct nvkm_subdev *subdev = &chan->disp->engine.subdev;
struct nvkm_device *device = subdev->device;
/* initialise channel for dma command submission */
nvkm_wr32(device, 0x611494, chan->push);
nvkm_wr32(device, 0x611498, 0x00010000);
nvkm_wr32(device, 0x61149c, 0x00000001);
nvkm_mask(device, 0x610490, 0x00000010, 0x00000010);
nvkm_wr32(device, 0x640000, chan->suspend_put);
nvkm_wr32(device, 0x610490, 0x01000013);
/* wait for it to go inactive */
if (nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x610490) & 0x80000000))
break;
) < 0) {
nvkm_error(subdev, "core init: %08x\n",
nvkm_rd32(device, 0x610490));
return -EBUSY;
}
return 0;
}
static const struct nvkm_disp_chan_func
gp102_disp_core_func = {
.push = nv50_disp_dmac_push,
.init = gp102_disp_core_init,
.fini = gf119_disp_core_fini,
.intr = gf119_disp_chan_intr,
.user = nv50_disp_chan_user,
.bind = gf119_disp_dmac_bind,
};
static const struct nvkm_disp_chan_user
gp102_disp_core = {
.func = &gp102_disp_core_func,
.ctrl = 0,
.user = 0,
.mthd = &gk104_disp_core_mthd,
};
static void
gp102_disp_intr_error(struct nvkm_disp *disp, int chid)
{
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_device *device = subdev->device;
u32 mthd = nvkm_rd32(device, 0x6111f0 + (chid * 12));
u32 data = nvkm_rd32(device, 0x6111f4 + (chid * 12));
u32 unkn = nvkm_rd32(device, 0x6111f8 + (chid * 12));
nvkm_error(subdev, "chid %d mthd %04x data %08x %08x %08x\n",
chid, (mthd & 0x0000ffc), data, mthd, unkn);
if (chid < ARRAY_SIZE(disp->chan)) {
switch (mthd & 0xffc) {
case 0x0080:
nv50_disp_chan_mthd(disp->chan[chid], NV_DBG_ERROR);
break;
default:
break;
}
}
nvkm_wr32(device, 0x61009c, (1 << chid));
nvkm_wr32(device, 0x6111f0 + (chid * 12), 0x90000000);
}
static const struct nvkm_disp_func
gp102_disp = {
.oneinit = nv50_disp_oneinit,
.init = gf119_disp_init,
.fini = gf119_disp_fini,
.intr = gf119_disp_intr,
.intr_error = gp102_disp_intr_error,
.super = gf119_disp_super,
.uevent = &gf119_disp_chan_uevent,
.head = { .cnt = gf119_head_cnt, .new = gf119_head_new },
.sor = { .cnt = gf119_sor_cnt, .new = gp100_sor_new },
.root = { 0,0,GP102_DISP },
.user = {
{{0,0,GK104_DISP_CURSOR }, nvkm_disp_chan_new, &gp102_disp_curs },
{{0,0,GK104_DISP_OVERLAY }, nvkm_disp_chan_new, &gp102_disp_oimm },
{{0,0,GK110_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, &gp102_disp_base },
{{0,0,GP102_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, &gp102_disp_core },
{{0,0,GK104_DISP_OVERLAY_CONTROL_DMA}, nvkm_disp_chan_new, &gp102_disp_ovly },
{}
},
};
int
gp102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&gp102_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,109 @@
/*
* Copyright 2012 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.
*
* Authors: Ben Skeggs
*/
#include "priv.h"
#include "chan.h"
#include "head.h"
#include "ior.h"
#include <nvif/class.h>
static const struct nvkm_disp_mthd_list
gt200_disp_ovly_mthd_base = {
.mthd = 0x0000,
.addr = 0x000000,
.data = {
{ 0x0080, 0x000000 },
{ 0x0084, 0x6109a0 },
{ 0x0088, 0x6109c0 },
{ 0x008c, 0x6109c8 },
{ 0x0090, 0x6109b4 },
{ 0x0094, 0x610970 },
{ 0x00a0, 0x610998 },
{ 0x00a4, 0x610964 },
{ 0x00b0, 0x610c98 },
{ 0x00b4, 0x610ca4 },
{ 0x00b8, 0x610cac },
{ 0x00c0, 0x610958 },
{ 0x00e0, 0x6109a8 },
{ 0x00e4, 0x6109d0 },
{ 0x00e8, 0x6109d8 },
{ 0x0100, 0x61094c },
{ 0x0104, 0x610984 },
{ 0x0108, 0x61098c },
{ 0x0800, 0x6109f8 },
{ 0x0808, 0x610a08 },
{ 0x080c, 0x610a10 },
{ 0x0810, 0x610a00 },
{}
}
};
static const struct nvkm_disp_chan_mthd
gt200_disp_ovly_mthd = {
.name = "Overlay",
.addr = 0x000540,
.prev = 0x000004,
.data = {
{ "Global", 1, &gt200_disp_ovly_mthd_base },
{}
}
};
const struct nvkm_disp_chan_user
gt200_disp_ovly = {
.func = &nv50_disp_dmac_func,
.ctrl = 3,
.user = 3,
.mthd = &gt200_disp_ovly_mthd,
};
static const struct nvkm_disp_func
gt200_disp = {
.oneinit = nv50_disp_oneinit,
.init = nv50_disp_init,
.fini = nv50_disp_fini,
.intr = nv50_disp_intr,
.super = nv50_disp_super,
.uevent = &nv50_disp_chan_uevent,
.head = { .cnt = nv50_head_cnt, .new = nv50_head_new },
.dac = { .cnt = nv50_dac_cnt, .new = nv50_dac_new },
.sor = { .cnt = nv50_sor_cnt, .new = g84_sor_new },
.pior = { .cnt = nv50_pior_cnt, .new = nv50_pior_new },
.root = { 0,0,GT200_DISP },
.user = {
{{0,0, G82_DISP_CURSOR }, nvkm_disp_chan_new, & nv50_disp_curs },
{{0,0, G82_DISP_OVERLAY }, nvkm_disp_chan_new, & nv50_disp_oimm },
{{0,0,GT200_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, & g84_disp_base },
{{0,0,GT200_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, & g84_disp_core },
{{0,0,GT200_DISP_OVERLAY_CHANNEL_DMA}, nvkm_disp_chan_new, &gt200_disp_ovly },
{}
},
};
int
gt200_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&gt200_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,267 @@
/*
* Copyright 2012 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.
*
* Authors: Ben Skeggs
*/
#include "priv.h"
#include "chan.h"
#include "hdmi.h"
#include "head.h"
#include "ior.h"
#include <subdev/timer.h>
#include <nvif/class.h>
static void
gt215_sor_hda_eld(struct nvkm_ior *ior, int head, u8 *data, u8 size)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 soff = ior->id * 0x800;
int i;
for (i = 0; i < size; i++)
nvkm_wr32(device, 0x61c440 + soff, (i << 8) | data[i]);
for (; i < 0x60; i++)
nvkm_wr32(device, 0x61c440 + soff, (i << 8));
nvkm_mask(device, 0x61c448 + soff, 0x80000002, 0x80000002);
}
static void
gt215_sor_hda_hpd(struct nvkm_ior *ior, int head, bool present)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
u32 data = 0x80000000;
u32 mask = 0x80000001;
if (present)
data |= 0x00000001;
else
mask |= 0x00000002;
nvkm_mask(device, 0x61c448 + ior->id * 0x800, mask, data);
}
const struct nvkm_ior_func_hda
gt215_sor_hda = {
.hpd = gt215_sor_hda_hpd,
.eld = gt215_sor_hda_eld,
};
void
gt215_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
const u32 data = 0x80000000 | (0x00000001 * enable);
const u32 mask = 0x8000000d;
nvkm_mask(device, 0x61c1e0 + soff, mask, data);
nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x61c1e0 + soff) & 0x80000000))
break;
);
}
static const struct nvkm_ior_func_dp
gt215_sor_dp = {
.lanes = { 2, 1, 0, 3 },
.links = g94_sor_dp_links,
.power = g94_sor_dp_power,
.pattern = g94_sor_dp_pattern,
.drive = g94_sor_dp_drive,
.audio = gt215_sor_dp_audio,
.audio_sym = g94_sor_dp_audio_sym,
.activesym = g94_sor_dp_activesym,
.watermark = g94_sor_dp_watermark,
};
static void
gt215_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
struct packed_hdmi_infoframe vsi;
const u32 soff = nv50_ior_base(ior);
pack_hdmi_infoframe(&vsi, data, size);
nvkm_mask(device, 0x61c53c + soff, 0x00010001, 0x00010000);
if (!size)
return;
nvkm_wr32(device, 0x61c544 + soff, vsi.header);
nvkm_wr32(device, 0x61c548 + soff, vsi.subpack0_low);
nvkm_wr32(device, 0x61c54c + soff, vsi.subpack0_high);
/* Is there a second (or up to fourth?) set of subpack registers here? */
/* nvkm_wr32(device, 0x61c550 + soff, vsi.subpack1_low); */
/* nvkm_wr32(device, 0x61c554 + soff, vsi.subpack1_high); */
nvkm_mask(device, 0x61c53c + soff, 0x00010001, 0x00010001);
}
static void
gt215_sor_hdmi_infoframe_avi(struct nvkm_ior *ior, int head, void *data, u32 size)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
struct packed_hdmi_infoframe avi;
const u32 soff = nv50_ior_base(ior);
pack_hdmi_infoframe(&avi, data, size);
nvkm_mask(device, 0x61c520 + soff, 0x00000001, 0x00000000);
if (!size)
return;
nvkm_wr32(device, 0x61c528 + soff, avi.header);
nvkm_wr32(device, 0x61c52c + soff, avi.subpack0_low);
nvkm_wr32(device, 0x61c530 + soff, avi.subpack0_high);
nvkm_wr32(device, 0x61c534 + soff, avi.subpack1_low);
nvkm_wr32(device, 0x61c538 + soff, avi.subpack1_high);
nvkm_mask(device, 0x61c520 + soff, 0x00000001, 0x00000001);
}
static void
gt215_sor_hdmi_ctrl(struct nvkm_ior *ior, int head, bool enable, u8 max_ac_packet, u8 rekey)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 ctrl = 0x40000000 * enable |
0x1f000000 /* ??? */ |
max_ac_packet << 16 |
rekey;
const u32 soff = nv50_ior_base(ior);
if (!(ctrl & 0x40000000)) {
nvkm_mask(device, 0x61c5a4 + soff, 0x40000000, 0x00000000);
nvkm_mask(device, 0x61c53c + soff, 0x00000001, 0x00000000);
nvkm_mask(device, 0x61c520 + soff, 0x00000001, 0x00000000);
nvkm_mask(device, 0x61c500 + soff, 0x00000001, 0x00000000);
return;
}
/* Audio InfoFrame */
nvkm_mask(device, 0x61c500 + soff, 0x00000001, 0x00000000);
nvkm_wr32(device, 0x61c508 + soff, 0x000a0184);
nvkm_wr32(device, 0x61c50c + soff, 0x00000071);
nvkm_wr32(device, 0x61c510 + soff, 0x00000000);
nvkm_mask(device, 0x61c500 + soff, 0x00000001, 0x00000001);
nvkm_mask(device, 0x61c5d0 + soff, 0x00070001, 0x00010001); /* SPARE, HW_CTS */
nvkm_mask(device, 0x61c568 + soff, 0x00010101, 0x00000000); /* ACR_CTRL, ?? */
nvkm_mask(device, 0x61c578 + soff, 0x80000000, 0x80000000); /* ACR_0441_ENABLE */
/* ??? */
nvkm_mask(device, 0x61733c, 0x00100000, 0x00100000); /* RESETF */
nvkm_mask(device, 0x61733c, 0x10000000, 0x10000000); /* LOOKUP_EN */
nvkm_mask(device, 0x61733c, 0x00100000, 0x00000000); /* !RESETF */
/* HDMI_CTRL */
nvkm_mask(device, 0x61c5a4 + soff, 0x5f1f007f, ctrl);
}
const struct nvkm_ior_func_hdmi
gt215_sor_hdmi = {
.ctrl = gt215_sor_hdmi_ctrl,
.infoframe_avi = gt215_sor_hdmi_infoframe_avi,
.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 = &gt215_sor_bl,
.hdmi = &gt215_sor_hdmi,
.dp = &gt215_sor_dp,
.hda = &gt215_sor_hda,
};
static int
gt215_sor_new(struct nvkm_disp *disp, int id)
{
return nvkm_ior_new_(&gt215_sor, disp, SOR, id, true);
}
static const struct nvkm_disp_func
gt215_disp = {
.oneinit = nv50_disp_oneinit,
.init = nv50_disp_init,
.fini = nv50_disp_fini,
.intr = nv50_disp_intr,
.super = nv50_disp_super,
.uevent = &nv50_disp_chan_uevent,
.head = { .cnt = nv50_head_cnt, .new = nv50_head_new },
.dac = { .cnt = nv50_dac_cnt, .new = nv50_dac_new },
.sor = { .cnt = g94_sor_cnt, .new = gt215_sor_new },
.pior = { .cnt = nv50_pior_cnt, .new = nv50_pior_new },
.root = { 0,0,GT214_DISP },
.user = {
{{0,0,GT214_DISP_CURSOR }, nvkm_disp_chan_new, & nv50_disp_curs },
{{0,0,GT214_DISP_OVERLAY }, nvkm_disp_chan_new, & nv50_disp_oimm },
{{0,0,GT214_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, & g84_disp_base },
{{0,0,GT214_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, & g94_disp_core },
{{0,0,GT214_DISP_OVERLAY_CHANNEL_DMA}, nvkm_disp_chan_new, & g84_disp_ovly },
{}
},
};
int
gt215_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&gt215_disp, device, type, inst, pdisp);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT
#include "hdmi.h"
void pack_hdmi_infoframe(struct packed_hdmi_infoframe *packed_frame,
u8 *raw_frame, ssize_t len)
{
u32 header = 0;
u32 subpack0_low = 0;
u32 subpack0_high = 0;
u32 subpack1_low = 0;
u32 subpack1_high = 0;
switch (len) {
/*
* "When in doubt, use brute force."
* -- Ken Thompson.
*/
default:
/*
* We presume that no valid frame is longer than 17
* octets, including header... And truncate to that
* if it's longer.
*/
case 17:
subpack1_high = (raw_frame[16] << 16);
fallthrough;
case 16:
subpack1_high |= (raw_frame[15] << 8);
fallthrough;
case 15:
subpack1_high |= raw_frame[14];
fallthrough;
case 14:
subpack1_low = (raw_frame[13] << 24);
fallthrough;
case 13:
subpack1_low |= (raw_frame[12] << 16);
fallthrough;
case 12:
subpack1_low |= (raw_frame[11] << 8);
fallthrough;
case 11:
subpack1_low |= raw_frame[10];
fallthrough;
case 10:
subpack0_high = (raw_frame[9] << 16);
fallthrough;
case 9:
subpack0_high |= (raw_frame[8] << 8);
fallthrough;
case 8:
subpack0_high |= raw_frame[7];
fallthrough;
case 7:
subpack0_low = (raw_frame[6] << 24);
fallthrough;
case 6:
subpack0_low |= (raw_frame[5] << 16);
fallthrough;
case 5:
subpack0_low |= (raw_frame[4] << 8);
fallthrough;
case 4:
subpack0_low |= raw_frame[3];
fallthrough;
case 3:
header = (raw_frame[2] << 16);
fallthrough;
case 2:
header |= (raw_frame[1] << 8);
fallthrough;
case 1:
header |= raw_frame[0];
fallthrough;
case 0:
break;
}
packed_frame->header = header;
packed_frame->subpack0_low = subpack0_low;
packed_frame->subpack0_high = subpack0_high;
packed_frame->subpack1_low = subpack1_low;
packed_frame->subpack1_high = subpack1_high;
}
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_DISP_HDMI_H__
#define __NVKM_DISP_HDMI_H__
#include "ior.h"
struct packed_hdmi_infoframe {
u32 header;
u32 subpack0_low;
u32 subpack0_high;
u32 subpack1_low;
u32 subpack1_high;
};
void pack_hdmi_infoframe(struct packed_hdmi_infoframe *packed_frame,
u8 *raw_frame, ssize_t len);
#endif
@@ -0,0 +1,67 @@
/*
* Copyright 2017 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.
*
* Authors: Ben Skeggs <bskeggs@redhat.com>
*/
#include "head.h"
#include <core/client.h>
#include <nvif/cl0046.h>
#include <nvif/unpack.h>
struct nvkm_head *
nvkm_head_find(struct nvkm_disp *disp, int id)
{
struct nvkm_head *head;
list_for_each_entry(head, &disp->heads, head) {
if (head->id == id)
return head;
}
return NULL;
}
void
nvkm_head_del(struct nvkm_head **phead)
{
struct nvkm_head *head = *phead;
if (head) {
HEAD_DBG(head, "dtor");
list_del(&head->head);
kfree(*phead);
*phead = NULL;
}
}
int
nvkm_head_new_(const struct nvkm_head_func *func,
struct nvkm_disp *disp, int id)
{
struct nvkm_head *head;
if (!(head = kzalloc_obj(*head)))
return -ENOMEM;
head->func = func;
head->disp = disp;
head->id = id;
list_add_tail(&head->head, &disp->heads);
HEAD_DBG(head, "ctor");
return 0;
}
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_DISP_HEAD_H__
#define __NVKM_DISP_HEAD_H__
#include <nvif/object.h>
#include "priv.h"
struct nvkm_head {
const struct nvkm_head_func *func;
struct nvkm_disp *disp;
int id;
struct list_head head;
struct nvkm_head_state {
u16 htotal;
u16 hsynce;
u16 hblanke;
u16 hblanks;
u16 vtotal;
u16 vsynce;
u16 vblanke;
u16 vblanks;
u32 hz;
/* Prior to GF119, these are set by the OR. */
struct {
u8 depth;
} or;
} arm, asy;
struct nvkm_object object;
};
int nvkm_head_new_(const struct nvkm_head_func *, struct nvkm_disp *, int id);
void nvkm_head_del(struct nvkm_head **);
struct nvkm_head *nvkm_head_find(struct nvkm_disp *, int id);
struct nvkm_head_func {
void (*state)(struct nvkm_head *, struct nvkm_head_state *);
void (*rgpos)(struct nvkm_head *, u16 *hline, u16 *vline);
void (*rgclk)(struct nvkm_head *, int div);
void (*vblank_get)(struct nvkm_head *);
void (*vblank_put)(struct nvkm_head *);
};
int nv50_head_cnt(struct nvkm_disp *, unsigned long *);
int nv50_head_new(struct nvkm_disp *, int id);
void nv50_head_rgpos(struct nvkm_head *, u16 *, u16 *);
int gf119_head_cnt(struct nvkm_disp *, unsigned long *);
int gf119_head_new(struct nvkm_disp *, int id);
void gf119_head_rgclk(struct nvkm_head *, int);
int gv100_head_cnt(struct nvkm_disp *, unsigned long *);
int gv100_head_new(struct nvkm_disp *, int id);
#define HEAD_MSG(h,l,f,a...) do { \
struct nvkm_head *_h = (h); \
nvkm_##l(&_h->disp->engine.subdev, "head-%d: "f"\n", _h->id, ##a); \
} while(0)
#define HEAD_WARN(h,f,a...) HEAD_MSG((h), warn, f, ##a)
#define HEAD_DBG(h,f,a...) HEAD_MSG((h), debug, f, ##a)
#endif
@@ -0,0 +1,72 @@
/*
* Copyright 2017 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.
*
* Authors: Ben Skeggs <bskeggs@redhat.com>
*/
#include "ior.h"
static const char *
nvkm_ior_name[] = {
[DAC] = "DAC",
[SOR] = "SOR",
[PIOR] = "PIOR",
};
struct nvkm_ior *
nvkm_ior_find(struct nvkm_disp *disp, enum nvkm_ior_type type, int id)
{
struct nvkm_ior *ior;
list_for_each_entry(ior, &disp->iors, head) {
if (ior->type == type && (id < 0 || ior->id == id))
return ior;
}
return NULL;
}
void
nvkm_ior_del(struct nvkm_ior **pior)
{
struct nvkm_ior *ior = *pior;
if (ior) {
IOR_DBG(ior, "dtor");
list_del(&ior->head);
kfree(*pior);
*pior = NULL;
}
}
int
nvkm_ior_new_(const struct nvkm_ior_func *func, struct nvkm_disp *disp,
enum nvkm_ior_type type, int id, bool hda)
{
struct nvkm_ior *ior;
if (!(ior = kzalloc_obj(*ior)))
return -ENOMEM;
ior->func = func;
ior->disp = disp;
ior->type = type;
ior->id = id;
ior->hda = hda;
snprintf(ior->name, sizeof(ior->name), "%s-%d", nvkm_ior_name[ior->type], ior->id);
list_add_tail(&ior->head, &disp->iors);
IOR_DBG(ior, "ctor");
return 0;
}
@@ -0,0 +1,209 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_DISP_IOR_H__
#define __NVKM_DISP_IOR_H__
#include "priv.h"
struct nvkm_i2c_aux;
struct nvkm_ior {
const struct nvkm_ior_func *func;
struct nvkm_disp *disp;
enum nvkm_ior_type {
DAC,
SOR,
PIOR,
} type;
int id;
bool hda;
char name[8];
struct list_head head;
bool identity;
struct nvkm_ior_state {
struct nvkm_outp *outp;
unsigned rgdiv;
unsigned proto_evo:4;
enum nvkm_ior_proto {
CRT,
TV,
TMDS,
LVDS,
DP,
UNKNOWN
} proto:3;
unsigned link:2;
unsigned head:8;
} arm, asy;
/* Armed DP state. */
struct {
bool mst;
bool ef;
u8 nr;
u8 bw;
} dp;
/* Armed TMDS state. */
struct {
bool high_speed;
} tmds;
};
struct nvkm_ior_func {
struct {
int (*get)(struct nvkm_outp *, int *link);
void (*set)(struct nvkm_outp *, struct nvkm_ior *);
} route;
void (*state)(struct nvkm_ior *, struct nvkm_ior_state *);
void (*power)(struct nvkm_ior *, bool normal, bool pu,
bool data, bool vsync, bool hsync);
int (*sense)(struct nvkm_ior *, u32 loadval);
void (*clock)(struct nvkm_ior *);
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 *, 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 {
u8 lanes[4];
int (*links)(struct nvkm_ior *, struct nvkm_i2c_aux *);
void (*power)(struct nvkm_ior *, int nr);
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);
void (*audio_sym)(struct nvkm_ior *, int head, u16 h, u32 v);
void (*activesym)(struct nvkm_ior *, int head,
u8 TU, u8 VTUa, u8 VTUf, u8 VTUi);
void (*watermark)(struct nvkm_ior *, int head, u8 watermark);
} *dp;
const struct nvkm_ior_func_hda {
void (*hpd)(struct nvkm_ior *, int head, bool present);
void (*eld)(struct nvkm_ior *, int head, u8 *data, u8 size);
void (*device_entry)(struct nvkm_ior *, int head);
} *hda;
};
int nvkm_ior_new_(const struct nvkm_ior_func *func, struct nvkm_disp *,
enum nvkm_ior_type type, int id, bool hda);
void nvkm_ior_del(struct nvkm_ior **);
struct nvkm_ior *nvkm_ior_find(struct nvkm_disp *, enum nvkm_ior_type, int id);
static inline u32
nv50_ior_base(struct nvkm_ior *ior)
{
return ior->id * 0x800;
}
int nv50_dac_cnt(struct nvkm_disp *, unsigned long *);
int nv50_dac_new(struct nvkm_disp *, int);
void nv50_dac_power(struct nvkm_ior *, bool, bool, bool, bool, bool);
int nv50_dac_sense(struct nvkm_ior *, u32);
int gf119_dac_cnt(struct nvkm_disp *, unsigned long *);
int gf119_dac_new(struct nvkm_disp *, int);
static inline u32
nv50_sor_link(struct nvkm_ior *ior)
{
return nv50_ior_base(ior) + ((ior->asy.link == 2) * 0x80);
}
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;
int g94_sor_cnt(struct nvkm_disp *, unsigned long *);
void g94_sor_state(struct nvkm_ior *, struct nvkm_ior_state *);
extern const struct nvkm_ior_func_dp g94_sor_dp;
int g94_sor_dp_links(struct nvkm_ior *, struct nvkm_i2c_aux *);
void g94_sor_dp_power(struct nvkm_ior *, int);
void g94_sor_dp_pattern(struct nvkm_ior *, int);
void g94_sor_dp_drive(struct nvkm_ior *, int, int, int, int, int);
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;
int gf119_sor_cnt(struct nvkm_disp *, unsigned long *);
void gf119_sor_state(struct nvkm_ior *, struct nvkm_ior_state *);
void gf119_sor_clock(struct nvkm_ior *);
extern const struct nvkm_ior_func_dp gf119_sor_dp;
int gf119_sor_dp_links(struct nvkm_ior *, struct nvkm_i2c_aux *);
void gf119_sor_dp_drive(struct nvkm_ior *, int, int, int, int, int);
void gf119_sor_dp_vcpi(struct nvkm_ior *, int, u8, u8, u16, u16);
void gf119_sor_dp_audio(struct nvkm_ior *, int, bool);
void gf119_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32);
void gf119_sor_dp_watermark(struct nvkm_ior *, int, u8);
extern const struct nvkm_ior_func_hda gf119_sor_hda;
void gf119_sor_hda_hpd(struct nvkm_ior *, int, bool);
void gf119_sor_hda_eld(struct nvkm_ior *, int, u8 *, u8);
int gk104_sor_new(struct nvkm_disp *, int);
extern const struct nvkm_ior_func_hdmi gk104_sor_hdmi;
void gk104_sor_hdmi_ctrl(struct nvkm_ior *, int, bool, u8, u8);
void gk104_sor_hdmi_infoframe_avi(struct nvkm_ior *, int, void *, u32);
void gk104_sor_hdmi_infoframe_vsi(struct nvkm_ior *, int, void *, u32);
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 *, 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);
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);
extern const struct nvkm_ior_func_hda gv100_sor_hda;
void tu102_sor_dp_vcpi(struct nvkm_ior *, int, u8, u8, u16, u16);
int nv50_pior_cnt(struct nvkm_disp *, unsigned long *);
int nv50_pior_new(struct nvkm_disp *, int);
void nv50_pior_depth(struct nvkm_ior *, struct nvkm_ior_state *, u32 ctrl);
#define IOR_MSG(i,l,f,a...) do { \
struct nvkm_ior *_ior = (i); \
nvkm_##l(&_ior->disp->engine.subdev, "%s: "f"\n", _ior->name, ##a); \
} while(0)
#define IOR_WARN(i,f,a...) IOR_MSG((i), warn, f, ##a)
#define IOR_DBG(i,f,a...) IOR_MSG((i), debug, f, ##a)
#endif
@@ -0,0 +1,73 @@
/*
* Copyright 2017 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 "head.h"
#include "ior.h"
#include <nvif/class.h>
static const struct nvkm_ior_func
mcp77_sor = {
.state = g94_sor_state,
.power = nv50_sor_power,
.clock = nv50_sor_clock,
.bl = &nv50_sor_bl,
.hdmi = &g84_sor_hdmi,
.dp = &g94_sor_dp,
};
static int
mcp77_sor_new(struct nvkm_disp *disp, int id)
{
return nvkm_ior_new_(&mcp77_sor, disp, SOR, id, false);
}
static const struct nvkm_disp_func
mcp77_disp = {
.oneinit = nv50_disp_oneinit,
.init = nv50_disp_init,
.fini = nv50_disp_fini,
.intr = nv50_disp_intr,
.super = nv50_disp_super,
.uevent = &nv50_disp_chan_uevent,
.head = { .cnt = nv50_head_cnt, .new = nv50_head_new },
.dac = { .cnt = nv50_dac_cnt, .new = nv50_dac_new },
.sor = { .cnt = g94_sor_cnt, .new = mcp77_sor_new },
.pior = { .cnt = nv50_pior_cnt, .new = nv50_pior_new },
.root = { 0,0,GT206_DISP },
.user = {
{{0,0, G82_DISP_CURSOR }, nvkm_disp_chan_new, & nv50_disp_curs },
{{0,0, G82_DISP_OVERLAY }, nvkm_disp_chan_new, & nv50_disp_oimm },
{{0,0,GT200_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, & g84_disp_base },
{{0,0,GT206_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, & g94_disp_core },
{{0,0,GT200_DISP_OVERLAY_CHANNEL_DMA}, nvkm_disp_chan_new, &gt200_disp_ovly },
{}
},
};
int
mcp77_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&mcp77_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,87 @@
/*
* Copyright 2017 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 "head.h"
#include "ior.h"
#include <nvif/class.h>
static const struct nvkm_ior_func_dp
mcp89_sor_dp = {
.lanes = { 3, 2, 1, 0 },
.links = g94_sor_dp_links,
.power = g94_sor_dp_power,
.pattern = g94_sor_dp_pattern,
.drive = g94_sor_dp_drive,
.audio = gt215_sor_dp_audio,
.audio_sym = g94_sor_dp_audio_sym,
.activesym = g94_sor_dp_activesym,
.watermark = g94_sor_dp_watermark,
};
static const struct nvkm_ior_func
mcp89_sor = {
.state = g94_sor_state,
.power = nv50_sor_power,
.clock = nv50_sor_clock,
.bl = &gt215_sor_bl,
.hdmi = &gt215_sor_hdmi,
.dp = &mcp89_sor_dp,
.hda = &gt215_sor_hda,
};
static int
mcp89_sor_new(struct nvkm_disp *disp, int id)
{
return nvkm_ior_new_(&mcp89_sor, disp, SOR, id, true);
}
static const struct nvkm_disp_func
mcp89_disp = {
.oneinit = nv50_disp_oneinit,
.init = nv50_disp_init,
.fini = nv50_disp_fini,
.intr = nv50_disp_intr,
.super = nv50_disp_super,
.uevent = &nv50_disp_chan_uevent,
.head = { .cnt = nv50_head_cnt, .new = nv50_head_new },
.dac = { .cnt = nv50_dac_cnt, .new = nv50_dac_new },
.sor = { .cnt = g94_sor_cnt, .new = mcp89_sor_new },
.pior = { .cnt = nv50_pior_cnt, .new = nv50_pior_new },
.root = { 0,0,GT214_DISP },
.user = {
{{0,0,GT214_DISP_CURSOR }, nvkm_disp_chan_new, &nv50_disp_curs },
{{0,0,GT214_DISP_OVERLAY }, nvkm_disp_chan_new, &nv50_disp_oimm },
{{0,0,GT214_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, & g84_disp_base },
{{0,0,GT214_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, & g94_disp_core },
{{0,0,GT214_DISP_OVERLAY_CHANNEL_DMA}, nvkm_disp_chan_new, & g84_disp_ovly },
{}
},
};
int
mcp89_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&mcp89_disp, device, type, inst, pdisp);
}
@@ -0,0 +1,130 @@
/*
* Copyright 2012 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.
*
* Authors: Ben Skeggs
*/
#include "priv.h"
#include "head.h"
#include <nvif/class.h>
static void
nv04_head_vblank_put(struct nvkm_head *head)
{
struct nvkm_device *device = head->disp->engine.subdev.device;
nvkm_wr32(device, 0x600140 + (head->id * 0x2000) , 0x00000000);
}
static void
nv04_head_vblank_get(struct nvkm_head *head)
{
struct nvkm_device *device = head->disp->engine.subdev.device;
nvkm_wr32(device, 0x600140 + (head->id * 0x2000) , 0x00000001);
}
static void
nv04_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline)
{
struct nvkm_device *device = head->disp->engine.subdev.device;
u32 data = nvkm_rd32(device, 0x600868 + (head->id * 0x2000));
*hline = (data & 0xffff0000) >> 16;
*vline = (data & 0x0000ffff);
}
static void
nv04_head_state(struct nvkm_head *head, struct nvkm_head_state *state)
{
struct nvkm_device *device = head->disp->engine.subdev.device;
const u32 hoff = head->id * 0x0200;
state->vblanks = nvkm_rd32(device, 0x680800 + hoff) & 0x0000ffff;
state->vtotal = nvkm_rd32(device, 0x680804 + hoff) & 0x0000ffff;
state->vblanke = state->vtotal - 1;
state->hblanks = nvkm_rd32(device, 0x680820 + hoff) & 0x0000ffff;
state->htotal = nvkm_rd32(device, 0x680824 + hoff) & 0x0000ffff;
state->hblanke = state->htotal - 1;
}
static const struct nvkm_head_func
nv04_head = {
.state = nv04_head_state,
.rgpos = nv04_head_rgpos,
.vblank_get = nv04_head_vblank_get,
.vblank_put = nv04_head_vblank_put,
};
static int
nv04_head_new(struct nvkm_disp *disp, int id)
{
return nvkm_head_new_(&nv04_head, disp, id);
}
static void
nv04_disp_intr(struct nvkm_disp *disp)
{
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_device *device = subdev->device;
u32 crtc0 = nvkm_rd32(device, 0x600100);
u32 crtc1 = nvkm_rd32(device, 0x602100);
u32 pvideo;
if (crtc0 & 0x00000001) {
nvkm_disp_vblank(disp, 0);
nvkm_wr32(device, 0x600100, 0x00000001);
}
if (crtc1 & 0x00000001) {
nvkm_disp_vblank(disp, 1);
nvkm_wr32(device, 0x602100, 0x00000001);
}
if (device->chipset >= 0x10 && device->chipset <= 0x40) {
pvideo = nvkm_rd32(device, 0x8100);
if (pvideo & ~0x11)
nvkm_info(subdev, "PVIDEO intr: %08x\n", pvideo);
nvkm_wr32(device, 0x8100, pvideo);
}
}
static const struct nvkm_disp_func
nv04_disp = {
.intr = nv04_disp_intr,
.root = { 0, 0, NV04_DISP },
.user = { {} },
};
int
nv04_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
int ret, i;
ret = nvkm_disp_new_(&nv04_disp, device, type, inst, pdisp);
if (ret)
return ret;
for (i = 0; i < 2; i++) {
ret = nv04_head_new(*pdisp, i);
if (ret)
return ret;
}
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,423 @@
/*
* Copyright 2014 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.
*
* 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>
static void
nvkm_outp_route(struct nvkm_disp *disp)
{
struct nvkm_outp *outp;
struct nvkm_ior *ior;
list_for_each_entry(ior, &disp->iors, head) {
if ((outp = ior->arm.outp) && ior->arm.outp != ior->asy.outp) {
OUTP_DBG(outp, "release %s", ior->name);
if (ior->func->route.set)
ior->func->route.set(outp, NULL);
ior->arm.outp = NULL;
}
}
list_for_each_entry(ior, &disp->iors, head) {
if ((outp = ior->asy.outp)) {
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;
}
}
}
}
static enum nvkm_ior_proto
nvkm_outp_xlat(struct nvkm_outp *outp, enum nvkm_ior_type *type)
{
switch (outp->info.location) {
case 0:
switch (outp->info.type) {
case DCB_OUTPUT_ANALOG: *type = DAC; return CRT;
case DCB_OUTPUT_TV : *type = DAC; return TV;
case DCB_OUTPUT_TMDS : *type = SOR; return TMDS;
case DCB_OUTPUT_LVDS : *type = SOR; return LVDS;
case DCB_OUTPUT_DP : *type = SOR; return DP;
default:
break;
}
break;
case 1:
switch (outp->info.type) {
case DCB_OUTPUT_TMDS: *type = PIOR; return TMDS;
case DCB_OUTPUT_DP : *type = PIOR; return TMDS; /* not a bug */
default:
break;
}
break;
default:
break;
}
WARN_ON(1);
return UNKNOWN;
}
void
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) {
outp->ior->asy.outp = NULL;
outp->ior = NULL;
}
}
}
int
nvkm_outp_acquire_ior(struct nvkm_outp *outp, u8 user, struct nvkm_ior *ior)
{
outp->ior = ior;
outp->ior->asy.outp = outp;
outp->ior->asy.link = outp->info.sorconf.link;
outp->acquired |= user;
return 0;
}
static inline int
nvkm_outp_acquire_hda(struct nvkm_outp *outp, enum nvkm_ior_type type,
u8 user, bool hda)
{
struct nvkm_ior *ior;
/* Failing that, a completely unused OR is the next best thing. */
list_for_each_entry(ior, &outp->disp->iors, head) {
if (!ior->identity && ior->hda == hda &&
!ior->asy.outp && ior->type == type && !ior->arm.outp &&
(ior->func->route.set || ior->id == __ffs(outp->info.or)))
return nvkm_outp_acquire_ior(outp, user, ior);
}
/* Last resort is to assign an OR that's already active on HW,
* but will be released during the next modeset.
*/
list_for_each_entry(ior, &outp->disp->iors, head) {
if (!ior->identity && ior->hda == hda &&
!ior->asy.outp && ior->type == type &&
(ior->func->route.set || ior->id == __ffs(outp->info.or)))
return nvkm_outp_acquire_ior(outp, user, ior);
}
return -ENOSPC;
}
int
nvkm_outp_acquire_or(struct nvkm_outp *outp, u8 user, bool hda)
{
struct nvkm_ior *ior = outp->ior;
enum nvkm_ior_proto proto;
enum nvkm_ior_type type;
OUTP_TRACE(outp, "acquire %02x |= %02x %p", outp->acquired, user, ior);
if (ior) {
outp->acquired |= user;
return 0;
}
/* Lookup a compatible, and unused, OR to assign to the device. */
proto = nvkm_outp_xlat(outp, &type);
if (proto == UNKNOWN)
return -ENOSYS;
/* Deal with panels requiring identity-mapped SOR assignment. */
if (outp->identity) {
ior = nvkm_ior_find(outp->disp, SOR, ffs(outp->info.or) - 1);
if (WARN_ON(!ior))
return -ENOSPC;
return nvkm_outp_acquire_ior(outp, user, ior);
}
/* First preference is to reuse the OR that is currently armed
* on HW, if any, in order to prevent unnecessary switching.
*/
list_for_each_entry(ior, &outp->disp->iors, head) {
if (!ior->identity && !ior->asy.outp && ior->arm.outp == outp) {
/*XXX: For various complicated reasons, we can't outright switch
* the boot-time OR on the first modeset without some fairly
* invasive changes.
*
* The systems that were fixed by modifying the OR selection
* code to account for HDA support shouldn't regress here as
* the HDA-enabled ORs match the relevant output's pad macro
* index, and the firmware seems to select an OR this way.
*
* This warning is to make it obvious if that proves wrong.
*/
WARN_ON(hda && !ior->hda);
return nvkm_outp_acquire_ior(outp, user, ior);
}
}
/* If we don't need HDA, first try to acquire an OR that doesn't
* support it to leave free the ones that do.
*/
if (!hda) {
if (!nvkm_outp_acquire_hda(outp, type, user, false))
return 0;
/* Use a HDA-supporting SOR anyway. */
return nvkm_outp_acquire_hda(outp, type, user, true);
}
/* We want HDA, try to acquire an OR that supports it. */
if (!nvkm_outp_acquire_hda(outp, type, user, true))
return 0;
/* There weren't any free ORs that support HDA, grab one that
* doesn't and at least allow display to work still.
*/
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_release(struct nvkm_outp *outp)
{
nvkm_outp_release_or(outp, NVKM_OUTP_USER);
nvkm_outp_route(outp->disp);
}
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;
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 NULL;
ior = nvkm_ior_find(disp, type, -1);
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 NULL;
}
} else {
/* Prior to DCB 4.1, this is hardwired like so. */
id = ffs(outp->info.or) - 1;
link = (ior->type == SOR) ? outp->info.sorconf.link : 0;
}
ior = nvkm_ior_find(disp, type, id);
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);
if (!ior->arm.head || ior->arm.proto != proto) {
OUTP_DBG(outp, "no heads (%x %d %d)", ior->arm.head,
ior->arm.proto, proto);
/* The EFI GOP driver on Ampere can leave unused DP links routed,
* which we don't expect. The DisableLT IED script *should* get
* us back to where we need to be.
*/
if (ior->func->route.get && !ior->arm.head && outp->info.type == DCB_OUTPUT_DP)
nvkm_dp_disable(outp, ior);
return;
}
OUTP_DBG(outp, "on %s link %x", ior->name, ior->arm.link);
ior->arm.outp = outp;
}
void
nvkm_outp_del(struct nvkm_outp **poutp)
{
struct nvkm_outp *outp = *poutp;
if (outp && !WARN_ON(!outp->func)) {
if (outp->func->dtor)
*poutp = outp->func->dtor(outp);
kfree(*poutp);
*poutp = NULL;
}
}
int
nvkm_outp_new_(const struct nvkm_outp_func *func, struct nvkm_disp *disp,
int index, struct dcb_output *dcbE, struct nvkm_outp **poutp)
{
struct nvkm_i2c *i2c = disp->engine.subdev.device->i2c;
struct nvkm_outp *outp;
enum nvkm_ior_proto proto;
enum nvkm_ior_type type;
if (!(outp = *poutp = kzalloc_obj(*outp)))
return -ENOMEM;
outp->func = func;
outp->disp = disp;
outp->index = index;
outp->info = *dcbE;
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",
outp->info.type, outp->info.location, outp->info.or,
outp->info.type >= 2 ? outp->info.sorconf.link : 0,
outp->info.connector, outp->info.i2c_index,
outp->info.bus, outp->info.heads);
/* Cull output paths we can't map to an output resource. */
proto = nvkm_outp_xlat(outp, &type);
if (proto == UNKNOWN)
return -ENODEV;
return 0;
}
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
nvkm_outp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
struct nvkm_outp **poutp)
{
return nvkm_outp_new_(&nvkm_outp, disp, index, dcbE, poutp);
}
@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_DISP_OUTP_H__
#define __NVKM_DISP_OUTP_H__
#include "priv.h"
#include <drm/display/drm_dp.h>
#include <subdev/bios.h>
#include <subdev/bios/dcb.h>
#include <subdev/bios/dp.h>
struct nvkm_outp {
const struct nvkm_outp_func *func;
struct nvkm_disp *disp;
int index;
struct dcb_output info;
struct nvkm_i2c_bus *i2c;
struct list_head head;
struct nvkm_conn *conn;
bool identity;
/* Assembly state. */
#define NVKM_OUTP_PRIV 1
#define NVKM_OUTP_USER 2
u8 acquired:2;
struct nvkm_ior *ior;
union {
struct {
bool dual;
bool bpc8;
} lvds;
struct {
struct nvbios_dpout info;
u8 version;
bool mst;
bool increased_wm;
struct nvkm_i2c_aux *aux;
bool enabled;
bool aux_pwr;
bool aux_pwr_pu;
u8 lttpr[6];
u8 lttprs;
u8 dpcd[DP_RECEIVER_CAP_SIZE];
struct {
int dpcd; /* -1, or index into SUPPORTED_LINK_RATES table */
u32 rate;
} rate[8];
int rates;
struct mutex mutex;
struct {
u8 nr;
u8 bw;
bool mst;
bool post_adj;
} lt;
} dp;
};
struct nvkm_object object;
struct {
struct nvkm_head *head;
} asy;
};
int nvkm_outp_new_(const struct nvkm_outp_func *, struct nvkm_disp *, int index,
struct dcb_output *, struct nvkm_outp **);
int nvkm_outp_new(struct nvkm_disp *, int index, struct dcb_output *, struct nvkm_outp **);
void nvkm_outp_del(struct nvkm_outp **);
void nvkm_outp_init(struct nvkm_outp *);
void nvkm_outp_fini(struct nvkm_outp *);
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 (*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 *);
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 { \
struct nvkm_outp *_outp = (o); \
nvkm_##l(&_outp->disp->engine.subdev, "outp %02x:%04x:%04x: "f"\n", \
_outp->index, _outp->info.hasht, _outp->info.hashm, ##a); \
} while(0)
#define OUTP_ERR(o,f,a...) OUTP_MSG((o), error, f, ##a)
#define OUTP_DBG(o,f,a...) OUTP_MSG((o), debug, f, ##a)
#define OUTP_TRACE(o,f,a...) OUTP_MSG((o), trace, f, ##a)
#endif
@@ -0,0 +1,90 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_DISP_PRIV_H__
#define __NVKM_DISP_PRIV_H__
#define nvkm_udisp(p) container_of((p), struct nvkm_disp, client.object)
#include <engine/disp.h>
#include <core/enum.h>
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,
struct nvkm_disp **);
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 *, bool suspend);
void (*intr)(struct nvkm_disp *);
void (*intr_error)(struct nvkm_disp *, int chid);
void (*super)(struct work_struct *);
const struct nvkm_event_func *uevent;
struct {
int (*cnt)(struct nvkm_disp *, unsigned long *mask);
int (*new)(struct nvkm_disp *, int id);
} wndw, head, dac, sor, pior;
u16 ramht_size;
struct nvkm_sclass root;
struct nvkm_disp_user {
struct nvkm_sclass base;
int (*ctor)(const struct nvkm_oclass *, void *argv, u32 argc,
struct nvkm_object **);
const struct nvkm_disp_chan_user *chan;
} user[];
};
int nv50_disp_oneinit(struct nvkm_disp *);
int nv50_disp_init(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 *);
void nv50_disp_super_1(struct nvkm_disp *);
void nv50_disp_super_1_0(struct nvkm_disp *, struct nvkm_head *);
void nv50_disp_super_2_0(struct nvkm_disp *, struct nvkm_head *);
void nv50_disp_super_2_1(struct nvkm_disp *, struct nvkm_head *);
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 *, 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 *, 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 *);
int gv100_disp_caps_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **);
int tu102_disp_init(struct nvkm_disp *);
void nv50_disp_dptmds_war_2(struct nvkm_disp *, struct dcb_output *);
void nv50_disp_dptmds_war_3(struct nvkm_disp *, struct dcb_output *);
void nv50_disp_update_sppll1(struct nvkm_disp *);
extern const struct nvkm_event_func nv50_disp_chan_uevent;
void nv50_disp_chan_uevent_send(struct nvkm_disp *, int);
extern const struct nvkm_event_func gf119_disp_chan_uevent;
extern const struct nvkm_event_func gv100_disp_chan_uevent;
int nvkm_udisp_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **);
int nvkm_uconn_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **);
int nvkm_uoutp_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **);
int nvkm_uhead_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **);
#endif
@@ -0,0 +1,241 @@
/*
* Copyright 2018 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 "chan.h"
#include "priv.h"
#include "head.h"
#include "ior.h"
#include <core/gpuobj.h>
#include <subdev/gsp.h>
#include <subdev/timer.h>
#include <nvif/class.h>
void
tu102_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 hoff = head * 0x800;
nvkm_mask(device, 0x61657c + hoff, 0xffffffff, (aligned << 16) | pbn);
nvkm_mask(device, 0x616578 + hoff, 0x00003f3f, (slot_nr << 8) | slot);
}
static int
tu102_sor_dp_links(struct nvkm_ior *sor, struct nvkm_i2c_aux *aux)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
const u32 loff = nv50_sor_link(sor);
u32 dpctrl = 0x00000000;
u32 clksor = 0x00000000;
clksor |= sor->dp.bw << 18;
dpctrl |= ((1 << sor->dp.nr) - 1) << 16;
if (sor->dp.mst)
dpctrl |= 0x40000000;
if (sor->dp.ef)
dpctrl |= 0x00004000;
nvkm_mask(device, 0x612300 + soff, 0x007c0000, clksor);
/*XXX*/
nvkm_msec(device, 40, NVKM_DELAY);
nvkm_mask(device, 0x612300 + soff, 0x00030000, 0x00010000);
nvkm_mask(device, 0x61c10c + loff, 0x00000003, 0x00000001);
nvkm_mask(device, 0x61c10c + loff, 0x401f4000, dpctrl);
return 0;
}
static const struct nvkm_ior_func_dp
tu102_sor_dp = {
.lanes = { 0, 1, 2, 3 },
.links = tu102_sor_dp_links,
.power = g94_sor_dp_power,
.pattern = gm107_sor_dp_pattern,
.drive = gm200_sor_dp_drive,
.vcpi = tu102_sor_dp_vcpi,
.audio = gv100_sor_dp_audio,
.audio_sym = gv100_sor_dp_audio_sym,
.watermark = gv100_sor_dp_watermark,
};
static const struct nvkm_ior_func
tu102_sor = {
.route = {
.get = gm200_sor_route_get,
.set = gm200_sor_route_set,
},
.state = gv100_sor_state,
.power = nv50_sor_power,
.clock = gf119_sor_clock,
.bl = &gt215_sor_bl,
.hdmi = &gv100_sor_hdmi,
.dp = &tu102_sor_dp,
.hda = &gv100_sor_hda,
};
static int
tu102_sor_new(struct nvkm_disp *disp, int id)
{
struct nvkm_device *device = disp->engine.subdev.device;
u32 hda = nvkm_rd32(device, 0x08a15c);
return nvkm_ior_new_(&tu102_sor, disp, SOR, id, hda & BIT(id));
}
int
tu102_disp_init(struct nvkm_disp *disp)
{
struct nvkm_device *device = disp->engine.subdev.device;
struct nvkm_head *head;
int i, j;
u32 tmp;
/* Claim ownership of display. */
if (nvkm_rd32(device, 0x6254e8) & 0x00000002) {
nvkm_mask(device, 0x6254e8, 0x00000001, 0x00000000);
if (nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x6254e8) & 0x00000002))
break;
) < 0)
return -EBUSY;
}
/* Lock pin capabilities. */
tmp = 0x00000021; /*XXX*/
nvkm_wr32(device, 0x640008, tmp);
/* SOR capabilities. */
for (i = 0; i < disp->sor.nr; i++) {
tmp = nvkm_rd32(device, 0x61c000 + (i * 0x800));
nvkm_mask(device, 0x640000, 0x00000100 << i, 0x00000100 << i);
nvkm_wr32(device, 0x640144 + (i * 0x08), tmp);
}
/* Head capabilities. */
list_for_each_entry(head, &disp->heads, head) {
const int id = head->id;
/* RG. */
tmp = nvkm_rd32(device, 0x616300 + (id * 0x800));
nvkm_wr32(device, 0x640048 + (id * 0x020), tmp);
/* POSTCOMP. */
for (j = 0; j < 5 * 4; j += 4) {
tmp = nvkm_rd32(device, 0x616140 + (id * 0x800) + j);
nvkm_wr32(device, 0x640680 + (id * 0x20) + j, tmp);
}
}
/* Window capabilities. */
for (i = 0; i < disp->wndw.nr; i++) {
nvkm_mask(device, 0x640004, 1 << i, 1 << i);
for (j = 0; j < 6 * 4; j += 4) {
tmp = nvkm_rd32(device, 0x630100 + (i * 0x800) + j);
nvkm_mask(device, 0x640780 + (i * 0x20) + j, 0xffffffff, tmp);
}
nvkm_mask(device, 0x64000c, 0x00000100, 0x00000100);
}
/* IHUB capabilities. */
for (i = 0; i < 3; i++) {
tmp = nvkm_rd32(device, 0x62e000 + (i * 0x04));
nvkm_wr32(device, 0x640010 + (i * 0x04), tmp);
}
nvkm_mask(device, 0x610078, 0x00000001, 0x00000001);
/* Setup instance memory. */
switch (nvkm_memory_target(disp->inst->memory)) {
case NVKM_MEM_TARGET_VRAM: tmp = 0x00000001; break;
case NVKM_MEM_TARGET_NCOH: tmp = 0x00000002; break;
case NVKM_MEM_TARGET_HOST: tmp = 0x00000003; break;
default:
break;
}
nvkm_wr32(device, 0x610010, 0x00000008 | tmp);
nvkm_wr32(device, 0x610014, disp->inst->addr >> 16);
/* CTRL_DISP: AWAKEN, ERROR, SUPERVISOR[1-3]. */
nvkm_wr32(device, 0x611cf0, 0x00000187); /* MSK. */
nvkm_wr32(device, 0x611db0, 0x00000187); /* EN. */
/* EXC_OTHER: CURSn, CORE. */
nvkm_wr32(device, 0x611cec, disp->head.mask << 16 |
0x00000001); /* MSK. */
nvkm_wr32(device, 0x611dac, 0x00000000); /* EN. */
/* EXC_WINIM. */
nvkm_wr32(device, 0x611ce8, disp->wndw.mask); /* MSK. */
nvkm_wr32(device, 0x611da8, 0x00000000); /* EN. */
/* EXC_WIN. */
nvkm_wr32(device, 0x611ce4, disp->wndw.mask); /* MSK. */
nvkm_wr32(device, 0x611da4, 0x00000000); /* EN. */
/* HEAD_TIMING(n): VBLANK. */
list_for_each_entry(head, &disp->heads, head) {
const u32 hoff = head->id * 4;
nvkm_wr32(device, 0x611cc0 + hoff, 0x00000004); /* MSK. */
nvkm_wr32(device, 0x611d80 + hoff, 0x00000000); /* EN. */
}
/* OR. */
nvkm_wr32(device, 0x611cf4, 0x00000000); /* MSK. */
nvkm_wr32(device, 0x611db4, 0x00000000); /* EN. */
return 0;
}
static const struct nvkm_disp_func
tu102_disp = {
.oneinit = nv50_disp_oneinit,
.init = tu102_disp_init,
.fini = gv100_disp_fini,
.intr = gv100_disp_intr,
.super = gv100_disp_super,
.uevent = &gv100_disp_chan_uevent,
.wndw = { .cnt = gv100_disp_wndw_cnt },
.head = { .cnt = gv100_head_cnt, .new = gv100_head_new },
.sor = { .cnt = gv100_sor_cnt, .new = tu102_sor_new },
.ramht_size = 0x2000,
.root = { 0, 0,TU102_DISP },
.user = {
{{-1,-1,GV100_DISP_CAPS }, gv100_disp_caps_new },
{{ 0, 0,TU102_DISP_CURSOR }, nvkm_disp_chan_new, &gv100_disp_curs },
{{ 0, 0,TU102_DISP_WINDOW_IMM_CHANNEL_DMA}, nvkm_disp_wndw_new, &gv100_disp_wimm },
{{ 0, 0,TU102_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, &gv100_disp_core },
{{ 0, 0,TU102_DISP_WINDOW_CHANNEL_DMA }, nvkm_disp_wndw_new, &gv100_disp_wndw },
{}
},
};
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);
}
@@ -0,0 +1,258 @@
/*
* Copyright 2021 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.
*/
#define nvkm_uconn(p) container_of((p), struct nvkm_conn, object)
#include "conn.h"
#include "outp.h"
#include <core/client.h>
#include <core/event.h>
#include <subdev/gpio.h>
#include <subdev/i2c.h>
#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;
args.v0.version = 0;
args.v0.types = 0;
if (bits & NVKM_I2C_PLUG)
args.v0.types |= NVIF_CONN_EVENT_V0_PLUG;
if (bits & NVKM_I2C_UNPLUG)
args.v0.types |= NVIF_CONN_EVENT_V0_UNPLUG;
if (bits & NVKM_I2C_IRQ)
args.v0.types |= NVIF_CONN_EVENT_V0_IRQ;
return object->client->event(token, &args, sizeof(args.v0));
}
static int
nvkm_uconn_uevent_gpio(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_GPIO_HI)
args.v0.types |= NVIF_CONN_EVENT_V0_PLUG;
if (bits & NVKM_GPIO_LO)
args.v0.types |= NVIF_CONN_EVENT_V0_UNPLUG;
return object->client->event(token, &args, sizeof(args.v0));
}
static bool
nvkm_connector_is_dp_dms(u8 type)
{
switch (type) {
case DCB_CONNECTOR_DMS59_DP0:
case DCB_CONNECTOR_DMS59_DP1:
return true;
default:
return false;
}
}
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_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 (!disp->rm.client.gsp && conn->info.hpd == DCB_GPIO_UNUSED)
return -ENOSYS;
return 0;
}
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
list_for_each_entry(outp, &conn->disp->outps, head) {
if (outp->info.connector == conn->index)
break;
}
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;
if (args->v0.types & NVIF_CONN_EVENT_V0_IRQ ) bits |= NVKM_I2C_IRQ;
return nvkm_uevent_add(uevent, &device->i2c->event, outp->dp.aux->id, bits,
nvkm_uconn_uevent_aux);
}
if (args->v0.types & NVIF_CONN_EVENT_V0_PLUG ) bits |= NVKM_GPIO_HI;
if (args->v0.types & NVIF_CONN_EVENT_V0_UNPLUG) bits |= NVKM_GPIO_LO;
if (args->v0.types & NVIF_CONN_EVENT_V0_IRQ) {
/* TODO: support DP IRQ on ANX9805 and remove this hack. */
if (!outp->info.location && !nvkm_connector_is_dp_dms(conn->info.type))
return -EINVAL;
}
return nvkm_uevent_add(uevent, &device->gpio->event, conn->info.hpd, bits,
nvkm_uconn_uevent_gpio);
}
static void *
nvkm_uconn_dtor(struct nvkm_object *object)
{
struct nvkm_conn *conn = nvkm_uconn(object);
struct nvkm_disp *disp = conn->disp;
spin_lock(&disp->client.lock);
conn->object.func = NULL;
spin_unlock(&disp->client.lock);
return NULL;
}
static const struct nvkm_object_func
nvkm_uconn = {
.dtor = nvkm_uconn_dtor,
.uevent = nvkm_uconn_uevent,
};
int
nvkm_uconn_new(const struct nvkm_oclass *oclass, void *argv, u32 argc, struct nvkm_object **pobject)
{
struct nvkm_disp *disp = nvkm_udisp(oclass->parent);
struct nvkm_conn *cont, *conn = NULL;
union nvif_conn_args *args = argv;
int ret;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
list_for_each_entry(cont, &disp->conns, head) {
if (cont->index == args->v0.id) {
conn = cont;
break;
}
}
if (!conn)
return -EINVAL;
ret = -EBUSY;
spin_lock(&disp->client.lock);
if (!conn->object.func) {
switch (conn->info.type) {
/* VGA */
case DCB_CONNECTOR_DVI_A :
case DCB_CONNECTOR_POD_VGA :
case DCB_CONNECTOR_VGA : args->v0.type = NVIF_CONN_V0_VGA; break;
/* TV */
case DCB_CONNECTOR_TV_0 :
case DCB_CONNECTOR_TV_1 :
case DCB_CONNECTOR_TV_2 :
case DCB_CONNECTOR_TV_SCART :
case DCB_CONNECTOR_TV_SCART_D :
case DCB_CONNECTOR_TV_DTERM :
case DCB_CONNECTOR_POD_TV_3 :
case DCB_CONNECTOR_POD_TV_1 :
case DCB_CONNECTOR_POD_TV_0 :
case DCB_CONNECTOR_TV_3 : args->v0.type = NVIF_CONN_V0_TV; break;
/* DVI */
case DCB_CONNECTOR_DVI_I_TV_1 :
case DCB_CONNECTOR_DVI_I_TV_0 :
case DCB_CONNECTOR_DVI_I_TV_2 :
case DCB_CONNECTOR_DVI_ADC :
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_TMDS :
case DCB_CONNECTOR_DVI_D : args->v0.type = NVIF_CONN_V0_DVI_D; break;
/* LVDS */
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;
/* DP */
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;
/* HDMI */
case DCB_CONNECTOR_HDMI_0 :
case DCB_CONNECTOR_HDMI_1 :
case DCB_CONNECTOR_HDMI_C : args->v0.type = NVIF_CONN_V0_HDMI; break;
/*
* Dock & unused outputs.
* BNC, SPDIF, WFD, and detached LVDS go here.
*/
default:
nvkm_warn(&disp->engine.subdev,
"unimplemented connector type 0x%02x\n",
conn->info.type);
args->v0.type = NVIF_CONN_V0_VGA;
ret = -EINVAL;
break;
}
nvkm_object_ctor(&nvkm_uconn, oclass, &conn->object);
*pobject = &conn->object;
ret = 0;
}
spin_unlock(&disp->client.lock);
return ret;
}
@@ -0,0 +1,114 @@
/*
* Copyright 2021 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 "conn.h"
#include "head.h"
#include "outp.h"
#include <nvif/class.h>
#include <nvif/if0010.h>
static int
nvkm_udisp_sclass(struct nvkm_object *object, int index, struct nvkm_oclass *sclass)
{
struct nvkm_disp *disp = nvkm_udisp(object);
if (index-- == 0) {
sclass->base = (struct nvkm_sclass) { 0, 0, NVIF_CLASS_CONN };
sclass->ctor = nvkm_uconn_new;
return 0;
}
if (index-- == 0) {
sclass->base = (struct nvkm_sclass) { 0, 0, NVIF_CLASS_OUTP };
sclass->ctor = nvkm_uoutp_new;
return 0;
}
if (index-- == 0) {
sclass->base = (struct nvkm_sclass) { 0, 0, NVIF_CLASS_HEAD };
sclass->ctor = nvkm_uhead_new;
return 0;
}
if (disp->func->user[index].ctor) {
sclass->base = disp->func->user[index].base;
sclass->ctor = disp->func->user[index].ctor;
return 0;
}
return -EINVAL;
}
static void *
nvkm_udisp_dtor(struct nvkm_object *object)
{
struct nvkm_disp *disp = nvkm_udisp(object);
spin_lock(&disp->client.lock);
if (object == &disp->client.object)
disp->client.object.func = NULL;
spin_unlock(&disp->client.lock);
return NULL;
}
static const struct nvkm_object_func
nvkm_udisp = {
.dtor = nvkm_udisp_dtor,
.sclass = nvkm_udisp_sclass,
};
int
nvkm_udisp_new(const struct nvkm_oclass *oclass, void *argv, u32 argc, struct nvkm_object **pobject)
{
struct nvkm_disp *disp = nvkm_disp(oclass->engine);
struct nvkm_conn *conn;
struct nvkm_outp *outp;
struct nvkm_head *head;
union nvif_disp_args *args = argv;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
spin_lock(&disp->client.lock);
if (disp->client.object.func) {
spin_unlock(&disp->client.lock);
return -EBUSY;
}
nvkm_object_ctor(&nvkm_udisp, oclass, &disp->client.object);
*pobject = &disp->client.object;
spin_unlock(&disp->client.lock);
args->v0.conn_mask = 0;
list_for_each_entry(conn, &disp->conns, head)
args->v0.conn_mask |= BIT(conn->index);
args->v0.outp_mask = 0;
list_for_each_entry(outp, &disp->outps, head)
args->v0.outp_mask |= BIT(outp->index);
args->v0.head_mask = 0;
list_for_each_entry(head, &disp->heads, head)
args->v0.head_mask |= BIT(head->id);
return 0;
}
@@ -0,0 +1,127 @@
/*
* Copyright 2021 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.
*/
#define nvkm_uhead(p) container_of((p), struct nvkm_head, object)
#include "head.h"
#include <core/event.h>
#include <nvif/if0013.h>
#include <nvif/event.h>
static int
nvkm_uhead_uevent(struct nvkm_object *object, void *argv, u32 argc, struct nvkm_uevent *uevent)
{
struct nvkm_head *head = nvkm_uhead(object);
union nvif_head_event_args *args = argv;
if (!uevent)
return 0;
if (argc != sizeof(args->vn))
return -ENOSYS;
return nvkm_uevent_add(uevent, &head->disp->vblank, head->id,
NVKM_DISP_HEAD_EVENT_VBLANK, NULL);
}
static int
nvkm_uhead_mthd_scanoutpos(struct nvkm_head *head, void *argv, u32 argc)
{
union nvif_head_scanoutpos_args *args = argv;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
head->func->state(head, &head->arm);
args->v0.vtotal = head->arm.vtotal;
args->v0.vblanks = head->arm.vblanks;
args->v0.vblanke = head->arm.vblanke;
args->v0.htotal = head->arm.htotal;
args->v0.hblanks = head->arm.hblanks;
args->v0.hblanke = head->arm.hblanke;
/* We don't support reading htotal/vtotal on pre-NV50 VGA,
* so we have to give up and trigger the timestamping
* fallback in the drm core.
*/
if (!args->v0.vtotal || !args->v0.htotal)
return -ENOTSUPP;
args->v0.time[0] = ktime_to_ns(ktime_get());
head->func->rgpos(head, &args->v0.hline, &args->v0.vline);
args->v0.time[1] = ktime_to_ns(ktime_get());
return 0;
}
static int
nvkm_uhead_mthd(struct nvkm_object *object, u32 mthd, void *argv, u32 argc)
{
struct nvkm_head *head = nvkm_uhead(object);
switch (mthd) {
case NVIF_HEAD_V0_SCANOUTPOS: return nvkm_uhead_mthd_scanoutpos(head, argv, argc);
default:
return -EINVAL;
}
}
static void *
nvkm_uhead_dtor(struct nvkm_object *object)
{
struct nvkm_head *head = nvkm_uhead(object);
struct nvkm_disp *disp = head->disp;
spin_lock(&disp->client.lock);
head->object.func = NULL;
spin_unlock(&disp->client.lock);
return NULL;
}
static const struct nvkm_object_func
nvkm_uhead = {
.dtor = nvkm_uhead_dtor,
.mthd = nvkm_uhead_mthd,
.uevent = nvkm_uhead_uevent,
};
int
nvkm_uhead_new(const struct nvkm_oclass *oclass, void *argv, u32 argc, struct nvkm_object **pobject)
{
struct nvkm_disp *disp = nvkm_udisp(oclass->parent);
struct nvkm_head *head;
union nvif_head_args *args = argv;
int ret;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
if (!(head = nvkm_head_find(disp, args->v0.id)))
return -EINVAL;
ret = -EBUSY;
spin_lock(&disp->client.lock);
if (!head->object.func) {
nvkm_object_ctor(&nvkm_uhead, oclass, &head->object);
*pobject = &head->object;
ret = 0;
}
spin_unlock(&disp->client.lock);
return ret;
}
@@ -0,0 +1,665 @@
/*
* Copyright 2021 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.
*/
#define nvkm_uoutp(p) container_of((p), struct nvkm_outp, object)
#include "outp.h"
#include "dp.h"
#include "head.h"
#include "ior.h"
#include <subdev/i2c.h>
#include <nvif/if0012.h>
static int
nvkm_uoutp_mthd_dp_mst_vcpi(struct nvkm_outp *outp, void *argv, u32 argc)
{
struct nvkm_ior *ior = outp->ior;
union nvif_outp_dp_mst_vcpi_args *args = argv;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
if (!ior->func->dp || !ior->func->dp->vcpi || !nvkm_head_find(outp->disp, args->v0.head))
return -EINVAL;
ior->func->dp->vcpi(ior, args->v0.head, args->v0.start_slot, args->v0.num_slots,
args->v0.pbn, args->v0.aligned_pbn);
return 0;
}
static int
nvkm_uoutp_mthd_dp_mst_id_put(struct nvkm_outp *outp, void *argv, u32 argc)
{
union nvif_outp_dp_mst_id_put_args *args = argv;
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 (!ior->func->dp || !nvkm_head_find(disp, args->v0.head))
return -EINVAL;
if (!ior->func->dp->sst)
return 0;
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
nvkm_uoutp_mthd_dp_aux_pwr(struct nvkm_outp *outp, void *argv, u32 argc)
{
union nvif_outp_dp_aux_pwr_args *args = argv;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
if (!outp->func->dp.aux_pwr)
return -EINVAL;
return outp->func->dp.aux_pwr(outp, !!args->v0.state);
}
static int
nvkm_uoutp_mthd_hda_eld(struct nvkm_outp *outp, void *argv, u32 argc)
{
struct nvkm_ior *ior = outp->ior;
union nvif_outp_hda_eld_args *args = argv;
if (argc < sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
argc -= sizeof(args->v0);
if (!ior->hda || !nvkm_head_find(outp->disp, args->v0.head))
return -EINVAL;
if (argc > 0x60)
return -E2BIG;
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);
else
if (ior->func->hdmi->audio)
ior->func->hdmi->audio(ior, args->v0.head, false);
}
return 0;
}
static int
nvkm_uoutp_mthd_infoframe(struct nvkm_outp *outp, void *argv, u32 argc)
{
struct nvkm_ior *ior = outp->ior;
union nvif_outp_infoframe_args *args = argv;
ssize_t size = argc - sizeof(*args);
if (argc < sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
if (!nvkm_head_find(outp->disp, args->v0.head))
return -EINVAL;
switch (ior->func->hdmi ? args->v0.type : 0xff) {
case NVIF_OUTP_INFOFRAME_V0_AVI:
ior->func->hdmi->infoframe_avi(ior, args->v0.head, &args->v0.data, size);
return 0;
case NVIF_OUTP_INFOFRAME_V0_VSI:
ior->func->hdmi->infoframe_vsi(ior, args->v0.head, &args->v0.data, size);
return 0;
default:
break;
}
return -EINVAL;
}
static int
nvkm_uoutp_mthd_hdmi(struct nvkm_outp *outp, void *argv, u32 argc)
{
union nvif_outp_hdmi_args *args = argv;
struct nvkm_ior *ior = outp->ior;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
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;
}
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_lvds(struct nvkm_outp *outp, void *argv, u32 argc)
{
union nvif_outp_lvds_args *args = argv;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
if (outp->info.type != DCB_OUTPUT_LVDS)
return -EINVAL;
outp->lvds.dual = !!args->v0.dual;
outp->lvds.bpc8 = !!args->v0.bpc8;
return 0;
}
static int
nvkm_uoutp_mthd_bl_set(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;
if (outp->func->bl.set)
ret = outp->func->bl.set(outp, args->v0.level);
else
ret = -EINVAL;
return ret;
}
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;
if (outp->func->bl.get) {
ret = outp->func->bl.get(outp);
if (ret >= 0) {
args->v0.level = ret;
ret = 0;
}
} else {
ret = -EINVAL;
}
return ret;
}
static int
nvkm_uoutp_mthd_release(struct nvkm_outp *outp, void *argv, u32 argc)
{
union nvif_outp_release_args *args = argv;
if (argc != sizeof(args->vn))
return -ENOSYS;
if (!outp->ior)
return -EINVAL;
outp->func->release(outp);
return 0;
}
static int
nvkm_uoutp_mthd_acquire(struct nvkm_outp *outp, void *argv, u32 argc)
{
union nvif_outp_acquire_args *args = argv;
int ret;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
if (outp->ior && args->v0.type <= NVIF_OUTP_ACQUIRE_V0_PIOR)
return -EBUSY;
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_SOR:
ret = outp->func->acquire(outp, args->v0.sor.hda);
break;
default:
ret = -EINVAL;
break;
}
if (ret)
return ret;
args->v0.or = outp->ior->id;
args->v0.link = outp->ior->asy.link;
return 0;
}
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;
int ret;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
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);
args->v0.load = ret < 0 ? 0 : ret;
} else {
ret = -EINVAL;
}
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_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;
}
return -EINVAL;
}
static int
nvkm_uoutp_mthd_noacquire(struct nvkm_outp *outp, u32 mthd, void *argv, u32 argc, bool *invalid)
{
switch (mthd) {
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;
}
*invalid = true;
return 0;
}
static int
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, &invalid);
if (!invalid)
goto done;
if (outp->ior)
ret = nvkm_uoutp_mthd_acquired(outp, mthd, argv, argc);
else
ret = -EIO;
done:
mutex_unlock(&disp->super.mutex);
return ret;
}
static void *
nvkm_uoutp_dtor(struct nvkm_object *object)
{
struct nvkm_outp *outp = nvkm_uoutp(object);
struct nvkm_disp *disp = outp->disp;
spin_lock(&disp->client.lock);
outp->object.func = NULL;
spin_unlock(&disp->client.lock);
return NULL;
}
static const struct nvkm_object_func
nvkm_uoutp = {
.dtor = nvkm_uoutp_dtor,
.mthd = nvkm_uoutp_mthd,
};
int
nvkm_uoutp_new(const struct nvkm_oclass *oclass, void *argv, u32 argc, struct nvkm_object **pobject)
{
struct nvkm_disp *disp = nvkm_udisp(oclass->parent);
struct nvkm_outp *outt, *outp = NULL;
union nvif_outp_args *args = argv;
int ret;
if (argc != sizeof(args->v0) || args->v0.version != 0)
return -ENOSYS;
list_for_each_entry(outt, &disp->outps, head) {
if (outt->index == args->v0.id) {
outp = outt;
break;
}
}
if (!outp)
return -EINVAL;
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;
}
@@ -0,0 +1,205 @@
/*
* Copyright 2012 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.
*
* Authors: Ben Skeggs
*/
#include <subdev/vga.h>
u8
nvkm_rdport(struct nvkm_device *device, int head, u16 port)
{
if (device->card_type >= NV_50)
return nvkm_rd08(device, 0x601000 + port);
if (port == 0x03c0 || port == 0x03c1 || /* AR */
port == 0x03c2 || port == 0x03da || /* INP0 */
port == 0x03d4 || port == 0x03d5) /* CR */
return nvkm_rd08(device, 0x601000 + (head * 0x2000) + port);
if (port == 0x03c2 || port == 0x03cc || /* MISC */
port == 0x03c4 || port == 0x03c5 || /* SR */
port == 0x03ce || port == 0x03cf) { /* GR */
if (device->card_type < NV_40)
head = 0; /* CR44 selects head */
return nvkm_rd08(device, 0x0c0000 + (head * 0x2000) + port);
}
return 0x00;
}
void
nvkm_wrport(struct nvkm_device *device, int head, u16 port, u8 data)
{
if (device->card_type >= NV_50)
nvkm_wr08(device, 0x601000 + port, data);
else
if (port == 0x03c0 || port == 0x03c1 || /* AR */
port == 0x03c2 || port == 0x03da || /* INP0 */
port == 0x03d4 || port == 0x03d5) /* CR */
nvkm_wr08(device, 0x601000 + (head * 0x2000) + port, data);
else
if (port == 0x03c2 || port == 0x03cc || /* MISC */
port == 0x03c4 || port == 0x03c5 || /* SR */
port == 0x03ce || port == 0x03cf) { /* GR */
if (device->card_type < NV_40)
head = 0; /* CR44 selects head */
nvkm_wr08(device, 0x0c0000 + (head * 0x2000) + port, data);
}
}
u8
nvkm_rdvgas(struct nvkm_device *device, int head, u8 index)
{
nvkm_wrport(device, head, 0x03c4, index);
return nvkm_rdport(device, head, 0x03c5);
}
void
nvkm_wrvgas(struct nvkm_device *device, int head, u8 index, u8 value)
{
nvkm_wrport(device, head, 0x03c4, index);
nvkm_wrport(device, head, 0x03c5, value);
}
u8
nvkm_rdvgag(struct nvkm_device *device, int head, u8 index)
{
nvkm_wrport(device, head, 0x03ce, index);
return nvkm_rdport(device, head, 0x03cf);
}
void
nvkm_wrvgag(struct nvkm_device *device, int head, u8 index, u8 value)
{
nvkm_wrport(device, head, 0x03ce, index);
nvkm_wrport(device, head, 0x03cf, value);
}
u8
nvkm_rdvgac(struct nvkm_device *device, int head, u8 index)
{
nvkm_wrport(device, head, 0x03d4, index);
return nvkm_rdport(device, head, 0x03d5);
}
void
nvkm_wrvgac(struct nvkm_device *device, int head, u8 index, u8 value)
{
nvkm_wrport(device, head, 0x03d4, index);
nvkm_wrport(device, head, 0x03d5, value);
}
u8
nvkm_rdvgai(struct nvkm_device *device, int head, u16 port, u8 index)
{
if (port == 0x03c4) return nvkm_rdvgas(device, head, index);
if (port == 0x03ce) return nvkm_rdvgag(device, head, index);
if (port == 0x03d4) return nvkm_rdvgac(device, head, index);
return 0x00;
}
void
nvkm_wrvgai(struct nvkm_device *device, int head, u16 port, u8 index, u8 value)
{
if (port == 0x03c4) nvkm_wrvgas(device, head, index, value);
else if (port == 0x03ce) nvkm_wrvgag(device, head, index, value);
else if (port == 0x03d4) nvkm_wrvgac(device, head, index, value);
}
bool
nvkm_lockvgac(struct nvkm_device *device, bool lock)
{
bool locked = !nvkm_rdvgac(device, 0, 0x1f);
u8 data = lock ? 0x99 : 0x57;
if (device->card_type < NV_50)
nvkm_wrvgac(device, 0, 0x1f, data);
else
nvkm_wrvgac(device, 0, 0x3f, data);
if (device->chipset == 0x11) {
if (!(nvkm_rd32(device, 0x001084) & 0x10000000))
nvkm_wrvgac(device, 1, 0x1f, data);
}
return locked;
}
/* CR44 takes values 0 (head A), 3 (head B) and 4 (heads tied)
* it affects only the 8 bit vga io regs, which we access using mmio at
* 0xc{0,2}3c*, 0x60{1,3}3*, and 0x68{1,3}3d*
* in general, the set value of cr44 does not matter: reg access works as
* expected and values can be set for the appropriate head by using a 0x2000
* offset as required
* however:
* a) pre nv40, the head B range of PRMVIO regs at 0xc23c* was not exposed and
* cr44 must be set to 0 or 3 for accessing values on the correct head
* through the common 0xc03c* addresses
* b) in tied mode (4) head B is programmed to the values set on head A, and
* access using the head B addresses can have strange results, ergo we leave
* tied mode in init once we know to what cr44 should be restored on exit
*
* the owner parameter is slightly abused:
* 0 and 1 are treated as head values and so the set value is (owner * 3)
* other values are treated as literal values to set
*/
u8
nvkm_rdvgaowner(struct nvkm_device *device)
{
if (device->card_type < NV_50) {
if (device->chipset == 0x11) {
u32 tied = nvkm_rd32(device, 0x001084) & 0x10000000;
if (tied == 0) {
u8 slA = nvkm_rdvgac(device, 0, 0x28) & 0x80;
u8 tvA = nvkm_rdvgac(device, 0, 0x33) & 0x01;
u8 slB = nvkm_rdvgac(device, 1, 0x28) & 0x80;
u8 tvB = nvkm_rdvgac(device, 1, 0x33) & 0x01;
if (slA && !tvA) return 0x00;
if (slB && !tvB) return 0x03;
if (slA) return 0x00;
if (slB) return 0x03;
return 0x00;
}
return 0x04;
}
return nvkm_rdvgac(device, 0, 0x44);
}
return 0x00;
}
void
nvkm_wrvgaowner(struct nvkm_device *device, u8 select)
{
if (device->card_type < NV_50) {
u8 owner = (select == 1) ? 3 : select;
if (device->chipset == 0x11) {
/* workaround hw lockup bug */
nvkm_rdvgac(device, 0, 0x1f);
nvkm_rdvgac(device, 1, 0x1f);
}
nvkm_wrvgac(device, 0, 0x44, owner);
if (device->chipset == 0x11) {
nvkm_wrvgac(device, 0, 0x2e, owner);
nvkm_wrvgac(device, 0, 0x2e, owner);
}
}
}