Skip to content

Commit

Permalink
nouveau: add runtime PM support (v0.9)
Browse files Browse the repository at this point in the history
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: Dave Airlie <airlied@redhat.com>
  • Loading branch information
Dave Airlie committed Aug 29, 2013
1 parent 13bb9cc commit 5addcf0
Show file tree
Hide file tree
Showing 13 changed files with 404 additions and 43 deletions.
19 changes: 19 additions & 0 deletions drivers/gpu/drm/nouveau/core/core/printk.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#include <core/subdev.h>
#include <core/printk.h>

int nv_printk_suspend_level = NV_DBG_DEBUG;

void
nv_printk_(struct nouveau_object *object, const char *pfx, int level,
const char *fmt, ...)
Expand Down Expand Up @@ -72,3 +74,20 @@ nv_printk_(struct nouveau_object *object, const char *pfx, int level,
vprintk(mfmt, 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;
}
13 changes: 13 additions & 0 deletions drivers/gpu/drm/nouveau/core/include/core/printk.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ struct nouveau_object;
#define NV_PRINTK_TRACE 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)
nv_printk_(struct nouveau_object *, const char *, int, const char *, ...);

Expand All @@ -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_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 { \
if (NV_DBG_FATAL <= CONFIG_NOUVEAU_DEBUG) \
nv_printk_(NULL, NV_PRINTK_FATAL, NV_DBG_FATAL, f "\n", ##a); \
Expand Down
2 changes: 1 addition & 1 deletion drivers/gpu/drm/nouveau/core/subdev/bios/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -2165,7 +2165,7 @@ nvbios_init(struct nouveau_subdev *subdev, bool execute)
u16 data;

if (execute)
nv_info(bios, "running init tables\n");
nv_suspend(bios, "running init tables\n");
while (!ret && (data = (init_script(bios, ++i)))) {
struct nvbios_init init = {
.subdev = subdev,
Expand Down
6 changes: 6 additions & 0 deletions drivers/gpu/drm/nouveau/core/subdev/mc/base.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@
*/

#include <subdev/mc.h>
#include <linux/pm_runtime.h>

static irqreturn_t
nouveau_mc_intr(int irq, void *arg)
{
struct nouveau_mc *pmc = arg;
const struct nouveau_mc_intr *map = pmc->intr_map;
struct nouveau_device *device = nv_device(pmc);
struct nouveau_subdev *unit;
u32 stat, intr;

intr = stat = nv_rd32(pmc, 0x000100);
if (intr == 0xffffffff)
return IRQ_NONE;
while (stat && map->stat) {
if (stat & map->stat) {
unit = nouveau_subdev(pmc, map->unit);
Expand All @@ -47,6 +51,8 @@ nouveau_mc_intr(int irq, void *arg)
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;
}

Expand Down
49 changes: 48 additions & 1 deletion drivers/gpu/drm/nouveau/dispnv04/crtc.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <linux/pm_runtime.h>

#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
Expand Down Expand Up @@ -1007,13 +1008,59 @@ nv04_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
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 = {
.save = nv_crtc_save,
.restore = nv_crtc_restore,
.cursor_set = nv04_crtc_cursor_set,
.cursor_move = nv04_crtc_cursor_move,
.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,
.destroy = nv_crtc_destroy,
};
Expand Down
42 changes: 37 additions & 5 deletions drivers/gpu/drm/nouveau/nouveau_acpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,27 @@
#define NOUVEAU_DSM_POWER_SPEED 0x01
#define NOUVEAU_DSM_POWER_STAMINA 0x02

#define NOUVEAU_DSM_OPTIMUS_FN 0x1A
#define NOUVEAU_DSM_OPTIMUS_ARGS 0x03000001
#define NOUVEAU_DSM_OPTIMUS_CAPS 0x1A
#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 {
bool dsm_detected;
Expand Down Expand Up @@ -251,9 +270,18 @@ static int nouveau_dsm_pci_probe(struct pci_dev *pdev)
retval |= NOUVEAU_DSM_HAS_MUX;

if (nouveau_test_dsm(dhandle, nouveau_optimus_dsm,
NOUVEAU_DSM_OPTIMUS_FN))
NOUVEAU_DSM_OPTIMUS_CAPS))
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)
nouveau_dsm_priv.dhandle = dhandle;

Expand Down Expand Up @@ -328,8 +356,12 @@ void nouveau_switcheroo_optimus_dsm(void)
if (!nouveau_dsm_priv.optimus_detected)
return;

nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FN,
NOUVEAU_DSM_OPTIMUS_ARGS, &result);
nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FLAGS,
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)
Expand Down
27 changes: 22 additions & 5 deletions drivers/gpu/drm/nouveau/nouveau_connector.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

#include <acpi/button.h>

#include <linux/pm_runtime.h>

#include <drm/drmP.h>
#include <drm/drm_edid.h>
#include <drm/drm_crtc_helper.h>
Expand Down Expand Up @@ -240,6 +242,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
struct nouveau_encoder *nv_partner;
struct nouveau_i2c_port *i2c;
int type;
int ret;
enum drm_connector_status conn_status = connector_status_disconnected;

/* Cleanup the previous EDID block. */
if (nv_connector->edid) {
Expand All @@ -248,6 +252,10 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
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);
if (i2c) {
nv_connector->edid = drm_get_edid(connector, &i2c->adapter);
Expand All @@ -263,7 +271,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
!nouveau_dp_detect(to_drm_encoder(nv_encoder))) {
NV_ERROR(drm, "Detected %s, but failed init\n",
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
Expand All @@ -290,13 +299,15 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
}

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);
if (nv_encoder) {
nouveau_connector_set_encoder(connector, nv_encoder);
return connector_status_connected;
conn_status = connector_status_connected;
goto out;
}

detect_analog:
Expand All @@ -311,12 +322,18 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
if (helper->detect(encoder, connector) ==
connector_status_connected) {
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
Expand Down
12 changes: 7 additions & 5 deletions drivers/gpu/drm/nouveau/nouveau_display.c
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ nouveau_display_suspend(struct drm_device *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) {
struct nouveau_framebuffer *nouveau_fb;

Expand All @@ -416,7 +416,7 @@ nouveau_display_suspend(struct drm_device *dev)
}

void
nouveau_display_resume(struct drm_device *dev)
nouveau_display_repin(struct drm_device *dev)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct drm_crtc *crtc;
Expand All @@ -441,10 +441,12 @@ nouveau_display_resume(struct drm_device *dev)
if (ret)
NV_ERROR(drm, "Could not pin/map cursor.\n");
}
}

nouveau_fbcon_set_suspend(dev, 0);
nouveau_fbcon_zfill_all(dev);

void
nouveau_display_resume(struct drm_device *dev)
{
struct drm_crtc *crtc;
nouveau_display_init(dev);

/* Force CLUT to get re-loaded during modeset */
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/nouveau/nouveau_display.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ void nouveau_display_destroy(struct drm_device *dev);
int nouveau_display_init(struct drm_device *dev);
void nouveau_display_fini(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);

int nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
Expand All @@ -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 *);

int nouveau_crtc_set_config(struct drm_mode_set *set);
#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
extern int nouveau_backlight_init(struct drm_device *);
extern void nouveau_backlight_exit(struct drm_device *);
Expand Down
Loading

0 comments on commit 5addcf0

Please sign in to comment.