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