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