linux/drivers/gpu/drm/nouveau/nv50_sor.c
Henrik Rydberg 273a50fbcd nouveau: Set special lane map for the right chipset
The refactoring of the nv50 logic, introduced in 8663bc7c, modified the
test for the special lane map used on some Apple computers with Nvidia
chipsets. The tested MBA3,1 would still boot, but resume from suspend
stopped working. This patch restores the old test, which fixes the problem.

Signed-off-by: Henrik Rydberg <rydberg@euromail.se>
Acked-by: Ben Skeggs <bskeggs@redhat.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
2012-04-19 16:37:53 +01:00

515 lines
14 KiB
C

/*
* Copyright (C) 2008 Maarten Maathuis.
* All Rights Reserved.
*
* 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 (including the
* next paragraph) 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 OWNER(S) AND/OR ITS SUPPLIERS 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 "drmP.h"
#include "drm_crtc_helper.h"
#define NOUVEAU_DMA_DEBUG (nouveau_reg_debug & NOUVEAU_REG_DEBUG_EVO)
#include "nouveau_reg.h"
#include "nouveau_drv.h"
#include "nouveau_dma.h"
#include "nouveau_encoder.h"
#include "nouveau_connector.h"
#include "nouveau_crtc.h"
#include "nv50_display.h"
static u32
nv50_sor_dp_lane_map(struct drm_device *dev, struct dcb_entry *dcb, u8 lane)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
static const u8 nvaf[] = { 24, 16, 8, 0 }; /* thanks, apple.. */
static const u8 nv50[] = { 16, 8, 0, 24 };
if (dev_priv->chipset == 0xaf)
return nvaf[lane];
return nv50[lane];
}
static void
nv50_sor_dp_train_set(struct drm_device *dev, struct dcb_entry *dcb, u8 pattern)
{
u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
nv_mask(dev, NV50_SOR_DP_CTRL(or, link), 0x0f000000, pattern << 24);
}
static void
nv50_sor_dp_train_adj(struct drm_device *dev, struct dcb_entry *dcb,
u8 lane, u8 swing, u8 preem)
{
u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
u32 shift = nv50_sor_dp_lane_map(dev, dcb, lane);
u32 mask = 0x000000ff << shift;
u8 *table, *entry, *config;
table = nouveau_dp_bios_data(dev, dcb, &entry);
if (!table || (table[0] != 0x20 && table[0] != 0x21)) {
NV_ERROR(dev, "PDISP: unsupported DP table for chipset\n");
return;
}
config = entry + table[4];
while (config[0] != swing || config[1] != preem) {
config += table[5];
if (config >= entry + table[4] + entry[4] * table[5])
return;
}
nv_mask(dev, NV50_SOR_DP_UNK118(or, link), mask, config[2] << shift);
nv_mask(dev, NV50_SOR_DP_UNK120(or, link), mask, config[3] << shift);
nv_mask(dev, NV50_SOR_DP_UNK130(or, link), 0x0000ff00, config[4] << 8);
}
static void
nv50_sor_dp_link_set(struct drm_device *dev, struct dcb_entry *dcb, int crtc,
int link_nr, u32 link_bw, bool enhframe)
{
u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
u32 dpctrl = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link)) & ~0x001f4000;
u32 clksor = nv_rd32(dev, 0x614300 + (or * 0x800)) & ~0x000c0000;
u8 *table, *entry, mask;
int i;
table = nouveau_dp_bios_data(dev, dcb, &entry);
if (!table || (table[0] != 0x20 && table[0] != 0x21)) {
NV_ERROR(dev, "PDISP: unsupported DP table for chipset\n");
return;
}
entry = ROMPTR(dev, entry[10]);
if (entry) {
while (link_bw < ROM16(entry[0]) * 10)
entry += 4;
nouveau_bios_run_init_table(dev, ROM16(entry[2]), dcb, crtc);
}
dpctrl |= ((1 << link_nr) - 1) << 16;
if (enhframe)
dpctrl |= 0x00004000;
if (link_bw > 162000)
clksor |= 0x00040000;
nv_wr32(dev, 0x614300 + (or * 0x800), clksor);
nv_wr32(dev, NV50_SOR_DP_CTRL(or, link), dpctrl);
mask = 0;
for (i = 0; i < link_nr; i++)
mask |= 1 << (nv50_sor_dp_lane_map(dev, dcb, i) >> 3);
nv_mask(dev, NV50_SOR_DP_UNK130(or, link), 0x0000000f, mask);
}
static void
nv50_sor_dp_link_get(struct drm_device *dev, u32 or, u32 link, u32 *nr, u32 *bw)
{
u32 dpctrl = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link)) & 0x000f0000;
u32 clksor = nv_rd32(dev, 0x614300 + (or * 0x800));
if (clksor & 0x000c0000)
*bw = 270000;
else
*bw = 162000;
if (dpctrl > 0x00030000) *nr = 4;
else if (dpctrl > 0x00010000) *nr = 2;
else *nr = 1;
}
void
nv50_sor_dp_calc_tu(struct drm_device *dev, int or, int link, u32 clk, u32 bpp)
{
const u32 symbol = 100000;
int bestTU = 0, bestVTUi = 0, bestVTUf = 0, bestVTUa = 0;
int TU, VTUi, VTUf, VTUa;
u64 link_data_rate, link_ratio, unk;
u32 best_diff = 64 * symbol;
u32 link_nr, link_bw, r;
/* calculate packed data rate for each lane */
nv50_sor_dp_link_get(dev, or, link, &link_nr, &link_bw);
link_data_rate = (clk * bpp / 8) / link_nr;
/* calculate ratio of packed data rate to link symbol rate */
link_ratio = link_data_rate * symbol;
r = do_div(link_ratio, link_bw);
for (TU = 64; TU >= 32; TU--) {
/* calculate average number of valid symbols in each TU */
u32 tu_valid = link_ratio * TU;
u32 calc, diff;
/* find a hw representation for the fraction.. */
VTUi = tu_valid / symbol;
calc = VTUi * symbol;
diff = tu_valid - calc;
if (diff) {
if (diff >= (symbol / 2)) {
VTUf = symbol / (symbol - diff);
if (symbol - (VTUf * diff))
VTUf++;
if (VTUf <= 15) {
VTUa = 1;
calc += symbol - (symbol / VTUf);
} else {
VTUa = 0;
VTUf = 1;
calc += symbol;
}
} else {
VTUa = 0;
VTUf = min((int)(symbol / diff), 15);
calc += symbol / VTUf;
}
diff = calc - tu_valid;
} else {
/* no remainder, but the hw doesn't like the fractional
* part to be zero. decrement the integer part and
* have the fraction add a whole symbol back
*/
VTUa = 0;
VTUf = 1;
VTUi--;
}
if (diff < best_diff) {
best_diff = diff;
bestTU = TU;
bestVTUa = VTUa;
bestVTUf = VTUf;
bestVTUi = VTUi;
if (diff == 0)
break;
}
}
if (!bestTU) {
NV_ERROR(dev, "DP: unable to find suitable config\n");
return;
}
/* XXX close to vbios numbers, but not right */
unk = (symbol - link_ratio) * bestTU;
unk *= link_ratio;
r = do_div(unk, symbol);
r = do_div(unk, symbol);
unk += 6;
nv_mask(dev, NV50_SOR_DP_CTRL(or, link), 0x000001fc, bestTU << 2);
nv_mask(dev, NV50_SOR_DP_SCFG(or, link), 0x010f7f3f, bestVTUa << 24 |
bestVTUf << 16 |
bestVTUi << 8 |
unk);
}
static void
nv50_sor_disconnect(struct drm_encoder *encoder)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct drm_device *dev = encoder->dev;
struct nouveau_channel *evo = nv50_display(dev)->master;
int ret;
if (!nv_encoder->crtc)
return;
nv50_crtc_blank(nouveau_crtc(nv_encoder->crtc), true);
NV_DEBUG_KMS(dev, "Disconnecting SOR %d\n", nv_encoder->or);
ret = RING_SPACE(evo, 4);
if (ret) {
NV_ERROR(dev, "no space while disconnecting SOR\n");
return;
}
BEGIN_RING(evo, 0, NV50_EVO_SOR(nv_encoder->or, MODE_CTRL), 1);
OUT_RING (evo, 0);
BEGIN_RING(evo, 0, NV50_EVO_UPDATE, 1);
OUT_RING (evo, 0);
nouveau_hdmi_mode_set(encoder, NULL);
nv_encoder->crtc = NULL;
nv_encoder->last_dpms = DRM_MODE_DPMS_OFF;
}
static void
nv50_sor_dpms(struct drm_encoder *encoder, int mode)
{
struct drm_device *dev = encoder->dev;
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct drm_encoder *enc;
uint32_t val;
int or = nv_encoder->or;
NV_DEBUG_KMS(dev, "or %d type %d mode %d\n", or, nv_encoder->dcb->type, mode);
nv_encoder->last_dpms = mode;
list_for_each_entry(enc, &dev->mode_config.encoder_list, head) {
struct nouveau_encoder *nvenc = nouveau_encoder(enc);
if (nvenc == nv_encoder ||
(nvenc->dcb->type != OUTPUT_TMDS &&
nvenc->dcb->type != OUTPUT_LVDS &&
nvenc->dcb->type != OUTPUT_DP) ||
nvenc->dcb->or != nv_encoder->dcb->or)
continue;
if (nvenc->last_dpms == DRM_MODE_DPMS_ON)
return;
}
/* wait for it to be done */
if (!nv_wait(dev, NV50_PDISPLAY_SOR_DPMS_CTRL(or),
NV50_PDISPLAY_SOR_DPMS_CTRL_PENDING, 0)) {
NV_ERROR(dev, "timeout: SOR_DPMS_CTRL_PENDING(%d) == 0\n", or);
NV_ERROR(dev, "SOR_DPMS_CTRL(%d) = 0x%08x\n", or,
nv_rd32(dev, NV50_PDISPLAY_SOR_DPMS_CTRL(or)));
}
val = nv_rd32(dev, NV50_PDISPLAY_SOR_DPMS_CTRL(or));
if (mode == DRM_MODE_DPMS_ON)
val |= NV50_PDISPLAY_SOR_DPMS_CTRL_ON;
else
val &= ~NV50_PDISPLAY_SOR_DPMS_CTRL_ON;
nv_wr32(dev, NV50_PDISPLAY_SOR_DPMS_CTRL(or), val |
NV50_PDISPLAY_SOR_DPMS_CTRL_PENDING);
if (!nv_wait(dev, NV50_PDISPLAY_SOR_DPMS_STATE(or),
NV50_PDISPLAY_SOR_DPMS_STATE_WAIT, 0)) {
NV_ERROR(dev, "timeout: SOR_DPMS_STATE_WAIT(%d) == 0\n", or);
NV_ERROR(dev, "SOR_DPMS_STATE(%d) = 0x%08x\n", or,
nv_rd32(dev, NV50_PDISPLAY_SOR_DPMS_STATE(or)));
}
if (nv_encoder->dcb->type == OUTPUT_DP) {
struct dp_train_func func = {
.link_set = nv50_sor_dp_link_set,
.train_set = nv50_sor_dp_train_set,
.train_adj = nv50_sor_dp_train_adj
};
nouveau_dp_dpms(encoder, mode, nv_encoder->dp.datarate, &func);
}
}
static void
nv50_sor_save(struct drm_encoder *encoder)
{
NV_ERROR(encoder->dev, "!!\n");
}
static void
nv50_sor_restore(struct drm_encoder *encoder)
{
NV_ERROR(encoder->dev, "!!\n");
}
static bool
nv50_sor_mode_fixup(struct drm_encoder *encoder, struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nouveau_connector *connector;
NV_DEBUG_KMS(encoder->dev, "or %d\n", nv_encoder->or);
connector = nouveau_encoder_connector_get(nv_encoder);
if (!connector) {
NV_ERROR(encoder->dev, "Encoder has no connector\n");
return false;
}
if (connector->scaling_mode != DRM_MODE_SCALE_NONE &&
connector->native_mode)
drm_mode_copy(adjusted_mode, connector->native_mode);
return true;
}
static void
nv50_sor_prepare(struct drm_encoder *encoder)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
nv50_sor_disconnect(encoder);
if (nv_encoder->dcb->type == OUTPUT_DP) {
/* avoid race between link training and supervisor intr */
nv50_display_sync(encoder->dev);
}
}
static void
nv50_sor_commit(struct drm_encoder *encoder)
{
}
static void
nv50_sor_mode_set(struct drm_encoder *encoder, struct drm_display_mode *umode,
struct drm_display_mode *mode)
{
struct nouveau_channel *evo = nv50_display(encoder->dev)->master;
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct drm_device *dev = encoder->dev;
struct nouveau_crtc *crtc = nouveau_crtc(encoder->crtc);
struct nouveau_connector *nv_connector;
uint32_t mode_ctl = 0;
int ret;
NV_DEBUG_KMS(dev, "or %d type %d -> crtc %d\n",
nv_encoder->or, nv_encoder->dcb->type, crtc->index);
nv_encoder->crtc = encoder->crtc;
switch (nv_encoder->dcb->type) {
case OUTPUT_TMDS:
if (nv_encoder->dcb->sorconf.link & 1) {
if (mode->clock < 165000)
mode_ctl = 0x0100;
else
mode_ctl = 0x0500;
} else
mode_ctl = 0x0200;
nouveau_hdmi_mode_set(encoder, mode);
break;
case OUTPUT_DP:
nv_connector = nouveau_encoder_connector_get(nv_encoder);
if (nv_connector && nv_connector->base.display_info.bpc == 6) {
nv_encoder->dp.datarate = mode->clock * 18 / 8;
mode_ctl |= 0x00020000;
} else {
nv_encoder->dp.datarate = mode->clock * 24 / 8;
mode_ctl |= 0x00050000;
}
if (nv_encoder->dcb->sorconf.link & 1)
mode_ctl |= 0x00000800;
else
mode_ctl |= 0x00000900;
break;
default:
break;
}
if (crtc->index == 1)
mode_ctl |= NV50_EVO_SOR_MODE_CTRL_CRTC1;
else
mode_ctl |= NV50_EVO_SOR_MODE_CTRL_CRTC0;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
mode_ctl |= NV50_EVO_SOR_MODE_CTRL_NHSYNC;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
mode_ctl |= NV50_EVO_SOR_MODE_CTRL_NVSYNC;
nv50_sor_dpms(encoder, DRM_MODE_DPMS_ON);
ret = RING_SPACE(evo, 2);
if (ret) {
NV_ERROR(dev, "no space while connecting SOR\n");
nv_encoder->crtc = NULL;
return;
}
BEGIN_RING(evo, 0, NV50_EVO_SOR(nv_encoder->or, MODE_CTRL), 1);
OUT_RING(evo, mode_ctl);
}
static struct drm_crtc *
nv50_sor_crtc_get(struct drm_encoder *encoder)
{
return nouveau_encoder(encoder)->crtc;
}
static const struct drm_encoder_helper_funcs nv50_sor_helper_funcs = {
.dpms = nv50_sor_dpms,
.save = nv50_sor_save,
.restore = nv50_sor_restore,
.mode_fixup = nv50_sor_mode_fixup,
.prepare = nv50_sor_prepare,
.commit = nv50_sor_commit,
.mode_set = nv50_sor_mode_set,
.get_crtc = nv50_sor_crtc_get,
.detect = NULL,
.disable = nv50_sor_disconnect
};
static void
nv50_sor_destroy(struct drm_encoder *encoder)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
if (!encoder)
return;
NV_DEBUG_KMS(encoder->dev, "\n");
drm_encoder_cleanup(encoder);
kfree(nv_encoder);
}
static const struct drm_encoder_funcs nv50_sor_encoder_funcs = {
.destroy = nv50_sor_destroy,
};
int
nv50_sor_create(struct drm_connector *connector, struct dcb_entry *entry)
{
struct nouveau_encoder *nv_encoder = NULL;
struct drm_device *dev = connector->dev;
struct drm_encoder *encoder;
int type;
NV_DEBUG_KMS(dev, "\n");
switch (entry->type) {
case OUTPUT_TMDS:
case OUTPUT_DP:
type = DRM_MODE_ENCODER_TMDS;
break;
case OUTPUT_LVDS:
type = DRM_MODE_ENCODER_LVDS;
break;
default:
return -EINVAL;
}
nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
if (!nv_encoder)
return -ENOMEM;
encoder = to_drm_encoder(nv_encoder);
nv_encoder->dcb = entry;
nv_encoder->or = ffs(entry->or) - 1;
nv_encoder->last_dpms = DRM_MODE_DPMS_OFF;
drm_encoder_init(dev, encoder, &nv50_sor_encoder_funcs, type);
drm_encoder_helper_add(encoder, &nv50_sor_helper_funcs);
encoder->possible_crtcs = entry->heads;
encoder->possible_clones = 0;
drm_mode_connector_attach_encoder(connector, encoder);
return 0;
}