Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5f08cb78 authored by Taniya Das's avatar Taniya Das
Browse files

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: default avatarTaniya Das <tdas@codeaurora.org>
parent b83f0c97
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ clk-qcom-y += common.o
clk-qcom-y += vdd-class.o
clk-qcom-y += clk-regmap.o
clk-qcom-y += clk-alpha-pll.o
clk-qcom-y += clk-opp.o
clk-qcom-y += clk-pll.o
clk-qcom-y += clk-rcg.o
clk-qcom-y += clk-rcg2.o
+178 −0
Original line number Diff line number Diff line
// 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);
+13 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */

#ifndef __QCOM_CLK_OPP_H__
#define __QCOM_CLK_OPP_H__

void clk_hw_populate_clock_opp_table(struct device_node *np,
							struct clk_hw *hw);

#define MAX_LEN_OPP_HANDLE	50
#define LEN_OPP_HANDLE		16

#endif
+3 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
#include <linux/clk/qcom.h>

#include "common.h"
#include "clk-opp.h"
#include "clk-rcg.h"
#include "clk-regmap.h"
#include "reset.h"
@@ -322,6 +323,8 @@ int qcom_cc_really_probe(struct platform_device *pdev,
		ret = devm_clk_register_regmap(dev, rclks[i]);
		if (ret)
			return ret;

		clk_hw_populate_clock_opp_table(dev->of_node, &rclks[i]->hw);
	}

	ret = devm_of_clk_add_hw_provider(dev, qcom_cc_clk_hw_get, cc);