mirror of
https://github.com/torvalds/linux.git
synced 2024-11-16 00:52:01 +00:00
drm/nv50/pm: initial work towards proper memory reclocking, with timings
Signed-off-by: Ben Skeggs <bskeggs@redhat.com> Signed-off-by: Martin Peres <martin.peres@labri.fr>
This commit is contained in:
parent
2d85bc8855
commit
6bdf68c9a4
@ -352,8 +352,13 @@ nv50_pm_clocks_get(struct drm_device *dev, struct nouveau_pm_level *perflvl)
|
||||
}
|
||||
|
||||
struct nv50_pm_state {
|
||||
struct nouveau_pm_level *perflvl;
|
||||
|
||||
struct hwsq_ucode mclk_hwsq;
|
||||
u32 mscript;
|
||||
u32 mmast;
|
||||
u32 mctrl;
|
||||
u32 mcoef;
|
||||
|
||||
u32 emast;
|
||||
u32 nctrl;
|
||||
@ -415,36 +420,155 @@ clk_same(u32 a, u32 b)
|
||||
return ((a / 1000) == (b / 1000));
|
||||
}
|
||||
|
||||
static void
|
||||
mclk_precharge(struct nouveau_mem_exec_func *exec)
|
||||
{
|
||||
struct nv50_pm_state *info = exec->priv;
|
||||
struct hwsq_ucode *hwsq = &info->mclk_hwsq;
|
||||
|
||||
hwsq_wr32(hwsq, 0x1002d4, 0x00000001);
|
||||
}
|
||||
|
||||
static void
|
||||
mclk_refresh(struct nouveau_mem_exec_func *exec)
|
||||
{
|
||||
struct nv50_pm_state *info = exec->priv;
|
||||
struct hwsq_ucode *hwsq = &info->mclk_hwsq;
|
||||
|
||||
hwsq_wr32(hwsq, 0x1002d0, 0x00000001);
|
||||
}
|
||||
|
||||
static void
|
||||
mclk_refresh_auto(struct nouveau_mem_exec_func *exec, bool enable)
|
||||
{
|
||||
struct nv50_pm_state *info = exec->priv;
|
||||
struct hwsq_ucode *hwsq = &info->mclk_hwsq;
|
||||
|
||||
hwsq_wr32(hwsq, 0x100210, enable ? 0x80000000 : 0x00000000);
|
||||
}
|
||||
|
||||
static void
|
||||
mclk_refresh_self(struct nouveau_mem_exec_func *exec, bool enable)
|
||||
{
|
||||
struct nv50_pm_state *info = exec->priv;
|
||||
struct hwsq_ucode *hwsq = &info->mclk_hwsq;
|
||||
|
||||
hwsq_wr32(hwsq, 0x1002dc, enable ? 0x00000001 : 0x00000000);
|
||||
}
|
||||
|
||||
static void
|
||||
mclk_wait(struct nouveau_mem_exec_func *exec, u32 nsec)
|
||||
{
|
||||
struct nv50_pm_state *info = exec->priv;
|
||||
struct hwsq_ucode *hwsq = &info->mclk_hwsq;
|
||||
|
||||
if (nsec > 1000)
|
||||
hwsq_usec(hwsq, (nsec + 500) / 1000);
|
||||
}
|
||||
|
||||
static u32
|
||||
mclk_mrg(struct nouveau_mem_exec_func *exec, int mr)
|
||||
{
|
||||
if (mr <= 1)
|
||||
return nv_rd32(exec->dev, 0x1002c0 + ((mr - 0) * 4));
|
||||
if (mr <= 3)
|
||||
return nv_rd32(exec->dev, 0x1002e0 + ((mr - 2) * 4));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
mclk_mrs(struct nouveau_mem_exec_func *exec, int mr, u32 data)
|
||||
{
|
||||
struct drm_nouveau_private *dev_priv = exec->dev->dev_private;
|
||||
struct nv50_pm_state *info = exec->priv;
|
||||
struct hwsq_ucode *hwsq = &info->mclk_hwsq;
|
||||
|
||||
if (mr <= 1) {
|
||||
if (dev_priv->vram_rank_B)
|
||||
hwsq_wr32(hwsq, 0x1002c8 + ((mr - 0) * 4), data);
|
||||
hwsq_wr32(hwsq, 0x1002c0 + ((mr - 0) * 4), data);
|
||||
} else
|
||||
if (mr <= 3) {
|
||||
if (dev_priv->vram_rank_B)
|
||||
hwsq_wr32(hwsq, 0x1002e8 + ((mr - 2) * 4), data);
|
||||
hwsq_wr32(hwsq, 0x1002e0 + ((mr - 2) * 4), data);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mclk_clock_set(struct nouveau_mem_exec_func *exec)
|
||||
{
|
||||
struct nv50_pm_state *info = exec->priv;
|
||||
struct hwsq_ucode *hwsq = &info->mclk_hwsq;
|
||||
u32 ctrl = nv_rd32(exec->dev, 0x004008);
|
||||
|
||||
info->mmast = nv_rd32(exec->dev, 0x00c040);
|
||||
info->mmast &= ~0xc0000000; /* get MCLK_2 from HREF */
|
||||
info->mmast |= 0x0000c000; /* use MCLK_2 as MPLL_BYPASS clock */
|
||||
|
||||
hwsq_wr32(hwsq, 0xc040, info->mmast);
|
||||
hwsq_wr32(hwsq, 0x4008, ctrl | 0x00000200); /* bypass MPLL */
|
||||
if (info->mctrl & 0x80000000)
|
||||
hwsq_wr32(hwsq, 0x400c, info->mcoef);
|
||||
hwsq_wr32(hwsq, 0x4008, info->mctrl);
|
||||
}
|
||||
|
||||
static void
|
||||
mclk_timing_set(struct nouveau_mem_exec_func *exec)
|
||||
{
|
||||
struct drm_device *dev = exec->dev;
|
||||
struct nv50_pm_state *info = exec->priv;
|
||||
struct nouveau_pm_level *perflvl = info->perflvl;
|
||||
struct hwsq_ucode *hwsq = &info->mclk_hwsq;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 9; i++) {
|
||||
u32 reg = 0x100220 + (i * 4);
|
||||
u32 val = nv_rd32(dev, reg);
|
||||
if (val != perflvl->timing.reg[i])
|
||||
hwsq_wr32(hwsq, reg, perflvl->timing.reg[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
calc_mclk(struct drm_device *dev, u32 freq, struct hwsq_ucode *hwsq)
|
||||
calc_mclk(struct drm_device *dev, struct nouveau_pm_level *perflvl,
|
||||
struct nv50_pm_state *info)
|
||||
{
|
||||
struct drm_nouveau_private *dev_priv = dev->dev_private;
|
||||
struct nouveau_mem_exec_func exec = {
|
||||
.dev = dev,
|
||||
.precharge = mclk_precharge,
|
||||
.refresh = mclk_refresh,
|
||||
.refresh_auto = mclk_refresh_auto,
|
||||
.refresh_self = mclk_refresh_self,
|
||||
.wait = mclk_wait,
|
||||
.mrg = mclk_mrg,
|
||||
.mrs = mclk_mrs,
|
||||
.clock_set = mclk_clock_set,
|
||||
.timing_set = mclk_timing_set,
|
||||
.priv = info
|
||||
};
|
||||
struct hwsq_ucode *hwsq = &info->mclk_hwsq;
|
||||
struct pll_lims pll;
|
||||
u32 mast = nv_rd32(dev, 0x00c040);
|
||||
u32 ctrl = nv_rd32(dev, 0x004008);
|
||||
u32 coef = nv_rd32(dev, 0x00400c);
|
||||
u32 orig = ctrl;
|
||||
u32 crtc_mask = 0;
|
||||
int N, M, P;
|
||||
int ret, i;
|
||||
|
||||
/* use pcie refclock if possible, otherwise use mpll */
|
||||
ctrl &= ~0x81ff0200;
|
||||
if (clk_same(freq, read_clk(dev, clk_src_href))) {
|
||||
ctrl |= 0x00000200 | (pll.log2p_bias << 19);
|
||||
info->mctrl = nv_rd32(dev, 0x004008);
|
||||
info->mctrl &= ~0x81ff0200;
|
||||
if (clk_same(perflvl->memory, read_clk(dev, clk_src_href))) {
|
||||
info->mctrl |= 0x00000200 | (pll.log2p_bias << 19);
|
||||
} else {
|
||||
ret = calc_pll(dev, 0x4008, &pll, freq, &N, &M, &P);
|
||||
ret = calc_pll(dev, 0x4008, &pll, perflvl->memory, &N, &M, &P);
|
||||
if (ret == 0)
|
||||
return -EINVAL;
|
||||
|
||||
ctrl |= 0x80000000 | (P << 22) | (P << 16);
|
||||
ctrl |= pll.log2p_bias << 19;
|
||||
coef = (N << 8) | M;
|
||||
info->mctrl |= 0x80000000 | (P << 22) | (P << 16);
|
||||
info->mctrl |= pll.log2p_bias << 19;
|
||||
info->mcoef = (N << 8) | M;
|
||||
}
|
||||
|
||||
mast &= ~0xc0000000; /* get MCLK_2 from HREF */
|
||||
mast |= 0x0000c000; /* use MCLK_2 as MPLL_BYPASS clock */
|
||||
|
||||
/* determine active crtcs */
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (nv_rd32(dev, NV50_PDISPLAY_CRTC_C(i, CLOCK)))
|
||||
@ -462,25 +586,10 @@ calc_mclk(struct drm_device *dev, u32 freq, struct hwsq_ucode *hwsq)
|
||||
hwsq_setf(hwsq, 0x10, 0); /* disable bus access */
|
||||
hwsq_op5f(hwsq, 0x00, 0x01); /* no idea :s */
|
||||
|
||||
/* prepare memory controller */
|
||||
hwsq_wr32(hwsq, 0x1002d4, 0x00000001); /* precharge banks and idle */
|
||||
hwsq_wr32(hwsq, 0x1002d0, 0x00000001); /* force refresh */
|
||||
hwsq_wr32(hwsq, 0x100210, 0x00000000); /* stop the automatic refresh */
|
||||
hwsq_wr32(hwsq, 0x1002dc, 0x00000001); /* start self refresh mode */
|
||||
ret = nouveau_mem_exec(&exec, perflvl);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* reclock memory */
|
||||
hwsq_wr32(hwsq, 0xc040, mast);
|
||||
hwsq_wr32(hwsq, 0x4008, orig | 0x00000200); /* bypass MPLL */
|
||||
hwsq_wr32(hwsq, 0x400c, coef);
|
||||
hwsq_wr32(hwsq, 0x4008, ctrl);
|
||||
|
||||
/* restart memory controller */
|
||||
hwsq_wr32(hwsq, 0x1002d4, 0x00000001); /* precharge banks and idle */
|
||||
hwsq_wr32(hwsq, 0x1002dc, 0x00000000); /* stop self refresh mode */
|
||||
hwsq_wr32(hwsq, 0x100210, 0x80000000); /* restart automatic refresh */
|
||||
hwsq_usec(hwsq, 12); /* wait for the PLL to stabilize */
|
||||
|
||||
hwsq_usec(hwsq, 48); /* may be unnecessary: causes flickering */
|
||||
hwsq_setf(hwsq, 0x10, 1); /* enable bus access */
|
||||
hwsq_op5f(hwsq, 0x00, 0x00); /* no idea, reverse of 0x00, 0x01? */
|
||||
if (dev_priv->chipset >= 0x92)
|
||||
@ -506,6 +615,17 @@ nv50_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl)
|
||||
info = kmalloc(sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
info->perflvl = perflvl;
|
||||
|
||||
/* memory: build hwsq ucode which we'll use to reclock memory.
|
||||
* use pcie refclock if possible, otherwise use mpll */
|
||||
info->mclk_hwsq.len = 0;
|
||||
if (perflvl->memory) {
|
||||
ret = calc_mclk(dev, perflvl, info);
|
||||
if (ret)
|
||||
goto error;
|
||||
info->mscript = perflvl->memscript;
|
||||
}
|
||||
|
||||
/* core: for the moment at least, always use nvpll */
|
||||
clk = calc_pll(dev, 0x4028, &pll, perflvl->core, &N, &M, &P1);
|
||||
@ -536,18 +656,6 @@ nv50_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl)
|
||||
info->scoef = (N << 8) | M;
|
||||
}
|
||||
|
||||
/* memory: build hwsq ucode which we'll use to reclock memory */
|
||||
info->mclk_hwsq.len = 0;
|
||||
if (perflvl->memory) {
|
||||
clk = calc_mclk(dev, perflvl->memory, &info->mclk_hwsq);
|
||||
if (clk < 0) {
|
||||
ret = clk;
|
||||
goto error;
|
||||
}
|
||||
|
||||
info->mscript = perflvl->memscript;
|
||||
}
|
||||
|
||||
/* vdec: avoid modifying xpll until we know exactly how the other
|
||||
* clock domains work, i suspect at least some of them can also be
|
||||
* tied to xpll...
|
||||
|
Loading…
Reference in New Issue
Block a user