diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/clk/qcom/krait-cc.c | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/clk/qcom/krait-cc.c')
-rw-r--r-- | drivers/clk/qcom/krait-cc.c | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/drivers/clk/qcom/krait-cc.c b/drivers/clk/qcom/krait-cc.c new file mode 100644 index 0000000000..410ae8390f --- /dev/null +++ b/drivers/clk/qcom/krait-cc.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018, The Linux Foundation. All rights reserved. + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/slab.h> + +#include "clk-krait.h" + +enum { + cpu0_mux = 0, + cpu1_mux, + cpu2_mux, + cpu3_mux, + l2_mux, + + clks_max, +}; + +static unsigned int sec_mux_map[] = { + 2, + 0, +}; + +static unsigned int pri_mux_map[] = { + 1, + 2, + 0, +}; + +/* + * Notifier function for switching the muxes to safe parent + * while the hfpll is getting reprogrammed. + */ +static int krait_notifier_cb(struct notifier_block *nb, + unsigned long event, + void *data) +{ + int ret = 0; + struct krait_mux_clk *mux = container_of(nb, struct krait_mux_clk, + clk_nb); + /* Switch to safe parent */ + if (event == PRE_RATE_CHANGE) { + mux->old_index = krait_mux_clk_ops.get_parent(&mux->hw); + ret = krait_mux_clk_ops.set_parent(&mux->hw, mux->safe_sel); + mux->reparent = false; + /* + * By the time POST_RATE_CHANGE notifier is called, + * clk framework itself would have changed the parent for the new rate. + * Only otherwise, put back to the old parent. + */ + } else if (event == POST_RATE_CHANGE) { + if (!mux->reparent) + ret = krait_mux_clk_ops.set_parent(&mux->hw, + mux->old_index); + } + + return notifier_from_errno(ret); +} + +static int krait_notifier_register(struct device *dev, struct clk *clk, + struct krait_mux_clk *mux) +{ + int ret = 0; + + mux->clk_nb.notifier_call = krait_notifier_cb; + ret = devm_clk_notifier_register(dev, clk, &mux->clk_nb); + if (ret) + dev_err(dev, "failed to register clock notifier: %d\n", ret); + + return ret; +} + +static struct clk_hw * +krait_add_div(struct device *dev, int id, const char *s, unsigned int offset) +{ + struct krait_div2_clk *div; + static struct clk_parent_data p_data[1]; + struct clk_init_data init = { + .num_parents = ARRAY_SIZE(p_data), + .ops = &krait_div2_clk_ops, + .flags = CLK_SET_RATE_PARENT, + }; + struct clk_hw *clk; + char *parent_name; + int cpu, ret; + + div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL); + if (!div) + return ERR_PTR(-ENOMEM); + + div->width = 2; + div->shift = 6; + div->lpl = id >= 0; + div->offset = offset; + div->hw.init = &init; + + init.name = kasprintf(GFP_KERNEL, "hfpll%s_div", s); + if (!init.name) + return ERR_PTR(-ENOMEM); + + init.parent_data = p_data; + parent_name = kasprintf(GFP_KERNEL, "hfpll%s", s); + if (!parent_name) { + clk = ERR_PTR(-ENOMEM); + goto err_parent_name; + } + + p_data[0].fw_name = parent_name; + p_data[0].name = parent_name; + + ret = devm_clk_hw_register(dev, &div->hw); + if (ret) { + clk = ERR_PTR(ret); + goto err_clk; + } + + clk = &div->hw; + + /* clk-krait ignore any rate change if mux is not flagged as enabled */ + if (id < 0) + for_each_online_cpu(cpu) + clk_prepare_enable(div->hw.clk); + else + clk_prepare_enable(div->hw.clk); + +err_clk: + kfree(parent_name); +err_parent_name: + kfree(init.name); + + return clk; +} + +static struct clk_hw * +krait_add_sec_mux(struct device *dev, int id, const char *s, + unsigned int offset, bool unique_aux) +{ + int cpu, ret; + struct krait_mux_clk *mux; + static struct clk_parent_data sec_mux_list[2] = { + { .name = "qsb", .fw_name = "qsb" }, + {}, + }; + struct clk_init_data init = { + .parent_data = sec_mux_list, + .num_parents = ARRAY_SIZE(sec_mux_list), + .ops = &krait_mux_clk_ops, + .flags = CLK_SET_RATE_PARENT, + }; + struct clk_hw *clk; + char *parent_name; + + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + mux->offset = offset; + mux->lpl = id >= 0; + mux->mask = 0x3; + mux->shift = 2; + mux->parent_map = sec_mux_map; + mux->hw.init = &init; + mux->safe_sel = 0; + + /* Checking for qcom,krait-cc-v1 or qcom,krait-cc-v2 is not + * enough to limit this to apq/ipq8064. Directly check machine + * compatible to correctly handle this errata. + */ + if (of_machine_is_compatible("qcom,ipq8064") || + of_machine_is_compatible("qcom,apq8064")) + mux->disable_sec_src_gating = true; + + init.name = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s); + if (!init.name) + return ERR_PTR(-ENOMEM); + + if (unique_aux) { + parent_name = kasprintf(GFP_KERNEL, "acpu%s_aux", s); + if (!parent_name) { + clk = ERR_PTR(-ENOMEM); + goto err_aux; + } + sec_mux_list[1].fw_name = parent_name; + sec_mux_list[1].name = parent_name; + } else { + sec_mux_list[1].name = "apu_aux"; + } + + ret = devm_clk_hw_register(dev, &mux->hw); + if (ret) { + clk = ERR_PTR(ret); + goto err_clk; + } + + clk = &mux->hw; + + ret = krait_notifier_register(dev, mux->hw.clk, mux); + if (ret) { + clk = ERR_PTR(ret); + goto err_clk; + } + + /* clk-krait ignore any rate change if mux is not flagged as enabled */ + if (id < 0) + for_each_online_cpu(cpu) + clk_prepare_enable(mux->hw.clk); + else + clk_prepare_enable(mux->hw.clk); + +err_clk: + if (unique_aux) + kfree(parent_name); +err_aux: + kfree(init.name); + return clk; +} + +static struct clk_hw * +krait_add_pri_mux(struct device *dev, struct clk_hw *hfpll_div, struct clk_hw *sec_mux, + int id, const char *s, unsigned int offset) +{ + int ret; + struct krait_mux_clk *mux; + static struct clk_parent_data p_data[3]; + struct clk_init_data init = { + .parent_data = p_data, + .num_parents = ARRAY_SIZE(p_data), + .ops = &krait_mux_clk_ops, + .flags = CLK_SET_RATE_PARENT, + }; + struct clk_hw *clk; + char *hfpll_name; + + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + mux->mask = 0x3; + mux->shift = 0; + mux->offset = offset; + mux->lpl = id >= 0; + mux->parent_map = pri_mux_map; + mux->hw.init = &init; + mux->safe_sel = 2; + + init.name = kasprintf(GFP_KERNEL, "krait%s_pri_mux", s); + if (!init.name) + return ERR_PTR(-ENOMEM); + + hfpll_name = kasprintf(GFP_KERNEL, "hfpll%s", s); + if (!hfpll_name) { + clk = ERR_PTR(-ENOMEM); + goto err_hfpll; + } + + p_data[0].fw_name = hfpll_name; + p_data[0].name = hfpll_name; + + p_data[1].hw = hfpll_div; + p_data[2].hw = sec_mux; + + ret = devm_clk_hw_register(dev, &mux->hw); + if (ret) { + clk = ERR_PTR(ret); + goto err_clk; + } + + clk = &mux->hw; + + ret = krait_notifier_register(dev, mux->hw.clk, mux); + if (ret) + clk = ERR_PTR(ret); + +err_clk: + kfree(hfpll_name); +err_hfpll: + kfree(init.name); + return clk; +} + +/* id < 0 for L2, otherwise id == physical CPU number */ +static struct clk_hw *krait_add_clks(struct device *dev, int id, bool unique_aux) +{ + struct clk_hw *hfpll_div, *sec_mux, *pri_mux; + unsigned int offset; + void *p = NULL; + const char *s; + + if (id >= 0) { + offset = 0x4501 + (0x1000 * id); + s = p = kasprintf(GFP_KERNEL, "%d", id); + if (!s) + return ERR_PTR(-ENOMEM); + } else { + offset = 0x500; + s = "_l2"; + } + + hfpll_div = krait_add_div(dev, id, s, offset); + if (IS_ERR(hfpll_div)) { + pri_mux = hfpll_div; + goto err; + } + + sec_mux = krait_add_sec_mux(dev, id, s, offset, unique_aux); + if (IS_ERR(sec_mux)) { + pri_mux = sec_mux; + goto err; + } + + pri_mux = krait_add_pri_mux(dev, hfpll_div, sec_mux, id, s, offset); + +err: + kfree(p); + return pri_mux; +} + +static struct clk *krait_of_get(struct of_phandle_args *clkspec, void *data) +{ + unsigned int idx = clkspec->args[0]; + struct clk **clks = data; + + if (idx >= clks_max) { + pr_err("%s: invalid clock index %d\n", __func__, idx); + return ERR_PTR(-EINVAL); + } + + return clks[idx] ? : ERR_PTR(-ENODEV); +} + +static const struct of_device_id krait_cc_match_table[] = { + { .compatible = "qcom,krait-cc-v1", (void *)1UL }, + { .compatible = "qcom,krait-cc-v2" }, + {} +}; +MODULE_DEVICE_TABLE(of, krait_cc_match_table); + +static int krait_cc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *id; + unsigned long cur_rate, aux_rate; + int cpu; + struct clk_hw *mux, *l2_pri_mux; + struct clk *clk, **clks; + + id = of_match_device(krait_cc_match_table, dev); + if (!id) + return -ENODEV; + + /* Rate is 1 because 0 causes problems for __clk_mux_determine_rate */ + clk = clk_register_fixed_rate(dev, "qsb", NULL, 0, 1); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + if (!id->data) { + clk = clk_register_fixed_factor(dev, "acpu_aux", + "gpll0_vote", 0, 1, 2); + if (IS_ERR(clk)) + return PTR_ERR(clk); + } + + /* Krait configurations have at most 4 CPUs and one L2 */ + clks = devm_kcalloc(dev, clks_max, sizeof(*clks), GFP_KERNEL); + if (!clks) + return -ENOMEM; + + for_each_possible_cpu(cpu) { + mux = krait_add_clks(dev, cpu, id->data); + if (IS_ERR(mux)) + return PTR_ERR(mux); + clks[cpu] = mux->clk; + } + + l2_pri_mux = krait_add_clks(dev, -1, id->data); + if (IS_ERR(l2_pri_mux)) + return PTR_ERR(l2_pri_mux); + clks[l2_mux] = l2_pri_mux->clk; + + /* + * We don't want the CPU or L2 clocks to be turned off at late init + * if CPUFREQ or HOTPLUG configs are disabled. So, bump up the + * refcount of these clocks. Any cpufreq/hotplug manager can assume + * that the clocks have already been prepared and enabled by the time + * they take over. + */ + for_each_online_cpu(cpu) { + clk_prepare_enable(clks[l2_mux]); + WARN(clk_prepare_enable(clks[cpu]), + "Unable to turn on CPU%d clock", cpu); + } + + /* + * Force reinit of HFPLLs and muxes to overwrite any potential + * incorrect configuration of HFPLLs and muxes by the bootloader. + * While at it, also make sure the cores are running at known rates + * and print the current rate. + * + * The clocks are set to aux clock rate first to make sure the + * secondary mux is not sourcing off of QSB. The rate is then set to + * two different rates to force a HFPLL reinit under all + * circumstances. + */ + cur_rate = clk_get_rate(clks[l2_mux]); + aux_rate = 384000000; + if (cur_rate < aux_rate) { + pr_info("L2 @ Undefined rate. Forcing new rate.\n"); + cur_rate = aux_rate; + } + clk_set_rate(clks[l2_mux], aux_rate); + clk_set_rate(clks[l2_mux], 2); + clk_set_rate(clks[l2_mux], cur_rate); + pr_info("L2 @ %lu KHz\n", clk_get_rate(clks[l2_mux]) / 1000); + for_each_possible_cpu(cpu) { + clk = clks[cpu]; + cur_rate = clk_get_rate(clk); + if (cur_rate < aux_rate) { + pr_info("CPU%d @ Undefined rate. Forcing new rate.\n", cpu); + cur_rate = aux_rate; + } + + clk_set_rate(clk, aux_rate); + clk_set_rate(clk, 2); + clk_set_rate(clk, cur_rate); + pr_info("CPU%d @ %lu KHz\n", cpu, clk_get_rate(clk) / 1000); + } + + of_clk_add_provider(dev->of_node, krait_of_get, clks); + + return 0; +} + +static struct platform_driver krait_cc_driver = { + .probe = krait_cc_probe, + .driver = { + .name = "krait-cc", + .of_match_table = krait_cc_match_table, + }, +}; +module_platform_driver(krait_cc_driver); + +MODULE_DESCRIPTION("Krait CPU Clock Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:krait-cc"); |