tegrakernel/kernel/nvidia/drivers/nvpmodel/nvpmodel_emc_cap.c

244 lines
5.7 KiB
C

/*
* drivers/nvpmodel/nvpmodel_emc_cap.c
*
* NVIDIA Tegra Nvpmodel driver for Tegra chips
*
* Copyright (c) 2017-2018, 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 that 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 <linux/module.h>
#include <linux/printk.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/platform/tegra/emc_bwmgr.h>
#include <linux/platform/tegra/bwmgr_mc.h>
#define AUTHOR "Terry Wang <terwang@nvidia.com>"
#define DESCRIPTION "Nvpmodel clock cap driver"
#define MODULE_NAME "Nvpmodel_clk_cap"
#define VERSION "1.0"
/* Module information */
MODULE_AUTHOR(AUTHOR);
MODULE_DESCRIPTION(DESCRIPTION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");
static struct kobject *clk_cap_kobject;
static unsigned long emc_iso_cap;
/* bandwidth manager handle */
struct tegra_bwmgr_client *bwmgr_handle;
struct nvpmodel_clk {
struct kobj_attribute attr;
struct clk *clk;
};
static struct nvpmodel_clk *clks;
static int num_clocks;
static ssize_t emc_iso_cap_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%lu\n", emc_iso_cap);
}
static ssize_t emc_iso_cap_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf,
size_t count)
{
int error = 0;
if (sscanf(buf, "%lu", &emc_iso_cap) != 1)
return -EINVAL;
error = tegra_bwmgr_set_emc(bwmgr_handle, emc_iso_cap,
TEGRA_BWMGR_SET_EMC_ISO_CAP);
if (error) {
pr_warn("Nvpmodel failed to set EMC hz=%lu errno=%d\n",
emc_iso_cap, error);
return error;
}
return count;
}
static struct kobj_attribute emc_iso_cap_attribute =
__ATTR(emc_iso_cap, 0660, emc_iso_cap_show, emc_iso_cap_store);
static ssize_t clk_cap_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
long rate;
struct nvpmodel_clk *clk = container_of(attr, struct nvpmodel_clk, attr);
rate = clk_round_rate(clk->clk, 1UL << 63);
if (rate < 0) {
pr_err("clk_round_rate failed: %ld\n", rate);
return rate;
}
return sprintf(buf, "%ld\n", rate);
}
static ssize_t clk_cap_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf,
size_t count)
{
unsigned long rate;
int ret;
struct nvpmodel_clk *clk = container_of(attr, struct nvpmodel_clk, attr);
if (sscanf(buf, "%lu", &rate) != 1)
return -EINVAL;
ret = clk_set_max_rate(clk->clk, rate);
if (ret) {
pr_err("setting cap failed: %d\n", ret);
return ret;
}
return count;
}
static void free_resources(void)
{
int i;
if (clks) {
for (i = 0; i < num_clocks; i++) {
if (clks[i].attr.attr.name)
kfree_const(clks[i].attr.attr.name);
if (clks[i].clk)
clk_put(clks[i].clk);
}
kfree(clks);
clks = NULL;
num_clocks = 0;
}
if (bwmgr_handle) {
tegra_bwmgr_unregister(bwmgr_handle);
bwmgr_handle = NULL;
}
if (clk_cap_kobject) {
kobject_put(clk_cap_kobject);
clk_cap_kobject = NULL;
}
}
static int __init nvpmodel_clk_cap_init(void)
{
int error = 0;
int i;
const char *clk_name;
struct device_node *dn;
dn = of_find_compatible_node(NULL, NULL, "nvidia,nvpmodel");
if (!dn || !of_device_is_available(dn)) {
of_node_put(dn);
return -ENODEV;
}
clk_cap_kobject = kobject_create_and_add("nvpmodel_emc_cap",
kernel_kobj);
if (!clk_cap_kobject) {
error = -ENOMEM;
goto exit;
}
bwmgr_handle = tegra_bwmgr_register(TEGRA_BWMGR_CLIENT_NVPMODEL);
if (IS_ERR_OR_NULL(bwmgr_handle)) {
error = IS_ERR(bwmgr_handle) ?
PTR_ERR(bwmgr_handle) : -ENODEV;
pr_err("Nvpmodel can't register EMC bwmgr (%d)\n", error);
goto exit;
}
error = sysfs_create_file(clk_cap_kobject,
&emc_iso_cap_attribute.attr);
if (error) {
pr_err("failed to create emc_iso_cap sysfs: error %d\n", error);
goto exit;
}
num_clocks = of_property_count_strings(dn, "clock-names");
if (num_clocks <= 0) {
num_clocks = 0;
goto exit;
}
clks = kzalloc(sizeof(*clks) * num_clocks, GFP_KERNEL);
if (!clks) {
num_clocks = 0;
pr_err("couldn't allocate clks!\n");
error = -ENOMEM;
goto exit;
}
for (i = 0; i < num_clocks; i++) {
if (of_property_read_string_index(dn, "clock-names", i,
&clk_name)) {
pr_warn("couldn't get clock %d from device tree\n", i);
continue;
}
clks[i].clk = of_clk_get(dn, i);
if (IS_ERR(clks[i].clk)) {
clks[i].clk = NULL;
pr_warn("couldn't get clock: %s, error %d\n", clk_name,
error);
continue;
}
clks[i].attr.attr.name = kstrdup_const(clk_name, GFP_KERNEL);
if (!clks[i].attr.attr.name) {
pr_warn("couldn't allocate memory for clock %s\n",
clk_name);
continue;
}
sysfs_attr_init(&(clks[i].attr.attr));
clks[i].attr.attr.mode = 0660;
clks[i].attr.show = clk_cap_show;
clks[i].attr.store = clk_cap_store;
if (sysfs_create_file(clk_cap_kobject,
&clks[i].attr.attr)) {
pr_warn("failed to create %s cap sysfs file: error %d\n",
clk_name, error);
continue;
}
}
exit:
if (error) {
free_resources();
pr_err("nvpmodel: initialization failed: error %d\n", error);
} else {
pr_info("nvpmodel: initialized successfully\n");
}
of_node_put(dn);
return error;
}
static void __exit nvpmodel_clk_cap_exit(void)
{
free_resources();
pr_info("Module exit successfully \n");
}
module_init(nvpmodel_clk_cap_init);
module_exit(nvpmodel_clk_cap_exit);