mirror of
git://nv-tegra.nvidia.com/linux-nvgpu.git
synced 2025-12-24 02:22:34 +03:00
(1) modified counters debug code to export to the driver (2) modified arbiter to read from those functions (3) modified counter for higher accuracy on MHz range JIRA DNVGPU-164 Change-Id: I2bbf7c9be4dc59718d1d91c53028a39020b5aea5 Signed-off-by: David Nieto <dmartineznie@nvidia.com> Reviewed-on: http://git-master/r/1239467 GVS: Gerrit_Virtual_Submit Reviewed-by: Thomas Fleury <tfleury@nvidia.com> Reviewed-by: Terje Bergstrom <tbergstrom@nvidia.com> Reviewed-on: http://git-master/r/1268008
1014 lines
23 KiB
C
1014 lines
23 KiB
C
/*
|
|
* Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*/
|
|
|
|
#include "gk20a/gk20a.h"
|
|
|
|
#include <linux/cdev.h>
|
|
#include <linux/file.h>
|
|
#include <linux/anon_inodes.h>
|
|
#include <linux/nvgpu.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
#include "clk/clk_arb.h"
|
|
|
|
|
|
#define MAX_F_POINTS 127
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int nvgpu_clk_arb_debugfs_init(struct gk20a *g);
|
|
#endif
|
|
|
|
static int nvgpu_clk_arb_release_event_dev(struct inode *inode,
|
|
struct file *filp);
|
|
static int nvgpu_clk_arb_release_completion_dev(struct inode *inode,
|
|
struct file *filp);
|
|
static unsigned int nvgpu_clk_arb_poll_dev(struct file *filp, poll_table *wait);
|
|
|
|
static void nvgpu_clk_arb_run_arbiter_cb(struct work_struct *work);
|
|
static void nvgpu_clk_arb_run_vftable_cb(struct work_struct *work);
|
|
static int nvgpu_clk_arb_update_vftable(struct nvgpu_clk_arb *);
|
|
|
|
struct nvgpu_clk_vf_point {
|
|
u16 mhz;
|
|
u32 uvolt;
|
|
u32 uvolt_sram;
|
|
};
|
|
|
|
struct nvgpu_clk_arb {
|
|
spinlock_t sessions_lock;
|
|
spinlock_t users_lock;
|
|
spinlock_t req_lock;
|
|
|
|
struct list_head users;
|
|
struct list_head sessions;
|
|
struct list_head requests;
|
|
|
|
struct gk20a *g;
|
|
spinlock_t data_lock;
|
|
spinlock_t vf_lock;
|
|
|
|
u16 gpc2clk_actual_mhz;
|
|
u16 gpc2clk_default_mhz;
|
|
|
|
u16 mclk_actual_mhz;
|
|
u16 mclk_default_mhz;
|
|
u32 voltuv_actual;
|
|
|
|
struct work_struct update_fn_work;
|
|
struct work_struct vftable_fn_work;
|
|
wait_queue_head_t vftable_wq;
|
|
|
|
u16 *mclk_f_points;
|
|
bool vftable_set;
|
|
|
|
struct nvgpu_clk_vf_point *mclk_vf_points;
|
|
u32 mclk_f_numpoints;
|
|
u16 *gpc2clk_f_points;
|
|
u32 gpc2clk_f_numpoints;
|
|
struct nvgpu_clk_vf_point *gpc2clk_vf_points;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
struct mutex debug_lock;
|
|
s64 switch_max;
|
|
s64 switch_min;
|
|
u64 switch_num;
|
|
s64 switch_avg;
|
|
s64 switch_std;
|
|
bool debugfs_set;
|
|
#endif
|
|
};
|
|
|
|
|
|
struct nvgpu_clk_dev {
|
|
struct nvgpu_clk_session *session;
|
|
struct list_head link;
|
|
wait_queue_head_t readout_wq;
|
|
atomic_t poll_mask;
|
|
u16 gpc2clk_target_mhz;
|
|
u16 mclk_target_mhz;
|
|
};
|
|
|
|
struct nvgpu_clk_session {
|
|
bool zombie;
|
|
struct gk20a *g;
|
|
struct kref refcount;
|
|
struct list_head link;
|
|
struct list_head targets;
|
|
|
|
spinlock_t target_lock;
|
|
u16 gpc2clk_target_mhz;
|
|
u16 mclk_target_mhz;
|
|
};
|
|
|
|
static const struct file_operations completion_dev_ops = {
|
|
.owner = THIS_MODULE,
|
|
.release = nvgpu_clk_arb_release_completion_dev,
|
|
.poll = nvgpu_clk_arb_poll_dev,
|
|
};
|
|
|
|
static const struct file_operations event_dev_ops = {
|
|
.owner = THIS_MODULE,
|
|
.release = nvgpu_clk_arb_release_event_dev,
|
|
.poll = nvgpu_clk_arb_poll_dev,
|
|
};
|
|
|
|
int nvgpu_clk_arb_init_arbiter(struct gk20a *g)
|
|
{
|
|
struct nvgpu_clk_arb *arb;
|
|
u16 default_mhz;
|
|
int err;
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
if (!g->ops.clk_arb.get_arbiter_clk_domains)
|
|
return 0;
|
|
|
|
arb = kzalloc(sizeof(struct nvgpu_clk_arb), GFP_KERNEL);
|
|
if (!arb) {
|
|
err = -ENOMEM;
|
|
goto init_fail;
|
|
}
|
|
|
|
arb->gpc2clk_f_numpoints = MAX_F_POINTS;
|
|
arb->mclk_f_numpoints = MAX_F_POINTS;
|
|
|
|
arb->gpc2clk_f_points = kcalloc(MAX_F_POINTS, sizeof(u16), GFP_KERNEL);
|
|
if (!arb->gpc2clk_f_points) {
|
|
err = -ENOMEM;
|
|
goto init_fail;
|
|
}
|
|
|
|
arb->mclk_f_points = kcalloc(MAX_F_POINTS, sizeof(u16), GFP_KERNEL);
|
|
if (!arb->mclk_f_points) {
|
|
err = -ENOMEM;
|
|
goto init_fail;
|
|
}
|
|
|
|
arb->gpc2clk_vf_points = kcalloc(MAX_F_POINTS,
|
|
sizeof(struct nvgpu_clk_vf_point), GFP_KERNEL);
|
|
if (!arb->gpc2clk_vf_points) {
|
|
err = -ENOMEM;
|
|
goto init_fail;
|
|
}
|
|
|
|
arb->mclk_vf_points = kcalloc(MAX_F_POINTS,
|
|
sizeof(struct nvgpu_clk_vf_point), GFP_KERNEL);
|
|
if (!arb->mclk_vf_points) {
|
|
err = -ENOMEM;
|
|
goto init_fail;
|
|
}
|
|
|
|
g->clk_arb = arb;
|
|
arb->g = g;
|
|
|
|
spin_lock_init(&arb->sessions_lock);
|
|
spin_lock_init(&arb->users_lock);
|
|
spin_lock_init(&arb->req_lock);
|
|
spin_lock_init(&arb->data_lock);
|
|
spin_lock_init(&arb->vf_lock);
|
|
|
|
err = g->ops.clk_arb.get_arbiter_clk_default(g,
|
|
NVGPU_GPU_CLK_DOMAIN_MCLK, &default_mhz);
|
|
if (err) {
|
|
err = -EINVAL;
|
|
goto init_fail;
|
|
}
|
|
|
|
arb->mclk_default_mhz = default_mhz;
|
|
|
|
err = g->ops.clk_arb.get_arbiter_clk_default(g,
|
|
NVGPU_GPU_CLK_DOMAIN_GPC2CLK, &default_mhz);
|
|
if (err) {
|
|
err = -EINVAL;
|
|
goto init_fail;
|
|
}
|
|
|
|
arb->gpc2clk_default_mhz = default_mhz;
|
|
|
|
INIT_LIST_HEAD(&arb->users);
|
|
INIT_LIST_HEAD(&arb->sessions);
|
|
INIT_LIST_HEAD(&arb->requests);
|
|
|
|
init_waitqueue_head(&arb->vftable_wq);
|
|
|
|
INIT_WORK(&arb->vftable_fn_work, nvgpu_clk_arb_run_vftable_cb);
|
|
|
|
INIT_WORK(&arb->update_fn_work, nvgpu_clk_arb_run_arbiter_cb);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
mutex_init(&arb->debug_lock);
|
|
if (!arb->debugfs_set) {
|
|
if (nvgpu_clk_arb_debugfs_init(g))
|
|
arb->debugfs_set = true;
|
|
}
|
|
#endif
|
|
err = nvgpu_clk_arb_update_vftable(arb);
|
|
if (err < 0)
|
|
goto init_fail;
|
|
|
|
/* Schedule first run */
|
|
schedule_work(&arb->update_fn_work);
|
|
|
|
return 0;
|
|
|
|
init_fail:
|
|
|
|
kfree(arb->gpc2clk_f_points);
|
|
kfree(arb->gpc2clk_vf_points);
|
|
|
|
kfree(arb->mclk_f_points);
|
|
kfree(arb->mclk_vf_points);
|
|
|
|
kfree(arb);
|
|
|
|
return err;
|
|
}
|
|
|
|
void nvgpu_clk_arb_cleanup_arbiter(struct gk20a *g)
|
|
{
|
|
kfree(g->clk_arb);
|
|
}
|
|
|
|
static int nvgpu_clk_arb_install_fd(struct gk20a *g,
|
|
struct nvgpu_clk_session *session,
|
|
const struct file_operations *fops,
|
|
struct nvgpu_clk_dev **_dev)
|
|
{
|
|
struct file *file;
|
|
char *name;
|
|
int fd;
|
|
int err;
|
|
struct nvgpu_clk_dev *dev;
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
fd = get_unused_fd_flags(O_RDWR);
|
|
if (fd < 0)
|
|
return fd;
|
|
|
|
name = kasprintf(GFP_KERNEL, "%s-clk-fd%d", dev_name(g->dev), fd);
|
|
file = anon_inode_getfile(name, fops, dev, O_RDWR);
|
|
kfree(name);
|
|
if (IS_ERR(file)) {
|
|
err = PTR_ERR(file);
|
|
goto fail;
|
|
}
|
|
|
|
fd_install(fd, file);
|
|
|
|
init_waitqueue_head(&dev->readout_wq);
|
|
atomic_set(&dev->poll_mask, 0);
|
|
|
|
dev->session = session;
|
|
kref_get(&session->refcount);
|
|
|
|
*_dev = dev;
|
|
|
|
return fd;
|
|
|
|
fail:
|
|
kfree(dev);
|
|
put_unused_fd(fd);
|
|
|
|
return err;
|
|
}
|
|
|
|
int nvgpu_clk_arb_init_session(struct gk20a *g,
|
|
struct nvgpu_clk_session **_session)
|
|
{
|
|
struct nvgpu_clk_arb *arb = g->clk_arb;
|
|
struct nvgpu_clk_session *session = *(_session);
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
if (!g->ops.clk_arb.get_arbiter_clk_domains)
|
|
return 0;
|
|
|
|
session = kzalloc(sizeof(struct nvgpu_clk_session), GFP_KERNEL);
|
|
if (!session)
|
|
return -ENOMEM;
|
|
session->g = g;
|
|
|
|
kref_init(&session->refcount);
|
|
spin_lock_init(&session->target_lock);
|
|
|
|
session->zombie = false;
|
|
session->mclk_target_mhz = arb->mclk_default_mhz;
|
|
session->gpc2clk_target_mhz = arb->gpc2clk_default_mhz;
|
|
INIT_LIST_HEAD(&session->targets);
|
|
|
|
spin_lock(&arb->sessions_lock);
|
|
list_add_tail(&session->link, &arb->sessions);
|
|
spin_unlock(&arb->sessions_lock);
|
|
|
|
*_session = session;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void nvgpu_clk_arb_free_session(struct kref *refcount)
|
|
{
|
|
struct nvgpu_clk_session *session = container_of(refcount,
|
|
struct nvgpu_clk_session, refcount);
|
|
struct nvgpu_clk_arb *arb = session->g->clk_arb;
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
spin_lock(&arb->sessions_lock);
|
|
list_del(&session->link);
|
|
spin_unlock(&arb->sessions_lock);
|
|
kfree(session);
|
|
;
|
|
}
|
|
|
|
void nvgpu_clk_arb_release_session(struct gk20a *g,
|
|
struct nvgpu_clk_session *session)
|
|
{
|
|
struct nvgpu_clk_arb *arb = g->clk_arb;
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
session->zombie = true;
|
|
kref_put(&session->refcount, nvgpu_clk_arb_free_session);
|
|
|
|
schedule_work(&arb->update_fn_work);
|
|
}
|
|
|
|
int nvgpu_clk_arb_install_event_fd(struct gk20a *g,
|
|
struct nvgpu_clk_session *session, int *event_fd)
|
|
{
|
|
struct nvgpu_clk_arb *arb = g->clk_arb;
|
|
struct nvgpu_clk_dev *dev;
|
|
int fd;
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
fd = nvgpu_clk_arb_install_fd(g, session, &event_dev_ops, &dev);
|
|
if (fd < 0)
|
|
return fd;
|
|
|
|
spin_lock(&arb->users_lock);
|
|
list_add_tail(&dev->link, &arb->users);
|
|
spin_unlock(&arb->users_lock);
|
|
|
|
*event_fd = fd;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nvgpu_clk_arb_install_request_fd(struct gk20a *g,
|
|
struct nvgpu_clk_session *session, int *request_fd)
|
|
{
|
|
struct nvgpu_clk_dev *dev;
|
|
int fd;
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
fd = nvgpu_clk_arb_install_fd(g, session, &completion_dev_ops, &dev);
|
|
if (fd < 0)
|
|
return fd;
|
|
|
|
*request_fd = fd;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nvgpu_clk_arb_update_vftable(struct nvgpu_clk_arb *arb)
|
|
{
|
|
struct gk20a *g = arb->g;
|
|
|
|
int i;
|
|
int status = 0;
|
|
u32 gpc2clk_voltuv = 0, mclk_voltuv = 0;
|
|
u32 gpc2clk_voltuv_sram = 0, mclk_voltuv_sram = 0;
|
|
|
|
/* the flag must be visible in all threads */
|
|
mb();
|
|
ACCESS_ONCE(arb->vftable_set) = false;
|
|
|
|
spin_lock(&arb->vf_lock);
|
|
|
|
if (!clk_domain_get_f_points(arb->g, NVGPU_GPU_CLK_DOMAIN_GPC2CLK,
|
|
&arb->gpc2clk_f_numpoints, arb->gpc2clk_f_points) < 0) {
|
|
gk20a_err(dev_from_gk20a(g),
|
|
"failed to fetch GPC2CLK frequency points");
|
|
goto exit_vftable;
|
|
}
|
|
if (clk_domain_get_f_points(arb->g, NVGPU_GPU_CLK_DOMAIN_MCLK,
|
|
&arb->mclk_f_numpoints, arb->mclk_f_points) < 0) {
|
|
gk20a_err(dev_from_gk20a(g),
|
|
"failed to fetch MCLK frequency points");
|
|
goto exit_vftable;
|
|
}
|
|
|
|
|
|
memset(arb->mclk_vf_points, 0,
|
|
arb->mclk_f_numpoints*sizeof(struct nvgpu_clk_vf_point));
|
|
memset(arb->gpc2clk_vf_points, 0,
|
|
arb->gpc2clk_f_numpoints*sizeof(struct nvgpu_clk_vf_point));
|
|
|
|
for (i = 0 ; i < arb->mclk_f_numpoints; i++) {
|
|
arb->mclk_vf_points[i].mhz = arb->mclk_f_points[i];
|
|
mclk_voltuv = mclk_voltuv_sram = 0;
|
|
|
|
status = clk_domain_get_f_or_v(g, CTRL_CLK_DOMAIN_MCLK,
|
|
&arb->mclk_vf_points[i].mhz, &mclk_voltuv,
|
|
CTRL_VOLT_DOMAIN_LOGIC);
|
|
if (status < 0) {
|
|
gk20a_err(dev_from_gk20a(g),
|
|
"failed to get MCLK LOGIC voltage");
|
|
goto exit_vftable;
|
|
}
|
|
status = clk_domain_get_f_or_v(g, CTRL_CLK_DOMAIN_MCLK,
|
|
&arb->mclk_vf_points[i].mhz, &mclk_voltuv_sram,
|
|
CTRL_VOLT_DOMAIN_SRAM);
|
|
if (status < 0) {
|
|
gk20a_err(dev_from_gk20a(g),
|
|
"failed to get MCLK SRAM voltage");
|
|
goto exit_vftable;
|
|
}
|
|
|
|
arb->mclk_vf_points[i].uvolt = mclk_voltuv;
|
|
arb->mclk_vf_points[i].uvolt_sram = mclk_voltuv_sram;
|
|
}
|
|
|
|
for (i = 0 ; i < arb->gpc2clk_f_numpoints; i++) {
|
|
arb->gpc2clk_vf_points[i].mhz = arb->gpc2clk_f_points[i];
|
|
gpc2clk_voltuv = gpc2clk_voltuv_sram = 0;
|
|
|
|
status = clk_domain_get_f_or_v(g, CTRL_CLK_DOMAIN_GPC2CLK,
|
|
&arb->gpc2clk_vf_points[i].mhz, &gpc2clk_voltuv,
|
|
CTRL_VOLT_DOMAIN_LOGIC);
|
|
if (status < 0) {
|
|
gk20a_err(dev_from_gk20a(g),
|
|
"failed to get GPC2CLK LOGIC voltage");
|
|
goto exit_vftable;
|
|
}
|
|
status = clk_domain_get_f_or_v(g, CTRL_CLK_DOMAIN_GPC2CLK,
|
|
&arb->gpc2clk_vf_points[i].mhz, &gpc2clk_voltuv_sram,
|
|
CTRL_VOLT_DOMAIN_SRAM);
|
|
if (status < 0) {
|
|
gk20a_err(dev_from_gk20a(g),
|
|
"failed to get GPC2CLK SRAM voltage");
|
|
goto exit_vftable;
|
|
}
|
|
|
|
arb->gpc2clk_vf_points[i].uvolt = gpc2clk_voltuv;
|
|
arb->gpc2clk_vf_points[i].uvolt_sram = gpc2clk_voltuv_sram;
|
|
|
|
}
|
|
|
|
/* make flag visible when all data has resolved in the tables */
|
|
wmb();
|
|
ACCESS_ONCE(arb->vftable_set) = true;
|
|
|
|
wake_up(&arb->vftable_wq);
|
|
exit_vftable:
|
|
|
|
spin_unlock(&arb->vf_lock);
|
|
|
|
return status;
|
|
}
|
|
|
|
void nvgpu_clk_arb_schedule_vftable_update(struct gk20a *g)
|
|
{
|
|
struct nvgpu_clk_arb *arb = g->clk_arb;
|
|
|
|
ACCESS_ONCE(arb->vftable_set) = false;
|
|
/* Disable the flag in case arbiter gets scheduled first */
|
|
mb();
|
|
|
|
schedule_work(&arb->vftable_fn_work);
|
|
schedule_work(&arb->update_fn_work);
|
|
}
|
|
|
|
static void nvgpu_clk_arb_run_vftable_cb(struct work_struct *work)
|
|
{
|
|
struct nvgpu_clk_arb *arb =
|
|
container_of(work, struct nvgpu_clk_arb, update_fn_work);
|
|
|
|
nvgpu_clk_arb_update_vftable(arb);
|
|
}
|
|
|
|
static void nvgpu_clk_arb_run_arbiter_cb(struct work_struct *work)
|
|
{
|
|
struct nvgpu_clk_arb *arb =
|
|
container_of(work, struct nvgpu_clk_arb, update_fn_work);
|
|
struct nvgpu_clk_session *session;
|
|
struct nvgpu_clk_dev *dev;
|
|
struct nvgpu_clk_dev *tmp;
|
|
struct gk20a *g = arb->g;
|
|
|
|
struct change_fll_clk fllclk;
|
|
u32 gpc2clk_voltuv = 0, mclk_voltuv = 0;
|
|
u32 gpc2clk_voltuv_sram = 0, mclk_voltuv_sram = 0;
|
|
|
|
u32 voltuv, voltuv_sram;
|
|
|
|
int status;
|
|
|
|
/* Temporary variables for checking target frequency */
|
|
u16 gpc2clk_target, mclk_target;
|
|
|
|
/* iteration index */
|
|
u32 index;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
u64 t0, t1;
|
|
#endif
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
g->ops.read_ptimer(g, &t0);
|
|
#endif
|
|
|
|
/* Only one arbiter should be running */
|
|
gpc2clk_target = 0;
|
|
mclk_target = 0;
|
|
|
|
spin_lock(&arb->sessions_lock);
|
|
list_for_each_entry(session, &arb->sessions, link) {
|
|
if (!session->zombie) {
|
|
spin_lock(&arb->req_lock);
|
|
spin_lock(&session->target_lock);
|
|
|
|
mclk_target = mclk_target > session->mclk_target_mhz ?
|
|
mclk_target : session->mclk_target_mhz;
|
|
|
|
gpc2clk_target =
|
|
gpc2clk_target > session->gpc2clk_target_mhz ?
|
|
gpc2clk_target : session->gpc2clk_target_mhz;
|
|
/* Move processed requests to notification list*/
|
|
list_for_each_entry_safe(dev, tmp, &session->targets,
|
|
link) {
|
|
list_del_init(&dev->link);
|
|
list_add_tail(&dev->link, &arb->requests);
|
|
}
|
|
spin_unlock(&session->target_lock);
|
|
spin_unlock(&arb->req_lock);
|
|
|
|
}
|
|
}
|
|
spin_unlock(&arb->sessions_lock);
|
|
|
|
gpc2clk_target = (gpc2clk_target > 0) ? gpc2clk_target :
|
|
arb->gpc2clk_actual_mhz ? gpc2clk_target :
|
|
arb->gpc2clk_default_mhz;
|
|
|
|
mclk_target = (mclk_target > 0) ? mclk_target :
|
|
arb->mclk_actual_mhz ? mclk_target :
|
|
arb->mclk_default_mhz;
|
|
|
|
if (!gpc2clk_target && !mclk_target) {
|
|
mclk_target = arb->mclk_default_mhz;
|
|
gpc2clk_target = arb->gpc2clk_default_mhz;
|
|
}
|
|
|
|
if (!gpc2clk_target)
|
|
gpc2clk_target = arb->gpc2clk_actual_mhz;
|
|
|
|
do {
|
|
/* Check that the table is set */
|
|
mb();
|
|
wait_event(arb->vftable_wq, arb->vftable_set);
|
|
} while (!ACCESS_ONCE(arb->vftable_set));
|
|
|
|
spin_lock(&arb->vf_lock);
|
|
/* round up the freq requests */
|
|
for (index = 0; index < arb->gpc2clk_f_numpoints; index++) {
|
|
if (arb->gpc2clk_vf_points[index].mhz >= gpc2clk_target) {
|
|
gpc2clk_target = arb->gpc2clk_vf_points[index].mhz;
|
|
gpc2clk_voltuv = arb->gpc2clk_vf_points[index].uvolt;
|
|
gpc2clk_voltuv_sram =
|
|
arb->gpc2clk_vf_points[index].uvolt_sram;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index == arb->gpc2clk_f_numpoints) {
|
|
gpc2clk_target = arb->gpc2clk_vf_points[index].mhz;
|
|
gpc2clk_voltuv = arb->gpc2clk_vf_points[index].uvolt;
|
|
gpc2clk_voltuv_sram =
|
|
arb->gpc2clk_vf_points[index].uvolt_sram;
|
|
}
|
|
|
|
if (!mclk_target)
|
|
mclk_target = arb->mclk_actual_mhz;
|
|
|
|
for (index = 0; index < arb->mclk_f_numpoints; index++) {
|
|
if (arb->mclk_vf_points[index].mhz >= mclk_target) {
|
|
mclk_target = arb->mclk_vf_points[index].mhz;
|
|
mclk_voltuv = arb->mclk_vf_points[index].uvolt;
|
|
mclk_voltuv_sram =
|
|
arb->mclk_vf_points[index].uvolt_sram;
|
|
break;
|
|
}
|
|
}
|
|
if (index == arb->mclk_f_numpoints) {
|
|
mclk_target = arb->mclk_vf_points[index].mhz;
|
|
mclk_voltuv = arb->mclk_vf_points[index].uvolt;
|
|
mclk_voltuv_sram =
|
|
arb->mclk_vf_points[index].uvolt_sram;
|
|
}
|
|
spin_unlock(&arb->vf_lock);
|
|
|
|
/* Program clocks */
|
|
/* A change in both mclk of gpc2clk may require a change in voltage */
|
|
if ((arb->gpc2clk_actual_mhz == gpc2clk_target) &&
|
|
(arb->mclk_actual_mhz == mclk_target)) {
|
|
goto exit_arb;
|
|
}
|
|
|
|
voltuv = gpc2clk_voltuv > mclk_voltuv ? gpc2clk_voltuv : mclk_voltuv;
|
|
voltuv_sram = gpc2clk_voltuv_sram > mclk_voltuv_sram ?
|
|
gpc2clk_voltuv_sram : mclk_voltuv_sram;
|
|
|
|
/* if voltage ascends we do:
|
|
* (1) FLL change
|
|
* (2) Voltage change
|
|
* (3) MCLK change
|
|
* If it goes down
|
|
* (1) MCLK change
|
|
* (2) Voltage change
|
|
* (3) FLL change
|
|
*/
|
|
|
|
/* descending */
|
|
if (voltuv <= arb->voltuv_actual) {
|
|
status = g->clk_pmu.clk_mclk.change(g, mclk_target);
|
|
if (status < 0)
|
|
goto exit_arb;
|
|
|
|
status = volt_set_voltage(g, voltuv, voltuv_sram);
|
|
if (status < 0)
|
|
goto exit_arb;
|
|
|
|
fllclk.api_clk_domain = CTRL_CLK_DOMAIN_GPC2CLK;
|
|
fllclk.clkmhz = gpc2clk_target;
|
|
fllclk.voltuv = voltuv;
|
|
status = clk_program_fll_clks(g, &fllclk);
|
|
if (status < 0)
|
|
goto exit_arb;
|
|
} else {
|
|
fllclk.api_clk_domain = CTRL_CLK_DOMAIN_GPC2CLK;
|
|
fllclk.clkmhz = gpc2clk_target;
|
|
fllclk.voltuv = voltuv;
|
|
status = clk_program_fll_clks(g, &fllclk);
|
|
if (status < 0)
|
|
goto exit_arb;
|
|
|
|
status = volt_set_voltage(g, voltuv, voltuv_sram);
|
|
if (status < 0)
|
|
goto exit_arb;
|
|
|
|
status = g->clk_pmu.clk_mclk.change(g, mclk_target);
|
|
if (status < 0)
|
|
goto exit_arb;
|
|
}
|
|
|
|
spin_lock(&arb->data_lock);
|
|
arb->gpc2clk_actual_mhz = gpc2clk_target;
|
|
arb->mclk_actual_mhz = mclk_target;
|
|
arb->voltuv_actual = voltuv;
|
|
/* Make changes visible to other threads */
|
|
wmb();
|
|
|
|
spin_unlock(&arb->data_lock);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
g->ops.read_ptimer(g, &t1);
|
|
arb->switch_num++;
|
|
|
|
mutex_lock(&arb->debug_lock);
|
|
if (arb->switch_num == 1) {
|
|
arb->switch_max = arb->switch_min =
|
|
arb->switch_avg = (t1-t0)/1000;
|
|
arb->switch_std = 0;
|
|
} else {
|
|
s64 prev_avg;
|
|
u64 curr = (t1-t0)/1000;
|
|
|
|
arb->switch_max = curr > arb->switch_max ?
|
|
curr : arb->switch_max;
|
|
arb->switch_min = arb->switch_min ?
|
|
(curr < arb->switch_min ?
|
|
curr : arb->switch_min) : curr;
|
|
prev_avg = arb->switch_avg;
|
|
arb->switch_avg = (curr +
|
|
(arb->switch_avg * (arb->switch_num-1))) /
|
|
arb->switch_num;
|
|
arb->switch_std +=
|
|
(curr - arb->switch_avg) * (curr - prev_avg);
|
|
}
|
|
mutex_unlock(&arb->debug_lock);
|
|
|
|
#endif
|
|
|
|
exit_arb:
|
|
|
|
spin_lock(&arb->req_lock);
|
|
/* notify completion for all requests */
|
|
list_for_each_entry_safe(dev, tmp, &arb->requests, link) {
|
|
atomic_set(&dev->poll_mask, POLLIN | POLLRDNORM);
|
|
wake_up_interruptible(&dev->readout_wq);
|
|
list_del_init(&dev->link);
|
|
}
|
|
spin_unlock(&arb->req_lock);
|
|
|
|
/* notify event for all users */
|
|
spin_lock(&arb->users_lock);
|
|
list_for_each_entry(dev, &arb->users, link) {
|
|
atomic_set(&dev->poll_mask, POLLIN | POLLRDNORM);
|
|
wake_up_interruptible(&dev->readout_wq);
|
|
}
|
|
spin_unlock(&arb->users_lock);
|
|
}
|
|
|
|
int nvgpu_clk_arb_commit_request_fd(struct gk20a *g,
|
|
struct nvgpu_clk_session *session, int request_fd)
|
|
{
|
|
struct nvgpu_clk_arb *arb = g->clk_arb;
|
|
struct nvgpu_clk_dev *dev;
|
|
struct fd fd;
|
|
int err = 0;
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
fd = fdget(request_fd);
|
|
|
|
if (!fd.file)
|
|
return -EINVAL;
|
|
|
|
dev = (struct nvgpu_clk_dev *) fd.file->private_data;
|
|
|
|
if (!dev || dev->session != session) {
|
|
err = -EINVAL;
|
|
goto fdput_fd;
|
|
}
|
|
spin_lock(&session->target_lock);
|
|
session->mclk_target_mhz = dev->mclk_target_mhz ? dev->mclk_target_mhz :
|
|
session->mclk_target_mhz;
|
|
session->gpc2clk_target_mhz = dev->gpc2clk_target_mhz ?
|
|
dev->gpc2clk_target_mhz :
|
|
session->gpc2clk_target_mhz;
|
|
|
|
list_add_tail(&dev->link, &session->targets);
|
|
spin_unlock(&session->target_lock);
|
|
|
|
schedule_work(&arb->update_fn_work);
|
|
|
|
fdput_fd:
|
|
fdput(fd);
|
|
return err;
|
|
}
|
|
|
|
static unsigned int nvgpu_clk_arb_poll_dev(struct file *filp, poll_table *wait)
|
|
{
|
|
struct nvgpu_clk_dev *dev = filp->private_data;
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
poll_wait(filp, &dev->readout_wq, wait);
|
|
return atomic_xchg(&dev->poll_mask, 0);
|
|
}
|
|
|
|
static int nvgpu_clk_arb_release_completion_dev(struct inode *inode,
|
|
struct file *filp)
|
|
{
|
|
struct nvgpu_clk_dev *dev = filp->private_data;
|
|
struct nvgpu_clk_session *session = dev->session;
|
|
struct nvgpu_clk_arb *arb;
|
|
|
|
arb = session->g->clk_arb;
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
spin_lock(&arb->req_lock);
|
|
spin_lock(&session->target_lock);
|
|
if (!list_empty(&dev->link))
|
|
list_del_init(&dev->link);
|
|
spin_unlock(&session->target_lock);
|
|
spin_unlock(&arb->req_lock);
|
|
|
|
kref_put(&session->refcount, nvgpu_clk_arb_free_session);
|
|
kfree(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nvgpu_clk_arb_release_event_dev(struct inode *inode,
|
|
struct file *filp)
|
|
{
|
|
struct nvgpu_clk_dev *dev = filp->private_data;
|
|
struct nvgpu_clk_session *session = dev->session;
|
|
struct nvgpu_clk_arb *arb;
|
|
|
|
arb = session->g->clk_arb;
|
|
|
|
gk20a_dbg_fn("");
|
|
|
|
spin_lock(&arb->users_lock);
|
|
list_del(&dev->link);
|
|
spin_unlock(&arb->users_lock);
|
|
|
|
kref_put(&session->refcount, nvgpu_clk_arb_free_session);
|
|
kfree(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nvgpu_clk_arb_set_session_target_mhz(struct nvgpu_clk_session *session,
|
|
int request_fd, u32 api_domain, u16 target_mhz)
|
|
{
|
|
struct nvgpu_clk_dev *dev;
|
|
struct fd fd;
|
|
int err = 0;
|
|
|
|
gk20a_dbg_fn("domain=0x%08x target_mhz=%u", api_domain, target_mhz);
|
|
|
|
fd = fdget(request_fd);
|
|
|
|
if (!fd.file)
|
|
return -EINVAL;
|
|
|
|
dev = fd.file->private_data;
|
|
if (!dev || dev->session != session) {
|
|
err = -EINVAL;
|
|
goto fdput_fd;
|
|
}
|
|
|
|
switch (api_domain) {
|
|
case NVGPU_GPU_CLK_DOMAIN_MCLK:
|
|
dev->mclk_target_mhz = target_mhz;
|
|
break;
|
|
|
|
case NVGPU_GPU_CLK_DOMAIN_GPC2CLK:
|
|
dev->gpc2clk_target_mhz = target_mhz;
|
|
break;
|
|
|
|
default:
|
|
err = -EINVAL;
|
|
}
|
|
|
|
fdput_fd:
|
|
fdput(fd);
|
|
return err;
|
|
}
|
|
|
|
int nvgpu_clk_arb_get_session_target_mhz(struct nvgpu_clk_session *session,
|
|
u32 api_domain, u16 *freq_mhz)
|
|
{
|
|
int err = 0;
|
|
|
|
spin_lock(&session->target_lock);
|
|
|
|
switch (api_domain) {
|
|
case NVGPU_GPU_CLK_DOMAIN_MCLK:
|
|
*freq_mhz = session->mclk_target_mhz;
|
|
break;
|
|
|
|
case NVGPU_GPU_CLK_DOMAIN_GPC2CLK:
|
|
*freq_mhz = session->gpc2clk_target_mhz;
|
|
break;
|
|
|
|
default:
|
|
*freq_mhz = 0;
|
|
err = -EINVAL;
|
|
}
|
|
|
|
spin_unlock(&session->target_lock);
|
|
return err;
|
|
}
|
|
|
|
int nvgpu_clk_arb_get_arbiter_actual_mhz(struct gk20a *g,
|
|
u32 api_domain, u16 *freq_mhz)
|
|
{
|
|
struct nvgpu_clk_arb *arb = g->clk_arb;
|
|
int err = 0;
|
|
|
|
spin_lock(&arb->data_lock);
|
|
|
|
switch (api_domain) {
|
|
case NVGPU_GPU_CLK_DOMAIN_MCLK:
|
|
*freq_mhz = arb->mclk_actual_mhz;
|
|
break;
|
|
|
|
case NVGPU_GPU_CLK_DOMAIN_GPC2CLK:
|
|
*freq_mhz = arb->gpc2clk_actual_mhz;
|
|
break;
|
|
|
|
default:
|
|
*freq_mhz = 0;
|
|
err = -EINVAL;
|
|
}
|
|
|
|
spin_unlock(&arb->data_lock);
|
|
return err;
|
|
}
|
|
|
|
int nvgpu_clk_arb_get_arbiter_effective_mhz(struct gk20a *g,
|
|
u32 api_domain, u16 *freq_mhz)
|
|
{
|
|
|
|
*freq_mhz = g->ops.clk.get_rate(g, api_domain);
|
|
return 0;
|
|
}
|
|
|
|
int nvgpu_clk_arb_get_arbiter_clk_range(struct gk20a *g, u32 api_domain,
|
|
u16 *min_mhz, u16 *max_mhz)
|
|
{
|
|
return g->ops.clk_arb.get_arbiter_clk_range(g, api_domain,
|
|
min_mhz, max_mhz);
|
|
}
|
|
|
|
u32 nvgpu_clk_arb_get_arbiter_clk_domains(struct gk20a *g)
|
|
{
|
|
return g->ops.clk_arb.get_arbiter_clk_domains(g);
|
|
}
|
|
|
|
int nvgpu_clk_arb_get_arbiter_clk_f_points(struct gk20a *g,
|
|
u32 api_domain, u32 *max_points, u16 *fpoints)
|
|
{
|
|
return (int)clk_domain_get_f_points(g, api_domain, max_points, fpoints);
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int nvgpu_clk_arb_stats_show(struct seq_file *s, void *unused)
|
|
{
|
|
struct gk20a *g = s->private;
|
|
struct nvgpu_clk_arb *arb = g->clk_arb;
|
|
u64 num;
|
|
s64 tmp, avg, std, max, min;
|
|
|
|
/* Make copy of structure to reduce time with lock held */
|
|
mutex_lock(&arb->debug_lock);
|
|
std = arb->switch_std;
|
|
avg = arb->switch_avg;
|
|
max = arb->switch_max;
|
|
min = arb->switch_min;
|
|
num = arb->switch_num;
|
|
mutex_unlock(&arb->debug_lock);
|
|
|
|
tmp = std;
|
|
do_div(tmp, num);
|
|
seq_printf(s, "Number of transitions: %lld\n",
|
|
num);
|
|
seq_printf(s, "max / min : %lld / %lld usec\n",
|
|
max, min);
|
|
seq_printf(s, "avg / std : %lld / %ld usec\n",
|
|
avg, int_sqrt(tmp));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nvgpu_clk_arb_stats_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, nvgpu_clk_arb_stats_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations nvgpu_clk_arb_stats_fops = {
|
|
.open = nvgpu_clk_arb_stats_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
|
|
static int nvgpu_clk_arb_debugfs_init(struct gk20a *g)
|
|
{
|
|
struct gk20a_platform *platform = dev_get_drvdata(g->dev);
|
|
|
|
struct dentry *gpu_root = platform->debugfs;
|
|
struct dentry *d;
|
|
|
|
gk20a_dbg(gpu_dbg_info, "g=%p", g);
|
|
|
|
d = debugfs_create_file(
|
|
"arb_stats",
|
|
S_IRUGO,
|
|
gpu_root,
|
|
g,
|
|
&nvgpu_clk_arb_stats_fops);
|
|
if (!d)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|