/* * 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 #include #include #include #include #include "clk/clk_arb.h" static int nvgpu_clk_arb_release_session_dev(struct inode *inode, struct file *filp); static unsigned int nvgpu_clk_arb_poll_session_dev(struct file *filp, poll_table *wait); static void nvgpu_clk_arb_run_arbiter_cb(struct work_struct *work); struct nvgpu_clk_arb { struct mutex wlock; struct mutex users_lock; struct list_head users; u32 gpc2clk_current_mhz; u32 gpc2clk_target_mhz; u32 gpc2clk_default_mhz; u32 mclk_current_mhz; u32 mclk_target_mhz; u32 mclk_default_mhz; atomic_t usercount; struct work_struct update_fn_work; atomic_t req_nr; /* for allocations */ atomic_t last_req_nr; /* last completed by arbiter */ }; struct nvgpu_clk_session { struct gk20a *g; int fd; atomic_t req_nr; struct kref refcount; wait_queue_head_t readout_wq; atomic_t poll_mask; struct list_head user; u32 gpc2clk_target_mhz; u32 mclk_target_mhz; }; const struct file_operations clk_dev_ops = { .owner = THIS_MODULE, .release = nvgpu_clk_arb_release_session_dev, .poll = nvgpu_clk_arb_poll_session_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) return -ENOMEM; g->clk_arb = arb; mutex_init(&arb->wlock); mutex_init(&arb->users_lock); err = g->ops.clk_arb.get_arbiter_clk_default(g, NVGPU_GPU_CLK_DOMAIN_MCLK, &default_mhz); if (err) return -EINVAL; arb->mclk_target_mhz = default_mhz; arb->mclk_current_mhz = default_mhz; 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) return -EINVAL; arb->gpc2clk_target_mhz = default_mhz; arb->gpc2clk_current_mhz = default_mhz; arb->gpc2clk_default_mhz = default_mhz; atomic_set(&arb->usercount, 0); atomic_set(&arb->req_nr, 0); atomic_set(&arb->last_req_nr, 0); INIT_LIST_HEAD(&arb->users); INIT_WORK(&arb->update_fn_work, nvgpu_clk_arb_run_arbiter_cb); return 0; } void nvgpu_clk_arb_cleanup_arbiter(struct gk20a *g) { kfree(g->clk_arb); } int nvgpu_clk_arb_install_session_fd(struct gk20a *g, struct nvgpu_clk_session *session) { struct file *file; char *name; int fd; int err; gk20a_dbg_fn(""); if (session->fd >= 0) goto done; 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, &clk_dev_ops, session, O_RDWR); kfree(name); if (IS_ERR(file)) { err = PTR_ERR(file); goto clean_up_fd; } BUG_ON(file->private_data != session); fd_install(fd, file); kref_get(&session->refcount); session->fd = fd; done: return session->fd; clean_up_fd: 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(""); *_session = NULL; 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; session->fd = -1; kref_init(&session->refcount); init_waitqueue_head(&session->readout_wq); atomic_set(&session->poll_mask, 0); atomic_set(&session->req_nr, 0); mutex_lock(&arb->users_lock); list_add_tail(&session->user, &arb->users); mutex_unlock(&arb->users_lock); atomic_inc(&arb->usercount); mutex_lock(&arb->wlock); session->mclk_target_mhz = arb->mclk_default_mhz; session->gpc2clk_target_mhz = arb->gpc2clk_default_mhz; mutex_unlock(&arb->wlock); *_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 gk20a *g = session->g; struct nvgpu_clk_arb *arb = g->clk_arb; mutex_lock(&arb->users_lock); list_del_init(&session->user); mutex_unlock(&arb->users_lock); if (atomic_dec_and_test(&arb->usercount)) nvgpu_clk_arb_apply_session_constraints(g, NULL); kfree(session); } void nvgpu_clk_arb_cleanup_session(struct gk20a *g, struct nvgpu_clk_session *session) { kref_put(&session->refcount, nvgpu_clk_arb_free_session); } 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; mutex_lock(&arb->wlock); /* TODO: loop up higher or equal VF points */ arb->mclk_current_mhz = arb->mclk_target_mhz; arb->gpc2clk_current_mhz = arb->gpc2clk_target_mhz; /* TODO: actually program the clocks */ atomic_set(&arb->last_req_nr, atomic_read(&arb->req_nr)); mutex_unlock(&arb->wlock); mutex_lock(&arb->users_lock); list_for_each_entry(session, &arb->users, user) { atomic_set(&session->poll_mask, POLLIN | POLLRDNORM); wake_up_interruptible(&session->readout_wq); } mutex_unlock(&arb->users_lock); } void nvgpu_clk_arb_apply_session_constraints(struct gk20a *g, struct nvgpu_clk_session *session) { struct nvgpu_clk_arb *arb = g->clk_arb; mutex_lock(&arb->wlock); atomic_inc(&arb->req_nr); /* TODO: arbitration between users. For now, last session to run arbiter wins. */ if (session) { arb->mclk_target_mhz = session->mclk_target_mhz; arb->gpc2clk_target_mhz = session->gpc2clk_target_mhz; atomic_set(&session->req_nr, atomic_read(&arb->req_nr)); } else { arb->mclk_target_mhz = arb->mclk_default_mhz; arb->gpc2clk_target_mhz = arb->gpc2clk_default_mhz; } mutex_unlock(&arb->wlock); schedule_work(&arb->update_fn_work); } static unsigned int nvgpu_clk_arb_poll_session_dev(struct file *filp, poll_table *wait) { struct nvgpu_clk_session *session = filp->private_data; gk20a_dbg_fn(""); poll_wait(filp, &session->readout_wq, wait); return atomic_xchg(&session->poll_mask, 0); } static int nvgpu_clk_arb_release_session_dev(struct inode *inode, struct file *filp) { struct nvgpu_clk_session *session = filp->private_data; struct gk20a *g = session->g; session->fd = -1; nvgpu_clk_arb_cleanup_session(g, session); return 0; } int nvgpu_clk_arb_set_session_target_mhz(struct nvgpu_clk_session *session, u32 api_domain, u16 target_mhz) { gk20a_dbg_fn("domain=0x%08x target_mhz=%u", api_domain, target_mhz); switch (api_domain) { case NVGPU_GPU_CLK_DOMAIN_MCLK: session->mclk_target_mhz = target_mhz; return 0; case NVGPU_GPU_CLK_DOMAIN_GPC2CLK: session->gpc2clk_target_mhz = target_mhz; return 0; default: return -EINVAL; } } int nvgpu_clk_arb_get_session_target_mhz(struct nvgpu_clk_session *session, u32 api_domain, u16 *target_mhz) { switch (api_domain) { case NVGPU_GPU_CLK_DOMAIN_MCLK: *target_mhz = session->mclk_target_mhz; return 0; case NVGPU_GPU_CLK_DOMAIN_GPC2CLK: *target_mhz = session->gpc2clk_target_mhz; return 0; default: *target_mhz = 0; return -EINVAL; } } int nvgpu_clk_arb_get_arbiter_actual_mhz(struct gk20a *g, u32 api_domain, u16 *actual_mhz) { struct nvgpu_clk_arb *arb = g->clk_arb; int err = 0; mutex_lock(&arb->wlock); switch (api_domain) { case NVGPU_GPU_CLK_DOMAIN_MCLK: *actual_mhz = arb->mclk_current_mhz; break; case NVGPU_GPU_CLK_DOMAIN_GPC2CLK: *actual_mhz = arb->gpc2clk_current_mhz; break; default: *actual_mhz = 0; err = -EINVAL; } mutex_unlock(&arb->wlock); return err; } u32 nvgpu_clk_arb_get_arbiter_req_nr(struct gk20a *g) { struct nvgpu_clk_arb *arb = g->clk_arb; return atomic_read(&arb->last_req_nr); } 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); } u32 nvgpu_clk_arb_get_session_req_nr(struct gk20a *g, struct nvgpu_clk_session *session) { return atomic_read(&session->req_nr); } 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); }