162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/clk-provider.h>
762306a36Sopenharmony_ci#include <linux/clkdev.h>
862306a36Sopenharmony_ci#include <linux/clk/at91_pmc.h>
962306a36Sopenharmony_ci#include <linux/of.h>
1062306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1162306a36Sopenharmony_ci#include <linux/regmap.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "pmc.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define to_clk_plldiv(hw) container_of(hw, struct clk_plldiv, hw)
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistruct clk_plldiv {
1862306a36Sopenharmony_ci	struct clk_hw hw;
1962306a36Sopenharmony_ci	struct regmap *regmap;
2062306a36Sopenharmony_ci};
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic unsigned long clk_plldiv_recalc_rate(struct clk_hw *hw,
2362306a36Sopenharmony_ci					    unsigned long parent_rate)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	struct clk_plldiv *plldiv = to_clk_plldiv(hw);
2662306a36Sopenharmony_ci	unsigned int mckr;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	regmap_read(plldiv->regmap, AT91_PMC_MCKR, &mckr);
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	if (mckr & AT91_PMC_PLLADIV2)
3162306a36Sopenharmony_ci		return parent_rate / 2;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	return parent_rate;
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic long clk_plldiv_round_rate(struct clk_hw *hw, unsigned long rate,
3762306a36Sopenharmony_ci					unsigned long *parent_rate)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	unsigned long div;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	if (rate > *parent_rate)
4262306a36Sopenharmony_ci		return *parent_rate;
4362306a36Sopenharmony_ci	div = *parent_rate / 2;
4462306a36Sopenharmony_ci	if (rate < div)
4562306a36Sopenharmony_ci		return div;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	if (rate - div < *parent_rate - rate)
4862306a36Sopenharmony_ci		return div;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	return *parent_rate;
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic int clk_plldiv_set_rate(struct clk_hw *hw, unsigned long rate,
5462306a36Sopenharmony_ci			       unsigned long parent_rate)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	struct clk_plldiv *plldiv = to_clk_plldiv(hw);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	if ((parent_rate != rate) && (parent_rate / 2 != rate))
5962306a36Sopenharmony_ci		return -EINVAL;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	regmap_update_bits(plldiv->regmap, AT91_PMC_MCKR, AT91_PMC_PLLADIV2,
6262306a36Sopenharmony_ci			   parent_rate != rate ? AT91_PMC_PLLADIV2 : 0);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	return 0;
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic const struct clk_ops plldiv_ops = {
6862306a36Sopenharmony_ci	.recalc_rate = clk_plldiv_recalc_rate,
6962306a36Sopenharmony_ci	.round_rate = clk_plldiv_round_rate,
7062306a36Sopenharmony_ci	.set_rate = clk_plldiv_set_rate,
7162306a36Sopenharmony_ci};
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistruct clk_hw * __init
7462306a36Sopenharmony_ciat91_clk_register_plldiv(struct regmap *regmap, const char *name,
7562306a36Sopenharmony_ci			 const char *parent_name)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	struct clk_plldiv *plldiv;
7862306a36Sopenharmony_ci	struct clk_hw *hw;
7962306a36Sopenharmony_ci	struct clk_init_data init;
8062306a36Sopenharmony_ci	int ret;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	plldiv = kzalloc(sizeof(*plldiv), GFP_KERNEL);
8362306a36Sopenharmony_ci	if (!plldiv)
8462306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	init.name = name;
8762306a36Sopenharmony_ci	init.ops = &plldiv_ops;
8862306a36Sopenharmony_ci	init.parent_names = parent_name ? &parent_name : NULL;
8962306a36Sopenharmony_ci	init.num_parents = parent_name ? 1 : 0;
9062306a36Sopenharmony_ci	init.flags = CLK_SET_RATE_GATE;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	plldiv->hw.init = &init;
9362306a36Sopenharmony_ci	plldiv->regmap = regmap;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	hw = &plldiv->hw;
9662306a36Sopenharmony_ci	ret = clk_hw_register(NULL, &plldiv->hw);
9762306a36Sopenharmony_ci	if (ret) {
9862306a36Sopenharmony_ci		kfree(plldiv);
9962306a36Sopenharmony_ci		hw = ERR_PTR(ret);
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	return hw;
10362306a36Sopenharmony_ci}
104