162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2021 Linaro Ltd.
462306a36Sopenharmony_ci * Copyright (C) 2021 Dávid Virág <virag.david003@gmail.com>
562306a36Sopenharmony_ci * Author: Sam Protsenko <semen.protsenko@linaro.org>
662306a36Sopenharmony_ci * Author: Dávid Virág <virag.david003@gmail.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * This file contains shared functions used by some arm64 Exynos SoCs,
962306a36Sopenharmony_ci * such as Exynos7885 or Exynos850 to register and init CMUs.
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci#include <linux/clk.h>
1262306a36Sopenharmony_ci#include <linux/of_address.h>
1362306a36Sopenharmony_ci#include <linux/of.h>
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1662306a36Sopenharmony_ci#include <linux/slab.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "clk-exynos-arm64.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/* Gate register bits */
2162306a36Sopenharmony_ci#define GATE_MANUAL		BIT(20)
2262306a36Sopenharmony_ci#define GATE_ENABLE_HWACG	BIT(28)
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/* Gate register offsets range */
2562306a36Sopenharmony_ci#define GATE_OFF_START		0x2000
2662306a36Sopenharmony_ci#define GATE_OFF_END		0x2fff
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistruct exynos_arm64_cmu_data {
2962306a36Sopenharmony_ci	struct samsung_clk_reg_dump *clk_save;
3062306a36Sopenharmony_ci	unsigned int nr_clk_save;
3162306a36Sopenharmony_ci	const struct samsung_clk_reg_dump *clk_suspend;
3262306a36Sopenharmony_ci	unsigned int nr_clk_suspend;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	struct clk *clk;
3562306a36Sopenharmony_ci	struct clk **pclks;
3662306a36Sopenharmony_ci	int nr_pclks;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	struct samsung_clk_provider *ctx;
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/**
4262306a36Sopenharmony_ci * exynos_arm64_init_clocks - Set clocks initial configuration
4362306a36Sopenharmony_ci * @np:			CMU device tree node with "reg" property (CMU addr)
4462306a36Sopenharmony_ci * @reg_offs:		Register offsets array for clocks to init
4562306a36Sopenharmony_ci * @reg_offs_len:	Number of register offsets in reg_offs array
4662306a36Sopenharmony_ci *
4762306a36Sopenharmony_ci * Set manual control mode for all gate clocks.
4862306a36Sopenharmony_ci */
4962306a36Sopenharmony_cistatic void __init exynos_arm64_init_clocks(struct device_node *np,
5062306a36Sopenharmony_ci		const unsigned long *reg_offs, size_t reg_offs_len)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	void __iomem *reg_base;
5362306a36Sopenharmony_ci	size_t i;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	reg_base = of_iomap(np, 0);
5662306a36Sopenharmony_ci	if (!reg_base)
5762306a36Sopenharmony_ci		panic("%s: failed to map registers\n", __func__);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	for (i = 0; i < reg_offs_len; ++i) {
6062306a36Sopenharmony_ci		void __iomem *reg = reg_base + reg_offs[i];
6162306a36Sopenharmony_ci		u32 val;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci		/* Modify only gate clock registers */
6462306a36Sopenharmony_ci		if (reg_offs[i] < GATE_OFF_START || reg_offs[i] > GATE_OFF_END)
6562306a36Sopenharmony_ci			continue;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci		val = readl(reg);
6862306a36Sopenharmony_ci		val |= GATE_MANUAL;
6962306a36Sopenharmony_ci		val &= ~GATE_ENABLE_HWACG;
7062306a36Sopenharmony_ci		writel(val, reg);
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	iounmap(reg_base);
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/**
7762306a36Sopenharmony_ci * exynos_arm64_enable_bus_clk - Enable parent clock of specified CMU
7862306a36Sopenharmony_ci *
7962306a36Sopenharmony_ci * @dev:	Device object; may be NULL if this function is not being
8062306a36Sopenharmony_ci *		called from platform driver probe function
8162306a36Sopenharmony_ci * @np:		CMU device tree node
8262306a36Sopenharmony_ci * @cmu:	CMU data
8362306a36Sopenharmony_ci *
8462306a36Sopenharmony_ci * Keep CMU parent clock running (needed for CMU registers access).
8562306a36Sopenharmony_ci *
8662306a36Sopenharmony_ci * Return: 0 on success or a negative error code on failure.
8762306a36Sopenharmony_ci */
8862306a36Sopenharmony_cistatic int __init exynos_arm64_enable_bus_clk(struct device *dev,
8962306a36Sopenharmony_ci		struct device_node *np, const struct samsung_cmu_info *cmu)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct clk *parent_clk;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	if (!cmu->clk_name)
9462306a36Sopenharmony_ci		return 0;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if (dev) {
9762306a36Sopenharmony_ci		struct exynos_arm64_cmu_data *data;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci		parent_clk = clk_get(dev, cmu->clk_name);
10062306a36Sopenharmony_ci		data = dev_get_drvdata(dev);
10162306a36Sopenharmony_ci		if (data)
10262306a36Sopenharmony_ci			data->clk = parent_clk;
10362306a36Sopenharmony_ci	} else {
10462306a36Sopenharmony_ci		parent_clk = of_clk_get_by_name(np, cmu->clk_name);
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	if (IS_ERR(parent_clk))
10862306a36Sopenharmony_ci		return PTR_ERR(parent_clk);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	return clk_prepare_enable(parent_clk);
11162306a36Sopenharmony_ci}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic int __init exynos_arm64_cmu_prepare_pm(struct device *dev,
11462306a36Sopenharmony_ci		const struct samsung_cmu_info *cmu)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct exynos_arm64_cmu_data *data = dev_get_drvdata(dev);
11762306a36Sopenharmony_ci	int i;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	data->clk_save = samsung_clk_alloc_reg_dump(cmu->clk_regs,
12062306a36Sopenharmony_ci						    cmu->nr_clk_regs);
12162306a36Sopenharmony_ci	if (!data->clk_save)
12262306a36Sopenharmony_ci		return -ENOMEM;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	data->nr_clk_save = cmu->nr_clk_regs;
12562306a36Sopenharmony_ci	data->clk_suspend = cmu->suspend_regs;
12662306a36Sopenharmony_ci	data->nr_clk_suspend = cmu->nr_suspend_regs;
12762306a36Sopenharmony_ci	data->nr_pclks = of_clk_get_parent_count(dev->of_node);
12862306a36Sopenharmony_ci	if (!data->nr_pclks)
12962306a36Sopenharmony_ci		return 0;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	data->pclks = devm_kcalloc(dev, sizeof(struct clk *), data->nr_pclks,
13262306a36Sopenharmony_ci				   GFP_KERNEL);
13362306a36Sopenharmony_ci	if (!data->pclks) {
13462306a36Sopenharmony_ci		kfree(data->clk_save);
13562306a36Sopenharmony_ci		return -ENOMEM;
13662306a36Sopenharmony_ci	}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	for (i = 0; i < data->nr_pclks; i++) {
13962306a36Sopenharmony_ci		struct clk *clk = of_clk_get(dev->of_node, i);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci		if (IS_ERR(clk)) {
14262306a36Sopenharmony_ci			kfree(data->clk_save);
14362306a36Sopenharmony_ci			while (--i >= 0)
14462306a36Sopenharmony_ci				clk_put(data->pclks[i]);
14562306a36Sopenharmony_ci			return PTR_ERR(clk);
14662306a36Sopenharmony_ci		}
14762306a36Sopenharmony_ci		data->pclks[i] = clk;
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	return 0;
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci/**
15462306a36Sopenharmony_ci * exynos_arm64_register_cmu - Register specified Exynos CMU domain
15562306a36Sopenharmony_ci * @dev:	Device object; may be NULL if this function is not being
15662306a36Sopenharmony_ci *		called from platform driver probe function
15762306a36Sopenharmony_ci * @np:		CMU device tree node
15862306a36Sopenharmony_ci * @cmu:	CMU data
15962306a36Sopenharmony_ci *
16062306a36Sopenharmony_ci * Register specified CMU domain, which includes next steps:
16162306a36Sopenharmony_ci *
16262306a36Sopenharmony_ci * 1. Enable parent clock of @cmu CMU
16362306a36Sopenharmony_ci * 2. Set initial registers configuration for @cmu CMU clocks
16462306a36Sopenharmony_ci * 3. Register @cmu CMU clocks using Samsung clock framework API
16562306a36Sopenharmony_ci */
16662306a36Sopenharmony_civoid __init exynos_arm64_register_cmu(struct device *dev,
16762306a36Sopenharmony_ci		struct device_node *np, const struct samsung_cmu_info *cmu)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	int err;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	/*
17262306a36Sopenharmony_ci	 * Try to boot even if the parent clock enablement fails, as it might be
17362306a36Sopenharmony_ci	 * already enabled by bootloader.
17462306a36Sopenharmony_ci	 */
17562306a36Sopenharmony_ci	err = exynos_arm64_enable_bus_clk(dev, np, cmu);
17662306a36Sopenharmony_ci	if (err)
17762306a36Sopenharmony_ci		pr_err("%s: could not enable bus clock %s; err = %d\n",
17862306a36Sopenharmony_ci		       __func__, cmu->clk_name, err);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	exynos_arm64_init_clocks(np, cmu->clk_regs, cmu->nr_clk_regs);
18162306a36Sopenharmony_ci	samsung_cmu_register_one(np, cmu);
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci/**
18562306a36Sopenharmony_ci * exynos_arm64_register_cmu_pm - Register Exynos CMU domain with PM support
18662306a36Sopenharmony_ci *
18762306a36Sopenharmony_ci * @pdev:	Platform device object
18862306a36Sopenharmony_ci * @set_manual:	If true, set gate clocks to manual mode
18962306a36Sopenharmony_ci *
19062306a36Sopenharmony_ci * It's a version of exynos_arm64_register_cmu() with PM support. Should be
19162306a36Sopenharmony_ci * called from probe function of platform driver.
19262306a36Sopenharmony_ci *
19362306a36Sopenharmony_ci * Return: 0 on success, or negative error code on error.
19462306a36Sopenharmony_ci */
19562306a36Sopenharmony_ciint __init exynos_arm64_register_cmu_pm(struct platform_device *pdev,
19662306a36Sopenharmony_ci					bool set_manual)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	const struct samsung_cmu_info *cmu;
19962306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
20062306a36Sopenharmony_ci	struct device_node *np = dev->of_node;
20162306a36Sopenharmony_ci	struct exynos_arm64_cmu_data *data;
20262306a36Sopenharmony_ci	void __iomem *reg_base;
20362306a36Sopenharmony_ci	int ret;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	cmu = of_device_get_match_data(dev);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
20862306a36Sopenharmony_ci	if (!data)
20962306a36Sopenharmony_ci		return -ENOMEM;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	platform_set_drvdata(pdev, data);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	ret = exynos_arm64_cmu_prepare_pm(dev, cmu);
21462306a36Sopenharmony_ci	if (ret)
21562306a36Sopenharmony_ci		return ret;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	/*
21862306a36Sopenharmony_ci	 * Try to boot even if the parent clock enablement fails, as it might be
21962306a36Sopenharmony_ci	 * already enabled by bootloader.
22062306a36Sopenharmony_ci	 */
22162306a36Sopenharmony_ci	ret = exynos_arm64_enable_bus_clk(dev, NULL, cmu);
22262306a36Sopenharmony_ci	if (ret)
22362306a36Sopenharmony_ci		dev_err(dev, "%s: could not enable bus clock %s; err = %d\n",
22462306a36Sopenharmony_ci		       __func__, cmu->clk_name, ret);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	if (set_manual)
22762306a36Sopenharmony_ci		exynos_arm64_init_clocks(np, cmu->clk_regs, cmu->nr_clk_regs);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	reg_base = devm_platform_ioremap_resource(pdev, 0);
23062306a36Sopenharmony_ci	if (IS_ERR(reg_base))
23162306a36Sopenharmony_ci		return PTR_ERR(reg_base);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	data->ctx = samsung_clk_init(dev, reg_base, cmu->nr_clk_ids);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	/*
23662306a36Sopenharmony_ci	 * Enable runtime PM here to allow the clock core using runtime PM
23762306a36Sopenharmony_ci	 * for the registered clocks. Additionally, we increase the runtime
23862306a36Sopenharmony_ci	 * PM usage count before registering the clocks, to prevent the
23962306a36Sopenharmony_ci	 * clock core from runtime suspending the device.
24062306a36Sopenharmony_ci	 */
24162306a36Sopenharmony_ci	pm_runtime_get_noresume(dev);
24262306a36Sopenharmony_ci	pm_runtime_set_active(dev);
24362306a36Sopenharmony_ci	pm_runtime_enable(dev);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	samsung_cmu_register_clocks(data->ctx, cmu);
24662306a36Sopenharmony_ci	samsung_clk_of_add_provider(dev->of_node, data->ctx);
24762306a36Sopenharmony_ci	pm_runtime_put_sync(dev);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	return 0;
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ciint exynos_arm64_cmu_suspend(struct device *dev)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	struct exynos_arm64_cmu_data *data = dev_get_drvdata(dev);
25562306a36Sopenharmony_ci	int i;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	samsung_clk_save(data->ctx->reg_base, data->clk_save,
25862306a36Sopenharmony_ci			 data->nr_clk_save);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	for (i = 0; i < data->nr_pclks; i++)
26162306a36Sopenharmony_ci		clk_prepare_enable(data->pclks[i]);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	/* For suspend some registers have to be set to certain values */
26462306a36Sopenharmony_ci	samsung_clk_restore(data->ctx->reg_base, data->clk_suspend,
26562306a36Sopenharmony_ci			    data->nr_clk_suspend);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	for (i = 0; i < data->nr_pclks; i++)
26862306a36Sopenharmony_ci		clk_disable_unprepare(data->pclks[i]);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	clk_disable_unprepare(data->clk);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	return 0;
27362306a36Sopenharmony_ci}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ciint exynos_arm64_cmu_resume(struct device *dev)
27662306a36Sopenharmony_ci{
27762306a36Sopenharmony_ci	struct exynos_arm64_cmu_data *data = dev_get_drvdata(dev);
27862306a36Sopenharmony_ci	int i;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	clk_prepare_enable(data->clk);
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	for (i = 0; i < data->nr_pclks; i++)
28362306a36Sopenharmony_ci		clk_prepare_enable(data->pclks[i]);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	samsung_clk_restore(data->ctx->reg_base, data->clk_save,
28662306a36Sopenharmony_ci			    data->nr_clk_save);
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	for (i = 0; i < data->nr_pclks; i++)
28962306a36Sopenharmony_ci		clk_disable_unprepare(data->pclks[i]);
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	return 0;
29262306a36Sopenharmony_ci}
293