linux/drivers/gpu/drm/nouveau/nouveau_dp.c
Ben Skeggs 6ee738610f drm/nouveau: Add DRM driver for NVIDIA GPUs
This adds a drm/kms staging non-API stable driver for GPUs from NVIDIA.

This driver is a KMS-based driver and requires a compatible nouveau
userspace libdrm and nouveau X.org driver.

This driver requires firmware files not available in this kernel tree,
interested parties can find them via the nouveau project git archive.

This driver is reverse engineered, and is in no way supported by nVidia.

Support for nearly the complete range of nvidia hw from nv04->g80 (nv50)
is available, and the kms driver should support driving nearly all
output types (displayport is under development still) along with supporting
suspend/resume.

This work is all from the upstream nouveau project found at
nouveau.freedesktop.org.

The original authors list from nouveau git tree is:
Anssi Hannula <anssi.hannula@iki.fi>
Ben Skeggs <bskeggs@redhat.com>
Francisco Jerez <currojerez@riseup.net>
Maarten Maathuis <madman2003@gmail.com>
Marcin Kościelnicki <koriakin@0x04.net>
Matthew Garrett <mjg@redhat.com>
Matt Parnell <mparnell@gmail.com>
Patrice Mandin <patmandin@gmail.com>
Pekka Paalanen <pq@iki.fi>
Xavier Chantry <shiningxc@gmail.com>
along with project founder Stephane Marchesin <marchesin@icps.u-strasbg.fr>

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
2009-12-11 21:29:34 +10:00

570 lines
15 KiB
C

/*
* Copyright 2009 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 "drmP.h"
#include "nouveau_drv.h"
#include "nouveau_i2c.h"
#include "nouveau_encoder.h"
static int
auxch_rd(struct drm_encoder *encoder, int address, uint8_t *buf, int size)
{
struct drm_device *dev = encoder->dev;
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nouveau_i2c_chan *auxch;
int ret;
auxch = nouveau_i2c_find(dev, nv_encoder->dcb->i2c_index);
if (!auxch)
return -ENODEV;
ret = nouveau_dp_auxch(auxch, 9, address, buf, size);
if (ret)
return ret;
return 0;
}
static int
auxch_wr(struct drm_encoder *encoder, int address, uint8_t *buf, int size)
{
struct drm_device *dev = encoder->dev;
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nouveau_i2c_chan *auxch;
int ret;
auxch = nouveau_i2c_find(dev, nv_encoder->dcb->i2c_index);
if (!auxch)
return -ENODEV;
ret = nouveau_dp_auxch(auxch, 8, address, buf, size);
return ret;
}
static int
nouveau_dp_lane_count_set(struct drm_encoder *encoder, uint8_t cmd)
{
struct drm_device *dev = encoder->dev;
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
uint32_t tmp;
int or = nv_encoder->or, link = !(nv_encoder->dcb->sorconf.link & 1);
tmp = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link));
tmp &= ~(NV50_SOR_DP_CTRL_ENHANCED_FRAME_ENABLED |
NV50_SOR_DP_CTRL_LANE_MASK);
tmp |= ((1 << (cmd & DP_LANE_COUNT_MASK)) - 1) << 16;
if (cmd & DP_LANE_COUNT_ENHANCED_FRAME_EN)
tmp |= NV50_SOR_DP_CTRL_ENHANCED_FRAME_ENABLED;
nv_wr32(dev, NV50_SOR_DP_CTRL(or, link), tmp);
return auxch_wr(encoder, DP_LANE_COUNT_SET, &cmd, 1);
}
static int
nouveau_dp_link_bw_set(struct drm_encoder *encoder, uint8_t cmd)
{
struct drm_device *dev = encoder->dev;
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
uint32_t tmp;
int reg = 0x614300 + (nv_encoder->or * 0x800);
tmp = nv_rd32(dev, reg);
tmp &= 0xfff3ffff;
if (cmd == DP_LINK_BW_2_7)
tmp |= 0x00040000;
nv_wr32(dev, reg, tmp);
return auxch_wr(encoder, DP_LINK_BW_SET, &cmd, 1);
}
static int
nouveau_dp_link_train_set(struct drm_encoder *encoder, int pattern)
{
struct drm_device *dev = encoder->dev;
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
uint32_t tmp;
uint8_t cmd;
int or = nv_encoder->or, link = !(nv_encoder->dcb->sorconf.link & 1);
int ret;
tmp = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link));
tmp &= ~NV50_SOR_DP_CTRL_TRAINING_PATTERN;
tmp |= (pattern << 24);
nv_wr32(dev, NV50_SOR_DP_CTRL(or, link), tmp);
ret = auxch_rd(encoder, DP_TRAINING_PATTERN_SET, &cmd, 1);
if (ret)
return ret;
cmd &= ~DP_TRAINING_PATTERN_MASK;
cmd |= (pattern & DP_TRAINING_PATTERN_MASK);
return auxch_wr(encoder, DP_TRAINING_PATTERN_SET, &cmd, 1);
}
static int
nouveau_dp_max_voltage_swing(struct drm_encoder *encoder)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct drm_device *dev = encoder->dev;
struct bit_displayport_encoder_table_entry *dpse;
struct bit_displayport_encoder_table *dpe;
int i, dpe_headerlen, max_vs = 0;
dpe = nouveau_bios_dp_table(dev, nv_encoder->dcb, &dpe_headerlen);
if (!dpe)
return false;
dpse = (void *)((char *)dpe + dpe_headerlen);
for (i = 0; i < dpe_headerlen; i++, dpse++) {
if (dpse->vs_level > max_vs)
max_vs = dpse->vs_level;
}
return max_vs;
}
static int
nouveau_dp_max_pre_emphasis(struct drm_encoder *encoder, int vs)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct drm_device *dev = encoder->dev;
struct bit_displayport_encoder_table_entry *dpse;
struct bit_displayport_encoder_table *dpe;
int i, dpe_headerlen, max_pre = 0;
dpe = nouveau_bios_dp_table(dev, nv_encoder->dcb, &dpe_headerlen);
if (!dpe)
return false;
dpse = (void *)((char *)dpe + dpe_headerlen);
for (i = 0; i < dpe_headerlen; i++, dpse++) {
if (dpse->vs_level != vs)
continue;
if (dpse->pre_level > max_pre)
max_pre = dpse->pre_level;
}
return max_pre;
}
static bool
nouveau_dp_link_train_adjust(struct drm_encoder *encoder, uint8_t *config)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct drm_device *dev = encoder->dev;
struct bit_displayport_encoder_table_entry *dpse;
struct bit_displayport_encoder_table *dpe;
int ret, i, dpe_headerlen, vs = 0, pre = 0;
uint8_t request[2];
dpe = nouveau_bios_dp_table(dev, nv_encoder->dcb, &dpe_headerlen);
if (!dpe)
return false;
dpse = (void *)((char *)dpe + dpe_headerlen);
ret = auxch_rd(encoder, DP_ADJUST_REQUEST_LANE0_1, request, 2);
if (ret)
return false;
NV_DEBUG(dev, "\t\tadjust 0x%02x 0x%02x\n", request[0], request[1]);
/* Keep all lanes at the same level.. */
for (i = 0; i < nv_encoder->dp.link_nr; i++) {
int lane_req = (request[i >> 1] >> ((i & 1) << 2)) & 0xf;
int lane_vs = lane_req & 3;
int lane_pre = (lane_req >> 2) & 3;
if (lane_vs > vs)
vs = lane_vs;
if (lane_pre > pre)
pre = lane_pre;
}
if (vs >= nouveau_dp_max_voltage_swing(encoder)) {
vs = nouveau_dp_max_voltage_swing(encoder);
vs |= 4;
}
if (pre >= nouveau_dp_max_pre_emphasis(encoder, vs & 3)) {
pre = nouveau_dp_max_pre_emphasis(encoder, vs & 3);
pre |= 4;
}
/* Update the configuration for all lanes.. */
for (i = 0; i < nv_encoder->dp.link_nr; i++)
config[i] = (pre << 3) | vs;
return true;
}
static bool
nouveau_dp_link_train_commit(struct drm_encoder *encoder, uint8_t *config)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct drm_device *dev = encoder->dev;
struct bit_displayport_encoder_table_entry *dpse;
struct bit_displayport_encoder_table *dpe;
int or = nv_encoder->or, link = !(nv_encoder->dcb->sorconf.link & 1);
int dpe_headerlen, ret, i;
NV_DEBUG(dev, "\t\tconfig 0x%02x 0x%02x 0x%02x 0x%02x\n",
config[0], config[1], config[2], config[3]);
dpe = nouveau_bios_dp_table(dev, nv_encoder->dcb, &dpe_headerlen);
if (!dpe)
return false;
dpse = (void *)((char *)dpe + dpe_headerlen);
for (i = 0; i < dpe->record_nr; i++, dpse++) {
if (dpse->vs_level == (config[0] & 3) &&
dpse->pre_level == ((config[0] >> 3) & 3))
break;
}
BUG_ON(i == dpe->record_nr);
for (i = 0; i < nv_encoder->dp.link_nr; i++) {
const int shift[4] = { 16, 8, 0, 24 };
uint32_t mask = 0xff << shift[i];
uint32_t reg0, reg1, reg2;
reg0 = nv_rd32(dev, NV50_SOR_DP_UNK118(or, link)) & ~mask;
reg0 |= (dpse->reg0 << shift[i]);
reg1 = nv_rd32(dev, NV50_SOR_DP_UNK120(or, link)) & ~mask;
reg1 |= (dpse->reg1 << shift[i]);
reg2 = nv_rd32(dev, NV50_SOR_DP_UNK130(or, link)) & 0xffff00ff;
reg2 |= (dpse->reg2 << 8);
nv_wr32(dev, NV50_SOR_DP_UNK118(or, link), reg0);
nv_wr32(dev, NV50_SOR_DP_UNK120(or, link), reg1);
nv_wr32(dev, NV50_SOR_DP_UNK130(or, link), reg2);
}
ret = auxch_wr(encoder, DP_TRAINING_LANE0_SET, config, 4);
if (ret)
return false;
return true;
}
bool
nouveau_dp_link_train(struct drm_encoder *encoder)
{
struct drm_device *dev = encoder->dev;
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
uint8_t config[4];
uint8_t status[3];
bool cr_done, cr_max_vs, eq_done;
int ret = 0, i, tries, voltage;
NV_DEBUG(dev, "link training!!\n");
train:
cr_done = eq_done = false;
/* set link configuration */
NV_DEBUG(dev, "\tbegin train: bw %d, lanes %d\n",
nv_encoder->dp.link_bw, nv_encoder->dp.link_nr);
ret = nouveau_dp_link_bw_set(encoder, nv_encoder->dp.link_bw);
if (ret)
return false;
config[0] = nv_encoder->dp.link_nr;
if (nv_encoder->dp.dpcd_version >= 0x11)
config[0] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
ret = nouveau_dp_lane_count_set(encoder, config[0]);
if (ret)
return false;
/* clock recovery */
NV_DEBUG(dev, "\tbegin cr\n");
ret = nouveau_dp_link_train_set(encoder, DP_TRAINING_PATTERN_1);
if (ret)
goto stop;
tries = 0;
voltage = -1;
memset(config, 0x00, sizeof(config));
for (;;) {
if (!nouveau_dp_link_train_commit(encoder, config))
break;
udelay(100);
ret = auxch_rd(encoder, DP_LANE0_1_STATUS, status, 2);
if (ret)
break;
NV_DEBUG(dev, "\t\tstatus: 0x%02x 0x%02x\n",
status[0], status[1]);
cr_done = true;
cr_max_vs = false;
for (i = 0; i < nv_encoder->dp.link_nr; i++) {
int lane = (status[i >> 1] >> ((i & 1) * 4)) & 0xf;
if (!(lane & DP_LANE_CR_DONE)) {
cr_done = false;
if (config[i] & DP_TRAIN_MAX_PRE_EMPHASIS_REACHED)
cr_max_vs = true;
break;
}
}
if ((config[0] & DP_TRAIN_VOLTAGE_SWING_MASK) != voltage) {
voltage = config[0] & DP_TRAIN_VOLTAGE_SWING_MASK;
tries = 0;
}
if (cr_done || cr_max_vs || (++tries == 5))
break;
if (!nouveau_dp_link_train_adjust(encoder, config))
break;
}
if (!cr_done)
goto stop;
/* channel equalisation */
NV_DEBUG(dev, "\tbegin eq\n");
ret = nouveau_dp_link_train_set(encoder, DP_TRAINING_PATTERN_2);
if (ret)
goto stop;
for (tries = 0; tries <= 5; tries++) {
udelay(400);
ret = auxch_rd(encoder, DP_LANE0_1_STATUS, status, 3);
if (ret)
break;
NV_DEBUG(dev, "\t\tstatus: 0x%02x 0x%02x\n",
status[0], status[1]);
eq_done = true;
if (!(status[2] & DP_INTERLANE_ALIGN_DONE))
eq_done = false;
for (i = 0; eq_done && i < nv_encoder->dp.link_nr; i++) {
int lane = (status[i >> 1] >> ((i & 1) * 4)) & 0xf;
if (!(lane & DP_LANE_CR_DONE)) {
cr_done = false;
break;
}
if (!(lane & DP_LANE_CHANNEL_EQ_DONE) ||
!(lane & DP_LANE_SYMBOL_LOCKED)) {
eq_done = false;
break;
}
}
if (eq_done || !cr_done)
break;
if (!nouveau_dp_link_train_adjust(encoder, config) ||
!nouveau_dp_link_train_commit(encoder, config))
break;
}
stop:
/* end link training */
ret = nouveau_dp_link_train_set(encoder, DP_TRAINING_PATTERN_DISABLE);
if (ret)
return false;
/* retry at a lower setting, if possible */
if (!ret && !(eq_done && cr_done)) {
NV_DEBUG(dev, "\twe failed\n");
if (nv_encoder->dp.link_bw != DP_LINK_BW_1_62) {
NV_DEBUG(dev, "retry link training at low rate\n");
nv_encoder->dp.link_bw = DP_LINK_BW_1_62;
goto train;
}
}
return eq_done;
}
bool
nouveau_dp_detect(struct drm_encoder *encoder)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct drm_device *dev = encoder->dev;
uint8_t dpcd[4];
int ret;
ret = auxch_rd(encoder, 0x0000, dpcd, 4);
if (ret)
return false;
NV_DEBUG(dev, "encoder: link_bw %d, link_nr %d\n"
"display: link_bw %d, link_nr %d version 0x%02x\n",
nv_encoder->dcb->dpconf.link_bw,
nv_encoder->dcb->dpconf.link_nr,
dpcd[1], dpcd[2] & 0x0f, dpcd[0]);
nv_encoder->dp.dpcd_version = dpcd[0];
nv_encoder->dp.link_bw = dpcd[1];
if (nv_encoder->dp.link_bw != DP_LINK_BW_1_62 &&
!nv_encoder->dcb->dpconf.link_bw)
nv_encoder->dp.link_bw = DP_LINK_BW_1_62;
nv_encoder->dp.link_nr = dpcd[2] & 0xf;
if (nv_encoder->dp.link_nr > nv_encoder->dcb->dpconf.link_nr)
nv_encoder->dp.link_nr = nv_encoder->dcb->dpconf.link_nr;
return true;
}
int
nouveau_dp_auxch(struct nouveau_i2c_chan *auxch, int cmd, int addr,
uint8_t *data, int data_nr)
{
struct drm_device *dev = auxch->dev;
uint32_t tmp, ctrl, stat = 0, data32[4] = {};
int ret = 0, i, index = auxch->rd;
NV_DEBUG(dev, "ch %d cmd %d addr 0x%x len %d\n", index, cmd, addr, data_nr);
tmp = nv_rd32(dev, NV50_AUXCH_CTRL(auxch->rd));
nv_wr32(dev, NV50_AUXCH_CTRL(auxch->rd), tmp | 0x00100000);
tmp = nv_rd32(dev, NV50_AUXCH_CTRL(auxch->rd));
if (!(tmp & 0x01000000)) {
NV_ERROR(dev, "expected bit 24 == 1, got 0x%08x\n", tmp);
ret = -EIO;
goto out;
}
for (i = 0; i < 3; i++) {
tmp = nv_rd32(dev, NV50_AUXCH_STAT(auxch->rd));
if (tmp & NV50_AUXCH_STAT_STATE_READY)
break;
udelay(100);
}
if (i == 3) {
ret = -EBUSY;
goto out;
}
if (!(cmd & 1)) {
memcpy(data32, data, data_nr);
for (i = 0; i < 4; i++) {
NV_DEBUG(dev, "wr %d: 0x%08x\n", i, data32[i]);
nv_wr32(dev, NV50_AUXCH_DATA_OUT(index, i), data32[i]);
}
}
nv_wr32(dev, NV50_AUXCH_ADDR(index), addr);
ctrl = nv_rd32(dev, NV50_AUXCH_CTRL(index));
ctrl &= ~(NV50_AUXCH_CTRL_CMD | NV50_AUXCH_CTRL_LEN);
ctrl |= (cmd << NV50_AUXCH_CTRL_CMD_SHIFT);
ctrl |= ((data_nr - 1) << NV50_AUXCH_CTRL_LEN_SHIFT);
for (;;) {
nv_wr32(dev, NV50_AUXCH_CTRL(index), ctrl | 0x80000000);
nv_wr32(dev, NV50_AUXCH_CTRL(index), ctrl);
nv_wr32(dev, NV50_AUXCH_CTRL(index), ctrl | 0x00010000);
if (!nv_wait(NV50_AUXCH_CTRL(index), 0x00010000, 0x00000000)) {
NV_ERROR(dev, "expected bit 16 == 0, got 0x%08x\n",
nv_rd32(dev, NV50_AUXCH_CTRL(index)));
return -EBUSY;
}
udelay(400);
stat = nv_rd32(dev, NV50_AUXCH_STAT(index));
if ((stat & NV50_AUXCH_STAT_REPLY_AUX) !=
NV50_AUXCH_STAT_REPLY_AUX_DEFER)
break;
}
if (cmd & 1) {
for (i = 0; i < 4; i++) {
data32[i] = nv_rd32(dev, NV50_AUXCH_DATA_IN(index, i));
NV_DEBUG(dev, "rd %d: 0x%08x\n", i, data32[i]);
}
memcpy(data, data32, data_nr);
}
out:
tmp = nv_rd32(dev, NV50_AUXCH_CTRL(auxch->rd));
nv_wr32(dev, NV50_AUXCH_CTRL(auxch->rd), tmp & ~0x00100000);
tmp = nv_rd32(dev, NV50_AUXCH_CTRL(auxch->rd));
if (tmp & 0x01000000) {
NV_ERROR(dev, "expected bit 24 == 0, got 0x%08x\n", tmp);
ret = -EIO;
}
udelay(400);
return ret ? ret : (stat & NV50_AUXCH_STAT_REPLY);
}
int
nouveau_dp_i2c_aux_ch(struct i2c_adapter *adapter, int mode,
uint8_t write_byte, uint8_t *read_byte)
{
struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data;
struct nouveau_i2c_chan *auxch = (struct nouveau_i2c_chan *)adapter;
struct drm_device *dev = auxch->dev;
int ret = 0, cmd, addr = algo_data->address;
uint8_t *buf;
if (mode == MODE_I2C_READ) {
cmd = AUX_I2C_READ;
buf = read_byte;
} else {
cmd = (mode & MODE_I2C_READ) ? AUX_I2C_READ : AUX_I2C_WRITE;
buf = &write_byte;
}
if (!(mode & MODE_I2C_STOP))
cmd |= AUX_I2C_MOT;
if (mode & MODE_I2C_START)
return 1;
for (;;) {
ret = nouveau_dp_auxch(auxch, cmd, addr, buf, 1);
if (ret < 0)
return ret;
switch (ret & NV50_AUXCH_STAT_REPLY_I2C) {
case NV50_AUXCH_STAT_REPLY_I2C_ACK:
return 1;
case NV50_AUXCH_STAT_REPLY_I2C_NACK:
return -EREMOTEIO;
case NV50_AUXCH_STAT_REPLY_I2C_DEFER:
udelay(100);
break;
default:
NV_ERROR(dev, "invalid auxch status: 0x%08x\n", ret);
return -EREMOTEIO;
}
}
}