162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl>
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/clk-provider.h>
762306a36Sopenharmony_ci#include <linux/err.h>
862306a36Sopenharmony_ci#include <linux/io.h>
962306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1062306a36Sopenharmony_ci#include <linux/of.h>
1162306a36Sopenharmony_ci#include <linux/of_address.h>
1262306a36Sopenharmony_ci#include <linux/regmap.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define PMU_XTAL_FREQ_RATIO			0x66c
1662306a36Sopenharmony_ci#define  XTAL_ALP_PER_4ILP			0x00001fff
1762306a36Sopenharmony_ci#define  XTAL_CTL_EN				0x80000000
1862306a36Sopenharmony_ci#define PMU_SLOW_CLK_PERIOD			0x6dc
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistruct bcm53573_ilp {
2162306a36Sopenharmony_ci	struct clk_hw hw;
2262306a36Sopenharmony_ci	struct regmap *regmap;
2362306a36Sopenharmony_ci};
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic int bcm53573_ilp_enable(struct clk_hw *hw)
2662306a36Sopenharmony_ci{
2762306a36Sopenharmony_ci	struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0x10199);
3062306a36Sopenharmony_ci	regmap_write(ilp->regmap, 0x674, 0x10000);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	return 0;
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic void bcm53573_ilp_disable(struct clk_hw *hw)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	regmap_write(ilp->regmap, PMU_SLOW_CLK_PERIOD, 0);
4062306a36Sopenharmony_ci	regmap_write(ilp->regmap, 0x674, 0);
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic unsigned long bcm53573_ilp_recalc_rate(struct clk_hw *hw,
4462306a36Sopenharmony_ci					      unsigned long parent_rate)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	struct bcm53573_ilp *ilp = container_of(hw, struct bcm53573_ilp, hw);
4762306a36Sopenharmony_ci	struct regmap *regmap = ilp->regmap;
4862306a36Sopenharmony_ci	u32 last_val, cur_val;
4962306a36Sopenharmony_ci	int sum = 0, num = 0, loop_num = 0;
5062306a36Sopenharmony_ci	int avg;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	/* Enable measurement */
5362306a36Sopenharmony_ci	regmap_write(regmap, PMU_XTAL_FREQ_RATIO, XTAL_CTL_EN);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	/* Read initial value */
5662306a36Sopenharmony_ci	regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &last_val);
5762306a36Sopenharmony_ci	last_val &= XTAL_ALP_PER_4ILP;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	/*
6062306a36Sopenharmony_ci	 * At minimum we should loop for a bit to let hardware do the
6162306a36Sopenharmony_ci	 * measurement. This isn't very accurate however, so for a better
6262306a36Sopenharmony_ci	 * precision lets try getting 20 different values for and use average.
6362306a36Sopenharmony_ci	 */
6462306a36Sopenharmony_ci	while (num < 20) {
6562306a36Sopenharmony_ci		regmap_read(regmap, PMU_XTAL_FREQ_RATIO, &cur_val);
6662306a36Sopenharmony_ci		cur_val &= XTAL_ALP_PER_4ILP;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci		if (cur_val != last_val) {
6962306a36Sopenharmony_ci			/* Got different value, use it */
7062306a36Sopenharmony_ci			sum += cur_val;
7162306a36Sopenharmony_ci			num++;
7262306a36Sopenharmony_ci			loop_num = 0;
7362306a36Sopenharmony_ci			last_val = cur_val;
7462306a36Sopenharmony_ci		} else if (++loop_num > 5000) {
7562306a36Sopenharmony_ci			/* Same value over and over, give up */
7662306a36Sopenharmony_ci			sum += cur_val;
7762306a36Sopenharmony_ci			num++;
7862306a36Sopenharmony_ci			break;
7962306a36Sopenharmony_ci		}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci		cpu_relax();
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	/* Disable measurement to save power */
8562306a36Sopenharmony_ci	regmap_write(regmap, PMU_XTAL_FREQ_RATIO, 0x0);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	avg = sum / num;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	return parent_rate * 4 / avg;
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic const struct clk_ops bcm53573_ilp_clk_ops = {
9362306a36Sopenharmony_ci	.enable = bcm53573_ilp_enable,
9462306a36Sopenharmony_ci	.disable = bcm53573_ilp_disable,
9562306a36Sopenharmony_ci	.recalc_rate = bcm53573_ilp_recalc_rate,
9662306a36Sopenharmony_ci};
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic void bcm53573_ilp_init(struct device_node *np)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	struct bcm53573_ilp *ilp;
10162306a36Sopenharmony_ci	struct clk_init_data init = { };
10262306a36Sopenharmony_ci	const char *parent_name;
10362306a36Sopenharmony_ci	int err;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	ilp = kzalloc(sizeof(*ilp), GFP_KERNEL);
10662306a36Sopenharmony_ci	if (!ilp)
10762306a36Sopenharmony_ci		return;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	parent_name = of_clk_get_parent_name(np, 0);
11062306a36Sopenharmony_ci	if (!parent_name) {
11162306a36Sopenharmony_ci		err = -ENOENT;
11262306a36Sopenharmony_ci		goto err_free_ilp;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	ilp->regmap = syscon_node_to_regmap(of_get_parent(np));
11662306a36Sopenharmony_ci	if (IS_ERR(ilp->regmap)) {
11762306a36Sopenharmony_ci		err = PTR_ERR(ilp->regmap);
11862306a36Sopenharmony_ci		goto err_free_ilp;
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	init.name = np->name;
12262306a36Sopenharmony_ci	init.ops = &bcm53573_ilp_clk_ops;
12362306a36Sopenharmony_ci	init.parent_names = &parent_name;
12462306a36Sopenharmony_ci	init.num_parents = 1;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	ilp->hw.init = &init;
12762306a36Sopenharmony_ci	err = clk_hw_register(NULL, &ilp->hw);
12862306a36Sopenharmony_ci	if (err)
12962306a36Sopenharmony_ci		goto err_free_ilp;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	err = of_clk_add_hw_provider(np, of_clk_hw_simple_get, &ilp->hw);
13262306a36Sopenharmony_ci	if (err)
13362306a36Sopenharmony_ci		goto err_clk_hw_unregister;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	return;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cierr_clk_hw_unregister:
13862306a36Sopenharmony_ci	clk_hw_unregister(&ilp->hw);
13962306a36Sopenharmony_cierr_free_ilp:
14062306a36Sopenharmony_ci	kfree(ilp);
14162306a36Sopenharmony_ci	pr_err("Failed to init ILP clock: %d\n", err);
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci/* We need it very early for arch code, before device model gets ready */
14562306a36Sopenharmony_ciCLK_OF_DECLARE(bcm53573_ilp_clk, "brcm,bcm53573-ilp", bcm53573_ilp_init);
146