diff options
author | Yunhong Jiang <yunhong.jiang@intel.com> | 2015-08-04 12:17:53 -0700 |
---|---|---|
committer | Yunhong Jiang <yunhong.jiang@intel.com> | 2015-08-04 15:44:42 -0700 |
commit | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (patch) | |
tree | 1c9cafbcd35f783a87880a10f85d1a060db1a563 /kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk | |
parent | 98260f3884f4a202f9ca5eabed40b1354c489b29 (diff) |
Add the rt linux 4.1.3-rt3 as base
Import the rt linux 4.1.3-rt3 as OPNFV kvm base.
It's from git://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-rt-devel.git linux-4.1.y-rt and
the base is:
commit 0917f823c59692d751951bf5ea699a2d1e2f26a2
Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Date: Sat Jul 25 12:13:34 2015 +0200
Prepare v4.1.3-rt3
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
We lose all the git history this way and it's not good. We
should apply another opnfv project repo in future.
Change-Id: I87543d81c9df70d99c5001fbdf646b202c19f423
Signed-off-by: Yunhong Jiang <yunhong.jiang@intel.com>
Diffstat (limited to 'kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk')
17 files changed, 4562 insertions, 0 deletions
diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/Kbuild b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/Kbuild new file mode 100644 index 000000000..9c2f688c9 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/Kbuild @@ -0,0 +1,12 @@ +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/pllnv04.o +nvkm-y += nvkm/subdev/clk/pllgt215.o diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c new file mode 100644 index 000000000..39a83d82e --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c @@ -0,0 +1,591 @@ +/* + * 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 <subdev/clk.h> +#include <subdev/bios.h> +#include <subdev/bios/boost.h> +#include <subdev/bios/cstep.h> +#include <subdev/bios/perf.h> +#include <subdev/fb.h> +#include <subdev/therm.h> +#include <subdev/volt.h> + +#include <core/device.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 = nvkm_bios(clk); + struct nvbios_boostE boostE; + u8 ver, hdr, cnt, len; + u16 data; + + data = nvbios_boostEm(bios, pstate, &ver, &hdr, &cnt, &len, &boostE); + if (data) { + struct nvbios_boostS boostS; + u8 idx = 0, sver, shdr; + u16 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 int +nvkm_cstate_prog(struct nvkm_clk *clk, struct nvkm_pstate *pstate, int cstatei) +{ + struct nvkm_therm *ptherm = nvkm_therm(clk); + struct nvkm_volt *volt = nvkm_volt(clk); + struct nvkm_cstate *cstate; + int ret; + + if (!list_empty(&pstate->list)) { + cstate = list_entry(pstate->list.prev, typeof(*cstate), head); + } else { + cstate = &pstate->base; + } + + if (ptherm) { + ret = nvkm_therm_cstate(ptherm, pstate->fanspeed, +1); + if (ret && ret != -ENODEV) { + nv_error(clk, "failed to raise fan speed: %d\n", ret); + return ret; + } + } + + if (volt) { + ret = volt->set_id(volt, cstate->voltage, +1); + if (ret && ret != -ENODEV) { + nv_error(clk, "failed to raise voltage: %d\n", ret); + return ret; + } + } + + ret = clk->calc(clk, cstate); + if (ret == 0) { + ret = clk->prog(clk); + clk->tidy(clk); + } + + if (volt) { + ret = volt->set_id(volt, cstate->voltage, -1); + if (ret && ret != -ENODEV) + nv_error(clk, "failed to lower voltage: %d\n", ret); + } + + if (ptherm) { + ret = nvkm_therm_cstate(ptherm, pstate->fanspeed, -1); + if (ret && ret != -ENODEV) + nv_error(clk, "failed to lower fan speed: %d\n", ret); + } + + return 0; +} + +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 = nvkm_bios(clk); + struct nvkm_domain *domain = clk->domains; + struct nvkm_cstate *cstate = NULL; + struct nvbios_cstepX cstepX; + u8 ver, hdr; + u16 data; + + data = nvbios_cstepXp(bios, idx, &ver, &hdr, &cstepX); + if (!data) + return -ENOENT; + + cstate = kzalloc(sizeof(*cstate), GFP_KERNEL); + if (!cstate) + return -ENOMEM; + + *cstate = pstate->base; + cstate->voltage = cstepX.voltage; + + 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_fb *pfb = nvkm_fb(clk); + struct nvkm_pstate *pstate; + int ret, idx = 0; + + list_for_each_entry(pstate, &clk->states, head) { + if (idx++ == pstatei) + break; + } + + nv_debug(clk, "setting performance state %d\n", pstatei); + clk->pstate = pstatei; + + if (pfb->ram && pfb->ram->calc) { + int khz = pstate->base.domain[nv_clk_src_mem]; + do { + ret = pfb->ram->calc(pfb, khz); + if (ret == 0) + ret = pfb->ram->prog(pfb); + } while (ret > 0); + pfb->ram->tidy(pfb); + } + + return nvkm_cstate_prog(clk, pstate, 0); +} + +static void +nvkm_pstate_work(struct work_struct *work) +{ + struct nvkm_clk *clk = container_of(work, typeof(*clk), work); + int pstate; + + if (!atomic_xchg(&clk->waiting, 0)) + return; + clk->pwrsrc = power_supply_is_system_supplied(); + + nv_trace(clk, "P %d PWR %d U(AC) %d U(DC) %d A %d T %d D %d\n", + clk->pstate, clk->pwrsrc, clk->ustate_ac, clk->ustate_dc, + clk->astate, clk->tstate, 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 - clk->tstate); + pstate = max(pstate, clk->dstate); + } else { + pstate = clk->pstate = -1; + } + + nv_trace(clk, "-> %d\n", pstate); + if (pstate != clk->pstate) { + int ret = nvkm_pstate_prog(clk, pstate); + if (ret) { + nv_error(clk, "error setting pstate %d: %d\n", + pstate, ret); + } + } + + wake_up_all(&clk->wait); + nvkm_notify_get(&clk->pwrsrc_ntfy); +} + +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) +{ + struct nvkm_domain *clock = clk->domains - 1; + struct nvkm_cstate *cstate; + 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; + + nv_debug(clk, "%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); + nv_debug(clk, "%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); + } + } + } + + nv_info(clk, "%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 = nvkm_bios(clk); + 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; + u16 data; + + data = nvbios_perfEp(bios, idx, &ver, &hdr, &cnt, &len, &perfE); + if (!data) + return -EINVAL; + if (perfE.pstate == 0xff) + return 0; + + pstate = kzalloc(sizeof(*pstate), GFP_KERNEL); + cstate = &pstate->base; + if (!pstate) + return -ENOMEM; + + INIT_LIST_HEAD(&pstate->list); + + pstate->pstate = perfE.pstate; + pstate->fanspeed = perfE.fanspeed; + 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, int req, int rel) +{ + if (!rel) clk->tstate = req; + if ( rel) clk->tstate += rel; + clk->tstate = min(clk->tstate, 0); + clk->tstate = max(clk->tstate, -(clk->state_nr - 1)); + return nvkm_pstate_calc(clk, true); +} + +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); +} + +static int +nvkm_clk_pwrsrc(struct nvkm_notify *notify) +{ + struct nvkm_clk *clk = + container_of(notify, typeof(*clk), pwrsrc_ntfy); + nvkm_pstate_calc(clk, false); + return NVKM_NOTIFY_DROP; +} + +/****************************************************************************** + * subdev base class implementation + *****************************************************************************/ + +int +_nvkm_clk_fini(struct nvkm_object *object, bool suspend) +{ + struct nvkm_clk *clk = (void *)object; + nvkm_notify_put(&clk->pwrsrc_ntfy); + return nvkm_subdev_fini(&clk->base, suspend); +} + +int +_nvkm_clk_init(struct nvkm_object *object) +{ + struct nvkm_clk *clk = (void *)object; + struct nvkm_domain *clock = clk->domains; + int ret; + + ret = nvkm_subdev_init(&clk->base); + if (ret) + return 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 = clk->read(clk, clock->name); + if (ret < 0) { + nv_error(clk, "%02x freq unknown\n", clock->name); + return ret; + } + clk->bstate.base.domain[clock->name] = ret; + clock++; + } + + nvkm_pstate_info(clk, &clk->bstate); + + clk->astate = clk->state_nr - 1; + clk->tstate = 0; + clk->dstate = 0; + clk->pstate = -1; + nvkm_pstate_calc(clk, true); + return 0; +} + +void +_nvkm_clk_dtor(struct nvkm_object *object) +{ + struct nvkm_clk *clk = (void *)object; + struct nvkm_pstate *pstate, *temp; + + nvkm_notify_fini(&clk->pwrsrc_ntfy); + + list_for_each_entry_safe(pstate, temp, &clk->states, head) { + nvkm_pstate_del(pstate); + } + + nvkm_subdev_destroy(&clk->base); +} + +int +nvkm_clk_create_(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, struct nvkm_domain *clocks, + struct nvkm_pstate *pstates, int nb_pstates, + bool allow_reclock, int length, void **object) +{ + struct nvkm_device *device = nv_device(parent); + struct nvkm_clk *clk; + int ret, idx, arglen; + const char *mode; + + ret = nvkm_subdev_create_(parent, engine, oclass, 0, "CLK", + "clock", length, object); + clk = *object; + if (ret) + return ret; + + INIT_LIST_HEAD(&clk->states); + clk->domains = clocks; + clk->ustate_ac = -1; + clk->ustate_dc = -1; + + 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 (!pstates) { + idx = 0; + do { + ret = nvkm_pstate_new(clk, idx++); + } while (ret == 0); + } else { + for (idx = 0; idx < nb_pstates; idx++) + list_add_tail(&pstates[idx].head, &clk->states); + clk->state_nr = nb_pstates; + } + + clk->allow_reclock = allow_reclock; + + ret = nvkm_notify_init(NULL, &device->event, nvkm_clk_pwrsrc, true, + NULL, 0, 0, &clk->pwrsrc_ntfy); + if (ret) + return ret; + + 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); + + return 0; +} diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/g84.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/g84.c new file mode 100644 index 000000000..4c90b9769 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/g84.c @@ -0,0 +1,47 @@ +/* + * 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 struct nvkm_domain +g84_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 } +}; + +struct nvkm_oclass * +g84_clk_oclass = &(struct nv50_clk_oclass) { + .base.handle = NV_SUBDEV(CLK, 0x84), + .base.ofuncs = &(struct nvkm_ofuncs) { + .ctor = nv50_clk_ctor, + .dtor = _nvkm_clk_dtor, + .init = _nvkm_clk_init, + .fini = _nvkm_clk_fini, + }, + .domains = g84_domains, +}.base; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gf100.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gf100.c new file mode 100644 index 000000000..3d7330d54 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gf100.c @@ -0,0 +1,462 @@ +/* + * 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 <subdev/clk.h> +#include "pll.h" + +#include <core/device.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_priv { + struct nvkm_clk base; + struct gf100_clk_info eng[16]; +}; + +static u32 read_div(struct gf100_clk_priv *, int, u32, u32); + +static u32 +read_vco(struct gf100_clk_priv *priv, u32 dsrc) +{ + struct nvkm_clk *clk = &priv->base; + u32 ssrc = nv_rd32(priv, dsrc); + if (!(ssrc & 0x00000100)) + return clk->read(clk, nv_clk_src_sppll0); + return clk->read(clk, nv_clk_src_sppll1); +} + +static u32 +read_pll(struct gf100_clk_priv *priv, u32 pll) +{ + struct nvkm_clk *clk = &priv->base; + u32 ctrl = nv_rd32(priv, pll + 0x00); + u32 coef = nv_rd32(priv, 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 = nv_device(priv)->crystal; + P = 1; + break; + case 0x132000: + sclk = clk->read(clk, nv_clk_src_mpllsrc); + break; + case 0x132020: + sclk = clk->read(clk, nv_clk_src_mpllsrcref); + break; + case 0x137000: + case 0x137020: + case 0x137040: + case 0x1370e0: + sclk = read_div(priv, (pll & 0xff) / 0x20, 0x137120, 0x137140); + break; + default: + return 0; + } + + return sclk * N / M / P; +} + +static u32 +read_div(struct gf100_clk_priv *priv, int doff, u32 dsrc, u32 dctl) +{ + u32 ssrc = nv_rd32(priv, dsrc + (doff * 4)); + u32 sctl = nv_rd32(priv, dctl + (doff * 4)); + + switch (ssrc & 0x00000003) { + case 0: + if ((ssrc & 0x00030000) != 0x00030000) + return nv_device(priv)->crystal; + return 108000; + case 2: + return 100000; + case 3: + if (sctl & 0x80000000) { + u32 sclk = read_vco(priv, dsrc + (doff * 4)); + u32 sdiv = (sctl & 0x0000003f) + 2; + return (sclk * 2) / sdiv; + } + + return read_vco(priv, dsrc + (doff * 4)); + default: + return 0; + } +} + +static u32 +read_clk(struct gf100_clk_priv *priv, int clk) +{ + u32 sctl = nv_rd32(priv, 0x137250 + (clk * 4)); + u32 ssel = nv_rd32(priv, 0x137100); + u32 sclk, sdiv; + + if (ssel & (1 << clk)) { + if (clk < 7) + sclk = read_pll(priv, 0x137000 + (clk * 0x20)); + else + sclk = read_pll(priv, 0x1370e0); + sdiv = ((sctl & 0x00003f00) >> 8) + 2; + } else { + sclk = read_div(priv, clk, 0x137160, 0x1371d0); + sdiv = ((sctl & 0x0000003f) >> 0) + 2; + } + + if (sctl & 0x80000000) + return (sclk * 2) / sdiv; + + return sclk; +} + +static int +gf100_clk_read(struct nvkm_clk *clk, enum nv_clk_src src) +{ + struct nvkm_device *device = nv_device(clk); + struct gf100_clk_priv *priv = (void *)clk; + + 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(priv, 0x00e800); + case nv_clk_src_sppll1: + return read_pll(priv, 0x00e820); + + case nv_clk_src_mpllsrcref: + return read_div(priv, 0, 0x137320, 0x137330); + case nv_clk_src_mpllsrc: + return read_pll(priv, 0x132020); + case nv_clk_src_mpll: + return read_pll(priv, 0x132000); + case nv_clk_src_mdiv: + return read_div(priv, 0, 0x137300, 0x137310); + case nv_clk_src_mem: + if (nv_rd32(priv, 0x1373f0) & 0x00000002) + return clk->read(clk, nv_clk_src_mpll); + return clk->read(clk, nv_clk_src_mdiv); + + case nv_clk_src_gpc: + return read_clk(priv, 0x00); + case nv_clk_src_rop: + return read_clk(priv, 0x01); + case nv_clk_src_hubk07: + return read_clk(priv, 0x02); + case nv_clk_src_hubk06: + return read_clk(priv, 0x07); + case nv_clk_src_hubk01: + return read_clk(priv, 0x08); + case nv_clk_src_copy: + return read_clk(priv, 0x09); + case nv_clk_src_daemon: + return read_clk(priv, 0x0c); + case nv_clk_src_vdec: + return read_clk(priv, 0x0e); + default: + nv_error(clk, "invalid clock source %d\n", src); + return -EINVAL; + } +} + +static u32 +calc_div(struct gf100_clk_priv *priv, int clk, 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_priv *priv, int clk, 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(priv, 0x137160 + (clk * 4)); + if (clk < 7) + sclk = calc_div(priv, clk, sclk, freq, ddiv); + return sclk; +} + +static u32 +calc_pll(struct gf100_clk_priv *priv, int clk, u32 freq, u32 *coef) +{ + struct nvkm_bios *bios = nvkm_bios(priv); + struct nvbios_pll limits; + int N, M, P, ret; + + ret = nvbios_pll_parse(bios, 0x137000 + (clk * 0x20), &limits); + if (ret) + return 0; + + limits.refclk = read_div(priv, clk, 0x137120, 0x137140); + if (!limits.refclk) + return 0; + + ret = gt215_pll_calc(nv_subdev(priv), &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_priv *priv, + struct nvkm_cstate *cstate, int clk, int dom) +{ + struct gf100_clk_info *info = &priv->eng[clk]; + 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(priv, clk, freq, &src0, &div0); + clk0 = calc_div(priv, clk, clk0, freq, &div1D); + + /* see if we can get any closer using PLLs */ + if (clk0 != freq && (0x00004387 & (1 << clk))) { + if (clk <= 7) + clk1 = calc_pll(priv, clk, freq, &info->coef); + else + clk1 = cstate->domain[nv_clk_src_hubk06]; + clk1 = calc_div(priv, clk, 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 << clk); + info->freq = clk1; + } + + return 0; +} + +static int +gf100_clk_calc(struct nvkm_clk *clk, struct nvkm_cstate *cstate) +{ + struct gf100_clk_priv *priv = (void *)clk; + int ret; + + if ((ret = calc_clk(priv, cstate, 0x00, nv_clk_src_gpc)) || + (ret = calc_clk(priv, cstate, 0x01, nv_clk_src_rop)) || + (ret = calc_clk(priv, cstate, 0x02, nv_clk_src_hubk07)) || + (ret = calc_clk(priv, cstate, 0x07, nv_clk_src_hubk06)) || + (ret = calc_clk(priv, cstate, 0x08, nv_clk_src_hubk01)) || + (ret = calc_clk(priv, cstate, 0x09, nv_clk_src_copy)) || + (ret = calc_clk(priv, cstate, 0x0c, nv_clk_src_daemon)) || + (ret = calc_clk(priv, cstate, 0x0e, nv_clk_src_vdec))) + return ret; + + return 0; +} + +static void +gf100_clk_prog_0(struct gf100_clk_priv *priv, int clk) +{ + struct gf100_clk_info *info = &priv->eng[clk]; + if (clk < 7 && !info->ssel) { + nv_mask(priv, 0x1371d0 + (clk * 0x04), 0x80003f3f, info->ddiv); + nv_wr32(priv, 0x137160 + (clk * 0x04), info->dsrc); + } +} + +static void +gf100_clk_prog_1(struct gf100_clk_priv *priv, int clk) +{ + nv_mask(priv, 0x137100, (1 << clk), 0x00000000); + nv_wait(priv, 0x137100, (1 << clk), 0x00000000); +} + +static void +gf100_clk_prog_2(struct gf100_clk_priv *priv, int clk) +{ + struct gf100_clk_info *info = &priv->eng[clk]; + const u32 addr = 0x137000 + (clk * 0x20); + if (clk <= 7) { + nv_mask(priv, addr + 0x00, 0x00000004, 0x00000000); + nv_mask(priv, addr + 0x00, 0x00000001, 0x00000000); + if (info->coef) { + nv_wr32(priv, addr + 0x04, info->coef); + nv_mask(priv, addr + 0x00, 0x00000001, 0x00000001); + nv_wait(priv, addr + 0x00, 0x00020000, 0x00020000); + nv_mask(priv, addr + 0x00, 0x00020004, 0x00000004); + } + } +} + +static void +gf100_clk_prog_3(struct gf100_clk_priv *priv, int clk) +{ + struct gf100_clk_info *info = &priv->eng[clk]; + if (info->ssel) { + nv_mask(priv, 0x137100, (1 << clk), info->ssel); + nv_wait(priv, 0x137100, (1 << clk), info->ssel); + } +} + +static void +gf100_clk_prog_4(struct gf100_clk_priv *priv, int clk) +{ + struct gf100_clk_info *info = &priv->eng[clk]; + nv_mask(priv, 0x137250 + (clk * 0x04), 0x00003f3f, info->mdiv); +} + +static int +gf100_clk_prog(struct nvkm_clk *clk) +{ + struct gf100_clk_priv *priv = (void *)clk; + struct { + void (*exec)(struct gf100_clk_priv *, 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(priv->eng); j++) { + if (!priv->eng[j].freq) + continue; + stage[i].exec(priv, j); + } + } + + return 0; +} + +static void +gf100_clk_tidy(struct nvkm_clk *clk) +{ + struct gf100_clk_priv *priv = (void *)clk; + memset(priv->eng, 0x00, sizeof(priv->eng)); +} + +static struct nvkm_domain +gf100_domain[] = { + { 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, 0, "core", 2000 }, + { nv_clk_src_rop , 0x04 }, + { nv_clk_src_mem , 0x05, 0, "memory", 1000 }, + { nv_clk_src_vdec , 0x06 }, + { nv_clk_src_daemon , 0x0a }, + { nv_clk_src_hubk07 , 0x0b }, + { nv_clk_src_max } +}; + +static int +gf100_clk_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct gf100_clk_priv *priv; + int ret; + + ret = nvkm_clk_create(parent, engine, oclass, gf100_domain, + NULL, 0, false, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.read = gf100_clk_read; + priv->base.calc = gf100_clk_calc; + priv->base.prog = gf100_clk_prog; + priv->base.tidy = gf100_clk_tidy; + return 0; +} + +struct nvkm_oclass +gf100_clk_oclass = { + .handle = NV_SUBDEV(CLK, 0xc0), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = gf100_clk_ctor, + .dtor = _nvkm_clk_dtor, + .init = _nvkm_clk_init, + .fini = _nvkm_clk_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gk104.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gk104.c new file mode 100644 index 000000000..e9b2310bd --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gk104.c @@ -0,0 +1,500 @@ +/* + * 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 <subdev/clk.h> +#include "pll.h" + +#include <core/device.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_priv { + struct nvkm_clk base; + struct gk104_clk_info eng[16]; +}; + +static u32 read_div(struct gk104_clk_priv *, int, u32, u32); +static u32 read_pll(struct gk104_clk_priv *, u32); + +static u32 +read_vco(struct gk104_clk_priv *priv, u32 dsrc) +{ + u32 ssrc = nv_rd32(priv, dsrc); + if (!(ssrc & 0x00000100)) + return read_pll(priv, 0x00e800); + return read_pll(priv, 0x00e820); +} + +static u32 +read_pll(struct gk104_clk_priv *priv, u32 pll) +{ + u32 ctrl = nv_rd32(priv, pll + 0x00); + u32 coef = nv_rd32(priv, 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 = nv_device(priv)->crystal; + P = 1; + break; + case 0x132000: + sclk = read_pll(priv, 0x132020); + P = (coef & 0x10000000) ? 2 : 1; + break; + case 0x132020: + sclk = read_div(priv, 0, 0x137320, 0x137330); + fN = nv_rd32(priv, pll + 0x10) >> 16; + break; + case 0x137000: + case 0x137020: + case 0x137040: + case 0x1370e0: + sclk = read_div(priv, (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_priv *priv, int doff, u32 dsrc, u32 dctl) +{ + u32 ssrc = nv_rd32(priv, dsrc + (doff * 4)); + u32 sctl = nv_rd32(priv, dctl + (doff * 4)); + + switch (ssrc & 0x00000003) { + case 0: + if ((ssrc & 0x00030000) != 0x00030000) + return nv_device(priv)->crystal; + return 108000; + case 2: + return 100000; + case 3: + if (sctl & 0x80000000) { + u32 sclk = read_vco(priv, dsrc + (doff * 4)); + u32 sdiv = (sctl & 0x0000003f) + 2; + return (sclk * 2) / sdiv; + } + + return read_vco(priv, dsrc + (doff * 4)); + default: + return 0; + } +} + +static u32 +read_mem(struct gk104_clk_priv *priv) +{ + switch (nv_rd32(priv, 0x1373f4) & 0x0000000f) { + case 1: return read_pll(priv, 0x132020); + case 2: return read_pll(priv, 0x132000); + default: + return 0; + } +} + +static u32 +read_clk(struct gk104_clk_priv *priv, int clk) +{ + u32 sctl = nv_rd32(priv, 0x137250 + (clk * 4)); + u32 sclk, sdiv; + + if (clk < 7) { + u32 ssel = nv_rd32(priv, 0x137100); + if (ssel & (1 << clk)) { + sclk = read_pll(priv, 0x137000 + (clk * 0x20)); + sdiv = 1; + } else { + sclk = read_div(priv, clk, 0x137160, 0x1371d0); + sdiv = 0; + } + } else { + u32 ssrc = nv_rd32(priv, 0x137160 + (clk * 0x04)); + if ((ssrc & 0x00000003) == 0x00000003) { + sclk = read_div(priv, clk, 0x137160, 0x1371d0); + if (ssrc & 0x00000100) { + if (ssrc & 0x40000000) + sclk = read_pll(priv, 0x1370e0); + sdiv = 1; + } else { + sdiv = 0; + } + } else { + sclk = read_div(priv, clk, 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 *clk, enum nv_clk_src src) +{ + struct nvkm_device *device = nv_device(clk); + struct gk104_clk_priv *priv = (void *)clk; + + 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(priv); + case nv_clk_src_gpc: + return read_clk(priv, 0x00); + case nv_clk_src_rop: + return read_clk(priv, 0x01); + case nv_clk_src_hubk07: + return read_clk(priv, 0x02); + case nv_clk_src_hubk06: + return read_clk(priv, 0x07); + case nv_clk_src_hubk01: + return read_clk(priv, 0x08); + case nv_clk_src_daemon: + return read_clk(priv, 0x0c); + case nv_clk_src_vdec: + return read_clk(priv, 0x0e); + default: + nv_error(clk, "invalid clock source %d\n", src); + return -EINVAL; + } +} + +static u32 +calc_div(struct gk104_clk_priv *priv, int clk, 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_priv *priv, int clk, 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(priv, 0x137160 + (clk * 4)); + if (clk < 7) + sclk = calc_div(priv, clk, sclk, freq, ddiv); + return sclk; +} + +static u32 +calc_pll(struct gk104_clk_priv *priv, int clk, u32 freq, u32 *coef) +{ + struct nvkm_bios *bios = nvkm_bios(priv); + struct nvbios_pll limits; + int N, M, P, ret; + + ret = nvbios_pll_parse(bios, 0x137000 + (clk * 0x20), &limits); + if (ret) + return 0; + + limits.refclk = read_div(priv, clk, 0x137120, 0x137140); + if (!limits.refclk) + return 0; + + ret = gt215_pll_calc(nv_subdev(priv), &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_priv *priv, + struct nvkm_cstate *cstate, int clk, int dom) +{ + struct gk104_clk_info *info = &priv->eng[clk]; + 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(priv, clk, freq, &src0, &div0); + clk0 = calc_div(priv, clk, clk0, freq, &div1D); + + /* see if we can get any closer using PLLs */ + if (clk0 != freq && (0x0000ff87 & (1 << clk))) { + if (clk <= 7) + clk1 = calc_pll(priv, clk, freq, &info->coef); + else + clk1 = cstate->domain[nv_clk_src_hubk06]; + clk1 = calc_div(priv, clk, 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 << clk); + info->dsrc = 0x40000100; + info->freq = clk1; + } + + return 0; +} + +static int +gk104_clk_calc(struct nvkm_clk *clk, struct nvkm_cstate *cstate) +{ + struct gk104_clk_priv *priv = (void *)clk; + int ret; + + if ((ret = calc_clk(priv, cstate, 0x00, nv_clk_src_gpc)) || + (ret = calc_clk(priv, cstate, 0x01, nv_clk_src_rop)) || + (ret = calc_clk(priv, cstate, 0x02, nv_clk_src_hubk07)) || + (ret = calc_clk(priv, cstate, 0x07, nv_clk_src_hubk06)) || + (ret = calc_clk(priv, cstate, 0x08, nv_clk_src_hubk01)) || + (ret = calc_clk(priv, cstate, 0x0c, nv_clk_src_daemon)) || + (ret = calc_clk(priv, cstate, 0x0e, nv_clk_src_vdec))) + return ret; + + return 0; +} + +static void +gk104_clk_prog_0(struct gk104_clk_priv *priv, int clk) +{ + struct gk104_clk_info *info = &priv->eng[clk]; + if (!info->ssel) { + nv_mask(priv, 0x1371d0 + (clk * 0x04), 0x8000003f, info->ddiv); + nv_wr32(priv, 0x137160 + (clk * 0x04), info->dsrc); + } +} + +static void +gk104_clk_prog_1_0(struct gk104_clk_priv *priv, int clk) +{ + nv_mask(priv, 0x137100, (1 << clk), 0x00000000); + nv_wait(priv, 0x137100, (1 << clk), 0x00000000); +} + +static void +gk104_clk_prog_1_1(struct gk104_clk_priv *priv, int clk) +{ + nv_mask(priv, 0x137160 + (clk * 0x04), 0x00000100, 0x00000000); +} + +static void +gk104_clk_prog_2(struct gk104_clk_priv *priv, int clk) +{ + struct gk104_clk_info *info = &priv->eng[clk]; + const u32 addr = 0x137000 + (clk * 0x20); + nv_mask(priv, addr + 0x00, 0x00000004, 0x00000000); + nv_mask(priv, addr + 0x00, 0x00000001, 0x00000000); + if (info->coef) { + nv_wr32(priv, addr + 0x04, info->coef); + nv_mask(priv, addr + 0x00, 0x00000001, 0x00000001); + nv_wait(priv, addr + 0x00, 0x00020000, 0x00020000); + nv_mask(priv, addr + 0x00, 0x00020004, 0x00000004); + } +} + +static void +gk104_clk_prog_3(struct gk104_clk_priv *priv, int clk) +{ + struct gk104_clk_info *info = &priv->eng[clk]; + if (info->ssel) + nv_mask(priv, 0x137250 + (clk * 0x04), 0x00003f00, info->mdiv); + else + nv_mask(priv, 0x137250 + (clk * 0x04), 0x0000003f, info->mdiv); +} + +static void +gk104_clk_prog_4_0(struct gk104_clk_priv *priv, int clk) +{ + struct gk104_clk_info *info = &priv->eng[clk]; + if (info->ssel) { + nv_mask(priv, 0x137100, (1 << clk), info->ssel); + nv_wait(priv, 0x137100, (1 << clk), info->ssel); + } +} + +static void +gk104_clk_prog_4_1(struct gk104_clk_priv *priv, int clk) +{ + struct gk104_clk_info *info = &priv->eng[clk]; + if (info->ssel) { + nv_mask(priv, 0x137160 + (clk * 0x04), 0x40000000, 0x40000000); + nv_mask(priv, 0x137160 + (clk * 0x04), 0x00000100, 0x00000100); + } +} + +static int +gk104_clk_prog(struct nvkm_clk *clk) +{ + struct gk104_clk_priv *priv = (void *)clk; + struct { + u32 mask; + void (*exec)(struct gk104_clk_priv *, 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(priv->eng); j++) { + if (!(stage[i].mask & (1 << j))) + continue; + if (!priv->eng[j].freq) + continue; + stage[i].exec(priv, j); + } + } + + return 0; +} + +static void +gk104_clk_tidy(struct nvkm_clk *clk) +{ + struct gk104_clk_priv *priv = (void *)clk; + memset(priv->eng, 0x00, sizeof(priv->eng)); +} + +static struct nvkm_domain +gk104_domain[] = { + { nv_clk_src_crystal, 0xff }, + { nv_clk_src_href , 0xff }, + { nv_clk_src_gpc , 0x00, NVKM_CLK_DOM_FLAG_CORE, "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_daemon , 0x07 }, + { nv_clk_src_max } +}; + +static int +gk104_clk_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct gk104_clk_priv *priv; + int ret; + + ret = nvkm_clk_create(parent, engine, oclass, gk104_domain, + NULL, 0, true, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.read = gk104_clk_read; + priv->base.calc = gk104_clk_calc; + priv->base.prog = gk104_clk_prog; + priv->base.tidy = gk104_clk_tidy; + return 0; +} + +struct nvkm_oclass +gk104_clk_oclass = { + .handle = NV_SUBDEV(CLK, 0xe0), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = gk104_clk_ctor, + .dtor = _nvkm_clk_dtor, + .init = _nvkm_clk_init, + .fini = _nvkm_clk_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gk20a.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gk20a.c new file mode 100644 index 000000000..65c532742 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gk20a.c @@ -0,0 +1,680 @@ +/* + * Copyright (c) 2014, 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 <subdev/clk.h> +#include <subdev/timer.h> + +#include <core/device.h> + +#ifdef __KERNEL__ +#include <nouveau_platform.h> +#endif + +#define MHZ (1000 * 1000) + +#define MASK(w) ((1 << w) - 1) + +#define SYS_GPCPLL_CFG_BASE 0x00137000 +#define GPC_BCASE_GPCPLL_CFG_BASE 0x00132800 + +#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_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_P_SHIFT 16 +#define GPCPLL_COEFF_P_WIDTH 6 + +#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_PLL_STEPB_SHIFT 16 + +#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 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_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)) + +#define GPC_BCAST_NDIV_SLOWDOWN_DEBUG (GPC_BCASE_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) + +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, +}; + +/* All frequencies in Mhz */ +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; +}; + +static const struct gk20a_clk_pllg_params gk20a_pllg_params = { + .min_vco = 1000, .max_vco = 2064, + .min_u = 12, .max_u = 38, + .min_m = 1, .max_m = 255, + .min_n = 8, .max_n = 255, + .min_pl = 1, .max_pl = 32, +}; + +struct gk20a_clk_priv { + struct nvkm_clk base; + const struct gk20a_clk_pllg_params *params; + u32 m, n, pl; + u32 parent_rate; +}; +#define to_gk20a_clk(base) container_of(base, struct gk20a_clk_priv, base) + +static void +gk20a_pllg_read_mnp(struct gk20a_clk_priv *priv) +{ + u32 val; + + val = nv_rd32(priv, GPCPLL_COEFF); + priv->m = (val >> GPCPLL_COEFF_M_SHIFT) & MASK(GPCPLL_COEFF_M_WIDTH); + priv->n = (val >> GPCPLL_COEFF_N_SHIFT) & MASK(GPCPLL_COEFF_N_WIDTH); + priv->pl = (val >> GPCPLL_COEFF_P_SHIFT) & MASK(GPCPLL_COEFF_P_WIDTH); +} + +static u32 +gk20a_pllg_calc_rate(struct gk20a_clk_priv *priv) +{ + u32 rate; + u32 divider; + + rate = priv->parent_rate * priv->n; + divider = priv->m * pl_to_div[priv->pl]; + do_div(rate, divider); + + return rate / 2; +} + +static int +gk20a_pllg_calc_mnp(struct gk20a_clk_priv *priv, unsigned long rate) +{ + 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, vco_f; + u32 best_m, best_n; + u32 u_f; + u32 m, n, n2; + u32 delta, lwv, best_delta = ~0; + u32 pl; + + target_clk_f = rate * 2 / MHZ; + ref_clk_f = priv->parent_rate / MHZ; + + max_vco_f = priv->params->max_vco; + min_vco_f = priv->params->min_vco; + best_m = priv->params->max_m; + best_n = priv->params->min_n; + best_pl = priv->params->min_pl; + + target_vco_f = target_clk_f + target_clk_f / 50; + if (max_vco_f < target_vco_f) + max_vco_f = target_vco_f; + + /* min_pl <= high_pl <= max_pl */ + high_pl = (max_vco_f + target_vco_f - 1) / target_vco_f; + high_pl = min(high_pl, priv->params->max_pl); + high_pl = max(high_pl, priv->params->min_pl); + + /* min_pl <= low_pl <= max_pl */ + low_pl = min_vco_f / target_vco_f; + low_pl = min(low_pl, priv->params->max_pl); + low_pl = max(low_pl, priv->params->min_pl); + + /* Find Indices of high_pl and low_pl */ + for (pl = 0; pl < ARRAY_SIZE(pl_to_div) - 1; pl++) { + if (pl_to_div[pl] >= low_pl) { + low_pl = pl; + break; + } + } + for (pl = 0; pl < ARRAY_SIZE(pl_to_div) - 1; pl++) { + if (pl_to_div[pl] >= high_pl) { + high_pl = pl; + break; + } + } + + nv_debug(priv, "low_PL %d(div%d), high_PL %d(div%d)", low_pl, + pl_to_div[low_pl], high_pl, pl_to_div[high_pl]); + + /* Select lowest possible VCO */ + for (pl = low_pl; pl <= high_pl; pl++) { + target_vco_f = target_clk_f * pl_to_div[pl]; + for (m = priv->params->min_m; m <= priv->params->max_m; m++) { + u_f = ref_clk_f / m; + + if (u_f < priv->params->min_u) + break; + if (u_f > priv->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 > priv->params->max_n) + break; + + for (; n <= n2; n++) { + if (n < priv->params->min_n) + continue; + if (n > priv->params->max_n) + break; + + vco_f = ref_clk_f * n / m; + + if (vco_f >= min_vco_f && vco_f <= max_vco_f) { + lwv = (vco_f + (pl_to_div[pl] / 2)) + / 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) + nv_debug(priv, "no best match for target @ %dMHz on gpc_pll", + target_clk_f); + + priv->m = best_m; + priv->n = best_n; + priv->pl = best_pl; + + target_freq = gk20a_pllg_calc_rate(priv) / MHZ; + + nv_debug(priv, "actual target freq %d MHz, M %d, N %d, PL %d(div%d)\n", + target_freq, priv->m, priv->n, priv->pl, pl_to_div[priv->pl]); + return 0; +} + +static int +gk20a_pllg_slide(struct gk20a_clk_priv *priv, u32 n) +{ + u32 val; + int ramp_timeout; + + /* get old coefficients */ + val = nv_rd32(priv, GPCPLL_COEFF); + /* do nothing if NDIV is the same */ + if (n == ((val >> GPCPLL_COEFF_N_SHIFT) & MASK(GPCPLL_COEFF_N_WIDTH))) + return 0; + + /* setup */ + nv_mask(priv, GPCPLL_CFG2, 0xff << GPCPLL_CFG2_PLL_STEPA_SHIFT, + 0x2b << GPCPLL_CFG2_PLL_STEPA_SHIFT); + nv_mask(priv, GPCPLL_CFG3, 0xff << GPCPLL_CFG3_PLL_STEPB_SHIFT, + 0xb << GPCPLL_CFG3_PLL_STEPB_SHIFT); + + /* pll slowdown mode */ + nv_mask(priv, GPCPLL_NDIV_SLOWDOWN, + BIT(GPCPLL_NDIV_SLOWDOWN_SLOWDOWN_USING_PLL_SHIFT), + BIT(GPCPLL_NDIV_SLOWDOWN_SLOWDOWN_USING_PLL_SHIFT)); + + /* new ndiv ready for ramp */ + val = nv_rd32(priv, GPCPLL_COEFF); + val &= ~(MASK(GPCPLL_COEFF_N_WIDTH) << GPCPLL_COEFF_N_SHIFT); + val |= (n & MASK(GPCPLL_COEFF_N_WIDTH)) << GPCPLL_COEFF_N_SHIFT; + udelay(1); + nv_wr32(priv, GPCPLL_COEFF, val); + + /* dynamic ramp to new ndiv */ + val = nv_rd32(priv, GPCPLL_NDIV_SLOWDOWN); + val |= 0x1 << GPCPLL_NDIV_SLOWDOWN_EN_DYNRAMP_SHIFT; + udelay(1); + nv_wr32(priv, GPCPLL_NDIV_SLOWDOWN, val); + + for (ramp_timeout = 500; ramp_timeout > 0; ramp_timeout--) { + udelay(1); + val = nv_rd32(priv, GPC_BCAST_NDIV_SLOWDOWN_DEBUG); + if (val & GPC_BCAST_NDIV_SLOWDOWN_DEBUG_PLL_DYNRAMP_DONE_SYNCED_MASK) + break; + } + + /* exit slowdown mode */ + nv_mask(priv, GPCPLL_NDIV_SLOWDOWN, + BIT(GPCPLL_NDIV_SLOWDOWN_SLOWDOWN_USING_PLL_SHIFT) | + BIT(GPCPLL_NDIV_SLOWDOWN_EN_DYNRAMP_SHIFT), 0); + nv_rd32(priv, GPCPLL_NDIV_SLOWDOWN); + + if (ramp_timeout <= 0) { + nv_error(priv, "gpcpll dynamic ramp timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void +_gk20a_pllg_enable(struct gk20a_clk_priv *priv) +{ + nv_mask(priv, GPCPLL_CFG, GPCPLL_CFG_ENABLE, GPCPLL_CFG_ENABLE); + nv_rd32(priv, GPCPLL_CFG); +} + +static void +_gk20a_pllg_disable(struct gk20a_clk_priv *priv) +{ + nv_mask(priv, GPCPLL_CFG, GPCPLL_CFG_ENABLE, 0); + nv_rd32(priv, GPCPLL_CFG); +} + +static int +_gk20a_pllg_program_mnp(struct gk20a_clk_priv *priv, bool allow_slide) +{ + u32 val, cfg; + u32 m_old, pl_old, n_lo; + + /* get old coefficients */ + val = nv_rd32(priv, GPCPLL_COEFF); + m_old = (val >> GPCPLL_COEFF_M_SHIFT) & MASK(GPCPLL_COEFF_M_WIDTH); + pl_old = (val >> GPCPLL_COEFF_P_SHIFT) & MASK(GPCPLL_COEFF_P_WIDTH); + + /* do NDIV slide if there is no change in M and PL */ + cfg = nv_rd32(priv, GPCPLL_CFG); + if (allow_slide && priv->m == m_old && priv->pl == pl_old && + (cfg & GPCPLL_CFG_ENABLE)) { + return gk20a_pllg_slide(priv, priv->n); + } + + /* slide down to NDIV_LO */ + n_lo = DIV_ROUND_UP(m_old * priv->params->min_vco, + priv->parent_rate / MHZ); + if (allow_slide && (cfg & GPCPLL_CFG_ENABLE)) { + int ret = gk20a_pllg_slide(priv, n_lo); + + if (ret) + return ret; + } + + /* split FO-to-bypass jump in halfs by setting out divider 1:2 */ + nv_mask(priv, GPC2CLK_OUT, GPC2CLK_OUT_VCODIV_MASK, + 0x2 << GPC2CLK_OUT_VCODIV_SHIFT); + + /* put PLL in bypass before programming it */ + val = nv_rd32(priv, SEL_VCO); + val &= ~(BIT(SEL_VCO_GPC2CLK_OUT_SHIFT)); + udelay(2); + nv_wr32(priv, SEL_VCO, val); + + /* get out from IDDQ */ + val = nv_rd32(priv, GPCPLL_CFG); + if (val & GPCPLL_CFG_IDDQ) { + val &= ~GPCPLL_CFG_IDDQ; + nv_wr32(priv, GPCPLL_CFG, val); + nv_rd32(priv, GPCPLL_CFG); + udelay(2); + } + + _gk20a_pllg_disable(priv); + + nv_debug(priv, "%s: m=%d n=%d pl=%d\n", __func__, priv->m, priv->n, + priv->pl); + + n_lo = DIV_ROUND_UP(priv->m * priv->params->min_vco, + priv->parent_rate / MHZ); + val = priv->m << GPCPLL_COEFF_M_SHIFT; + val |= (allow_slide ? n_lo : priv->n) << GPCPLL_COEFF_N_SHIFT; + val |= priv->pl << GPCPLL_COEFF_P_SHIFT; + nv_wr32(priv, GPCPLL_COEFF, val); + + _gk20a_pllg_enable(priv); + + val = nv_rd32(priv, GPCPLL_CFG); + if (val & GPCPLL_CFG_LOCK_DET_OFF) { + val &= ~GPCPLL_CFG_LOCK_DET_OFF; + nv_wr32(priv, GPCPLL_CFG, val); + } + + if (!nvkm_timer_wait_eq(priv, 300000, GPCPLL_CFG, GPCPLL_CFG_LOCK, + GPCPLL_CFG_LOCK)) { + nv_error(priv, "%s: timeout waiting for pllg lock\n", __func__); + return -ETIMEDOUT; + } + + /* switch to VCO mode */ + nv_mask(priv, SEL_VCO, 0, BIT(SEL_VCO_GPC2CLK_OUT_SHIFT)); + + /* restore out divider 1:1 */ + val = nv_rd32(priv, GPC2CLK_OUT); + val &= ~GPC2CLK_OUT_VCODIV_MASK; + udelay(2); + nv_wr32(priv, GPC2CLK_OUT, val); + + /* slide up to new NDIV */ + return allow_slide ? gk20a_pllg_slide(priv, priv->n) : 0; +} + +static int +gk20a_pllg_program_mnp(struct gk20a_clk_priv *priv) +{ + int err; + + err = _gk20a_pllg_program_mnp(priv, true); + if (err) + err = _gk20a_pllg_program_mnp(priv, false); + + return err; +} + +static void +gk20a_pllg_disable(struct gk20a_clk_priv *priv) +{ + u32 val; + + /* slide to VCO min */ + val = nv_rd32(priv, GPCPLL_CFG); + if (val & GPCPLL_CFG_ENABLE) { + u32 coeff, m, n_lo; + + coeff = nv_rd32(priv, GPCPLL_COEFF); + m = (coeff >> GPCPLL_COEFF_M_SHIFT) & MASK(GPCPLL_COEFF_M_WIDTH); + n_lo = DIV_ROUND_UP(m * priv->params->min_vco, + priv->parent_rate / MHZ); + gk20a_pllg_slide(priv, n_lo); + } + + /* put PLL in bypass before disabling it */ + nv_mask(priv, SEL_VCO, BIT(SEL_VCO_GPC2CLK_OUT_SHIFT), 0); + + _gk20a_pllg_disable(priv); +} + +#define GK20A_CLK_GPC_MDIV 1000 + +static struct nvkm_domain +gk20a_domains[] = { + { nv_clk_src_crystal, 0xff }, + { nv_clk_src_gpc, 0xff, 0, "core", GK20A_CLK_GPC_MDIV }, + { nv_clk_src_max } +}; + +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, + }, + }, +}; + +static int +gk20a_clk_read(struct nvkm_clk *clk, enum nv_clk_src src) +{ + struct gk20a_clk_priv *priv = (void *)clk; + + switch (src) { + case nv_clk_src_crystal: + return nv_device(clk)->crystal; + case nv_clk_src_gpc: + gk20a_pllg_read_mnp(priv); + return gk20a_pllg_calc_rate(priv) / GK20A_CLK_GPC_MDIV; + default: + nv_error(clk, "invalid clock source %d\n", src); + return -EINVAL; + } +} + +static int +gk20a_clk_calc(struct nvkm_clk *clk, struct nvkm_cstate *cstate) +{ + struct gk20a_clk_priv *priv = (void *)clk; + + return gk20a_pllg_calc_mnp(priv, cstate->domain[nv_clk_src_gpc] * + GK20A_CLK_GPC_MDIV); +} + +static int +gk20a_clk_prog(struct nvkm_clk *clk) +{ + struct gk20a_clk_priv *priv = (void *)clk; + + return gk20a_pllg_program_mnp(priv); +} + +static void +gk20a_clk_tidy(struct nvkm_clk *clk) +{ +} + +static int +gk20a_clk_fini(struct nvkm_object *object, bool suspend) +{ + struct gk20a_clk_priv *priv = (void *)object; + int ret; + + ret = nvkm_clk_fini(&priv->base, false); + + gk20a_pllg_disable(priv); + + return ret; +} + +static int +gk20a_clk_init(struct nvkm_object *object) +{ + struct gk20a_clk_priv *priv = (void *)object; + int ret; + + nv_mask(priv, GPC2CLK_OUT, GPC2CLK_OUT_INIT_MASK, GPC2CLK_OUT_INIT_VAL); + + ret = nvkm_clk_init(&priv->base); + if (ret) + return ret; + + ret = gk20a_clk_prog(&priv->base); + if (ret) { + nv_error(priv, "cannot initialize clock\n"); + return ret; + } + + return 0; +} + +static int +gk20a_clk_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct gk20a_clk_priv *priv; + struct nouveau_platform_device *plat; + int ret; + int i; + + /* Finish initializing the pstates */ + for (i = 0; i < ARRAY_SIZE(gk20a_pstates); i++) { + INIT_LIST_HEAD(&gk20a_pstates[i].list); + gk20a_pstates[i].pstate = i + 1; + } + + ret = nvkm_clk_create(parent, engine, oclass, gk20a_domains, + gk20a_pstates, ARRAY_SIZE(gk20a_pstates), + true, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->params = &gk20a_pllg_params; + + plat = nv_device_to_platform(nv_device(parent)); + priv->parent_rate = clk_get_rate(plat->gpu->clk); + nv_info(priv, "parent clock rate: %d Mhz\n", priv->parent_rate / MHZ); + + priv->base.read = gk20a_clk_read; + priv->base.calc = gk20a_clk_calc; + priv->base.prog = gk20a_clk_prog; + priv->base.tidy = gk20a_clk_tidy; + return 0; +} + +struct nvkm_oclass +gk20a_clk_oclass = { + .handle = NV_SUBDEV(CLK, 0xea), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = gk20a_clk_ctor, + .dtor = _nvkm_subdev_dtor, + .init = gk20a_clk_init, + .fini = gk20a_clk_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gt215.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gt215.c new file mode 100644 index 000000000..822d32a28 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gt215.c @@ -0,0 +1,533 @@ +/* + * 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 + */ +#include "gt215.h" +#include "pll.h" + +#include <core/device.h> +#include <engine/fifo.h> +#include <subdev/bios.h> +#include <subdev/bios/pll.h> +#include <subdev/timer.h> + +struct gt215_clk_priv { + struct nvkm_clk base; + struct gt215_clk_info eng[nv_clk_src_max]; +}; + +static u32 read_clk(struct gt215_clk_priv *, int, bool); +static u32 read_pll(struct gt215_clk_priv *, int, u32); + +static u32 +read_vco(struct gt215_clk_priv *priv, int clk) +{ + u32 sctl = nv_rd32(priv, 0x4120 + (clk * 4)); + + switch (sctl & 0x00000030) { + case 0x00000000: + return nv_device(priv)->crystal; + case 0x00000020: + return read_pll(priv, 0x41, 0x00e820); + case 0x00000030: + return read_pll(priv, 0x42, 0x00e8a0); + default: + return 0; + } +} + +static u32 +read_clk(struct gt215_clk_priv *priv, int clk, bool ignore_en) +{ + u32 sctl, sdiv, sclk; + + /* refclk for the 0xe8xx plls is a fixed frequency */ + if (clk >= 0x40) { + if (nv_device(priv)->chipset == 0xaf) { + /* no joke.. seriously.. sigh.. */ + return nv_rd32(priv, 0x00471c) * 1000; + } + + return nv_device(priv)->crystal; + } + + sctl = nv_rd32(priv, 0x4120 + (clk * 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 nv_device(priv)->crystal; + return 0; + case 0x00002000: + if (sctl & 0x00000040) + return 108000; + return 100000; + case 0x00003000: + /* vco_enable */ + if (!(sctl & 0x00000001)) + return 0; + + sclk = read_vco(priv, clk); + sdiv = ((sctl & 0x003f0000) >> 16) + 2; + return (sclk * 2) / sdiv; + default: + return 0; + } +} + +static u32 +read_pll(struct gt215_clk_priv *priv, int clk, u32 pll) +{ + u32 ctrl = nv_rd32(priv, pll + 0); + u32 sclk = 0, P = 1, N = 1, M = 1; + + if (!(ctrl & 0x00000008)) { + if (ctrl & 0x00000001) { + u32 coef = nv_rd32(priv, 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(priv, 0x00 + clk, false); + } + } else { + sclk = read_clk(priv, 0x10 + clk, false); + } + + if (M * P) + return sclk * N / (M * P); + + return 0; +} + +static int +gt215_clk_read(struct nvkm_clk *clk, enum nv_clk_src src) +{ + struct gt215_clk_priv *priv = (void *)clk; + u32 hsrc; + + switch (src) { + case nv_clk_src_crystal: + return nv_device(priv)->crystal; + case nv_clk_src_core: + case nv_clk_src_core_intm: + return read_pll(priv, 0x00, 0x4200); + case nv_clk_src_shader: + return read_pll(priv, 0x01, 0x4220); + case nv_clk_src_mem: + return read_pll(priv, 0x02, 0x4000); + case nv_clk_src_disp: + return read_clk(priv, 0x20, false); + case nv_clk_src_vdec: + return read_clk(priv, 0x21, false); + case nv_clk_src_daemon: + return read_clk(priv, 0x25, false); + case nv_clk_src_host: + hsrc = (nv_rd32(priv, 0xc040) & 0x30000000) >> 28; + switch (hsrc) { + case 0: + return read_clk(priv, 0x1d, false); + case 2: + case 3: + return 277000; + default: + nv_error(clk, "unknown HOST clock source %d\n", hsrc); + return -EINVAL; + } + default: + nv_error(clk, "invalid clock source %d\n", src); + return -EINVAL; + } + + return 0; +} + +int +gt215_clk_info(struct nvkm_clk *clock, int clk, u32 khz, + struct gt215_clk_info *info) +{ + struct gt215_clk_priv *priv = (void *)clock; + u32 oclk, sclk, sdiv, 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(priv, clk); + 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 *clock, int clk, u32 pll, u32 khz, + struct gt215_clk_info *info) +{ + struct nvkm_bios *bios = nvkm_bios(clock); + struct gt215_clk_priv *priv = (void *)clock; + 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(clock, clk, khz, info); + diff = khz - ret; + if (!pll || (diff >= -2000 && diff < 3000)) { + goto out; + } + + /* Try with PLL */ + ret = nvbios_pll_parse(bios, pll, &limits); + if (ret) + return ret; + + ret = gt215_clk_info(clock, clk - 0x10, limits.refclk, info); + if (ret != limits.refclk) + return -EINVAL; + + ret = gt215_pll_calc(nv_subdev(priv), &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_priv *priv, struct nvkm_cstate *cstate, + int clk, u32 pll, int idx) +{ + int ret = gt215_pll_info(&priv->base, clk, pll, cstate->domain[idx], + &priv->eng[idx]); + if (ret >= 0) + return 0; + return ret; +} + +static int +calc_host(struct gt215_clk_priv *priv, struct nvkm_cstate *cstate) +{ + int ret = 0; + u32 kHz = cstate->domain[nv_clk_src_host]; + struct gt215_clk_info *info = &priv->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(&priv->base, 0x1d, kHz, info); + if (ret >= 0) + return 0; + + return ret; +} + +int +gt215_clk_pre(struct nvkm_clk *clk, unsigned long *flags) +{ + struct nvkm_fifo *pfifo = nvkm_fifo(clk); + + /* halt and idle execution engines */ + nv_mask(clk, 0x020060, 0x00070000, 0x00000000); + nv_mask(clk, 0x002504, 0x00000001, 0x00000001); + /* Wait until the interrupt handler is finished */ + if (!nv_wait(clk, 0x000100, 0xffffffff, 0x00000000)) + return -EBUSY; + + if (pfifo) + pfifo->pause(pfifo, flags); + + if (!nv_wait(clk, 0x002504, 0x00000010, 0x00000010)) + return -EIO; + if (!nv_wait(clk, 0x00251c, 0x0000003f, 0x0000003f)) + return -EIO; + + return 0; +} + +void +gt215_clk_post(struct nvkm_clk *clk, unsigned long *flags) +{ + struct nvkm_fifo *pfifo = nvkm_fifo(clk); + + if (pfifo && flags) + pfifo->start(pfifo, flags); + + nv_mask(clk, 0x002504, 0x00000001, 0x00000000); + nv_mask(clk, 0x020060, 0x00070000, 0x00040000); +} + +static void +disable_clk_src(struct gt215_clk_priv *priv, u32 src) +{ + nv_mask(priv, src, 0x00000100, 0x00000000); + nv_mask(priv, src, 0x00000001, 0x00000000); +} + +static void +prog_pll(struct gt215_clk_priv *priv, int clk, u32 pll, int idx) +{ + struct gt215_clk_info *info = &priv->eng[idx]; + const u32 src0 = 0x004120 + (clk * 4); + const u32 src1 = 0x004160 + (clk * 4); + const u32 ctrl = pll + 0; + const u32 coef = pll + 4; + u32 bypass; + + if (info->pll) { + /* Always start from a non-PLL clock */ + bypass = nv_rd32(priv, ctrl) & 0x00000008; + if (!bypass) { + nv_mask(priv, src1, 0x00000101, 0x00000101); + nv_mask(priv, ctrl, 0x00000008, 0x00000008); + udelay(20); + } + + nv_mask(priv, src0, 0x003f3141, 0x00000101 | info->clk); + nv_wr32(priv, coef, info->pll); + nv_mask(priv, ctrl, 0x00000015, 0x00000015); + nv_mask(priv, ctrl, 0x00000010, 0x00000000); + if (!nv_wait(priv, ctrl, 0x00020000, 0x00020000)) { + nv_mask(priv, ctrl, 0x00000010, 0x00000010); + nv_mask(priv, src0, 0x00000101, 0x00000000); + return; + } + nv_mask(priv, ctrl, 0x00000010, 0x00000010); + nv_mask(priv, ctrl, 0x00000008, 0x00000000); + disable_clk_src(priv, src1); + } else { + nv_mask(priv, src1, 0x003f3141, 0x00000101 | info->clk); + nv_mask(priv, ctrl, 0x00000018, 0x00000018); + udelay(20); + nv_mask(priv, ctrl, 0x00000001, 0x00000000); + disable_clk_src(priv, src0); + } +} + +static void +prog_clk(struct gt215_clk_priv *priv, int clk, int idx) +{ + struct gt215_clk_info *info = &priv->eng[idx]; + nv_mask(priv, 0x004120 + (clk * 4), 0x003f3141, 0x00000101 | info->clk); +} + +static void +prog_host(struct gt215_clk_priv *priv) +{ + struct gt215_clk_info *info = &priv->eng[nv_clk_src_host]; + u32 hsrc = (nv_rd32(priv, 0xc040)); + + switch (info->host_out) { + case NVA3_HOST_277: + if ((hsrc & 0x30000000) == 0) { + nv_wr32(priv, 0xc040, hsrc | 0x20000000); + disable_clk_src(priv, 0x4194); + } + break; + case NVA3_HOST_CLK: + prog_clk(priv, 0x1d, nv_clk_src_host); + if ((hsrc & 0x30000000) >= 0x20000000) { + nv_wr32(priv, 0xc040, hsrc & ~0x30000000); + } + break; + default: + break; + } + + /* This seems to be a clock gating factor on idle, always set to 64 */ + nv_wr32(priv, 0xc044, 0x3e); +} + +static void +prog_core(struct gt215_clk_priv *priv, int idx) +{ + struct gt215_clk_info *info = &priv->eng[idx]; + u32 fb_delay = nv_rd32(priv, 0x10002c); + + if (fb_delay < info->fb_delay) + nv_wr32(priv, 0x10002c, info->fb_delay); + + prog_pll(priv, 0x00, 0x004200, idx); + + if (fb_delay > info->fb_delay) + nv_wr32(priv, 0x10002c, info->fb_delay); +} + +static int +gt215_clk_calc(struct nvkm_clk *clk, struct nvkm_cstate *cstate) +{ + struct gt215_clk_priv *priv = (void *)clk; + struct gt215_clk_info *core = &priv->eng[nv_clk_src_core]; + int ret; + + if ((ret = calc_clk(priv, cstate, 0x10, 0x4200, nv_clk_src_core)) || + (ret = calc_clk(priv, cstate, 0x11, 0x4220, nv_clk_src_shader)) || + (ret = calc_clk(priv, cstate, 0x20, 0x0000, nv_clk_src_disp)) || + (ret = calc_clk(priv, cstate, 0x21, 0x0000, nv_clk_src_vdec)) || + (ret = calc_host(priv, 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, 0x10, + cstate->domain[nv_clk_src_core_intm], + &priv->eng[nv_clk_src_core_intm]); + if (ret < 0) + return ret; + } + + return 0; +} + +static int +gt215_clk_prog(struct nvkm_clk *clk) +{ + struct gt215_clk_priv *priv = (void *)clk; + struct gt215_clk_info *core = &priv->eng[nv_clk_src_core]; + int ret = 0; + unsigned long flags; + unsigned long *f = &flags; + + ret = gt215_clk_pre(clk, f); + if (ret) + goto out; + + if (core->pll) + prog_core(priv, nv_clk_src_core_intm); + + prog_core(priv, nv_clk_src_core); + prog_pll(priv, 0x01, 0x004220, nv_clk_src_shader); + prog_clk(priv, 0x20, nv_clk_src_disp); + prog_clk(priv, 0x21, nv_clk_src_vdec); + prog_host(priv); + +out: + if (ret == -EBUSY) + f = NULL; + + gt215_clk_post(clk, f); + return ret; +} + +static void +gt215_clk_tidy(struct nvkm_clk *clk) +{ +} + +static struct nvkm_domain +gt215_domain[] = { + { 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 } +}; + +static int +gt215_clk_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct gt215_clk_priv *priv; + int ret; + + ret = nvkm_clk_create(parent, engine, oclass, gt215_domain, + NULL, 0, true, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.read = gt215_clk_read; + priv->base.calc = gt215_clk_calc; + priv->base.prog = gt215_clk_prog; + priv->base.tidy = gt215_clk_tidy; + return 0; +} + +struct nvkm_oclass +gt215_clk_oclass = { + .handle = NV_SUBDEV(CLK, 0xa3), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = gt215_clk_ctor, + .dtor = _nvkm_clk_dtor, + .init = _nvkm_clk_init, + .fini = _nvkm_clk_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gt215.h b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gt215.h new file mode 100644 index 000000000..b447d9cd4 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/gt215.h @@ -0,0 +1,18 @@ +#ifndef __NVKM_CLK_NVA3_H__ +#define __NVKM_CLK_NVA3_H__ +#include <subdev/clk.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 *clk, unsigned long *flags); +void gt215_clk_post(struct nvkm_clk *clk, unsigned long *flags); +#endif diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/mcp77.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/mcp77.c new file mode 100644 index 000000000..c54417b14 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/mcp77.c @@ -0,0 +1,429 @@ +/* + * 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 "gt215.h" +#include "pll.h" + +#include <core/device.h> +#include <subdev/bios.h> +#include <subdev/bios/pll.h> +#include <subdev/timer.h> + +struct mcp77_clk_priv { + 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 nvkm_clk *clk) +{ + return nv_rd32(clk, 0x004600); +} + +static u32 +read_pll(struct nvkm_clk *clk, u32 base) +{ + u32 ctrl = nv_rd32(clk, base + 0); + u32 coef = nv_rd32(clk, base + 4); + u32 ref = clk->read(clk, nv_clk_src_href); + u32 post_div = 0; + u32 clock = 0; + int N1, M1; + + switch (base){ + case 0x4020: + post_div = 1 << ((nv_rd32(clk, 0x4070) & 0x000f0000) >> 16); + break; + case 0x4028: + post_div = (nv_rd32(clk, 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 *clk, enum nv_clk_src src) +{ + struct mcp77_clk_priv *priv = (void *)clk; + u32 mast = nv_rd32(clk, 0x00c054); + u32 P = 0; + + switch (src) { + case nv_clk_src_crystal: + return nv_device(priv)->crystal; + case nv_clk_src_href: + return 100000; /* PCIE reference clock */ + case nv_clk_src_hclkm4: + return clk->read(clk, nv_clk_src_href) * 4; + case nv_clk_src_hclkm2d3: + return clk->read(clk, nv_clk_src_href) * 2 / 3; + case nv_clk_src_host: + switch (mast & 0x000c0000) { + case 0x00000000: return clk->read(clk, nv_clk_src_hclkm2d3); + case 0x00040000: break; + case 0x00080000: return clk->read(clk, nv_clk_src_hclkm4); + case 0x000c0000: return clk->read(clk, nv_clk_src_cclk); + } + break; + case nv_clk_src_core: + P = (nv_rd32(clk, 0x004028) & 0x00070000) >> 16; + + switch (mast & 0x00000003) { + case 0x00000000: return clk->read(clk, nv_clk_src_crystal) >> P; + case 0x00000001: return 0; + case 0x00000002: return clk->read(clk, nv_clk_src_hclkm4) >> P; + case 0x00000003: return read_pll(clk, 0x004028) >> P; + } + break; + case nv_clk_src_cclk: + if ((mast & 0x03000000) != 0x03000000) + return clk->read(clk, nv_clk_src_core); + + if ((mast & 0x00000200) == 0x00000000) + return clk->read(clk, nv_clk_src_core); + + switch (mast & 0x00000c00) { + case 0x00000000: return clk->read(clk, nv_clk_src_href); + case 0x00000400: return clk->read(clk, nv_clk_src_hclkm4); + case 0x00000800: return clk->read(clk, nv_clk_src_hclkm2d3); + default: return 0; + } + case nv_clk_src_shader: + P = (nv_rd32(clk, 0x004020) & 0x00070000) >> 16; + switch (mast & 0x00000030) { + case 0x00000000: + if (mast & 0x00000040) + return clk->read(clk, nv_clk_src_href) >> P; + return clk->read(clk, 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; + break; + case nv_clk_src_vdec: + P = (read_div(clk) & 0x00000700) >> 8; + + switch (mast & 0x00400000) { + case 0x00400000: + return clk->read(clk, nv_clk_src_core) >> P; + break; + default: + return 500000 >> P; + break; + } + break; + default: + break; + } + + nv_debug(priv, "unknown clock source %d 0x%08x\n", src, mast); + return 0; +} + +static u32 +calc_pll(struct mcp77_clk_priv *priv, u32 reg, + u32 clock, int *N, int *M, int *P) +{ + struct nvkm_bios *bios = nvkm_bios(priv); + struct nvbios_pll pll; + struct nvkm_clk *clk = &priv->base; + int ret; + + ret = nvbios_pll_parse(bios, reg, &pll); + if (ret) + return 0; + + pll.vco2.max_freq = 0; + pll.refclk = clk->read(clk, nv_clk_src_href); + if (!pll.refclk) + return 0; + + return nv04_pll_calc(nv_subdev(priv), &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 *clk, struct nvkm_cstate *cstate) +{ + struct mcp77_clk_priv *priv = (void *)clk; + 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]; + 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 < clk->read(clk, nv_clk_src_hclkm4)) + out = calc_P(clk->read(clk, nv_clk_src_hclkm4), core, &divs); + + /* Calculate clock * 2, so shader clock can use it too */ + clock = calc_pll(priv, 0x4028, (core << 1), &N, &M, &P1); + + if (abs(core - out) <= abs(core - (clock >> 1))) { + priv->csrc = nv_clk_src_hclkm4; + priv->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; + } + + priv->csrc = nv_clk_src_core; + priv->ccoef = (N << 8) | M; + + priv->cctrl = (P2 + 1) << 16; + priv->cpost = (1 << P1) << 16; + } + + /* sclk: nvpll + divisor, href or spll */ + out = 0; + if (shader == clk->read(clk, nv_clk_src_href)) { + priv->ssrc = nv_clk_src_href; + } else { + clock = calc_pll(priv, 0x4020, shader, &N, &M, &P1); + if (priv->csrc == nv_clk_src_core) + out = calc_P((core << 1), shader, &divs); + + if (abs(shader - out) <= + abs(shader - clock) && + (divs + P2) <= 7) { + priv->ssrc = nv_clk_src_core; + priv->sctrl = (divs + P2) << 16; + } else { + priv->ssrc = nv_clk_src_shader; + priv->scoef = (N << 8) | M; + priv->sctrl = P1 << 16; + } + } + + /* vclk */ + out = calc_P(core, vdec, &divs); + clock = calc_P(500000, vdec, &P1); + if(abs(vdec - out) <= abs(vdec - clock)) { + priv->vsrc = nv_clk_src_cclk; + priv->vdiv = divs << 16; + } else { + priv->vsrc = nv_clk_src_vdec; + priv->vdiv = P1 << 16; + } + + /* Print strategy! */ + nv_debug(priv, "nvpll: %08x %08x %08x\n", + priv->ccoef, priv->cpost, priv->cctrl); + nv_debug(priv, " spll: %08x %08x %08x\n", + priv->scoef, priv->spost, priv->sctrl); + nv_debug(priv, " vdiv: %08x\n", priv->vdiv); + if (priv->csrc == nv_clk_src_hclkm4) + nv_debug(priv, "core: hrefm4\n"); + else + nv_debug(priv, "core: nvpll\n"); + + if (priv->ssrc == nv_clk_src_hclkm4) + nv_debug(priv, "shader: hrefm4\n"); + else if (priv->ssrc == nv_clk_src_core) + nv_debug(priv, "shader: nvpll\n"); + else + nv_debug(priv, "shader: spll\n"); + + if (priv->vsrc == nv_clk_src_hclkm4) + nv_debug(priv, "vdec: 500MHz\n"); + else + nv_debug(priv, "vdec: core\n"); + + return 0; +} + +static int +mcp77_clk_prog(struct nvkm_clk *clk) +{ + struct mcp77_clk_priv *priv = (void *)clk; + u32 pllmask = 0, mast; + unsigned long flags; + unsigned long *f = &flags; + int ret = 0; + + ret = gt215_clk_pre(clk, f); + if (ret) + goto out; + + /* First switch to safe clocks: href */ + mast = nv_mask(clk, 0xc054, 0x03400e70, 0x03400640); + mast &= ~0x00400e73; + mast |= 0x03000000; + + switch (priv->csrc) { + case nv_clk_src_hclkm4: + nv_mask(clk, 0x4028, 0x00070000, priv->cctrl); + mast |= 0x00000002; + break; + case nv_clk_src_core: + nv_wr32(clk, 0x402c, priv->ccoef); + nv_wr32(clk, 0x4028, 0x80000000 | priv->cctrl); + nv_wr32(clk, 0x4040, priv->cpost); + pllmask |= (0x3 << 8); + mast |= 0x00000003; + break; + default: + nv_warn(priv,"Reclocking failed: unknown core clock\n"); + goto resume; + } + + switch (priv->ssrc) { + case nv_clk_src_href: + nv_mask(clk, 0x4020, 0x00070000, 0x00000000); + /* mast |= 0x00000000; */ + break; + case nv_clk_src_core: + nv_mask(clk, 0x4020, 0x00070000, priv->sctrl); + mast |= 0x00000020; + break; + case nv_clk_src_shader: + nv_wr32(clk, 0x4024, priv->scoef); + nv_wr32(clk, 0x4020, 0x80000000 | priv->sctrl); + nv_wr32(clk, 0x4070, priv->spost); + pllmask |= (0x3 << 12); + mast |= 0x00000030; + break; + default: + nv_warn(priv,"Reclocking failed: unknown sclk clock\n"); + goto resume; + } + + if (!nv_wait(clk, 0x004080, pllmask, pllmask)) { + nv_warn(priv,"Reclocking failed: unstable PLLs\n"); + goto resume; + } + + switch (priv->vsrc) { + case nv_clk_src_cclk: + mast |= 0x00400000; + default: + nv_wr32(clk, 0x4600, priv->vdiv); + } + + nv_wr32(clk, 0xc054, mast); + +resume: + /* Disable some PLLs and dividers when unused */ + if (priv->csrc != nv_clk_src_core) { + nv_wr32(clk, 0x4040, 0x00000000); + nv_mask(clk, 0x4028, 0x80000000, 0x00000000); + } + + if (priv->ssrc != nv_clk_src_shader) { + nv_wr32(clk, 0x4070, 0x00000000); + nv_mask(clk, 0x4020, 0x80000000, 0x00000000); + } + +out: + if (ret == -EBUSY) + f = NULL; + + gt215_clk_post(clk, f); + return ret; +} + +static void +mcp77_clk_tidy(struct nvkm_clk *clk) +{ +} + +static struct nvkm_domain +mcp77_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 } +}; + +static int +mcp77_clk_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct mcp77_clk_priv *priv; + int ret; + + ret = nvkm_clk_create(parent, engine, oclass, mcp77_domains, + NULL, 0, true, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.read = mcp77_clk_read; + priv->base.calc = mcp77_clk_calc; + priv->base.prog = mcp77_clk_prog; + priv->base.tidy = mcp77_clk_tidy; + return 0; +} + +struct nvkm_oclass * +mcp77_clk_oclass = &(struct nvkm_oclass) { + .handle = NV_SUBDEV(CLK, 0xaa), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = mcp77_clk_ctor, + .dtor = _nvkm_clk_dtor, + .init = _nvkm_clk_init, + .fini = _nvkm_clk_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv04.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv04.c new file mode 100644 index 000000000..63dbbb575 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv04.c @@ -0,0 +1,103 @@ +/* + * 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 <subdev/clk.h> +#include "pll.h" + +#include <subdev/bios.h> +#include <subdev/bios/pll.h> +#include <subdev/devinit/nv04.h> + +struct nv04_clk_priv { + struct nvkm_clk base; +}; + +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(nv_subdev(clock), 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_devinit *devinit = nvkm_devinit(clk); + int cv = nvkm_bios(clk)->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 struct nvkm_domain +nv04_domain[] = { + { nv_clk_src_max } +}; + +static int +nv04_clk_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct nv04_clk_priv *priv; + int ret; + + ret = nvkm_clk_create(parent, engine, oclass, nv04_domain, + NULL, 0, false, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.pll_calc = nv04_clk_pll_calc; + priv->base.pll_prog = nv04_clk_pll_prog; + return 0; +} + +struct nvkm_oclass +nv04_clk_oclass = { + .handle = NV_SUBDEV(CLK, 0x04), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = nv04_clk_ctor, + .dtor = _nvkm_clk_dtor, + .init = _nvkm_clk_init, + .fini = _nvkm_clk_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv40.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv40.c new file mode 100644 index 000000000..ed838130c --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv40.c @@ -0,0 +1,241 @@ +/* + * 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 <subdev/clk.h> +#include "pll.h" + +#include <core/device.h> +#include <subdev/bios.h> +#include <subdev/bios/pll.h> + +struct nv40_clk_priv { + struct nvkm_clk base; + u32 ctrl; + u32 npll_ctrl; + u32 npll_coef; + u32 spll; +}; + +static struct nvkm_domain +nv40_domain[] = { + { 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 } +}; + +static u32 +read_pll_1(struct nv40_clk_priv *priv, u32 reg) +{ + u32 ctrl = nv_rd32(priv, reg + 0x00); + int P = (ctrl & 0x00070000) >> 16; + int N = (ctrl & 0x0000ff00) >> 8; + int M = (ctrl & 0x000000ff) >> 0; + u32 ref = 27000, clk = 0; + + if (ctrl & 0x80000000) + clk = ref * N / M; + + return clk >> P; +} + +static u32 +read_pll_2(struct nv40_clk_priv *priv, u32 reg) +{ + u32 ctrl = nv_rd32(priv, reg + 0x00); + u32 coef = nv_rd32(priv, reg + 0x04); + int N2 = (coef & 0xff000000) >> 24; + int M2 = (coef & 0x00ff0000) >> 16; + int N1 = (coef & 0x0000ff00) >> 8; + int M1 = (coef & 0x000000ff) >> 0; + int P = (ctrl & 0x00070000) >> 16; + u32 ref = 27000, clk = 0; + + if ((ctrl & 0x80000000) && M1) { + clk = ref * N1 / M1; + if ((ctrl & 0x40000100) == 0x40000000) { + if (M2) + clk = clk * N2 / M2; + else + clk = 0; + } + } + + return clk >> P; +} + +static u32 +read_clk(struct nv40_clk_priv *priv, u32 src) +{ + switch (src) { + case 3: + return read_pll_2(priv, 0x004000); + case 2: + return read_pll_1(priv, 0x004008); + default: + break; + } + + return 0; +} + +static int +nv40_clk_read(struct nvkm_clk *clk, enum nv_clk_src src) +{ + struct nv40_clk_priv *priv = (void *)clk; + u32 mast = nv_rd32(priv, 0x00c040); + + switch (src) { + case nv_clk_src_crystal: + return nv_device(priv)->crystal; + case nv_clk_src_href: + return 100000; /*XXX: PCIE/AGP differ*/ + case nv_clk_src_core: + return read_clk(priv, (mast & 0x00000003) >> 0); + case nv_clk_src_shader: + return read_clk(priv, (mast & 0x00000030) >> 4); + case nv_clk_src_mem: + return read_pll_2(priv, 0x4020); + default: + break; + } + + nv_debug(priv, "unknown clock source %d 0x%08x\n", src, mast); + return -EINVAL; +} + +static int +nv40_clk_calc_pll(struct nv40_clk_priv *priv, u32 reg, u32 clk, + int *N1, int *M1, int *N2, int *M2, int *log2P) +{ + struct nvkm_bios *bios = nvkm_bios(priv); + struct nvbios_pll pll; + int ret; + + ret = nvbios_pll_parse(bios, reg, &pll); + if (ret) + return ret; + + if (clk < pll.vco1.max_freq) + pll.vco2.max_freq = 0; + + ret = nv04_pll_calc(nv_subdev(priv), &pll, clk, N1, M1, N2, M2, log2P); + if (ret == 0) + return -ERANGE; + + return ret; +} + +static int +nv40_clk_calc(struct nvkm_clk *clk, struct nvkm_cstate *cstate) +{ + struct nv40_clk_priv *priv = (void *)clk; + 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(priv, 0x004000, gclk, + &N1, &M1, &N2, &M2, &log2P); + if (ret < 0) + return ret; + + if (N2 == M2) { + priv->npll_ctrl = 0x80000100 | (log2P << 16); + priv->npll_coef = (N1 << 8) | M1; + } else { + priv->npll_ctrl = 0xc0000000 | (log2P << 16); + priv->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(priv, 0x004008, sclk, + &N1, &M1, NULL, NULL, &log2P); + if (ret < 0) + return ret; + + priv->spll = 0xc0000000 | (log2P << 16) | (N1 << 8) | M1; + priv->ctrl = 0x00000223; + } else { + priv->spll = 0x00000000; + priv->ctrl = 0x00000333; + } + + return 0; +} + +static int +nv40_clk_prog(struct nvkm_clk *clk) +{ + struct nv40_clk_priv *priv = (void *)clk; + nv_mask(priv, 0x00c040, 0x00000333, 0x00000000); + nv_wr32(priv, 0x004004, priv->npll_coef); + nv_mask(priv, 0x004000, 0xc0070100, priv->npll_ctrl); + nv_mask(priv, 0x004008, 0xc007ffff, priv->spll); + mdelay(5); + nv_mask(priv, 0x00c040, 0x00000333, priv->ctrl); + return 0; +} + +static void +nv40_clk_tidy(struct nvkm_clk *clk) +{ +} + +static int +nv40_clk_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct nv40_clk_priv *priv; + int ret; + + ret = nvkm_clk_create(parent, engine, oclass, nv40_domain, + NULL, 0, true, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.pll_calc = nv04_clk_pll_calc; + priv->base.pll_prog = nv04_clk_pll_prog; + priv->base.read = nv40_clk_read; + priv->base.calc = nv40_clk_calc; + priv->base.prog = nv40_clk_prog; + priv->base.tidy = nv40_clk_tidy; + return 0; +} + +struct nvkm_oclass +nv40_clk_oclass = { + .handle = NV_SUBDEV(CLK, 0x40), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = nv40_clk_ctor, + .dtor = _nvkm_clk_dtor, + .init = _nvkm_clk_init, + .fini = _nvkm_clk_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv50.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv50.c new file mode 100644 index 000000000..9b4ffd634 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv50.c @@ -0,0 +1,561 @@ +/* + * 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 <core/device.h> +#include <subdev/bios.h> +#include <subdev/bios/pll.h> + +static u32 +read_div(struct nv50_clk_priv *priv) +{ + switch (nv_device(priv)->chipset) { + case 0x50: /* it exists, but only has bit 31, not the dividers.. */ + case 0x84: + case 0x86: + case 0x98: + case 0xa0: + return nv_rd32(priv, 0x004700); + case 0x92: + case 0x94: + case 0x96: + return nv_rd32(priv, 0x004800); + default: + return 0x00000000; + } +} + +static u32 +read_pll_src(struct nv50_clk_priv *priv, u32 base) +{ + struct nvkm_clk *clk = &priv->base; + u32 coef, ref = clk->read(clk, nv_clk_src_crystal); + u32 rsel = nv_rd32(priv, 0x00e18c); + int P, N, M, id; + + switch (nv_device(priv)->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: + nv_error(priv, "ref: bad pll 0x%06x\n", base); + return 0; + } + + coef = nv_rd32(priv, 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 = nv_rd32(priv, 0x00e81c); + P = (coef & 0x00070000) >> 16; + N = (coef & 0x0000ff00) >> 8; + M = (coef & 0x000000ff) >> 0; + break; + case 0x94: + case 0x96: + case 0x98: + rsel = nv_rd32(priv, 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: + nv_error(priv, "ref: bad pll 0x%06x\n", base); + return 0; + } + + switch (rsel) { + case 0: id = 1; break; + case 1: return clk->read(clk, nv_clk_src_crystal); + case 2: return clk->read(clk, nv_clk_src_href); + case 3: id = 0; break; + } + + coef = nv_rd32(priv, 0x00e81c + (id * 0x28)); + P = (nv_rd32(priv, 0x00e824 + (id * 0x28)) >> 16) & 7; + P += (coef & 0x00070000) >> 16; + N = (coef & 0x0000ff00) >> 8; + M = (coef & 0x000000ff) >> 0; + break; + default: + BUG_ON(1); + } + + if (M) + return (ref * N / M) >> P; + + return 0; +} + +static u32 +read_pll_ref(struct nv50_clk_priv *priv, u32 base) +{ + struct nvkm_clk *clk = &priv->base; + u32 src, mast = nv_rd32(priv, 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 clk->read(clk, nv_clk_src_crystal); + default: + nv_error(priv, "bad pll 0x%06x\n", base); + return 0; + } + + if (src) + return clk->read(clk, nv_clk_src_href); + + return read_pll_src(priv, base); +} + +static u32 +read_pll(struct nv50_clk_priv *priv, u32 base) +{ + struct nvkm_clk *clk = &priv->base; + u32 mast = nv_rd32(priv, 0x00c040); + u32 ctrl = nv_rd32(priv, base + 0); + u32 coef = nv_rd32(priv, base + 4); + u32 ref = read_pll_ref(priv, base); + u32 freq = 0; + int N1, N2, M1, M2; + + if (base == 0x004028 && (mast & 0x00100000)) { + /* wtf, appears to only disable post-divider on gt200 */ + if (nv_device(priv)->chipset != 0xa0) + return clk->read(clk, 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; +} + +static int +nv50_clk_read(struct nvkm_clk *clk, enum nv_clk_src src) +{ + struct nv50_clk_priv *priv = (void *)clk; + u32 mast = nv_rd32(priv, 0x00c040); + u32 P = 0; + + switch (src) { + case nv_clk_src_crystal: + return nv_device(priv)->crystal; + case nv_clk_src_href: + return 100000; /* PCIE reference clock */ + case nv_clk_src_hclk: + return div_u64((u64)clk->read(clk, nv_clk_src_href) * 27778, 10000); + case nv_clk_src_hclkm3: + return clk->read(clk, nv_clk_src_hclk) * 3; + case nv_clk_src_hclkm3d2: + return clk->read(clk, nv_clk_src_hclk) * 3 / 2; + case nv_clk_src_host: + switch (mast & 0x30000000) { + case 0x00000000: return clk->read(clk, nv_clk_src_href); + case 0x10000000: break; + case 0x20000000: /* !0x50 */ + case 0x30000000: return clk->read(clk, nv_clk_src_hclk); + } + break; + case nv_clk_src_core: + if (!(mast & 0x00100000)) + P = (nv_rd32(priv, 0x004028) & 0x00070000) >> 16; + switch (mast & 0x00000003) { + case 0x00000000: return clk->read(clk, nv_clk_src_crystal) >> P; + case 0x00000001: return clk->read(clk, nv_clk_src_dom6); + case 0x00000002: return read_pll(priv, 0x004020) >> P; + case 0x00000003: return read_pll(priv, 0x004028) >> P; + } + break; + case nv_clk_src_shader: + P = (nv_rd32(priv, 0x004020) & 0x00070000) >> 16; + switch (mast & 0x00000030) { + case 0x00000000: + if (mast & 0x00000080) + return clk->read(clk, nv_clk_src_host) >> P; + return clk->read(clk, nv_clk_src_crystal) >> P; + case 0x00000010: break; + case 0x00000020: return read_pll(priv, 0x004028) >> P; + case 0x00000030: return read_pll(priv, 0x004020) >> P; + } + break; + case nv_clk_src_mem: + P = (nv_rd32(priv, 0x004008) & 0x00070000) >> 16; + if (nv_rd32(priv, 0x004008) & 0x00000200) { + switch (mast & 0x0000c000) { + case 0x00000000: + return clk->read(clk, nv_clk_src_crystal) >> P; + case 0x00008000: + case 0x0000c000: + return clk->read(clk, nv_clk_src_href) >> P; + } + } else { + return read_pll(priv, 0x004008) >> P; + } + break; + case nv_clk_src_vdec: + P = (read_div(priv) & 0x00000700) >> 8; + switch (nv_device(priv)->chipset) { + case 0x84: + case 0x86: + case 0x92: + case 0x94: + case 0x96: + case 0xa0: + switch (mast & 0x00000c00) { + case 0x00000000: + if (nv_device(priv)->chipset == 0xa0) /* wtf?? */ + return clk->read(clk, nv_clk_src_core) >> P; + return clk->read(clk, nv_clk_src_crystal) >> P; + case 0x00000400: + return 0; + case 0x00000800: + if (mast & 0x01000000) + return read_pll(priv, 0x004028) >> P; + return read_pll(priv, 0x004030) >> P; + case 0x00000c00: + return clk->read(clk, nv_clk_src_core) >> P; + } + break; + case 0x98: + switch (mast & 0x00000c00) { + case 0x00000000: + return clk->read(clk, nv_clk_src_core) >> P; + case 0x00000400: + return 0; + case 0x00000800: + return clk->read(clk, nv_clk_src_hclkm3d2) >> P; + case 0x00000c00: + return clk->read(clk, nv_clk_src_mem) >> P; + } + break; + } + break; + case nv_clk_src_dom6: + switch (nv_device(priv)->chipset) { + case 0x50: + case 0xa0: + return read_pll(priv, 0x00e810) >> 2; + case 0x84: + case 0x86: + case 0x92: + case 0x94: + case 0x96: + case 0x98: + P = (read_div(priv) & 0x00000007) >> 0; + switch (mast & 0x0c000000) { + case 0x00000000: return clk->read(clk, nv_clk_src_href); + case 0x04000000: break; + case 0x08000000: return clk->read(clk, nv_clk_src_hclk); + case 0x0c000000: + return clk->read(clk, nv_clk_src_hclkm3) >> P; + } + break; + default: + break; + } + default: + break; + } + + nv_debug(priv, "unknown clock source %d 0x%08x\n", src, mast); + return -EINVAL; +} + +static u32 +calc_pll(struct nv50_clk_priv *priv, u32 reg, u32 clk, int *N, int *M, int *P) +{ + struct nvkm_bios *bios = nvkm_bios(priv); + struct nvbios_pll pll; + int ret; + + ret = nvbios_pll_parse(bios, reg, &pll); + if (ret) + return 0; + + pll.vco2.max_freq = 0; + pll.refclk = read_pll_ref(priv, reg); + if (!pll.refclk) + return 0; + + return nv04_pll_calc(nv_subdev(priv), &pll, clk, 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)); +} + +static int +nv50_clk_calc(struct nvkm_clk *clk, struct nvkm_cstate *cstate) +{ + struct nv50_clk_priv *priv = (void *)clk; + struct nv50_clk_hwsq *hwsq = &priv->hwsq; + 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, nv_subdev(clk)); + 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 (nv_device(priv)->chipset != 0x98) + out = read_pll(priv, 0x004030); + else + out = clk->read(clk, nv_clk_src_hclkm3d2); + out = calc_div(out, vdec, &P2); + + /* select whichever gets us closest */ + if (abs(vdec - freq) <= abs(vdec - out)) { + if (nv_device(priv)->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, clk->read(clk, nv_clk_src_href))) { + mastv |= 0x00000000; + } else + if (clk_same(dom6, clk->read(clk, nv_clk_src_hclk))) { + mastv |= 0x08000000; + } else { + freq = clk->read(clk, nv_clk_src_hclk) * 3; + freq = 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 (nv_device(priv)->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(priv, 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(priv, 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; +} + +static int +nv50_clk_prog(struct nvkm_clk *clk) +{ + struct nv50_clk_priv *priv = (void *)clk; + return clk_exec(&priv->hwsq, true); +} + +static void +nv50_clk_tidy(struct nvkm_clk *clk) +{ + struct nv50_clk_priv *priv = (void *)clk; + clk_exec(&priv->hwsq, false); +} + +int +nv50_clk_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct nv50_clk_oclass *pclass = (void *)oclass; + struct nv50_clk_priv *priv; + int ret; + + ret = nvkm_clk_create(parent, engine, oclass, pclass->domains, + NULL, 0, false, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->hwsq.r_fifo = hwsq_reg(0x002504); + priv->hwsq.r_spll[0] = hwsq_reg(0x004020); + priv->hwsq.r_spll[1] = hwsq_reg(0x004024); + priv->hwsq.r_nvpll[0] = hwsq_reg(0x004028); + priv->hwsq.r_nvpll[1] = hwsq_reg(0x00402c); + switch (nv_device(priv)->chipset) { + case 0x92: + case 0x94: + case 0x96: + priv->hwsq.r_divs = hwsq_reg(0x004800); + break; + default: + priv->hwsq.r_divs = hwsq_reg(0x004700); + break; + } + priv->hwsq.r_mast = hwsq_reg(0x00c040); + + priv->base.read = nv50_clk_read; + priv->base.calc = nv50_clk_calc; + priv->base.prog = nv50_clk_prog; + priv->base.tidy = nv50_clk_tidy; + return 0; +} + +static struct nvkm_domain +nv50_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 } +}; + +struct nvkm_oclass * +nv50_clk_oclass = &(struct nv50_clk_oclass) { + .base.handle = NV_SUBDEV(CLK, 0x50), + .base.ofuncs = &(struct nvkm_ofuncs) { + .ctor = nv50_clk_ctor, + .dtor = _nvkm_clk_dtor, + .init = _nvkm_clk_init, + .fini = _nvkm_clk_fini, + }, + .domains = nv50_domains, +}.base; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv50.h b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv50.h new file mode 100644 index 000000000..0ead76a32 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/nv50.h @@ -0,0 +1,28 @@ +#ifndef __NVKM_CLK_NV50_H__ +#define __NVKM_CLK_NV50_H__ +#include <subdev/bus/hwsq.h> +#include <subdev/clk.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_priv { + struct nvkm_clk base; + struct nv50_clk_hwsq hwsq; +}; + +int nv50_clk_ctor(struct nvkm_object *, struct nvkm_object *, + struct nvkm_oclass *, void *, u32, + struct nvkm_object **); + +struct nv50_clk_oclass { + struct nvkm_oclass base; + struct nvkm_domain *domains; +}; +#endif diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/pll.h b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/pll.h new file mode 100644 index 000000000..44020a30d --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/pll.h @@ -0,0 +1,11 @@ +#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 diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/pllgt215.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/pllgt215.c new file mode 100644 index 000000000..783a3e78d --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/pllgt215.c @@ -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)) { + nv_error(subdev, "unable to find matching pll values\n"); + return -EINVAL; + } + + return info->refclk * *pN / *pM / *P; +} diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/pllnv04.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/pllnv04.c new file mode 100644 index 000000000..f2292895a --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/pllnv04.c @@ -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 = nvkm_bios(subdev); + 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 = nvkm_bios(subdev)->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) + nv_error(subdev, "unable to compute acceptable pll values\n"); + return ret; +} diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/seq.h b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/seq.h new file mode 100644 index 000000000..d717e8b8f --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/clk/seq.h @@ -0,0 +1,14 @@ +#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 |