root/drivers/clk/qcom/common.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
 */

#include <linux/export.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/interconnect-clk.h>
#include <linux/pm_runtime.h>
#include <linux/reset-controller.h>
#include <linux/of.h>

#include "common.h"
#include "clk-alpha-pll.h"
#include "clk-branch.h"
#include "clk-rcg.h"
#include "clk-regmap.h"
#include "reset.h"
#include "gdsc.h"

struct qcom_cc {
        struct qcom_reset_controller reset;
        struct clk_regmap **rclks;
        size_t num_rclks;
        struct dev_pm_domain_list *pd_list;
};

const
struct freq_tbl *qcom_find_freq(const struct freq_tbl *f, unsigned long rate)
{
        if (!f)
                return NULL;

        if (!f->freq)
                return f;

        for (; f->freq; f++)
                if (rate <= f->freq)
                        return f;

        /* Default to our fastest rate */
        return f - 1;
}
EXPORT_SYMBOL_GPL(qcom_find_freq);

const struct freq_multi_tbl *qcom_find_freq_multi(const struct freq_multi_tbl *f,
                                                  unsigned long rate)
{
        if (!f)
                return NULL;

        if (!f->freq)
                return f;

        for (; f->freq; f++)
                if (rate <= f->freq)
                        return f;

        /* Default to our fastest rate */
        return f - 1;
}
EXPORT_SYMBOL_GPL(qcom_find_freq_multi);

const struct freq_tbl *qcom_find_freq_floor(const struct freq_tbl *f,
                                            unsigned long rate)
{
        const struct freq_tbl *best = NULL;

        for ( ; f->freq; f++) {
                if (rate >= f->freq)
                        best = f;
                else
                        break;
        }

        return best;
}
EXPORT_SYMBOL_GPL(qcom_find_freq_floor);

int qcom_find_src_index(struct clk_hw *hw, const struct parent_map *map, u8 src)
{
        int i, num_parents = clk_hw_get_num_parents(hw);

        for (i = 0; i < num_parents; i++)
                if (src == map[i].src)
                        return i;

        return -ENOENT;
}
EXPORT_SYMBOL_GPL(qcom_find_src_index);

int qcom_find_cfg_index(struct clk_hw *hw, const struct parent_map *map, u8 cfg)
{
        int i, num_parents = clk_hw_get_num_parents(hw);

        for (i = 0; i < num_parents; i++)
                if (cfg == map[i].cfg)
                        return i;

        return -ENOENT;
}
EXPORT_SYMBOL_GPL(qcom_find_cfg_index);

struct regmap *
qcom_cc_map(struct platform_device *pdev, const struct qcom_cc_desc *desc)
{
        void __iomem *base;
        struct device *dev = &pdev->dev;

        base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(base))
                return ERR_CAST(base);

        return devm_regmap_init_mmio(dev, base, desc->config);
}
EXPORT_SYMBOL_GPL(qcom_cc_map);

void
qcom_pll_set_fsm_mode(struct regmap *map, u32 reg, u8 bias_count, u8 lock_count)
{
        u32 val;
        u32 mask;

        /* De-assert reset to FSM */
        regmap_update_bits(map, reg, PLL_VOTE_FSM_RESET, 0);

        /* Program bias count and lock count */
        val = bias_count << PLL_BIAS_COUNT_SHIFT |
                lock_count << PLL_LOCK_COUNT_SHIFT;
        mask = PLL_BIAS_COUNT_MASK << PLL_BIAS_COUNT_SHIFT;
        mask |= PLL_LOCK_COUNT_MASK << PLL_LOCK_COUNT_SHIFT;
        regmap_update_bits(map, reg, mask, val);

        /* Enable PLL FSM voting */
        regmap_update_bits(map, reg, PLL_VOTE_FSM_ENA, PLL_VOTE_FSM_ENA);
}
EXPORT_SYMBOL_GPL(qcom_pll_set_fsm_mode);

static void qcom_cc_gdsc_unregister(void *data)
{
        gdsc_unregister(data);
}

/*
 * Backwards compatibility with old DTs. Register a pass-through factor 1/1
 * clock to translate 'path' clk into 'name' clk and register the 'path'
 * clk as a fixed rate clock if it isn't present.
 */
static int _qcom_cc_register_board_clk(struct device *dev, const char *path,
                                       const char *name, unsigned long rate,
                                       bool add_factor)
{
        struct device_node *node = NULL;
        struct device_node *clocks_node;
        struct clk_fixed_factor *factor;
        struct clk_fixed_rate *fixed;
        struct clk_init_data init_data = { };
        int ret;

        clocks_node = of_find_node_by_path("/clocks");
        if (clocks_node) {
                node = of_get_child_by_name(clocks_node, path);
                of_node_put(clocks_node);
        }

        if (!node) {
                fixed = devm_kzalloc(dev, sizeof(*fixed), GFP_KERNEL);
                if (!fixed)
                        return -EINVAL;

                fixed->fixed_rate = rate;
                fixed->hw.init = &init_data;

                init_data.name = path;
                init_data.ops = &clk_fixed_rate_ops;

                ret = devm_clk_hw_register(dev, &fixed->hw);
                if (ret)
                        return ret;
        }
        of_node_put(node);

        if (add_factor) {
                factor = devm_kzalloc(dev, sizeof(*factor), GFP_KERNEL);
                if (!factor)
                        return -EINVAL;

                factor->mult = factor->div = 1;
                factor->hw.init = &init_data;

                init_data.name = name;
                init_data.parent_names = &path;
                init_data.num_parents = 1;
                init_data.flags = 0;
                init_data.ops = &clk_fixed_factor_ops;

                ret = devm_clk_hw_register(dev, &factor->hw);
                if (ret)
                        return ret;
        }

        return 0;
}

int qcom_cc_register_board_clk(struct device *dev, const char *path,
                               const char *name, unsigned long rate)
{
        bool add_factor = true;

        /*
         * TODO: The RPM clock driver currently does not support the xo clock.
         * When xo is added to the RPM clock driver, we should change this
         * function to skip registration of xo factor clocks.
         */

        return _qcom_cc_register_board_clk(dev, path, name, rate, add_factor);
}
EXPORT_SYMBOL_GPL(qcom_cc_register_board_clk);

int qcom_cc_register_sleep_clk(struct device *dev)
{
        return _qcom_cc_register_board_clk(dev, "sleep_clk", "sleep_clk_src",
                                           32768, true);
}
EXPORT_SYMBOL_GPL(qcom_cc_register_sleep_clk);

/* Drop 'protected-clocks' from the list of clocks to register */
static void qcom_cc_drop_protected(struct device *dev, struct qcom_cc *cc)
{
        struct device_node *np = dev->of_node;
        u32 i;

        of_property_for_each_u32(np, "protected-clocks", i) {
                if (i >= cc->num_rclks)
                        continue;

                cc->rclks[i] = NULL;
        }
}

static struct clk_hw *qcom_cc_clk_hw_get(struct of_phandle_args *clkspec,
                                         void *data)
{
        struct qcom_cc *cc = data;
        unsigned int idx = clkspec->args[0];

        if (idx >= cc->num_rclks) {
                pr_err("%s: invalid index %u\n", __func__, idx);
                return ERR_PTR(-EINVAL);
        }

        return cc->rclks[idx] ? &cc->rclks[idx]->hw : NULL;
}

static int qcom_cc_icc_register(struct device *dev,
                                const struct qcom_cc_desc *desc)
{
        struct icc_clk_data *icd;
        struct clk_hw *hws;
        int i;

        if (!IS_ENABLED(CONFIG_INTERCONNECT_CLK))
                return 0;

        if (!desc->icc_hws)
                return 0;

        icd = devm_kcalloc(dev, desc->num_icc_hws, sizeof(*icd), GFP_KERNEL);
        if (!icd)
                return -ENOMEM;

        for (i = 0; i < desc->num_icc_hws; i++) {
                icd[i].master_id = desc->icc_hws[i].master_id;
                icd[i].slave_id = desc->icc_hws[i].slave_id;
                hws = &desc->clks[desc->icc_hws[i].clk_id]->hw;
                icd[i].clk = devm_clk_hw_get_clk(dev, hws, "icc");
                if (IS_ERR(icd[i].clk))
                        return dev_err_probe(dev, PTR_ERR(icd[i].clk),
                                             "(%d) clock entry is null\n", i);
                icd[i].name = clk_hw_get_name(hws);
        }

        return devm_icc_clk_register(dev, desc->icc_first_node_id,
                                                     desc->num_icc_hws, icd);
}

static int qcom_cc_clk_pll_configure(const struct qcom_cc_driver_data *data,
                                     struct regmap *regmap)
{
        const struct clk_init_data *init;
        struct clk_alpha_pll *pll;
        int i;

        for (i = 0; i < data->num_alpha_plls; i++) {
                pll = data->alpha_plls[i];
                init = pll->clkr.hw.init;

                if (!pll->config || !pll->regs) {
                        pr_err("%s: missing pll config or regs\n", init->name);
                        return -EINVAL;
                }

                qcom_clk_alpha_pll_configure(pll, regmap);
        }

        return 0;
}

static void qcom_cc_clk_regs_configure(struct device *dev, const struct qcom_cc_driver_data *data,
                                       struct regmap *regmap)
{
        int i;

        for (i = 0; i < data->num_clk_cbcrs; i++)
                qcom_branch_set_clk_en(regmap, data->clk_cbcrs[i]);

        if (data->clk_regs_configure)
                data->clk_regs_configure(dev, regmap);
}

int qcom_cc_really_probe(struct device *dev,
                         const struct qcom_cc_desc *desc, struct regmap *regmap)
{
        int i, ret;
        struct qcom_reset_controller *reset;
        struct qcom_cc *cc;
        struct gdsc_desc *scd;
        size_t num_clks = desc->num_clks;
        struct clk_regmap **rclks = desc->clks;
        size_t num_clk_hws = desc->num_clk_hws;
        struct clk_hw **clk_hws = desc->clk_hws;

        cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL);
        if (!cc)
                return -ENOMEM;

        ret = devm_pm_domain_attach_list(dev, NULL, &cc->pd_list);
        if (ret < 0 && ret != -EEXIST)
                return ret;

        if (desc->use_rpm) {
                ret = devm_pm_runtime_enable(dev);
                if (ret)
                        return ret;

                ret = pm_runtime_resume_and_get(dev);
                if (ret)
                        return ret;
        }

        if (desc->driver_data) {
                ret = qcom_cc_clk_pll_configure(desc->driver_data, regmap);
                if (ret)
                        goto put_rpm;

                qcom_cc_clk_regs_configure(dev, desc->driver_data, regmap);
        }

        reset = &cc->reset;
        reset->rcdev.of_node = dev->of_node;
        reset->rcdev.ops = &qcom_reset_ops;
        reset->rcdev.owner = dev->driver->owner;
        reset->rcdev.nr_resets = desc->num_resets;
        reset->regmap = regmap;
        reset->reset_map = desc->resets;

        ret = devm_reset_controller_register(dev, &reset->rcdev);
        if (ret)
                goto put_rpm;

        if (desc->gdscs && desc->num_gdscs) {
                scd = devm_kzalloc(dev, sizeof(*scd), GFP_KERNEL);
                if (!scd) {
                        ret = -ENOMEM;
                        goto put_rpm;
                }
                scd->dev = dev;
                scd->scs = desc->gdscs;
                scd->num = desc->num_gdscs;
                scd->pd_list = cc->pd_list;
                ret = gdsc_register(scd, &reset->rcdev, regmap);
                if (ret)
                        goto put_rpm;
                ret = devm_add_action_or_reset(dev, qcom_cc_gdsc_unregister,
                                               scd);
                if (ret)
                        goto put_rpm;
        }

        if (desc->driver_data &&
            desc->driver_data->dfs_rcgs &&
            desc->driver_data->num_dfs_rcgs) {
                ret = qcom_cc_register_rcg_dfs(regmap,
                                               desc->driver_data->dfs_rcgs,
                                               desc->driver_data->num_dfs_rcgs);
                if (ret)
                        goto put_rpm;
        }

        cc->rclks = rclks;
        cc->num_rclks = num_clks;

        qcom_cc_drop_protected(dev, cc);

        for (i = 0; i < num_clk_hws; i++) {
                ret = devm_clk_hw_register(dev, clk_hws[i]);
                if (ret)
                        goto put_rpm;
        }

        for (i = 0; i < num_clks; i++) {
                if (!rclks[i])
                        continue;

                ret = devm_clk_register_regmap(dev, rclks[i]);
                if (ret)
                        goto put_rpm;
        }

        ret = devm_of_clk_add_hw_provider(dev, qcom_cc_clk_hw_get, cc);
        if (ret)
                goto put_rpm;

        ret = qcom_cc_icc_register(dev, desc);

put_rpm:
        if (desc->use_rpm)
                pm_runtime_put(dev);

        return ret;
}
EXPORT_SYMBOL_GPL(qcom_cc_really_probe);

int qcom_cc_probe(struct platform_device *pdev, const struct qcom_cc_desc *desc)
{
        struct regmap *regmap;

        regmap = qcom_cc_map(pdev, desc);
        if (IS_ERR(regmap))
                return PTR_ERR(regmap);

        return qcom_cc_really_probe(&pdev->dev, desc, regmap);
}
EXPORT_SYMBOL_GPL(qcom_cc_probe);

int qcom_cc_probe_by_index(struct platform_device *pdev, int index,
                           const struct qcom_cc_desc *desc)
{
        struct regmap *regmap;
        void __iomem *base;

        base = devm_platform_ioremap_resource(pdev, index);
        if (IS_ERR(base))
                return PTR_ERR(base);

        regmap = devm_regmap_init_mmio(&pdev->dev, base, desc->config);
        if (IS_ERR(regmap))
                return PTR_ERR(regmap);

        return qcom_cc_really_probe(&pdev->dev, desc, regmap);
}
EXPORT_SYMBOL_GPL(qcom_cc_probe_by_index);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("QTI Common Clock module");