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#define div_shift_width 16 153d0407baSopenharmony_ci#define div_odd 2 163d0407baSopenharmony_ci 173d0407baSopenharmony_cistatic unsigned long clk_dclk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) 183d0407baSopenharmony_ci{ 193d0407baSopenharmony_ci struct clk_divider *divider = to_clk_divider(hw); 203d0407baSopenharmony_ci unsigned int val; 213d0407baSopenharmony_ci 223d0407baSopenharmony_ci val = readl(divider->reg) >> divider->shift; 233d0407baSopenharmony_ci val &= div_mask(divider->width); 243d0407baSopenharmony_ci 253d0407baSopenharmony_ci return DIV_ROUND_UP_ULL(((u64)parent_rate), val + 1); 263d0407baSopenharmony_ci} 273d0407baSopenharmony_ci 283d0407baSopenharmony_cistatic long clk_dclk_round_rate(struct clk_hw *hw, unsigned long rate, 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 % div_odd) { 353d0407baSopenharmony_ci div = __rounddown_pow_of_two(div); 363d0407baSopenharmony_ci } 373d0407baSopenharmony_ci div = div > maxdiv ? maxdiv : div; 383d0407baSopenharmony_ci *prate = div * rate; 393d0407baSopenharmony_ci return rate; 403d0407baSopenharmony_ci} 413d0407baSopenharmony_ci 423d0407baSopenharmony_cistatic int clk_dclk_set_rate(struct clk_hw *hw, unsigned long rate, 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, divider->width, divider->flags); 503d0407baSopenharmony_ci 513d0407baSopenharmony_ci if (divider->lock) { 523d0407baSopenharmony_ci spin_lock_irqsave(divider->lock, flags); 533d0407baSopenharmony_ci } else { 543d0407baSopenharmony_ci __acquire(divider->lock); 553d0407baSopenharmony_ci } 563d0407baSopenharmony_ci 573d0407baSopenharmony_ci if (divider->flags & CLK_DIVIDER_HIWORD_MASK) { 583d0407baSopenharmony_ci val = div_mask(divider->width) << (divider->shift + div_shift_width); 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 723d0407baSopenharmony_ci return 0; 733d0407baSopenharmony_ci} 743d0407baSopenharmony_ci 753d0407baSopenharmony_ciconst struct clk_ops clk_dclk_divider_ops = { 763d0407baSopenharmony_ci .recalc_rate = clk_dclk_recalc_rate, 773d0407baSopenharmony_ci .round_rate = clk_dclk_round_rate, 783d0407baSopenharmony_ci .set_rate = clk_dclk_set_rate, 793d0407baSopenharmony_ci}; 803d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(clk_dclk_divider_ops); 813d0407baSopenharmony_ci 823d0407baSopenharmony_ci/** 833d0407baSopenharmony_ci * Register a clock branch. 843d0407baSopenharmony_ci * Most clock branches have a form like 853d0407baSopenharmony_ci * 863d0407baSopenharmony_ci * src1 --|--\ 873d0407baSopenharmony_ci * |M |--[GATE]-[DIV]- 883d0407baSopenharmony_ci * src2 --|--/ 893d0407baSopenharmony_ci * 903d0407baSopenharmony_ci * sometimes without one of those components. 913d0407baSopenharmony_ci */ 923d0407baSopenharmony_cistruct clk *rockchip_clk_register_dclk_branch(const char *name, const char *const *parent_names, u8 num_parents, 933d0407baSopenharmony_ci void __iomem *base, int muxdiv_offset, u8 mux_shift, u8 mux_width, 943d0407baSopenharmony_ci u8 mux_flags, int div_offset, u8 div_shift, u8 div_width, u8 div_flags, 953d0407baSopenharmony_ci struct clk_div_table *div_table, int gate_offset, u8 gate_shift, 963d0407baSopenharmony_ci u8 gate_flags, unsigned long flags, unsigned long max_prate, 973d0407baSopenharmony_ci spinlock_t *lock) 983d0407baSopenharmony_ci{ 993d0407baSopenharmony_ci struct clk *clk; 1003d0407baSopenharmony_ci struct clk_mux *mux = NULL; 1013d0407baSopenharmony_ci struct clk_gate *gate = NULL; 1023d0407baSopenharmony_ci struct clk_divider *div = NULL; 1033d0407baSopenharmony_ci const struct clk_ops *mux_ops = NULL, *div_ops = NULL, *gate_ops = NULL; 1043d0407baSopenharmony_ci 1053d0407baSopenharmony_ci if (num_parents > 1) { 1063d0407baSopenharmony_ci mux = kzalloc(sizeof(*mux), GFP_KERNEL); 1073d0407baSopenharmony_ci if (!mux) { 1083d0407baSopenharmony_ci return ERR_PTR(-ENOMEM); 1093d0407baSopenharmony_ci } 1103d0407baSopenharmony_ci 1113d0407baSopenharmony_ci mux->reg = base + muxdiv_offset; 1123d0407baSopenharmony_ci mux->shift = mux_shift; 1133d0407baSopenharmony_ci mux->mask = BIT(mux_width) - 1; 1143d0407baSopenharmony_ci mux->flags = mux_flags; 1153d0407baSopenharmony_ci mux->lock = lock; 1163d0407baSopenharmony_ci mux_ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops : &clk_mux_ops; 1173d0407baSopenharmony_ci } 1183d0407baSopenharmony_ci 1193d0407baSopenharmony_ci if (gate_offset >= 0) { 1203d0407baSopenharmony_ci gate = kzalloc(sizeof(*gate), GFP_KERNEL); 1213d0407baSopenharmony_ci if (!gate) { 1223d0407baSopenharmony_ci goto err_gate; 1233d0407baSopenharmony_ci } 1243d0407baSopenharmony_ci 1253d0407baSopenharmony_ci gate->flags = gate_flags; 1263d0407baSopenharmony_ci gate->reg = base + gate_offset; 1273d0407baSopenharmony_ci gate->bit_idx = gate_shift; 1283d0407baSopenharmony_ci gate->lock = lock; 1293d0407baSopenharmony_ci gate_ops = &clk_gate_ops; 1303d0407baSopenharmony_ci } 1313d0407baSopenharmony_ci 1323d0407baSopenharmony_ci if (div_width > 0) { 1333d0407baSopenharmony_ci div = kzalloc(sizeof(*div), GFP_KERNEL); 1343d0407baSopenharmony_ci if (!div) { 1353d0407baSopenharmony_ci goto err_div; 1363d0407baSopenharmony_ci } 1373d0407baSopenharmony_ci 1383d0407baSopenharmony_ci div->flags = div_flags; 1393d0407baSopenharmony_ci if (div_offset) { 1403d0407baSopenharmony_ci div->reg = base + div_offset; 1413d0407baSopenharmony_ci } else { 1423d0407baSopenharmony_ci div->reg = base + muxdiv_offset; 1433d0407baSopenharmony_ci } 1443d0407baSopenharmony_ci div->shift = div_shift; 1453d0407baSopenharmony_ci div->width = div_width; 1463d0407baSopenharmony_ci div->lock = lock; 1473d0407baSopenharmony_ci div->max_prate = max_prate; 1483d0407baSopenharmony_ci div_ops = &clk_dclk_divider_ops; 1493d0407baSopenharmony_ci } 1503d0407baSopenharmony_ci 1513d0407baSopenharmony_ci clk = clk_register_composite(NULL, name, parent_names, num_parents, mux ? &mux->hw : NULL, mux_ops, 1523d0407baSopenharmony_ci div ? &div->hw : NULL, div_ops, gate ? &gate->hw : NULL, gate_ops, flags); 1533d0407baSopenharmony_ci 1543d0407baSopenharmony_ci return clk; 1553d0407baSopenharmony_cierr_div: 1563d0407baSopenharmony_ci kfree(gate); 1573d0407baSopenharmony_cierr_gate: 1583d0407baSopenharmony_ci kfree(mux); 1593d0407baSopenharmony_ci return ERR_PTR(-ENOMEM); 1603d0407baSopenharmony_ci} 161