Commit 5addcf0a authored by Dave Airlie's avatar Dave Airlie

nouveau: add runtime PM support (v0.9)

This hooks nouveau up to the runtime PM system to enable
dynamic power management for secondary GPUs in switchable
and optimus laptops.

a) rewrite suspend/resume printks to hide them during dynamic s/r
to avoid cluttering logs
b) add runtime pm suspend to irq handler, crtc display, ioctl handler,
connector status,
c) handle hdmi audio dynamic power on/off using magic register.

v0.5:
make sure we hit D3 properly
fix fbdev_set_suspend locking interaction, we only will poweroff if we have no
active crtcs/fbcon anyways.
add reference for active crtcs.
sprinkle mark last busy for autosuspend timeout

v0.6:
allow more flexible debugging - to avoid log spam
add option to enable/disable dynpm
got to D3Cold

v0.7:
add hdmi audio support.

v0.8:
call autosuspend from idle, so pci config space access doesn't go straight
back to sleep, this makes starting X faster.
only signal usage if we actually handle the irq, otherwise usb keeps us awake.
fix nv50 display active powerdown

v0.9:
use masking function to enable hdmi audio
set busy when we fail to suspend
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent 13bb9cc8
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
#include <core/subdev.h> #include <core/subdev.h>
#include <core/printk.h> #include <core/printk.h>
int nv_printk_suspend_level = NV_DBG_DEBUG;
void void
nv_printk_(struct nouveau_object *object, const char *pfx, int level, nv_printk_(struct nouveau_object *object, const char *pfx, int level,
const char *fmt, ...) const char *fmt, ...)
...@@ -72,3 +74,20 @@ nv_printk_(struct nouveau_object *object, const char *pfx, int level, ...@@ -72,3 +74,20 @@ nv_printk_(struct nouveau_object *object, const char *pfx, int level,
vprintk(mfmt, args); vprintk(mfmt, args);
va_end(args); va_end(args);
} }
#define CONV_LEVEL(x) case NV_DBG_##x: return NV_PRINTK_##x
const char *nv_printk_level_to_pfx(int level)
{
switch (level) {
CONV_LEVEL(FATAL);
CONV_LEVEL(ERROR);
CONV_LEVEL(WARN);
CONV_LEVEL(INFO);
CONV_LEVEL(DEBUG);
CONV_LEVEL(PARANOIA);
CONV_LEVEL(TRACE);
CONV_LEVEL(SPAM);
}
return NV_PRINTK_DEBUG;
}
...@@ -15,6 +15,12 @@ struct nouveau_object; ...@@ -15,6 +15,12 @@ struct nouveau_object;
#define NV_PRINTK_TRACE KERN_DEBUG #define NV_PRINTK_TRACE KERN_DEBUG
#define NV_PRINTK_SPAM KERN_DEBUG #define NV_PRINTK_SPAM KERN_DEBUG
extern int nv_printk_suspend_level;
#define NV_DBG_SUSPEND (nv_printk_suspend_level)
#define NV_PRINTK_SUSPEND (nv_printk_level_to_pfx(nv_printk_suspend_level))
const char *nv_printk_level_to_pfx(int level);
void __printf(4, 5) void __printf(4, 5)
nv_printk_(struct nouveau_object *, const char *, int, const char *, ...); nv_printk_(struct nouveau_object *, const char *, int, const char *, ...);
...@@ -31,6 +37,13 @@ nv_printk_(struct nouveau_object *, const char *, int, const char *, ...); ...@@ -31,6 +37,13 @@ nv_printk_(struct nouveau_object *, const char *, int, const char *, ...);
#define nv_trace(o,f,a...) nv_printk((o), TRACE, f, ##a) #define nv_trace(o,f,a...) nv_printk((o), TRACE, f, ##a)
#define nv_spam(o,f,a...) nv_printk((o), SPAM, f, ##a) #define nv_spam(o,f,a...) nv_printk((o), SPAM, f, ##a)
#define nv_suspend(o,f,a...) nv_printk((o), SUSPEND, f, ##a)
static inline void nv_suspend_set_printk_level(int level)
{
nv_printk_suspend_level = level;
}
#define nv_assert(f,a...) do { \ #define nv_assert(f,a...) do { \
if (NV_DBG_FATAL <= CONFIG_NOUVEAU_DEBUG) \ if (NV_DBG_FATAL <= CONFIG_NOUVEAU_DEBUG) \
nv_printk_(NULL, NV_PRINTK_FATAL, NV_DBG_FATAL, f "\n", ##a); \ nv_printk_(NULL, NV_PRINTK_FATAL, NV_DBG_FATAL, f "\n", ##a); \
......
...@@ -2165,7 +2165,7 @@ nvbios_init(struct nouveau_subdev *subdev, bool execute) ...@@ -2165,7 +2165,7 @@ nvbios_init(struct nouveau_subdev *subdev, bool execute)
u16 data; u16 data;
if (execute) if (execute)
nv_info(bios, "running init tables\n"); nv_suspend(bios, "running init tables\n");
while (!ret && (data = (init_script(bios, ++i)))) { while (!ret && (data = (init_script(bios, ++i)))) {
struct nvbios_init init = { struct nvbios_init init = {
.subdev = subdev, .subdev = subdev,
......
...@@ -23,16 +23,20 @@ ...@@ -23,16 +23,20 @@
*/ */
#include <subdev/mc.h> #include <subdev/mc.h>
#include <linux/pm_runtime.h>
static irqreturn_t static irqreturn_t
nouveau_mc_intr(int irq, void *arg) nouveau_mc_intr(int irq, void *arg)
{ {
struct nouveau_mc *pmc = arg; struct nouveau_mc *pmc = arg;
const struct nouveau_mc_intr *map = pmc->intr_map; const struct nouveau_mc_intr *map = pmc->intr_map;
struct nouveau_device *device = nv_device(pmc);
struct nouveau_subdev *unit; struct nouveau_subdev *unit;
u32 stat, intr; u32 stat, intr;
intr = stat = nv_rd32(pmc, 0x000100); intr = stat = nv_rd32(pmc, 0x000100);
if (intr == 0xffffffff)
return IRQ_NONE;
while (stat && map->stat) { while (stat && map->stat) {
if (stat & map->stat) { if (stat & map->stat) {
unit = nouveau_subdev(pmc, map->unit); unit = nouveau_subdev(pmc, map->unit);
...@@ -47,6 +51,8 @@ nouveau_mc_intr(int irq, void *arg) ...@@ -47,6 +51,8 @@ nouveau_mc_intr(int irq, void *arg)
nv_error(pmc, "unknown intr 0x%08x\n", stat); nv_error(pmc, "unknown intr 0x%08x\n", stat);
} }
if (stat == IRQ_HANDLED)
pm_runtime_mark_last_busy(&device->pdev->dev);
return stat ? IRQ_HANDLED : IRQ_NONE; return stat ? IRQ_HANDLED : IRQ_NONE;
} }
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE. * DEALINGS IN THE SOFTWARE.
*/ */
#include <linux/pm_runtime.h>
#include <drm/drmP.h> #include <drm/drmP.h>
#include <drm/drm_crtc_helper.h> #include <drm/drm_crtc_helper.h>
...@@ -1007,13 +1008,59 @@ nv04_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) ...@@ -1007,13 +1008,59 @@ nv04_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
return 0; return 0;
} }
int
nouveau_crtc_set_config(struct drm_mode_set *set)
{
struct drm_device *dev;
struct nouveau_drm *drm;
int ret;
struct drm_crtc *crtc;
bool active = false;
if (!set || !set->crtc)
return -EINVAL;
dev = set->crtc->dev;
/* get a pm reference here */
ret = pm_runtime_get_sync(dev->dev);
if (ret < 0)
return ret;
ret = drm_crtc_helper_set_config(set);
drm = nouveau_drm(dev);
/* if we get here with no crtcs active then we can drop a reference */
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
if (crtc->enabled)
active = true;
}
pm_runtime_mark_last_busy(dev->dev);
/* if we have active crtcs and we don't have a power ref,
take the current one */
if (active && !drm->have_disp_power_ref) {
drm->have_disp_power_ref = true;
return ret;
}
/* if we have no active crtcs, then drop the power ref
we got before */
if (!active && drm->have_disp_power_ref) {
pm_runtime_put_autosuspend(dev->dev);
drm->have_disp_power_ref = false;
}
/* drop the power reference we got coming in here */
pm_runtime_put_autosuspend(dev->dev);
return ret;
}
static const struct drm_crtc_funcs nv04_crtc_funcs = { static const struct drm_crtc_funcs nv04_crtc_funcs = {
.save = nv_crtc_save, .save = nv_crtc_save,
.restore = nv_crtc_restore, .restore = nv_crtc_restore,
.cursor_set = nv04_crtc_cursor_set, .cursor_set = nv04_crtc_cursor_set,
.cursor_move = nv04_crtc_cursor_move, .cursor_move = nv04_crtc_cursor_move,
.gamma_set = nv_crtc_gamma_set, .gamma_set = nv_crtc_gamma_set,
.set_config = drm_crtc_helper_set_config, .set_config = nouveau_crtc_set_config,
.page_flip = nouveau_crtc_page_flip, .page_flip = nouveau_crtc_page_flip,
.destroy = nv_crtc_destroy, .destroy = nv_crtc_destroy,
}; };
......
...@@ -25,8 +25,27 @@ ...@@ -25,8 +25,27 @@
#define NOUVEAU_DSM_POWER_SPEED 0x01 #define NOUVEAU_DSM_POWER_SPEED 0x01
#define NOUVEAU_DSM_POWER_STAMINA 0x02 #define NOUVEAU_DSM_POWER_STAMINA 0x02
#define NOUVEAU_DSM_OPTIMUS_FN 0x1A #define NOUVEAU_DSM_OPTIMUS_CAPS 0x1A
#define NOUVEAU_DSM_OPTIMUS_ARGS 0x03000001 #define NOUVEAU_DSM_OPTIMUS_FLAGS 0x1B
#define NOUVEAU_DSM_OPTIMUS_POWERDOWN_PS3 (3 << 24)
#define NOUVEAU_DSM_OPTIMUS_NO_POWERDOWN_PS3 (2 << 24)
#define NOUVEAU_DSM_OPTIMUS_FLAGS_CHANGED (1)
#define NOUVEAU_DSM_OPTIMUS_SET_POWERDOWN (NOUVEAU_DSM_OPTIMUS_POWERDOWN_PS3 | NOUVEAU_DSM_OPTIMUS_FLAGS_CHANGED)
/* result of the optimus caps function */
#define OPTIMUS_ENABLED (1 << 0)
#define OPTIMUS_STATUS_MASK (3 << 3)
#define OPTIMUS_STATUS_OFF (0 << 3)
#define OPTIMUS_STATUS_ON_ENABLED (1 << 3)
#define OPTIMUS_STATUS_PWR_STABLE (3 << 3)
#define OPTIMUS_DISPLAY_HOTPLUG (1 << 6)
#define OPTIMUS_CAPS_MASK (7 << 24)
#define OPTIMUS_DYNAMIC_PWR_CAP (1 << 24)
#define OPTIMUS_AUDIO_CAPS_MASK (3 << 27)
#define OPTIMUS_HDA_CODEC_MASK (2 << 27) /* hda bios control */
static struct nouveau_dsm_priv { static struct nouveau_dsm_priv {
bool dsm_detected; bool dsm_detected;
...@@ -251,9 +270,18 @@ static int nouveau_dsm_pci_probe(struct pci_dev *pdev) ...@@ -251,9 +270,18 @@ static int nouveau_dsm_pci_probe(struct pci_dev *pdev)
retval |= NOUVEAU_DSM_HAS_MUX; retval |= NOUVEAU_DSM_HAS_MUX;
if (nouveau_test_dsm(dhandle, nouveau_optimus_dsm, if (nouveau_test_dsm(dhandle, nouveau_optimus_dsm,
NOUVEAU_DSM_OPTIMUS_FN)) NOUVEAU_DSM_OPTIMUS_CAPS))
retval |= NOUVEAU_DSM_HAS_OPT; retval |= NOUVEAU_DSM_HAS_OPT;
if (retval & NOUVEAU_DSM_HAS_OPT) {
uint32_t result;
nouveau_optimus_dsm(dhandle, NOUVEAU_DSM_OPTIMUS_CAPS, 0,
&result);
dev_info(&pdev->dev, "optimus capabilities: %s, status %s%s\n",
(result & OPTIMUS_ENABLED) ? "enabled" : "disabled",
(result & OPTIMUS_DYNAMIC_PWR_CAP) ? "dynamic power, " : "",
(result & OPTIMUS_HDA_CODEC_MASK) ? "hda bios codec supported" : "");
}
if (retval) if (retval)
nouveau_dsm_priv.dhandle = dhandle; nouveau_dsm_priv.dhandle = dhandle;
...@@ -328,8 +356,12 @@ void nouveau_switcheroo_optimus_dsm(void) ...@@ -328,8 +356,12 @@ void nouveau_switcheroo_optimus_dsm(void)
if (!nouveau_dsm_priv.optimus_detected) if (!nouveau_dsm_priv.optimus_detected)
return; return;
nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FN, nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FLAGS,
NOUVEAU_DSM_OPTIMUS_ARGS, &result); 0x3, &result);
nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_CAPS,
NOUVEAU_DSM_OPTIMUS_SET_POWERDOWN, &result);
} }
void nouveau_unregister_dsm_handler(void) void nouveau_unregister_dsm_handler(void)
......
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
#include <acpi/button.h> #include <acpi/button.h>
#include <linux/pm_runtime.h>
#include <drm/drmP.h> #include <drm/drmP.h>
#include <drm/drm_edid.h> #include <drm/drm_edid.h>
#include <drm/drm_crtc_helper.h> #include <drm/drm_crtc_helper.h>
...@@ -240,6 +242,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) ...@@ -240,6 +242,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
struct nouveau_encoder *nv_partner; struct nouveau_encoder *nv_partner;
struct nouveau_i2c_port *i2c; struct nouveau_i2c_port *i2c;
int type; int type;
int ret;
enum drm_connector_status conn_status = connector_status_disconnected;
/* Cleanup the previous EDID block. */ /* Cleanup the previous EDID block. */
if (nv_connector->edid) { if (nv_connector->edid) {
...@@ -248,6 +252,10 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) ...@@ -248,6 +252,10 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
nv_connector->edid = NULL; nv_connector->edid = NULL;
} }
ret = pm_runtime_get_sync(connector->dev->dev);
if (ret < 0)
return conn_status;
i2c = nouveau_connector_ddc_detect(connector, &nv_encoder); i2c = nouveau_connector_ddc_detect(connector, &nv_encoder);
if (i2c) { if (i2c) {
nv_connector->edid = drm_get_edid(connector, &i2c->adapter); nv_connector->edid = drm_get_edid(connector, &i2c->adapter);
...@@ -263,7 +271,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) ...@@ -263,7 +271,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
!nouveau_dp_detect(to_drm_encoder(nv_encoder))) { !nouveau_dp_detect(to_drm_encoder(nv_encoder))) {
NV_ERROR(drm, "Detected %s, but failed init\n", NV_ERROR(drm, "Detected %s, but failed init\n",
drm_get_connector_name(connector)); drm_get_connector_name(connector));
return connector_status_disconnected; conn_status = connector_status_disconnected;
goto out;
} }
/* Override encoder type for DVI-I based on whether EDID /* Override encoder type for DVI-I based on whether EDID
...@@ -290,13 +299,15 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) ...@@ -290,13 +299,15 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
} }
nouveau_connector_set_encoder(connector, nv_encoder); nouveau_connector_set_encoder(connector, nv_encoder);
return connector_status_connected; conn_status = connector_status_connected;
goto out;
} }
nv_encoder = nouveau_connector_of_detect(connector); nv_encoder = nouveau_connector_of_detect(connector);
if (nv_encoder) { if (nv_encoder) {
nouveau_connector_set_encoder(connector, nv_encoder); nouveau_connector_set_encoder(connector, nv_encoder);
return connector_status_connected; conn_status = connector_status_connected;
goto out;
} }
detect_analog: detect_analog:
...@@ -311,12 +322,18 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) ...@@ -311,12 +322,18 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
if (helper->detect(encoder, connector) == if (helper->detect(encoder, connector) ==
connector_status_connected) { connector_status_connected) {
nouveau_connector_set_encoder(connector, nv_encoder); nouveau_connector_set_encoder(connector, nv_encoder);
return connector_status_connected; conn_status = connector_status_connected;
goto out;
} }
} }
return connector_status_disconnected; out:
pm_runtime_mark_last_busy(connector->dev->dev);
pm_runtime_put_autosuspend(connector->dev->dev);
return conn_status;
} }
static enum drm_connector_status static enum drm_connector_status
......
...@@ -394,7 +394,7 @@ nouveau_display_suspend(struct drm_device *dev) ...@@ -394,7 +394,7 @@ nouveau_display_suspend(struct drm_device *dev)
nouveau_display_fini(dev); nouveau_display_fini(dev);
NV_INFO(drm, "unpinning framebuffer(s)...\n"); NV_SUSPEND(drm, "unpinning framebuffer(s)...\n");
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
struct nouveau_framebuffer *nouveau_fb; struct nouveau_framebuffer *nouveau_fb;
...@@ -416,7 +416,7 @@ nouveau_display_suspend(struct drm_device *dev) ...@@ -416,7 +416,7 @@ nouveau_display_suspend(struct drm_device *dev)
} }
void void
nouveau_display_resume(struct drm_device *dev) nouveau_display_repin(struct drm_device *dev)
{ {
struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_drm *drm = nouveau_drm(dev);
struct drm_crtc *crtc; struct drm_crtc *crtc;
...@@ -441,10 +441,12 @@ nouveau_display_resume(struct drm_device *dev) ...@@ -441,10 +441,12 @@ nouveau_display_resume(struct drm_device *dev)
if (ret) if (ret)
NV_ERROR(drm, "Could not pin/map cursor.\n"); NV_ERROR(drm, "Could not pin/map cursor.\n");
} }
}
nouveau_fbcon_set_suspend(dev, 0); void
nouveau_fbcon_zfill_all(dev); nouveau_display_resume(struct drm_device *dev)
{
struct drm_crtc *crtc;
nouveau_display_init(dev); nouveau_display_init(dev);
/* Force CLUT to get re-loaded during modeset */ /* Force CLUT to get re-loaded during modeset */
......
...@@ -57,6 +57,7 @@ void nouveau_display_destroy(struct drm_device *dev); ...@@ -57,6 +57,7 @@ void nouveau_display_destroy(struct drm_device *dev);
int nouveau_display_init(struct drm_device *dev); int nouveau_display_init(struct drm_device *dev);
void nouveau_display_fini(struct drm_device *dev); void nouveau_display_fini(struct drm_device *dev);
int nouveau_display_suspend(struct drm_device *dev); int nouveau_display_suspend(struct drm_device *dev);
void nouveau_display_repin(struct drm_device *dev);
void nouveau_display_resume(struct drm_device *dev); void nouveau_display_resume(struct drm_device *dev);
int nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, int nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
...@@ -71,6 +72,7 @@ int nouveau_display_dumb_map_offset(struct drm_file *, struct drm_device *, ...@@ -71,6 +72,7 @@ int nouveau_display_dumb_map_offset(struct drm_file *, struct drm_device *,
void nouveau_hdmi_mode_set(struct drm_encoder *, struct drm_display_mode *); void nouveau_hdmi_mode_set(struct drm_encoder *, struct drm_display_mode *);
int nouveau_crtc_set_config(struct drm_mode_set *set);
#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
extern int nouveau_backlight_init(struct drm_device *); extern int nouveau_backlight_init(struct drm_device *);
extern void nouveau_backlight_exit(struct drm_device *); extern void nouveau_backlight_exit(struct drm_device *);
......
...@@ -25,7 +25,10 @@ ...@@ -25,7 +25,10 @@
#include <linux/console.h> #include <linux/console.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/pci.h> #include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/vga_switcheroo.h>
#include "drmP.h"
#include "drm_crtc_helper.h"
#include <core/device.h> #include <core/device.h>
#include <core/client.h> #include <core/client.h>
#include <core/gpuobj.h> #include <core/gpuobj.h>
...@@ -69,6 +72,10 @@ MODULE_PARM_DESC(modeset, "enable driver (default: auto, " ...@@ -69,6 +72,10 @@ MODULE_PARM_DESC(modeset, "enable driver (default: auto, "
int nouveau_modeset = -1; int nouveau_modeset = -1;
module_param_named(modeset, nouveau_modeset, int, 0400); module_param_named(modeset, nouveau_modeset, int, 0400);
MODULE_PARM_DESC(runpm, "disable (0), force enable (1), optimus only default (-1)");
int nouveau_runtime_pm = -1;
module_param_named(runpm, nouveau_runtime_pm, int, 0400);
static struct drm_driver driver; static struct drm_driver driver;
static int static int
...@@ -296,6 +303,31 @@ static int nouveau_drm_probe(struct pci_dev *pdev, ...@@ -296,6 +303,31 @@ static int nouveau_drm_probe(struct pci_dev *pdev,
return 0; return 0;
} }
#define PCI_CLASS_MULTIMEDIA_HD_AUDIO 0x0403
static void
nouveau_get_hdmi_dev(struct drm_device *dev)
{
struct nouveau_drm *drm = dev->dev_private;
struct pci_dev *pdev = dev->pdev;
/* subfunction one is a hdmi audio device? */
drm->hdmi_device = pci_get_bus_and_slot((unsigned int)pdev->bus->number,
PCI_DEVFN(PCI_SLOT(pdev->devfn), 1));
if (!drm->hdmi_device) {
DRM_INFO("hdmi device not found %d %d %d\n", pdev->bus->number, PCI_SLOT(pdev->devfn), 1);
return;
}
if ((drm->hdmi_device->class >> 8) != PCI_CLASS_MULTIMEDIA_HD_AUDIO) {
DRM_INFO("possible hdmi device not audio %d\n", drm->hdmi_device->class);
pci_dev_put(drm->hdmi_device);
drm->hdmi_device = NULL;
return;
}
}
static int static int
nouveau_drm_load(struct drm_device *dev, unsigned long flags) nouveau_drm_load(struct drm_device *dev, unsigned long flags)
{ {
...@@ -314,6 +346,8 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags) ...@@ -314,6 +346,8 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags)
INIT_LIST_HEAD(&drm->clients); INIT_LIST_HEAD(&drm->clients);
spin_lock_init(&drm->tile.lock); spin_lock_init(&drm->tile.lock);
nouveau_get_hdmi_dev(dev);
/* make sure AGP controller is in a consistent state before we /* make sure AGP controller is in a consistent state before we
* (possibly) execute vbios init tables (see nouveau_agp.h) * (possibly) execute vbios init tables (see nouveau_agp.h)
*/ */
...@@ -388,6 +422,15 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags) ...@@ -388,6 +422,15 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags)
nouveau_accel_init(drm); nouveau_accel_init(drm);
nouveau_fbcon_init(dev); nouveau_fbcon_init(dev);
if (nouveau_runtime_pm != 0) {
pm_runtime_use_autosuspend(dev->dev);
pm_runtime_set_autosuspend_delay(dev->dev, 5000);
pm_runtime_set_active(dev->dev);
pm_runtime_allow(dev->dev);
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put(dev->dev);
}
return 0; return 0;
fail_dispinit: fail_dispinit:
...@@ -409,6 +452,7 @@ nouveau_drm_unload(struct drm_device *dev) ...@@ -409,6 +452,7 @@ nouveau_drm_unload(struct drm_device *dev)
{ {
struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_drm *drm = nouveau_drm(dev);
pm_runtime_get_sync(dev->dev);
nouveau_fbcon_fini(dev); nouveau_fbcon_fini(dev);
nouveau_accel_fini(drm); nouveau_accel_fini(drm);
...@@ -424,6 +468,8 @@ nouveau_drm_unload(struct drm_device *dev) ...@@ -424,6 +468,8 @@ nouveau_drm_unload(struct drm_device *dev)
nouveau_agp_fini(drm); nouveau_agp_fini(drm);
nouveau_vga_fini(drm); nouveau_vga_fini(drm);
if (drm->hdmi_device)
pci_dev_put(drm->hdmi_device);
nouveau_cli_destroy(&drm->client); nouveau_cli_destroy(&drm->client);
return 0; return 0;
} }
...@@ -450,19 +496,16 @@ nouveau_do_suspend(struct drm_device *dev) ...@@ -450,19 +496,16 @@ nouveau_do_suspend(struct drm_device *dev)
int ret; int ret;
if (dev->mode_config.num_crtc) { if (dev->mode_config.num_crtc) {
NV_INFO(drm, "suspending fbcon...\n"); NV_SUSPEND(drm, "suspending display...\n");
nouveau_fbcon_set_suspend(dev, 1);
NV_INFO(drm, "suspending display...\n");
ret = nouveau_display_suspend(dev); ret = nouveau_display_suspend(dev);
if (ret) if (ret)
return ret; return ret;
} }
NV_INFO(drm, "evicting buffers...\n"); NV_SUSPEND(drm, "evicting buffers...\n");
ttm_bo_evict_mm(&drm->ttm.bdev, TTM_PL_VRAM); ttm_bo_evict_mm(&drm->ttm.bdev, TTM_PL_VRAM);
NV_INFO(drm, "waiting for kernel channels to go idle...\n"); NV_SUSPEND(drm, "waiting for kernel channels to go idle...\n");
if (drm->cechan) { if (drm->cechan) {
ret = nouveau_channel_idle(drm->cechan); ret = nouveau_channel_idle(drm->cechan);
if (ret) if (ret)
...@@ -475,7 +518,7 @@ nouveau_do_suspend(struct drm_device *dev) ...@@ -475,7 +518,7 @@ nouveau_do_suspend(struct drm_device *dev)
return ret; return ret;
} }
NV_INFO(drm, "suspending client object trees...\n"); NV_SUSPEND(drm, "suspending client object trees...\n");
if (drm->fence && nouveau_fence(drm)->suspend) { if (drm->fence && nouveau_fence(drm)->suspend) {
if (!nouveau_fence(drm)->suspend(drm)) if (!nouveau_fence(drm)->suspend(drm))
return -ENOMEM; return -ENOMEM;
...@@ -487,7 +530,7 @@ nouveau_do_suspend(struct drm_device *dev) ...@@ -487,7 +530,7 @@ nouveau_do_suspend(struct drm_device *dev)
goto fail_client; goto fail_client;
} }
NV_INFO(drm, "suspending kernel object tree...\n"); NV_SUSPEND(drm, "suspending kernel object tree...\n");
ret = nouveau_client_fini(&drm->client.base, true); ret = nouveau_client_fini(&drm->client.base, true);
if (ret) if (ret)
goto fail_client; goto fail_client;
...@@ -501,7 +544,7 @@ nouveau_do_suspend(struct drm_device *dev) ...@@ -501,7 +544,7 @@ nouveau_do_suspend(struct drm_device *dev)
} }
if (dev->mode_config.num_crtc) { if (dev->mode_config.num_crtc) {
NV_INFO(drm, "resuming display...\n"); NV_SUSPEND(drm, "resuming display...\n");
nouveau_display_resume(dev); nouveau_display_resume(dev);
} }
return ret; return ret;
...@@ -513,9 +556,14 @@ int nouveau_pmops_suspend(struct device *dev) ...@@ -513,9 +556,14 @@ int nouveau_pmops_suspend(struct device *dev)
struct drm_device *drm_dev = pci_get_drvdata(pdev); struct drm_device *drm_dev = pci_get_drvdata(pdev);
int ret; int ret;
if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF) if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF ||
drm_dev->switch_power_state == DRM_SWITCH_POWER_DYNAMIC_OFF)
return 0; return 0;
if (drm_dev->mode_config.num_crtc)
nouveau_fbcon_set_suspend(drm_dev, 1);
nv_suspend_set_printk_level(NV_DBG_INFO);
ret = nouveau_do_suspend(drm_dev); ret = nouveau_do_suspend(drm_dev);
if (ret) if (ret)
return ret; return ret;
...@@ -523,6 +571,7 @@ int nouveau_pmops_suspend(struct device *dev) ...@@ -523,6 +571,7 @@ int nouveau_pmops_suspend(struct device *dev)
pci_save_state(pdev); pci_save_state(pdev);
pci_disable_device(pdev); pci_disable_device(pdev);
pci_set_power_state(pdev, PCI_D3hot); pci_set_power_state(pdev, PCI_D3hot);
nv_suspend_set_printk_level(NV_DBG_DEBUG);
return 0; return 0;
} }
...@@ -533,15 +582,15 @@ nouveau_do_resume(struct drm_device *dev) ...@@ -533,15 +582,15 @@ nouveau_do_resume(struct drm_device *dev)
struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_cli *cli; struct nouveau_cli *cli;
NV_INFO(drm, "re-enabling device...\n"); NV_SUSPEND(drm, "re-enabling device...\n");
nouveau_agp_reset(drm); nouveau_agp_reset(drm);
NV_INFO(drm, "resuming kernel object tree...\n"); NV_SUSPEND(drm, "resuming kernel object tree...\n");
nouveau_client_init(&drm->client.base); nouveau_client_init(&drm->client.base);
nouveau_agp_init(drm); nouveau_agp_init(drm);
NV_INFO(drm, "resuming client object trees...\n"); NV_SUSPEND(drm, "resuming client object trees...\n");
if (drm->fence && nouveau_fence(drm)->resume) if (drm->fence && nouveau_fence(drm)->resume)
nouveau_fence(drm)->resume(drm); nouveau_fence(drm)->resume(drm);
...@@ -553,9 +602,10 @@ nouveau_do_resume(struct drm_device *dev) ...@@ -553,9 +602,10 @@ nouveau_do_resume(struct drm_device *dev)
nouveau_pm_resume(dev); nouveau_pm_resume(dev);
if (dev->mode_config.num_crtc) { if (dev->mode_config.num_crtc) {
NV_INFO(drm, "resuming display...\n"); NV_SUSPEND(drm, "resuming display...\n");
nouveau_display_resume(dev); nouveau_display_repin(dev);
} }
return 0; return 0;
} }
...@@ -565,7 +615,8 @@ int nouveau_pmops_resume(struct device *dev) ...@@ -565,7 +615,8 @@ int nouveau_pmops_resume(struct device *dev)
struct drm_device *drm_dev = pci_get_drvdata(pdev); struct drm_device *drm_dev = pci_get_drvdata(pdev);
int ret; int ret;
if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF) if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF ||
drm_dev->switch_power_state == DRM_SWITCH_POWER_DYNAMIC_OFF)
return 0; return 0;
pci_set_power_state(pdev, PCI_D0); pci_set_power_state(pdev, PCI_D0);
...@@ -575,23 +626,54 @@ int nouveau_pmops_resume(struct device *dev) ...@@ -575,23 +626,54 @@ int nouveau_pmops_resume(struct device *dev)
return ret; return ret;
pci_set_master(pdev); pci_set_master(pdev);
return nouveau_do_resume(drm_dev); nv_suspend_set_printk_level(NV_DBG_INFO);
ret = nouveau_do_resume(drm_dev);
if (ret) {
nv_suspend_set_printk_level(NV_DBG_DEBUG);
return ret;
}
if (drm_dev->mode_config.num_crtc)
nouveau_fbcon_set_suspend(drm_dev, 0);
nouveau_fbcon_zfill_all(drm_dev);
nouveau_display_resume(drm_dev);
nv_suspend_set_printk_level(NV_DBG_DEBUG);
return 0;
} }
static int nouveau_pmops_freeze(struct device *dev) static int nouveau_pmops_freeze(struct device *dev)
{ {
struct pci_dev *pdev = to_pci_dev(dev); struct pci_dev *pdev = to_pci_dev(dev);
struct drm_device *drm_dev = pci_get_drvdata(pdev); struct drm_device *drm_dev = pci_get_drvdata(pdev);
int ret;
nv_suspend_set_printk_level(NV_DBG_INFO);
if (drm_dev->mode_config.num_crtc)
nouveau_fbcon_set_suspend(drm_dev, 1);
return nouveau_do_suspend(drm_dev); ret = nouveau_do_suspend(drm_dev);
nv_suspend_set_printk_level(NV_DBG_DEBUG);
return ret;
} }
static int nouveau_pmops_thaw(struct device *dev) static int nouveau_pmops_thaw(struct device *dev)
{ {
struct pci_dev *pdev = to_pci_dev(dev); struct pci_dev *pdev = to_pci_dev(dev);
struct drm_device *drm_dev = pci_get_drvdata(pdev); struct drm_device *drm_dev = pci_get_drvdata(pdev);
int ret;
return nouveau_do_resume(drm_dev); nv_suspend_set_printk_level(NV_DBG_INFO);
ret = nouveau_do_resume(drm_dev);
if (ret) {
nv_suspend_set_printk_level(NV_DBG_DEBUG);
return ret;
}
if (drm_dev->mode_config.num_crtc)
nouveau_fbcon_set_suspend(drm_dev, 0);
nouveau_fbcon_zfill_all(drm_dev);
nouveau_display_resume(drm_dev);
nv_suspend_set_printk_level(NV_DBG_DEBUG);
return 0;
} }
...@@ -604,19 +686,24 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv) ...@@ -604,19 +686,24 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv)
char name[32], tmpname[TASK_COMM_LEN]; char name[32], tmpname[TASK_COMM_LEN];
int ret; int ret;
/* need to bring up power immediately if opening device */
ret = pm_runtime_get_sync(dev->dev);
if (ret < 0)
return ret;
get_task_comm(tmpname, current); get_task_comm(tmpname, current);
snprintf(name, sizeof(name), "%s[%d]", tmpname, pid_nr(fpriv->pid)); snprintf(name, sizeof(name), "%s[%d]", tmpname, pid_nr(fpriv->pid));
ret = nouveau_cli_create(pdev, name, sizeof(*cli), (void **)&cli); ret = nouveau_cli_create(pdev, name, sizeof(*cli), (void **)&cli);
if (ret) if (ret)
return ret; goto out_suspend;
if (nv_device(drm->device)->card_type >= NV_50) { if (nv_device(drm->device)->card_type >= NV_50) {
ret = nouveau_vm_new(nv_device(drm->device), 0, (1ULL << 40), ret = nouveau_vm_new(nv_device(drm->device), 0, (1ULL << 40),
0x1000, &cli->base.vm); 0x1000, &cli->base.vm);
if (ret) { if (ret) {
nouveau_cli_destroy(cli); nouveau_cli_destroy(cli);
return ret; goto out_suspend;
} }
} }
...@@ -625,7 +712,12 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv) ...@@ -625,7 +712,12 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv)
mutex_lock(&drm->client.mutex); mutex_lock(&drm->client.mutex);
list_add(&cli->head, &drm->clients); list_add(&cli->head, &drm->clients);
mutex_unlock(&drm->client.mutex); mutex_unlock(&drm->client.mutex);
return 0;
out_suspend:
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
return ret;
} }
static void static void
...@@ -634,12 +726,15 @@ nouveau_drm_preclose(struct drm_device *dev, struct drm_file *fpriv) ...@@ -634,12 +726,15 @@ nouveau_drm_preclose(struct drm_device *dev, struct drm_file *fpriv)
struct nouveau_cli *cli = nouveau_cli(fpriv); struct nouveau_cli *cli = nouveau_cli(fpriv);
struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_drm *drm = nouveau_drm(dev);
pm_runtime_get_sync(dev->dev);
if (cli->abi16) if (cli->abi16)
nouveau_abi16_fini(cli->abi16); nouveau_abi16_fini(cli->abi16);
mutex_lock(&drm->client.mutex); mutex_lock(&drm->client.mutex);
list_del(&cli->head); list_del(&cli->head);
mutex_unlock(&drm->client.mutex); mutex_unlock(&drm->client.mutex);
} }
static void static void
...@@ -647,6 +742,8 @@ nouveau_drm_postclose(struct drm_device *dev, struct drm_file *fpriv) ...@@ -647,6 +742,8 @@ nouveau_drm_postclose(struct drm_device *dev, struct drm_file *fpriv)
{ {
struct nouveau_cli *cli = nouveau_cli(fpriv); struct nouveau_cli *cli = nouveau_cli(fpriv);
nouveau_cli_destroy(cli); nouveau_cli_destroy(cli);
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
} }
static const struct drm_ioctl_desc static const struct drm_ioctl_desc
...@@ -665,12 +762,30 @@ nouveau_ioctls[] = { ...@@ -665,12 +762,30 @@ nouveau_ioctls[] = {
DRM_IOCTL_DEF_DRV(NOUVEAU_GEM_INFO, nouveau_gem_ioctl_info, DRM_UNLOCKED|DRM_AUTH), DRM_IOCTL_DEF_DRV(NOUVEAU_GEM_INFO, nouveau_gem_ioctl_info, DRM_UNLOCKED|DRM_AUTH),
}; };
long nouveau_drm_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg)
{
struct drm_file *file_priv = filp->private_data;
struct drm_device *dev;
long ret;
dev = file_priv->minor->dev;
ret = pm_runtime_get_sync(dev->dev);
if (ret < 0)
return ret;
ret = drm_ioctl(filp, cmd, arg);
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
return ret;
}
static const struct file_operations static const struct file_operations
nouveau_driver_fops = { nouveau_driver_fops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.open = drm_open, .open = drm_open,
.release = drm_release, .release = drm_release,
.unlocked_ioctl = drm_ioctl, .unlocked_ioctl = nouveau_drm_ioctl,
.mmap = nouveau_ttm_mmap, .mmap = nouveau_ttm_mmap,
.poll = drm_poll, .poll = drm_poll,
.read = drm_read, .read = drm_read,
...@@ -753,6 +868,90 @@ nouveau_drm_pci_table[] = { ...@@ -753,6 +868,90 @@ nouveau_drm_pci_table[] = {
{} {}
}; };
static int nouveau_pmops_runtime_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct drm_device *drm_dev = pci_get_drvdata(pdev);
int ret;
if (nouveau_runtime_pm == 0)
return -EINVAL;
drm_kms_helper_poll_disable(drm_dev);
vga_switcheroo_set_dynamic_switch(pdev, VGA_SWITCHEROO_OFF);
nouveau_switcheroo_optimus_dsm();
ret = nouveau_do_suspend(drm_dev);
pci_save_state(pdev);
pci_disable_device(pdev);
pci_set_power_state(pdev, PCI_D3cold);
drm_dev->switch_power_state = DRM_SWITCH_POWER_DYNAMIC_OFF;
return ret;
}
static int nouveau_pmops_runtime_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct drm_device *drm_dev = pci_get_drvdata(pdev);
struct nouveau_device *device = nouveau_dev(drm_dev);
int ret;
if (nouveau_runtime_pm == 0)
return -EINVAL;
pci_set_power_state(pdev, PCI_D0);
pci_restore_state(pdev);
ret = pci_enable_device(pdev);
if (ret)
return ret;
pci_set_master(pdev);
ret = nouveau_do_resume(drm_dev);
nouveau_display_resume(drm_dev);
drm_kms_helper_poll_enable(drm_dev);
/* do magic */
nv_mask(device, 0x88488, (1 << 25), (1 << 25));
vga_switcheroo_set_dynamic_switch(pdev, VGA_SWITCHEROO_ON);
drm_dev->switch_power_state = DRM_SWITCH_POWER_ON;
return ret;
}
static int nouveau_pmops_runtime_idle(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct drm_device *drm_dev = pci_get_drvdata(pdev);
struct nouveau_drm *drm = nouveau_drm(drm_dev);
struct drm_crtc *crtc;
if (nouveau_runtime_pm == 0)
return -EBUSY;
/* are we optimus enabled? */
if (nouveau_runtime_pm == -1 && !nouveau_is_optimus() && !nouveau_is_v1_dsm()) {
DRM_DEBUG_DRIVER("failing to power off - not optimus\n");
return -EBUSY;
}
/* if we have a hdmi audio device - make sure it has a driver loaded */
if (drm->hdmi_device) {
if (!drm->hdmi_device->driver) {
DRM_DEBUG_DRIVER("failing to power off - no HDMI audio driver loaded\n");
pm_runtime_mark_last_busy(dev);
return -EBUSY;
}
}
list_for_each_entry(crtc, &drm->dev->mode_config.crtc_list, head) {
if (crtc->enabled) {
DRM_DEBUG_DRIVER("failing to power off - crtc active\n");
return -EBUSY;
}
}
pm_runtime_mark_last_busy(dev);
pm_runtime_autosuspend(dev);
/* we don't want the main rpm_idle to call suspend - we want to autosuspend */
return 1;
}
static const struct dev_pm_ops nouveau_pm_ops = { static const struct dev_pm_ops nouveau_pm_ops = {
.suspend = nouveau_pmops_suspend, .suspend = nouveau_pmops_suspend,
.resume = nouveau_pmops_resume, .resume = nouveau_pmops_resume,
...@@ -760,6 +959,9 @@ static const struct dev_pm_ops nouveau_pm_ops = { ...@@ -760,6 +959,9 @@ static const struct dev_pm_ops nouveau_pm_ops = {
.thaw = nouveau_pmops_thaw, .thaw = nouveau_pmops_thaw,
.poweroff = nouveau_pmops_freeze, .poweroff = nouveau_pmops_freeze,
.restore = nouveau_pmops_resume, .restore = nouveau_pmops_resume,
.runtime_suspend = nouveau_pmops_runtime_suspend,
.runtime_resume = nouveau_pmops_runtime_resume,
.runtime_idle = nouveau_pmops_runtime_idle,
}; };
static struct pci_driver static struct pci_driver
......
...@@ -70,6 +70,8 @@ nouveau_cli(struct drm_file *fpriv) ...@@ -70,6 +70,8 @@ nouveau_cli(struct drm_file *fpriv)
return fpriv ? fpriv->driver_priv : NULL; return fpriv ? fpriv->driver_priv : NULL;
} }
extern int nouveau_runtime_pm;
struct nouveau_drm { struct nouveau_drm {
struct nouveau_cli client; struct nouveau_cli client;
struct drm_device *dev; struct drm_device *dev;
...@@ -129,6 +131,12 @@ struct nouveau_drm { ...@@ -129,6 +131,12 @@ struct nouveau_drm {
/* power management */ /* power management */
struct nouveau_pm *pm; struct nouveau_pm *pm;
/* display power reference */
bool have_disp_power_ref;
struct dev_pm_domain vga_pm_domain;
struct pci_dev *hdmi_device;
}; };
static inline struct nouveau_drm * static inline struct nouveau_drm *
...@@ -146,6 +154,7 @@ nouveau_dev(struct drm_device *dev) ...@@ -146,6 +154,7 @@ nouveau_dev(struct drm_device *dev)
int nouveau_pmops_suspend(struct device *); int nouveau_pmops_suspend(struct device *);
int nouveau_pmops_resume(struct device *); int nouveau_pmops_resume(struct device *);
#define NV_SUSPEND(cli, fmt, args...) nv_suspend((cli), fmt, ##args)
#define NV_FATAL(cli, fmt, args...) nv_fatal((cli), fmt, ##args) #define NV_FATAL(cli, fmt, args...) nv_fatal((cli), fmt, ##args)
#define NV_ERROR(cli, fmt, args...) nv_error((cli), fmt, ##args) #define NV_ERROR(cli, fmt, args...) nv_error((cli), fmt, ##args)
#define NV_WARN(cli, fmt, args...) nv_warn((cli), fmt, ##args) #define NV_WARN(cli, fmt, args...) nv_warn((cli), fmt, ##args)
......
...@@ -32,6 +32,9 @@ nouveau_switcheroo_set_state(struct pci_dev *pdev, ...@@ -32,6 +32,9 @@ nouveau_switcheroo_set_state(struct pci_dev *pdev,
{ {
struct drm_device *dev = pci_get_drvdata(pdev); struct drm_device *dev = pci_get_drvdata(pdev);
if ((nouveau_is_optimus() || nouveau_is_v1_dsm()) && state == VGA_SWITCHEROO_OFF)
return;
if (state == VGA_SWITCHEROO_ON) { if (state == VGA_SWITCHEROO_ON) {
printk(KERN_ERR "VGA switcheroo: switched nouveau on\n"); printk(KERN_ERR "VGA switcheroo: switched nouveau on\n");
dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; dev->switch_power_state = DRM_SWITCH_POWER_CHANGING;
...@@ -78,8 +81,17 @@ void ...@@ -78,8 +81,17 @@ void
nouveau_vga_init(struct nouveau_drm *drm) nouveau_vga_init(struct nouveau_drm *drm)
{ {
struct drm_device *dev = drm->dev; struct drm_device *dev = drm->dev;
bool runtime = false;
vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode); vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode);
vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops, false);
if (nouveau_runtime_pm == 1)
runtime = true;
if ((nouveau_runtime_pm == -1) && (nouveau_is_optimus() || nouveau_is_v1_dsm()))
runtime = true;
vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops, runtime);
if (runtime && nouveau_is_v1_dsm() && !nouveau_is_optimus())
vga_switcheroo_init_domain_pm_ops(drm->dev->dev, &drm->vga_pm_domain);
} }
void void
......
...@@ -1326,7 +1326,7 @@ static const struct drm_crtc_funcs nv50_crtc_func = { ...@@ -1326,7 +1326,7 @@ static const struct drm_crtc_funcs nv50_crtc_func = {
.cursor_set = nv50_crtc_cursor_set, .cursor_set = nv50_crtc_cursor_set,
.cursor_move = nv50_crtc_cursor_move, .cursor_move = nv50_crtc_cursor_move,
.gamma_set = nv50_crtc_gamma_set, .gamma_set = nv50_crtc_gamma_set,
.set_config = drm_crtc_helper_set_config, .set_config = nouveau_crtc_set_config,
.destroy = nv50_crtc_destroy, .destroy = nv50_crtc_destroy,
.page_flip = nouveau_crtc_page_flip, .page_flip = nouveau_crtc_page_flip,
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment