18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2016 Maxime Ripard
48c2ecf20Sopenharmony_ci * Maxime Ripard <maxime.ripard@free-electrons.com>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/clk-provider.h>
88c2ecf20Sopenharmony_ci#include <linux/io.h>
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include "ccu_gate.h"
118c2ecf20Sopenharmony_ci#include "ccu_nk.h"
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_cistruct _ccu_nk {
148c2ecf20Sopenharmony_ci	unsigned long	n, min_n, max_n;
158c2ecf20Sopenharmony_ci	unsigned long	k, min_k, max_k;
168c2ecf20Sopenharmony_ci};
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistatic void ccu_nk_find_best(unsigned long parent, unsigned long rate,
198c2ecf20Sopenharmony_ci			     struct _ccu_nk *nk)
208c2ecf20Sopenharmony_ci{
218c2ecf20Sopenharmony_ci	unsigned long best_rate = 0;
228c2ecf20Sopenharmony_ci	unsigned int best_k = 0, best_n = 0;
238c2ecf20Sopenharmony_ci	unsigned int _k, _n;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	for (_k = nk->min_k; _k <= nk->max_k; _k++) {
268c2ecf20Sopenharmony_ci		for (_n = nk->min_n; _n <= nk->max_n; _n++) {
278c2ecf20Sopenharmony_ci			unsigned long tmp_rate = parent * _n * _k;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci			if (tmp_rate > rate)
308c2ecf20Sopenharmony_ci				continue;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci			if ((rate - tmp_rate) < (rate - best_rate)) {
338c2ecf20Sopenharmony_ci				best_rate = tmp_rate;
348c2ecf20Sopenharmony_ci				best_k = _k;
358c2ecf20Sopenharmony_ci				best_n = _n;
368c2ecf20Sopenharmony_ci			}
378c2ecf20Sopenharmony_ci		}
388c2ecf20Sopenharmony_ci	}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	nk->k = best_k;
418c2ecf20Sopenharmony_ci	nk->n = best_n;
428c2ecf20Sopenharmony_ci}
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic void ccu_nk_disable(struct clk_hw *hw)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	struct ccu_nk *nk = hw_to_ccu_nk(hw);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	return ccu_gate_helper_disable(&nk->common, nk->enable);
498c2ecf20Sopenharmony_ci}
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic int ccu_nk_enable(struct clk_hw *hw)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	struct ccu_nk *nk = hw_to_ccu_nk(hw);
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	return ccu_gate_helper_enable(&nk->common, nk->enable);
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic int ccu_nk_is_enabled(struct clk_hw *hw)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	struct ccu_nk *nk = hw_to_ccu_nk(hw);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	return ccu_gate_helper_is_enabled(&nk->common, nk->enable);
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic unsigned long ccu_nk_recalc_rate(struct clk_hw *hw,
668c2ecf20Sopenharmony_ci					unsigned long parent_rate)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	struct ccu_nk *nk = hw_to_ccu_nk(hw);
698c2ecf20Sopenharmony_ci	unsigned long rate, n, k;
708c2ecf20Sopenharmony_ci	u32 reg;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	reg = readl(nk->common.base + nk->common.reg);
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	n = reg >> nk->n.shift;
758c2ecf20Sopenharmony_ci	n &= (1 << nk->n.width) - 1;
768c2ecf20Sopenharmony_ci	n += nk->n.offset;
778c2ecf20Sopenharmony_ci	if (!n)
788c2ecf20Sopenharmony_ci		n++;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	k = reg >> nk->k.shift;
818c2ecf20Sopenharmony_ci	k &= (1 << nk->k.width) - 1;
828c2ecf20Sopenharmony_ci	k += nk->k.offset;
838c2ecf20Sopenharmony_ci	if (!k)
848c2ecf20Sopenharmony_ci		k++;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	rate = parent_rate * n * k;
878c2ecf20Sopenharmony_ci	if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
888c2ecf20Sopenharmony_ci		rate /= nk->fixed_post_div;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	return rate;
918c2ecf20Sopenharmony_ci}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic long ccu_nk_round_rate(struct clk_hw *hw, unsigned long rate,
948c2ecf20Sopenharmony_ci			      unsigned long *parent_rate)
958c2ecf20Sopenharmony_ci{
968c2ecf20Sopenharmony_ci	struct ccu_nk *nk = hw_to_ccu_nk(hw);
978c2ecf20Sopenharmony_ci	struct _ccu_nk _nk;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
1008c2ecf20Sopenharmony_ci		rate *= nk->fixed_post_div;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	_nk.min_n = nk->n.min ?: 1;
1038c2ecf20Sopenharmony_ci	_nk.max_n = nk->n.max ?: 1 << nk->n.width;
1048c2ecf20Sopenharmony_ci	_nk.min_k = nk->k.min ?: 1;
1058c2ecf20Sopenharmony_ci	_nk.max_k = nk->k.max ?: 1 << nk->k.width;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	ccu_nk_find_best(*parent_rate, rate, &_nk);
1088c2ecf20Sopenharmony_ci	rate = *parent_rate * _nk.n * _nk.k;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
1118c2ecf20Sopenharmony_ci		rate = rate / nk->fixed_post_div;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	return rate;
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic int ccu_nk_set_rate(struct clk_hw *hw, unsigned long rate,
1178c2ecf20Sopenharmony_ci			   unsigned long parent_rate)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	struct ccu_nk *nk = hw_to_ccu_nk(hw);
1208c2ecf20Sopenharmony_ci	unsigned long flags;
1218c2ecf20Sopenharmony_ci	struct _ccu_nk _nk;
1228c2ecf20Sopenharmony_ci	u32 reg;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
1258c2ecf20Sopenharmony_ci		rate = rate * nk->fixed_post_div;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	_nk.min_n = nk->n.min ?: 1;
1288c2ecf20Sopenharmony_ci	_nk.max_n = nk->n.max ?: 1 << nk->n.width;
1298c2ecf20Sopenharmony_ci	_nk.min_k = nk->k.min ?: 1;
1308c2ecf20Sopenharmony_ci	_nk.max_k = nk->k.max ?: 1 << nk->k.width;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	ccu_nk_find_best(parent_rate, rate, &_nk);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	spin_lock_irqsave(nk->common.lock, flags);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	reg = readl(nk->common.base + nk->common.reg);
1378c2ecf20Sopenharmony_ci	reg &= ~GENMASK(nk->n.width + nk->n.shift - 1, nk->n.shift);
1388c2ecf20Sopenharmony_ci	reg &= ~GENMASK(nk->k.width + nk->k.shift - 1, nk->k.shift);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	reg |= (_nk.k - nk->k.offset) << nk->k.shift;
1418c2ecf20Sopenharmony_ci	reg |= (_nk.n - nk->n.offset) << nk->n.shift;
1428c2ecf20Sopenharmony_ci	writel(reg, nk->common.base + nk->common.reg);
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(nk->common.lock, flags);
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	ccu_helper_wait_for_lock(&nk->common, nk->lock);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	return 0;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ciconst struct clk_ops ccu_nk_ops = {
1528c2ecf20Sopenharmony_ci	.disable	= ccu_nk_disable,
1538c2ecf20Sopenharmony_ci	.enable		= ccu_nk_enable,
1548c2ecf20Sopenharmony_ci	.is_enabled	= ccu_nk_is_enabled,
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	.recalc_rate	= ccu_nk_recalc_rate,
1578c2ecf20Sopenharmony_ci	.round_rate	= ccu_nk_round_rate,
1588c2ecf20Sopenharmony_ci	.set_rate	= ccu_nk_set_rate,
1598c2ecf20Sopenharmony_ci};
160