提交 6ee73861 编写于 作者: B Ben Skeggs 提交者: Dave Airlie

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: NBen Skeggs <bskeggs@redhat.com>
Signed-off-by: NDave Airlie <airlied@redhat.com>
上级 d1ede145
......@@ -31,3 +31,5 @@ obj-$(CONFIG_DRM_I915) += i915/
obj-$(CONFIG_DRM_SIS) += sis/
obj-$(CONFIG_DRM_SAVAGE)+= savage/
obj-$(CONFIG_DRM_VIA) +=via/
obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
obj-y += i2c/
ccflags-y := -Iinclude/drm
ch7006-y := ch7006_drv.o ch7006_mode.o
obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o
/*
* Copyright (C) 2009 Francisco Jerez.
* 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 "ch7006_priv.h"
/* DRM encoder functions */
static void ch7006_encoder_set_config(struct drm_encoder *encoder,
void *params)
{
struct ch7006_priv *priv = to_ch7006_priv(encoder);
priv->params = params;
}
static void ch7006_encoder_destroy(struct drm_encoder *encoder)
{
struct ch7006_priv *priv = to_ch7006_priv(encoder);
drm_property_destroy(encoder->dev, priv->scale_property);
kfree(priv);
to_encoder_slave(encoder)->slave_priv = NULL;
drm_i2c_encoder_destroy(encoder);
}
static void ch7006_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
struct ch7006_priv *priv = to_ch7006_priv(encoder);
struct ch7006_state *state = &priv->state;
ch7006_dbg(client, "\n");
if (mode == priv->last_dpms)
return;
priv->last_dpms = mode;
ch7006_setup_power_state(encoder);
ch7006_load_reg(client, state, CH7006_POWER);
}
static void ch7006_encoder_save(struct drm_encoder *encoder)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
struct ch7006_priv *priv = to_ch7006_priv(encoder);
ch7006_dbg(client, "\n");
ch7006_state_save(client, &priv->saved_state);
}
static void ch7006_encoder_restore(struct drm_encoder *encoder)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
struct ch7006_priv *priv = to_ch7006_priv(encoder);
ch7006_dbg(client, "\n");
ch7006_state_load(client, &priv->saved_state);
}
static bool ch7006_encoder_mode_fixup(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct ch7006_priv *priv = to_ch7006_priv(encoder);
/* The ch7006 is painfully picky with the input timings so no
* custom modes for now... */
priv->mode = ch7006_lookup_mode(encoder, mode);
return !!priv->mode;
}
static int ch7006_encoder_mode_valid(struct drm_encoder *encoder,
struct drm_display_mode *mode)
{
if (ch7006_lookup_mode(encoder, mode))
return MODE_OK;
else
return MODE_BAD;
}
static void ch7006_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *drm_mode,
struct drm_display_mode *adjusted_mode)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
struct ch7006_priv *priv = to_ch7006_priv(encoder);
struct ch7006_encoder_params *params = priv->params;
struct ch7006_state *state = &priv->state;
uint8_t *regs = state->regs;
struct ch7006_mode *mode = priv->mode;
struct ch7006_tv_norm_info *norm = &ch7006_tv_norms[priv->norm];
int start_active;
ch7006_dbg(client, "\n");
regs[CH7006_DISPMODE] = norm->dispmode | mode->dispmode;
regs[CH7006_BWIDTH] = 0;
regs[CH7006_INPUT_FORMAT] = bitf(CH7006_INPUT_FORMAT_FORMAT,
params->input_format);
regs[CH7006_CLKMODE] = CH7006_CLKMODE_SUBC_LOCK
| bitf(CH7006_CLKMODE_XCM, params->xcm)
| bitf(CH7006_CLKMODE_PCM, params->pcm);
if (params->clock_mode)
regs[CH7006_CLKMODE] |= CH7006_CLKMODE_MASTER;
if (params->clock_edge)
regs[CH7006_CLKMODE] |= CH7006_CLKMODE_POS_EDGE;
start_active = (drm_mode->htotal & ~0x7) - (drm_mode->hsync_start & ~0x7);
regs[CH7006_POV] = bitf(CH7006_POV_START_ACTIVE_8, start_active);
regs[CH7006_START_ACTIVE] = bitf(CH7006_START_ACTIVE_0, start_active);
regs[CH7006_INPUT_SYNC] = 0;
if (params->sync_direction)
regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_OUTPUT;
if (params->sync_encoding)
regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_EMBEDDED;
if (drm_mode->flags & DRM_MODE_FLAG_PVSYNC)
regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_PVSYNC;
if (drm_mode->flags & DRM_MODE_FLAG_PHSYNC)
regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_PHSYNC;
regs[CH7006_DETECT] = 0;
regs[CH7006_BCLKOUT] = 0;
regs[CH7006_SUBC_INC3] = 0;
if (params->pout_level)
regs[CH7006_SUBC_INC3] |= CH7006_SUBC_INC3_POUT_3_3V;
regs[CH7006_SUBC_INC4] = 0;
if (params->active_detect)
regs[CH7006_SUBC_INC4] |= CH7006_SUBC_INC4_DS_INPUT;
regs[CH7006_PLL_CONTROL] = priv->saved_state.regs[CH7006_PLL_CONTROL];
ch7006_setup_levels(encoder);
ch7006_setup_subcarrier(encoder);
ch7006_setup_pll(encoder);
ch7006_setup_power_state(encoder);
ch7006_setup_properties(encoder);
ch7006_state_load(client, state);
}
static enum drm_connector_status ch7006_encoder_detect(struct drm_encoder *encoder,
struct drm_connector *connector)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
struct ch7006_priv *priv = to_ch7006_priv(encoder);
struct ch7006_state *state = &priv->state;
int det;
ch7006_dbg(client, "\n");
ch7006_save_reg(client, state, CH7006_DETECT);
ch7006_save_reg(client, state, CH7006_POWER);
ch7006_save_reg(client, state, CH7006_CLKMODE);
ch7006_write(client, CH7006_POWER, CH7006_POWER_RESET |
bitfs(CH7006_POWER_LEVEL, NORMAL));
ch7006_write(client, CH7006_CLKMODE, CH7006_CLKMODE_MASTER);
ch7006_write(client, CH7006_DETECT, CH7006_DETECT_SENSE);
ch7006_write(client, CH7006_DETECT, 0);
det = ch7006_read(client, CH7006_DETECT);
ch7006_load_reg(client, state, CH7006_CLKMODE);
ch7006_load_reg(client, state, CH7006_POWER);
ch7006_load_reg(client, state, CH7006_DETECT);
if ((det & (CH7006_DETECT_SVIDEO_Y_TEST|
CH7006_DETECT_SVIDEO_C_TEST|
CH7006_DETECT_CVBS_TEST)) == 0)
priv->subconnector = DRM_MODE_SUBCONNECTOR_SCART;
else if ((det & (CH7006_DETECT_SVIDEO_Y_TEST|
CH7006_DETECT_SVIDEO_C_TEST)) == 0)
priv->subconnector = DRM_MODE_SUBCONNECTOR_SVIDEO;
else if ((det & CH7006_DETECT_CVBS_TEST) == 0)
priv->subconnector = DRM_MODE_SUBCONNECTOR_Composite;
else
priv->subconnector = DRM_MODE_SUBCONNECTOR_Unknown;
drm_connector_property_set_value(connector,
encoder->dev->mode_config.tv_subconnector_property,
priv->subconnector);
return priv->subconnector ? connector_status_connected :
connector_status_disconnected;
}
static int ch7006_encoder_get_modes(struct drm_encoder *encoder,
struct drm_connector *connector)
{
struct ch7006_priv *priv = to_ch7006_priv(encoder);
struct ch7006_mode *mode;
int n = 0;
for (mode = ch7006_modes; mode->mode.clock; mode++) {
if (~mode->valid_scales & 1<<priv->scale ||
~mode->valid_norms & 1<<priv->norm)
continue;
drm_mode_probed_add(connector,
drm_mode_duplicate(encoder->dev, &mode->mode));
n++;
}
return n;
}
static int ch7006_encoder_create_resources(struct drm_encoder *encoder,
struct drm_connector *connector)
{
struct ch7006_priv *priv = to_ch7006_priv(encoder);
struct drm_device *dev = encoder->dev;
struct drm_mode_config *conf = &dev->mode_config;
drm_mode_create_tv_properties(dev, NUM_TV_NORMS, ch7006_tv_norm_names);
priv->scale_property = drm_property_create(dev, DRM_MODE_PROP_RANGE,
"scale", 2);
priv->scale_property->values[0] = 0;
priv->scale_property->values[1] = 2;
drm_connector_attach_property(connector, conf->tv_select_subconnector_property,
priv->select_subconnector);
drm_connector_attach_property(connector, conf->tv_subconnector_property,
priv->subconnector);
drm_connector_attach_property(connector, conf->tv_left_margin_property,
priv->hmargin);
drm_connector_attach_property(connector, conf->tv_bottom_margin_property,
priv->vmargin);
drm_connector_attach_property(connector, conf->tv_mode_property,
priv->norm);
drm_connector_attach_property(connector, conf->tv_brightness_property,
priv->brightness);
drm_connector_attach_property(connector, conf->tv_contrast_property,
priv->contrast);
drm_connector_attach_property(connector, conf->tv_flicker_reduction_property,
priv->flicker);
drm_connector_attach_property(connector, priv->scale_property,
priv->scale);
return 0;
}
static int ch7006_encoder_set_property(struct drm_encoder *encoder,
struct drm_connector *connector,
struct drm_property *property,
uint64_t val)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
struct ch7006_priv *priv = to_ch7006_priv(encoder);
struct ch7006_state *state = &priv->state;
struct drm_mode_config *conf = &encoder->dev->mode_config;
struct drm_crtc *crtc = encoder->crtc;
bool modes_changed = false;
ch7006_dbg(client, "\n");
if (property == conf->tv_select_subconnector_property) {
priv->select_subconnector = val;
ch7006_setup_power_state(encoder);
ch7006_load_reg(client, state, CH7006_POWER);
} else if (property == conf->tv_left_margin_property) {
priv->hmargin = val;
ch7006_setup_properties(encoder);
ch7006_load_reg(client, state, CH7006_POV);
ch7006_load_reg(client, state, CH7006_HPOS);
} else if (property == conf->tv_bottom_margin_property) {
priv->vmargin = val;
ch7006_setup_properties(encoder);
ch7006_load_reg(client, state, CH7006_POV);
ch7006_load_reg(client, state, CH7006_VPOS);
} else if (property == conf->tv_mode_property) {
if (connector->dpms != DRM_MODE_DPMS_OFF)
return -EINVAL;
priv->norm = val;
modes_changed = true;
} else if (property == conf->tv_brightness_property) {
priv->brightness = val;
ch7006_setup_levels(encoder);
ch7006_load_reg(client, state, CH7006_BLACK_LEVEL);
} else if (property == conf->tv_contrast_property) {
priv->contrast = val;
ch7006_setup_properties(encoder);
ch7006_load_reg(client, state, CH7006_CONTRAST);
} else if (property == conf->tv_flicker_reduction_property) {
priv->flicker = val;
ch7006_setup_properties(encoder);
ch7006_load_reg(client, state, CH7006_FFILTER);
} else if (property == priv->scale_property) {
if (connector->dpms != DRM_MODE_DPMS_OFF)
return -EINVAL;
priv->scale = val;
modes_changed = true;
} else {
return -EINVAL;
}
if (modes_changed) {
drm_helper_probe_single_connector_modes(connector, 0, 0);
/* Disable the crtc to ensure a full modeset is
* performed whenever it's turned on again. */
if (crtc) {
struct drm_mode_set modeset = {
.crtc = crtc,
};
crtc->funcs->set_config(&modeset);
}
}
return 0;
}
static struct drm_encoder_slave_funcs ch7006_encoder_funcs = {
.set_config = ch7006_encoder_set_config,
.destroy = ch7006_encoder_destroy,
.dpms = ch7006_encoder_dpms,
.save = ch7006_encoder_save,
.restore = ch7006_encoder_restore,
.mode_fixup = ch7006_encoder_mode_fixup,
.mode_valid = ch7006_encoder_mode_valid,
.mode_set = ch7006_encoder_mode_set,
.detect = ch7006_encoder_detect,
.get_modes = ch7006_encoder_get_modes,
.create_resources = ch7006_encoder_create_resources,
.set_property = ch7006_encoder_set_property,
};
/* I2C driver functions */
static int ch7006_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
uint8_t addr = CH7006_VERSION_ID;
uint8_t val;
int ret;
ch7006_dbg(client, "\n");
ret = i2c_master_send(client, &addr, sizeof(addr));
if (ret < 0)
goto fail;
ret = i2c_master_recv(client, &val, sizeof(val));
if (ret < 0)
goto fail;
ch7006_info(client, "Detected version ID: %x\n", val);
return 0;
fail:
ch7006_err(client, "Error %d reading version ID\n", ret);
return -ENODEV;
}
static int ch7006_remove(struct i2c_client *client)
{
ch7006_dbg(client, "\n");
return 0;
}
static int ch7006_encoder_init(struct i2c_client *client,
struct drm_device *dev,
struct drm_encoder_slave *encoder)
{
struct ch7006_priv *priv;
int i;
ch7006_dbg(client, "\n");
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
encoder->slave_priv = priv;
encoder->slave_funcs = &ch7006_encoder_funcs;
priv->norm = TV_NORM_PAL;
priv->select_subconnector = DRM_MODE_SUBCONNECTOR_Automatic;
priv->subconnector = DRM_MODE_SUBCONNECTOR_Unknown;
priv->scale = 1;
priv->contrast = 50;
priv->brightness = 50;
priv->flicker = 50;
priv->hmargin = 50;
priv->vmargin = 50;
priv->last_dpms = -1;
if (ch7006_tv_norm) {
for (i = 0; i < NUM_TV_NORMS; i++) {
if (!strcmp(ch7006_tv_norm_names[i], ch7006_tv_norm)) {
priv->norm = i;
break;
}
}
if (i == NUM_TV_NORMS)
ch7006_err(client, "Invalid TV norm setting \"%s\".\n",
ch7006_tv_norm);
}
if (ch7006_scale >= 0 && ch7006_scale <= 2)
priv->scale = ch7006_scale;
else
ch7006_err(client, "Invalid scale setting \"%d\".\n",
ch7006_scale);
return 0;
}
static struct i2c_device_id ch7006_ids[] = {
{ "ch7006", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ch7006_ids);
static struct drm_i2c_encoder_driver ch7006_driver = {
.i2c_driver = {
.probe = ch7006_probe,
.remove = ch7006_remove,
.driver = {
.name = "ch7006",
},
.id_table = ch7006_ids,
},
.encoder_init = ch7006_encoder_init,
};
/* Module initialization */
static int __init ch7006_init(void)
{
return drm_i2c_encoder_register(THIS_MODULE, &ch7006_driver);
}
static void __exit ch7006_exit(void)
{
drm_i2c_encoder_unregister(&ch7006_driver);
}
int ch7006_debug;
module_param_named(debug, ch7006_debug, int, 0600);
MODULE_PARM_DESC(debug, "Enable debug output.");
char *ch7006_tv_norm;
module_param_named(tv_norm, ch7006_tv_norm, charp, 0600);
MODULE_PARM_DESC(tv_norm, "Default TV norm.\n"
"\t\tSupported: PAL, PAL-M, PAL-N, PAL-Nc, PAL-60, NTSC-M, NTSC-J.\n"
"\t\tDefault: PAL");
int ch7006_scale = 1;
module_param_named(scale, ch7006_scale, int, 0600);
MODULE_PARM_DESC(scale, "Default scale.\n"
"\t\tSupported: 0 -> Select video modes with a higher blanking ratio.\n"
"\t\t\t1 -> Select default video modes.\n"
"\t\t\t2 -> Select video modes with a lower blanking ratio.");
MODULE_AUTHOR("Francisco Jerez <currojerez@riseup.net>");
MODULE_DESCRIPTION("Chrontel ch7006 TV encoder driver");
MODULE_LICENSE("GPL and additional rights");
module_init(ch7006_init);
module_exit(ch7006_exit);
/*
* Copyright (C) 2009 Francisco Jerez.
* 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 "ch7006_priv.h"
char *ch7006_tv_norm_names[] = {
[TV_NORM_PAL] = "PAL",
[TV_NORM_PAL_M] = "PAL-M",
[TV_NORM_PAL_N] = "PAL-N",
[TV_NORM_PAL_NC] = "PAL-Nc",
[TV_NORM_PAL_60] = "PAL-60",
[TV_NORM_NTSC_M] = "NTSC-M",
[TV_NORM_NTSC_J] = "NTSC-J",
};
#define NTSC_LIKE_TIMINGS .vrefresh = 60 * fixed1/1.001, \
.vdisplay = 480, \
.vtotal = 525, \
.hvirtual = 660
#define PAL_LIKE_TIMINGS .vrefresh = 50 * fixed1, \
.vdisplay = 576, \
.vtotal = 625, \
.hvirtual = 810
struct ch7006_tv_norm_info ch7006_tv_norms[] = {
[TV_NORM_NTSC_M] = {
NTSC_LIKE_TIMINGS,
.black_level = 0.339 * fixed1,
.subc_freq = 3579545 * fixed1,
.dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, NTSC),
.voffset = 0,
},
[TV_NORM_NTSC_J] = {
NTSC_LIKE_TIMINGS,
.black_level = 0.286 * fixed1,
.subc_freq = 3579545 * fixed1,
.dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, NTSC_J),
.voffset = 0,
},
[TV_NORM_PAL] = {
PAL_LIKE_TIMINGS,
.black_level = 0.3 * fixed1,
.subc_freq = 4433618.75 * fixed1,
.dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, PAL),
.voffset = 0,
},
[TV_NORM_PAL_M] = {
NTSC_LIKE_TIMINGS,
.black_level = 0.339 * fixed1,
.subc_freq = 3575611.433 * fixed1,
.dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, PAL_M),
.voffset = 16,
},
/* The following modes seem to work right but they're
* undocumented */
[TV_NORM_PAL_N] = {
PAL_LIKE_TIMINGS,
.black_level = 0.339 * fixed1,
.subc_freq = 4433618.75 * fixed1,
.dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, PAL),
.voffset = 0,
},
[TV_NORM_PAL_NC] = {
PAL_LIKE_TIMINGS,
.black_level = 0.3 * fixed1,
.subc_freq = 3582056.25 * fixed1,
.dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, PAL),
.voffset = 0,
},
[TV_NORM_PAL_60] = {
NTSC_LIKE_TIMINGS,
.black_level = 0.3 * fixed1,
.subc_freq = 4433618.75 * fixed1,
.dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, PAL_M),
.voffset = 16,
},
};
#define __MODE(f, hd, vd, ht, vt, hsynp, vsynp, \
subc, scale, scale_mask, norm_mask, e_hd, e_vd) { \
.mode = { \
.name = #hd "x" #vd, \
.status = 0, \
.type = DRM_MODE_TYPE_DRIVER, \
.clock = f, \
.hdisplay = hd, \
.hsync_start = e_hd + 16, \
.hsync_end = e_hd + 80, \
.htotal = ht, \
.hskew = 0, \
.vdisplay = vd, \
.vsync_start = vd + 10, \
.vsync_end = vd + 26, \
.vtotal = vt, \
.vscan = 0, \
.flags = DRM_MODE_FLAG_##hsynp##HSYNC | \
DRM_MODE_FLAG_##vsynp##VSYNC, \
.vrefresh = 0, \
}, \
.enc_hdisp = e_hd, \
.enc_vdisp = e_vd, \
.subc_coeff = subc * fixed1, \
.dispmode = bitfs(CH7006_DISPMODE_SCALING_RATIO, scale) | \
bitfs(CH7006_DISPMODE_INPUT_RES, e_hd##x##e_vd), \
.valid_scales = scale_mask, \
.valid_norms = norm_mask \
}
#define MODE(f, hd, vd, ht, vt, hsynp, vsynp, \
subc, scale, scale_mask, norm_mask) \
__MODE(f, hd, vd, ht, vt, hsynp, vsynp, subc, scale, \
scale_mask, norm_mask, hd, vd)
#define NTSC_LIKE (1 << TV_NORM_NTSC_M | 1 << TV_NORM_NTSC_J | \
1 << TV_NORM_PAL_M | 1 << TV_NORM_PAL_60)
#define PAL_LIKE (1 << TV_NORM_PAL | 1 << TV_NORM_PAL_N | 1 << TV_NORM_PAL_NC)
struct ch7006_mode ch7006_modes[] = {
MODE(21000, 512, 384, 840, 500, N, N, 181.797557582, 5_4, 0x6, PAL_LIKE),
MODE(26250, 512, 384, 840, 625, N, N, 145.438046066, 1_1, 0x1, PAL_LIKE),
MODE(20140, 512, 384, 800, 420, N, N, 213.257083791, 5_4, 0x4, NTSC_LIKE),
MODE(24671, 512, 384, 784, 525, N, N, 174.0874153, 1_1, 0x3, NTSC_LIKE),
MODE(28125, 720, 400, 1125, 500, N, N, 135.742176298, 5_4, 0x6, PAL_LIKE),
MODE(34875, 720, 400, 1116, 625, N, N, 109.469496898, 1_1, 0x1, PAL_LIKE),
MODE(23790, 720, 400, 945, 420, N, N, 160.475642016, 5_4, 0x4, NTSC_LIKE),
MODE(29455, 720, 400, 936, 525, N, N, 129.614941843, 1_1, 0x3, NTSC_LIKE),
MODE(25000, 640, 400, 1000, 500, N, N, 152.709948279, 5_4, 0x6, PAL_LIKE),
MODE(31500, 640, 400, 1008, 625, N, N, 121.198371646, 1_1, 0x1, PAL_LIKE),
MODE(21147, 640, 400, 840, 420, N, N, 180.535097338, 5_4, 0x4, NTSC_LIKE),
MODE(26434, 640, 400, 840, 525, N, N, 144.42807787, 1_1, 0x2, NTSC_LIKE),
MODE(30210, 640, 400, 840, 600, N, N, 126.374568276, 7_8, 0x1, NTSC_LIKE),
MODE(21000, 640, 480, 840, 500, N, N, 181.797557582, 5_4, 0x4, PAL_LIKE),
MODE(26250, 640, 480, 840, 625, N, N, 145.438046066, 1_1, 0x2, PAL_LIKE),
MODE(31500, 640, 480, 840, 750, N, N, 121.198371646, 5_6, 0x1, PAL_LIKE),
MODE(24671, 640, 480, 784, 525, N, N, 174.0874153, 1_1, 0x4, NTSC_LIKE),
MODE(28196, 640, 480, 784, 600, N, N, 152.326488422, 7_8, 0x2, NTSC_LIKE),
MODE(30210, 640, 480, 800, 630, N, N, 142.171389101, 5_6, 0x1, NTSC_LIKE),
__MODE(29500, 720, 576, 944, 625, P, P, 145.592111636, 1_1, 0x7, PAL_LIKE, 800, 600),
MODE(36000, 800, 600, 960, 750, P, P, 119.304647022, 5_6, 0x6, PAL_LIKE),
MODE(39000, 800, 600, 936, 836, P, P, 110.127366499, 3_4, 0x1, PAL_LIKE),
MODE(39273, 800, 600, 1040, 630, P, P, 145.816809399, 5_6, 0x4, NTSC_LIKE),
MODE(43636, 800, 600, 1040, 700, P, P, 131.235128487, 3_4, 0x2, NTSC_LIKE),
MODE(47832, 800, 600, 1064, 750, P, P, 119.723275165, 7_10, 0x1, NTSC_LIKE),
{}
};
struct ch7006_mode *ch7006_lookup_mode(struct drm_encoder *encoder,
struct drm_display_mode *drm_mode)
{
struct ch7006_priv *priv = to_ch7006_priv(encoder);
struct ch7006_mode *mode;
for (mode = ch7006_modes; mode->mode.clock; mode++) {
if (~mode->valid_norms & 1<<priv->norm)
continue;
if (mode->mode.hdisplay != drm_mode->hdisplay ||
mode->mode.vdisplay != drm_mode->vdisplay ||
mode->mode.vtotal != drm_mode->vtotal ||
mode->mode.htotal != drm_mode->htotal ||
mode->mode.clock != drm_mode->clock)
continue;
return mode;
}
return NULL;
}
/* Some common HW state calculation code */
void ch7006_setup_levels(struct drm_encoder *encoder)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
struct ch7006_priv *priv = to_ch7006_priv(encoder);
uint8_t *regs = priv->state.regs;
struct ch7006_tv_norm_info *norm = &ch7006_tv_norms[priv->norm];
int gain;
int black_level;
/* Set DAC_GAIN if the voltage drop between white and black is
* high enough. */
if (norm->black_level < 339*fixed1/1000) {
gain = 76;
regs[CH7006_INPUT_FORMAT] |= CH7006_INPUT_FORMAT_DAC_GAIN;
} else {
gain = 71;
regs[CH7006_INPUT_FORMAT] &= ~CH7006_INPUT_FORMAT_DAC_GAIN;
}
black_level = round_fixed(norm->black_level*26625)/gain;
/* Correct it with the specified brightness. */
black_level = interpolate(90, black_level, 208, priv->brightness);
regs[CH7006_BLACK_LEVEL] = bitf(CH7006_BLACK_LEVEL_0, black_level);
ch7006_dbg(client, "black level: %d\n", black_level);
}
void ch7006_setup_subcarrier(struct drm_encoder *encoder)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
struct ch7006_priv *priv = to_ch7006_priv(encoder);
struct ch7006_state *state = &priv->state;
struct ch7006_tv_norm_info *norm = &ch7006_tv_norms[priv->norm];
struct ch7006_mode *mode = priv->mode;
uint32_t subc_inc;
subc_inc = round_fixed((mode->subc_coeff >> 8)
* (norm->subc_freq >> 24));
setbitf(state, CH7006_SUBC_INC0, 28, subc_inc);
setbitf(state, CH7006_SUBC_INC1, 24, subc_inc);
setbitf(state, CH7006_SUBC_INC2, 20, subc_inc);
setbitf(state, CH7006_SUBC_INC3, 16, subc_inc);
setbitf(state, CH7006_SUBC_INC4, 12, subc_inc);
setbitf(state, CH7006_SUBC_INC5, 8, subc_inc);
setbitf(state, CH7006_SUBC_INC6, 4, subc_inc);
setbitf(state, CH7006_SUBC_INC7, 0, subc_inc);
ch7006_dbg(client, "subcarrier inc: %u\n", subc_inc);
}
void ch7006_setup_pll(struct drm_encoder *encoder)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
struct ch7006_priv *priv = to_ch7006_priv(encoder);
uint8_t *regs = priv->state.regs;
struct ch7006_mode *mode = priv->mode;
int n, best_n = 0;
int m, best_m = 0;
int freq, best_freq = 0;
for (n = 0; n < CH7006_MAXN; n++) {
for (m = 0; m < CH7006_MAXM; m++) {
freq = CH7006_FREQ0*(n+2)/(m+2);
if (abs(freq - mode->mode.clock) <
abs(best_freq - mode->mode.clock)) {
best_freq = freq;
best_n = n;
best_m = m;
}
}
}
regs[CH7006_PLLOV] = bitf(CH7006_PLLOV_N_8, best_n) |
bitf(CH7006_PLLOV_M_8, best_m);
regs[CH7006_PLLM] = bitf(CH7006_PLLM_0, best_m);
regs[CH7006_PLLN] = bitf(CH7006_PLLN_0, best_n);
if (best_n < 108)
regs[CH7006_PLL_CONTROL] |= CH7006_PLL_CONTROL_CAPACITOR;
else
regs[CH7006_PLL_CONTROL] &= ~CH7006_PLL_CONTROL_CAPACITOR;
ch7006_dbg(client, "n=%d m=%d f=%d c=%d\n",
best_n, best_m, best_freq, best_n < 108);
}
void ch7006_setup_power_state(struct drm_encoder *encoder)
{
struct ch7006_priv *priv = to_ch7006_priv(encoder);
uint8_t *power = &priv->state.regs[CH7006_POWER];
int subconnector;
subconnector = priv->select_subconnector ? priv->select_subconnector :
priv->subconnector;
*power = CH7006_POWER_RESET;
if (priv->last_dpms == DRM_MODE_DPMS_ON) {
switch (subconnector) {
case DRM_MODE_SUBCONNECTOR_SVIDEO:
*power |= bitfs(CH7006_POWER_LEVEL, CVBS_OFF);
break;
case DRM_MODE_SUBCONNECTOR_Composite:
*power |= bitfs(CH7006_POWER_LEVEL, SVIDEO_OFF);
break;
case DRM_MODE_SUBCONNECTOR_SCART:
*power |= bitfs(CH7006_POWER_LEVEL, NORMAL) |
CH7006_POWER_SCART;
break;
}
} else {
*power |= bitfs(CH7006_POWER_LEVEL, FULL_POWER_OFF);
}
}
void ch7006_setup_properties(struct drm_encoder *encoder)
{
struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
struct ch7006_priv *priv = to_ch7006_priv(encoder);
struct ch7006_state *state = &priv->state;
struct ch7006_tv_norm_info *norm = &ch7006_tv_norms[priv->norm];
struct ch7006_mode *ch_mode = priv->mode;
struct drm_display_mode *mode = &ch_mode->mode;
uint8_t *regs = state->regs;
int flicker, contrast, hpos, vpos;
uint64_t scale, aspect;
flicker = interpolate(0, 2, 3, priv->flicker);
regs[CH7006_FFILTER] = bitf(CH7006_FFILTER_TEXT, flicker) |
bitf(CH7006_FFILTER_LUMA, flicker) |
bitf(CH7006_FFILTER_CHROMA, 1);
contrast = interpolate(0, 5, 7, priv->contrast);
regs[CH7006_CONTRAST] = bitf(CH7006_CONTRAST_0, contrast);
scale = norm->vtotal*fixed1;
do_div(scale, mode->vtotal);
aspect = ch_mode->enc_hdisp*fixed1;
do_div(aspect, ch_mode->enc_vdisp);
hpos = round_fixed((norm->hvirtual * aspect - mode->hdisplay * scale)
* priv->hmargin * mode->vtotal) / norm->vtotal / 100 / 4;
setbitf(state, CH7006_POV, HPOS_8, hpos);
setbitf(state, CH7006_HPOS, 0, hpos);
vpos = max(0, norm->vdisplay - round_fixed(mode->vdisplay*scale)
+ norm->voffset) * priv->vmargin / 100 / 2;
setbitf(state, CH7006_POV, VPOS_8, vpos);
setbitf(state, CH7006_VPOS, 0, vpos);
ch7006_dbg(client, "hpos: %d, vpos: %d\n", hpos, vpos);
}
/* HW access functions */
void ch7006_write(struct i2c_client *client, uint8_t addr, uint8_t val)
{
uint8_t buf[] = {addr, val};
int ret;
ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
if (ret < 0)
ch7006_err(client, "Error %d writing to subaddress 0x%x\n",
ret, addr);
}
uint8_t ch7006_read(struct i2c_client *client, uint8_t addr)
{
uint8_t val;
int ret;
ret = i2c_master_send(client, &addr, sizeof(addr));
if (ret < 0)
goto fail;
ret = i2c_master_recv(client, &val, sizeof(val));
if (ret < 0)
goto fail;
return val;
fail:
ch7006_err(client, "Error %d reading from subaddress 0x%x\n",
ret, addr);
return 0;
}
void ch7006_state_load(struct i2c_client *client,
struct ch7006_state *state)
{
ch7006_load_reg(client, state, CH7006_POWER);
ch7006_load_reg(client, state, CH7006_DISPMODE);
ch7006_load_reg(client, state, CH7006_FFILTER);
ch7006_load_reg(client, state, CH7006_BWIDTH);
ch7006_load_reg(client, state, CH7006_INPUT_FORMAT);
ch7006_load_reg(client, state, CH7006_CLKMODE);
ch7006_load_reg(client, state, CH7006_START_ACTIVE);
ch7006_load_reg(client, state, CH7006_POV);
ch7006_load_reg(client, state, CH7006_BLACK_LEVEL);
ch7006_load_reg(client, state, CH7006_HPOS);
ch7006_load_reg(client, state, CH7006_VPOS);
ch7006_load_reg(client, state, CH7006_INPUT_SYNC);
ch7006_load_reg(client, state, CH7006_DETECT);
ch7006_load_reg(client, state, CH7006_CONTRAST);
ch7006_load_reg(client, state, CH7006_PLLOV);
ch7006_load_reg(client, state, CH7006_PLLM);
ch7006_load_reg(client, state, CH7006_PLLN);
ch7006_load_reg(client, state, CH7006_BCLKOUT);
ch7006_load_reg(client, state, CH7006_SUBC_INC0);
ch7006_load_reg(client, state, CH7006_SUBC_INC1);
ch7006_load_reg(client, state, CH7006_SUBC_INC2);
ch7006_load_reg(client, state, CH7006_SUBC_INC3);
ch7006_load_reg(client, state, CH7006_SUBC_INC4);
ch7006_load_reg(client, state, CH7006_SUBC_INC5);
ch7006_load_reg(client, state, CH7006_SUBC_INC6);
ch7006_load_reg(client, state, CH7006_SUBC_INC7);
ch7006_load_reg(client, state, CH7006_PLL_CONTROL);
ch7006_load_reg(client, state, CH7006_CALC_SUBC_INC0);
/* I don't know what this is for, but otherwise I get no
* signal.
*/
ch7006_write(client, 0x3d, 0x0);
}
void ch7006_state_save(struct i2c_client *client,
struct ch7006_state *state)
{
ch7006_save_reg(client, state, CH7006_POWER);
ch7006_save_reg(client, state, CH7006_DISPMODE);
ch7006_save_reg(client, state, CH7006_FFILTER);
ch7006_save_reg(client, state, CH7006_BWIDTH);
ch7006_save_reg(client, state, CH7006_INPUT_FORMAT);
ch7006_save_reg(client, state, CH7006_CLKMODE);
ch7006_save_reg(client, state, CH7006_START_ACTIVE);
ch7006_save_reg(client, state, CH7006_POV);
ch7006_save_reg(client, state, CH7006_BLACK_LEVEL);
ch7006_save_reg(client, state, CH7006_HPOS);
ch7006_save_reg(client, state, CH7006_VPOS);
ch7006_save_reg(client, state, CH7006_INPUT_SYNC);
ch7006_save_reg(client, state, CH7006_DETECT);
ch7006_save_reg(client, state, CH7006_CONTRAST);
ch7006_save_reg(client, state, CH7006_PLLOV);
ch7006_save_reg(client, state, CH7006_PLLM);
ch7006_save_reg(client, state, CH7006_PLLN);
ch7006_save_reg(client, state, CH7006_BCLKOUT);
ch7006_save_reg(client, state, CH7006_SUBC_INC0);
ch7006_save_reg(client, state, CH7006_SUBC_INC1);
ch7006_save_reg(client, state, CH7006_SUBC_INC2);
ch7006_save_reg(client, state, CH7006_SUBC_INC3);
ch7006_save_reg(client, state, CH7006_SUBC_INC4);
ch7006_save_reg(client, state, CH7006_SUBC_INC5);
ch7006_save_reg(client, state, CH7006_SUBC_INC6);
ch7006_save_reg(client, state, CH7006_SUBC_INC7);
ch7006_save_reg(client, state, CH7006_PLL_CONTROL);
ch7006_save_reg(client, state, CH7006_CALC_SUBC_INC0);
state->regs[CH7006_FFILTER] = (state->regs[CH7006_FFILTER] & 0xf0) |
(state->regs[CH7006_FFILTER] & 0x0c) >> 2 |
(state->regs[CH7006_FFILTER] & 0x03) << 2;
}
/*
* Copyright (C) 2009 Francisco Jerez.
* 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.
*
*/
#ifndef __DRM_I2C_CH7006_PRIV_H__
#define __DRM_I2C_CH7006_PRIV_H__
#include "drmP.h"
#include "drm_crtc_helper.h"
#include "drm_encoder_slave.h"
#include "i2c/ch7006.h"
typedef int64_t fixed;
#define fixed1 (1LL << 32)
enum ch7006_tv_norm {
TV_NORM_PAL,
TV_NORM_PAL_M,
TV_NORM_PAL_N,
TV_NORM_PAL_NC,
TV_NORM_PAL_60,
TV_NORM_NTSC_M,
TV_NORM_NTSC_J,
NUM_TV_NORMS
};
struct ch7006_tv_norm_info {
fixed vrefresh;
int vdisplay;
int vtotal;
int hvirtual;
fixed subc_freq;
fixed black_level;
uint32_t dispmode;
int voffset;
};
struct ch7006_mode {
struct drm_display_mode mode;
int enc_hdisp;
int enc_vdisp;
fixed subc_coeff;
uint32_t dispmode;
uint32_t valid_scales;
uint32_t valid_norms;
};
struct ch7006_state {
uint8_t regs[0x26];
};
struct ch7006_priv {
struct ch7006_encoder_params *params;
struct ch7006_mode *mode;
struct ch7006_state state;
struct ch7006_state saved_state;
struct drm_property *scale_property;
int select_subconnector;
int subconnector;
int hmargin;
int vmargin;
enum ch7006_tv_norm norm;
int brightness;
int contrast;
int flicker;
int scale;
int last_dpms;
};
#define to_ch7006_priv(x) \
((struct ch7006_priv *)to_encoder_slave(x)->slave_priv)
extern int ch7006_debug;
extern char *ch7006_tv_norm;
extern int ch7006_scale;
extern char *ch7006_tv_norm_names[];
extern struct ch7006_tv_norm_info ch7006_tv_norms[];
extern struct ch7006_mode ch7006_modes[];
struct ch7006_mode *ch7006_lookup_mode(struct drm_encoder *encoder,
struct drm_display_mode *drm_mode);
void ch7006_setup_levels(struct drm_encoder *encoder);
void ch7006_setup_subcarrier(struct drm_encoder *encoder);
void ch7006_setup_pll(struct drm_encoder *encoder);
void ch7006_setup_power_state(struct drm_encoder *encoder);
void ch7006_setup_properties(struct drm_encoder *encoder);
void ch7006_write(struct i2c_client *client, uint8_t addr, uint8_t val);
uint8_t ch7006_read(struct i2c_client *client, uint8_t addr);
void ch7006_state_load(struct i2c_client *client,
struct ch7006_state *state);
void ch7006_state_save(struct i2c_client *client,
struct ch7006_state *state);
/* Some helper macros */
#define ch7006_dbg(client, format, ...) do { \
if (ch7006_debug) \
dev_printk(KERN_DEBUG, &client->dev, \
"%s: " format, __func__, ## __VA_ARGS__); \
} while (0)
#define ch7006_info(client, format, ...) \
dev_info(&client->dev, format, __VA_ARGS__)
#define ch7006_err(client, format, ...) \
dev_err(&client->dev, format, __VA_ARGS__)
#define __mask(src, bitfield) \
(((2 << (1 ? bitfield)) - 1) & ~((1 << (0 ? bitfield)) - 1))
#define mask(bitfield) __mask(bitfield)
#define __bitf(src, bitfield, x) \
(((x) >> (src) << (0 ? bitfield)) & __mask(src, bitfield))
#define bitf(bitfield, x) __bitf(bitfield, x)
#define bitfs(bitfield, s) __bitf(bitfield, bitfield##_##s)
#define setbitf(state, reg, bitfield, x) \
state->regs[reg] = (state->regs[reg] & ~mask(reg##_##bitfield)) \
| bitf(reg##_##bitfield, x)
#define __unbitf(src, bitfield, x) \
((x & __mask(src, bitfield)) >> (0 ? bitfield) << (src))
#define unbitf(bitfield, x) __unbitf(bitfield, x)
static inline int interpolate(int y0, int y1, int y2, int x)
{
return y1 + (x < 50 ? y1 - y0 : y2 - y1) * (x - 50) / 50;
}
static inline int32_t round_fixed(fixed x)
{
return (x + fixed1/2) >> 32;
}
#define ch7006_load_reg(client, state, reg) ch7006_write(client, reg, state->regs[reg])
#define ch7006_save_reg(client, state, reg) state->regs[reg] = ch7006_read(client, reg)
/* Fixed hardware specs */
#define CH7006_FREQ0 14318
#define CH7006_MAXN 650
#define CH7006_MAXM 315
/* Register definitions */
#define CH7006_DISPMODE 0x00
#define CH7006_DISPMODE_INPUT_RES 0, 7:5
#define CH7006_DISPMODE_INPUT_RES_512x384 0x0
#define CH7006_DISPMODE_INPUT_RES_720x400 0x1
#define CH7006_DISPMODE_INPUT_RES_640x400 0x2
#define CH7006_DISPMODE_INPUT_RES_640x480 0x3
#define CH7006_DISPMODE_INPUT_RES_800x600 0x4
#define CH7006_DISPMODE_INPUT_RES_NATIVE 0x5
#define CH7006_DISPMODE_OUTPUT_STD 0, 4:3
#define CH7006_DISPMODE_OUTPUT_STD_PAL 0x0
#define CH7006_DISPMODE_OUTPUT_STD_NTSC 0x1
#define CH7006_DISPMODE_OUTPUT_STD_PAL_M 0x2
#define CH7006_DISPMODE_OUTPUT_STD_NTSC_J 0x3
#define CH7006_DISPMODE_SCALING_RATIO 0, 2:0
#define CH7006_DISPMODE_SCALING_RATIO_5_4 0x0
#define CH7006_DISPMODE_SCALING_RATIO_1_1 0x1
#define CH7006_DISPMODE_SCALING_RATIO_7_8 0x2
#define CH7006_DISPMODE_SCALING_RATIO_5_6 0x3
#define CH7006_DISPMODE_SCALING_RATIO_3_4 0x4
#define CH7006_DISPMODE_SCALING_RATIO_7_10 0x5
#define CH7006_FFILTER 0x01
#define CH7006_FFILTER_TEXT 0, 5:4
#define CH7006_FFILTER_LUMA 0, 3:2
#define CH7006_FFILTER_CHROMA 0, 1:0
#define CH7006_FFILTER_CHROMA_NO_DCRAWL 0x3
#define CH7006_BWIDTH 0x03
#define CH7006_BWIDTH_5L_FFILER (1 << 7)
#define CH7006_BWIDTH_CVBS_NO_CHROMA (1 << 6)
#define CH7006_BWIDTH_CHROMA 0, 5:4
#define CH7006_BWIDTH_SVIDEO_YPEAK (1 << 3)
#define CH7006_BWIDTH_SVIDEO_LUMA 0, 2:1
#define CH7006_BWIDTH_CVBS_LUMA 0, 0:0
#define CH7006_INPUT_FORMAT 0x04
#define CH7006_INPUT_FORMAT_DAC_GAIN (1 << 6)
#define CH7006_INPUT_FORMAT_RGB_PASS_THROUGH (1 << 5)
#define CH7006_INPUT_FORMAT_FORMAT 0, 3:0
#define CH7006_INPUT_FORMAT_FORMAT_RGB16 0x0
#define CH7006_INPUT_FORMAT_FORMAT_YCrCb24m16 0x1
#define CH7006_INPUT_FORMAT_FORMAT_RGB24m16 0x2
#define CH7006_INPUT_FORMAT_FORMAT_RGB15 0x3
#define CH7006_INPUT_FORMAT_FORMAT_RGB24m12C 0x4
#define CH7006_INPUT_FORMAT_FORMAT_RGB24m12I 0x5
#define CH7006_INPUT_FORMAT_FORMAT_RGB24m8 0x6
#define CH7006_INPUT_FORMAT_FORMAT_RGB16m8 0x7
#define CH7006_INPUT_FORMAT_FORMAT_RGB15m8 0x8
#define CH7006_INPUT_FORMAT_FORMAT_YCrCb24m8 0x9
#define CH7006_CLKMODE 0x06
#define CH7006_CLKMODE_SUBC_LOCK (1 << 7)
#define CH7006_CLKMODE_MASTER (1 << 6)
#define CH7006_CLKMODE_POS_EDGE (1 << 4)
#define CH7006_CLKMODE_XCM 0, 3:2
#define CH7006_CLKMODE_PCM 0, 1:0
#define CH7006_START_ACTIVE 0x07
#define CH7006_START_ACTIVE_0 0, 7:0
#define CH7006_POV 0x08
#define CH7006_POV_START_ACTIVE_8 8, 2:2
#define CH7006_POV_HPOS_8 8, 1:1
#define CH7006_POV_VPOS_8 8, 0:0
#define CH7006_BLACK_LEVEL 0x09
#define CH7006_BLACK_LEVEL_0 0, 7:0
#define CH7006_HPOS 0x0a
#define CH7006_HPOS_0 0, 7:0
#define CH7006_VPOS 0x0b
#define CH7006_VPOS_0 0, 7:0
#define CH7006_INPUT_SYNC 0x0d
#define CH7006_INPUT_SYNC_EMBEDDED (1 << 3)
#define CH7006_INPUT_SYNC_OUTPUT (1 << 2)
#define CH7006_INPUT_SYNC_PVSYNC (1 << 1)
#define CH7006_INPUT_SYNC_PHSYNC (1 << 0)
#define CH7006_POWER 0x0e
#define CH7006_POWER_SCART (1 << 4)
#define CH7006_POWER_RESET (1 << 3)
#define CH7006_POWER_LEVEL 0, 2:0
#define CH7006_POWER_LEVEL_CVBS_OFF 0x0
#define CH7006_POWER_LEVEL_POWER_OFF 0x1
#define CH7006_POWER_LEVEL_SVIDEO_OFF 0x2
#define CH7006_POWER_LEVEL_NORMAL 0x3
#define CH7006_POWER_LEVEL_FULL_POWER_OFF 0x4
#define CH7006_DETECT 0x10
#define CH7006_DETECT_SVIDEO_Y_TEST (1 << 3)
#define CH7006_DETECT_SVIDEO_C_TEST (1 << 2)
#define CH7006_DETECT_CVBS_TEST (1 << 1)
#define CH7006_DETECT_SENSE (1 << 0)
#define CH7006_CONTRAST 0x11
#define CH7006_CONTRAST_0 0, 2:0
#define CH7006_PLLOV 0x13
#define CH7006_PLLOV_N_8 8, 2:1
#define CH7006_PLLOV_M_8 8, 0:0
#define CH7006_PLLM 0x14
#define CH7006_PLLM_0 0, 7:0
#define CH7006_PLLN 0x15
#define CH7006_PLLN_0 0, 7:0
#define CH7006_BCLKOUT 0x17
#define CH7006_SUBC_INC0 0x18
#define CH7006_SUBC_INC0_28 28, 3:0
#define CH7006_SUBC_INC1 0x19
#define CH7006_SUBC_INC1_24 24, 3:0
#define CH7006_SUBC_INC2 0x1a
#define CH7006_SUBC_INC2_20 20, 3:0
#define CH7006_SUBC_INC3 0x1b
#define CH7006_SUBC_INC3_GPIO1_VAL (1 << 7)
#define CH7006_SUBC_INC3_GPIO0_VAL (1 << 6)
#define CH7006_SUBC_INC3_POUT_3_3V (1 << 5)
#define CH7006_SUBC_INC3_POUT_INV (1 << 4)
#define CH7006_SUBC_INC3_16 16, 3:0
#define CH7006_SUBC_INC4 0x1c
#define CH7006_SUBC_INC4_GPIO1_IN (1 << 7)
#define CH7006_SUBC_INC4_GPIO0_IN (1 << 6)
#define CH7006_SUBC_INC4_DS_INPUT (1 << 4)
#define CH7006_SUBC_INC4_12 12, 3:0
#define CH7006_SUBC_INC5 0x1d
#define CH7006_SUBC_INC5_8 8, 3:0
#define CH7006_SUBC_INC6 0x1e
#define CH7006_SUBC_INC6_4 4, 3:0
#define CH7006_SUBC_INC7 0x1f
#define CH7006_SUBC_INC7_0 0, 3:0
#define CH7006_PLL_CONTROL 0x20
#define CH7006_PLL_CONTROL_CPI (1 << 5)
#define CH7006_PLL_CONTROL_CAPACITOR (1 << 4)
#define CH7006_PLL_CONTROL_7STAGES (1 << 3)
#define CH7006_PLL_CONTROL_DIGITAL_5V (1 << 2)
#define CH7006_PLL_CONTROL_ANALOG_5V (1 << 1)
#define CH7006_PLL_CONTROL_MEMORY_5V (1 << 0)
#define CH7006_CALC_SUBC_INC0 0x21
#define CH7006_CALC_SUBC_INC0_24 24, 4:3
#define CH7006_CALC_SUBC_INC0_HYST 0, 2:1
#define CH7006_CALC_SUBC_INC0_AUTO (1 << 0)
#define CH7006_CALC_SUBC_INC1 0x22
#define CH7006_CALC_SUBC_INC1_16 16, 7:0
#define CH7006_CALC_SUBC_INC2 0x23
#define CH7006_CALC_SUBC_INC2_8 8, 7:0
#define CH7006_CALC_SUBC_INC3 0x24
#define CH7006_CALC_SUBC_INC3_0 0, 7:0
#define CH7006_VERSION_ID 0x25
#endif
config DRM_NOUVEAU
tristate "Nouveau (nVidia) cards"
depends on DRM
select FW_LOADER
select DRM_KMS_HELPER
select DRM_TTM
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
select FB
select FRAMEBUFFER_CONSOLE if !EMBEDDED
select FB_BACKLIGHT if DRM_NOUVEAU_BACKLIGHT
help
Choose this option for open-source nVidia support.
config DRM_NOUVEAU_BACKLIGHT
bool "Support for backlight control"
depends on DRM_NOUVEAU
default y
help
Say Y here if you want to control the backlight of your display
(e.g. a laptop panel).
config DRM_NOUVEAU_DEBUG
bool "Build in Nouveau's debugfs support"
depends on DRM_NOUVEAU && DEBUG_FS
default y
help
Say Y here if you want Nouveau to output debugging information
via debugfs.
menu "I2C encoder or helper chips"
depends on DRM
config DRM_I2C_CH7006
tristate "Chrontel ch7006 TV encoder"
default m if DRM_NOUVEAU
help
Support for Chrontel ch7006 and similar TV encoders, found
on some nVidia video cards.
This driver is currently only useful if you're also using
the nouveau driver.
endmenu
#
# Makefile for the drm device driver. This driver provides support for the
# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
ccflags-y := -Iinclude/drm
nouveau-y := nouveau_drv.o nouveau_state.o nouveau_channel.o nouveau_mem.o \
nouveau_object.o nouveau_irq.o nouveau_notifier.o \
nouveau_sgdma.o nouveau_dma.o \
nouveau_bo.o nouveau_fence.o nouveau_gem.o nouveau_ttm.o \
nouveau_hw.o nouveau_calc.o nouveau_bios.o nouveau_i2c.o \
nouveau_display.o nouveau_connector.o nouveau_fbcon.o \
nouveau_dp.o \
nv04_timer.o \
nv04_mc.o nv40_mc.o nv50_mc.o \
nv04_fb.o nv10_fb.o nv40_fb.o \
nv04_fifo.o nv10_fifo.o nv40_fifo.o nv50_fifo.o \
nv04_graph.o nv10_graph.o nv20_graph.o \
nv40_graph.o nv50_graph.o \
nv04_instmem.o nv50_instmem.o \
nv50_crtc.o nv50_dac.o nv50_sor.o \
nv50_cursor.o nv50_display.o nv50_fbcon.o \
nv04_dac.o nv04_dfp.o nv04_tv.o nv17_tv.o nv17_tv_modes.o \
nv04_crtc.o nv04_display.o nv04_cursor.o nv04_fbcon.o \
nv17_gpio.o
nouveau-$(CONFIG_DRM_NOUVEAU_DEBUG) += nouveau_debugfs.o
nouveau-$(CONFIG_COMPAT) += nouveau_ioc32.o
nouveau-$(CONFIG_DRM_NOUVEAU_BACKLIGHT) += nouveau_backlight.o
nouveau-$(CONFIG_ACPI) += nouveau_acpi.o
obj-$(CONFIG_DRM_NOUVEAU)+= nouveau.o
#include <linux/pci.h>
#include <linux/acpi.h>
#include <acpi/acpi_drivers.h>
#include <acpi/acpi_bus.h>
#include "drmP.h"
#include "drm.h"
#include "drm_sarea.h"
#include "drm_crtc_helper.h"
#include "nouveau_drv.h"
#include "nouveau_drm.h"
#include "nv50_display.h"
#define NOUVEAU_DSM_SUPPORTED 0x00
#define NOUVEAU_DSM_SUPPORTED_FUNCTIONS 0x00
#define NOUVEAU_DSM_ACTIVE 0x01
#define NOUVEAU_DSM_ACTIVE_QUERY 0x00
#define NOUVEAU_DSM_LED 0x02
#define NOUVEAU_DSM_LED_STATE 0x00
#define NOUVEAU_DSM_LED_OFF 0x10
#define NOUVEAU_DSM_LED_STAMINA 0x11
#define NOUVEAU_DSM_LED_SPEED 0x12
#define NOUVEAU_DSM_POWER 0x03
#define NOUVEAU_DSM_POWER_STATE 0x00
#define NOUVEAU_DSM_POWER_SPEED 0x01
#define NOUVEAU_DSM_POWER_STAMINA 0x02
static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result)
{
static char muid[] = {
0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D,
0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4,
};
struct pci_dev *pdev = dev->pdev;
struct acpi_handle *handle;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_object_list input;
union acpi_object params[4];
union acpi_object *obj;
int err;
handle = DEVICE_ACPI_HANDLE(&pdev->dev);
if (!handle)
return -ENODEV;
input.count = 4;
input.pointer = params;
params[0].type = ACPI_TYPE_BUFFER;
params[0].buffer.length = sizeof(muid);
params[0].buffer.pointer = (char *)muid;
params[1].type = ACPI_TYPE_INTEGER;
params[1].integer.value = 0x00000102;
params[2].type = ACPI_TYPE_INTEGER;
params[2].integer.value = func;
params[3].type = ACPI_TYPE_INTEGER;
params[3].integer.value = arg;
err = acpi_evaluate_object(handle, "_DSM", &input, &output);
if (err) {
NV_INFO(dev, "failed to evaluate _DSM: %d\n", err);
return err;
}
obj = (union acpi_object *)output.pointer;
if (obj->type == ACPI_TYPE_INTEGER)
if (obj->integer.value == 0x80000002)
return -ENODEV;
if (obj->type == ACPI_TYPE_BUFFER) {
if (obj->buffer.length == 4 && result) {
*result = 0;
*result |= obj->buffer.pointer[0];
*result |= (obj->buffer.pointer[1] << 8);
*result |= (obj->buffer.pointer[2] << 16);
*result |= (obj->buffer.pointer[3] << 24);
}
}
kfree(output.pointer);
return 0;
}
int nouveau_hybrid_setup(struct drm_device *dev)
{
int result;
if (nouveau_dsm(dev, NOUVEAU_DSM_ACTIVE, NOUVEAU_DSM_ACTIVE_QUERY,
&result))
return -ENODEV;
NV_INFO(dev, "_DSM hardware status gave 0x%x\n", result);
if (result & 0x1) { /* Stamina mode - disable the external GPU */
nouveau_dsm(dev, NOUVEAU_DSM_LED, NOUVEAU_DSM_LED_STAMINA,
NULL);
nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_STAMINA,
NULL);
} else { /* Ensure that the external GPU is enabled */
nouveau_dsm(dev, NOUVEAU_DSM_LED, NOUVEAU_DSM_LED_SPEED, NULL);
nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_SPEED,
NULL);
}
return 0;
}
bool nouveau_dsm_probe(struct drm_device *dev)
{
int support = 0;
if (nouveau_dsm(dev, NOUVEAU_DSM_SUPPORTED,
NOUVEAU_DSM_SUPPORTED_FUNCTIONS, &support))
return false;
if (!support)
return false;
return true;
}
/*
* Copyright (C) 2009 Red Hat <mjg@redhat.com>
*
* 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.
*
*/
/*
* Authors:
* Matthew Garrett <mjg@redhat.com>
*
* Register locations derived from NVClock by Roderick Colenbrander
*/
#include <linux/backlight.h>
#include "drmP.h"
#include "nouveau_drv.h"
#include "nouveau_drm.h"
#include "nouveau_reg.h"
static int nv40_get_intensity(struct backlight_device *bd)
{
struct drm_device *dev = bl_get_data(bd);
int val = (nv_rd32(dev, NV40_PMC_BACKLIGHT) & NV40_PMC_BACKLIGHT_MASK)
>> 16;
return val;
}
static int nv40_set_intensity(struct backlight_device *bd)
{
struct drm_device *dev = bl_get_data(bd);
int val = bd->props.brightness;
int reg = nv_rd32(dev, NV40_PMC_BACKLIGHT);
nv_wr32(dev, NV40_PMC_BACKLIGHT,
(val << 16) | (reg & ~NV40_PMC_BACKLIGHT_MASK));
return 0;
}
static struct backlight_ops nv40_bl_ops = {
.options = BL_CORE_SUSPENDRESUME,
.get_brightness = nv40_get_intensity,
.update_status = nv40_set_intensity,
};
static int nv50_get_intensity(struct backlight_device *bd)
{
struct drm_device *dev = bl_get_data(bd);
return nv_rd32(dev, NV50_PDISPLAY_SOR_BACKLIGHT);
}
static int nv50_set_intensity(struct backlight_device *bd)
{
struct drm_device *dev = bl_get_data(bd);
int val = bd->props.brightness;
nv_wr32(dev, NV50_PDISPLAY_SOR_BACKLIGHT,
val | NV50_PDISPLAY_SOR_BACKLIGHT_ENABLE);
return 0;
}
static struct backlight_ops nv50_bl_ops = {
.options = BL_CORE_SUSPENDRESUME,
.get_brightness = nv50_get_intensity,
.update_status = nv50_set_intensity,
};
static int nouveau_nv40_backlight_init(struct drm_device *dev)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct backlight_device *bd;
if (!(nv_rd32(dev, NV40_PMC_BACKLIGHT) & NV40_PMC_BACKLIGHT_MASK))
return 0;
bd = backlight_device_register("nv_backlight", &dev->pdev->dev, dev,
&nv40_bl_ops);
if (IS_ERR(bd))
return PTR_ERR(bd);
dev_priv->backlight = bd;
bd->props.max_brightness = 31;
bd->props.brightness = nv40_get_intensity(bd);
backlight_update_status(bd);
return 0;
}
static int nouveau_nv50_backlight_init(struct drm_device *dev)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct backlight_device *bd;
if (!nv_rd32(dev, NV50_PDISPLAY_SOR_BACKLIGHT))
return 0;
bd = backlight_device_register("nv_backlight", &dev->pdev->dev, dev,
&nv50_bl_ops);
if (IS_ERR(bd))
return PTR_ERR(bd);
dev_priv->backlight = bd;
bd->props.max_brightness = 1025;
bd->props.brightness = nv50_get_intensity(bd);
backlight_update_status(bd);
return 0;
}
int nouveau_backlight_init(struct drm_device *dev)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
switch (dev_priv->card_type) {
case NV_40:
return nouveau_nv40_backlight_init(dev);
case NV_50:
return nouveau_nv50_backlight_init(dev);
default:
break;
}
return 0;
}
void nouveau_backlight_exit(struct drm_device *dev)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
if (dev_priv->backlight) {
backlight_device_unregister(dev_priv->backlight);
dev_priv->backlight = NULL;
}
}
此差异已折叠。
/*
* Copyright 2007-2008 Nouveau Project
*
* 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 AUTHORS OR COPYRIGHT HOLDERS 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.
*/
#ifndef __NOUVEAU_BIOS_H__
#define __NOUVEAU_BIOS_H__
#include "nvreg.h"
#include "nouveau_i2c.h"
#define DCB_MAX_NUM_ENTRIES 16
#define DCB_MAX_NUM_I2C_ENTRIES 16
#define DCB_MAX_NUM_GPIO_ENTRIES 32
#define DCB_MAX_NUM_CONNECTOR_ENTRIES 16
#define DCB_LOC_ON_CHIP 0
struct dcb_entry {
int index; /* may not be raw dcb index if merging has happened */
uint8_t type;
uint8_t i2c_index;
uint8_t heads;
uint8_t connector;
uint8_t bus;
uint8_t location;
uint8_t or;
bool duallink_possible;
union {
struct sor_conf {
int link;
} sorconf;
struct {
int maxfreq;
} crtconf;
struct {
struct sor_conf sor;
bool use_straps_for_mode;
bool use_power_scripts;
} lvdsconf;
struct {
bool has_component_output;
} tvconf;
struct {
struct sor_conf sor;
int link_nr;
int link_bw;
} dpconf;
struct {
struct sor_conf sor;
} tmdsconf;
};
bool i2c_upper_default;
};
struct dcb_i2c_entry {
uint8_t port_type;
uint8_t read, write;
struct nouveau_i2c_chan *chan;
};
struct parsed_dcb {
int entries;
struct dcb_entry entry[DCB_MAX_NUM_ENTRIES];
struct dcb_i2c_entry i2c[DCB_MAX_NUM_I2C_ENTRIES];
};
enum dcb_gpio_tag {
DCB_GPIO_TVDAC0 = 0xc,
DCB_GPIO_TVDAC1 = 0x2d,
};
struct dcb_gpio_entry {
enum dcb_gpio_tag tag;
int line;
bool invert;
};
struct parsed_dcb_gpio {
int entries;
struct dcb_gpio_entry entry[DCB_MAX_NUM_GPIO_ENTRIES];
};
struct dcb_connector_table_entry {
uint32_t entry;
uint8_t type;
uint8_t index;
uint8_t gpio_tag;
};
struct dcb_connector_table {
int entries;
struct dcb_connector_table_entry entry[DCB_MAX_NUM_CONNECTOR_ENTRIES];
};
struct bios_parsed_dcb {
uint8_t version;
struct parsed_dcb dcb;
uint8_t *i2c_table;
uint8_t i2c_default_indices;
uint16_t gpio_table_ptr;
struct parsed_dcb_gpio gpio;
uint16_t connector_table_ptr;
struct dcb_connector_table connector;
};
enum nouveau_encoder_type {
OUTPUT_ANALOG = 0,
OUTPUT_TV = 1,
OUTPUT_TMDS = 2,
OUTPUT_LVDS = 3,
OUTPUT_DP = 6,
OUTPUT_ANY = -1
};
enum nouveau_or {
OUTPUT_A = (1 << 0),
OUTPUT_B = (1 << 1),
OUTPUT_C = (1 << 2)
};
enum LVDS_script {
/* Order *does* matter here */
LVDS_INIT = 1,
LVDS_RESET,
LVDS_BACKLIGHT_ON,
LVDS_BACKLIGHT_OFF,
LVDS_PANEL_ON,
LVDS_PANEL_OFF
};
/* changing these requires matching changes to reg tables in nv_get_clock */
#define MAX_PLL_TYPES 4
enum pll_types {
NVPLL,
MPLL,
VPLL1,
VPLL2
};
struct pll_lims {
struct {
int minfreq;
int maxfreq;
int min_inputfreq;
int max_inputfreq;
uint8_t min_m;
uint8_t max_m;
uint8_t min_n;
uint8_t max_n;
} vco1, vco2;
uint8_t max_log2p;
/*
* for most pre nv50 cards setting a log2P of 7 (the common max_log2p
* value) is no different to 6 (at least for vplls) so allowing the MNP
* calc to use 7 causes the generated clock to be out by a factor of 2.
* however, max_log2p cannot be fixed-up during parsing as the
* unmodified max_log2p value is still needed for setting mplls, hence
* an additional max_usable_log2p member
*/
uint8_t max_usable_log2p;
uint8_t log2p_bias;
uint8_t min_p;
uint8_t max_p;
int refclk;
};
struct nouveau_bios_info {
struct parsed_dcb *dcb;
uint8_t chip_version;
uint32_t dactestval;
uint32_t tvdactestval;
uint8_t digital_min_front_porch;
bool fp_no_ddc;
};
struct nvbios {
struct drm_device *dev;
struct nouveau_bios_info pub;
uint8_t data[NV_PROM_SIZE];
unsigned int length;
bool execute;
uint8_t major_version;
uint8_t feature_byte;
bool is_mobile;
uint32_t fmaxvco, fminvco;
bool old_style_init;
uint16_t init_script_tbls_ptr;
uint16_t extra_init_script_tbl_ptr;
uint16_t macro_index_tbl_ptr;
uint16_t macro_tbl_ptr;
uint16_t condition_tbl_ptr;
uint16_t io_condition_tbl_ptr;
uint16_t io_flag_condition_tbl_ptr;
uint16_t init_function_tbl_ptr;
uint16_t pll_limit_tbl_ptr;
uint16_t ram_restrict_tbl_ptr;
uint16_t some_script_ptr; /* BIT I + 14 */
uint16_t init96_tbl_ptr; /* BIT I + 16 */
struct bios_parsed_dcb bdcb;
struct {
int crtchead;
/* these need remembering across suspend */
uint32_t saved_nv_pfb_cfg0;
} state;
struct {
struct dcb_entry *output;
uint16_t script_table_ptr;
uint16_t dp_table_ptr;
} display;
struct {
uint16_t fptablepointer; /* also used by tmds */
uint16_t fpxlatetableptr;
int xlatwidth;
uint16_t lvdsmanufacturerpointer;
uint16_t fpxlatemanufacturertableptr;
uint16_t mode_ptr;
uint16_t xlated_entry;
bool power_off_for_reset;
bool reset_after_pclk_change;
bool dual_link;
bool link_c_increment;
bool BITbit1;
bool if_is_24bit;
int duallink_transition_clk;
uint8_t strapless_is_24bit;
uint8_t *edid;
/* will need resetting after suspend */
int last_script_invoc;
bool lvds_init_run;
} fp;
struct {
uint16_t output0_script_ptr;
uint16_t output1_script_ptr;
} tmds;
struct {
uint16_t mem_init_tbl_ptr;
uint16_t sdr_seq_tbl_ptr;
uint16_t ddr_seq_tbl_ptr;
struct {
uint8_t crt, tv, panel;
} i2c_indices;
uint16_t lvds_single_a_script_ptr;
} legacy;
};
#endif
/*
* Copyright 2007 Dave Airlied
* 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
* VA LINUX SYSTEMS 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.
*/
/*
* Authors: Dave Airlied <airlied@linux.ie>
* Ben Skeggs <darktama@iinet.net.au>
* Jeremy Kolb <jkolb@brandeis.edu>
*/
#include "drmP.h"
#include "nouveau_drm.h"
#include "nouveau_drv.h"
#include "nouveau_dma.h"
static void
nouveau_bo_del_ttm(struct ttm_buffer_object *bo)
{
struct drm_nouveau_private *dev_priv = nouveau_bdev(bo->bdev);
struct nouveau_bo *nvbo = nouveau_bo(bo);
ttm_bo_kunmap(&nvbo->kmap);
if (unlikely(nvbo->gem))
DRM_ERROR("bo %p still attached to GEM object\n", bo);
spin_lock(&dev_priv->ttm.bo_list_lock);
list_del(&nvbo->head);
spin_unlock(&dev_priv->ttm.bo_list_lock);
kfree(nvbo);
}
int
nouveau_bo_new(struct drm_device *dev, struct nouveau_channel *chan,
int size, int align, uint32_t flags, uint32_t tile_mode,
uint32_t tile_flags, bool no_vm, bool mappable,
struct nouveau_bo **pnvbo)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_bo *nvbo;
int ret, n = 0;
nvbo = kzalloc(sizeof(struct nouveau_bo), GFP_KERNEL);
if (!nvbo)
return -ENOMEM;
INIT_LIST_HEAD(&nvbo->head);
INIT_LIST_HEAD(&nvbo->entry);
nvbo->mappable = mappable;
nvbo->no_vm = no_vm;
nvbo->tile_mode = tile_mode;
nvbo->tile_flags = tile_flags;
/*
* Some of the tile_flags have a periodic structure of N*4096 bytes,
* align to to that as well as the page size. Overallocate memory to
* avoid corruption of other buffer objects.
*/
switch (tile_flags) {
case 0x1800:
case 0x2800:
case 0x4800:
case 0x7a00:
if (dev_priv->chipset >= 0xA0) {
/* This is based on high end cards with 448 bits
* memory bus, could be different elsewhere.*/
size += 6 * 28672;
/* 8 * 28672 is the actual alignment requirement,
* but we must also align to page size. */
align = 2 * 8 * 28672;
} else if (dev_priv->chipset >= 0x90) {
size += 3 * 16384;
align = 12 * 16834;
} else {
size += 3 * 8192;
/* 12 * 8192 is the actual alignment requirement,
* but we must also align to page size. */
align = 2 * 12 * 8192;
}
break;
default:
break;
}
align >>= PAGE_SHIFT;
size = (size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
if (dev_priv->card_type == NV_50) {
size = (size + 65535) & ~65535;
if (align < (65536 / PAGE_SIZE))
align = (65536 / PAGE_SIZE);
}
if (flags & TTM_PL_FLAG_VRAM)
nvbo->placements[n++] = TTM_PL_FLAG_VRAM | TTM_PL_MASK_CACHING;
if (flags & TTM_PL_FLAG_TT)
nvbo->placements[n++] = TTM_PL_FLAG_TT | TTM_PL_MASK_CACHING;
nvbo->placement.fpfn = 0;
nvbo->placement.lpfn = mappable ? dev_priv->fb_mappable_pages : 0;
nvbo->placement.placement = nvbo->placements;
nvbo->placement.busy_placement = nvbo->placements;
nvbo->placement.num_placement = n;
nvbo->placement.num_busy_placement = n;
nvbo->channel = chan;
nouveau_bo_placement_set(nvbo, flags);
ret = ttm_bo_init(&dev_priv->ttm.bdev, &nvbo->bo, size,
ttm_bo_type_device, &nvbo->placement, align, 0,
false, NULL, size, nouveau_bo_del_ttm);
nvbo->channel = NULL;
if (ret) {
/* ttm will call nouveau_bo_del_ttm if it fails.. */
return ret;
}
spin_lock(&dev_priv->ttm.bo_list_lock);
list_add_tail(&nvbo->head, &dev_priv->ttm.bo_list);
spin_unlock(&dev_priv->ttm.bo_list_lock);
*pnvbo = nvbo;
return 0;
}
void
nouveau_bo_placement_set(struct nouveau_bo *nvbo, uint32_t memtype)
{
int n = 0;
if (memtype & TTM_PL_FLAG_VRAM)
nvbo->placements[n++] = TTM_PL_FLAG_VRAM | TTM_PL_MASK_CACHING;
if (memtype & TTM_PL_FLAG_TT)
nvbo->placements[n++] = TTM_PL_FLAG_TT | TTM_PL_MASK_CACHING;
if (memtype & TTM_PL_FLAG_SYSTEM)
nvbo->placements[n++] = TTM_PL_FLAG_SYSTEM | TTM_PL_MASK_CACHING;
nvbo->placement.placement = nvbo->placements;
nvbo->placement.busy_placement = nvbo->placements;
nvbo->placement.num_placement = n;
nvbo->placement.num_busy_placement = n;
}
int
nouveau_bo_pin(struct nouveau_bo *nvbo, uint32_t memtype)
{
struct drm_nouveau_private *dev_priv = nouveau_bdev(nvbo->bo.bdev);
struct ttm_buffer_object *bo = &nvbo->bo;
int ret, i;
if (nvbo->pin_refcnt && !(memtype & (1 << bo->mem.mem_type))) {
NV_ERROR(nouveau_bdev(bo->bdev)->dev,
"bo %p pinned elsewhere: 0x%08x vs 0x%08x\n", bo,
1 << bo->mem.mem_type, memtype);
return -EINVAL;
}
if (nvbo->pin_refcnt++)
return 0;
ret = ttm_bo_reserve(bo, false, false, false, 0);
if (ret)
goto out;
nouveau_bo_placement_set(nvbo, memtype);
for (i = 0; i < nvbo->placement.num_placement; i++)
nvbo->placements[i] |= TTM_PL_FLAG_NO_EVICT;
ret = ttm_bo_validate(bo, &nvbo->placement, false, false);
if (ret == 0) {
switch (bo->mem.mem_type) {
case TTM_PL_VRAM:
dev_priv->fb_aper_free -= bo->mem.size;
break;
case TTM_PL_TT:
dev_priv->gart_info.aper_free -= bo->mem.size;
break;
default:
break;
}
}
ttm_bo_unreserve(bo);
out:
if (unlikely(ret))
nvbo->pin_refcnt--;
return ret;
}
int
nouveau_bo_unpin(struct nouveau_bo *nvbo)
{
struct drm_nouveau_private *dev_priv = nouveau_bdev(nvbo->bo.bdev);
struct ttm_buffer_object *bo = &nvbo->bo;
int ret, i;
if (--nvbo->pin_refcnt)
return 0;
ret = ttm_bo_reserve(bo, false, false, false, 0);
if (ret)
return ret;
for (i = 0; i < nvbo->placement.num_placement; i++)
nvbo->placements[i] &= ~TTM_PL_FLAG_NO_EVICT;
ret = ttm_bo_validate(bo, &nvbo->placement, false, false);
if (ret == 0) {
switch (bo->mem.mem_type) {
case TTM_PL_VRAM:
dev_priv->fb_aper_free += bo->mem.size;
break;
case TTM_PL_TT:
dev_priv->gart_info.aper_free += bo->mem.size;
break;
default:
break;
}
}
ttm_bo_unreserve(bo);
return ret;
}
int
nouveau_bo_map(struct nouveau_bo *nvbo)
{
int ret;
ret = ttm_bo_reserve(&nvbo->bo, false, false, false, 0);
if (ret)
return ret;
ret = ttm_bo_kmap(&nvbo->bo, 0, nvbo->bo.mem.num_pages, &nvbo->kmap);
ttm_bo_unreserve(&nvbo->bo);
return ret;
}
void
nouveau_bo_unmap(struct nouveau_bo *nvbo)
{
ttm_bo_kunmap(&nvbo->kmap);
}
u16
nouveau_bo_rd16(struct nouveau_bo *nvbo, unsigned index)
{
bool is_iomem;
u16 *mem = ttm_kmap_obj_virtual(&nvbo->kmap, &is_iomem);
mem = &mem[index];
if (is_iomem)
return ioread16_native((void __force __iomem *)mem);
else
return *mem;
}
void
nouveau_bo_wr16(struct nouveau_bo *nvbo, unsigned index, u16 val)
{
bool is_iomem;
u16 *mem = ttm_kmap_obj_virtual(&nvbo->kmap, &is_iomem);
mem = &mem[index];
if (is_iomem)
iowrite16_native(val, (void __force __iomem *)mem);
else
*mem = val;
}
u32
nouveau_bo_rd32(struct nouveau_bo *nvbo, unsigned index)
{
bool is_iomem;
u32 *mem = ttm_kmap_obj_virtual(&nvbo->kmap, &is_iomem);
mem = &mem[index];
if (is_iomem)
return ioread32_native((void __force __iomem *)mem);
else
return *mem;
}
void
nouveau_bo_wr32(struct nouveau_bo *nvbo, unsigned index, u32 val)
{
bool is_iomem;
u32 *mem = ttm_kmap_obj_virtual(&nvbo->kmap, &is_iomem);
mem = &mem[index];
if (is_iomem)
iowrite32_native(val, (void __force __iomem *)mem);
else
*mem = val;
}
static struct ttm_backend *
nouveau_bo_create_ttm_backend_entry(struct ttm_bo_device *bdev)
{
struct drm_nouveau_private *dev_priv = nouveau_bdev(bdev);
struct drm_device *dev = dev_priv->dev;
switch (dev_priv->gart_info.type) {
case NOUVEAU_GART_AGP:
return ttm_agp_backend_init(bdev, dev->agp->bridge);
case NOUVEAU_GART_SGDMA:
return nouveau_sgdma_init_ttm(dev);
default:
NV_ERROR(dev, "Unknown GART type %d\n",
dev_priv->gart_info.type);
break;
}
return NULL;
}
static int
nouveau_bo_invalidate_caches(struct ttm_bo_device *bdev, uint32_t flags)
{
/* We'll do this from user space. */
return 0;
}
static int
nouveau_bo_init_mem_type(struct ttm_bo_device *bdev, uint32_t type,
struct ttm_mem_type_manager *man)
{
struct drm_nouveau_private *dev_priv = nouveau_bdev(bdev);
struct drm_device *dev = dev_priv->dev;
switch (type) {
case TTM_PL_SYSTEM:
man->flags = TTM_MEMTYPE_FLAG_MAPPABLE;
man->available_caching = TTM_PL_MASK_CACHING;
man->default_caching = TTM_PL_FLAG_CACHED;
break;
case TTM_PL_VRAM:
man->flags = TTM_MEMTYPE_FLAG_FIXED |
TTM_MEMTYPE_FLAG_MAPPABLE |
TTM_MEMTYPE_FLAG_NEEDS_IOREMAP;
man->available_caching = TTM_PL_FLAG_UNCACHED |
TTM_PL_FLAG_WC;
man->default_caching = TTM_PL_FLAG_WC;
man->io_addr = NULL;
man->io_offset = drm_get_resource_start(dev, 1);
man->io_size = drm_get_resource_len(dev, 1);
if (man->io_size > nouveau_mem_fb_amount(dev))
man->io_size = nouveau_mem_fb_amount(dev);
man->gpu_offset = dev_priv->vm_vram_base;
break;
case TTM_PL_TT:
switch (dev_priv->gart_info.type) {
case NOUVEAU_GART_AGP:
man->flags = TTM_MEMTYPE_FLAG_MAPPABLE |
TTM_MEMTYPE_FLAG_NEEDS_IOREMAP;
man->available_caching = TTM_PL_FLAG_UNCACHED;
man->default_caching = TTM_PL_FLAG_UNCACHED;
break;
case NOUVEAU_GART_SGDMA:
man->flags = TTM_MEMTYPE_FLAG_MAPPABLE |
TTM_MEMTYPE_FLAG_CMA;
man->available_caching = TTM_PL_MASK_CACHING;
man->default_caching = TTM_PL_FLAG_CACHED;
break;
default:
NV_ERROR(dev, "Unknown GART type: %d\n",
dev_priv->gart_info.type);
return -EINVAL;
}
man->io_offset = dev_priv->gart_info.aper_base;
man->io_size = dev_priv->gart_info.aper_size;
man->io_addr = NULL;
man->gpu_offset = dev_priv->vm_gart_base;
break;
default:
NV_ERROR(dev, "Unsupported memory type %u\n", (unsigned)type);
return -EINVAL;
}
return 0;
}
static void
nouveau_bo_evict_flags(struct ttm_buffer_object *bo, struct ttm_placement *pl)
{
struct nouveau_bo *nvbo = nouveau_bo(bo);
switch (bo->mem.mem_type) {
default:
nouveau_bo_placement_set(nvbo, TTM_PL_FLAG_SYSTEM);
break;
}
}
/* GPU-assisted copy using NV_MEMORY_TO_MEMORY_FORMAT, can access
* TTM_PL_{VRAM,TT} directly.
*/
static int
nouveau_bo_move_accel_cleanup(struct nouveau_channel *chan,
struct nouveau_bo *nvbo, bool evict, bool no_wait,
struct ttm_mem_reg *new_mem)
{
struct nouveau_fence *fence = NULL;
int ret;
ret = nouveau_fence_new(chan, &fence, true);
if (ret)
return ret;
ret = ttm_bo_move_accel_cleanup(&nvbo->bo, fence, NULL,
evict, no_wait, new_mem);
nouveau_fence_unref((void *)&fence);
return ret;
}
static inline uint32_t
nouveau_bo_mem_ctxdma(struct nouveau_bo *nvbo, struct nouveau_channel *chan,
struct ttm_mem_reg *mem)
{
if (chan == nouveau_bdev(nvbo->bo.bdev)->channel) {
if (mem->mem_type == TTM_PL_TT)
return NvDmaGART;
return NvDmaVRAM;
}
if (mem->mem_type == TTM_PL_TT)
return chan->gart_handle;
return chan->vram_handle;
}
static int
nouveau_bo_move_m2mf(struct ttm_buffer_object *bo, int evict, int no_wait,
struct ttm_mem_reg *old_mem, struct ttm_mem_reg *new_mem)
{
struct nouveau_bo *nvbo = nouveau_bo(bo);
struct drm_nouveau_private *dev_priv = nouveau_bdev(bo->bdev);
struct nouveau_channel *chan;
uint64_t src_offset, dst_offset;
uint32_t page_count;
int ret;
chan = nvbo->channel;
if (!chan || nvbo->tile_flags || nvbo->no_vm) {
chan = dev_priv->channel;
if (!chan)
return -EINVAL;
}
src_offset = old_mem->mm_node->start << PAGE_SHIFT;
dst_offset = new_mem->mm_node->start << PAGE_SHIFT;
if (chan != dev_priv->channel) {
if (old_mem->mem_type == TTM_PL_TT)
src_offset += dev_priv->vm_gart_base;
else
src_offset += dev_priv->vm_vram_base;
if (new_mem->mem_type == TTM_PL_TT)
dst_offset += dev_priv->vm_gart_base;
else
dst_offset += dev_priv->vm_vram_base;
}
ret = RING_SPACE(chan, 3);
if (ret)
return ret;
BEGIN_RING(chan, NvSubM2MF, NV_MEMORY_TO_MEMORY_FORMAT_DMA_SOURCE, 2);
OUT_RING(chan, nouveau_bo_mem_ctxdma(nvbo, chan, old_mem));
OUT_RING(chan, nouveau_bo_mem_ctxdma(nvbo, chan, new_mem));
if (dev_priv->card_type >= NV_50) {
ret = RING_SPACE(chan, 4);
if (ret)
return ret;
BEGIN_RING(chan, NvSubM2MF, 0x0200, 1);
OUT_RING(chan, 1);
BEGIN_RING(chan, NvSubM2MF, 0x021c, 1);
OUT_RING(chan, 1);
}
page_count = new_mem->num_pages;
while (page_count) {
int line_count = (page_count > 2047) ? 2047 : page_count;
if (dev_priv->card_type >= NV_50) {
ret = RING_SPACE(chan, 3);
if (ret)
return ret;
BEGIN_RING(chan, NvSubM2MF, 0x0238, 2);
OUT_RING(chan, upper_32_bits(src_offset));
OUT_RING(chan, upper_32_bits(dst_offset));
}
ret = RING_SPACE(chan, 11);
if (ret)
return ret;
BEGIN_RING(chan, NvSubM2MF,
NV_MEMORY_TO_MEMORY_FORMAT_OFFSET_IN, 8);
OUT_RING(chan, lower_32_bits(src_offset));
OUT_RING(chan, lower_32_bits(dst_offset));
OUT_RING(chan, PAGE_SIZE); /* src_pitch */
OUT_RING(chan, PAGE_SIZE); /* dst_pitch */
OUT_RING(chan, PAGE_SIZE); /* line_length */
OUT_RING(chan, line_count);
OUT_RING(chan, (1<<8)|(1<<0));
OUT_RING(chan, 0);
BEGIN_RING(chan, NvSubM2MF, NV_MEMORY_TO_MEMORY_FORMAT_NOP, 1);
OUT_RING(chan, 0);
page_count -= line_count;
src_offset += (PAGE_SIZE * line_count);
dst_offset += (PAGE_SIZE * line_count);
}
return nouveau_bo_move_accel_cleanup(chan, nvbo, evict, no_wait, new_mem);
}
static int
nouveau_bo_move_flipd(struct ttm_buffer_object *bo, bool evict, bool intr,
bool no_wait, struct ttm_mem_reg *new_mem)
{
u32 placement_memtype = TTM_PL_FLAG_TT | TTM_PL_MASK_CACHING;
struct ttm_placement placement;
struct ttm_mem_reg tmp_mem;
int ret;
placement.fpfn = placement.lpfn = 0;
placement.num_placement = placement.num_busy_placement = 1;
placement.placement = &placement_memtype;
tmp_mem = *new_mem;
tmp_mem.mm_node = NULL;
ret = ttm_bo_mem_space(bo, &placement, &tmp_mem, intr, no_wait);
if (ret)
return ret;
ret = ttm_tt_bind(bo->ttm, &tmp_mem);
if (ret)
goto out;
ret = nouveau_bo_move_m2mf(bo, true, no_wait, &bo->mem, &tmp_mem);
if (ret)
goto out;
ret = ttm_bo_move_ttm(bo, evict, no_wait, new_mem);
out:
if (tmp_mem.mm_node) {
spin_lock(&bo->bdev->glob->lru_lock);
drm_mm_put_block(tmp_mem.mm_node);
spin_unlock(&bo->bdev->glob->lru_lock);
}
return ret;
}
static int
nouveau_bo_move_flips(struct ttm_buffer_object *bo, bool evict, bool intr,
bool no_wait, struct ttm_mem_reg *new_mem)
{
u32 placement_memtype = TTM_PL_FLAG_TT | TTM_PL_MASK_CACHING;
struct ttm_placement placement;
struct ttm_mem_reg tmp_mem;
int ret;
placement.fpfn = placement.lpfn = 0;
placement.num_placement = placement.num_busy_placement = 1;
placement.placement = &placement_memtype;
tmp_mem = *new_mem;
tmp_mem.mm_node = NULL;
ret = ttm_bo_mem_space(bo, &placement, &tmp_mem, intr, no_wait);
if (ret)
return ret;
ret = ttm_bo_move_ttm(bo, evict, no_wait, &tmp_mem);
if (ret)
goto out;
ret = nouveau_bo_move_m2mf(bo, true, no_wait, &bo->mem, new_mem);
if (ret)
goto out;
out:
if (tmp_mem.mm_node) {
spin_lock(&bo->bdev->glob->lru_lock);
drm_mm_put_block(tmp_mem.mm_node);
spin_unlock(&bo->bdev->glob->lru_lock);
}
return ret;
}
static int
nouveau_bo_move(struct ttm_buffer_object *bo, bool evict, bool intr,
bool no_wait, struct ttm_mem_reg *new_mem)
{
struct drm_nouveau_private *dev_priv = nouveau_bdev(bo->bdev);
struct nouveau_bo *nvbo = nouveau_bo(bo);
struct drm_device *dev = dev_priv->dev;
struct ttm_mem_reg *old_mem = &bo->mem;
int ret;
if (dev_priv->card_type == NV_50 && new_mem->mem_type == TTM_PL_VRAM &&
!nvbo->no_vm) {
uint64_t offset = new_mem->mm_node->start << PAGE_SHIFT;
ret = nv50_mem_vm_bind_linear(dev,
offset + dev_priv->vm_vram_base,
new_mem->size, nvbo->tile_flags,
offset);
if (ret)
return ret;
}
if (dev_priv->init_state != NOUVEAU_CARD_INIT_DONE)
return ttm_bo_move_memcpy(bo, evict, no_wait, new_mem);
if (old_mem->mem_type == TTM_PL_SYSTEM && !bo->ttm) {
BUG_ON(bo->mem.mm_node != NULL);
bo->mem = *new_mem;
new_mem->mm_node = NULL;
return 0;
}
if (new_mem->mem_type == TTM_PL_SYSTEM) {
if (old_mem->mem_type == TTM_PL_SYSTEM)
return ttm_bo_move_memcpy(bo, evict, no_wait, new_mem);
if (nouveau_bo_move_flipd(bo, evict, intr, no_wait, new_mem))
return ttm_bo_move_memcpy(bo, evict, no_wait, new_mem);
} else if (old_mem->mem_type == TTM_PL_SYSTEM) {
if (nouveau_bo_move_flips(bo, evict, intr, no_wait, new_mem))
return ttm_bo_move_memcpy(bo, evict, no_wait, new_mem);
} else {
if (nouveau_bo_move_m2mf(bo, evict, no_wait, old_mem, new_mem))
return ttm_bo_move_memcpy(bo, evict, no_wait, new_mem);
}
return 0;
}
static int
nouveau_bo_verify_access(struct ttm_buffer_object *bo, struct file *filp)
{
return 0;
}
struct ttm_bo_driver nouveau_bo_driver = {
.create_ttm_backend_entry = nouveau_bo_create_ttm_backend_entry,
.invalidate_caches = nouveau_bo_invalidate_caches,
.init_mem_type = nouveau_bo_init_mem_type,
.evict_flags = nouveau_bo_evict_flags,
.move = nouveau_bo_move,
.verify_access = nouveau_bo_verify_access,
.sync_obj_signaled = nouveau_fence_signalled,
.sync_obj_wait = nouveau_fence_wait,
.sync_obj_flush = nouveau_fence_flush,
.sync_obj_unref = nouveau_fence_unref,
.sync_obj_ref = nouveau_fence_ref,
};
/*
* Copyright 1993-2003 NVIDIA, Corporation
* Copyright 2007-2009 Stuart Bennett
*
* 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 AUTHORS 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 "nouveau_drv.h"
#include "nouveau_hw.h"
/****************************************************************************\
* *
* The video arbitration routines calculate some "magic" numbers. Fixes *
* the snow seen when accessing the framebuffer without it. *
* It just works (I hope). *
* *
\****************************************************************************/
struct nv_fifo_info {
int lwm;
int burst;
};
struct nv_sim_state {
int pclk_khz;
int mclk_khz;
int nvclk_khz;
int bpp;
int mem_page_miss;
int mem_latency;
int memory_type;
int memory_width;
int two_heads;
};
static void
nv04_calc_arb(struct nv_fifo_info *fifo, struct nv_sim_state *arb)
{
int pagemiss, cas, width, bpp;
int nvclks, mclks, pclks, crtpagemiss;
int found, mclk_extra, mclk_loop, cbs, m1, p1;
int mclk_freq, pclk_freq, nvclk_freq;
int us_m, us_n, us_p, crtc_drain_rate;
int cpm_us, us_crt, clwm;
pclk_freq = arb->pclk_khz;
mclk_freq = arb->mclk_khz;
nvclk_freq = arb->nvclk_khz;
pagemiss = arb->mem_page_miss;
cas = arb->mem_latency;
width = arb->memory_width >> 6;
bpp = arb->bpp;
cbs = 128;
pclks = 2;
nvclks = 10;
mclks = 13 + cas;
mclk_extra = 3;
found = 0;
while (!found) {
found = 1;
mclk_loop = mclks + mclk_extra;
us_m = mclk_loop * 1000 * 1000 / mclk_freq;
us_n = nvclks * 1000 * 1000 / nvclk_freq;
us_p = nvclks * 1000 * 1000 / pclk_freq;
crtc_drain_rate = pclk_freq * bpp / 8;
crtpagemiss = 2;
crtpagemiss += 1;
cpm_us = crtpagemiss * pagemiss * 1000 * 1000 / mclk_freq;
us_crt = cpm_us + us_m + us_n + us_p;
clwm = us_crt * crtc_drain_rate / (1000 * 1000);
clwm++;
m1 = clwm + cbs - 512;
p1 = m1 * pclk_freq / mclk_freq;
p1 = p1 * bpp / 8;
if ((p1 < m1 && m1 > 0) || clwm > 519) {
found = !mclk_extra;
mclk_extra--;
}
if (clwm < 384)
clwm = 384;
fifo->lwm = clwm;
fifo->burst = cbs;
}
}
static void
nv10_calc_arb(struct nv_fifo_info *fifo, struct nv_sim_state *arb)
{
int fill_rate, drain_rate;
int pclks, nvclks, mclks, xclks;
int pclk_freq, nvclk_freq, mclk_freq;
int fill_lat, extra_lat;
int max_burst_o, max_burst_l;
int fifo_len, min_lwm, max_lwm;
const int burst_lat = 80; /* Maximum allowable latency due
* to the CRTC FIFO burst. (ns) */
pclk_freq = arb->pclk_khz;
nvclk_freq = arb->nvclk_khz;
mclk_freq = arb->mclk_khz;
fill_rate = mclk_freq * arb->memory_width / 8; /* kB/s */
drain_rate = pclk_freq * arb->bpp / 8; /* kB/s */
fifo_len = arb->two_heads ? 1536 : 1024; /* B */
/* Fixed FIFO refill latency. */
pclks = 4; /* lwm detect. */
nvclks = 3 /* lwm -> sync. */
+ 2 /* fbi bus cycles (1 req + 1 busy) */
+ 1 /* 2 edge sync. may be very close to edge so
* just put one. */
+ 1 /* fbi_d_rdv_n */
+ 1 /* Fbi_d_rdata */
+ 1; /* crtfifo load */
mclks = 1 /* 2 edge sync. may be very close to edge so
* just put one. */
+ 1 /* arb_hp_req */
+ 5 /* tiling pipeline */
+ 2 /* latency fifo */
+ 2 /* memory request to fbio block */
+ 7; /* data returned from fbio block */
/* Need to accumulate 256 bits for read */
mclks += (arb->memory_type == 0 ? 2 : 1)
* arb->memory_width / 32;
fill_lat = mclks * 1000 * 1000 / mclk_freq /* minimum mclk latency */
+ nvclks * 1000 * 1000 / nvclk_freq /* nvclk latency */
+ pclks * 1000 * 1000 / pclk_freq; /* pclk latency */
/* Conditional FIFO refill latency. */
xclks = 2 * arb->mem_page_miss + mclks /* Extra latency due to
* the overlay. */
+ 2 * arb->mem_page_miss /* Extra pagemiss latency. */
+ (arb->bpp == 32 ? 8 : 4); /* Margin of error. */
extra_lat = xclks * 1000 * 1000 / mclk_freq;
if (arb->two_heads)
/* Account for another CRTC. */
extra_lat += fill_lat + extra_lat + burst_lat;
/* FIFO burst */
/* Max burst not leading to overflows. */
max_burst_o = (1 + fifo_len - extra_lat * drain_rate / (1000 * 1000))
* (fill_rate / 1000) / ((fill_rate - drain_rate) / 1000);
fifo->burst = min(max_burst_o, 1024);
/* Max burst value with an acceptable latency. */
max_burst_l = burst_lat * fill_rate / (1000 * 1000);
fifo->burst = min(max_burst_l, fifo->burst);
fifo->burst = rounddown_pow_of_two(fifo->burst);
/* FIFO low watermark */
min_lwm = (fill_lat + extra_lat) * drain_rate / (1000 * 1000) + 1;
max_lwm = fifo_len - fifo->burst
+ fill_lat * drain_rate / (1000 * 1000)
+ fifo->burst * drain_rate / fill_rate;
fifo->lwm = min_lwm + 10 * (max_lwm - min_lwm) / 100; /* Empirical. */
}
static void
nv04_update_arb(struct drm_device *dev, int VClk, int bpp,
int *burst, int *lwm)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nv_fifo_info fifo_data;
struct nv_sim_state sim_data;
int MClk = nouveau_hw_get_clock(dev, MPLL);
int NVClk = nouveau_hw_get_clock(dev, NVPLL);
uint32_t cfg1 = nvReadFB(dev, NV_PFB_CFG1);
sim_data.pclk_khz = VClk;
sim_data.mclk_khz = MClk;
sim_data.nvclk_khz = NVClk;
sim_data.bpp = bpp;
sim_data.two_heads = nv_two_heads(dev);
if ((dev->pci_device & 0xffff) == 0x01a0 /*CHIPSET_NFORCE*/ ||
(dev->pci_device & 0xffff) == 0x01f0 /*CHIPSET_NFORCE2*/) {
uint32_t type;
pci_read_config_dword(pci_get_bus_and_slot(0, 1), 0x7c, &type);
sim_data.memory_type = (type >> 12) & 1;
sim_data.memory_width = 64;
sim_data.mem_latency = 3;
sim_data.mem_page_miss = 10;
} else {
sim_data.memory_type = nvReadFB(dev, NV_PFB_CFG0) & 0x1;
sim_data.memory_width = (nvReadEXTDEV(dev, NV_PEXTDEV_BOOT_0) & 0x10) ? 128 : 64;
sim_data.mem_latency = cfg1 & 0xf;
sim_data.mem_page_miss = ((cfg1 >> 4) & 0xf) + ((cfg1 >> 31) & 0x1);
}
if (dev_priv->card_type == NV_04)
nv04_calc_arb(&fifo_data, &sim_data);
else
nv10_calc_arb(&fifo_data, &sim_data);
*burst = ilog2(fifo_data.burst >> 4);
*lwm = fifo_data.lwm >> 3;
}
static void
nv30_update_arb(int *burst, int *lwm)
{
unsigned int fifo_size, burst_size, graphics_lwm;
fifo_size = 2048;
burst_size = 512;
graphics_lwm = fifo_size - burst_size;
*burst = ilog2(burst_size >> 5);
*lwm = graphics_lwm >> 3;
}
void
nouveau_calc_arb(struct drm_device *dev, int vclk, int bpp, int *burst, int *lwm)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
if (dev_priv->card_type < NV_30)
nv04_update_arb(dev, vclk, bpp, burst, lwm);
else if ((dev->pci_device & 0xfff0) == 0x0240 /*CHIPSET_C51*/ ||
(dev->pci_device & 0xfff0) == 0x03d0 /*CHIPSET_C512*/) {
*burst = 128;
*lwm = 0x0480;
} else
nv30_update_arb(burst, lwm);
}
static int
getMNP_single(struct drm_device *dev, struct pll_lims *pll_lim, int clk,
struct nouveau_pll_vals *bestpv)
{
/* Find M, N and P for a single stage PLL
*
* Note that some bioses (NV3x) have lookup tables of precomputed MNP
* values, but we're too lazy to use those atm
*
* "clk" parameter in kHz
* returns calculated clock
*/
struct drm_nouveau_private *dev_priv = dev->dev_private;
int cv = dev_priv->vbios->chip_version;
int minvco = pll_lim->vco1.minfreq, maxvco = pll_lim->vco1.maxfreq;
int minM = pll_lim->vco1.min_m, maxM = pll_lim->vco1.max_m;
int minN = pll_lim->vco1.min_n, maxN = pll_lim->vco1.max_n;
int minU = pll_lim->vco1.min_inputfreq;
int maxU = pll_lim->vco1.max_inputfreq;
int minP = pll_lim->max_p ? pll_lim->min_p : 0;
int maxP = pll_lim->max_p ? pll_lim->max_p : pll_lim->max_usable_log2p;
int crystal = pll_lim->refclk;
int M, N, thisP, P;
int clkP, calcclk;
int delta, bestdelta = INT_MAX;
int bestclk = 0;
/* this division verified for nv20, nv18, nv28 (Haiku), and nv34 */
/* possibly correlated with introduction of 27MHz crystal */
if (dev_priv->card_type < NV_50) {
if (cv < 0x17 || cv == 0x1a || cv == 0x20) {
if (clk > 250000)
maxM = 6;
if (clk > 340000)
maxM = 2;
} else if (cv < 0x40) {
if (clk > 150000)
maxM = 6;
if (clk > 200000)
maxM = 4;
if (clk > 340000)
maxM = 2;
}
}
P = pll_lim->max_p ? maxP : (1 << maxP);
if ((clk * P) < minvco) {
minvco = clk * maxP;
maxvco = minvco * 2;
}
if (clk + clk/200 > maxvco) /* +0.5% */
maxvco = clk + clk/200;
/* NV34 goes maxlog2P->0, NV20 goes 0->maxlog2P */
for (thisP = minP; thisP <= maxP; thisP++) {
P = pll_lim->max_p ? thisP : (1 << thisP);
clkP = clk * P;
if (clkP < minvco)
continue;
if (clkP > maxvco)
return bestclk;
for (M = minM; M <= maxM; M++) {
if (crystal/M < minU)
return bestclk;
if (crystal/M > maxU)
continue;
/* add crystal/2 to round better */
N = (clkP * M + crystal/2) / crystal;
if (N < minN)
continue;
if (N > maxN)
break;
/* more rounding additions */
calcclk = ((N * crystal + P/2) / P + M/2) / M;
delta = abs(calcclk - clk);
/* we do an exhaustive search rather than terminating
* on an optimality condition...
*/
if (delta < bestdelta) {
bestdelta = delta;
bestclk = calcclk;
bestpv->N1 = N;
bestpv->M1 = M;
bestpv->log2P = thisP;
if (delta == 0) /* except this one */
return bestclk;
}
}
}
return bestclk;
}
static int
getMNP_double(struct drm_device *dev, struct pll_lims *pll_lim, int clk,
struct nouveau_pll_vals *bestpv)
{
/* Find M, N and P for a two stage PLL
*
* Note that some bioses (NV30+) have lookup tables of precomputed MNP
* values, but we're too lazy to use those atm
*
* "clk" parameter in kHz
* returns calculated clock
*/
struct drm_nouveau_private *dev_priv = dev->dev_private;
int chip_version = dev_priv->vbios->chip_version;
int minvco1 = pll_lim->vco1.minfreq, maxvco1 = pll_lim->vco1.maxfreq;
int minvco2 = pll_lim->vco2.minfreq, maxvco2 = pll_lim->vco2.maxfreq;
int minU1 = pll_lim->vco1.min_inputfreq, minU2 = pll_lim->vco2.min_inputfreq;
int maxU1 = pll_lim->vco1.max_inputfreq, maxU2 = pll_lim->vco2.max_inputfreq;
int minM1 = pll_lim->vco1.min_m, maxM1 = pll_lim->vco1.max_m;
int minN1 = pll_lim->vco1.min_n, maxN1 = pll_lim->vco1.max_n;
int minM2 = pll_lim->vco2.min_m, maxM2 = pll_lim->vco2.max_m;
int minN2 = pll_lim->vco2.min_n, maxN2 = pll_lim->vco2.max_n;
int maxlog2P = pll_lim->max_usable_log2p;
int crystal = pll_lim->refclk;
bool fixedgain2 = (minM2 == maxM2 && minN2 == maxN2);
int M1, N1, M2, N2, log2P;
int clkP, calcclk1, calcclk2, calcclkout;
int delta, bestdelta = INT_MAX;
int bestclk = 0;
int vco2 = (maxvco2 - maxvco2/200) / 2;
for (log2P = 0; clk && log2P < maxlog2P && clk <= (vco2 >> log2P); log2P++)
;
clkP = clk << log2P;
if (maxvco2 < clk + clk/200) /* +0.5% */
maxvco2 = clk + clk/200;
for (M1 = minM1; M1 <= maxM1; M1++) {
if (crystal/M1 < minU1)
return bestclk;
if (crystal/M1 > maxU1)
continue;
for (N1 = minN1; N1 <= maxN1; N1++) {
calcclk1 = crystal * N1 / M1;
if (calcclk1 < minvco1)
continue;
if (calcclk1 > maxvco1)
break;
for (M2 = minM2; M2 <= maxM2; M2++) {
if (calcclk1/M2 < minU2)
break;
if (calcclk1/M2 > maxU2)
continue;
/* add calcclk1/2 to round better */
N2 = (clkP * M2 + calcclk1/2) / calcclk1;
if (N2 < minN2)
continue;
if (N2 > maxN2)
break;
if (!fixedgain2) {
if (chip_version < 0x60)
if (N2/M2 < 4 || N2/M2 > 10)
continue;
calcclk2 = calcclk1 * N2 / M2;
if (calcclk2 < minvco2)
break;
if (calcclk2 > maxvco2)
continue;
} else
calcclk2 = calcclk1;
calcclkout = calcclk2 >> log2P;
delta = abs(calcclkout - clk);
/* we do an exhaustive search rather than terminating
* on an optimality condition...
*/
if (delta < bestdelta) {
bestdelta = delta;
bestclk = calcclkout;
bestpv->N1 = N1;
bestpv->M1 = M1;
bestpv->N2 = N2;
bestpv->M2 = M2;
bestpv->log2P = log2P;
if (delta == 0) /* except this one */
return bestclk;
}
}
}
}
return bestclk;
}
int
nouveau_calc_pll_mnp(struct drm_device *dev, struct pll_lims *pll_lim, int clk,
struct nouveau_pll_vals *pv)
{
int outclk;
if (!pll_lim->vco2.maxfreq)
outclk = getMNP_single(dev, pll_lim, clk, pv);
else
outclk = getMNP_double(dev, pll_lim, clk, pv);
if (!outclk)
NV_ERROR(dev, "Could not find a compatible set of PLL values\n");
return outclk;
}
/*
* Copyright 2005-2006 Stephane Marchesin
* 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
* PRECISION INSIGHT 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.h"
#include "nouveau_drv.h"
#include "nouveau_drm.h"
#include "nouveau_dma.h"
static int
nouveau_channel_pushbuf_ctxdma_init(struct nouveau_channel *chan)
{
struct drm_device *dev = chan->dev;
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_bo *pb = chan->pushbuf_bo;
struct nouveau_gpuobj *pushbuf = NULL;
uint32_t start = pb->bo.mem.mm_node->start << PAGE_SHIFT;
int ret;
if (pb->bo.mem.mem_type == TTM_PL_TT) {
ret = nouveau_gpuobj_gart_dma_new(chan, 0,
dev_priv->gart_info.aper_size,
NV_DMA_ACCESS_RO, &pushbuf,
NULL);
chan->pushbuf_base = start;
} else
if (dev_priv->card_type != NV_04) {
ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY, 0,
dev_priv->fb_available_size,
NV_DMA_ACCESS_RO,
NV_DMA_TARGET_VIDMEM, &pushbuf);
chan->pushbuf_base = start;
} else {
/* NV04 cmdbuf hack, from original ddx.. not sure of it's
* exact reason for existing :) PCI access to cmdbuf in
* VRAM.
*/
ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY,
drm_get_resource_start(dev, 1),
dev_priv->fb_available_size,
NV_DMA_ACCESS_RO,
NV_DMA_TARGET_PCI, &pushbuf);
chan->pushbuf_base = start;
}
ret = nouveau_gpuobj_ref_add(dev, chan, 0, pushbuf, &chan->pushbuf);
if (ret) {
NV_ERROR(dev, "Error referencing pushbuf ctxdma: %d\n", ret);
if (pushbuf != dev_priv->gart_info.sg_ctxdma)
nouveau_gpuobj_del(dev, &pushbuf);
return ret;
}
return 0;
}
static struct nouveau_bo *
nouveau_channel_user_pushbuf_alloc(struct drm_device *dev)
{
struct nouveau_bo *pushbuf = NULL;
int location, ret;
if (nouveau_vram_pushbuf)
location = TTM_PL_FLAG_VRAM;
else
location = TTM_PL_FLAG_TT;
ret = nouveau_bo_new(dev, NULL, 65536, 0, location, 0, 0x0000, false,
true, &pushbuf);
if (ret) {
NV_ERROR(dev, "error allocating DMA push buffer: %d\n", ret);
return NULL;
}
ret = nouveau_bo_pin(pushbuf, location);
if (ret) {
NV_ERROR(dev, "error pinning DMA push buffer: %d\n", ret);
nouveau_bo_ref(NULL, &pushbuf);
return NULL;
}
return pushbuf;
}
/* allocates and initializes a fifo for user space consumption */
int
nouveau_channel_alloc(struct drm_device *dev, struct nouveau_channel **chan_ret,
struct drm_file *file_priv,
uint32_t vram_handle, uint32_t tt_handle)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_pgraph_engine *pgraph = &dev_priv->engine.graph;
struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo;
struct nouveau_channel *chan;
int channel, user;
int ret;
/*
* Alright, here is the full story
* Nvidia cards have multiple hw fifo contexts (praise them for that,
* no complicated crash-prone context switches)
* We allocate a new context for each app and let it write to it
* directly (woo, full userspace command submission !)
* When there are no more contexts, you lost
*/
for (channel = 0; channel < pfifo->channels; channel++) {
if (dev_priv->fifos[channel] == NULL)
break;
}
/* no more fifos. you lost. */
if (channel == pfifo->channels)
return -EINVAL;
dev_priv->fifos[channel] = kzalloc(sizeof(struct nouveau_channel),
GFP_KERNEL);
if (!dev_priv->fifos[channel])
return -ENOMEM;
dev_priv->fifo_alloc_count++;
chan = dev_priv->fifos[channel];
INIT_LIST_HEAD(&chan->nvsw.vbl_wait);
INIT_LIST_HEAD(&chan->fence.pending);
chan->dev = dev;
chan->id = channel;
chan->file_priv = file_priv;
chan->vram_handle = vram_handle;
chan->gart_handle = tt_handle;
NV_INFO(dev, "Allocating FIFO number %d\n", channel);
/* Allocate DMA push buffer */
chan->pushbuf_bo = nouveau_channel_user_pushbuf_alloc(dev);
if (!chan->pushbuf_bo) {
ret = -ENOMEM;
NV_ERROR(dev, "pushbuf %d\n", ret);
nouveau_channel_free(chan);
return ret;
}
/* Locate channel's user control regs */
if (dev_priv->card_type < NV_40)
user = NV03_USER(channel);
else
if (dev_priv->card_type < NV_50)
user = NV40_USER(channel);
else
user = NV50_USER(channel);
chan->user = ioremap(pci_resource_start(dev->pdev, 0) + user,
PAGE_SIZE);
if (!chan->user) {
NV_ERROR(dev, "ioremap of regs failed.\n");
nouveau_channel_free(chan);
return -ENOMEM;
}
chan->user_put = 0x40;
chan->user_get = 0x44;
/* Allocate space for per-channel fixed notifier memory */
ret = nouveau_notifier_init_channel(chan);
if (ret) {
NV_ERROR(dev, "ntfy %d\n", ret);
nouveau_channel_free(chan);
return ret;
}
/* Setup channel's default objects */
ret = nouveau_gpuobj_channel_init(chan, vram_handle, tt_handle);
if (ret) {
NV_ERROR(dev, "gpuobj %d\n", ret);
nouveau_channel_free(chan);
return ret;
}
/* Create a dma object for the push buffer */
ret = nouveau_channel_pushbuf_ctxdma_init(chan);
if (ret) {
NV_ERROR(dev, "pbctxdma %d\n", ret);
nouveau_channel_free(chan);
return ret;
}
/* disable the fifo caches */
pfifo->reassign(dev, false);
/* Create a graphics context for new channel */
ret = pgraph->create_context(chan);
if (ret) {
nouveau_channel_free(chan);
return ret;
}
/* Construct inital RAMFC for new channel */
ret = pfifo->create_context(chan);
if (ret) {
nouveau_channel_free(chan);
return ret;
}
pfifo->reassign(dev, true);
ret = nouveau_dma_init(chan);
if (!ret)
ret = nouveau_fence_init(chan);
if (ret) {
nouveau_channel_free(chan);
return ret;
}
nouveau_debugfs_channel_init(chan);
NV_INFO(dev, "%s: initialised FIFO %d\n", __func__, channel);
*chan_ret = chan;
return 0;
}
int
nouveau_channel_idle(struct nouveau_channel *chan)
{
struct drm_device *dev = chan->dev;
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_engine *engine = &dev_priv->engine;
uint32_t caches;
int idle;
if (!chan) {
NV_ERROR(dev, "no channel...\n");
return 1;
}
caches = nv_rd32(dev, NV03_PFIFO_CACHES);
nv_wr32(dev, NV03_PFIFO_CACHES, caches & ~1);
if (engine->fifo.channel_id(dev) != chan->id) {
struct nouveau_gpuobj *ramfc =
chan->ramfc ? chan->ramfc->gpuobj : NULL;
if (!ramfc) {
NV_ERROR(dev, "No RAMFC for channel %d\n", chan->id);
return 1;
}
engine->instmem.prepare_access(dev, false);
if (nv_ro32(dev, ramfc, 0) != nv_ro32(dev, ramfc, 1))
idle = 0;
else
idle = 1;
engine->instmem.finish_access(dev);
} else {
idle = (nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_GET) ==
nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_PUT));
}
nv_wr32(dev, NV03_PFIFO_CACHES, caches);
return idle;
}
/* stops a fifo */
void
nouveau_channel_free(struct nouveau_channel *chan)
{
struct drm_device *dev = chan->dev;
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_pgraph_engine *pgraph = &dev_priv->engine.graph;
struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo;
unsigned long flags;
int ret;
NV_INFO(dev, "%s: freeing fifo %d\n", __func__, chan->id);
nouveau_debugfs_channel_fini(chan);
/* Give outstanding push buffers a chance to complete */
spin_lock_irqsave(&chan->fence.lock, flags);
nouveau_fence_update(chan);
spin_unlock_irqrestore(&chan->fence.lock, flags);
if (chan->fence.sequence != chan->fence.sequence_ack) {
struct nouveau_fence *fence = NULL;
ret = nouveau_fence_new(chan, &fence, true);
if (ret == 0) {
ret = nouveau_fence_wait(fence, NULL, false, false);
nouveau_fence_unref((void *)&fence);
}
if (ret)
NV_ERROR(dev, "Failed to idle channel %d.\n", chan->id);
}
/* Ensure all outstanding fences are signaled. They should be if the
* above attempts at idling were OK, but if we failed this'll tell TTM
* we're done with the buffers.
*/
nouveau_fence_fini(chan);
/* Ensure the channel is no longer active on the GPU */
pfifo->reassign(dev, false);
if (pgraph->channel(dev) == chan) {
pgraph->fifo_access(dev, false);
pgraph->unload_context(dev);
pgraph->fifo_access(dev, true);
}
pgraph->destroy_context(chan);
if (pfifo->channel_id(dev) == chan->id) {
pfifo->disable(dev);
pfifo->unload_context(dev);
pfifo->enable(dev);
}
pfifo->destroy_context(chan);
pfifo->reassign(dev, true);
/* Release the channel's resources */
nouveau_gpuobj_ref_del(dev, &chan->pushbuf);
if (chan->pushbuf_bo) {
nouveau_bo_unpin(chan->pushbuf_bo);
nouveau_bo_ref(NULL, &chan->pushbuf_bo);
}
nouveau_gpuobj_channel_takedown(chan);
nouveau_notifier_takedown_channel(chan);
if (chan->user)
iounmap(chan->user);
dev_priv->fifos[chan->id] = NULL;
dev_priv->fifo_alloc_count--;
kfree(chan);
}
/* cleans up all the fifos from file_priv */
void
nouveau_channel_cleanup(struct drm_device *dev, struct drm_file *file_priv)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_engine *engine = &dev_priv->engine;
int i;
NV_DEBUG(dev, "clearing FIFO enables from file_priv\n");
for (i = 0; i < engine->fifo.channels; i++) {
struct nouveau_channel *chan = dev_priv->fifos[i];
if (chan && chan->file_priv == file_priv)
nouveau_channel_free(chan);
}
}
int
nouveau_channel_owner(struct drm_device *dev, struct drm_file *file_priv,
int channel)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_engine *engine = &dev_priv->engine;
if (channel >= engine->fifo.channels)
return 0;
if (dev_priv->fifos[channel] == NULL)
return 0;
return (dev_priv->fifos[channel]->file_priv == file_priv);
}
/***********************************
* ioctls wrapping the functions
***********************************/
static int
nouveau_ioctl_fifo_alloc(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct drm_nouveau_channel_alloc *init = data;
struct nouveau_channel *chan;
int ret;
NOUVEAU_CHECK_INITIALISED_WITH_RETURN;
if (dev_priv->engine.graph.accel_blocked)
return -ENODEV;
if (init->fb_ctxdma_handle == ~0 || init->tt_ctxdma_handle == ~0)
return -EINVAL;
ret = nouveau_channel_alloc(dev, &chan, file_priv,
init->fb_ctxdma_handle,
init->tt_ctxdma_handle);
if (ret)
return ret;
init->channel = chan->id;
init->subchan[0].handle = NvM2MF;
if (dev_priv->card_type < NV_50)
init->subchan[0].grclass = 0x0039;
else
init->subchan[0].grclass = 0x5039;
init->nr_subchan = 1;
/* Named memory object area */
ret = drm_gem_handle_create(file_priv, chan->notifier_bo->gem,
&init->notifier_handle);
if (ret) {
nouveau_channel_free(chan);
return ret;
}
return 0;
}
static int
nouveau_ioctl_fifo_free(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct drm_nouveau_channel_free *cfree = data;
struct nouveau_channel *chan;
NOUVEAU_CHECK_INITIALISED_WITH_RETURN;
NOUVEAU_GET_USER_CHANNEL_WITH_RETURN(cfree->channel, file_priv, chan);
nouveau_channel_free(chan);
return 0;
}
/***********************************
* finally, the ioctl table
***********************************/
struct drm_ioctl_desc nouveau_ioctls[] = {
DRM_IOCTL_DEF(DRM_NOUVEAU_CARD_INIT, nouveau_ioctl_card_init, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GETPARAM, nouveau_ioctl_getparam, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_SETPARAM, nouveau_ioctl_setparam, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
DRM_IOCTL_DEF(DRM_NOUVEAU_CHANNEL_ALLOC, nouveau_ioctl_fifo_alloc, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_CHANNEL_FREE, nouveau_ioctl_fifo_free, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GROBJ_ALLOC, nouveau_ioctl_grobj_alloc, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_NOTIFIEROBJ_ALLOC, nouveau_ioctl_notifier_alloc, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GPUOBJ_FREE, nouveau_ioctl_gpuobj_free, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GEM_NEW, nouveau_gem_ioctl_new, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GEM_PUSHBUF, nouveau_gem_ioctl_pushbuf, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GEM_PUSHBUF_CALL, nouveau_gem_ioctl_pushbuf_call, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GEM_PIN, nouveau_gem_ioctl_pin, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GEM_UNPIN, nouveau_gem_ioctl_unpin, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GEM_CPU_PREP, nouveau_gem_ioctl_cpu_prep, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GEM_CPU_FINI, nouveau_gem_ioctl_cpu_fini, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GEM_INFO, nouveau_gem_ioctl_info, DRM_AUTH),
DRM_IOCTL_DEF(DRM_NOUVEAU_GEM_PUSHBUF_CALL2, nouveau_gem_ioctl_pushbuf_call2, DRM_AUTH),
};
int nouveau_max_ioctl = DRM_ARRAY_SIZE(nouveau_ioctls);
此差异已折叠。
/*
* 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.
*
*/
#ifndef __NOUVEAU_CONNECTOR_H__
#define __NOUVEAU_CONNECTOR_H__
#include "drm_edid.h"
#include "nouveau_i2c.h"
struct nouveau_connector {
struct drm_connector base;
struct dcb_connector_table_entry *dcb;
int scaling_mode;
bool use_dithering;
struct nouveau_encoder *detected_encoder;
struct edid *edid;
struct drm_display_mode *native_mode;
};
static inline struct nouveau_connector *nouveau_connector(
struct drm_connector *con)
{
return container_of(con, struct nouveau_connector, base);
}
int nouveau_connector_create(struct drm_device *dev, int i2c_index, int type);
#endif /* __NOUVEAU_CONNECTOR_H__ */
/*
* 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.
*
*/
#ifndef __NOUVEAU_CRTC_H__
#define __NOUVEAU_CRTC_H__
struct nouveau_crtc {
struct drm_crtc base;
int index;
struct drm_display_mode *mode;
uint32_t dpms_saved_fp_control;
uint32_t fp_users;
int saturation;
int sharpness;
int last_dpms;
struct {
int cpp;
bool blanked;
uint32_t offset;
uint32_t tile_flags;
} fb;
struct {
struct nouveau_bo *nvbo;
bool visible;
uint32_t offset;
void (*set_offset)(struct nouveau_crtc *, uint32_t offset);
void (*set_pos)(struct nouveau_crtc *, int x, int y);
void (*hide)(struct nouveau_crtc *, bool update);
void (*show)(struct nouveau_crtc *, bool update);
} cursor;
struct {
struct nouveau_bo *nvbo;
uint16_t r[256];
uint16_t g[256];
uint16_t b[256];
int depth;
} lut;
int (*set_dither)(struct nouveau_crtc *crtc, bool on, bool update);
int (*set_scale)(struct nouveau_crtc *crtc, int mode, bool update);
};
static inline struct nouveau_crtc *nouveau_crtc(struct drm_crtc *crtc)
{
return container_of(crtc, struct nouveau_crtc, base);
}
static inline struct drm_crtc *to_drm_crtc(struct nouveau_crtc *crtc)
{
return &crtc->base;
}
int nv50_crtc_create(struct drm_device *dev, int index);
int nv50_cursor_init(struct nouveau_crtc *);
void nv50_cursor_fini(struct nouveau_crtc *);
int nv50_crtc_cursor_set(struct drm_crtc *drm_crtc, struct drm_file *file_priv,
uint32_t buffer_handle, uint32_t width,
uint32_t height);
int nv50_crtc_cursor_move(struct drm_crtc *drm_crtc, int x, int y);
int nv04_cursor_init(struct nouveau_crtc *);
struct nouveau_connector *
nouveau_crtc_connector_get(struct nouveau_crtc *crtc);
#endif /* __NOUVEAU_CRTC_H__ */
/*
* Copyright (C) 2009 Red Hat <bskeggs@redhat.com>
*
* 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.
*
*/
/*
* Authors:
* Ben Skeggs <bskeggs@redhat.com>
*/
#include <linux/debugfs.h>
#include "drmP.h"
#include "nouveau_drv.h"
static int
nouveau_debugfs_channel_info(struct seq_file *m, void *data)
{
struct drm_info_node *node = (struct drm_info_node *) m->private;
struct nouveau_channel *chan = node->info_ent->data;
seq_printf(m, "channel id : %d\n", chan->id);
seq_printf(m, "cpu fifo state:\n");
seq_printf(m, " base: 0x%08x\n", chan->pushbuf_base);
seq_printf(m, " max: 0x%08x\n", chan->dma.max << 2);
seq_printf(m, " cur: 0x%08x\n", chan->dma.cur << 2);
seq_printf(m, " put: 0x%08x\n", chan->dma.put << 2);
seq_printf(m, " free: 0x%08x\n", chan->dma.free << 2);
seq_printf(m, "gpu fifo state:\n");
seq_printf(m, " get: 0x%08x\n",
nvchan_rd32(chan, chan->user_get));
seq_printf(m, " put: 0x%08x\n",
nvchan_rd32(chan, chan->user_put));
seq_printf(m, "last fence : %d\n", chan->fence.sequence);
seq_printf(m, "last signalled: %d\n", chan->fence.sequence_ack);
return 0;
}
int
nouveau_debugfs_channel_init(struct nouveau_channel *chan)
{
struct drm_nouveau_private *dev_priv = chan->dev->dev_private;
struct drm_minor *minor = chan->dev->primary;
int ret;
if (!dev_priv->debugfs.channel_root) {
dev_priv->debugfs.channel_root =
debugfs_create_dir("channel", minor->debugfs_root);
if (!dev_priv->debugfs.channel_root)
return -ENOENT;
}
snprintf(chan->debugfs.name, 32, "%d", chan->id);
chan->debugfs.info.name = chan->debugfs.name;
chan->debugfs.info.show = nouveau_debugfs_channel_info;
chan->debugfs.info.driver_features = 0;
chan->debugfs.info.data = chan;
ret = drm_debugfs_create_files(&chan->debugfs.info, 1,
dev_priv->debugfs.channel_root,
chan->dev->primary);
if (ret == 0)
chan->debugfs.active = true;
return ret;
}
void
nouveau_debugfs_channel_fini(struct nouveau_channel *chan)
{
struct drm_nouveau_private *dev_priv = chan->dev->dev_private;
if (!chan->debugfs.active)
return;
drm_debugfs_remove_files(&chan->debugfs.info, 1, chan->dev->primary);
chan->debugfs.active = false;
if (chan == dev_priv->channel) {
debugfs_remove(dev_priv->debugfs.channel_root);
dev_priv->debugfs.channel_root = NULL;
}
}
static int
nouveau_debugfs_chipset_info(struct seq_file *m, void *data)
{
struct drm_info_node *node = (struct drm_info_node *) m->private;
struct drm_minor *minor = node->minor;
struct drm_device *dev = minor->dev;
struct drm_nouveau_private *dev_priv = dev->dev_private;
uint32_t ppci_0;
ppci_0 = nv_rd32(dev, dev_priv->chipset >= 0x40 ? 0x88000 : 0x1800);
seq_printf(m, "PMC_BOOT_0: 0x%08x\n", nv_rd32(dev, NV03_PMC_BOOT_0));
seq_printf(m, "PCI ID : 0x%04x:0x%04x\n",
ppci_0 & 0xffff, ppci_0 >> 16);
return 0;
}
static int
nouveau_debugfs_memory_info(struct seq_file *m, void *data)
{
struct drm_info_node *node = (struct drm_info_node *) m->private;
struct drm_minor *minor = node->minor;
struct drm_device *dev = minor->dev;
seq_printf(m, "VRAM total: %dKiB\n",
(int)(nouveau_mem_fb_amount(dev) >> 10));
return 0;
}
static struct drm_info_list nouveau_debugfs_list[] = {
{ "chipset", nouveau_debugfs_chipset_info, 0, NULL },
{ "memory", nouveau_debugfs_memory_info, 0, NULL },
};
#define NOUVEAU_DEBUGFS_ENTRIES ARRAY_SIZE(nouveau_debugfs_list)
int
nouveau_debugfs_init(struct drm_minor *minor)
{
drm_debugfs_create_files(nouveau_debugfs_list, NOUVEAU_DEBUGFS_ENTRIES,
minor->debugfs_root, minor);
return 0;
}
void
nouveau_debugfs_takedown(struct drm_minor *minor)
{
drm_debugfs_remove_files(nouveau_debugfs_list, NOUVEAU_DEBUGFS_ENTRIES,
minor);
}
/*
* 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"
#include "nouveau_drv.h"
#include "nouveau_fb.h"
#include "nouveau_fbcon.h"
static void
nouveau_user_framebuffer_destroy(struct drm_framebuffer *drm_fb)
{
struct nouveau_framebuffer *fb = nouveau_framebuffer(drm_fb);
struct drm_device *dev = drm_fb->dev;
if (drm_fb->fbdev)
nouveau_fbcon_remove(dev, drm_fb);
if (fb->nvbo) {
mutex_lock(&dev->struct_mutex);
drm_gem_object_unreference(fb->nvbo->gem);
mutex_unlock(&dev->struct_mutex);
}
drm_framebuffer_cleanup(drm_fb);
kfree(fb);
}
static int
nouveau_user_framebuffer_create_handle(struct drm_framebuffer *drm_fb,
struct drm_file *file_priv,
unsigned int *handle)
{
struct nouveau_framebuffer *fb = nouveau_framebuffer(drm_fb);
return drm_gem_handle_create(file_priv, fb->nvbo->gem, handle);
}
static const struct drm_framebuffer_funcs nouveau_framebuffer_funcs = {
.destroy = nouveau_user_framebuffer_destroy,
.create_handle = nouveau_user_framebuffer_create_handle,
};
struct drm_framebuffer *
nouveau_framebuffer_create(struct drm_device *dev, struct nouveau_bo *nvbo,
struct drm_mode_fb_cmd *mode_cmd)
{
struct nouveau_framebuffer *fb;
int ret;
fb = kzalloc(sizeof(struct nouveau_framebuffer), GFP_KERNEL);
if (!fb)
return NULL;
ret = drm_framebuffer_init(dev, &fb->base, &nouveau_framebuffer_funcs);
if (ret) {
kfree(fb);
return NULL;
}
drm_helper_mode_fill_fb_struct(&fb->base, mode_cmd);
fb->nvbo = nvbo;
return &fb->base;
}
static struct drm_framebuffer *
nouveau_user_framebuffer_create(struct drm_device *dev,
struct drm_file *file_priv,
struct drm_mode_fb_cmd *mode_cmd)
{
struct drm_framebuffer *fb;
struct drm_gem_object *gem;
gem = drm_gem_object_lookup(dev, file_priv, mode_cmd->handle);
if (!gem)
return NULL;
fb = nouveau_framebuffer_create(dev, nouveau_gem_object(gem), mode_cmd);
if (!fb) {
drm_gem_object_unreference(gem);
return NULL;
}
return fb;
}
const struct drm_mode_config_funcs nouveau_mode_config_funcs = {
.fb_create = nouveau_user_framebuffer_create,
.fb_changed = nouveau_fbcon_probe,
};
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
/*
* 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.
*
*/
#ifndef __NOUVEAU_ENCODER_H__
#define __NOUVEAU_ENCODER_H__
#include "drm_encoder_slave.h"
#include "nouveau_drv.h"
#define NV_DPMS_CLEARED 0x80
struct nouveau_encoder {
struct drm_encoder_slave base;
struct dcb_entry *dcb;
int or;
struct drm_display_mode mode;
int last_dpms;
struct nv04_output_reg restore;
void (*disconnect)(struct nouveau_encoder *encoder);
union {
struct {
int dpcd_version;
int link_nr;
int link_bw;
} dp;
};
};
static inline struct nouveau_encoder *nouveau_encoder(struct drm_encoder *enc)
{
struct drm_encoder_slave *slave = to_encoder_slave(enc);
return container_of(slave, struct nouveau_encoder, base);
}
static inline struct drm_encoder *to_drm_encoder(struct nouveau_encoder *enc)
{
return &enc->base.base;
}
struct nouveau_connector *
nouveau_encoder_connector_get(struct nouveau_encoder *encoder);
int nv50_sor_create(struct drm_device *dev, struct dcb_entry *entry);
int nv50_dac_create(struct drm_device *dev, struct dcb_entry *entry);
struct bit_displayport_encoder_table {
uint32_t match;
uint8_t record_nr;
uint8_t unknown;
uint16_t script0;
uint16_t script1;
uint16_t unknown_table;
} __attribute__ ((packed));
struct bit_displayport_encoder_table_entry {
uint8_t vs_level;
uint8_t pre_level;
uint8_t reg0;
uint8_t reg1;
uint8_t reg2;
} __attribute__ ((packed));
#endif /* __NOUVEAU_ENCODER_H__ */
/*
* 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.
*
*/
#ifndef __NOUVEAU_FB_H__
#define __NOUVEAU_FB_H__
struct nouveau_framebuffer {
struct drm_framebuffer base;
struct nouveau_bo *nvbo;
};
static inline struct nouveau_framebuffer *
nouveau_framebuffer(struct drm_framebuffer *fb)
{
return container_of(fb, struct nouveau_framebuffer, base);
}
extern const struct drm_mode_config_funcs nouveau_mode_config_funcs;
struct drm_framebuffer *
nouveau_framebuffer_create(struct drm_device *, struct nouveau_bo *,
struct drm_mode_fb_cmd *);
#endif /* __NOUVEAU_FB_H__ */
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册