13d0407baSopenharmony_ci// SPDX-License-Identifier: GPL-2.0
23d0407baSopenharmony_ci/*
33d0407baSopenharmony_ci * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd
43d0407baSopenharmony_ci */
53d0407baSopenharmony_ci
63d0407baSopenharmony_ci#include <linux/slab.h>
73d0407baSopenharmony_ci#include <linux/bitops.h>
83d0407baSopenharmony_ci#include <linux/regmap.h>
93d0407baSopenharmony_ci#include <linux/clk.h>
103d0407baSopenharmony_ci#include <linux/clk-provider.h>
113d0407baSopenharmony_ci#include "clk.h"
123d0407baSopenharmony_ci
133d0407baSopenharmony_ci#define div_mask(width)	((1 << (width)) - 1)
143d0407baSopenharmony_ci
153d0407baSopenharmony_cistatic unsigned long clk_dclk_recalc_rate(struct clk_hw *hw,
163d0407baSopenharmony_ci					  unsigned long parent_rate)
173d0407baSopenharmony_ci{
183d0407baSopenharmony_ci	struct clk_divider *divider = to_clk_divider(hw);
193d0407baSopenharmony_ci	unsigned int val;
203d0407baSopenharmony_ci
213d0407baSopenharmony_ci	val = readl(divider->reg) >> divider->shift;
223d0407baSopenharmony_ci	val &= div_mask(divider->width);
233d0407baSopenharmony_ci
243d0407baSopenharmony_ci	return DIV_ROUND_UP_ULL(((u64)parent_rate), val + 1);
253d0407baSopenharmony_ci}
263d0407baSopenharmony_ci
273d0407baSopenharmony_cistatic long clk_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
283d0407baSopenharmony_ci				unsigned long *prate)
293d0407baSopenharmony_ci{
303d0407baSopenharmony_ci	struct clk_divider *divider = to_clk_divider(hw);
313d0407baSopenharmony_ci	int div, maxdiv =  div_mask(divider->width) + 1;
323d0407baSopenharmony_ci
333d0407baSopenharmony_ci	div = DIV_ROUND_UP_ULL(divider->max_prate, rate);
343d0407baSopenharmony_ci	if (div % 2)
353d0407baSopenharmony_ci		div = __rounddown_pow_of_two(div);
363d0407baSopenharmony_ci	div = div > maxdiv ? maxdiv : div;
373d0407baSopenharmony_ci	*prate = div  * rate;
383d0407baSopenharmony_ci	return rate;
393d0407baSopenharmony_ci}
403d0407baSopenharmony_ci
413d0407baSopenharmony_cistatic int clk_dclk_set_rate(struct clk_hw *hw, unsigned long rate,
423d0407baSopenharmony_ci			     unsigned long parent_rate)
433d0407baSopenharmony_ci{
443d0407baSopenharmony_ci	struct clk_divider *divider = to_clk_divider(hw);
453d0407baSopenharmony_ci	unsigned int value;
463d0407baSopenharmony_ci	unsigned long flags = 0;
473d0407baSopenharmony_ci	u32 val;
483d0407baSopenharmony_ci
493d0407baSopenharmony_ci	value = divider_get_val(rate, parent_rate, divider->table,
503d0407baSopenharmony_ci				divider->width, divider->flags);
513d0407baSopenharmony_ci
523d0407baSopenharmony_ci	if (divider->lock)
533d0407baSopenharmony_ci		spin_lock_irqsave(divider->lock, flags);
543d0407baSopenharmony_ci	else
553d0407baSopenharmony_ci		__acquire(divider->lock);
563d0407baSopenharmony_ci
573d0407baSopenharmony_ci	if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
583d0407baSopenharmony_ci		val = div_mask(divider->width) << (divider->shift + 16);
593d0407baSopenharmony_ci	} else {
603d0407baSopenharmony_ci		val = readl(divider->reg);
613d0407baSopenharmony_ci		val &= ~(div_mask(divider->width) << divider->shift);
623d0407baSopenharmony_ci	}
633d0407baSopenharmony_ci	val |= value << divider->shift;
643d0407baSopenharmony_ci	writel(val, divider->reg);
653d0407baSopenharmony_ci
663d0407baSopenharmony_ci	if (divider->lock)
673d0407baSopenharmony_ci		spin_unlock_irqrestore(divider->lock, flags);
683d0407baSopenharmony_ci	else
693d0407baSopenharmony_ci		__release(divider->lock);
703d0407baSopenharmony_ci
713d0407baSopenharmony_ci	return 0;
723d0407baSopenharmony_ci}
733d0407baSopenharmony_ci
743d0407baSopenharmony_ciconst struct clk_ops clk_dclk_divider_ops = {
753d0407baSopenharmony_ci	.recalc_rate = clk_dclk_recalc_rate,
763d0407baSopenharmony_ci	.round_rate = clk_dclk_round_rate,
773d0407baSopenharmony_ci	.set_rate = clk_dclk_set_rate,
783d0407baSopenharmony_ci};
793d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(clk_dclk_divider_ops);
803d0407baSopenharmony_ci
813d0407baSopenharmony_ci/**
823d0407baSopenharmony_ci * Register a clock branch.
833d0407baSopenharmony_ci * Most clock branches have a form like
843d0407baSopenharmony_ci *
853d0407baSopenharmony_ci * src1 --|--\
863d0407baSopenharmony_ci *        |M |--[GATE]-[DIV]-
873d0407baSopenharmony_ci * src2 --|--/
883d0407baSopenharmony_ci *
893d0407baSopenharmony_ci * sometimes without one of those components.
903d0407baSopenharmony_ci */
913d0407baSopenharmony_cistruct clk *rockchip_clk_register_dclk_branch(const char *name,
923d0407baSopenharmony_ci					      const char *const *parent_names,
933d0407baSopenharmony_ci					      u8 num_parents,
943d0407baSopenharmony_ci					      void __iomem *base,
953d0407baSopenharmony_ci					      int muxdiv_offset, u8 mux_shift,
963d0407baSopenharmony_ci					      u8 mux_width, u8 mux_flags,
973d0407baSopenharmony_ci					      int div_offset, u8 div_shift,
983d0407baSopenharmony_ci					      u8 div_width, u8 div_flags,
993d0407baSopenharmony_ci					      struct clk_div_table *div_table,
1003d0407baSopenharmony_ci					      int gate_offset,
1013d0407baSopenharmony_ci					      u8 gate_shift, u8 gate_flags,
1023d0407baSopenharmony_ci					      unsigned long flags,
1033d0407baSopenharmony_ci					      unsigned long max_prate,
1043d0407baSopenharmony_ci					      spinlock_t *lock)
1053d0407baSopenharmony_ci{
1063d0407baSopenharmony_ci	struct clk *clk;
1073d0407baSopenharmony_ci	struct clk_mux *mux = NULL;
1083d0407baSopenharmony_ci	struct clk_gate *gate = NULL;
1093d0407baSopenharmony_ci	struct clk_divider *div = NULL;
1103d0407baSopenharmony_ci	const struct clk_ops *mux_ops = NULL, *div_ops = NULL,
1113d0407baSopenharmony_ci			     *gate_ops = NULL;
1123d0407baSopenharmony_ci
1133d0407baSopenharmony_ci	if (num_parents > 1) {
1143d0407baSopenharmony_ci		mux = kzalloc(sizeof(*mux), GFP_KERNEL);
1153d0407baSopenharmony_ci		if (!mux)
1163d0407baSopenharmony_ci			return ERR_PTR(-ENOMEM);
1173d0407baSopenharmony_ci
1183d0407baSopenharmony_ci		mux->reg = base + muxdiv_offset;
1193d0407baSopenharmony_ci		mux->shift = mux_shift;
1203d0407baSopenharmony_ci		mux->mask = BIT(mux_width) - 1;
1213d0407baSopenharmony_ci		mux->flags = mux_flags;
1223d0407baSopenharmony_ci		mux->lock = lock;
1233d0407baSopenharmony_ci		mux_ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops
1243d0407baSopenharmony_ci							: &clk_mux_ops;
1253d0407baSopenharmony_ci	}
1263d0407baSopenharmony_ci
1273d0407baSopenharmony_ci	if (gate_offset >= 0) {
1283d0407baSopenharmony_ci		gate = kzalloc(sizeof(*gate), GFP_KERNEL);
1293d0407baSopenharmony_ci		if (!gate)
1303d0407baSopenharmony_ci			goto err_gate;
1313d0407baSopenharmony_ci
1323d0407baSopenharmony_ci		gate->flags = gate_flags;
1333d0407baSopenharmony_ci		gate->reg = base + gate_offset;
1343d0407baSopenharmony_ci		gate->bit_idx = gate_shift;
1353d0407baSopenharmony_ci		gate->lock = lock;
1363d0407baSopenharmony_ci		gate_ops = &clk_gate_ops;
1373d0407baSopenharmony_ci	}
1383d0407baSopenharmony_ci
1393d0407baSopenharmony_ci	if (div_width > 0) {
1403d0407baSopenharmony_ci		div = kzalloc(sizeof(*div), GFP_KERNEL);
1413d0407baSopenharmony_ci		if (!div)
1423d0407baSopenharmony_ci			goto err_div;
1433d0407baSopenharmony_ci
1443d0407baSopenharmony_ci		div->flags = div_flags;
1453d0407baSopenharmony_ci		if (div_offset)
1463d0407baSopenharmony_ci			div->reg = base + div_offset;
1473d0407baSopenharmony_ci		else
1483d0407baSopenharmony_ci			div->reg = base + muxdiv_offset;
1493d0407baSopenharmony_ci		div->shift = div_shift;
1503d0407baSopenharmony_ci		div->width = div_width;
1513d0407baSopenharmony_ci		div->lock = lock;
1523d0407baSopenharmony_ci		div->max_prate = max_prate;
1533d0407baSopenharmony_ci		div_ops = &clk_dclk_divider_ops;
1543d0407baSopenharmony_ci	}
1553d0407baSopenharmony_ci
1563d0407baSopenharmony_ci	clk = clk_register_composite(NULL, name, parent_names, num_parents,
1573d0407baSopenharmony_ci				     mux ? &mux->hw : NULL, mux_ops,
1583d0407baSopenharmony_ci				     div ? &div->hw : NULL, div_ops,
1593d0407baSopenharmony_ci				     gate ? &gate->hw : NULL, gate_ops,
1603d0407baSopenharmony_ci				     flags);
1613d0407baSopenharmony_ci
1623d0407baSopenharmony_ci	return clk;
1633d0407baSopenharmony_cierr_div:
1643d0407baSopenharmony_ci	kfree(gate);
1653d0407baSopenharmony_cierr_gate:
1663d0407baSopenharmony_ci	kfree(mux);
1673d0407baSopenharmony_ci	return ERR_PTR(-ENOMEM);
1683d0407baSopenharmony_ci}
169