brintos

brintos / linux-shallow public Read only

0
0
Text · 7.4 KiB · 19d530d Raw
313 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * System Control and Power Interface (SCPI) Protocol based clock driver4 *5 * Copyright (C) 2015 ARM Ltd.6 */7 8#include <linux/clk-provider.h>9#include <linux/device.h>10#include <linux/err.h>11#include <linux/of.h>12#include <linux/module.h>13#include <linux/platform_device.h>14#include <linux/scpi_protocol.h>15 16struct scpi_clk {17	u32 id;18	struct clk_hw hw;19	struct scpi_dvfs_info *info;20	struct scpi_ops *scpi_ops;21};22 23#define to_scpi_clk(clk) container_of(clk, struct scpi_clk, hw)24 25static struct platform_device *cpufreq_dev;26 27static unsigned long scpi_clk_recalc_rate(struct clk_hw *hw,28					  unsigned long parent_rate)29{30	struct scpi_clk *clk = to_scpi_clk(hw);31 32	return clk->scpi_ops->clk_get_val(clk->id);33}34 35static long scpi_clk_round_rate(struct clk_hw *hw, unsigned long rate,36				unsigned long *parent_rate)37{38	/*39	 * We can't figure out what rate it will be, so just return the40	 * rate back to the caller. scpi_clk_recalc_rate() will be called41	 * after the rate is set and we'll know what rate the clock is42	 * running at then.43	 */44	return rate;45}46 47static int scpi_clk_set_rate(struct clk_hw *hw, unsigned long rate,48			     unsigned long parent_rate)49{50	struct scpi_clk *clk = to_scpi_clk(hw);51 52	return clk->scpi_ops->clk_set_val(clk->id, rate);53}54 55static const struct clk_ops scpi_clk_ops = {56	.recalc_rate = scpi_clk_recalc_rate,57	.round_rate = scpi_clk_round_rate,58	.set_rate = scpi_clk_set_rate,59};60 61/* find closest match to given frequency in OPP table */62static long __scpi_dvfs_round_rate(struct scpi_clk *clk, unsigned long rate)63{64	int idx;65	unsigned long fmin = 0, fmax = ~0, ftmp;66	const struct scpi_opp *opp = clk->info->opps;67 68	for (idx = 0; idx < clk->info->count; idx++, opp++) {69		ftmp = opp->freq;70		if (ftmp >= rate) {71			if (ftmp <= fmax)72				fmax = ftmp;73			break;74		} else if (ftmp >= fmin) {75			fmin = ftmp;76		}77	}78	return fmax != ~0 ? fmax : fmin;79}80 81static unsigned long scpi_dvfs_recalc_rate(struct clk_hw *hw,82					   unsigned long parent_rate)83{84	struct scpi_clk *clk = to_scpi_clk(hw);85	int idx = clk->scpi_ops->dvfs_get_idx(clk->id);86	const struct scpi_opp *opp;87 88	if (idx < 0)89		return 0;90 91	opp = clk->info->opps + idx;92	return opp->freq;93}94 95static long scpi_dvfs_round_rate(struct clk_hw *hw, unsigned long rate,96				 unsigned long *parent_rate)97{98	struct scpi_clk *clk = to_scpi_clk(hw);99 100	return __scpi_dvfs_round_rate(clk, rate);101}102 103static int __scpi_find_dvfs_index(struct scpi_clk *clk, unsigned long rate)104{105	int idx, max_opp = clk->info->count;106	const struct scpi_opp *opp = clk->info->opps;107 108	for (idx = 0; idx < max_opp; idx++, opp++)109		if (opp->freq == rate)110			return idx;111	return -EINVAL;112}113 114static int scpi_dvfs_set_rate(struct clk_hw *hw, unsigned long rate,115			      unsigned long parent_rate)116{117	struct scpi_clk *clk = to_scpi_clk(hw);118	int ret = __scpi_find_dvfs_index(clk, rate);119 120	if (ret < 0)121		return ret;122	return clk->scpi_ops->dvfs_set_idx(clk->id, (u8)ret);123}124 125static const struct clk_ops scpi_dvfs_ops = {126	.recalc_rate = scpi_dvfs_recalc_rate,127	.round_rate = scpi_dvfs_round_rate,128	.set_rate = scpi_dvfs_set_rate,129};130 131static const struct of_device_id scpi_clk_match[] __maybe_unused = {132	{ .compatible = "arm,scpi-dvfs-clocks", .data = &scpi_dvfs_ops, },133	{ .compatible = "arm,scpi-variable-clocks", .data = &scpi_clk_ops, },134	{}135};136 137static int138scpi_clk_ops_init(struct device *dev, const struct of_device_id *match,139		  struct scpi_clk *sclk, const char *name)140{141	struct clk_init_data init;142	unsigned long min = 0, max = 0;143	int ret;144 145	init.name = name;146	init.flags = 0;147	init.num_parents = 0;148	init.ops = match->data;149	sclk->hw.init = &init;150	sclk->scpi_ops = get_scpi_ops();151 152	if (init.ops == &scpi_dvfs_ops) {153		sclk->info = sclk->scpi_ops->dvfs_get_info(sclk->id);154		if (IS_ERR(sclk->info))155			return PTR_ERR(sclk->info);156	} else if (init.ops == &scpi_clk_ops) {157		if (sclk->scpi_ops->clk_get_range(sclk->id, &min, &max) || !max)158			return -EINVAL;159	} else {160		return -EINVAL;161	}162 163	ret = devm_clk_hw_register(dev, &sclk->hw);164	if (!ret && max)165		clk_hw_set_rate_range(&sclk->hw, min, max);166	return ret;167}168 169struct scpi_clk_data {170	struct scpi_clk **clk;171	unsigned int clk_num;172};173 174static struct clk_hw *175scpi_of_clk_src_get(struct of_phandle_args *clkspec, void *data)176{177	struct scpi_clk *sclk;178	struct scpi_clk_data *clk_data = data;179	unsigned int idx = clkspec->args[0], count;180 181	for (count = 0; count < clk_data->clk_num; count++) {182		sclk = clk_data->clk[count];183		if (idx == sclk->id)184			return &sclk->hw;185	}186 187	return ERR_PTR(-EINVAL);188}189 190static int scpi_clk_add(struct device *dev, struct device_node *np,191			const struct of_device_id *match)192{193	int idx, count, err;194	struct scpi_clk_data *clk_data;195 196	count = of_property_count_strings(np, "clock-output-names");197	if (count < 0) {198		dev_err(dev, "%pOFn: invalid clock output count\n", np);199		return -EINVAL;200	}201 202	clk_data = devm_kmalloc(dev, sizeof(*clk_data), GFP_KERNEL);203	if (!clk_data)204		return -ENOMEM;205 206	clk_data->clk_num = count;207	clk_data->clk = devm_kcalloc(dev, count, sizeof(*clk_data->clk),208				     GFP_KERNEL);209	if (!clk_data->clk)210		return -ENOMEM;211 212	for (idx = 0; idx < count; idx++) {213		struct scpi_clk *sclk;214		const char *name;215		u32 val;216 217		sclk = devm_kzalloc(dev, sizeof(*sclk), GFP_KERNEL);218		if (!sclk)219			return -ENOMEM;220 221		if (of_property_read_string_index(np, "clock-output-names",222						  idx, &name)) {223			dev_err(dev, "invalid clock name @ %pOFn\n", np);224			return -EINVAL;225		}226 227		if (of_property_read_u32_index(np, "clock-indices",228					       idx, &val)) {229			dev_err(dev, "invalid clock index @ %pOFn\n", np);230			return -EINVAL;231		}232 233		sclk->id = val;234 235		err = scpi_clk_ops_init(dev, match, sclk, name);236		if (err) {237			dev_err(dev, "failed to register clock '%s'\n", name);238			return err;239		}240 241		dev_dbg(dev, "Registered clock '%s'\n", name);242		clk_data->clk[idx] = sclk;243	}244 245	return of_clk_add_hw_provider(np, scpi_of_clk_src_get, clk_data);246}247 248static void scpi_clocks_remove(struct platform_device *pdev)249{250	struct device *dev = &pdev->dev;251	struct device_node *child, *np = dev->of_node;252 253	if (cpufreq_dev) {254		platform_device_unregister(cpufreq_dev);255		cpufreq_dev = NULL;256	}257 258	for_each_available_child_of_node(np, child)259		of_clk_del_provider(np);260}261 262static int scpi_clocks_probe(struct platform_device *pdev)263{264	int ret;265	struct device *dev = &pdev->dev;266	struct device_node *child, *np = dev->of_node;267	const struct of_device_id *match;268 269	if (!get_scpi_ops())270		return -ENXIO;271 272	for_each_available_child_of_node(np, child) {273		match = of_match_node(scpi_clk_match, child);274		if (!match)275			continue;276		ret = scpi_clk_add(dev, child, match);277		if (ret) {278			scpi_clocks_remove(pdev);279			of_node_put(child);280			return ret;281		}282 283		if (match->data != &scpi_dvfs_ops)284			continue;285		/* Add the virtual cpufreq device if it's DVFS clock provider */286		cpufreq_dev = platform_device_register_simple("scpi-cpufreq",287							      -1, NULL, 0);288		if (IS_ERR(cpufreq_dev))289			pr_warn("unable to register cpufreq device");290	}291	return 0;292}293 294static const struct of_device_id scpi_clocks_ids[] = {295	{ .compatible = "arm,scpi-clocks", },296	{}297};298MODULE_DEVICE_TABLE(of, scpi_clocks_ids);299 300static struct platform_driver scpi_clocks_driver = {301	.driver	= {302		.name = "scpi_clocks",303		.of_match_table = scpi_clocks_ids,304	},305	.probe = scpi_clocks_probe,306	.remove = scpi_clocks_remove,307};308module_platform_driver(scpi_clocks_driver);309 310MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");311MODULE_DESCRIPTION("ARM SCPI clock driver");312MODULE_LICENSE("GPL v2");313