| // 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"); |