android_kernel_samsung_sm8650/drivers/clk/qcom/clk-opp.c
Taniya Das 0fb891f9e5 clk: Add support to provide OPP tables for clocks
Some clock driver clients need the frequency to voltage
mapping to be exposed. Add support to load and expose
these values through OPP tables associating to a device.

Change-Id: Iab3e34f53cab1db07fb2e78b378ed18a4dd8d90b
Signed-off-by: Taniya Das <tdas@codeaurora.org>
2022-03-29 15:13:20 -07:00

179 lines
4.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */
#include <linux/clk-provider.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/of_platform.h>
#include <linux/pm_opp.h>
#include <linux/slab.h>
#include "clk-opp.h"
#include "clk-regmap.h"
static int derive_device_list(struct device **device_list,
struct clk_hw *hw,
struct device_node *np,
char *clk_handle_name, int count)
{
struct platform_device *pdev;
struct device_node *dev_node;
int j;
for (j = 0; j < count; j++) {
device_list[j] = NULL;
dev_node = of_parse_phandle(np, clk_handle_name, j);
if (!dev_node) {
pr_err("Unable to get device_node pointer for %s opp-handle (%s)\n",
clk_hw_get_name(hw), clk_handle_name);
return -ENODEV;
}
pdev = of_find_device_by_node(dev_node);
if (!pdev) {
pr_err("Unable to find platform_device node for %s opp-handle\n",
clk_hw_get_name(hw));
return -ENODEV;
}
device_list[j] = &pdev->dev;
}
return 0;
}
static int clk_get_voltage(struct clk_hw *hw, unsigned long rate, int n)
{
struct clk_regmap *rclk = to_clk_regmap(hw);
struct clk_vdd_class_data *vdd_data = &rclk->vdd_data;
struct clk_vdd_class *vdd;
int level, corner;
/* Use the first regulator in the vdd class for the OPP table. */
vdd = rclk->vdd_data.vdd_class;
if (vdd->num_regulators > 1) {
corner = vdd->vdd_uv[vdd->num_regulators * n];
} else {
level = clk_find_vdd_level(hw, vdd_data, rate);
if (level < 0) {
pr_err("Could not find vdd level\n");
return -EINVAL;
}
corner = vdd->vdd_uv[level];
}
if (!corner) {
pr_err("%s: Unable to find vdd level for rate %lu\n",
clk_hw_get_name(hw), rate);
return -EINVAL;
}
return corner;
}
static int clk_add_and_print_opp(struct clk_hw *hw,
struct device **device_list, int count,
unsigned long rate, int uv, int n)
{
int j, ret;
for (j = 0; j < count; j++) {
ret = dev_pm_opp_add(device_list[j], rate, uv);
if (ret) {
pr_err("%s: couldn't add OPP for %lu - err: %d\n",
clk_hw_get_name(hw), rate, ret);
return ret;
}
pr_info("%s: set OPP pair(%lu Hz: %u uV) on %s\n",
clk_hw_get_name(hw), rate, uv,
dev_name(device_list[j]));
}
return 0;
}
void clk_hw_populate_clock_opp_table(struct device_node *np, struct clk_hw *hw)
{
struct device **device_list;
struct clk_regmap *rclk = to_clk_regmap(hw);
struct clk_vdd_class_data *vdd_data;
char clk_handle_name[MAX_LEN_OPP_HANDLE];
int n, len, count, uv, ret;
unsigned long rate = 0, rrate;
if (!rclk->vdd_data.vdd_class || !rclk->vdd_data.num_rate_max)
return;
if (strlen(clk_hw_get_name(hw)) + LEN_OPP_HANDLE < MAX_LEN_OPP_HANDLE) {
ret = scnprintf(clk_handle_name, ARRAY_SIZE(clk_handle_name),
"qcom,%s-opp-handle", clk_hw_get_name(hw));
if (ret < strlen(clk_hw_get_name(hw)) + LEN_OPP_HANDLE) {
pr_err("%s: Failed to hold clk_handle_name\n",
clk_hw_get_name(hw));
return;
}
} else {
pr_err("clk name (%s) too large to fit in clk_handle_name\n",
clk_hw_get_name(hw));
return;
}
if (of_find_property(np, clk_handle_name, &len)) {
count = len/sizeof(u32);
device_list = kmalloc_array(count, sizeof(struct device *),
GFP_KERNEL);
if (!device_list)
return;
ret = derive_device_list(device_list, hw, np,
clk_handle_name, count);
if (ret < 0) {
pr_err("Failed to fill device_list for %s\n",
clk_handle_name);
goto err_derive_device_list;
}
} else {
pr_debug("Unable to find %s\n", clk_handle_name);
return;
}
vdd_data = &rclk->vdd_data;
for (n = 0; n < vdd_data->num_rate_max; n++) {
rrate = clk_hw_round_rate(hw, rate + 1);
if (!rrate) {
/*
* If the parent is not ready before this clock,
* most likely the round rate would fail.
*/
pr_err("clk_hw_round_rate failed for %s\n",
clk_hw_get_name(hw));
goto err_derive_device_list;
}
pr_debug("Rate %lu , uv %d, rrate %lu\n", rate + 1, uv,
clk_hw_round_rate(hw, rate + 1));
/*
* If clk_hw_round_rate gives the same value on consecutive
* iterations, exit the loop since we're at the maximum clock
* frequency.
*/
if (rate == rrate)
break;
rate = rrate;
uv = clk_get_voltage(hw, rate, n);
if (uv < 0)
goto err_derive_device_list;
ret = clk_add_and_print_opp(hw, device_list, count,
rate, uv, n);
if (ret)
pr_err("Failed to add OPP table\n");
}
err_derive_device_list:
kfree(device_list);
}
EXPORT_SYMBOL(clk_hw_populate_clock_opp_table);