Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,17 @@
# SPDX-License-Identifier: MIT
nvkm-y += nvkm/subdev/clk/base.o
nvkm-y += nvkm/subdev/clk/nv04.o
nvkm-y += nvkm/subdev/clk/nv40.o
nvkm-y += nvkm/subdev/clk/nv50.o
nvkm-y += nvkm/subdev/clk/g84.o
nvkm-y += nvkm/subdev/clk/gt215.o
nvkm-y += nvkm/subdev/clk/mcp77.o
nvkm-y += nvkm/subdev/clk/gf100.o
nvkm-y += nvkm/subdev/clk/gk104.o
nvkm-y += nvkm/subdev/clk/gk20a.o
nvkm-y += nvkm/subdev/clk/gm20b.o
nvkm-y += nvkm/subdev/clk/gp10b.o
nvkm-$(CONFIG_PM_DEVFREQ) += nvkm/subdev/clk/gk20a_devfreq.o
nvkm-y += nvkm/subdev/clk/pllnv04.o
nvkm-y += nvkm/subdev/clk/pllgt215.o
@@ -0,0 +1,716 @@
/*
* Copyright 2013 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 "priv.h"
#include <subdev/bios.h>
#include <subdev/bios/boost.h>
#include <subdev/bios/cstep.h>
#include <subdev/bios/perf.h>
#include <subdev/bios/vpstate.h>
#include <subdev/fb.h>
#include <subdev/therm.h>
#include <subdev/volt.h>
#include <core/option.h>
/******************************************************************************
* misc
*****************************************************************************/
static u32
nvkm_clk_adjust(struct nvkm_clk *clk, bool adjust,
u8 pstate, u8 domain, u32 input)
{
struct nvkm_bios *bios = clk->subdev.device->bios;
struct nvbios_boostE boostE;
u8 ver, hdr, cnt, len;
u32 data;
data = nvbios_boostEm(bios, pstate, &ver, &hdr, &cnt, &len, &boostE);
if (data) {
struct nvbios_boostS boostS;
u8 idx = 0, sver, shdr;
u32 subd;
input = max(boostE.min, input);
input = min(boostE.max, input);
do {
sver = ver;
shdr = hdr;
subd = nvbios_boostSp(bios, idx++, data, &sver, &shdr,
cnt, len, &boostS);
if (subd && boostS.domain == domain) {
if (adjust)
input = input * boostS.percent / 100;
input = max(boostS.min, input);
input = min(boostS.max, input);
break;
}
} while (subd);
}
return input;
}
/******************************************************************************
* C-States
*****************************************************************************/
static bool
nvkm_cstate_valid(struct nvkm_clk *clk, struct nvkm_cstate *cstate,
u32 max_volt, int temp)
{
const struct nvkm_domain *domain = clk->domains;
struct nvkm_volt *volt = clk->subdev.device->volt;
int voltage;
while (domain && domain->name != nv_clk_src_max) {
if (domain->flags & NVKM_CLK_DOM_FLAG_VPSTATE) {
u32 freq = cstate->domain[domain->name];
switch (clk->boost_mode) {
case NVKM_CLK_BOOST_NONE:
if (clk->base_khz && freq > clk->base_khz)
return false;
fallthrough;
case NVKM_CLK_BOOST_BIOS:
if (clk->boost_khz && freq > clk->boost_khz)
return false;
}
}
domain++;
}
if (!volt)
return true;
voltage = nvkm_volt_map(volt, cstate->voltage, temp);
if (voltage < 0)
return false;
return voltage <= min(max_volt, volt->max_uv);
}
static struct nvkm_cstate *
nvkm_cstate_find_best(struct nvkm_clk *clk, struct nvkm_pstate *pstate,
struct nvkm_cstate *cstate)
{
struct nvkm_device *device = clk->subdev.device;
struct nvkm_volt *volt = device->volt;
int max_volt;
if (!pstate || !cstate)
return NULL;
if (!volt)
return cstate;
max_volt = volt->max_uv;
if (volt->max0_id != 0xff)
max_volt = min(max_volt,
nvkm_volt_map(volt, volt->max0_id, clk->temp));
if (volt->max1_id != 0xff)
max_volt = min(max_volt,
nvkm_volt_map(volt, volt->max1_id, clk->temp));
if (volt->max2_id != 0xff)
max_volt = min(max_volt,
nvkm_volt_map(volt, volt->max2_id, clk->temp));
list_for_each_entry_from_reverse(cstate, &pstate->list, head) {
if (nvkm_cstate_valid(clk, cstate, max_volt, clk->temp))
return cstate;
}
return NULL;
}
static struct nvkm_cstate *
nvkm_cstate_get(struct nvkm_clk *clk, struct nvkm_pstate *pstate, int cstatei)
{
struct nvkm_cstate *cstate;
if (cstatei == NVKM_CLK_CSTATE_HIGHEST)
return list_last_entry(&pstate->list, typeof(*cstate), head);
else {
list_for_each_entry(cstate, &pstate->list, head) {
if (cstate->id == cstatei)
return cstate;
}
}
return NULL;
}
static int
nvkm_cstate_prog(struct nvkm_clk *clk, struct nvkm_pstate *pstate, int cstatei)
{
struct nvkm_subdev *subdev = &clk->subdev;
struct nvkm_device *device = subdev->device;
struct nvkm_therm *therm = device->therm;
struct nvkm_volt *volt = device->volt;
struct nvkm_cstate *cstate;
int ret;
if (!list_empty(&pstate->list)) {
cstate = nvkm_cstate_get(clk, pstate, cstatei);
cstate = nvkm_cstate_find_best(clk, pstate, cstate);
if (!cstate)
return -EINVAL;
} else {
cstate = &pstate->base;
}
if (therm) {
ret = nvkm_therm_cstate(therm, pstate->fanspeed, +1);
if (ret && ret != -ENODEV) {
nvkm_error(subdev, "failed to raise fan speed: %d\n", ret);
return ret;
}
}
if (volt) {
ret = nvkm_volt_set_id(volt, cstate->voltage,
pstate->base.voltage, clk->temp, +1);
if (ret && ret != -ENODEV) {
nvkm_error(subdev, "failed to raise voltage: %d\n", ret);
return ret;
}
}
ret = clk->func->calc(clk, cstate);
if (ret == 0) {
ret = clk->func->prog(clk);
clk->func->tidy(clk);
}
if (volt) {
ret = nvkm_volt_set_id(volt, cstate->voltage,
pstate->base.voltage, clk->temp, -1);
if (ret && ret != -ENODEV)
nvkm_error(subdev, "failed to lower voltage: %d\n", ret);
}
if (therm) {
ret = nvkm_therm_cstate(therm, pstate->fanspeed, -1);
if (ret && ret != -ENODEV)
nvkm_error(subdev, "failed to lower fan speed: %d\n", ret);
}
return ret;
}
static void
nvkm_cstate_del(struct nvkm_cstate *cstate)
{
list_del(&cstate->head);
kfree(cstate);
}
static int
nvkm_cstate_new(struct nvkm_clk *clk, int idx, struct nvkm_pstate *pstate)
{
struct nvkm_bios *bios = clk->subdev.device->bios;
struct nvkm_volt *volt = clk->subdev.device->volt;
const struct nvkm_domain *domain = clk->domains;
struct nvkm_cstate *cstate = NULL;
struct nvbios_cstepX cstepX;
u8 ver, hdr;
u32 data;
data = nvbios_cstepXp(bios, idx, &ver, &hdr, &cstepX);
if (!data)
return -ENOENT;
if (volt && nvkm_volt_map_min(volt, cstepX.voltage) > volt->max_uv)
return -EINVAL;
cstate = kzalloc_obj(*cstate);
if (!cstate)
return -ENOMEM;
*cstate = pstate->base;
cstate->voltage = cstepX.voltage;
cstate->id = idx;
while (domain && domain->name != nv_clk_src_max) {
if (domain->flags & NVKM_CLK_DOM_FLAG_CORE) {
u32 freq = nvkm_clk_adjust(clk, true, pstate->pstate,
domain->bios, cstepX.freq);
cstate->domain[domain->name] = freq;
}
domain++;
}
list_add(&cstate->head, &pstate->list);
return 0;
}
/******************************************************************************
* P-States
*****************************************************************************/
static int
nvkm_pstate_prog(struct nvkm_clk *clk, int pstatei)
{
struct nvkm_subdev *subdev = &clk->subdev;
struct nvkm_fb *fb = subdev->device->fb;
struct nvkm_pci *pci = subdev->device->pci;
struct nvkm_pstate *pstate;
int ret, idx = 0;
list_for_each_entry(pstate, &clk->states, head) {
if (idx++ == pstatei)
break;
}
nvkm_debug(subdev, "setting performance state %d\n", pstatei);
clk->pstate = pstatei;
nvkm_pcie_set_link(pci, pstate->pcie_speed, pstate->pcie_width);
if (fb && fb->ram && fb->ram->func->calc) {
struct nvkm_ram *ram = fb->ram;
int khz = pstate->base.domain[nv_clk_src_mem];
do {
ret = ram->func->calc(ram, khz);
if (ret == 0)
ret = ram->func->prog(ram);
} while (ret > 0);
ram->func->tidy(ram);
}
return nvkm_cstate_prog(clk, pstate, NVKM_CLK_CSTATE_HIGHEST);
}
static void
nvkm_pstate_work(struct work_struct *work)
{
struct nvkm_clk *clk = container_of(work, typeof(*clk), work);
struct nvkm_subdev *subdev = &clk->subdev;
int pstate;
if (!atomic_xchg(&clk->waiting, 0))
return;
clk->pwrsrc = power_supply_is_system_supplied();
nvkm_trace(subdev, "P %d PWR %d U(AC) %d U(DC) %d A %d T %d°C D %d\n",
clk->pstate, clk->pwrsrc, clk->ustate_ac, clk->ustate_dc,
clk->astate, clk->temp, clk->dstate);
pstate = clk->pwrsrc ? clk->ustate_ac : clk->ustate_dc;
if (clk->state_nr && pstate != -1) {
pstate = (pstate < 0) ? clk->astate : pstate;
pstate = min(pstate, clk->state_nr - 1);
pstate = max(pstate, clk->dstate);
} else {
pstate = clk->pstate = -1;
}
nvkm_trace(subdev, "-> %d\n", pstate);
if (pstate != clk->pstate) {
int ret = nvkm_pstate_prog(clk, pstate);
if (ret) {
nvkm_error(subdev, "error setting pstate %d: %d\n",
pstate, ret);
}
}
wake_up_all(&clk->wait);
}
static int
nvkm_pstate_calc(struct nvkm_clk *clk, bool wait)
{
atomic_set(&clk->waiting, 1);
schedule_work(&clk->work);
if (wait)
wait_event(clk->wait, !atomic_read(&clk->waiting));
return 0;
}
static void
nvkm_pstate_info(struct nvkm_clk *clk, struct nvkm_pstate *pstate)
{
const struct nvkm_domain *clock = clk->domains - 1;
struct nvkm_cstate *cstate;
struct nvkm_subdev *subdev = &clk->subdev;
char info[3][32] = { "", "", "" };
char name[4] = "--";
int i = -1;
if (pstate->pstate != 0xff)
snprintf(name, sizeof(name), "%02x", pstate->pstate);
while ((++clock)->name != nv_clk_src_max) {
u32 lo = pstate->base.domain[clock->name];
u32 hi = lo;
if (hi == 0)
continue;
nvkm_debug(subdev, "%02x: %10d KHz\n", clock->name, lo);
list_for_each_entry(cstate, &pstate->list, head) {
u32 freq = cstate->domain[clock->name];
lo = min(lo, freq);
hi = max(hi, freq);
nvkm_debug(subdev, "%10d KHz\n", freq);
}
if (clock->mname && ++i < ARRAY_SIZE(info)) {
lo /= clock->mdiv;
hi /= clock->mdiv;
if (lo == hi) {
snprintf(info[i], sizeof(info[i]), "%s %d MHz",
clock->mname, lo);
} else {
snprintf(info[i], sizeof(info[i]),
"%s %d-%d MHz", clock->mname, lo, hi);
}
}
}
nvkm_debug(subdev, "%s: %s %s %s\n", name, info[0], info[1], info[2]);
}
static void
nvkm_pstate_del(struct nvkm_pstate *pstate)
{
struct nvkm_cstate *cstate, *temp;
list_for_each_entry_safe(cstate, temp, &pstate->list, head) {
nvkm_cstate_del(cstate);
}
list_del(&pstate->head);
kfree(pstate);
}
static int
nvkm_pstate_new(struct nvkm_clk *clk, int idx)
{
struct nvkm_bios *bios = clk->subdev.device->bios;
const struct nvkm_domain *domain = clk->domains - 1;
struct nvkm_pstate *pstate;
struct nvkm_cstate *cstate;
struct nvbios_cstepE cstepE;
struct nvbios_perfE perfE;
u8 ver, hdr, cnt, len;
u32 data;
data = nvbios_perfEp(bios, idx, &ver, &hdr, &cnt, &len, &perfE);
if (!data)
return -EINVAL;
if (perfE.pstate == 0xff)
return 0;
pstate = kzalloc_obj(*pstate);
if (!pstate)
return -ENOMEM;
INIT_LIST_HEAD(&pstate->list);
pstate->pstate = perfE.pstate;
pstate->fanspeed = perfE.fanspeed;
pstate->pcie_speed = perfE.pcie_speed;
pstate->pcie_width = perfE.pcie_width;
cstate = &pstate->base;
cstate->voltage = perfE.voltage;
cstate->domain[nv_clk_src_core] = perfE.core;
cstate->domain[nv_clk_src_shader] = perfE.shader;
cstate->domain[nv_clk_src_mem] = perfE.memory;
cstate->domain[nv_clk_src_vdec] = perfE.vdec;
cstate->domain[nv_clk_src_dom6] = perfE.disp;
while (ver >= 0x40 && (++domain)->name != nv_clk_src_max) {
struct nvbios_perfS perfS;
u8 sver = ver, shdr = hdr;
u32 perfSe = nvbios_perfSp(bios, data, domain->bios,
&sver, &shdr, cnt, len, &perfS);
if (perfSe == 0 || sver != 0x40)
continue;
if (domain->flags & NVKM_CLK_DOM_FLAG_CORE) {
perfS.v40.freq = nvkm_clk_adjust(clk, false,
pstate->pstate,
domain->bios,
perfS.v40.freq);
}
cstate->domain[domain->name] = perfS.v40.freq;
}
data = nvbios_cstepEm(bios, pstate->pstate, &ver, &hdr, &cstepE);
if (data) {
int idx = cstepE.index;
do {
nvkm_cstate_new(clk, idx, pstate);
} while(idx--);
}
nvkm_pstate_info(clk, pstate);
list_add_tail(&pstate->head, &clk->states);
clk->state_nr++;
return 0;
}
/******************************************************************************
* Adjustment triggers
*****************************************************************************/
static int
nvkm_clk_ustate_update(struct nvkm_clk *clk, int req)
{
struct nvkm_pstate *pstate;
int i = 0;
if (!clk->allow_reclock)
return -ENOSYS;
if (req != -1 && req != -2) {
list_for_each_entry(pstate, &clk->states, head) {
if (pstate->pstate == req)
break;
i++;
}
if (pstate->pstate != req)
return -EINVAL;
req = i;
}
return req + 2;
}
static int
nvkm_clk_nstate(struct nvkm_clk *clk, const char *mode, int arglen)
{
int ret = 1;
if (clk->allow_reclock && !strncasecmpz(mode, "auto", arglen))
return -2;
if (strncasecmpz(mode, "disabled", arglen)) {
char save = mode[arglen];
long v;
((char *)mode)[arglen] = '\0';
if (!kstrtol(mode, 0, &v)) {
ret = nvkm_clk_ustate_update(clk, v);
if (ret < 0)
ret = 1;
}
((char *)mode)[arglen] = save;
}
return ret - 2;
}
int
nvkm_clk_ustate(struct nvkm_clk *clk, int req, int pwr)
{
int ret = nvkm_clk_ustate_update(clk, req);
if (ret >= 0) {
if (ret -= 2, pwr) clk->ustate_ac = ret;
else clk->ustate_dc = ret;
return nvkm_pstate_calc(clk, true);
}
return ret;
}
int
nvkm_clk_astate(struct nvkm_clk *clk, int req, int rel, bool wait)
{
if (!rel) clk->astate = req;
if ( rel) clk->astate += rel;
clk->astate = min(clk->astate, clk->state_nr - 1);
clk->astate = max(clk->astate, 0);
return nvkm_pstate_calc(clk, wait);
}
int
nvkm_clk_tstate(struct nvkm_clk *clk, u8 temp)
{
if (clk->temp == temp)
return 0;
clk->temp = temp;
return nvkm_pstate_calc(clk, false);
}
int
nvkm_clk_dstate(struct nvkm_clk *clk, int req, int rel)
{
if (!rel) clk->dstate = req;
if ( rel) clk->dstate += rel;
clk->dstate = min(clk->dstate, clk->state_nr - 1);
clk->dstate = max(clk->dstate, 0);
return nvkm_pstate_calc(clk, true);
}
int
nvkm_clk_pwrsrc(struct nvkm_device *device)
{
if (device->clk)
return nvkm_pstate_calc(device->clk, false);
return 0;
}
/******************************************************************************
* subdev base class implementation
*****************************************************************************/
int
nvkm_clk_read(struct nvkm_clk *clk, enum nv_clk_src src)
{
return clk->func->read(clk, src);
}
static int
nvkm_clk_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend)
{
struct nvkm_clk *clk = nvkm_clk(subdev);
flush_work(&clk->work);
if (clk->func->fini)
clk->func->fini(clk);
return 0;
}
static int
nvkm_clk_init(struct nvkm_subdev *subdev)
{
struct nvkm_clk *clk = nvkm_clk(subdev);
const struct nvkm_domain *clock = clk->domains;
int ret;
memset(&clk->bstate, 0x00, sizeof(clk->bstate));
INIT_LIST_HEAD(&clk->bstate.list);
clk->bstate.pstate = 0xff;
while (clock->name != nv_clk_src_max) {
ret = nvkm_clk_read(clk, clock->name);
if (ret < 0) {
nvkm_error(subdev, "%02x freq unknown\n", clock->name);
return ret;
}
clk->bstate.base.domain[clock->name] = ret;
clock++;
}
nvkm_pstate_info(clk, &clk->bstate);
if (clk->func->init)
return clk->func->init(clk);
clk->astate = clk->state_nr - 1;
clk->dstate = 0;
clk->pstate = -1;
clk->temp = 90; /* reasonable default value */
nvkm_pstate_calc(clk, true);
return 0;
}
static void *
nvkm_clk_dtor(struct nvkm_subdev *subdev)
{
struct nvkm_clk *clk = nvkm_clk(subdev);
struct nvkm_pstate *pstate, *temp;
/* Early return if the pstates have been provided statically */
if (clk->func->pstates)
return clk;
list_for_each_entry_safe(pstate, temp, &clk->states, head) {
nvkm_pstate_del(pstate);
}
return clk;
}
static const struct nvkm_subdev_func
nvkm_clk = {
.dtor = nvkm_clk_dtor,
.init = nvkm_clk_init,
.fini = nvkm_clk_fini,
};
int
nvkm_clk_ctor(const struct nvkm_clk_func *func, struct nvkm_device *device,
enum nvkm_subdev_type type, int inst, bool allow_reclock, struct nvkm_clk *clk)
{
struct nvkm_subdev *subdev = &clk->subdev;
struct nvkm_bios *bios = device->bios;
int ret, idx, arglen;
const char *mode;
struct nvbios_vpstate_header h;
nvkm_subdev_ctor(&nvkm_clk, device, type, inst, subdev);
if (bios && !nvbios_vpstate_parse(bios, &h)) {
struct nvbios_vpstate_entry base, boost;
if (!nvbios_vpstate_entry(bios, &h, h.boost_id, &boost))
clk->boost_khz = boost.clock_mhz * 1000;
if (!nvbios_vpstate_entry(bios, &h, h.base_id, &base))
clk->base_khz = base.clock_mhz * 1000;
}
clk->func = func;
INIT_LIST_HEAD(&clk->states);
clk->domains = func->domains;
clk->ustate_ac = -1;
clk->ustate_dc = -1;
clk->allow_reclock = allow_reclock;
INIT_WORK(&clk->work, nvkm_pstate_work);
init_waitqueue_head(&clk->wait);
atomic_set(&clk->waiting, 0);
/* If no pstates are provided, try and fetch them from the BIOS */
if (!func->pstates) {
idx = 0;
do {
ret = nvkm_pstate_new(clk, idx++);
} while (ret == 0);
} else {
for (idx = 0; idx < func->nr_pstates; idx++)
list_add_tail(&func->pstates[idx].head, &clk->states);
clk->state_nr = func->nr_pstates;
}
mode = nvkm_stropt(device->cfgopt, "NvClkMode", &arglen);
if (mode) {
clk->ustate_ac = nvkm_clk_nstate(clk, mode, arglen);
clk->ustate_dc = nvkm_clk_nstate(clk, mode, arglen);
}
mode = nvkm_stropt(device->cfgopt, "NvClkModeAC", &arglen);
if (mode)
clk->ustate_ac = nvkm_clk_nstate(clk, mode, arglen);
mode = nvkm_stropt(device->cfgopt, "NvClkModeDC", &arglen);
if (mode)
clk->ustate_dc = nvkm_clk_nstate(clk, mode, arglen);
clk->boost_mode = nvkm_longopt(device->cfgopt, "NvBoost",
NVKM_CLK_BOOST_NONE);
return 0;
}
int
nvkm_clk_new_(const struct nvkm_clk_func *func, struct nvkm_device *device,
enum nvkm_subdev_type type, int inst, bool allow_reclock, struct nvkm_clk **pclk)
{
if (!(*pclk = kzalloc_obj(**pclk)))
return -ENOMEM;
return nvkm_clk_ctor(func, device, type, inst, allow_reclock, *pclk);
}
@@ -0,0 +1,48 @@
/*
* Copyright 2013 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 <bskeggs@redhat.com>
*/
#include "nv50.h"
static const struct nvkm_clk_func
g84_clk = {
.read = nv50_clk_read,
.calc = nv50_clk_calc,
.prog = nv50_clk_prog,
.tidy = nv50_clk_tidy,
.domains = {
{ nv_clk_src_crystal, 0xff },
{ nv_clk_src_href , 0xff },
{ nv_clk_src_core , 0xff, 0, "core", 1000 },
{ nv_clk_src_shader , 0xff, 0, "shader", 1000 },
{ nv_clk_src_mem , 0xff, 0, "memory", 1000 },
{ nv_clk_src_vdec , 0xff },
{ nv_clk_src_max }
}
};
int
g84_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_clk **pclk)
{
return nv50_clk_new_(&g84_clk, device, type, inst, (device->chipset >= 0x94), pclk);
}
@@ -0,0 +1,481 @@
/*
* Copyright 2012 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
*/
#define gf100_clk(p) container_of((p), struct gf100_clk, base)
#include "priv.h"
#include "pll.h"
#include <subdev/bios.h>
#include <subdev/bios/pll.h>
#include <subdev/timer.h>
struct gf100_clk_info {
u32 freq;
u32 ssel;
u32 mdiv;
u32 dsrc;
u32 ddiv;
u32 coef;
};
struct gf100_clk {
struct nvkm_clk base;
struct gf100_clk_info eng[16];
};
static u32 read_div(struct gf100_clk *, int, u32, u32);
static u32
read_vco(struct gf100_clk *clk, u32 dsrc)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 ssrc = nvkm_rd32(device, dsrc);
if (!(ssrc & 0x00000100))
return nvkm_clk_read(&clk->base, nv_clk_src_sppll0);
return nvkm_clk_read(&clk->base, nv_clk_src_sppll1);
}
static u32
read_pll(struct gf100_clk *clk, u32 pll)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 ctrl = nvkm_rd32(device, pll + 0x00);
u32 coef = nvkm_rd32(device, pll + 0x04);
u32 P = (coef & 0x003f0000) >> 16;
u32 N = (coef & 0x0000ff00) >> 8;
u32 M = (coef & 0x000000ff) >> 0;
u32 sclk;
if (!(ctrl & 0x00000001))
return 0;
switch (pll) {
case 0x00e800:
case 0x00e820:
sclk = device->crystal;
P = 1;
break;
case 0x132000:
sclk = nvkm_clk_read(&clk->base, nv_clk_src_mpllsrc);
break;
case 0x132020:
sclk = nvkm_clk_read(&clk->base, nv_clk_src_mpllsrcref);
break;
case 0x137000:
case 0x137020:
case 0x137040:
case 0x1370e0:
sclk = read_div(clk, (pll & 0xff) / 0x20, 0x137120, 0x137140);
break;
default:
return 0;
}
return sclk * N / M / P;
}
static u32
read_div(struct gf100_clk *clk, int doff, u32 dsrc, u32 dctl)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 ssrc = nvkm_rd32(device, dsrc + (doff * 4));
u32 sclk, sctl, sdiv = 2;
switch (ssrc & 0x00000003) {
case 0:
if ((ssrc & 0x00030000) != 0x00030000)
return device->crystal;
return 108000;
case 2:
return 100000;
case 3:
sclk = read_vco(clk, dsrc + (doff * 4));
/* Memclk has doff of 0 despite its alt. location */
if (doff <= 2) {
sctl = nvkm_rd32(device, dctl + (doff * 4));
if (sctl & 0x80000000) {
if (ssrc & 0x100)
sctl >>= 8;
sdiv = (sctl & 0x3f) + 2;
}
}
return (sclk * 2) / sdiv;
default:
return 0;
}
}
static u32
read_clk(struct gf100_clk *clk, int idx)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 sctl = nvkm_rd32(device, 0x137250 + (idx * 4));
u32 ssel = nvkm_rd32(device, 0x137100);
u32 sclk, sdiv;
if (ssel & (1 << idx)) {
if (idx < 7)
sclk = read_pll(clk, 0x137000 + (idx * 0x20));
else
sclk = read_pll(clk, 0x1370e0);
sdiv = ((sctl & 0x00003f00) >> 8) + 2;
} else {
sclk = read_div(clk, idx, 0x137160, 0x1371d0);
sdiv = ((sctl & 0x0000003f) >> 0) + 2;
}
if (sctl & 0x80000000)
return (sclk * 2) / sdiv;
return sclk;
}
static int
gf100_clk_read(struct nvkm_clk *base, enum nv_clk_src src)
{
struct gf100_clk *clk = gf100_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
switch (src) {
case nv_clk_src_crystal:
return device->crystal;
case nv_clk_src_href:
return 100000;
case nv_clk_src_sppll0:
return read_pll(clk, 0x00e800);
case nv_clk_src_sppll1:
return read_pll(clk, 0x00e820);
case nv_clk_src_mpllsrcref:
return read_div(clk, 0, 0x137320, 0x137330);
case nv_clk_src_mpllsrc:
return read_pll(clk, 0x132020);
case nv_clk_src_mpll:
return read_pll(clk, 0x132000);
case nv_clk_src_mdiv:
return read_div(clk, 0, 0x137300, 0x137310);
case nv_clk_src_mem:
if (nvkm_rd32(device, 0x1373f0) & 0x00000002)
return nvkm_clk_read(&clk->base, nv_clk_src_mpll);
return nvkm_clk_read(&clk->base, nv_clk_src_mdiv);
case nv_clk_src_gpc:
return read_clk(clk, 0x00);
case nv_clk_src_rop:
return read_clk(clk, 0x01);
case nv_clk_src_hubk07:
return read_clk(clk, 0x02);
case nv_clk_src_hubk06:
return read_clk(clk, 0x07);
case nv_clk_src_hubk01:
return read_clk(clk, 0x08);
case nv_clk_src_copy:
return read_clk(clk, 0x09);
case nv_clk_src_pmu:
return read_clk(clk, 0x0c);
case nv_clk_src_vdec:
return read_clk(clk, 0x0e);
default:
nvkm_error(subdev, "invalid clock source %d\n", src);
return -EINVAL;
}
}
static u32
calc_div(struct gf100_clk *clk, int idx, u32 ref, u32 freq, u32 *ddiv)
{
u32 div = min((ref * 2) / freq, (u32)65);
if (div < 2)
div = 2;
*ddiv = div - 2;
return (ref * 2) / div;
}
static u32
calc_src(struct gf100_clk *clk, int idx, u32 freq, u32 *dsrc, u32 *ddiv)
{
u32 sclk;
/* use one of the fixed frequencies if possible */
*ddiv = 0x00000000;
switch (freq) {
case 27000:
case 108000:
*dsrc = 0x00000000;
if (freq == 108000)
*dsrc |= 0x00030000;
return freq;
case 100000:
*dsrc = 0x00000002;
return freq;
default:
*dsrc = 0x00000003;
break;
}
/* otherwise, calculate the closest divider */
sclk = read_vco(clk, 0x137160 + (idx * 4));
if (idx < 7)
sclk = calc_div(clk, idx, sclk, freq, ddiv);
return sclk;
}
static u32
calc_pll(struct gf100_clk *clk, int idx, u32 freq, u32 *coef)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_bios *bios = subdev->device->bios;
struct nvbios_pll limits;
int N, M, P, ret;
ret = nvbios_pll_parse(bios, 0x137000 + (idx * 0x20), &limits);
if (ret)
return 0;
limits.refclk = read_div(clk, idx, 0x137120, 0x137140);
if (!limits.refclk)
return 0;
ret = gt215_pll_calc(subdev, &limits, freq, &N, NULL, &M, &P);
if (ret <= 0)
return 0;
*coef = (P << 16) | (N << 8) | M;
return ret;
}
static int
calc_clk(struct gf100_clk *clk, struct nvkm_cstate *cstate, int idx, int dom)
{
struct gf100_clk_info *info = &clk->eng[idx];
u32 freq = cstate->domain[dom];
u32 src0, div0, div1D, div1P = 0;
u32 clk0, clk1 = 0;
/* invalid clock domain */
if (!freq)
return 0;
/* first possible path, using only dividers */
clk0 = calc_src(clk, idx, freq, &src0, &div0);
clk0 = calc_div(clk, idx, clk0, freq, &div1D);
/* see if we can get any closer using PLLs */
if (clk0 != freq && (0x00004387 & (1 << idx))) {
if (idx <= 7)
clk1 = calc_pll(clk, idx, freq, &info->coef);
else
clk1 = cstate->domain[nv_clk_src_hubk06];
clk1 = calc_div(clk, idx, clk1, freq, &div1P);
}
/* select the method which gets closest to target freq */
if (abs((int)freq - clk0) <= abs((int)freq - clk1)) {
info->dsrc = src0;
if (div0) {
info->ddiv |= 0x80000000;
info->ddiv |= div0 << 8;
info->ddiv |= div0;
}
if (div1D) {
info->mdiv |= 0x80000000;
info->mdiv |= div1D;
}
info->ssel = info->coef = 0;
info->freq = clk0;
} else {
if (div1P) {
info->mdiv |= 0x80000000;
info->mdiv |= div1P << 8;
}
info->ssel = (1 << idx);
info->freq = clk1;
}
return 0;
}
static int
gf100_clk_calc(struct nvkm_clk *base, struct nvkm_cstate *cstate)
{
struct gf100_clk *clk = gf100_clk(base);
int ret;
if ((ret = calc_clk(clk, cstate, 0x00, nv_clk_src_gpc)) ||
(ret = calc_clk(clk, cstate, 0x01, nv_clk_src_rop)) ||
(ret = calc_clk(clk, cstate, 0x02, nv_clk_src_hubk07)) ||
(ret = calc_clk(clk, cstate, 0x07, nv_clk_src_hubk06)) ||
(ret = calc_clk(clk, cstate, 0x08, nv_clk_src_hubk01)) ||
(ret = calc_clk(clk, cstate, 0x09, nv_clk_src_copy)) ||
(ret = calc_clk(clk, cstate, 0x0c, nv_clk_src_pmu)) ||
(ret = calc_clk(clk, cstate, 0x0e, nv_clk_src_vdec)))
return ret;
return 0;
}
static void
gf100_clk_prog_0(struct gf100_clk *clk, int idx)
{
struct gf100_clk_info *info = &clk->eng[idx];
struct nvkm_device *device = clk->base.subdev.device;
if (idx < 7 && !info->ssel) {
nvkm_mask(device, 0x1371d0 + (idx * 0x04), 0x80003f3f, info->ddiv);
nvkm_wr32(device, 0x137160 + (idx * 0x04), info->dsrc);
}
}
static void
gf100_clk_prog_1(struct gf100_clk *clk, int idx)
{
struct nvkm_device *device = clk->base.subdev.device;
nvkm_mask(device, 0x137100, (1 << idx), 0x00000000);
nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x137100) & (1 << idx)))
break;
);
}
static void
gf100_clk_prog_2(struct gf100_clk *clk, int idx)
{
struct gf100_clk_info *info = &clk->eng[idx];
struct nvkm_device *device = clk->base.subdev.device;
const u32 addr = 0x137000 + (idx * 0x20);
if (idx <= 7) {
nvkm_mask(device, addr + 0x00, 0x00000004, 0x00000000);
nvkm_mask(device, addr + 0x00, 0x00000001, 0x00000000);
if (info->coef) {
nvkm_wr32(device, addr + 0x04, info->coef);
nvkm_mask(device, addr + 0x00, 0x00000001, 0x00000001);
/* Test PLL lock */
nvkm_mask(device, addr + 0x00, 0x00000010, 0x00000000);
nvkm_msec(device, 2000,
if (nvkm_rd32(device, addr + 0x00) & 0x00020000)
break;
);
nvkm_mask(device, addr + 0x00, 0x00000010, 0x00000010);
/* Enable sync mode */
nvkm_mask(device, addr + 0x00, 0x00000004, 0x00000004);
}
}
}
static void
gf100_clk_prog_3(struct gf100_clk *clk, int idx)
{
struct gf100_clk_info *info = &clk->eng[idx];
struct nvkm_device *device = clk->base.subdev.device;
if (info->ssel) {
nvkm_mask(device, 0x137100, (1 << idx), info->ssel);
nvkm_msec(device, 2000,
u32 tmp = nvkm_rd32(device, 0x137100) & (1 << idx);
if (tmp == info->ssel)
break;
);
}
}
static void
gf100_clk_prog_4(struct gf100_clk *clk, int idx)
{
struct gf100_clk_info *info = &clk->eng[idx];
struct nvkm_device *device = clk->base.subdev.device;
nvkm_mask(device, 0x137250 + (idx * 0x04), 0x00003f3f, info->mdiv);
}
static int
gf100_clk_prog(struct nvkm_clk *base)
{
struct gf100_clk *clk = gf100_clk(base);
struct {
void (*exec)(struct gf100_clk *, int);
} stage[] = {
{ gf100_clk_prog_0 }, /* div programming */
{ gf100_clk_prog_1 }, /* select div mode */
{ gf100_clk_prog_2 }, /* (maybe) program pll */
{ gf100_clk_prog_3 }, /* (maybe) select pll mode */
{ gf100_clk_prog_4 }, /* final divider */
};
int i, j;
for (i = 0; i < ARRAY_SIZE(stage); i++) {
for (j = 0; j < ARRAY_SIZE(clk->eng); j++) {
if (!clk->eng[j].freq)
continue;
stage[i].exec(clk, j);
}
}
return 0;
}
static void
gf100_clk_tidy(struct nvkm_clk *base)
{
struct gf100_clk *clk = gf100_clk(base);
memset(clk->eng, 0x00, sizeof(clk->eng));
}
static const struct nvkm_clk_func
gf100_clk = {
.read = gf100_clk_read,
.calc = gf100_clk_calc,
.prog = gf100_clk_prog,
.tidy = gf100_clk_tidy,
.domains = {
{ nv_clk_src_crystal, 0xff },
{ nv_clk_src_href , 0xff },
{ nv_clk_src_hubk06 , 0x00 },
{ nv_clk_src_hubk01 , 0x01 },
{ nv_clk_src_copy , 0x02 },
{ nv_clk_src_gpc , 0x03, NVKM_CLK_DOM_FLAG_VPSTATE, "core", 2000 },
{ nv_clk_src_rop , 0x04 },
{ nv_clk_src_mem , 0x05, 0, "memory", 1000 },
{ nv_clk_src_vdec , 0x06 },
{ nv_clk_src_pmu , 0x0a },
{ nv_clk_src_hubk07 , 0x0b },
{ nv_clk_src_max }
}
};
int
gf100_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_clk **pclk)
{
struct gf100_clk *clk;
if (!(clk = kzalloc_obj(*clk)))
return -ENOMEM;
*pclk = &clk->base;
return nvkm_clk_ctor(&gf100_clk, device, type, inst, false, &clk->base);
}
@@ -0,0 +1,517 @@
/*
* Copyright 2013 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
*/
#define gk104_clk(p) container_of((p), struct gk104_clk, base)
#include "priv.h"
#include "pll.h"
#include <subdev/timer.h>
#include <subdev/bios.h>
#include <subdev/bios/pll.h>
struct gk104_clk_info {
u32 freq;
u32 ssel;
u32 mdiv;
u32 dsrc;
u32 ddiv;
u32 coef;
};
struct gk104_clk {
struct nvkm_clk base;
struct gk104_clk_info eng[16];
};
static u32 read_div(struct gk104_clk *, int, u32, u32);
static u32 read_pll(struct gk104_clk *, u32);
static u32
read_vco(struct gk104_clk *clk, u32 dsrc)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 ssrc = nvkm_rd32(device, dsrc);
if (!(ssrc & 0x00000100))
return read_pll(clk, 0x00e800);
return read_pll(clk, 0x00e820);
}
static u32
read_pll(struct gk104_clk *clk, u32 pll)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 ctrl = nvkm_rd32(device, pll + 0x00);
u32 coef = nvkm_rd32(device, pll + 0x04);
u32 P = (coef & 0x003f0000) >> 16;
u32 N = (coef & 0x0000ff00) >> 8;
u32 M = (coef & 0x000000ff) >> 0;
u32 sclk;
u16 fN = 0xf000;
if (!(ctrl & 0x00000001))
return 0;
switch (pll) {
case 0x00e800:
case 0x00e820:
sclk = device->crystal;
P = 1;
break;
case 0x132000:
sclk = read_pll(clk, 0x132020);
P = (coef & 0x10000000) ? 2 : 1;
break;
case 0x132020:
sclk = read_div(clk, 0, 0x137320, 0x137330);
fN = nvkm_rd32(device, pll + 0x10) >> 16;
break;
case 0x137000:
case 0x137020:
case 0x137040:
case 0x1370e0:
sclk = read_div(clk, (pll & 0xff) / 0x20, 0x137120, 0x137140);
break;
default:
return 0;
}
if (P == 0)
P = 1;
sclk = (sclk * N) + (((u16)(fN + 4096) * sclk) >> 13);
return sclk / (M * P);
}
static u32
read_div(struct gk104_clk *clk, int doff, u32 dsrc, u32 dctl)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 ssrc = nvkm_rd32(device, dsrc + (doff * 4));
u32 sctl = nvkm_rd32(device, dctl + (doff * 4));
switch (ssrc & 0x00000003) {
case 0:
if ((ssrc & 0x00030000) != 0x00030000)
return device->crystal;
return 108000;
case 2:
return 100000;
case 3:
if (sctl & 0x80000000) {
u32 sclk = read_vco(clk, dsrc + (doff * 4));
u32 sdiv = (sctl & 0x0000003f) + 2;
return (sclk * 2) / sdiv;
}
return read_vco(clk, dsrc + (doff * 4));
default:
return 0;
}
}
static u32
read_mem(struct gk104_clk *clk)
{
struct nvkm_device *device = clk->base.subdev.device;
switch (nvkm_rd32(device, 0x1373f4) & 0x0000000f) {
case 1: return read_pll(clk, 0x132020);
case 2: return read_pll(clk, 0x132000);
default:
return 0;
}
}
static u32
read_clk(struct gk104_clk *clk, int idx)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 sctl = nvkm_rd32(device, 0x137250 + (idx * 4));
u32 sclk, sdiv;
if (idx < 7) {
u32 ssel = nvkm_rd32(device, 0x137100);
if (ssel & (1 << idx)) {
sclk = read_pll(clk, 0x137000 + (idx * 0x20));
sdiv = 1;
} else {
sclk = read_div(clk, idx, 0x137160, 0x1371d0);
sdiv = 0;
}
} else {
u32 ssrc = nvkm_rd32(device, 0x137160 + (idx * 0x04));
if ((ssrc & 0x00000003) == 0x00000003) {
sclk = read_div(clk, idx, 0x137160, 0x1371d0);
if (ssrc & 0x00000100) {
if (ssrc & 0x40000000)
sclk = read_pll(clk, 0x1370e0);
sdiv = 1;
} else {
sdiv = 0;
}
} else {
sclk = read_div(clk, idx, 0x137160, 0x1371d0);
sdiv = 0;
}
}
if (sctl & 0x80000000) {
if (sdiv)
sdiv = ((sctl & 0x00003f00) >> 8) + 2;
else
sdiv = ((sctl & 0x0000003f) >> 0) + 2;
return (sclk * 2) / sdiv;
}
return sclk;
}
static int
gk104_clk_read(struct nvkm_clk *base, enum nv_clk_src src)
{
struct gk104_clk *clk = gk104_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
switch (src) {
case nv_clk_src_crystal:
return device->crystal;
case nv_clk_src_href:
return 100000;
case nv_clk_src_mem:
return read_mem(clk);
case nv_clk_src_gpc:
return read_clk(clk, 0x00);
case nv_clk_src_rop:
return read_clk(clk, 0x01);
case nv_clk_src_hubk07:
return read_clk(clk, 0x02);
case nv_clk_src_hubk06:
return read_clk(clk, 0x07);
case nv_clk_src_hubk01:
return read_clk(clk, 0x08);
case nv_clk_src_pmu:
return read_clk(clk, 0x0c);
case nv_clk_src_vdec:
return read_clk(clk, 0x0e);
default:
nvkm_error(subdev, "invalid clock source %d\n", src);
return -EINVAL;
}
}
static u32
calc_div(struct gk104_clk *clk, int idx, u32 ref, u32 freq, u32 *ddiv)
{
u32 div = min((ref * 2) / freq, (u32)65);
if (div < 2)
div = 2;
*ddiv = div - 2;
return (ref * 2) / div;
}
static u32
calc_src(struct gk104_clk *clk, int idx, u32 freq, u32 *dsrc, u32 *ddiv)
{
u32 sclk;
/* use one of the fixed frequencies if possible */
*ddiv = 0x00000000;
switch (freq) {
case 27000:
case 108000:
*dsrc = 0x00000000;
if (freq == 108000)
*dsrc |= 0x00030000;
return freq;
case 100000:
*dsrc = 0x00000002;
return freq;
default:
*dsrc = 0x00000003;
break;
}
/* otherwise, calculate the closest divider */
sclk = read_vco(clk, 0x137160 + (idx * 4));
if (idx < 7)
sclk = calc_div(clk, idx, sclk, freq, ddiv);
return sclk;
}
static u32
calc_pll(struct gk104_clk *clk, int idx, u32 freq, u32 *coef)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_bios *bios = subdev->device->bios;
struct nvbios_pll limits;
int N, M, P, ret;
ret = nvbios_pll_parse(bios, 0x137000 + (idx * 0x20), &limits);
if (ret)
return 0;
limits.refclk = read_div(clk, idx, 0x137120, 0x137140);
if (!limits.refclk)
return 0;
ret = gt215_pll_calc(subdev, &limits, freq, &N, NULL, &M, &P);
if (ret <= 0)
return 0;
*coef = (P << 16) | (N << 8) | M;
return ret;
}
static int
calc_clk(struct gk104_clk *clk,
struct nvkm_cstate *cstate, int idx, int dom)
{
struct gk104_clk_info *info = &clk->eng[idx];
u32 freq = cstate->domain[dom];
u32 src0, div0, div1D, div1P = 0;
u32 clk0, clk1 = 0;
/* invalid clock domain */
if (!freq)
return 0;
/* first possible path, using only dividers */
clk0 = calc_src(clk, idx, freq, &src0, &div0);
clk0 = calc_div(clk, idx, clk0, freq, &div1D);
/* see if we can get any closer using PLLs */
if (clk0 != freq && (0x0000ff87 & (1 << idx))) {
if (idx <= 7)
clk1 = calc_pll(clk, idx, freq, &info->coef);
else
clk1 = cstate->domain[nv_clk_src_hubk06];
clk1 = calc_div(clk, idx, clk1, freq, &div1P);
}
/* select the method which gets closest to target freq */
if (abs((int)freq - clk0) <= abs((int)freq - clk1)) {
info->dsrc = src0;
if (div0) {
info->ddiv |= 0x80000000;
info->ddiv |= div0;
}
if (div1D) {
info->mdiv |= 0x80000000;
info->mdiv |= div1D;
}
info->ssel = 0;
info->freq = clk0;
} else {
if (div1P) {
info->mdiv |= 0x80000000;
info->mdiv |= div1P << 8;
}
info->ssel = (1 << idx);
info->dsrc = 0x40000100;
info->freq = clk1;
}
return 0;
}
static int
gk104_clk_calc(struct nvkm_clk *base, struct nvkm_cstate *cstate)
{
struct gk104_clk *clk = gk104_clk(base);
int ret;
if ((ret = calc_clk(clk, cstate, 0x00, nv_clk_src_gpc)) ||
(ret = calc_clk(clk, cstate, 0x01, nv_clk_src_rop)) ||
(ret = calc_clk(clk, cstate, 0x02, nv_clk_src_hubk07)) ||
(ret = calc_clk(clk, cstate, 0x07, nv_clk_src_hubk06)) ||
(ret = calc_clk(clk, cstate, 0x08, nv_clk_src_hubk01)) ||
(ret = calc_clk(clk, cstate, 0x0c, nv_clk_src_pmu)) ||
(ret = calc_clk(clk, cstate, 0x0e, nv_clk_src_vdec)))
return ret;
return 0;
}
static void
gk104_clk_prog_0(struct gk104_clk *clk, int idx)
{
struct gk104_clk_info *info = &clk->eng[idx];
struct nvkm_device *device = clk->base.subdev.device;
if (!info->ssel) {
nvkm_mask(device, 0x1371d0 + (idx * 0x04), 0x8000003f, info->ddiv);
nvkm_wr32(device, 0x137160 + (idx * 0x04), info->dsrc);
}
}
static void
gk104_clk_prog_1_0(struct gk104_clk *clk, int idx)
{
struct nvkm_device *device = clk->base.subdev.device;
nvkm_mask(device, 0x137100, (1 << idx), 0x00000000);
nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x137100) & (1 << idx)))
break;
);
}
static void
gk104_clk_prog_1_1(struct gk104_clk *clk, int idx)
{
struct nvkm_device *device = clk->base.subdev.device;
nvkm_mask(device, 0x137160 + (idx * 0x04), 0x00000100, 0x00000000);
}
static void
gk104_clk_prog_2(struct gk104_clk *clk, int idx)
{
struct gk104_clk_info *info = &clk->eng[idx];
struct nvkm_device *device = clk->base.subdev.device;
const u32 addr = 0x137000 + (idx * 0x20);
nvkm_mask(device, addr + 0x00, 0x00000004, 0x00000000);
nvkm_mask(device, addr + 0x00, 0x00000001, 0x00000000);
if (info->coef) {
nvkm_wr32(device, addr + 0x04, info->coef);
nvkm_mask(device, addr + 0x00, 0x00000001, 0x00000001);
/* Test PLL lock */
nvkm_mask(device, addr + 0x00, 0x00000010, 0x00000000);
nvkm_msec(device, 2000,
if (nvkm_rd32(device, addr + 0x00) & 0x00020000)
break;
);
nvkm_mask(device, addr + 0x00, 0x00000010, 0x00000010);
/* Enable sync mode */
nvkm_mask(device, addr + 0x00, 0x00000004, 0x00000004);
}
}
static void
gk104_clk_prog_3(struct gk104_clk *clk, int idx)
{
struct gk104_clk_info *info = &clk->eng[idx];
struct nvkm_device *device = clk->base.subdev.device;
if (info->ssel)
nvkm_mask(device, 0x137250 + (idx * 0x04), 0x00003f00, info->mdiv);
else
nvkm_mask(device, 0x137250 + (idx * 0x04), 0x0000003f, info->mdiv);
}
static void
gk104_clk_prog_4_0(struct gk104_clk *clk, int idx)
{
struct gk104_clk_info *info = &clk->eng[idx];
struct nvkm_device *device = clk->base.subdev.device;
if (info->ssel) {
nvkm_mask(device, 0x137100, (1 << idx), info->ssel);
nvkm_msec(device, 2000,
u32 tmp = nvkm_rd32(device, 0x137100) & (1 << idx);
if (tmp == info->ssel)
break;
);
}
}
static void
gk104_clk_prog_4_1(struct gk104_clk *clk, int idx)
{
struct gk104_clk_info *info = &clk->eng[idx];
struct nvkm_device *device = clk->base.subdev.device;
if (info->ssel) {
nvkm_mask(device, 0x137160 + (idx * 0x04), 0x40000000, 0x40000000);
nvkm_mask(device, 0x137160 + (idx * 0x04), 0x00000100, 0x00000100);
}
}
static int
gk104_clk_prog(struct nvkm_clk *base)
{
struct gk104_clk *clk = gk104_clk(base);
struct {
u32 mask;
void (*exec)(struct gk104_clk *, int);
} stage[] = {
{ 0x007f, gk104_clk_prog_0 }, /* div programming */
{ 0x007f, gk104_clk_prog_1_0 }, /* select div mode */
{ 0xff80, gk104_clk_prog_1_1 },
{ 0x00ff, gk104_clk_prog_2 }, /* (maybe) program pll */
{ 0xff80, gk104_clk_prog_3 }, /* final divider */
{ 0x007f, gk104_clk_prog_4_0 }, /* (maybe) select pll mode */
{ 0xff80, gk104_clk_prog_4_1 },
};
int i, j;
for (i = 0; i < ARRAY_SIZE(stage); i++) {
for (j = 0; j < ARRAY_SIZE(clk->eng); j++) {
if (!(stage[i].mask & (1 << j)))
continue;
if (!clk->eng[j].freq)
continue;
stage[i].exec(clk, j);
}
}
return 0;
}
static void
gk104_clk_tidy(struct nvkm_clk *base)
{
struct gk104_clk *clk = gk104_clk(base);
memset(clk->eng, 0x00, sizeof(clk->eng));
}
static const struct nvkm_clk_func
gk104_clk = {
.read = gk104_clk_read,
.calc = gk104_clk_calc,
.prog = gk104_clk_prog,
.tidy = gk104_clk_tidy,
.domains = {
{ nv_clk_src_crystal, 0xff },
{ nv_clk_src_href , 0xff },
{ nv_clk_src_gpc , 0x00, NVKM_CLK_DOM_FLAG_CORE | NVKM_CLK_DOM_FLAG_VPSTATE, "core", 2000 },
{ nv_clk_src_hubk07 , 0x01, NVKM_CLK_DOM_FLAG_CORE },
{ nv_clk_src_rop , 0x02, NVKM_CLK_DOM_FLAG_CORE },
{ nv_clk_src_mem , 0x03, 0, "memory", 500 },
{ nv_clk_src_hubk06 , 0x04, NVKM_CLK_DOM_FLAG_CORE },
{ nv_clk_src_hubk01 , 0x05 },
{ nv_clk_src_vdec , 0x06 },
{ nv_clk_src_pmu , 0x07 },
{ nv_clk_src_max }
}
};
int
gk104_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_clk **pclk)
{
struct gk104_clk *clk;
if (!(clk = kzalloc_obj(*clk)))
return -ENOMEM;
*pclk = &clk->base;
return nvkm_clk_ctor(&gk104_clk, device, type, inst, true, &clk->base);
}
@@ -0,0 +1,662 @@
/*
* Copyright (c) 2014-2016, NVIDIA CORPORATION. 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 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.
*
* Shamelessly ripped off from ChromeOS's gk20a/clk_pllg.c
*
*/
#include "priv.h"
#include "gk20a_devfreq.h"
#include "gk20a.h"
#include <core/tegra.h>
#include <subdev/timer.h>
static const u8 _pl_to_div[] = {
/* PL: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 */
/* p: */ 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 12, 16, 20, 24, 32,
};
static u32 pl_to_div(u32 pl)
{
if (pl >= ARRAY_SIZE(_pl_to_div))
return 1;
return _pl_to_div[pl];
}
static u32 div_to_pl(u32 div)
{
u32 pl;
for (pl = 0; pl < ARRAY_SIZE(_pl_to_div) - 1; pl++) {
if (_pl_to_div[pl] >= div)
return pl;
}
return ARRAY_SIZE(_pl_to_div) - 1;
}
static const struct gk20a_clk_pllg_params gk20a_pllg_params = {
.min_vco = 1000000, .max_vco = 2064000,
.min_u = 12000, .max_u = 38000,
.min_m = 1, .max_m = 255,
.min_n = 8, .max_n = 255,
.min_pl = 1, .max_pl = 32,
};
void
gk20a_pllg_read_mnp(struct gk20a_clk *clk, struct gk20a_pll *pll)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 val;
val = nvkm_rd32(device, GPCPLL_COEFF);
pll->m = (val >> GPCPLL_COEFF_M_SHIFT) & MASK(GPCPLL_COEFF_M_WIDTH);
pll->n = (val >> GPCPLL_COEFF_N_SHIFT) & MASK(GPCPLL_COEFF_N_WIDTH);
pll->pl = (val >> GPCPLL_COEFF_P_SHIFT) & MASK(GPCPLL_COEFF_P_WIDTH);
}
void
gk20a_pllg_write_mnp(struct gk20a_clk *clk, const struct gk20a_pll *pll)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 val;
val = (pll->m & MASK(GPCPLL_COEFF_M_WIDTH)) << GPCPLL_COEFF_M_SHIFT;
val |= (pll->n & MASK(GPCPLL_COEFF_N_WIDTH)) << GPCPLL_COEFF_N_SHIFT;
val |= (pll->pl & MASK(GPCPLL_COEFF_P_WIDTH)) << GPCPLL_COEFF_P_SHIFT;
nvkm_wr32(device, GPCPLL_COEFF, val);
}
u32
gk20a_pllg_calc_rate(struct gk20a_clk *clk, struct gk20a_pll *pll)
{
u32 rate;
u32 divider;
rate = clk->parent_rate * pll->n;
divider = pll->m * clk->pl_to_div(pll->pl);
return rate / divider / 2;
}
int
gk20a_pllg_calc_mnp(struct gk20a_clk *clk, unsigned long rate,
struct gk20a_pll *pll)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
u32 target_clk_f, ref_clk_f, target_freq;
u32 min_vco_f, max_vco_f;
u32 low_pl, high_pl, best_pl;
u32 target_vco_f;
u32 best_m, best_n;
u32 best_delta = ~0;
u32 pl;
target_clk_f = rate * 2 / KHZ;
ref_clk_f = clk->parent_rate / KHZ;
target_vco_f = target_clk_f + target_clk_f / 50;
max_vco_f = max(clk->params->max_vco, target_vco_f);
min_vco_f = clk->params->min_vco;
best_m = clk->params->max_m;
best_n = clk->params->min_n;
best_pl = clk->params->min_pl;
/* min_pl <= high_pl <= max_pl */
high_pl = (max_vco_f + target_vco_f - 1) / target_vco_f;
high_pl = min(high_pl, clk->params->max_pl);
high_pl = max(high_pl, clk->params->min_pl);
high_pl = clk->div_to_pl(high_pl);
/* min_pl <= low_pl <= max_pl */
low_pl = min_vco_f / target_vco_f;
low_pl = min(low_pl, clk->params->max_pl);
low_pl = max(low_pl, clk->params->min_pl);
low_pl = clk->div_to_pl(low_pl);
nvkm_debug(subdev, "low_PL %d(div%d), high_PL %d(div%d)", low_pl,
clk->pl_to_div(low_pl), high_pl, clk->pl_to_div(high_pl));
/* Select lowest possible VCO */
for (pl = low_pl; pl <= high_pl; pl++) {
u32 m, n, n2;
target_vco_f = target_clk_f * clk->pl_to_div(pl);
for (m = clk->params->min_m; m <= clk->params->max_m; m++) {
u32 u_f = ref_clk_f / m;
if (u_f < clk->params->min_u)
break;
if (u_f > clk->params->max_u)
continue;
n = (target_vco_f * m) / ref_clk_f;
n2 = ((target_vco_f * m) + (ref_clk_f - 1)) / ref_clk_f;
if (n > clk->params->max_n)
break;
for (; n <= n2; n++) {
u32 vco_f;
if (n < clk->params->min_n)
continue;
if (n > clk->params->max_n)
break;
vco_f = ref_clk_f * n / m;
if (vco_f >= min_vco_f && vco_f <= max_vco_f) {
u32 delta, lwv;
lwv = (vco_f + (clk->pl_to_div(pl) / 2))
/ clk->pl_to_div(pl);
delta = abs(lwv - target_clk_f);
if (delta < best_delta) {
best_delta = delta;
best_m = m;
best_n = n;
best_pl = pl;
if (best_delta == 0)
goto found_match;
}
}
}
}
}
found_match:
WARN_ON(best_delta == ~0);
if (best_delta != 0)
nvkm_debug(subdev,
"no best match for target @ %dMHz on gpc_pll",
target_clk_f / KHZ);
pll->m = best_m;
pll->n = best_n;
pll->pl = best_pl;
target_freq = gk20a_pllg_calc_rate(clk, pll);
nvkm_debug(subdev,
"actual target freq %d KHz, M %d, N %d, PL %d(div%d)\n",
target_freq / KHZ, pll->m, pll->n, pll->pl,
clk->pl_to_div(pll->pl));
return 0;
}
static int
gk20a_pllg_slide(struct gk20a_clk *clk, u32 n)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
struct gk20a_pll pll;
int ret = 0;
/* get old coefficients */
gk20a_pllg_read_mnp(clk, &pll);
/* do nothing if NDIV is the same */
if (n == pll.n)
return 0;
/* pll slowdown mode */
nvkm_mask(device, GPCPLL_NDIV_SLOWDOWN,
BIT(GPCPLL_NDIV_SLOWDOWN_SLOWDOWN_USING_PLL_SHIFT),
BIT(GPCPLL_NDIV_SLOWDOWN_SLOWDOWN_USING_PLL_SHIFT));
/* new ndiv ready for ramp */
pll.n = n;
udelay(1);
gk20a_pllg_write_mnp(clk, &pll);
/* dynamic ramp to new ndiv */
udelay(1);
nvkm_mask(device, GPCPLL_NDIV_SLOWDOWN,
BIT(GPCPLL_NDIV_SLOWDOWN_EN_DYNRAMP_SHIFT),
BIT(GPCPLL_NDIV_SLOWDOWN_EN_DYNRAMP_SHIFT));
/* wait for ramping to complete */
if (nvkm_wait_usec(device, 500, GPC_BCAST_NDIV_SLOWDOWN_DEBUG,
GPC_BCAST_NDIV_SLOWDOWN_DEBUG_PLL_DYNRAMP_DONE_SYNCED_MASK,
GPC_BCAST_NDIV_SLOWDOWN_DEBUG_PLL_DYNRAMP_DONE_SYNCED_MASK) < 0)
ret = -ETIMEDOUT;
/* exit slowdown mode */
nvkm_mask(device, GPCPLL_NDIV_SLOWDOWN,
BIT(GPCPLL_NDIV_SLOWDOWN_SLOWDOWN_USING_PLL_SHIFT) |
BIT(GPCPLL_NDIV_SLOWDOWN_EN_DYNRAMP_SHIFT), 0);
nvkm_rd32(device, GPCPLL_NDIV_SLOWDOWN);
return ret;
}
static int
gk20a_pllg_enable(struct gk20a_clk *clk)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 val;
nvkm_mask(device, GPCPLL_CFG, GPCPLL_CFG_ENABLE, GPCPLL_CFG_ENABLE);
nvkm_rd32(device, GPCPLL_CFG);
/* enable lock detection */
val = nvkm_rd32(device, GPCPLL_CFG);
if (val & GPCPLL_CFG_LOCK_DET_OFF) {
val &= ~GPCPLL_CFG_LOCK_DET_OFF;
nvkm_wr32(device, GPCPLL_CFG, val);
}
/* wait for lock */
if (nvkm_wait_usec(device, 300, GPCPLL_CFG, GPCPLL_CFG_LOCK,
GPCPLL_CFG_LOCK) < 0)
return -ETIMEDOUT;
/* switch to VCO mode */
nvkm_mask(device, SEL_VCO, BIT(SEL_VCO_GPC2CLK_OUT_SHIFT),
BIT(SEL_VCO_GPC2CLK_OUT_SHIFT));
return 0;
}
static void
gk20a_pllg_disable(struct gk20a_clk *clk)
{
struct nvkm_device *device = clk->base.subdev.device;
/* put PLL in bypass before disabling it */
nvkm_mask(device, SEL_VCO, BIT(SEL_VCO_GPC2CLK_OUT_SHIFT), 0);
nvkm_mask(device, GPCPLL_CFG, GPCPLL_CFG_ENABLE, 0);
nvkm_rd32(device, GPCPLL_CFG);
}
static int
gk20a_pllg_program_mnp(struct gk20a_clk *clk, const struct gk20a_pll *pll)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
struct gk20a_pll cur_pll;
int ret;
gk20a_pllg_read_mnp(clk, &cur_pll);
/* split VCO-to-bypass jump in half by setting out divider 1:2 */
nvkm_mask(device, GPC2CLK_OUT, GPC2CLK_OUT_VCODIV_MASK,
GPC2CLK_OUT_VCODIV2 << GPC2CLK_OUT_VCODIV_SHIFT);
/* Intentional 2nd write to assure linear divider operation */
nvkm_mask(device, GPC2CLK_OUT, GPC2CLK_OUT_VCODIV_MASK,
GPC2CLK_OUT_VCODIV2 << GPC2CLK_OUT_VCODIV_SHIFT);
nvkm_rd32(device, GPC2CLK_OUT);
udelay(2);
gk20a_pllg_disable(clk);
gk20a_pllg_write_mnp(clk, pll);
ret = gk20a_pllg_enable(clk);
if (ret)
return ret;
/* restore out divider 1:1 */
udelay(2);
nvkm_mask(device, GPC2CLK_OUT, GPC2CLK_OUT_VCODIV_MASK,
GPC2CLK_OUT_VCODIV1 << GPC2CLK_OUT_VCODIV_SHIFT);
/* Intentional 2nd write to assure linear divider operation */
nvkm_mask(device, GPC2CLK_OUT, GPC2CLK_OUT_VCODIV_MASK,
GPC2CLK_OUT_VCODIV1 << GPC2CLK_OUT_VCODIV_SHIFT);
nvkm_rd32(device, GPC2CLK_OUT);
return 0;
}
static int
gk20a_pllg_program_mnp_slide(struct gk20a_clk *clk, const struct gk20a_pll *pll)
{
struct gk20a_pll cur_pll;
int ret;
if (gk20a_pllg_is_enabled(clk)) {
gk20a_pllg_read_mnp(clk, &cur_pll);
/* just do NDIV slide if there is no change to M and PL */
if (pll->m == cur_pll.m && pll->pl == cur_pll.pl)
return gk20a_pllg_slide(clk, pll->n);
/* slide down to current NDIV_LO */
cur_pll.n = gk20a_pllg_n_lo(clk, &cur_pll);
ret = gk20a_pllg_slide(clk, cur_pll.n);
if (ret)
return ret;
}
/* program MNP with the new clock parameters and new NDIV_LO */
cur_pll = *pll;
cur_pll.n = gk20a_pllg_n_lo(clk, &cur_pll);
ret = gk20a_pllg_program_mnp(clk, &cur_pll);
if (ret)
return ret;
/* slide up to new NDIV */
return gk20a_pllg_slide(clk, pll->n);
}
static struct nvkm_pstate
gk20a_pstates[] = {
{
.base = {
.domain[nv_clk_src_gpc] = 72000,
.voltage = 0,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 108000,
.voltage = 1,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 180000,
.voltage = 2,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 252000,
.voltage = 3,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 324000,
.voltage = 4,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 396000,
.voltage = 5,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 468000,
.voltage = 6,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 540000,
.voltage = 7,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 612000,
.voltage = 8,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 648000,
.voltage = 9,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 684000,
.voltage = 10,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 708000,
.voltage = 11,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 756000,
.voltage = 12,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 804000,
.voltage = 13,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 852000,
.voltage = 14,
},
},
};
int
gk20a_clk_read(struct nvkm_clk *base, enum nv_clk_src src)
{
struct gk20a_clk *clk = gk20a_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
struct gk20a_pll pll;
switch (src) {
case nv_clk_src_crystal:
return device->crystal;
case nv_clk_src_gpc:
gk20a_pllg_read_mnp(clk, &pll);
return gk20a_pllg_calc_rate(clk, &pll) / GK20A_CLK_GPC_MDIV;
default:
nvkm_error(subdev, "invalid clock source %d\n", src);
return -EINVAL;
}
}
int
gk20a_clk_calc(struct nvkm_clk *base, struct nvkm_cstate *cstate)
{
struct gk20a_clk *clk = gk20a_clk(base);
return gk20a_pllg_calc_mnp(clk, cstate->domain[nv_clk_src_gpc] *
GK20A_CLK_GPC_MDIV, &clk->pll);
}
int
gk20a_clk_prog(struct nvkm_clk *base)
{
struct gk20a_clk *clk = gk20a_clk(base);
int ret;
ret = gk20a_pllg_program_mnp_slide(clk, &clk->pll);
if (ret)
ret = gk20a_pllg_program_mnp(clk, &clk->pll);
return ret;
}
void
gk20a_clk_tidy(struct nvkm_clk *base)
{
}
int
gk20a_clk_setup_slide(struct gk20a_clk *clk)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
u32 step_a, step_b;
switch (clk->parent_rate) {
case 12000000:
case 12800000:
case 13000000:
step_a = 0x2b;
step_b = 0x0b;
break;
case 19200000:
step_a = 0x12;
step_b = 0x08;
break;
case 38400000:
step_a = 0x04;
step_b = 0x05;
break;
default:
nvkm_error(subdev, "invalid parent clock rate %u KHz",
clk->parent_rate / KHZ);
return -EINVAL;
}
nvkm_mask(device, GPCPLL_CFG2, 0xff << GPCPLL_CFG2_PLL_STEPA_SHIFT,
step_a << GPCPLL_CFG2_PLL_STEPA_SHIFT);
nvkm_mask(device, GPCPLL_CFG3, 0xff << GPCPLL_CFG3_PLL_STEPB_SHIFT,
step_b << GPCPLL_CFG3_PLL_STEPB_SHIFT);
return 0;
}
void
gk20a_clk_fini(struct nvkm_clk *base)
{
struct nvkm_device *device = base->subdev.device;
struct gk20a_clk *clk = gk20a_clk(base);
/* slide to VCO min */
if (gk20a_pllg_is_enabled(clk)) {
struct gk20a_pll pll;
u32 n_lo;
gk20a_pllg_read_mnp(clk, &pll);
n_lo = gk20a_pllg_n_lo(clk, &pll);
gk20a_pllg_slide(clk, n_lo);
}
gk20a_pllg_disable(clk);
/* set IDDQ */
nvkm_mask(device, GPCPLL_CFG, GPCPLL_CFG_IDDQ, 1);
}
static int
gk20a_clk_init(struct nvkm_clk *base)
{
struct gk20a_clk *clk = gk20a_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
int ret;
/* get out from IDDQ */
nvkm_mask(device, GPCPLL_CFG, GPCPLL_CFG_IDDQ, 0);
nvkm_rd32(device, GPCPLL_CFG);
udelay(5);
nvkm_mask(device, GPC2CLK_OUT, GPC2CLK_OUT_INIT_MASK,
GPC2CLK_OUT_INIT_VAL);
ret = gk20a_clk_setup_slide(clk);
if (ret)
return ret;
/* Start with lowest frequency */
base->func->calc(base, &base->func->pstates[0].base);
ret = base->func->prog(&clk->base);
if (ret) {
nvkm_error(subdev, "cannot initialize clock\n");
return ret;
}
ret = gk20a_devfreq_init(base, &clk->devfreq);
if (ret)
return ret;
return 0;
}
static const struct nvkm_clk_func
gk20a_clk = {
.init = gk20a_clk_init,
.fini = gk20a_clk_fini,
.read = gk20a_clk_read,
.calc = gk20a_clk_calc,
.prog = gk20a_clk_prog,
.tidy = gk20a_clk_tidy,
.pstates = gk20a_pstates,
.nr_pstates = ARRAY_SIZE(gk20a_pstates),
.domains = {
{ nv_clk_src_crystal, 0xff },
{ nv_clk_src_gpc, 0xff, 0, "core", GK20A_CLK_GPC_MDIV },
{ nv_clk_src_max }
}
};
int
gk20a_clk_ctor(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
const struct nvkm_clk_func *func, const struct gk20a_clk_pllg_params *params,
struct gk20a_clk *clk)
{
struct nvkm_device_tegra *tdev = device->func->tegra(device);
int ret;
int i;
/* Finish initializing the pstates */
for (i = 0; i < func->nr_pstates; i++) {
INIT_LIST_HEAD(&func->pstates[i].list);
func->pstates[i].pstate = i + 1;
}
clk->params = params;
clk->parent_rate = clk_get_rate(tdev->clk);
ret = nvkm_clk_ctor(func, device, type, inst, true, &clk->base);
if (ret)
return ret;
nvkm_debug(&clk->base.subdev, "parent clock rate: %d Khz\n",
clk->parent_rate / KHZ);
return 0;
}
int
gk20a_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_clk **pclk)
{
struct gk20a_clk *clk;
int ret;
clk = kzalloc_obj(*clk);
if (!clk)
return -ENOMEM;
*pclk = &clk->base;
ret = gk20a_clk_ctor(device, type, inst, &gk20a_clk, &gk20a_pllg_params, clk);
clk->pl_to_div = pl_to_div;
clk->div_to_pl = div_to_pl;
return ret;
}
@@ -0,0 +1,160 @@
/*
* Copyright (c) 2016, NVIDIA CORPORATION. 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 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 __NVKM_CLK_GK20A_H__
#define __NVKM_CLK_GK20A_H__
#define KHZ (1000)
#define MHZ (KHZ * 1000)
#define MASK(w) ((1 << (w)) - 1)
#define GK20A_CLK_GPC_MDIV 1000
#define SYS_GPCPLL_CFG_BASE 0x00137000
#define GPCPLL_CFG (SYS_GPCPLL_CFG_BASE + 0)
#define GPCPLL_CFG_ENABLE BIT(0)
#define GPCPLL_CFG_IDDQ BIT(1)
#define GPCPLL_CFG_LOCK_DET_OFF BIT(4)
#define GPCPLL_CFG_LOCK BIT(17)
#define GPCPLL_CFG2 (SYS_GPCPLL_CFG_BASE + 0xc)
#define GPCPLL_CFG2_SETUP2_SHIFT 16
#define GPCPLL_CFG2_PLL_STEPA_SHIFT 24
#define GPCPLL_CFG3 (SYS_GPCPLL_CFG_BASE + 0x18)
#define GPCPLL_CFG3_VCO_CTRL_SHIFT 0
#define GPCPLL_CFG3_VCO_CTRL_WIDTH 9
#define GPCPLL_CFG3_VCO_CTRL_MASK \
(MASK(GPCPLL_CFG3_VCO_CTRL_WIDTH) << GPCPLL_CFG3_VCO_CTRL_SHIFT)
#define GPCPLL_CFG3_PLL_STEPB_SHIFT 16
#define GPCPLL_CFG3_PLL_STEPB_WIDTH 8
#define GPCPLL_COEFF (SYS_GPCPLL_CFG_BASE + 4)
#define GPCPLL_COEFF_M_SHIFT 0
#define GPCPLL_COEFF_M_WIDTH 8
#define GPCPLL_COEFF_N_SHIFT 8
#define GPCPLL_COEFF_N_WIDTH 8
#define GPCPLL_COEFF_N_MASK \
(MASK(GPCPLL_COEFF_N_WIDTH) << GPCPLL_COEFF_N_SHIFT)
#define GPCPLL_COEFF_P_SHIFT 16
#define GPCPLL_COEFF_P_WIDTH 6
#define GPCPLL_NDIV_SLOWDOWN (SYS_GPCPLL_CFG_BASE + 0x1c)
#define GPCPLL_NDIV_SLOWDOWN_NDIV_LO_SHIFT 0
#define GPCPLL_NDIV_SLOWDOWN_NDIV_MID_SHIFT 8
#define GPCPLL_NDIV_SLOWDOWN_STEP_SIZE_LO2MID_SHIFT 16
#define GPCPLL_NDIV_SLOWDOWN_SLOWDOWN_USING_PLL_SHIFT 22
#define GPCPLL_NDIV_SLOWDOWN_EN_DYNRAMP_SHIFT 31
#define GPC_BCAST_GPCPLL_CFG_BASE 0x00132800
#define GPC_BCAST_NDIV_SLOWDOWN_DEBUG (GPC_BCAST_GPCPLL_CFG_BASE + 0xa0)
#define GPC_BCAST_NDIV_SLOWDOWN_DEBUG_PLL_DYNRAMP_DONE_SYNCED_SHIFT 24
#define GPC_BCAST_NDIV_SLOWDOWN_DEBUG_PLL_DYNRAMP_DONE_SYNCED_MASK \
(0x1 << GPC_BCAST_NDIV_SLOWDOWN_DEBUG_PLL_DYNRAMP_DONE_SYNCED_SHIFT)
#define SEL_VCO (SYS_GPCPLL_CFG_BASE + 0x100)
#define SEL_VCO_GPC2CLK_OUT_SHIFT 0
#define GPC2CLK_OUT (SYS_GPCPLL_CFG_BASE + 0x250)
#define GPC2CLK_OUT_SDIV14_INDIV4_WIDTH 1
#define GPC2CLK_OUT_SDIV14_INDIV4_SHIFT 31
#define GPC2CLK_OUT_SDIV14_INDIV4_MODE 1
#define GPC2CLK_OUT_VCODIV_WIDTH 6
#define GPC2CLK_OUT_VCODIV_SHIFT 8
#define GPC2CLK_OUT_VCODIV1 0
#define GPC2CLK_OUT_VCODIV2 2
#define GPC2CLK_OUT_VCODIV_MASK (MASK(GPC2CLK_OUT_VCODIV_WIDTH) << \
GPC2CLK_OUT_VCODIV_SHIFT)
#define GPC2CLK_OUT_BYPDIV_WIDTH 6
#define GPC2CLK_OUT_BYPDIV_SHIFT 0
#define GPC2CLK_OUT_BYPDIV31 0x3c
#define GPC2CLK_OUT_INIT_MASK ((MASK(GPC2CLK_OUT_SDIV14_INDIV4_WIDTH) << \
GPC2CLK_OUT_SDIV14_INDIV4_SHIFT)\
| (MASK(GPC2CLK_OUT_VCODIV_WIDTH) << GPC2CLK_OUT_VCODIV_SHIFT)\
| (MASK(GPC2CLK_OUT_BYPDIV_WIDTH) << GPC2CLK_OUT_BYPDIV_SHIFT))
#define GPC2CLK_OUT_INIT_VAL ((GPC2CLK_OUT_SDIV14_INDIV4_MODE << \
GPC2CLK_OUT_SDIV14_INDIV4_SHIFT) \
| (GPC2CLK_OUT_VCODIV1 << GPC2CLK_OUT_VCODIV_SHIFT) \
| (GPC2CLK_OUT_BYPDIV31 << GPC2CLK_OUT_BYPDIV_SHIFT))
/* All frequencies in Khz */
struct gk20a_clk_pllg_params {
u32 min_vco, max_vco;
u32 min_u, max_u;
u32 min_m, max_m;
u32 min_n, max_n;
u32 min_pl, max_pl;
};
struct gk20a_pll {
u32 m;
u32 n;
u32 pl;
};
struct gk20a_clk {
struct nvkm_clk base;
const struct gk20a_clk_pllg_params *params;
struct gk20a_pll pll;
u32 parent_rate;
struct gk20a_devfreq *devfreq;
u32 (*div_to_pl)(u32);
u32 (*pl_to_div)(u32);
};
#define gk20a_clk(p) container_of((p), struct gk20a_clk, base)
u32 gk20a_pllg_calc_rate(struct gk20a_clk *, struct gk20a_pll *);
int gk20a_pllg_calc_mnp(struct gk20a_clk *, unsigned long, struct gk20a_pll *);
void gk20a_pllg_read_mnp(struct gk20a_clk *, struct gk20a_pll *);
void gk20a_pllg_write_mnp(struct gk20a_clk *, const struct gk20a_pll *);
static inline bool
gk20a_pllg_is_enabled(struct gk20a_clk *clk)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 val;
val = nvkm_rd32(device, GPCPLL_CFG);
return val & GPCPLL_CFG_ENABLE;
}
static inline u32
gk20a_pllg_n_lo(struct gk20a_clk *clk, struct gk20a_pll *pll)
{
return DIV_ROUND_UP(pll->m * clk->params->min_vco,
clk->parent_rate / KHZ);
}
int gk20a_clk_ctor(struct nvkm_device *, enum nvkm_subdev_type, int, const struct nvkm_clk_func *,
const struct gk20a_clk_pllg_params *, struct gk20a_clk *);
void gk20a_clk_fini(struct nvkm_clk *);
int gk20a_clk_read(struct nvkm_clk *, enum nv_clk_src);
int gk20a_clk_calc(struct nvkm_clk *, struct nvkm_cstate *);
int gk20a_clk_prog(struct nvkm_clk *);
void gk20a_clk_tidy(struct nvkm_clk *);
int gk20a_clk_setup_slide(struct gk20a_clk *);
#endif
@@ -0,0 +1,320 @@
// SPDX-License-Identifier: MIT
#include <linux/clk.h>
#include <linux/math64.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <drm/drm_managed.h>
#include <subdev/clk.h>
#include "nouveau_drv.h"
#include "nouveau_chan.h"
#include "priv.h"
#include "gk20a_devfreq.h"
#include "gk20a.h"
#include "gp10b.h"
#define PMU_BUSY_CYCLES_NORM_MAX 1000U
#define PWR_PMU_IDLE_COUNTER_TOTAL 0U
#define PWR_PMU_IDLE_COUNTER_BUSY 4U
#define PWR_PMU_IDLE_COUNT_REG_OFFSET 0x0010A508U
#define PWR_PMU_IDLE_COUNT_REG_SIZE 16U
#define PWR_PMU_IDLE_COUNT_MASK 0x7FFFFFFFU
#define PWR_PMU_IDLE_COUNT_RESET_VALUE (0x1U << 31U)
#define PWR_PMU_IDLE_INTR_REG_OFFSET 0x0010A9E8U
#define PWR_PMU_IDLE_INTR_ENABLE_VALUE 0U
#define PWR_PMU_IDLE_INTR_STATUS_REG_OFFSET 0x0010A9ECU
#define PWR_PMU_IDLE_INTR_STATUS_MASK 0x00000001U
#define PWR_PMU_IDLE_INTR_STATUS_RESET_VALUE 0x1U
#define PWR_PMU_IDLE_THRESHOLD_REG_OFFSET 0x0010A8A0U
#define PWR_PMU_IDLE_THRESHOLD_REG_SIZE 4U
#define PWR_PMU_IDLE_THRESHOLD_MAX_VALUE 0x7FFFFFFFU
#define PWR_PMU_IDLE_CTRL_REG_OFFSET 0x0010A50CU
#define PWR_PMU_IDLE_CTRL_REG_SIZE 16U
#define PWR_PMU_IDLE_CTRL_VALUE_MASK 0x3U
#define PWR_PMU_IDLE_CTRL_VALUE_BUSY 0x2U
#define PWR_PMU_IDLE_CTRL_VALUE_ALWAYS 0x3U
#define PWR_PMU_IDLE_CTRL_FILTER_MASK (0x1U << 2)
#define PWR_PMU_IDLE_CTRL_FILTER_DISABLED 0x0U
#define PWR_PMU_IDLE_MASK_REG_OFFSET 0x0010A504U
#define PWR_PMU_IDLE_MASK_REG_SIZE 16U
#define PWM_PMU_IDLE_MASK_GR_ENABLED 0x1U
#define PWM_PMU_IDLE_MASK_CE_2_ENABLED 0x200000U
/**
* struct gk20a_devfreq - Device frequency management
*/
struct gk20a_devfreq {
/** @devfreq: devfreq device. */
struct devfreq *devfreq;
/** @regs: Device registers. */
void __iomem *regs;
/** @gov_data: Governor data. */
struct devfreq_simple_ondemand_data gov_data;
/** @busy_time: Busy time. */
ktime_t busy_time;
/** @total_time: Total time. */
ktime_t total_time;
/** @time_last_update: Last update time. */
ktime_t time_last_update;
};
static struct gk20a_devfreq *dev_to_gk20a_devfreq(struct device *dev)
{
struct nouveau_drm *drm = dev_get_drvdata(dev);
struct nvkm_subdev *subdev = nvkm_device_subdev(drm->nvkm, NVKM_SUBDEV_CLK, 0);
struct nvkm_clk *base = nvkm_clk(subdev);
switch (drm->nvkm->chipset) {
case 0x13b: return gp10b_clk(base)->devfreq; break;
default: return gk20a_clk(base)->devfreq; break;
}
}
static void gk20a_pmu_init_perfmon_counter(struct gk20a_devfreq *gdevfreq)
{
u32 data;
// Set pmu idle intr status bit on total counter overflow
writel(PWR_PMU_IDLE_INTR_ENABLE_VALUE,
gdevfreq->regs + PWR_PMU_IDLE_INTR_REG_OFFSET);
writel(PWR_PMU_IDLE_THRESHOLD_MAX_VALUE,
gdevfreq->regs + PWR_PMU_IDLE_THRESHOLD_REG_OFFSET +
(PWR_PMU_IDLE_COUNTER_TOTAL * PWR_PMU_IDLE_THRESHOLD_REG_SIZE));
// Setup counter for total cycles
data = readl(gdevfreq->regs + PWR_PMU_IDLE_CTRL_REG_OFFSET +
(PWR_PMU_IDLE_COUNTER_TOTAL * PWR_PMU_IDLE_CTRL_REG_SIZE));
data &= ~(PWR_PMU_IDLE_CTRL_VALUE_MASK | PWR_PMU_IDLE_CTRL_FILTER_MASK);
data |= PWR_PMU_IDLE_CTRL_VALUE_ALWAYS | PWR_PMU_IDLE_CTRL_FILTER_DISABLED;
writel(data, gdevfreq->regs + PWR_PMU_IDLE_CTRL_REG_OFFSET +
(PWR_PMU_IDLE_COUNTER_TOTAL * PWR_PMU_IDLE_CTRL_REG_SIZE));
// Setup counter for busy cycles
writel(PWM_PMU_IDLE_MASK_GR_ENABLED | PWM_PMU_IDLE_MASK_CE_2_ENABLED,
gdevfreq->regs + PWR_PMU_IDLE_MASK_REG_OFFSET +
(PWR_PMU_IDLE_COUNTER_BUSY * PWR_PMU_IDLE_MASK_REG_SIZE));
data = readl(gdevfreq->regs + PWR_PMU_IDLE_CTRL_REG_OFFSET +
(PWR_PMU_IDLE_COUNTER_BUSY * PWR_PMU_IDLE_CTRL_REG_SIZE));
data &= ~(PWR_PMU_IDLE_CTRL_VALUE_MASK | PWR_PMU_IDLE_CTRL_FILTER_MASK);
data |= PWR_PMU_IDLE_CTRL_VALUE_BUSY | PWR_PMU_IDLE_CTRL_FILTER_DISABLED;
writel(data, gdevfreq->regs + PWR_PMU_IDLE_CTRL_REG_OFFSET +
(PWR_PMU_IDLE_COUNTER_BUSY * PWR_PMU_IDLE_CTRL_REG_SIZE));
}
static u32 gk20a_pmu_read_idle_counter(struct gk20a_devfreq *gdevfreq, u32 counter_id)
{
u32 ret;
ret = readl(gdevfreq->regs + PWR_PMU_IDLE_COUNT_REG_OFFSET +
(counter_id * PWR_PMU_IDLE_COUNT_REG_SIZE));
return ret & PWR_PMU_IDLE_COUNT_MASK;
}
static void gk20a_pmu_reset_idle_counter(struct gk20a_devfreq *gdevfreq, u32 counter_id)
{
writel(PWR_PMU_IDLE_COUNT_RESET_VALUE, gdevfreq->regs + PWR_PMU_IDLE_COUNT_REG_OFFSET +
(counter_id * PWR_PMU_IDLE_COUNT_REG_SIZE));
}
static u32 gk20a_pmu_read_idle_intr_status(struct gk20a_devfreq *gdevfreq)
{
u32 ret;
ret = readl(gdevfreq->regs + PWR_PMU_IDLE_INTR_STATUS_REG_OFFSET);
return ret & PWR_PMU_IDLE_INTR_STATUS_MASK;
}
static void gk20a_pmu_clear_idle_intr_status(struct gk20a_devfreq *gdevfreq)
{
writel(PWR_PMU_IDLE_INTR_STATUS_RESET_VALUE,
gdevfreq->regs + PWR_PMU_IDLE_INTR_STATUS_REG_OFFSET);
}
static void gk20a_devfreq_update_utilization(struct gk20a_devfreq *gdevfreq)
{
ktime_t now, last;
u64 busy_cycles, total_cycles;
u32 norm, intr_status;
now = ktime_get();
last = gdevfreq->time_last_update;
gdevfreq->total_time = ktime_us_delta(now, last);
busy_cycles = gk20a_pmu_read_idle_counter(gdevfreq, PWR_PMU_IDLE_COUNTER_BUSY);
total_cycles = gk20a_pmu_read_idle_counter(gdevfreq, PWR_PMU_IDLE_COUNTER_TOTAL);
intr_status = gk20a_pmu_read_idle_intr_status(gdevfreq);
gk20a_pmu_reset_idle_counter(gdevfreq, PWR_PMU_IDLE_COUNTER_BUSY);
gk20a_pmu_reset_idle_counter(gdevfreq, PWR_PMU_IDLE_COUNTER_TOTAL);
if (intr_status != 0UL) {
norm = PMU_BUSY_CYCLES_NORM_MAX;
gk20a_pmu_clear_idle_intr_status(gdevfreq);
} else if (total_cycles == 0ULL || busy_cycles > total_cycles) {
norm = PMU_BUSY_CYCLES_NORM_MAX;
} else {
norm = (u32)div64_u64(busy_cycles * PMU_BUSY_CYCLES_NORM_MAX,
total_cycles);
}
gdevfreq->busy_time = div_u64(gdevfreq->total_time * norm, PMU_BUSY_CYCLES_NORM_MAX);
gdevfreq->time_last_update = now;
}
static int gk20a_devfreq_target(struct device *dev, unsigned long *freq,
u32 flags)
{
struct nouveau_drm *drm = dev_get_drvdata(dev);
struct nvkm_subdev *subdev = nvkm_device_subdev(drm->nvkm, NVKM_SUBDEV_CLK, 0);
struct nvkm_clk *base = nvkm_clk(subdev);
struct nvkm_pstate *pstates = base->func->pstates;
int nr_pstates = base->func->nr_pstates;
int i, ret;
for (i = 0; i < nr_pstates - 1; i++)
if (pstates[i].base.domain[nv_clk_src_gpc] * GK20A_CLK_GPC_MDIV >= *freq)
break;
ret = nvkm_clk_ustate(base, pstates[i].pstate, 0);
ret |= nvkm_clk_ustate(base, pstates[i].pstate, 1);
if (ret) {
nvkm_error(subdev, "cannot update clock\n");
return ret;
}
*freq = pstates[i].base.domain[nv_clk_src_gpc] * GK20A_CLK_GPC_MDIV;
return 0;
}
static int gk20a_devfreq_get_cur_freq(struct device *dev, unsigned long *freq)
{
struct nouveau_drm *drm = dev_get_drvdata(dev);
struct nvkm_subdev *subdev = nvkm_device_subdev(drm->nvkm, NVKM_SUBDEV_CLK, 0);
struct nvkm_clk *base = nvkm_clk(subdev);
*freq = nvkm_clk_read(base, nv_clk_src_gpc) * GK20A_CLK_GPC_MDIV;
return 0;
}
static void gk20a_devfreq_reset(struct gk20a_devfreq *gdevfreq)
{
gk20a_pmu_reset_idle_counter(gdevfreq, PWR_PMU_IDLE_COUNTER_BUSY);
gk20a_pmu_reset_idle_counter(gdevfreq, PWR_PMU_IDLE_COUNTER_TOTAL);
gk20a_pmu_clear_idle_intr_status(gdevfreq);
gdevfreq->busy_time = 0;
gdevfreq->total_time = 0;
gdevfreq->time_last_update = ktime_get();
}
static int gk20a_devfreq_get_dev_status(struct device *dev,
struct devfreq_dev_status *status)
{
struct nouveau_drm *drm = dev_get_drvdata(dev);
struct gk20a_devfreq *gdevfreq = dev_to_gk20a_devfreq(dev);
gk20a_devfreq_get_cur_freq(dev, &status->current_frequency);
gk20a_devfreq_update_utilization(gdevfreq);
status->busy_time = ktime_to_ns(gdevfreq->busy_time);
status->total_time = ktime_to_ns(gdevfreq->total_time);
gk20a_devfreq_reset(gdevfreq);
NV_DEBUG(drm, "busy %lu total %lu %lu %% freq %lu MHz\n",
status->busy_time, status->total_time,
status->busy_time / (status->total_time / 100),
status->current_frequency / 1000 / 1000);
return 0;
}
static struct devfreq_dev_profile gk20a_devfreq_profile = {
.timer = DEVFREQ_TIMER_DELAYED,
.polling_ms = 50,
.target = gk20a_devfreq_target,
.get_cur_freq = gk20a_devfreq_get_cur_freq,
.get_dev_status = gk20a_devfreq_get_dev_status,
};
int gk20a_devfreq_init(struct nvkm_clk *base, struct gk20a_devfreq **gdevfreq)
{
struct nvkm_device *device = base->subdev.device;
struct nouveau_drm *drm = dev_get_drvdata(device->dev);
struct nvkm_device_tegra *tdev = device->func->tegra(device);
struct nvkm_pstate *pstates = base->func->pstates;
int nr_pstates = base->func->nr_pstates;
struct gk20a_devfreq *new_gdevfreq;
int i;
new_gdevfreq = drmm_kzalloc(drm->dev, sizeof(struct gk20a_devfreq), GFP_KERNEL);
if (!new_gdevfreq)
return -ENOMEM;
new_gdevfreq->regs = tdev->regs;
for (i = 0; i < nr_pstates; i++)
dev_pm_opp_add(base->subdev.device->dev,
pstates[i].base.domain[nv_clk_src_gpc] * GK20A_CLK_GPC_MDIV, 0);
gk20a_pmu_init_perfmon_counter(new_gdevfreq);
gk20a_devfreq_reset(new_gdevfreq);
gk20a_devfreq_profile.initial_freq =
nvkm_clk_read(base, nv_clk_src_gpc) * GK20A_CLK_GPC_MDIV;
new_gdevfreq->gov_data.upthreshold = 45;
new_gdevfreq->gov_data.downdifferential = 5;
new_gdevfreq->devfreq = devm_devfreq_add_device(device->dev,
&gk20a_devfreq_profile,
DEVFREQ_GOV_SIMPLE_ONDEMAND,
&new_gdevfreq->gov_data);
if (IS_ERR(new_gdevfreq->devfreq))
return PTR_ERR(new_gdevfreq->devfreq);
*gdevfreq = new_gdevfreq;
return 0;
}
int gk20a_devfreq_resume(struct device *dev)
{
struct gk20a_devfreq *gdevfreq = dev_to_gk20a_devfreq(dev);
if (!gdevfreq || !gdevfreq->devfreq)
return 0;
return devfreq_resume_device(gdevfreq->devfreq);
}
int gk20a_devfreq_suspend(struct device *dev)
{
struct gk20a_devfreq *gdevfreq = dev_to_gk20a_devfreq(dev);
if (!gdevfreq || !gdevfreq->devfreq)
return 0;
return devfreq_suspend_device(gdevfreq->devfreq);
}
@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: MIT */
#ifndef __GK20A_DEVFREQ_H__
#define __GK20A_DEVFREQ_H__
#include <linux/devfreq.h>
struct gk20a_devfreq;
#if defined(CONFIG_PM_DEVFREQ)
int gk20a_devfreq_init(struct nvkm_clk *base, struct gk20a_devfreq **devfreq);
int gk20a_devfreq_resume(struct device *dev);
int gk20a_devfreq_suspend(struct device *dev);
#else
static inline int gk20a_devfreq_init(struct nvkm_clk *base, struct gk20a_devfreq **devfreq)
{
return 0;
}
static inline int gk20a_devfreq_resume(struct device dev) { return 0; }
static inline int gk20a_devfreq_suspend(struct device *dev) { return 0; }
#endif /* CONFIG_PM_DEVFREQ */
#endif /* __GK20A_DEVFREQ_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,185 @@
// SPDX-License-Identifier: MIT
#include <subdev/clk.h>
#include <subdev/timer.h>
#include <core/device.h>
#include <core/tegra.h>
#include "priv.h"
#include "gk20a_devfreq.h"
#include "gk20a.h"
#include "gp10b.h"
static int
gp10b_clk_init(struct nvkm_clk *base)
{
struct gp10b_clk *clk = gp10b_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
int ret;
/* Start with the highest frequency, matching the BPMP default */
base->func->calc(base, &base->func->pstates[base->func->nr_pstates - 1].base);
ret = base->func->prog(base);
if (ret) {
nvkm_error(subdev, "cannot initialize clock\n");
return ret;
}
ret = gk20a_devfreq_init(base, &clk->devfreq);
if (ret)
return ret;
return 0;
}
static int
gp10b_clk_read(struct nvkm_clk *base, enum nv_clk_src src)
{
struct gp10b_clk *clk = gp10b_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
switch (src) {
case nv_clk_src_gpc:
return clk_get_rate(clk->clk) / GK20A_CLK_GPC_MDIV;
default:
nvkm_error(subdev, "invalid clock source %d\n", src);
return -EINVAL;
}
}
static int
gp10b_clk_calc(struct nvkm_clk *base, struct nvkm_cstate *cstate)
{
struct gp10b_clk *clk = gp10b_clk(base);
u32 target_rate = cstate->domain[nv_clk_src_gpc] * GK20A_CLK_GPC_MDIV;
clk->new_rate = clk_round_rate(clk->clk, target_rate) / GK20A_CLK_GPC_MDIV;
return 0;
}
static int
gp10b_clk_prog(struct nvkm_clk *base)
{
struct gp10b_clk *clk = gp10b_clk(base);
int ret;
ret = clk_set_rate(clk->clk, clk->new_rate * GK20A_CLK_GPC_MDIV);
if (ret < 0)
return ret;
clk->rate = clk_get_rate(clk->clk) / GK20A_CLK_GPC_MDIV;
return 0;
}
static struct nvkm_pstate
gp10b_pstates[] = {
{
.base = {
.domain[nv_clk_src_gpc] = 114750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 216750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 318750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 420750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 522750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 624750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 726750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 828750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 930750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 1032750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 1134750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 1236750,
},
},
{
.base = {
.domain[nv_clk_src_gpc] = 1300500,
},
},
};
static const struct nvkm_clk_func
gp10b_clk = {
.init = gp10b_clk_init,
.read = gp10b_clk_read,
.calc = gp10b_clk_calc,
.prog = gp10b_clk_prog,
.tidy = gk20a_clk_tidy,
.pstates = gp10b_pstates,
.nr_pstates = ARRAY_SIZE(gp10b_pstates),
.domains = {
{ nv_clk_src_gpc, 0xff, 0, "core", GK20A_CLK_GPC_MDIV },
{ nv_clk_src_max }
}
};
int
gp10b_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_clk **pclk)
{
struct nvkm_device_tegra *tdev = device->func->tegra(device);
const struct nvkm_clk_func *func = &gp10b_clk;
struct gp10b_clk *clk;
int ret, i;
clk = kzalloc_obj(*clk);
if (!clk)
return -ENOMEM;
*pclk = &clk->base;
clk->clk = tdev->clk;
/* Finish initializing the pstates */
for (i = 0; i < func->nr_pstates; i++) {
INIT_LIST_HEAD(&func->pstates[i].list);
func->pstates[i].pstate = i + 1;
}
ret = nvkm_clk_ctor(func, device, type, inst, true, &clk->base);
if (ret)
return ret;
return 0;
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_CLK_GP10B_H__
#define __NVKM_CLK_GP10B_H__
struct gp10b_clk {
/* currently applied parameters */
struct nvkm_clk base;
struct gk20a_devfreq *devfreq;
struct clk *clk;
u32 rate;
/* new parameters to apply */
u32 new_rate;
};
#define gp10b_clk(p) container_of((p), struct gp10b_clk, base)
#endif
@@ -0,0 +1,550 @@
/*
* Copyright 2012 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
* Roy Spliet
*/
#define gt215_clk(p) container_of((p), struct gt215_clk, base)
#include "gt215.h"
#include "pll.h"
#include <engine/fifo.h>
#include <subdev/bios.h>
#include <subdev/bios/pll.h>
#include <subdev/timer.h>
struct gt215_clk {
struct nvkm_clk base;
struct gt215_clk_info eng[nv_clk_src_max];
};
static u32 read_clk(struct gt215_clk *, int, bool);
static u32 read_pll(struct gt215_clk *, int, u32);
static u32
read_vco(struct gt215_clk *clk, int idx)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 sctl = nvkm_rd32(device, 0x4120 + (idx * 4));
switch (sctl & 0x00000030) {
case 0x00000000:
return device->crystal;
case 0x00000020:
return read_pll(clk, 0x41, 0x00e820);
case 0x00000030:
return read_pll(clk, 0x42, 0x00e8a0);
default:
return 0;
}
}
static u32
read_clk(struct gt215_clk *clk, int idx, bool ignore_en)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 sctl, sdiv, sclk;
/* refclk for the 0xe8xx plls is a fixed frequency */
if (idx >= 0x40) {
if (device->chipset == 0xaf) {
/* no joke.. seriously.. sigh.. */
return nvkm_rd32(device, 0x00471c) * 1000;
}
return device->crystal;
}
sctl = nvkm_rd32(device, 0x4120 + (idx * 4));
if (!ignore_en && !(sctl & 0x00000100))
return 0;
/* out_alt */
if (sctl & 0x00000400)
return 108000;
/* vco_out */
switch (sctl & 0x00003000) {
case 0x00000000:
if (!(sctl & 0x00000200))
return device->crystal;
return 0;
case 0x00002000:
if (sctl & 0x00000040)
return 108000;
return 100000;
case 0x00003000:
/* vco_enable */
if (!(sctl & 0x00000001))
return 0;
sclk = read_vco(clk, idx);
sdiv = ((sctl & 0x003f0000) >> 16) + 2;
return (sclk * 2) / sdiv;
default:
return 0;
}
}
static u32
read_pll(struct gt215_clk *clk, int idx, u32 pll)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 ctrl = nvkm_rd32(device, pll + 0);
u32 sclk = 0, P = 1, N = 1, M = 1;
u32 MP;
if (!(ctrl & 0x00000008)) {
if (ctrl & 0x00000001) {
u32 coef = nvkm_rd32(device, pll + 4);
M = (coef & 0x000000ff) >> 0;
N = (coef & 0x0000ff00) >> 8;
P = (coef & 0x003f0000) >> 16;
/* no post-divider on these..
* XXX: it looks more like two post-"dividers" that
* cross each other out in the default RPLL config */
if ((pll & 0x00ff00) == 0x00e800)
P = 1;
sclk = read_clk(clk, 0x00 + idx, false);
}
} else {
sclk = read_clk(clk, 0x10 + idx, false);
}
MP = M * P;
if (!MP)
return 0;
return sclk * N / MP;
}
static int
gt215_clk_read(struct nvkm_clk *base, enum nv_clk_src src)
{
struct gt215_clk *clk = gt215_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
u32 hsrc;
switch (src) {
case nv_clk_src_crystal:
return device->crystal;
case nv_clk_src_core:
case nv_clk_src_core_intm:
return read_pll(clk, 0x00, 0x4200);
case nv_clk_src_shader:
return read_pll(clk, 0x01, 0x4220);
case nv_clk_src_mem:
return read_pll(clk, 0x02, 0x4000);
case nv_clk_src_disp:
return read_clk(clk, 0x20, false);
case nv_clk_src_vdec:
return read_clk(clk, 0x21, false);
case nv_clk_src_pmu:
return read_clk(clk, 0x25, false);
case nv_clk_src_host:
hsrc = (nvkm_rd32(device, 0xc040) & 0x30000000) >> 28;
switch (hsrc) {
case 0:
return read_clk(clk, 0x1d, false);
case 2:
case 3:
return 277000;
default:
nvkm_error(subdev, "unknown HOST clock source %d\n", hsrc);
return -EINVAL;
}
default:
nvkm_error(subdev, "invalid clock source %d\n", src);
return -EINVAL;
}
return 0;
}
static int
gt215_clk_info(struct nvkm_clk *base, int idx, u32 khz,
struct gt215_clk_info *info)
{
struct gt215_clk *clk = gt215_clk(base);
u32 oclk, sclk, sdiv;
s32 diff;
info->clk = 0;
switch (khz) {
case 27000:
info->clk = 0x00000100;
return khz;
case 100000:
info->clk = 0x00002100;
return khz;
case 108000:
info->clk = 0x00002140;
return khz;
default:
sclk = read_vco(clk, idx);
sdiv = min((sclk * 2) / khz, (u32)65);
oclk = (sclk * 2) / sdiv;
diff = ((khz + 3000) - oclk);
/* When imprecise, play it safe and aim for a clock lower than
* desired rather than higher */
if (diff < 0) {
sdiv++;
oclk = (sclk * 2) / sdiv;
}
/* divider can go as low as 2, limited here because NVIDIA
* and the VBIOS on my NVA8 seem to prefer using the PLL
* for 810MHz - is there a good reason?
* XXX: PLLs with refclk 810MHz? */
if (sdiv > 4) {
info->clk = (((sdiv - 2) << 16) | 0x00003100);
return oclk;
}
break;
}
return -ERANGE;
}
int
gt215_pll_info(struct nvkm_clk *base, int idx, u32 pll, u32 khz,
struct gt215_clk_info *info)
{
struct gt215_clk *clk = gt215_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvbios_pll limits;
int P, N, M, diff;
int ret;
info->pll = 0;
/* If we can get a within [-2, 3) MHz of a divider, we'll disable the
* PLL and use the divider instead. */
ret = gt215_clk_info(&clk->base, idx, khz, info);
diff = khz - ret;
if (!pll || (diff >= -2000 && diff < 3000)) {
goto out;
}
/* Try with PLL */
ret = nvbios_pll_parse(subdev->device->bios, pll, &limits);
if (ret)
return ret;
ret = gt215_clk_info(&clk->base, idx - 0x10, limits.refclk, info);
if (ret != limits.refclk)
return -EINVAL;
ret = gt215_pll_calc(subdev, &limits, khz, &N, NULL, &M, &P);
if (ret >= 0) {
info->pll = (P << 16) | (N << 8) | M;
}
out:
info->fb_delay = max(((khz + 7566) / 15133), (u32) 18);
return ret ? ret : -ERANGE;
}
static int
calc_clk(struct gt215_clk *clk, struct nvkm_cstate *cstate,
int idx, u32 pll, int dom)
{
int ret = gt215_pll_info(&clk->base, idx, pll, cstate->domain[dom],
&clk->eng[dom]);
if (ret >= 0)
return 0;
return ret;
}
static int
calc_host(struct gt215_clk *clk, struct nvkm_cstate *cstate)
{
int ret = 0;
u32 kHz = cstate->domain[nv_clk_src_host];
struct gt215_clk_info *info = &clk->eng[nv_clk_src_host];
if (kHz == 277000) {
info->clk = 0;
info->host_out = NVA3_HOST_277;
return 0;
}
info->host_out = NVA3_HOST_CLK;
ret = gt215_clk_info(&clk->base, 0x1d, kHz, info);
if (ret >= 0)
return 0;
return ret;
}
int
gt215_clk_pre(struct nvkm_clk *clk, unsigned long *flags)
{
struct nvkm_device *device = clk->subdev.device;
struct nvkm_fifo *fifo = device->fifo;
/* halt and idle execution engines */
nvkm_mask(device, 0x020060, 0x00070000, 0x00000000);
nvkm_mask(device, 0x002504, 0x00000001, 0x00000001);
/* Wait until the interrupt handler is finished */
if (nvkm_msec(device, 2000,
if (!nvkm_rd32(device, 0x000100))
break;
) < 0)
return -EBUSY;
if (fifo)
nvkm_fifo_pause(fifo, flags);
if (nvkm_msec(device, 2000,
if (nvkm_rd32(device, 0x002504) & 0x00000010)
break;
) < 0)
return -EIO;
if (nvkm_msec(device, 2000,
u32 tmp = nvkm_rd32(device, 0x00251c) & 0x0000003f;
if (tmp == 0x0000003f)
break;
) < 0)
return -EIO;
return 0;
}
void
gt215_clk_post(struct nvkm_clk *clk, unsigned long *flags)
{
struct nvkm_device *device = clk->subdev.device;
struct nvkm_fifo *fifo = device->fifo;
if (fifo && flags)
nvkm_fifo_start(fifo, flags);
nvkm_mask(device, 0x002504, 0x00000001, 0x00000000);
nvkm_mask(device, 0x020060, 0x00070000, 0x00040000);
}
static void
disable_clk_src(struct gt215_clk *clk, u32 src)
{
struct nvkm_device *device = clk->base.subdev.device;
nvkm_mask(device, src, 0x00000100, 0x00000000);
nvkm_mask(device, src, 0x00000001, 0x00000000);
}
static void
prog_pll(struct gt215_clk *clk, int idx, u32 pll, int dom)
{
struct gt215_clk_info *info = &clk->eng[dom];
struct nvkm_device *device = clk->base.subdev.device;
const u32 src0 = 0x004120 + (idx * 4);
const u32 src1 = 0x004160 + (idx * 4);
const u32 ctrl = pll + 0;
const u32 coef = pll + 4;
u32 bypass;
if (info->pll) {
/* Always start from a non-PLL clock */
bypass = nvkm_rd32(device, ctrl) & 0x00000008;
if (!bypass) {
nvkm_mask(device, src1, 0x00000101, 0x00000101);
nvkm_mask(device, ctrl, 0x00000008, 0x00000008);
udelay(20);
}
nvkm_mask(device, src0, 0x003f3141, 0x00000101 | info->clk);
nvkm_wr32(device, coef, info->pll);
nvkm_mask(device, ctrl, 0x00000015, 0x00000015);
nvkm_mask(device, ctrl, 0x00000010, 0x00000000);
if (nvkm_msec(device, 2000,
if (nvkm_rd32(device, ctrl) & 0x00020000)
break;
) < 0) {
nvkm_mask(device, ctrl, 0x00000010, 0x00000010);
nvkm_mask(device, src0, 0x00000101, 0x00000000);
return;
}
nvkm_mask(device, ctrl, 0x00000010, 0x00000010);
nvkm_mask(device, ctrl, 0x00000008, 0x00000000);
disable_clk_src(clk, src1);
} else {
nvkm_mask(device, src1, 0x003f3141, 0x00000101 | info->clk);
nvkm_mask(device, ctrl, 0x00000018, 0x00000018);
udelay(20);
nvkm_mask(device, ctrl, 0x00000001, 0x00000000);
disable_clk_src(clk, src0);
}
}
static void
prog_clk(struct gt215_clk *clk, int idx, int dom)
{
struct gt215_clk_info *info = &clk->eng[dom];
struct nvkm_device *device = clk->base.subdev.device;
nvkm_mask(device, 0x004120 + (idx * 4), 0x003f3141, 0x00000101 | info->clk);
}
static void
prog_host(struct gt215_clk *clk)
{
struct gt215_clk_info *info = &clk->eng[nv_clk_src_host];
struct nvkm_device *device = clk->base.subdev.device;
u32 hsrc = (nvkm_rd32(device, 0xc040));
switch (info->host_out) {
case NVA3_HOST_277:
if ((hsrc & 0x30000000) == 0) {
nvkm_wr32(device, 0xc040, hsrc | 0x20000000);
disable_clk_src(clk, 0x4194);
}
break;
case NVA3_HOST_CLK:
prog_clk(clk, 0x1d, nv_clk_src_host);
if ((hsrc & 0x30000000) >= 0x20000000) {
nvkm_wr32(device, 0xc040, hsrc & ~0x30000000);
}
break;
default:
break;
}
/* This seems to be a clock gating factor on idle, always set to 64 */
nvkm_wr32(device, 0xc044, 0x3e);
}
static void
prog_core(struct gt215_clk *clk, int dom)
{
struct gt215_clk_info *info = &clk->eng[dom];
struct nvkm_device *device = clk->base.subdev.device;
u32 fb_delay = nvkm_rd32(device, 0x10002c);
if (fb_delay < info->fb_delay)
nvkm_wr32(device, 0x10002c, info->fb_delay);
prog_pll(clk, 0x00, 0x004200, dom);
if (fb_delay > info->fb_delay)
nvkm_wr32(device, 0x10002c, info->fb_delay);
}
static int
gt215_clk_calc(struct nvkm_clk *base, struct nvkm_cstate *cstate)
{
struct gt215_clk *clk = gt215_clk(base);
struct gt215_clk_info *core = &clk->eng[nv_clk_src_core];
int ret;
if ((ret = calc_clk(clk, cstate, 0x10, 0x4200, nv_clk_src_core)) ||
(ret = calc_clk(clk, cstate, 0x11, 0x4220, nv_clk_src_shader)) ||
(ret = calc_clk(clk, cstate, 0x20, 0x0000, nv_clk_src_disp)) ||
(ret = calc_clk(clk, cstate, 0x21, 0x0000, nv_clk_src_vdec)) ||
(ret = calc_host(clk, cstate)))
return ret;
/* XXX: Should be reading the highest bit in the VBIOS clock to decide
* whether to use a PLL or not... but using a PLL defeats the purpose */
if (core->pll) {
ret = gt215_clk_info(&clk->base, 0x10,
cstate->domain[nv_clk_src_core_intm],
&clk->eng[nv_clk_src_core_intm]);
if (ret < 0)
return ret;
}
return 0;
}
static int
gt215_clk_prog(struct nvkm_clk *base)
{
struct gt215_clk *clk = gt215_clk(base);
struct gt215_clk_info *core = &clk->eng[nv_clk_src_core];
int ret = 0;
unsigned long flags;
unsigned long *f = &flags;
ret = gt215_clk_pre(&clk->base, f);
if (ret)
goto out;
if (core->pll)
prog_core(clk, nv_clk_src_core_intm);
prog_core(clk, nv_clk_src_core);
prog_pll(clk, 0x01, 0x004220, nv_clk_src_shader);
prog_clk(clk, 0x20, nv_clk_src_disp);
prog_clk(clk, 0x21, nv_clk_src_vdec);
prog_host(clk);
out:
if (ret == -EBUSY)
f = NULL;
gt215_clk_post(&clk->base, f);
return ret;
}
static void
gt215_clk_tidy(struct nvkm_clk *base)
{
}
static const struct nvkm_clk_func
gt215_clk = {
.read = gt215_clk_read,
.calc = gt215_clk_calc,
.prog = gt215_clk_prog,
.tidy = gt215_clk_tidy,
.domains = {
{ nv_clk_src_crystal , 0xff },
{ nv_clk_src_core , 0x00, 0, "core", 1000 },
{ nv_clk_src_shader , 0x01, 0, "shader", 1000 },
{ nv_clk_src_mem , 0x02, 0, "memory", 1000 },
{ nv_clk_src_vdec , 0x03 },
{ nv_clk_src_disp , 0x04 },
{ nv_clk_src_host , 0x05 },
{ nv_clk_src_core_intm, 0x06 },
{ nv_clk_src_max }
}
};
int
gt215_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_clk **pclk)
{
struct gt215_clk *clk;
if (!(clk = kzalloc_obj(*clk)))
return -ENOMEM;
*pclk = &clk->base;
return nvkm_clk_ctor(&gt215_clk, device, type, inst, true, &clk->base);
}
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_CLK_NVA3_H__
#define __NVKM_CLK_NVA3_H__
#include "priv.h"
struct gt215_clk_info {
u32 clk;
u32 pll;
enum {
NVA3_HOST_277,
NVA3_HOST_CLK,
} host_out;
u32 fb_delay;
};
int gt215_pll_info(struct nvkm_clk *, int, u32, u32, struct gt215_clk_info *);
int gt215_clk_pre(struct nvkm_clk *, unsigned long *flags);
void gt215_clk_post(struct nvkm_clk *, unsigned long *flags);
#endif
@@ -0,0 +1,422 @@
/*
* Copyright 2012 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
*/
#define mcp77_clk(p) container_of((p), struct mcp77_clk, base)
#include "gt215.h"
#include "pll.h"
#include <subdev/bios.h>
#include <subdev/bios/pll.h>
#include <subdev/timer.h>
struct mcp77_clk {
struct nvkm_clk base;
enum nv_clk_src csrc, ssrc, vsrc;
u32 cctrl, sctrl;
u32 ccoef, scoef;
u32 cpost, spost;
u32 vdiv;
};
static u32
read_div(struct mcp77_clk *clk)
{
struct nvkm_device *device = clk->base.subdev.device;
return nvkm_rd32(device, 0x004600);
}
static u32
read_pll(struct mcp77_clk *clk, u32 base)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 ctrl = nvkm_rd32(device, base + 0);
u32 coef = nvkm_rd32(device, base + 4);
u32 ref = nvkm_clk_read(&clk->base, nv_clk_src_href);
u32 post_div = 0;
u32 clock = 0;
int N1, M1;
switch (base){
case 0x4020:
post_div = 1 << ((nvkm_rd32(device, 0x4070) & 0x000f0000) >> 16);
break;
case 0x4028:
post_div = (nvkm_rd32(device, 0x4040) & 0x000f0000) >> 16;
break;
default:
break;
}
N1 = (coef & 0x0000ff00) >> 8;
M1 = (coef & 0x000000ff);
if ((ctrl & 0x80000000) && M1) {
clock = ref * N1 / M1;
clock = clock / post_div;
}
return clock;
}
static int
mcp77_clk_read(struct nvkm_clk *base, enum nv_clk_src src)
{
struct mcp77_clk *clk = mcp77_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
u32 mast = nvkm_rd32(device, 0x00c054);
u32 P = 0;
switch (src) {
case nv_clk_src_crystal:
return device->crystal;
case nv_clk_src_href:
return 100000; /* PCIE reference clock */
case nv_clk_src_hclkm4:
return nvkm_clk_read(&clk->base, nv_clk_src_href) * 4;
case nv_clk_src_hclkm2d3:
return nvkm_clk_read(&clk->base, nv_clk_src_href) * 2 / 3;
case nv_clk_src_host:
switch (mast & 0x000c0000) {
case 0x00000000: return nvkm_clk_read(&clk->base, nv_clk_src_hclkm2d3);
case 0x00040000: break;
case 0x00080000: return nvkm_clk_read(&clk->base, nv_clk_src_hclkm4);
case 0x000c0000: return nvkm_clk_read(&clk->base, nv_clk_src_cclk);
}
break;
case nv_clk_src_core:
P = (nvkm_rd32(device, 0x004028) & 0x00070000) >> 16;
switch (mast & 0x00000003) {
case 0x00000000: return nvkm_clk_read(&clk->base, nv_clk_src_crystal) >> P;
case 0x00000001: return 0;
case 0x00000002: return nvkm_clk_read(&clk->base, nv_clk_src_hclkm4) >> P;
case 0x00000003: return read_pll(clk, 0x004028) >> P;
}
break;
case nv_clk_src_cclk:
if ((mast & 0x03000000) != 0x03000000)
return nvkm_clk_read(&clk->base, nv_clk_src_core);
if ((mast & 0x00000200) == 0x00000000)
return nvkm_clk_read(&clk->base, nv_clk_src_core);
switch (mast & 0x00000c00) {
case 0x00000000: return nvkm_clk_read(&clk->base, nv_clk_src_href);
case 0x00000400: return nvkm_clk_read(&clk->base, nv_clk_src_hclkm4);
case 0x00000800: return nvkm_clk_read(&clk->base, nv_clk_src_hclkm2d3);
default: return 0;
}
case nv_clk_src_shader:
P = (nvkm_rd32(device, 0x004020) & 0x00070000) >> 16;
switch (mast & 0x00000030) {
case 0x00000000:
if (mast & 0x00000040)
return nvkm_clk_read(&clk->base, nv_clk_src_href) >> P;
return nvkm_clk_read(&clk->base, nv_clk_src_crystal) >> P;
case 0x00000010: break;
case 0x00000020: return read_pll(clk, 0x004028) >> P;
case 0x00000030: return read_pll(clk, 0x004020) >> P;
}
break;
case nv_clk_src_mem:
return 0;
case nv_clk_src_vdec:
P = (read_div(clk) & 0x00000700) >> 8;
switch (mast & 0x00400000) {
case 0x00400000:
return nvkm_clk_read(&clk->base, nv_clk_src_core) >> P;
default:
return 500000 >> P;
}
break;
default:
break;
}
nvkm_debug(subdev, "unknown clock source %d %08x\n", src, mast);
return 0;
}
static u32
calc_pll(struct mcp77_clk *clk, u32 reg,
u32 clock, int *N, int *M, int *P)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvbios_pll pll;
int ret;
ret = nvbios_pll_parse(subdev->device->bios, reg, &pll);
if (ret)
return 0;
pll.vco2.max_freq = 0;
pll.refclk = nvkm_clk_read(&clk->base, nv_clk_src_href);
if (!pll.refclk)
return 0;
return nv04_pll_calc(subdev, &pll, clock, N, M, NULL, NULL, P);
}
static inline u32
calc_P(u32 src, u32 target, int *div)
{
u32 clk0 = src, clk1 = src;
for (*div = 0; *div <= 7; (*div)++) {
if (clk0 <= target) {
clk1 = clk0 << (*div ? 1 : 0);
break;
}
clk0 >>= 1;
}
if (target - clk0 <= clk1 - target)
return clk0;
(*div)--;
return clk1;
}
static int
mcp77_clk_calc(struct nvkm_clk *base, struct nvkm_cstate *cstate)
{
struct mcp77_clk *clk = mcp77_clk(base);
const int shader = cstate->domain[nv_clk_src_shader];
const int core = cstate->domain[nv_clk_src_core];
const int vdec = cstate->domain[nv_clk_src_vdec];
struct nvkm_subdev *subdev = &clk->base.subdev;
u32 out = 0, clock = 0;
int N, M, P1, P2 = 0;
int divs = 0;
/* cclk: find suitable source, disable PLL if we can */
if (core < nvkm_clk_read(&clk->base, nv_clk_src_hclkm4))
out = calc_P(nvkm_clk_read(&clk->base, nv_clk_src_hclkm4), core, &divs);
/* Calculate clock * 2, so shader clock can use it too */
clock = calc_pll(clk, 0x4028, (core << 1), &N, &M, &P1);
if (abs(core - out) <= abs(core - (clock >> 1))) {
clk->csrc = nv_clk_src_hclkm4;
clk->cctrl = divs << 16;
} else {
/* NVCTRL is actually used _after_ NVPOST, and after what we
* call NVPLL. To make matters worse, NVPOST is an integer
* divider instead of a right-shift number. */
if(P1 > 2) {
P2 = P1 - 2;
P1 = 2;
}
clk->csrc = nv_clk_src_core;
clk->ccoef = (N << 8) | M;
clk->cctrl = (P2 + 1) << 16;
clk->cpost = (1 << P1) << 16;
}
/* sclk: nvpll + divisor, href or spll */
out = 0;
if (shader == nvkm_clk_read(&clk->base, nv_clk_src_href)) {
clk->ssrc = nv_clk_src_href;
} else {
clock = calc_pll(clk, 0x4020, shader, &N, &M, &P1);
if (clk->csrc == nv_clk_src_core)
out = calc_P((core << 1), shader, &divs);
if (abs(shader - out) <=
abs(shader - clock) &&
(divs + P2) <= 7) {
clk->ssrc = nv_clk_src_core;
clk->sctrl = (divs + P2) << 16;
} else {
clk->ssrc = nv_clk_src_shader;
clk->scoef = (N << 8) | M;
clk->sctrl = P1 << 16;
}
}
/* vclk */
out = calc_P(core, vdec, &divs);
clock = calc_P(500000, vdec, &P1);
if(abs(vdec - out) <= abs(vdec - clock)) {
clk->vsrc = nv_clk_src_cclk;
clk->vdiv = divs << 16;
} else {
clk->vsrc = nv_clk_src_vdec;
clk->vdiv = P1 << 16;
}
/* Print strategy! */
nvkm_debug(subdev, "nvpll: %08x %08x %08x\n",
clk->ccoef, clk->cpost, clk->cctrl);
nvkm_debug(subdev, " spll: %08x %08x %08x\n",
clk->scoef, clk->spost, clk->sctrl);
nvkm_debug(subdev, " vdiv: %08x\n", clk->vdiv);
if (clk->csrc == nv_clk_src_hclkm4)
nvkm_debug(subdev, "core: hrefm4\n");
else
nvkm_debug(subdev, "core: nvpll\n");
if (clk->ssrc == nv_clk_src_hclkm4)
nvkm_debug(subdev, "shader: hrefm4\n");
else if (clk->ssrc == nv_clk_src_core)
nvkm_debug(subdev, "shader: nvpll\n");
else
nvkm_debug(subdev, "shader: spll\n");
if (clk->vsrc == nv_clk_src_hclkm4)
nvkm_debug(subdev, "vdec: 500MHz\n");
else
nvkm_debug(subdev, "vdec: core\n");
return 0;
}
static int
mcp77_clk_prog(struct nvkm_clk *base)
{
struct mcp77_clk *clk = mcp77_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
u32 pllmask = 0, mast;
unsigned long flags;
unsigned long *f = &flags;
int ret = 0;
ret = gt215_clk_pre(&clk->base, f);
if (ret)
goto out;
/* First switch to safe clocks: href */
mast = nvkm_mask(device, 0xc054, 0x03400e70, 0x03400640);
mast &= ~0x00400e73;
mast |= 0x03000000;
switch (clk->csrc) {
case nv_clk_src_hclkm4:
nvkm_mask(device, 0x4028, 0x00070000, clk->cctrl);
mast |= 0x00000002;
break;
case nv_clk_src_core:
nvkm_wr32(device, 0x402c, clk->ccoef);
nvkm_wr32(device, 0x4028, 0x80000000 | clk->cctrl);
nvkm_wr32(device, 0x4040, clk->cpost);
pllmask |= (0x3 << 8);
mast |= 0x00000003;
break;
default:
nvkm_warn(subdev, "Reclocking failed: unknown core clock\n");
goto resume;
}
switch (clk->ssrc) {
case nv_clk_src_href:
nvkm_mask(device, 0x4020, 0x00070000, 0x00000000);
/* mast |= 0x00000000; */
break;
case nv_clk_src_core:
nvkm_mask(device, 0x4020, 0x00070000, clk->sctrl);
mast |= 0x00000020;
break;
case nv_clk_src_shader:
nvkm_wr32(device, 0x4024, clk->scoef);
nvkm_wr32(device, 0x4020, 0x80000000 | clk->sctrl);
nvkm_wr32(device, 0x4070, clk->spost);
pllmask |= (0x3 << 12);
mast |= 0x00000030;
break;
default:
nvkm_warn(subdev, "Reclocking failed: unknown sclk clock\n");
goto resume;
}
if (nvkm_msec(device, 2000,
u32 tmp = nvkm_rd32(device, 0x004080) & pllmask;
if (tmp == pllmask)
break;
) < 0)
goto resume;
switch (clk->vsrc) {
case nv_clk_src_cclk:
mast |= 0x00400000;
fallthrough;
default:
nvkm_wr32(device, 0x4600, clk->vdiv);
}
nvkm_wr32(device, 0xc054, mast);
resume:
/* Disable some PLLs and dividers when unused */
if (clk->csrc != nv_clk_src_core) {
nvkm_wr32(device, 0x4040, 0x00000000);
nvkm_mask(device, 0x4028, 0x80000000, 0x00000000);
}
if (clk->ssrc != nv_clk_src_shader) {
nvkm_wr32(device, 0x4070, 0x00000000);
nvkm_mask(device, 0x4020, 0x80000000, 0x00000000);
}
out:
if (ret == -EBUSY)
f = NULL;
gt215_clk_post(&clk->base, f);
return ret;
}
static void
mcp77_clk_tidy(struct nvkm_clk *base)
{
}
static const struct nvkm_clk_func
mcp77_clk = {
.read = mcp77_clk_read,
.calc = mcp77_clk_calc,
.prog = mcp77_clk_prog,
.tidy = mcp77_clk_tidy,
.domains = {
{ nv_clk_src_crystal, 0xff },
{ nv_clk_src_href , 0xff },
{ nv_clk_src_core , 0xff, 0, "core", 1000 },
{ nv_clk_src_shader , 0xff, 0, "shader", 1000 },
{ nv_clk_src_vdec , 0xff, 0, "vdec", 1000 },
{ nv_clk_src_max }
}
};
int
mcp77_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_clk **pclk)
{
struct mcp77_clk *clk;
if (!(clk = kzalloc_obj(*clk)))
return -ENOMEM;
*pclk = &clk->base;
return nvkm_clk_ctor(&mcp77_clk, device, type, inst, true, &clk->base);
}
@@ -0,0 +1,84 @@
/*
* Copyright 2012 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 "priv.h"
#include "pll.h"
#include <subdev/bios.h>
#include <subdev/bios/pll.h>
#include <subdev/devinit/nv04.h>
int
nv04_clk_pll_calc(struct nvkm_clk *clock, struct nvbios_pll *info,
int clk, struct nvkm_pll_vals *pv)
{
int N1, M1, N2, M2, P;
int ret = nv04_pll_calc(&clock->subdev, info, clk, &N1, &M1, &N2, &M2, &P);
if (ret) {
pv->refclk = info->refclk;
pv->N1 = N1;
pv->M1 = M1;
pv->N2 = N2;
pv->M2 = M2;
pv->log2P = P;
}
return ret;
}
int
nv04_clk_pll_prog(struct nvkm_clk *clk, u32 reg1, struct nvkm_pll_vals *pv)
{
struct nvkm_device *device = clk->subdev.device;
struct nvkm_devinit *devinit = device->devinit;
int cv = device->bios->version.chip;
if (cv == 0x30 || cv == 0x31 || cv == 0x35 || cv == 0x36 ||
cv >= 0x40) {
if (reg1 > 0x405c)
setPLL_double_highregs(devinit, reg1, pv);
else
setPLL_double_lowregs(devinit, reg1, pv);
} else
setPLL_single(devinit, reg1, pv);
return 0;
}
static const struct nvkm_clk_func
nv04_clk = {
.domains = {
{ nv_clk_src_max }
}
};
int
nv04_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_clk **pclk)
{
int ret = nvkm_clk_new_(&nv04_clk, device, type, inst, false, pclk);
if (ret == 0) {
(*pclk)->pll_calc = nv04_clk_pll_calc;
(*pclk)->pll_prog = nv04_clk_pll_prog;
}
return ret;
}
@@ -0,0 +1,233 @@
/*
* Copyright 2012 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
*/
#define nv40_clk(p) container_of((p), struct nv40_clk, base)
#include "priv.h"
#include "pll.h"
#include <subdev/bios.h>
#include <subdev/bios/pll.h>
struct nv40_clk {
struct nvkm_clk base;
u32 ctrl;
u32 npll_ctrl;
u32 npll_coef;
u32 spll;
};
static u32
read_pll_1(struct nv40_clk *clk, u32 reg)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 ctrl = nvkm_rd32(device, reg + 0x00);
int P = (ctrl & 0x00070000) >> 16;
int N = (ctrl & 0x0000ff00) >> 8;
int M = (ctrl & 0x000000ff) >> 0;
u32 ref = 27000, khz = 0;
if (ctrl & 0x80000000)
khz = ref * N / M;
return khz >> P;
}
static u32
read_pll_2(struct nv40_clk *clk, u32 reg)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 ctrl = nvkm_rd32(device, reg + 0x00);
u32 coef = nvkm_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, khz = 0;
if ((ctrl & 0x80000000) && M1) {
khz = ref * N1 / M1;
if ((ctrl & 0x40000100) == 0x40000000) {
if (M2)
khz = khz * N2 / M2;
else
khz = 0;
}
}
return khz >> P;
}
static u32
read_clk(struct nv40_clk *clk, u32 src)
{
switch (src) {
case 3:
return read_pll_2(clk, 0x004000);
case 2:
return read_pll_1(clk, 0x004008);
default:
break;
}
return 0;
}
static int
nv40_clk_read(struct nvkm_clk *base, enum nv_clk_src src)
{
struct nv40_clk *clk = nv40_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
u32 mast = nvkm_rd32(device, 0x00c040);
switch (src) {
case nv_clk_src_crystal:
return device->crystal;
case nv_clk_src_href:
return 100000; /*XXX: PCIE/AGP differ*/
case nv_clk_src_core:
return read_clk(clk, (mast & 0x00000003) >> 0);
case nv_clk_src_shader:
return read_clk(clk, (mast & 0x00000030) >> 4);
case nv_clk_src_mem:
return read_pll_2(clk, 0x4020);
default:
break;
}
nvkm_debug(subdev, "unknown clock source %d %08x\n", src, mast);
return -EINVAL;
}
static int
nv40_clk_calc_pll(struct nv40_clk *clk, u32 reg, u32 khz,
int *N1, int *M1, int *N2, int *M2, int *log2P)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvbios_pll pll;
int ret;
ret = nvbios_pll_parse(subdev->device->bios, reg, &pll);
if (ret)
return ret;
if (khz < pll.vco1.max_freq)
pll.vco2.max_freq = 0;
ret = nv04_pll_calc(subdev, &pll, khz, N1, M1, N2, M2, log2P);
if (ret == 0)
return -ERANGE;
return ret;
}
static int
nv40_clk_calc(struct nvkm_clk *base, struct nvkm_cstate *cstate)
{
struct nv40_clk *clk = nv40_clk(base);
int gclk = cstate->domain[nv_clk_src_core];
int sclk = cstate->domain[nv_clk_src_shader];
int N1, M1, N2, M2, log2P;
int ret;
/* core/geometric clock */
ret = nv40_clk_calc_pll(clk, 0x004000, gclk,
&N1, &M1, &N2, &M2, &log2P);
if (ret < 0)
return ret;
if (N2 == M2) {
clk->npll_ctrl = 0x80000100 | (log2P << 16);
clk->npll_coef = (N1 << 8) | M1;
} else {
clk->npll_ctrl = 0xc0000000 | (log2P << 16);
clk->npll_coef = (N2 << 24) | (M2 << 16) | (N1 << 8) | M1;
}
/* use the second pll for shader/rop clock, if it differs from core */
if (sclk && sclk != gclk) {
ret = nv40_clk_calc_pll(clk, 0x004008, sclk,
&N1, &M1, NULL, NULL, &log2P);
if (ret < 0)
return ret;
clk->spll = 0xc0000000 | (log2P << 16) | (N1 << 8) | M1;
clk->ctrl = 0x00000223;
} else {
clk->spll = 0x00000000;
clk->ctrl = 0x00000333;
}
return 0;
}
static int
nv40_clk_prog(struct nvkm_clk *base)
{
struct nv40_clk *clk = nv40_clk(base);
struct nvkm_device *device = clk->base.subdev.device;
nvkm_mask(device, 0x00c040, 0x00000333, 0x00000000);
nvkm_wr32(device, 0x004004, clk->npll_coef);
nvkm_mask(device, 0x004000, 0xc0070100, clk->npll_ctrl);
nvkm_mask(device, 0x004008, 0xc007ffff, clk->spll);
mdelay(5);
nvkm_mask(device, 0x00c040, 0x00000333, clk->ctrl);
return 0;
}
static void
nv40_clk_tidy(struct nvkm_clk *obj)
{
}
static const struct nvkm_clk_func
nv40_clk = {
.read = nv40_clk_read,
.calc = nv40_clk_calc,
.prog = nv40_clk_prog,
.tidy = nv40_clk_tidy,
.domains = {
{ nv_clk_src_crystal, 0xff },
{ nv_clk_src_href , 0xff },
{ nv_clk_src_core , 0xff, 0, "core", 1000 },
{ nv_clk_src_shader , 0xff, 0, "shader", 1000 },
{ nv_clk_src_mem , 0xff, 0, "memory", 1000 },
{ nv_clk_src_max }
}
};
int
nv40_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_clk **pclk)
{
struct nv40_clk *clk;
if (!(clk = kzalloc_obj(*clk)))
return -ENOMEM;
clk->base.pll_calc = nv04_clk_pll_calc;
clk->base.pll_prog = nv04_clk_pll_prog;
*pclk = &clk->base;
return nvkm_clk_ctor(&nv40_clk, device, type, inst, true, &clk->base);
}
@@ -0,0 +1,563 @@
/*
* Copyright 2012 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 "nv50.h"
#include "pll.h"
#include "seq.h"
#include <subdev/bios.h>
#include <subdev/bios/pll.h>
static u32
read_div(struct nv50_clk *clk)
{
struct nvkm_device *device = clk->base.subdev.device;
switch (device->chipset) {
case 0x50: /* it exists, but only has bit 31, not the dividers.. */
case 0x84:
case 0x86:
case 0x98:
case 0xa0:
return nvkm_rd32(device, 0x004700);
case 0x92:
case 0x94:
case 0x96:
return nvkm_rd32(device, 0x004800);
default:
return 0x00000000;
}
}
static u32
read_pll_src(struct nv50_clk *clk, u32 base)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
u32 coef, ref = nvkm_clk_read(&clk->base, nv_clk_src_crystal);
u32 rsel = nvkm_rd32(device, 0x00e18c);
int P, N, M, id;
switch (device->chipset) {
case 0x50:
case 0xa0:
switch (base) {
case 0x4020:
case 0x4028: id = !!(rsel & 0x00000004); break;
case 0x4008: id = !!(rsel & 0x00000008); break;
case 0x4030: id = 0; break;
default:
nvkm_error(subdev, "ref: bad pll %06x\n", base);
return 0;
}
coef = nvkm_rd32(device, 0x00e81c + (id * 0x0c));
ref *= (coef & 0x01000000) ? 2 : 4;
P = (coef & 0x00070000) >> 16;
N = ((coef & 0x0000ff00) >> 8) + 1;
M = ((coef & 0x000000ff) >> 0) + 1;
break;
case 0x84:
case 0x86:
case 0x92:
coef = nvkm_rd32(device, 0x00e81c);
P = (coef & 0x00070000) >> 16;
N = (coef & 0x0000ff00) >> 8;
M = (coef & 0x000000ff) >> 0;
break;
case 0x94:
case 0x96:
case 0x98:
rsel = nvkm_rd32(device, 0x00c050);
switch (base) {
case 0x4020: rsel = (rsel & 0x00000003) >> 0; break;
case 0x4008: rsel = (rsel & 0x0000000c) >> 2; break;
case 0x4028: rsel = (rsel & 0x00001800) >> 11; break;
case 0x4030: rsel = 3; break;
default:
nvkm_error(subdev, "ref: bad pll %06x\n", base);
return 0;
}
switch (rsel) {
case 0: id = 1; break;
case 1: return nvkm_clk_read(&clk->base, nv_clk_src_crystal);
case 2: return nvkm_clk_read(&clk->base, nv_clk_src_href);
case 3: id = 0; break;
}
coef = nvkm_rd32(device, 0x00e81c + (id * 0x28));
P = (nvkm_rd32(device, 0x00e824 + (id * 0x28)) >> 16) & 7;
P += (coef & 0x00070000) >> 16;
N = (coef & 0x0000ff00) >> 8;
M = (coef & 0x000000ff) >> 0;
break;
default:
BUG();
}
if (M)
return (ref * N / M) >> P;
return 0;
}
static u32
read_pll_ref(struct nv50_clk *clk, u32 base)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
u32 src, mast = nvkm_rd32(device, 0x00c040);
switch (base) {
case 0x004028:
src = !!(mast & 0x00200000);
break;
case 0x004020:
src = !!(mast & 0x00400000);
break;
case 0x004008:
src = !!(mast & 0x00010000);
break;
case 0x004030:
src = !!(mast & 0x02000000);
break;
case 0x00e810:
return nvkm_clk_read(&clk->base, nv_clk_src_crystal);
default:
nvkm_error(subdev, "bad pll %06x\n", base);
return 0;
}
if (src)
return nvkm_clk_read(&clk->base, nv_clk_src_href);
return read_pll_src(clk, base);
}
static u32
read_pll(struct nv50_clk *clk, u32 base)
{
struct nvkm_device *device = clk->base.subdev.device;
u32 mast = nvkm_rd32(device, 0x00c040);
u32 ctrl = nvkm_rd32(device, base + 0);
u32 coef = nvkm_rd32(device, base + 4);
u32 ref = read_pll_ref(clk, base);
u32 freq = 0;
int N1, N2, M1, M2;
if (base == 0x004028 && (mast & 0x00100000)) {
/* wtf, appears to only disable post-divider on gt200 */
if (device->chipset != 0xa0)
return nvkm_clk_read(&clk->base, nv_clk_src_dom6);
}
N2 = (coef & 0xff000000) >> 24;
M2 = (coef & 0x00ff0000) >> 16;
N1 = (coef & 0x0000ff00) >> 8;
M1 = (coef & 0x000000ff);
if ((ctrl & 0x80000000) && M1) {
freq = ref * N1 / M1;
if ((ctrl & 0x40000100) == 0x40000000) {
if (M2)
freq = freq * N2 / M2;
else
freq = 0;
}
}
return freq;
}
int
nv50_clk_read(struct nvkm_clk *base, enum nv_clk_src src)
{
struct nv50_clk *clk = nv50_clk(base);
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
u32 mast = nvkm_rd32(device, 0x00c040);
u32 P = 0;
switch (src) {
case nv_clk_src_crystal:
return device->crystal;
case nv_clk_src_href:
return 100000; /* PCIE reference clock */
case nv_clk_src_hclk:
return div_u64((u64)nvkm_clk_read(&clk->base, nv_clk_src_href) * 27778, 10000);
case nv_clk_src_hclkm3:
return nvkm_clk_read(&clk->base, nv_clk_src_hclk) * 3;
case nv_clk_src_hclkm3d2:
return nvkm_clk_read(&clk->base, nv_clk_src_hclk) * 3 / 2;
case nv_clk_src_host:
switch (mast & 0x30000000) {
case 0x00000000: return nvkm_clk_read(&clk->base, nv_clk_src_href);
case 0x10000000: break;
case 0x20000000: /* !0x50 */
case 0x30000000: return nvkm_clk_read(&clk->base, nv_clk_src_hclk);
}
break;
case nv_clk_src_core:
if (!(mast & 0x00100000))
P = (nvkm_rd32(device, 0x004028) & 0x00070000) >> 16;
switch (mast & 0x00000003) {
case 0x00000000: return nvkm_clk_read(&clk->base, nv_clk_src_crystal) >> P;
case 0x00000001: return nvkm_clk_read(&clk->base, nv_clk_src_dom6);
case 0x00000002: return read_pll(clk, 0x004020) >> P;
case 0x00000003: return read_pll(clk, 0x004028) >> P;
}
break;
case nv_clk_src_shader:
P = (nvkm_rd32(device, 0x004020) & 0x00070000) >> 16;
switch (mast & 0x00000030) {
case 0x00000000:
if (mast & 0x00000080)
return nvkm_clk_read(&clk->base, nv_clk_src_host) >> P;
return nvkm_clk_read(&clk->base, nv_clk_src_crystal) >> P;
case 0x00000010: break;
case 0x00000020: return read_pll(clk, 0x004028) >> P;
case 0x00000030: return read_pll(clk, 0x004020) >> P;
}
break;
case nv_clk_src_mem:
P = (nvkm_rd32(device, 0x004008) & 0x00070000) >> 16;
if (nvkm_rd32(device, 0x004008) & 0x00000200) {
switch (mast & 0x0000c000) {
case 0x00000000:
return nvkm_clk_read(&clk->base, nv_clk_src_crystal) >> P;
case 0x00008000:
case 0x0000c000:
return nvkm_clk_read(&clk->base, nv_clk_src_href) >> P;
}
} else {
return read_pll(clk, 0x004008) >> P;
}
break;
case nv_clk_src_vdec:
P = (read_div(clk) & 0x00000700) >> 8;
switch (device->chipset) {
case 0x84:
case 0x86:
case 0x92:
case 0x94:
case 0x96:
case 0xa0:
switch (mast & 0x00000c00) {
case 0x00000000:
if (device->chipset == 0xa0) /* wtf?? */
return nvkm_clk_read(&clk->base, nv_clk_src_core) >> P;
return nvkm_clk_read(&clk->base, nv_clk_src_crystal) >> P;
case 0x00000400:
return 0;
case 0x00000800:
if (mast & 0x01000000)
return read_pll(clk, 0x004028) >> P;
return read_pll(clk, 0x004030) >> P;
case 0x00000c00:
return nvkm_clk_read(&clk->base, nv_clk_src_core) >> P;
}
break;
case 0x98:
switch (mast & 0x00000c00) {
case 0x00000000:
return nvkm_clk_read(&clk->base, nv_clk_src_core) >> P;
case 0x00000400:
return 0;
case 0x00000800:
return nvkm_clk_read(&clk->base, nv_clk_src_hclkm3d2) >> P;
case 0x00000c00:
return nvkm_clk_read(&clk->base, nv_clk_src_mem) >> P;
}
break;
}
break;
case nv_clk_src_dom6:
switch (device->chipset) {
case 0x50:
case 0xa0:
return read_pll(clk, 0x00e810) >> 2;
case 0x84:
case 0x86:
case 0x92:
case 0x94:
case 0x96:
case 0x98:
P = (read_div(clk) & 0x00000007) >> 0;
switch (mast & 0x0c000000) {
case 0x00000000: return nvkm_clk_read(&clk->base, nv_clk_src_href);
case 0x04000000: break;
case 0x08000000: return nvkm_clk_read(&clk->base, nv_clk_src_hclk);
case 0x0c000000:
return nvkm_clk_read(&clk->base, nv_clk_src_hclkm3) >> P;
}
break;
default:
break;
}
break;
default:
break;
}
nvkm_debug(subdev, "unknown clock source %d %08x\n", src, mast);
return -EINVAL;
}
static u32
calc_pll(struct nv50_clk *clk, u32 reg, u32 idx, int *N, int *M, int *P)
{
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvbios_pll pll;
int ret;
ret = nvbios_pll_parse(subdev->device->bios, reg, &pll);
if (ret)
return 0;
pll.vco2.max_freq = 0;
pll.refclk = read_pll_ref(clk, reg);
if (!pll.refclk)
return 0;
return nv04_pll_calc(subdev, &pll, idx, N, M, NULL, NULL, P);
}
static inline u32
calc_div(u32 src, u32 target, int *div)
{
u32 clk0 = src, clk1 = src;
for (*div = 0; *div <= 7; (*div)++) {
if (clk0 <= target) {
clk1 = clk0 << (*div ? 1 : 0);
break;
}
clk0 >>= 1;
}
if (target - clk0 <= clk1 - target)
return clk0;
(*div)--;
return clk1;
}
static inline u32
clk_same(u32 a, u32 b)
{
return ((a / 1000) == (b / 1000));
}
int
nv50_clk_calc(struct nvkm_clk *base, struct nvkm_cstate *cstate)
{
struct nv50_clk *clk = nv50_clk(base);
struct nv50_clk_hwsq *hwsq = &clk->hwsq;
struct nvkm_subdev *subdev = &clk->base.subdev;
struct nvkm_device *device = subdev->device;
const int shader = cstate->domain[nv_clk_src_shader];
const int core = cstate->domain[nv_clk_src_core];
const int vdec = cstate->domain[nv_clk_src_vdec];
const int dom6 = cstate->domain[nv_clk_src_dom6];
u32 mastm = 0, mastv = 0;
u32 divsm = 0, divsv = 0;
int N, M, P1, P2;
int freq, out;
/* prepare a hwsq script from which we'll perform the reclock */
out = clk_init(hwsq, subdev);
if (out)
return out;
clk_wr32(hwsq, fifo, 0x00000001); /* block fifo */
clk_nsec(hwsq, 8000);
clk_setf(hwsq, 0x10, 0x00); /* disable fb */
clk_wait(hwsq, 0x00, 0x01); /* wait for fb disabled */
/* 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...
*/
if (vdec) {
/* see how close we can get using nvclk as a source */
freq = calc_div(core, vdec, &P1);
/* see how close we can get using xpll/hclk as a source */
if (device->chipset != 0x98)
out = read_pll(clk, 0x004030);
else
out = nvkm_clk_read(&clk->base, nv_clk_src_hclkm3d2);
out = calc_div(out, vdec, &P2);
/* select whichever gets us closest */
if (abs(vdec - freq) <= abs(vdec - out)) {
if (device->chipset != 0x98)
mastv |= 0x00000c00;
divsv |= P1 << 8;
} else {
mastv |= 0x00000800;
divsv |= P2 << 8;
}
mastm |= 0x00000c00;
divsm |= 0x00000700;
}
/* dom6: nfi what this is, but we're limited to various combinations
* of the host clock frequency
*/
if (dom6) {
if (clk_same(dom6, nvkm_clk_read(&clk->base, nv_clk_src_href))) {
mastv |= 0x00000000;
} else
if (clk_same(dom6, nvkm_clk_read(&clk->base, nv_clk_src_hclk))) {
mastv |= 0x08000000;
} else {
freq = nvkm_clk_read(&clk->base, nv_clk_src_hclk) * 3;
calc_div(freq, dom6, &P1);
mastv |= 0x0c000000;
divsv |= P1;
}
mastm |= 0x0c000000;
divsm |= 0x00000007;
}
/* vdec/dom6: switch to "safe" clocks temporarily, update dividers
* and then switch to target clocks
*/
clk_mask(hwsq, mast, mastm, 0x00000000);
clk_mask(hwsq, divs, divsm, divsv);
clk_mask(hwsq, mast, mastm, mastv);
/* core/shader: disconnect nvclk/sclk from their PLLs (nvclk to dom6,
* sclk to hclk) before reprogramming
*/
if (device->chipset < 0x92)
clk_mask(hwsq, mast, 0x001000b0, 0x00100080);
else
clk_mask(hwsq, mast, 0x000000b3, 0x00000081);
/* core: for the moment at least, always use nvpll */
freq = calc_pll(clk, 0x4028, core, &N, &M, &P1);
if (freq == 0)
return -ERANGE;
clk_mask(hwsq, nvpll[0], 0xc03f0100,
0x80000000 | (P1 << 19) | (P1 << 16));
clk_mask(hwsq, nvpll[1], 0x0000ffff, (N << 8) | M);
/* shader: tie to nvclk if possible, otherwise use spll. have to be
* very careful that the shader clock is at least twice the core, or
* some chipsets will be very unhappy. i expect most or all of these
* cases will be handled by tying to nvclk, but it's possible there's
* corners
*/
if (P1-- && shader == (core << 1)) {
clk_mask(hwsq, spll[0], 0xc03f0100, (P1 << 19) | (P1 << 16));
clk_mask(hwsq, mast, 0x00100033, 0x00000023);
} else {
freq = calc_pll(clk, 0x4020, shader, &N, &M, &P1);
if (freq == 0)
return -ERANGE;
clk_mask(hwsq, spll[0], 0xc03f0100,
0x80000000 | (P1 << 19) | (P1 << 16));
clk_mask(hwsq, spll[1], 0x0000ffff, (N << 8) | M);
clk_mask(hwsq, mast, 0x00100033, 0x00000033);
}
/* restore normal operation */
clk_setf(hwsq, 0x10, 0x01); /* enable fb */
clk_wait(hwsq, 0x00, 0x00); /* wait for fb enabled */
clk_wr32(hwsq, fifo, 0x00000000); /* un-block fifo */
return 0;
}
int
nv50_clk_prog(struct nvkm_clk *base)
{
struct nv50_clk *clk = nv50_clk(base);
return clk_exec(&clk->hwsq, true);
}
void
nv50_clk_tidy(struct nvkm_clk *base)
{
struct nv50_clk *clk = nv50_clk(base);
clk_exec(&clk->hwsq, false);
}
int
nv50_clk_new_(const struct nvkm_clk_func *func, struct nvkm_device *device,
enum nvkm_subdev_type type, int inst, bool allow_reclock, struct nvkm_clk **pclk)
{
struct nv50_clk *clk;
int ret;
if (!(clk = kzalloc_obj(*clk)))
return -ENOMEM;
ret = nvkm_clk_ctor(func, device, type, inst, allow_reclock, &clk->base);
*pclk = &clk->base;
if (ret)
return ret;
clk->hwsq.r_fifo = hwsq_reg(0x002504);
clk->hwsq.r_spll[0] = hwsq_reg(0x004020);
clk->hwsq.r_spll[1] = hwsq_reg(0x004024);
clk->hwsq.r_nvpll[0] = hwsq_reg(0x004028);
clk->hwsq.r_nvpll[1] = hwsq_reg(0x00402c);
switch (device->chipset) {
case 0x92:
case 0x94:
case 0x96:
clk->hwsq.r_divs = hwsq_reg(0x004800);
break;
default:
clk->hwsq.r_divs = hwsq_reg(0x004700);
break;
}
clk->hwsq.r_mast = hwsq_reg(0x00c040);
return 0;
}
static const struct nvkm_clk_func
nv50_clk = {
.read = nv50_clk_read,
.calc = nv50_clk_calc,
.prog = nv50_clk_prog,
.tidy = nv50_clk_tidy,
.domains = {
{ nv_clk_src_crystal, 0xff },
{ nv_clk_src_href , 0xff },
{ nv_clk_src_core , 0xff, 0, "core", 1000 },
{ nv_clk_src_shader , 0xff, 0, "shader", 1000 },
{ nv_clk_src_mem , 0xff, 0, "memory", 1000 },
{ nv_clk_src_max }
}
};
int
nv50_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_clk **pclk)
{
return nv50_clk_new_(&nv50_clk, device, type, inst, false, pclk);
}
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NV50_CLK_H__
#define __NV50_CLK_H__
#define nv50_clk(p) container_of((p), struct nv50_clk, base)
#include "priv.h"
#include <subdev/bus/hwsq.h>
struct nv50_clk_hwsq {
struct hwsq base;
struct hwsq_reg r_fifo;
struct hwsq_reg r_spll[2];
struct hwsq_reg r_nvpll[2];
struct hwsq_reg r_divs;
struct hwsq_reg r_mast;
};
struct nv50_clk {
struct nvkm_clk base;
struct nv50_clk_hwsq hwsq;
};
int nv50_clk_new_(const struct nvkm_clk_func *, struct nvkm_device *, enum nvkm_subdev_type, int,
bool, struct nvkm_clk **);
int nv50_clk_read(struct nvkm_clk *, enum nv_clk_src);
int nv50_clk_calc(struct nvkm_clk *, struct nvkm_cstate *);
int nv50_clk_prog(struct nvkm_clk *);
void nv50_clk_tidy(struct nvkm_clk *);
#endif
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_PLL_H__
#define __NVKM_PLL_H__
#include <core/os.h>
struct nvkm_subdev;
struct nvbios_pll;
int nv04_pll_calc(struct nvkm_subdev *, struct nvbios_pll *, u32 freq,
int *N1, int *M1, int *N2, int *M2, int *P);
int gt215_pll_calc(struct nvkm_subdev *, struct nvbios_pll *, u32 freq,
int *N, int *fN, int *M, int *P);
#endif
@@ -0,0 +1,87 @@
/*
* 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 "pll.h"
#include <subdev/bios.h>
#include <subdev/bios/pll.h>
int
gt215_pll_calc(struct nvkm_subdev *subdev, struct nvbios_pll *info,
u32 freq, int *pN, int *pfN, int *pM, int *P)
{
u32 best_err = ~0, err;
int M, lM, hM, N, fN;
*P = info->vco1.max_freq / freq;
if (*P > info->max_p)
*P = info->max_p;
if (*P < info->min_p)
*P = info->min_p;
lM = (info->refclk + info->vco1.max_inputfreq) / info->vco1.max_inputfreq;
lM = max(lM, (int)info->vco1.min_m);
hM = (info->refclk + info->vco1.min_inputfreq) / info->vco1.min_inputfreq;
hM = min(hM, (int)info->vco1.max_m);
lM = min(lM, hM);
for (M = lM; M <= hM; M++) {
u32 tmp = freq * *P * M;
N = tmp / info->refclk;
fN = tmp % info->refclk;
if (!pfN) {
if (fN >= info->refclk / 2)
N++;
} else {
if (fN < info->refclk / 2)
N--;
fN = tmp - (N * info->refclk);
}
if (N < info->vco1.min_n)
continue;
if (N > info->vco1.max_n)
break;
err = abs(freq - (info->refclk * N / M / *P));
if (err < best_err) {
best_err = err;
*pN = N;
*pM = M;
}
if (pfN) {
*pfN = ((fN << 13) + info->refclk / 2) / info->refclk;
*pfN = (*pfN - 4096) & 0xffff;
return freq;
}
}
if (unlikely(best_err == ~0)) {
nvkm_error(subdev, "unable to find matching pll values\n");
return -EINVAL;
}
return info->refclk * *pN / *pM / *P;
}
@@ -0,0 +1,245 @@
/*
* 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 "pll.h"
#include <subdev/bios.h>
#include <subdev/bios/pll.h>
static int
getMNP_single(struct nvkm_subdev *subdev, struct nvbios_pll *info, int clk,
int *pN, int *pM, int *pP)
{
/* 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 nvkm_bios *bios = subdev->device->bios;
int minvco = info->vco1.min_freq, maxvco = info->vco1.max_freq;
int minM = info->vco1.min_m, maxM = info->vco1.max_m;
int minN = info->vco1.min_n, maxN = info->vco1.max_n;
int minU = info->vco1.min_inputfreq;
int maxU = info->vco1.max_inputfreq;
int minP = info->min_p;
int maxP = info->max_p_usable;
int crystal = info->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 (bios->version.major < 0x60) {
int cv = bios->version.chip;
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 = 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 = 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;
*pN = N;
*pM = M;
*pP = thisP;
if (delta == 0) /* except this one */
return bestclk;
}
}
}
return bestclk;
}
static int
getMNP_double(struct nvkm_subdev *subdev, struct nvbios_pll *info, int clk,
int *pN1, int *pM1, int *pN2, int *pM2, int *pP)
{
/* 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
*/
int chip_version = subdev->device->bios->version.chip;
int minvco1 = info->vco1.min_freq, maxvco1 = info->vco1.max_freq;
int minvco2 = info->vco2.min_freq, maxvco2 = info->vco2.max_freq;
int minU1 = info->vco1.min_inputfreq, minU2 = info->vco2.min_inputfreq;
int maxU1 = info->vco1.max_inputfreq, maxU2 = info->vco2.max_inputfreq;
int minM1 = info->vco1.min_m, maxM1 = info->vco1.max_m;
int minN1 = info->vco1.min_n, maxN1 = info->vco1.max_n;
int minM2 = info->vco2.min_m, maxM2 = info->vco2.max_m;
int minN2 = info->vco2.min_n, maxN2 = info->vco2.max_n;
int maxlog2P = info->max_p_usable;
int crystal = info->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;
*pN1 = N1;
*pM1 = M1;
*pN2 = N2;
*pM2 = M2;
*pP = log2P;
if (delta == 0) /* except this one */
return bestclk;
}
}
}
}
return bestclk;
}
int
nv04_pll_calc(struct nvkm_subdev *subdev, struct nvbios_pll *info, u32 freq,
int *N1, int *M1, int *N2, int *M2, int *P)
{
int ret;
if (!info->vco2.max_freq || !N2) {
ret = getMNP_single(subdev, info, freq, N1, M1, P);
if (N2) {
*N2 = 1;
*M2 = 1;
}
} else {
ret = getMNP_double(subdev, info, freq, N1, M1, N2, M2, P);
}
if (!ret)
nvkm_error(subdev, "unable to compute acceptable pll values\n");
return ret;
}
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_CLK_PRIV_H__
#define __NVKM_CLK_PRIV_H__
#define nvkm_clk(p) container_of((p), struct nvkm_clk, subdev)
#include <subdev/clk.h>
struct nvkm_clk_func {
int (*init)(struct nvkm_clk *);
void (*fini)(struct nvkm_clk *);
int (*read)(struct nvkm_clk *, enum nv_clk_src);
int (*calc)(struct nvkm_clk *, struct nvkm_cstate *);
int (*prog)(struct nvkm_clk *);
void (*tidy)(struct nvkm_clk *);
struct nvkm_pstate *pstates;
int nr_pstates;
struct nvkm_domain domains[];
};
int nvkm_clk_ctor(const struct nvkm_clk_func *, struct nvkm_device *, enum nvkm_subdev_type, int,
bool allow_reclock, struct nvkm_clk *);
int nvkm_clk_new_(const struct nvkm_clk_func *, struct nvkm_device *, enum nvkm_subdev_type, int,
bool allow_reclock, struct nvkm_clk **);
int nv04_clk_pll_calc(struct nvkm_clk *, struct nvbios_pll *, int clk,
struct nvkm_pll_vals *);
int nv04_clk_pll_prog(struct nvkm_clk *, u32 reg1, struct nvkm_pll_vals *);
#endif
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: MIT */
#ifndef __NVKM_CLK_SEQ_H__
#define __NVKM_CLK_SEQ_H__
#include <subdev/bus/hwsq.h>
#define clk_init(s,p) hwsq_init(&(s)->base, (p))
#define clk_exec(s,e) hwsq_exec(&(s)->base, (e))
#define clk_have(s,r) ((s)->r_##r.addr != 0x000000)
#define clk_rd32(s,r) hwsq_rd32(&(s)->base, &(s)->r_##r)
#define clk_wr32(s,r,d) hwsq_wr32(&(s)->base, &(s)->r_##r, (d))
#define clk_mask(s,r,m,d) hwsq_mask(&(s)->base, &(s)->r_##r, (m), (d))
#define clk_setf(s,f,d) hwsq_setf(&(s)->base, (f), (d))
#define clk_wait(s,f,d) hwsq_wait(&(s)->base, (f), (d))
#define clk_nsec(s,n) hwsq_nsec(&(s)->base, (n))
#endif