root/drivers/clk/spacemit/ccu_common.c
// SPDX-License-Identifier: GPL-2.0-only

#include <linux/clk-provider.h>
#include <linux/device/devres.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <soc/spacemit/ccu.h>

#include "ccu_common.h"

static DEFINE_IDA(auxiliary_ids);
static int spacemit_ccu_register(struct device *dev,
                                 struct regmap *regmap,
                                 struct regmap *lock_regmap,
                                 const struct spacemit_ccu_data *data)
{
        struct clk_hw_onecell_data *clk_data;
        int i, ret;

        /* Nothing to do if the CCU does not implement any clocks */
        if (!data->hws)
                return 0;

        clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, data->num),
                                GFP_KERNEL);
        if (!clk_data)
                return -ENOMEM;

        clk_data->num = data->num;

        for (i = 0; i < data->num; i++) {
                struct clk_hw *hw = data->hws[i];
                struct ccu_common *common;
                const char *name;

                if (!hw) {
                        clk_data->hws[i] = ERR_PTR(-ENOENT);
                        continue;
                }

                name = hw->init->name;

                common = hw_to_ccu_common(hw);
                common->regmap          = regmap;
                common->lock_regmap     = lock_regmap;

                ret = devm_clk_hw_register(dev, hw);
                if (ret) {
                        dev_err(dev, "Cannot register clock %d - %s\n",
                                i, name);
                        return ret;
                }

                clk_data->hws[i] = hw;
        }

        ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
        if (ret)
                dev_err(dev, "failed to add clock hardware provider (%d)\n", ret);

        return ret;
}

static void spacemit_cadev_release(struct device *dev)
{
        struct auxiliary_device *adev = to_auxiliary_dev(dev);

        ida_free(&auxiliary_ids, adev->id);
        kfree(to_spacemit_ccu_adev(adev));
}

static void spacemit_adev_unregister(void *data)
{
        struct auxiliary_device *adev = data;

        auxiliary_device_delete(adev);
        auxiliary_device_uninit(adev);
}

static int spacemit_ccu_reset_register(struct device *dev,
                                       struct regmap *regmap,
                                       const char *reset_name)
{
        struct spacemit_ccu_adev *cadev;
        struct auxiliary_device *adev;
        int ret;

        /* Nothing to do if the CCU does not implement a reset controller */
        if (!reset_name)
                return 0;

        cadev = kzalloc_obj(*cadev);
        if (!cadev)
                return -ENOMEM;

        cadev->regmap = regmap;

        adev = &cadev->adev;
        adev->name = reset_name;
        adev->dev.parent = dev;
        adev->dev.release = spacemit_cadev_release;
        adev->dev.of_node = dev->of_node;
        ret = ida_alloc(&auxiliary_ids, GFP_KERNEL);
        if (ret < 0)
                goto err_free_cadev;
        adev->id = ret;

        ret = auxiliary_device_init(adev);
        if (ret)
                goto err_free_aux_id;

        ret = auxiliary_device_add(adev);
        if (ret) {
                auxiliary_device_uninit(adev);
                return ret;
        }

        return devm_add_action_or_reset(dev, spacemit_adev_unregister, adev);

err_free_aux_id:
        ida_free(&auxiliary_ids, adev->id);
err_free_cadev:
        kfree(cadev);

        return ret;
}

int spacemit_ccu_probe(struct platform_device *pdev, const char *compat)
{
        struct regmap *base_regmap, *lock_regmap = NULL;
        const struct spacemit_ccu_data *data;
        struct device *dev = &pdev->dev;
        int ret;

        base_regmap = device_node_to_regmap(dev->of_node);
        if (IS_ERR(base_regmap))
                return dev_err_probe(dev, PTR_ERR(base_regmap),
                                     "failed to get regmap\n");

        /*
         * The lock status of PLLs locate in MPMU region, while PLLs themselves
         * are in APBS region. Reference to MPMU syscon is required to check PLL
         * status.
         */
        if (compat && of_device_is_compatible(dev->of_node, compat)) {
                struct device_node *mpmu = of_parse_phandle(dev->of_node,
                                                            "spacemit,mpmu", 0);
                if (!mpmu)
                        return dev_err_probe(dev, -ENODEV,
                                             "Cannot parse MPMU region\n");

                lock_regmap = device_node_to_regmap(mpmu);
                of_node_put(mpmu);

                if (IS_ERR(lock_regmap))
                        return dev_err_probe(dev, PTR_ERR(lock_regmap),
                                             "failed to get lock regmap\n");
        }

        data = of_device_get_match_data(dev);

        ret = spacemit_ccu_register(dev, base_regmap, lock_regmap, data);
        if (ret)
                return dev_err_probe(dev, ret, "failed to register clocks\n");

        ret = spacemit_ccu_reset_register(dev, base_regmap, data->reset_name);
        if (ret)
                return dev_err_probe(dev, ret, "failed to register resets\n");

        return 0;
}
EXPORT_SYMBOL_NS_GPL(spacemit_ccu_probe, "CLK_SPACEMIT");

MODULE_DESCRIPTION("SpacemiT CCU common clock driver");
MODULE_LICENSE("GPL");