162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2016 Maxime Ripard
462306a36Sopenharmony_ci * Maxime Ripard <maxime.ripard@free-electrons.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/clk-provider.h>
862306a36Sopenharmony_ci#include <linux/io.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include "ccu_gate.h"
1162306a36Sopenharmony_ci#include "ccu_div.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_cistatic unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
1462306a36Sopenharmony_ci					struct clk_hw *parent,
1562306a36Sopenharmony_ci					unsigned long *parent_rate,
1662306a36Sopenharmony_ci					unsigned long rate,
1762306a36Sopenharmony_ci					void *data)
1862306a36Sopenharmony_ci{
1962306a36Sopenharmony_ci	struct ccu_div *cd = data;
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci	if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV)
2262306a36Sopenharmony_ci		rate *= cd->fixed_post_div;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	rate = divider_round_rate_parent(&cd->common.hw, parent,
2562306a36Sopenharmony_ci					 rate, parent_rate,
2662306a36Sopenharmony_ci					 cd->div.table, cd->div.width,
2762306a36Sopenharmony_ci					 cd->div.flags);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV)
3062306a36Sopenharmony_ci		rate /= cd->fixed_post_div;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	return rate;
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic void ccu_div_disable(struct clk_hw *hw)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	struct ccu_div *cd = hw_to_ccu_div(hw);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	return ccu_gate_helper_disable(&cd->common, cd->enable);
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int ccu_div_enable(struct clk_hw *hw)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct ccu_div *cd = hw_to_ccu_div(hw);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	return ccu_gate_helper_enable(&cd->common, cd->enable);
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic int ccu_div_is_enabled(struct clk_hw *hw)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	struct ccu_div *cd = hw_to_ccu_div(hw);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	return ccu_gate_helper_is_enabled(&cd->common, cd->enable);
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
5762306a36Sopenharmony_ci					unsigned long parent_rate)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	struct ccu_div *cd = hw_to_ccu_div(hw);
6062306a36Sopenharmony_ci	unsigned long val;
6162306a36Sopenharmony_ci	u32 reg;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	reg = readl(cd->common.base + cd->common.reg);
6462306a36Sopenharmony_ci	val = reg >> cd->div.shift;
6562306a36Sopenharmony_ci	val &= (1 << cd->div.width) - 1;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1,
6862306a36Sopenharmony_ci						  parent_rate);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	val = divider_recalc_rate(hw, parent_rate, val, cd->div.table,
7162306a36Sopenharmony_ci				  cd->div.flags, cd->div.width);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV)
7462306a36Sopenharmony_ci		val /= cd->fixed_post_div;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	return val;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic int ccu_div_determine_rate(struct clk_hw *hw,
8062306a36Sopenharmony_ci				struct clk_rate_request *req)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	struct ccu_div *cd = hw_to_ccu_div(hw);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	return ccu_mux_helper_determine_rate(&cd->common, &cd->mux,
8562306a36Sopenharmony_ci					     req, ccu_div_round_rate, cd);
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic int ccu_div_set_rate(struct clk_hw *hw, unsigned long rate,
8962306a36Sopenharmony_ci			   unsigned long parent_rate)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct ccu_div *cd = hw_to_ccu_div(hw);
9262306a36Sopenharmony_ci	unsigned long flags;
9362306a36Sopenharmony_ci	unsigned long val;
9462306a36Sopenharmony_ci	u32 reg;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1,
9762306a36Sopenharmony_ci						  parent_rate);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV)
10062306a36Sopenharmony_ci		rate *= cd->fixed_post_div;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width,
10362306a36Sopenharmony_ci			      cd->div.flags);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	spin_lock_irqsave(cd->common.lock, flags);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	reg = readl(cd->common.base + cd->common.reg);
10862306a36Sopenharmony_ci	reg &= ~GENMASK(cd->div.width + cd->div.shift - 1, cd->div.shift);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	writel(reg | (val << cd->div.shift),
11162306a36Sopenharmony_ci	       cd->common.base + cd->common.reg);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	spin_unlock_irqrestore(cd->common.lock, flags);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	return 0;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic u8 ccu_div_get_parent(struct clk_hw *hw)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct ccu_div *cd = hw_to_ccu_div(hw);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	return ccu_mux_helper_get_parent(&cd->common, &cd->mux);
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic int ccu_div_set_parent(struct clk_hw *hw, u8 index)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	struct ccu_div *cd = hw_to_ccu_div(hw);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	return ccu_mux_helper_set_parent(&cd->common, &cd->mux, index);
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ciconst struct clk_ops ccu_div_ops = {
13362306a36Sopenharmony_ci	.disable	= ccu_div_disable,
13462306a36Sopenharmony_ci	.enable		= ccu_div_enable,
13562306a36Sopenharmony_ci	.is_enabled	= ccu_div_is_enabled,
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	.get_parent	= ccu_div_get_parent,
13862306a36Sopenharmony_ci	.set_parent	= ccu_div_set_parent,
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	.determine_rate	= ccu_div_determine_rate,
14162306a36Sopenharmony_ci	.recalc_rate	= ccu_div_recalc_rate,
14262306a36Sopenharmony_ci	.set_rate	= ccu_div_set_rate,
14362306a36Sopenharmony_ci};
14462306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_div_ops, SUNXI_CCU);
145