Contributors: 5
Author Tokens Token Proportion Commits Commit Proportion
Yixun Lan 340 44.44% 1 16.67%
Haylen Chu 243 31.76% 1 16.67%
Alex Elder 178 23.27% 2 33.33%
Inochi Amaoto 3 0.39% 1 16.67%
Kees Cook 1 0.13% 1 16.67%
Total 765 6


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