Commit b9ed919f authored by Ben Skeggs's avatar Ben Skeggs

drm/nouveau/drm/pm: remove everything except the hwmon interfaces to THERM

Signed-off-by: default avatarBen Skeggs <bskeggs@redhat.com>
parent c52f4fa6
......@@ -270,9 +270,7 @@ include $(src)/dispnv04/Makefile
nouveau-y += nv50_display.o
# drm/pm
nouveau-y += nouveau_pm.o nouveau_volt.o nouveau_perf.o
nouveau-y += nv04_pm.o nv40_pm.o nv50_pm.o nva3_pm.o nvc0_pm.o
nouveau-y += nouveau_mem.o
nouveau-y += nouveau_hwmon.o
# other random bits
nouveau-$(CONFIG_COMPAT) += nouveau_ioc32.o
......
......@@ -46,7 +46,7 @@
#include "nouveau_gem.h"
#include "nouveau_agp.h"
#include "nouveau_vga.h"
#include "nouveau_pm.h"
#include "nouveau_hwmon.h"
#include "nouveau_acpi.h"
#include "nouveau_bios.h"
#include "nouveau_ioctl.h"
......@@ -384,8 +384,7 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags)
goto fail_dispinit;
}
nouveau_pm_init(dev);
nouveau_hwmon_init(dev);
nouveau_accel_init(drm);
nouveau_fbcon_init(dev);
......@@ -421,8 +420,7 @@ nouveau_drm_unload(struct drm_device *dev)
pm_runtime_get_sync(dev->dev);
nouveau_fbcon_fini(dev);
nouveau_accel_fini(drm);
nouveau_pm_fini(dev);
nouveau_hwmon_fini(dev);
if (dev->mode_config.num_crtc)
nouveau_display_fini(dev);
......@@ -562,7 +560,6 @@ nouveau_do_resume(struct drm_device *dev)
}
nouveau_run_vbios_init(dev);
nouveau_pm_resume(dev);
if (dev->mode_config.num_crtc) {
NV_INFO(drm, "resuming display...\n");
......
......@@ -129,7 +129,7 @@ struct nouveau_drm {
struct backlight_device *backlight;
/* power management */
struct nouveau_pm *pm;
struct nouveau_hwmon *hwmon;
/* display power reference */
bool have_disp_power_ref;
......
......@@ -22,94 +22,22 @@
* Authors: Ben Skeggs
*/
#ifndef __NOUVEAU_HWSQ_H__
#define __NOUVEAU_HWSQ_H__
#ifndef __NOUVEAU_PM_H__
#define __NOUVEAU_PM_H__
struct hwsq_ucode {
u8 data[0x200];
union {
u8 *u08;
u16 *u16;
u32 *u32;
} ptr;
u16 len;
u32 reg;
u32 val;
struct nouveau_hwmon {
struct drm_device *dev;
struct device *hwmon;
};
static inline void
hwsq_init(struct hwsq_ucode *hwsq)
{
hwsq->ptr.u08 = hwsq->data;
hwsq->reg = 0xffffffff;
hwsq->val = 0xffffffff;
}
static inline void
hwsq_fini(struct hwsq_ucode *hwsq)
{
do {
*hwsq->ptr.u08++ = 0x7f;
hwsq->len = hwsq->ptr.u08 - hwsq->data;
} while (hwsq->len & 3);
hwsq->ptr.u08 = hwsq->data;
}
static inline void
hwsq_usec(struct hwsq_ucode *hwsq, u8 usec)
{
u32 shift = 0;
while (usec & ~3) {
usec >>= 2;
shift++;
}
*hwsq->ptr.u08++ = (shift << 2) | usec;
}
static inline void
hwsq_setf(struct hwsq_ucode *hwsq, u8 flag, int val)
{
flag += 0x80;
if (val >= 0)
flag += 0x20;
if (val >= 1)
flag += 0x20;
*hwsq->ptr.u08++ = flag;
}
static inline void
hwsq_op5f(struct hwsq_ucode *hwsq, u8 v0, u8 v1)
static inline struct nouveau_hwmon *
nouveau_hwmon(struct drm_device *dev)
{
*hwsq->ptr.u08++ = 0x5f;
*hwsq->ptr.u08++ = v0;
*hwsq->ptr.u08++ = v1;
return nouveau_drm(dev)->hwmon;
}
static inline void
hwsq_wr32(struct hwsq_ucode *hwsq, u32 reg, u32 val)
{
if (val != hwsq->val) {
if ((val & 0xffff0000) == (hwsq->val & 0xffff0000)) {
*hwsq->ptr.u08++ = 0x42;
*hwsq->ptr.u16++ = (val & 0x0000ffff);
} else {
*hwsq->ptr.u08++ = 0xe2;
*hwsq->ptr.u32++ = val;
}
hwsq->val = val;
}
if ((reg & 0xffff0000) == (hwsq->reg & 0xffff0000)) {
*hwsq->ptr.u08++ = 0x40;
*hwsq->ptr.u16++ = (reg & 0x0000ffff);
} else {
*hwsq->ptr.u08++ = 0xe0;
*hwsq->ptr.u32++ = reg;
}
hwsq->reg = reg;
}
/* nouveau_hwmon.c */
int nouveau_hwmon_init(struct drm_device *dev);
void nouveau_hwmon_fini(struct drm_device *dev);
#endif
This diff is collapsed.
This diff is collapsed.
/*
* Copyright 2010 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#ifndef __NOUVEAU_PM_H__
#define __NOUVEAU_PM_H__
#include <subdev/bios/pll.h>
#include <subdev/clock.h>
struct nouveau_pm_voltage_level {
u32 voltage; /* microvolts */
u8 vid;
};
struct nouveau_pm_voltage {
bool supported;
u8 version;
u8 vid_mask;
struct nouveau_pm_voltage_level *level;
int nr_level;
};
/* Exclusive upper limits */
#define NV_MEM_CL_DDR2_MAX 8
#define NV_MEM_WR_DDR2_MAX 9
#define NV_MEM_CL_DDR3_MAX 17
#define NV_MEM_WR_DDR3_MAX 17
#define NV_MEM_CL_GDDR3_MAX 16
#define NV_MEM_WR_GDDR3_MAX 18
#define NV_MEM_CL_GDDR5_MAX 21
#define NV_MEM_WR_GDDR5_MAX 20
struct nouveau_pm_memtiming {
int id;
u32 reg[9];
u32 mr[4];
u8 tCWL;
u8 odt;
u8 drive_strength;
};
struct nouveau_pm_tbl_header {
u8 version;
u8 header_len;
u8 entry_cnt;
u8 entry_len;
};
struct nouveau_pm_tbl_entry {
u8 tWR;
u8 tWTR;
u8 tCL;
u8 tRC;
u8 empty_4;
u8 tRFC; /* Byte 5 */
u8 empty_6;
u8 tRAS; /* Byte 7 */
u8 empty_8;
u8 tRP; /* Byte 9 */
u8 tRCDRD;
u8 tRCDWR;
u8 tRRD;
u8 tUNK_13;
u8 RAM_FT1; /* 14, a bitmask of random RAM features */
u8 empty_15;
u8 tUNK_16;
u8 empty_17;
u8 tUNK_18;
u8 tCWL;
u8 tUNK_20, tUNK_21;
};
struct nouveau_pm_profile;
struct nouveau_pm_profile_func {
void (*destroy)(struct nouveau_pm_profile *);
void (*init)(struct nouveau_pm_profile *);
void (*fini)(struct nouveau_pm_profile *);
struct nouveau_pm_level *(*select)(struct nouveau_pm_profile *);
};
struct nouveau_pm_profile {
const struct nouveau_pm_profile_func *func;
struct list_head head;
char name[8];
};
#define NOUVEAU_PM_MAX_LEVEL 8
struct nouveau_pm_level {
struct nouveau_pm_profile profile;
struct device_attribute dev_attr;
char name[32];
int id;
struct nouveau_pm_memtiming timing;
u32 memory;
u16 memscript;
u32 core;
u32 shader;
u32 rop;
u32 copy;
u32 daemon;
u32 vdec;
u32 dom6;
u32 unka0; /* nva3:nvc0 */
u32 hub01; /* nvc0- */
u32 hub06; /* nvc0- */
u32 hub07; /* nvc0- */
u32 volt_min; /* microvolts */
u32 volt_max;
u8 fanspeed;
};
struct nouveau_pm_temp_sensor_constants {
u16 offset_constant;
s16 offset_mult;
s16 offset_div;
s16 slope_mult;
s16 slope_div;
};
struct nouveau_pm_threshold_temp {
s16 critical;
s16 down_clock;
};
struct nouveau_pm {
struct drm_device *dev;
struct nouveau_pm_voltage voltage;
struct nouveau_pm_level perflvl[NOUVEAU_PM_MAX_LEVEL];
int nr_perflvl;
struct nouveau_pm_temp_sensor_constants sensor_constants;
struct nouveau_pm_threshold_temp threshold_temp;
struct nouveau_pm_profile *profile_ac;
struct nouveau_pm_profile *profile_dc;
struct nouveau_pm_profile *profile;
struct list_head profiles;
struct nouveau_pm_level boot;
struct nouveau_pm_level *cur;
struct device *hwmon;
struct notifier_block acpi_nb;
int (*clocks_get)(struct drm_device *, struct nouveau_pm_level *);
void *(*clocks_pre)(struct drm_device *, struct nouveau_pm_level *);
int (*clocks_set)(struct drm_device *, void *);
int (*voltage_get)(struct drm_device *);
int (*voltage_set)(struct drm_device *, int voltage);
};
static inline struct nouveau_pm *
nouveau_pm(struct drm_device *dev)
{
return nouveau_drm(dev)->pm;
}
struct nouveau_mem_exec_func {
struct drm_device *dev;
void (*precharge)(struct nouveau_mem_exec_func *);
void (*refresh)(struct nouveau_mem_exec_func *);
void (*refresh_auto)(struct nouveau_mem_exec_func *, bool);
void (*refresh_self)(struct nouveau_mem_exec_func *, bool);
void (*wait)(struct nouveau_mem_exec_func *, u32 nsec);
u32 (*mrg)(struct nouveau_mem_exec_func *, int mr);
void (*mrs)(struct nouveau_mem_exec_func *, int mr, u32 data);
void (*clock_set)(struct nouveau_mem_exec_func *);
void (*timing_set)(struct nouveau_mem_exec_func *);
void *priv;
};
/* nouveau_mem.c */
int nouveau_mem_exec(struct nouveau_mem_exec_func *,
struct nouveau_pm_level *);
/* nouveau_pm.c */
int nouveau_pm_init(struct drm_device *dev);
void nouveau_pm_fini(struct drm_device *dev);
void nouveau_pm_resume(struct drm_device *dev);
extern const struct nouveau_pm_profile_func nouveau_pm_static_profile_func;
void nouveau_pm_trigger(struct drm_device *dev);
/* nouveau_volt.c */
void nouveau_volt_init(struct drm_device *);
void nouveau_volt_fini(struct drm_device *);
int nouveau_volt_vid_lookup(struct drm_device *, int voltage);
int nouveau_volt_lvl_lookup(struct drm_device *, int vid);
int nouveau_voltage_gpio_get(struct drm_device *);
int nouveau_voltage_gpio_set(struct drm_device *, int voltage);
/* nouveau_perf.c */
void nouveau_perf_init(struct drm_device *);
void nouveau_perf_fini(struct drm_device *);
u8 *nouveau_perf_rammap(struct drm_device *, u32 freq, u8 *ver,
u8 *hdr, u8 *cnt, u8 *len);
u8 *nouveau_perf_ramcfg(struct drm_device *, u32 freq, u8 *ver, u8 *len);
u8 *nouveau_perf_timing(struct drm_device *, u32 freq, u8 *ver, u8 *len);
/* nouveau_mem.c */
void nouveau_mem_timing_init(struct drm_device *);
void nouveau_mem_timing_fini(struct drm_device *);
/* nv04_pm.c */
int nv04_pm_clocks_get(struct drm_device *, struct nouveau_pm_level *);
void *nv04_pm_clocks_pre(struct drm_device *, struct nouveau_pm_level *);
int nv04_pm_clocks_set(struct drm_device *, void *);
/* nv40_pm.c */
int nv40_pm_clocks_get(struct drm_device *, struct nouveau_pm_level *);
void *nv40_pm_clocks_pre(struct drm_device *, struct nouveau_pm_level *);
int nv40_pm_clocks_set(struct drm_device *, void *);
int nv40_pm_pwm_get(struct drm_device *, int, u32 *, u32 *);
int nv40_pm_pwm_set(struct drm_device *, int, u32, u32);
/* nv50_pm.c */
int nv50_pm_clocks_get(struct drm_device *, struct nouveau_pm_level *);
void *nv50_pm_clocks_pre(struct drm_device *, struct nouveau_pm_level *);
int nv50_pm_clocks_set(struct drm_device *, void *);
int nv50_pm_pwm_get(struct drm_device *, int, u32 *, u32 *);
int nv50_pm_pwm_set(struct drm_device *, int, u32, u32);
/* nva3_pm.c */
int nva3_pm_clocks_get(struct drm_device *, struct nouveau_pm_level *);
void *nva3_pm_clocks_pre(struct drm_device *, struct nouveau_pm_level *);
int nva3_pm_clocks_set(struct drm_device *, void *);
/* nvc0_pm.c */
int nvc0_pm_clocks_get(struct drm_device *, struct nouveau_pm_level *);
void *nvc0_pm_clocks_pre(struct drm_device *, struct nouveau_pm_level *);
int nvc0_pm_clocks_set(struct drm_device *, void *);
/* nouveau_mem.c */
int nouveau_mem_timing_calc(struct drm_device *, u32 freq,
struct nouveau_pm_memtiming *);
void nouveau_mem_timing_read(struct drm_device *,
struct nouveau_pm_memtiming *);
static inline int
nva3_calc_pll(struct drm_device *dev, struct nvbios_pll *pll, u32 freq,
int *N, int *fN, int *M, int *P)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_clock *clk = nouveau_clock(device);
struct nouveau_pll_vals pv;
int ret;
ret = clk->pll_calc(clk, pll, freq, &pv);
*N = pv.N1;
*M = pv.M1;
*P = pv.log2P;
return ret;
}
#endif
/*
* Copyright 2010 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include <drm/drmP.h>
#include "nouveau_drm.h"
#include "nouveau_pm.h"
#include <subdev/bios/gpio.h>
#include <subdev/gpio.h>
static const enum dcb_gpio_func_name vidtag[] = { 0x04, 0x05, 0x06, 0x1a, 0x73 };
static int nr_vidtag = sizeof(vidtag) / sizeof(vidtag[0]);
int
nouveau_voltage_gpio_get(struct drm_device *dev)
{
struct nouveau_pm_voltage *volt = &nouveau_pm(dev)->voltage;
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_gpio *gpio = nouveau_gpio(device);
u8 vid = 0;
int i;
for (i = 0; i < nr_vidtag; i++) {
if (!(volt->vid_mask & (1 << i)))
continue;
vid |= gpio->get(gpio, 0, vidtag[i], 0xff) << i;
}
return nouveau_volt_lvl_lookup(dev, vid);
}
int
nouveau_voltage_gpio_set(struct drm_device *dev, int voltage)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_gpio *gpio = nouveau_gpio(device);
struct nouveau_pm_voltage *volt = &nouveau_pm(dev)->voltage;
int vid, i;
vid = nouveau_volt_vid_lookup(dev, voltage);
if (vid < 0)
return vid;
for (i = 0; i < nr_vidtag; i++) {
if (!(volt->vid_mask & (1 << i)))
continue;
gpio->set(gpio, 0, vidtag[i], 0xff, !!(vid & (1 << i)));
}
return 0;
}
int
nouveau_volt_vid_lookup(struct drm_device *dev, int voltage)
{
struct nouveau_pm_voltage *volt = &nouveau_pm(dev)->voltage;
int i;
for (i = 0; i < volt->nr_level; i++) {
if (volt->level[i].voltage == voltage)
return volt->level[i].vid;
}
return -ENOENT;
}
int
nouveau_volt_lvl_lookup(struct drm_device *dev, int vid)
{
struct nouveau_pm_voltage *volt = &nouveau_pm(dev)->voltage;
int i;
for (i = 0; i < volt->nr_level; i++) {
if (volt->level[i].vid == vid)
return volt->level[i].voltage;
}
return -ENOENT;
}
void
nouveau_volt_init(struct drm_device *dev)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_gpio *gpio = nouveau_gpio(drm->device);
struct nouveau_pm *pm = nouveau_pm(dev);
struct nouveau_pm_voltage *voltage = &pm->voltage;
struct nvbios *bios = &drm->vbios;
struct dcb_gpio_func func;
struct bit_entry P;
u8 *volt = NULL, *entry;
int i, headerlen, recordlen, entries, vidmask, vidshift;
if (bios->type == NVBIOS_BIT) {
if (bit_table(dev, 'P', &P))
return;
if (P.version == 1)
volt = ROMPTR(dev, P.data[16]);
else
if (P.version == 2)
volt = ROMPTR(dev, P.data[12]);
else {
NV_WARN(drm, "unknown volt for BIT P %d\n", P.version);
}
} else {
if (bios->data[bios->offset + 6] < 0x27) {
NV_DEBUG(drm, "BMP version too old for voltage\n");
return;
}
volt = ROMPTR(dev, bios->data[bios->offset + 0x98]);
}
if (!volt) {
NV_DEBUG(drm, "voltage table pointer invalid\n");
return;
}
switch (volt[0]) {
case 0x10:
case 0x11:
case 0x12:
headerlen = 5;
recordlen = volt[1];
entries = volt[2];
vidshift = 0;
vidmask = volt[4];
break;
case 0x20:
headerlen = volt[1];
recordlen = volt[3];
entries = volt[2];
vidshift = 0; /* could be vidshift like 0x30? */
vidmask = volt[5];
break;
case 0x30:
headerlen = volt[1];
recordlen = volt[2];
entries = volt[3];
vidmask = volt[4];
/* no longer certain what volt[5] is, if it's related to
* the vid shift then it's definitely not a function of
* how many bits are set.
*
* after looking at a number of nva3+ vbios images, they
* all seem likely to have a static shift of 2.. lets
* go with that for now until proven otherwise.
*/
vidshift = 2;
break;
case 0x40:
headerlen = volt[1];
recordlen = volt[2];
entries = volt[3]; /* not a clue what the entries are for.. */
vidmask = volt[11]; /* guess.. */
vidshift = 0;
break;
default:
NV_WARN(drm, "voltage table 0x%02x unknown\n", volt[0]);
return;
}
/* validate vid mask */
voltage->vid_mask = vidmask;
if (!voltage->vid_mask)
return;
i = 0;
while (vidmask) {
if (i > nr_vidtag) {
NV_DEBUG(drm, "vid bit %d unknown\n", i);
return;
}
if (gpio && gpio->find(gpio, 0, vidtag[i], 0xff, &func)) {
NV_DEBUG(drm, "vid bit %d has no gpio tag\n", i);
return;
}
vidmask >>= 1;
i++;
}
/* parse vbios entries into common format */
voltage->version = volt[0];
if (voltage->version < 0x40) {
voltage->nr_level = entries;
voltage->level =
kcalloc(entries, sizeof(*voltage->level), GFP_KERNEL);
if (!voltage->level)
return;
entry = volt + headerlen;
for (i = 0; i < entries; i++, entry += recordlen) {
voltage->level[i].voltage = entry[0] * 10000;
voltage->level[i].vid = entry[1] >> vidshift;
}
} else {
u32 volt_uv = ROM32(volt[4]);
s16 step_uv = ROM16(volt[8]);
u8 vid;
voltage->nr_level = voltage->vid_mask + 1;
voltage->level = kcalloc(voltage->nr_level,
sizeof(*voltage->level), GFP_KERNEL);
if (!voltage->level)
return;
for (vid = 0; vid <= voltage->vid_mask; vid++) {
voltage->level[vid].voltage = volt_uv;
voltage->level[vid].vid = vid;
volt_uv += step_uv;
}
}
voltage->supported = true;
}
void
nouveau_volt_fini(struct drm_device *dev)
{
struct nouveau_pm_voltage *volt = &nouveau_pm(dev)->voltage;
kfree(volt->level);
}
/*
* Copyright 2010 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include <drm/drmP.h>
#include "nouveau_drm.h"
#include "nouveau_reg.h"
#include "dispnv04/hw.h"
#include "nouveau_pm.h"
#include <subdev/bios/pll.h>
#include <subdev/clock.h>
#include <subdev/timer.h>
int
nv04_pm_clocks_get(struct drm_device *dev, struct nouveau_pm_level *perflvl)
{
int ret;
ret = nouveau_hw_get_clock(dev, PLL_CORE);
if (ret < 0)
return ret;
perflvl->core = ret;
ret = nouveau_hw_get_clock(dev, PLL_MEMORY);
if (ret < 0)
return ret;
perflvl->memory = ret;
return 0;
}
struct nv04_pm_clock {
struct nvbios_pll pll;
struct nouveau_pll_vals calc;
};
struct nv04_pm_state {
struct nv04_pm_clock core;
struct nv04_pm_clock memory;
};
static int
calc_pll(struct drm_device *dev, u32 id, int khz, struct nv04_pm_clock *clk)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_bios *bios = nouveau_bios(device);
struct nouveau_clock *pclk = nouveau_clock(device);
int ret;
ret = nvbios_pll_parse(bios, id, &clk->pll);
if (ret)
return ret;
ret = pclk->pll_calc(pclk, &clk->pll, khz, &clk->calc);
if (!ret)
return -EINVAL;
return 0;
}
void *
nv04_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl)
{
struct nv04_pm_state *info;
int ret;
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return ERR_PTR(-ENOMEM);
ret = calc_pll(dev, PLL_CORE, perflvl->core, &info->core);
if (ret)
goto error;
if (perflvl->memory) {
ret = calc_pll(dev, PLL_MEMORY, perflvl->memory, &info->memory);
if (ret)
goto error;
}
return info;
error:
kfree(info);
return ERR_PTR(ret);
}
static void
prog_pll(struct drm_device *dev, struct nv04_pm_clock *clk)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_clock *pclk = nouveau_clock(device);
u32 reg = clk->pll.reg;
/* thank the insane nouveau_hw_setpll() interface for this */
if (device->card_type >= NV_40)
reg += 4;
pclk->pll_prog(pclk, reg, &clk->calc);
}
int
nv04_pm_clocks_set(struct drm_device *dev, void *pre_state)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_timer *ptimer = nouveau_timer(device);
struct nv04_pm_state *state = pre_state;
prog_pll(dev, &state->core);
if (state->memory.pll.reg) {
prog_pll(dev, &state->memory);
if (device->card_type < NV_30) {
if (device->card_type == NV_20)
nv_mask(device, 0x1002c4, 0, 1 << 20);
/* Reset the DLLs */
nv_mask(device, 0x1002c0, 0, 1 << 8);
}
}
nv_ofuncs(ptimer)->init(nv_object(ptimer));
kfree(state);
return 0;
}
/*
* Copyright 2011 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include <drm/drmP.h>
#include "nouveau_drm.h"
#include "nouveau_bios.h"
#include "nouveau_pm.h"
#include "dispnv04/hw.h"
#include <subdev/bios/pll.h>
#include <subdev/clock.h>
#include <subdev/timer.h>
#include <engine/fifo.h>
#define min2(a,b) ((a) < (b) ? (a) : (b))
static u32
read_pll_1(struct drm_device *dev, u32 reg)
{
struct nouveau_device *device = nouveau_dev(dev);
u32 ctrl = nv_rd32(device, reg + 0x00);
int P = (ctrl & 0x00070000) >> 16;
int N = (ctrl & 0x0000ff00) >> 8;
int M = (ctrl & 0x000000ff) >> 0;
u32 ref = 27000, clk = 0;
if (ctrl & 0x80000000)
clk = ref * N / M;
return clk >> P;
}
static u32
read_pll_2(struct drm_device *dev, u32 reg)
{
struct nouveau_device *device = nouveau_dev(dev);
u32 ctrl = nv_rd32(device, reg + 0x00);
u32 coef = nv_rd32(device, reg + 0x04);
int N2 = (coef & 0xff000000) >> 24;
int M2 = (coef & 0x00ff0000) >> 16;
int N1 = (coef & 0x0000ff00) >> 8;
int M1 = (coef & 0x000000ff) >> 0;
int P = (ctrl & 0x00070000) >> 16;
u32 ref = 27000, clk = 0;
if ((ctrl & 0x80000000) && M1) {
clk = ref * N1 / M1;
if ((ctrl & 0x40000100) == 0x40000000) {
if (M2)
clk = clk * N2 / M2;
else
clk = 0;
}
}
return clk >> P;
}
static u32
read_clk(struct drm_device *dev, u32 src)
{
switch (src) {
case 3:
return read_pll_2(dev, 0x004000);
case 2:
return read_pll_1(dev, 0x004008);
default:
break;
}
return 0;
}
int
nv40_pm_clocks_get(struct drm_device *dev, struct nouveau_pm_level *perflvl)
{
struct nouveau_device *device = nouveau_dev(dev);
u32 ctrl = nv_rd32(device, 0x00c040);
perflvl->core = read_clk(dev, (ctrl & 0x00000003) >> 0);
perflvl->shader = read_clk(dev, (ctrl & 0x00000030) >> 4);
perflvl->memory = read_pll_2(dev, 0x4020);
return 0;
}
struct nv40_pm_state {
u32 ctrl;
u32 npll_ctrl;
u32 npll_coef;
u32 spll;
u32 mpll_ctrl;
u32 mpll_coef;
};
static int
nv40_calc_pll(struct drm_device *dev, u32 reg, struct nvbios_pll *pll,
u32 clk, int *N1, int *M1, int *N2, int *M2, int *log2P)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_bios *bios = nouveau_bios(device);
struct nouveau_clock *pclk = nouveau_clock(device);
struct nouveau_pll_vals coef;
int ret;
ret = nvbios_pll_parse(bios, reg, pll);
if (ret)
return ret;
if (clk < pll->vco1.max_freq)
pll->vco2.max_freq = 0;
ret = pclk->pll_calc(pclk, pll, clk, &coef);
if (ret == 0)
return -ERANGE;
*N1 = coef.N1;
*M1 = coef.M1;
if (N2 && M2) {
if (pll->vco2.max_freq) {
*N2 = coef.N2;
*M2 = coef.M2;
} else {
*N2 = 1;
*M2 = 1;
}
}
*log2P = coef.log2P;
return 0;
}
void *
nv40_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl)
{
struct nv40_pm_state *info;
struct nvbios_pll pll;
int N1, N2, M1, M2, log2P;
int ret;
info = kmalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return ERR_PTR(-ENOMEM);
/* core/geometric clock */
ret = nv40_calc_pll(dev, 0x004000, &pll, perflvl->core,
&N1, &M1, &N2, &M2, &log2P);
if (ret < 0)
goto out;
if (N2 == M2) {
info->npll_ctrl = 0x80000100 | (log2P << 16);
info->npll_coef = (N1 << 8) | M1;
} else {
info->npll_ctrl = 0xc0000000 | (log2P << 16);
info->npll_coef = (N2 << 24) | (M2 << 16) | (N1 << 8) | M1;
}
/* use the second PLL for shader/rop clock, if it differs from core */
if (perflvl->shader && perflvl->shader != perflvl->core) {
ret = nv40_calc_pll(dev, 0x004008, &pll, perflvl->shader,
&N1, &M1, NULL, NULL, &log2P);
if (ret < 0)
goto out;
info->spll = 0xc0000000 | (log2P << 16) | (N1 << 8) | M1;
info->ctrl = 0x00000223;
} else {
info->spll = 0x00000000;
info->ctrl = 0x00000333;
}
/* memory clock */
if (!perflvl->memory) {
info->mpll_ctrl = 0x00000000;
goto out;
}
ret = nv40_calc_pll(dev, 0x004020, &pll, perflvl->memory,
&N1, &M1, &N2, &M2, &log2P);
if (ret < 0)
goto out;
info->mpll_ctrl = 0x80000000 | (log2P << 16);
info->mpll_ctrl |= min2(pll.bias_p + log2P, pll.max_p) << 20;
if (N2 == M2) {
info->mpll_ctrl |= 0x00000100;
info->mpll_coef = (N1 << 8) | M1;
} else {
info->mpll_ctrl |= 0x40000000;
info->mpll_coef = (N2 << 24) | (M2 << 16) | (N1 << 8) | M1;
}
out:
if (ret < 0) {
kfree(info);
info = ERR_PTR(ret);
}
return info;
}
static bool
nv40_pm_gr_idle(void *data)
{
struct drm_device *dev = data;
struct nouveau_device *device = nouveau_dev(dev);
if ((nv_rd32(device, 0x400760) & 0x000000f0) >> 4 !=
(nv_rd32(device, 0x400760) & 0x0000000f))
return false;
if (nv_rd32(device, 0x400700))
return false;
return true;
}
int
nv40_pm_clocks_set(struct drm_device *dev, void *pre_state)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_fifo *pfifo = nouveau_fifo(device);
struct nouveau_drm *drm = nouveau_drm(dev);
struct nv40_pm_state *info = pre_state;
unsigned long flags;
struct bit_entry M;
u32 crtc_mask = 0;
u8 sr1[2];
int i, ret = -EAGAIN;
/* determine which CRTCs are active, fetch VGA_SR1 for each */
for (i = 0; i < 2; i++) {
u32 vbl = nv_rd32(device, 0x600808 + (i * 0x2000));
u32 cnt = 0;
do {
if (vbl != nv_rd32(device, 0x600808 + (i * 0x2000))) {
nv_wr08(device, 0x0c03c4 + (i * 0x2000), 0x01);
sr1[i] = nv_rd08(device, 0x0c03c5 + (i * 0x2000));
if (!(sr1[i] & 0x20))
crtc_mask |= (1 << i);
break;
}
udelay(1);
} while (cnt++ < 32);
}
/* halt and idle engines */
pfifo->pause(pfifo, &flags);
if (!nv_wait_cb(device, nv40_pm_gr_idle, dev))
goto resume;
ret = 0;
/* set engine clocks */
nv_mask(device, 0x00c040, 0x00000333, 0x00000000);
nv_wr32(device, 0x004004, info->npll_coef);
nv_mask(device, 0x004000, 0xc0070100, info->npll_ctrl);
nv_mask(device, 0x004008, 0xc007ffff, info->spll);
mdelay(5);
nv_mask(device, 0x00c040, 0x00000333, info->ctrl);
if (!info->mpll_ctrl)
goto resume;
/* wait for vblank start on active crtcs, disable memory access */
for (i = 0; i < 2; i++) {
if (!(crtc_mask & (1 << i)))
continue;
nv_wait(device, 0x600808 + (i * 0x2000), 0x00010000, 0x00000000);
nv_wait(device, 0x600808 + (i * 0x2000), 0x00010000, 0x00010000);
nv_wr08(device, 0x0c03c4 + (i * 0x2000), 0x01);
nv_wr08(device, 0x0c03c5 + (i * 0x2000), sr1[i] | 0x20);
}
/* prepare ram for reclocking */
nv_wr32(device, 0x1002d4, 0x00000001); /* precharge */
nv_wr32(device, 0x1002d0, 0x00000001); /* refresh */
nv_wr32(device, 0x1002d0, 0x00000001); /* refresh */
nv_mask(device, 0x100210, 0x80000000, 0x00000000); /* no auto refresh */
nv_wr32(device, 0x1002dc, 0x00000001); /* enable self-refresh */
/* change the PLL of each memory partition */
nv_mask(device, 0x00c040, 0x0000c000, 0x00000000);
switch (nv_device(drm->device)->chipset) {
case 0x40:
case 0x45:
case 0x41:
case 0x42:
case 0x47:
nv_mask(device, 0x004044, 0xc0771100, info->mpll_ctrl);
nv_mask(device, 0x00402c, 0xc0771100, info->mpll_ctrl);
nv_wr32(device, 0x004048, info->mpll_coef);
nv_wr32(device, 0x004030, info->mpll_coef);
case 0x43:
case 0x49:
case 0x4b:
nv_mask(device, 0x004038, 0xc0771100, info->mpll_ctrl);
nv_wr32(device, 0x00403c, info->mpll_coef);
default:
nv_mask(device, 0x004020, 0xc0771100, info->mpll_ctrl);
nv_wr32(device, 0x004024, info->mpll_coef);
break;
}
udelay(100);
nv_mask(device, 0x00c040, 0x0000c000, 0x0000c000);
/* re-enable normal operation of memory controller */
nv_wr32(device, 0x1002dc, 0x00000000);
nv_mask(device, 0x100210, 0x80000000, 0x80000000);
udelay(100);
/* execute memory reset script from vbios */
if (!bit_table(dev, 'M', &M))
nouveau_bios_run_init_table(dev, ROM16(M.data[0]), NULL, 0);
/* make sure we're in vblank (hopefully the same one as before), and
* then re-enable crtc memory access
*/
for (i = 0; i < 2; i++) {
if (!(crtc_mask & (1 << i)))
continue;
nv_wait(device, 0x600808 + (i * 0x2000), 0x00010000, 0x00010000);
nv_wr08(device, 0x0c03c4 + (i * 0x2000), 0x01);
nv_wr08(device, 0x0c03c5 + (i * 0x2000), sr1[i]);
}
/* resume engines */
resume:
pfifo->start(pfifo, &flags);
kfree(info);
return ret;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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