diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nvkm/subdev/pmu/gk20a.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/nvkm/subdev/pmu/gk20a.c | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/gk20a.c b/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/gk20a.c new file mode 100644 index 000000000..a67a42e73 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/gk20a.c @@ -0,0 +1,230 @@ +/* + * 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. + */ +#define gk20a_pmu(p) container_of((p), struct gk20a_pmu, base) +#include "priv.h" + +#include <subdev/clk.h> +#include <subdev/timer.h> +#include <subdev/volt.h> + +#define BUSY_SLOT 0 +#define CLK_SLOT 7 + +struct gk20a_pmu_dvfs_data { + int p_load_target; + int p_load_max; + int p_smooth; + unsigned int avg_load; +}; + +struct gk20a_pmu { + struct nvkm_pmu base; + struct nvkm_alarm alarm; + struct gk20a_pmu_dvfs_data *data; +}; + +struct gk20a_pmu_dvfs_dev_status { + u32 total; + u32 busy; +}; + +static int +gk20a_pmu_dvfs_target(struct gk20a_pmu *pmu, int *state) +{ + struct nvkm_clk *clk = pmu->base.subdev.device->clk; + + return nvkm_clk_astate(clk, *state, 0, false); +} + +static void +gk20a_pmu_dvfs_get_cur_state(struct gk20a_pmu *pmu, int *state) +{ + struct nvkm_clk *clk = pmu->base.subdev.device->clk; + + *state = clk->pstate; +} + +static int +gk20a_pmu_dvfs_get_target_state(struct gk20a_pmu *pmu, + int *state, int load) +{ + struct gk20a_pmu_dvfs_data *data = pmu->data; + struct nvkm_clk *clk = pmu->base.subdev.device->clk; + int cur_level, level; + + /* For GK20A, the performance level is directly mapped to pstate */ + level = cur_level = clk->pstate; + + if (load > data->p_load_max) { + level = min(clk->state_nr - 1, level + (clk->state_nr / 3)); + } else { + level += ((load - data->p_load_target) * 10 / + data->p_load_target) / 2; + level = max(0, level); + level = min(clk->state_nr - 1, level); + } + + nvkm_trace(&pmu->base.subdev, "cur level = %d, new level = %d\n", + cur_level, level); + + *state = level; + + return (level != cur_level); +} + +static void +gk20a_pmu_dvfs_get_dev_status(struct gk20a_pmu *pmu, + struct gk20a_pmu_dvfs_dev_status *status) +{ + struct nvkm_falcon *falcon = &pmu->base.falcon; + + status->busy = nvkm_falcon_rd32(falcon, 0x508 + (BUSY_SLOT * 0x10)); + status->total= nvkm_falcon_rd32(falcon, 0x508 + (CLK_SLOT * 0x10)); +} + +static void +gk20a_pmu_dvfs_reset_dev_status(struct gk20a_pmu *pmu) +{ + struct nvkm_falcon *falcon = &pmu->base.falcon; + + nvkm_falcon_wr32(falcon, 0x508 + (BUSY_SLOT * 0x10), 0x80000000); + nvkm_falcon_wr32(falcon, 0x508 + (CLK_SLOT * 0x10), 0x80000000); +} + +static void +gk20a_pmu_dvfs_work(struct nvkm_alarm *alarm) +{ + struct gk20a_pmu *pmu = + container_of(alarm, struct gk20a_pmu, alarm); + struct gk20a_pmu_dvfs_data *data = pmu->data; + struct gk20a_pmu_dvfs_dev_status status; + struct nvkm_subdev *subdev = &pmu->base.subdev; + struct nvkm_device *device = subdev->device; + struct nvkm_clk *clk = device->clk; + struct nvkm_timer *tmr = device->timer; + struct nvkm_volt *volt = device->volt; + u32 utilization = 0; + int state; + + /* + * The PMU is initialized before CLK and VOLT, so we have to make sure the + * CLK and VOLT are ready here. + */ + if (!clk || !volt) + goto resched; + + gk20a_pmu_dvfs_get_dev_status(pmu, &status); + + if (status.total) + utilization = div_u64((u64)status.busy * 100, status.total); + + data->avg_load = (data->p_smooth * data->avg_load) + utilization; + data->avg_load /= data->p_smooth + 1; + nvkm_trace(subdev, "utilization = %d %%, avg_load = %d %%\n", + utilization, data->avg_load); + + gk20a_pmu_dvfs_get_cur_state(pmu, &state); + + if (gk20a_pmu_dvfs_get_target_state(pmu, &state, data->avg_load)) { + nvkm_trace(subdev, "set new state to %d\n", state); + gk20a_pmu_dvfs_target(pmu, &state); + } + +resched: + gk20a_pmu_dvfs_reset_dev_status(pmu); + nvkm_timer_alarm(tmr, 100000000, alarm); +} + +static void +gk20a_pmu_fini(struct nvkm_pmu *pmu) +{ + struct gk20a_pmu *gpmu = gk20a_pmu(pmu); + nvkm_timer_alarm(pmu->subdev.device->timer, 0, &gpmu->alarm); + + nvkm_falcon_put(&pmu->falcon, &pmu->subdev); +} + +static int +gk20a_pmu_init(struct nvkm_pmu *pmu) +{ + struct gk20a_pmu *gpmu = gk20a_pmu(pmu); + struct nvkm_subdev *subdev = &pmu->subdev; + struct nvkm_device *device = pmu->subdev.device; + struct nvkm_falcon *falcon = &pmu->falcon; + int ret; + + ret = nvkm_falcon_get(falcon, subdev); + if (ret) { + nvkm_error(subdev, "cannot acquire %s falcon!\n", falcon->name); + return ret; + } + + /* init pwr perf counter */ + nvkm_falcon_wr32(falcon, 0x504 + (BUSY_SLOT * 0x10), 0x00200001); + nvkm_falcon_wr32(falcon, 0x50c + (BUSY_SLOT * 0x10), 0x00000002); + nvkm_falcon_wr32(falcon, 0x50c + (CLK_SLOT * 0x10), 0x00000003); + + nvkm_timer_alarm(device->timer, 2000000000, &gpmu->alarm); + return 0; +} + +static struct gk20a_pmu_dvfs_data +gk20a_dvfs_data= { + .p_load_target = 70, + .p_load_max = 90, + .p_smooth = 1, +}; + +static const struct nvkm_pmu_func +gk20a_pmu = { + .flcn = >215_pmu_flcn, + .enabled = gf100_pmu_enabled, + .init = gk20a_pmu_init, + .fini = gk20a_pmu_fini, + .reset = gf100_pmu_reset, +}; + +static const struct nvkm_pmu_fwif +gk20a_pmu_fwif[] = { + { -1, gf100_pmu_nofw, &gk20a_pmu }, + {} +}; + +int +gk20a_pmu_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, + struct nvkm_pmu **ppmu) +{ + struct gk20a_pmu *pmu; + int ret; + + if (!(pmu = kzalloc(sizeof(*pmu), GFP_KERNEL))) + return -ENOMEM; + *ppmu = &pmu->base; + + ret = nvkm_pmu_ctor(gk20a_pmu_fwif, device, type, inst, &pmu->base); + if (ret) + return ret; + + pmu->data = &gk20a_dvfs_data; + nvkm_alarm_init(&pmu->alarm, gk20a_pmu_dvfs_work); + return 0; +} |