| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- /*
- * Copyright (c) 2006-2022, RT-Thread Development Team
- *
- * SPDX-License-Identifier: Apache-2.0
- *
- * Change Logs:
- * Date Author Notes
- * 2022-3-08 GuEe-GUI the first version
- */
- #include "clk-rk-fraction-divider.h"
- #include "clk-rk-mux.h"
- #include "clk-rk-gate.h"
- struct u32_fract
- {
- rt_uint32_t numerator;
- rt_uint32_t denominator;
- };
- #define CLK_FD_MSHIFT 16
- #define CLK_FD_MWIDTH 16
- #define CLK_FD_NSHIFT 0
- #define CLK_FD_NWIDTH 16
- rt_inline rt_uint32_t clk_fd_readl(struct rockchip_clk_cell *rk_cell)
- {
- void *base = rk_cell->provider->reg_base;
- if (rk_cell->div_flags & CLK_FRAC_DIVIDER_BIG_ENDIAN)
- {
- return rt_be32_to_cpu(HWREG32(base + rk_cell->muxdiv_offset));
- }
- return HWREG32(base + rk_cell->muxdiv_offset);
- }
- rt_inline void clk_fd_writel(struct rockchip_clk_cell *rk_cell, rt_uint32_t val)
- {
- void *base = rk_cell->provider->reg_base;
- if (rk_cell->div_flags & CLK_FRAC_DIVIDER_BIG_ENDIAN)
- {
- HWREG32(base + rk_cell->muxdiv_offset) = rt_cpu_to_be32(val);
- }
- else
- {
- HWREG32(base + rk_cell->muxdiv_offset) = val;
- }
- }
- static void clk_fd_get_div(struct rt_clk_cell *cell, struct u32_fract *fract)
- {
- rt_ubase_t m, n;
- rt_uint32_t val, mmask, nmask;
- struct rockchip_clk_cell *rk_cell = cell_to_rockchip_clk_cell(cell);
- val = clk_fd_readl(rk_cell);
- mmask = RT_GENMASK(CLK_FD_MWIDTH - 1, 0) << CLK_FD_MSHIFT;
- nmask = RT_GENMASK(CLK_FD_NWIDTH - 1, 0) << CLK_FD_NSHIFT;
- m = (val & mmask) >> CLK_FD_MSHIFT;
- n = (val & nmask) >> CLK_FD_NSHIFT;
- if (rk_cell->div_flags & CLK_FRAC_DIVIDER_ZERO_BASED)
- {
- ++m;
- ++n;
- }
- fract->numerator = m;
- fract->denominator = n;
- }
- static rt_ubase_t clk_fd_recalc_rate(struct rt_clk_cell *cell, rt_ubase_t parent_rate)
- {
- rt_uint64_t ret;
- struct u32_fract fract;
- clk_fd_get_div(cell, &fract);
- if (!fract.numerator || !fract.denominator)
- {
- return parent_rate;
- }
- ret = (rt_uint64_t)parent_rate * fract.numerator;
- rt_do_div(ret, fract.denominator);
- return ret;
- }
- static void clk_fractional_divider_general_approximation(struct rt_clk_cell *cell,
- rt_ubase_t rate, rt_ubase_t *parent_rate, rt_ubase_t *m, rt_ubase_t *n)
- {
- struct rockchip_clk_cell *rk_cell = cell_to_rockchip_clk_cell(cell);
- /*
- * Get rate closer to *parent_rate to guarantee there is no overflow
- * for m and n. In the result it will be the nearest rate left shifted
- * by (scale - CLK_FD_NWIDTH) bits.
- *
- * For the detailed explanation see the top comment in this file.
- */
- if (rk_cell->div_flags & CLK_FRAC_DIVIDER_POWER_OF_TWO_PS)
- {
- rt_ubase_t scale = fls_long(*parent_rate / rate - 1);
- if (scale > CLK_FD_NWIDTH)
- {
- rate <<= scale - CLK_FD_NWIDTH;
- }
- }
- rational_best_approximation(rate, *parent_rate,
- RT_GENMASK(CLK_FD_MWIDTH - 1, 0), RT_GENMASK(CLK_FD_NWIDTH - 1, 0), m, n);
- }
- /*
- * fractional divider must set that denominator is 20 times larger than
- * numerator to generate precise clock frequency.
- */
- static void rockchip_fractional_approximation(struct rt_clk_cell *cell,
- rt_ubase_t rate, rt_ubase_t *parent_rate, rt_ubase_t *m, rt_ubase_t *n)
- {
- struct rt_clk_cell *p_parent;
- rt_ubase_t p_rate, p_parent_rate;
- struct rockchip_clk_cell *rk_cell = cell_to_rockchip_clk_cell(cell);
- if (rate == 0)
- {
- *m = 0;
- *n = 1;
- return;
- }
- p_rate = rt_clk_cell_get_rate(rt_clk_cell_get_parent(cell));
- if (rate * 20 > p_rate && p_rate % rate != 0)
- {
- p_parent = rt_clk_cell_get_parent(rt_clk_cell_get_parent(cell));
- if (!p_parent)
- {
- *parent_rate = p_rate;
- }
- else
- {
- p_parent_rate = rt_clk_cell_get_rate(p_parent);
- *parent_rate = p_parent_rate;
- }
- if (*parent_rate == 0)
- {
- *m = 0;
- *n = 1;
- return;
- }
- if (*parent_rate < rate * 20)
- {
- /*
- * Fractional frequency divider to do
- * integer frequency divider does not need 20 times the limit.
- */
- if (!(*parent_rate % rate))
- {
- *m = 1;
- *n = *parent_rate / rate;
- return;
- }
- else if (!(rk_cell->div_flags & CLK_FRAC_DIVIDER_NO_LIMIT))
- {
- *m = 0;
- *n = 1;
- return;
- }
- }
- }
- rk_cell->div_flags |= CLK_FRAC_DIVIDER_POWER_OF_TWO_PS;
- clk_fractional_divider_general_approximation(cell, rate, parent_rate, m, n);
- }
- static rt_base_t clk_fd_round_rate(struct rt_clk_cell *cell,
- rt_ubase_t rate, rt_ubase_t *parent_rate)
- {
- rt_ubase_t m, n;
- rt_uint64_t ret;
- if (!rate || rate >= *parent_rate)
- {
- return *parent_rate;
- }
- rockchip_fractional_approximation(cell, rate, parent_rate, &m, &n);
- ret = (rt_uint64_t)*parent_rate * m;
- rt_do_div(ret, n);
- return ret;
- }
- static rt_err_t clk_fd_set_rate(struct rt_clk_cell *cell, rt_ubase_t rate, rt_ubase_t parent_rate)
- {
- rt_ubase_t m, n;
- rt_uint32_t mmask, nmask, val;
- struct rockchip_clk_cell *rk_cell = cell_to_rockchip_clk_cell(cell);
- rational_best_approximation(rate, parent_rate,
- RT_GENMASK(CLK_FD_MWIDTH - 1, 0), RT_GENMASK(CLK_FD_NWIDTH - 1, 0), &m, &n);
- if (rk_cell->div_flags & CLK_FRAC_DIVIDER_ZERO_BASED)
- {
- --m;
- --n;
- }
- mmask = RT_GENMASK(CLK_FD_MWIDTH - 1, 0) << CLK_FD_MSHIFT;
- nmask = RT_GENMASK(CLK_FD_NWIDTH - 1, 0) << CLK_FD_NSHIFT;
- /*
- * When compensation the fractional divider,
- * the [1:0] bits of the numerator register are omitted,
- * which will lead to a large deviation in the result.
- * Therefore, it is required that the numerator must
- * be greater than 4.
- *
- * Note that there are some exceptions here:
- * If there is an even frac div, we need to keep the original
- * numerator(<4) and denominator. Otherwise, it may cause the
- * issue that the duty ratio is not 50%.
- */
- if (m < 4 && m != 0)
- {
- if (n % 2 == 0)
- {
- val = 1;
- }
- else
- {
- val = RT_DIV_ROUND_UP(4, m);
- }
- n *= val;
- m *= val;
- if (n > nmask)
- {
- n = nmask;
- }
- }
- mmask = RT_GENMASK(CLK_FD_MWIDTH - 1, 0) << CLK_FD_MSHIFT;
- nmask = RT_GENMASK(CLK_FD_NWIDTH - 1, 0) << CLK_FD_NSHIFT;
- val = clk_fd_readl(rk_cell);
- val &= ~(mmask | nmask);
- val |= (m << CLK_FD_MSHIFT) | (n << CLK_FD_NSHIFT);
- clk_fd_writel(rk_cell, val);
- return RT_EOK;
- }
- static rt_err_t rockchip_clk_frac_notify(struct rt_clk_notifier *notifier,
- rt_ubase_t msg, rt_ubase_t old_rate, rt_ubase_t new_rate)
- {
- struct rt_clk_cell *cell;
- struct rockchip_fraction_divider_clk_cell *fraction_divider_cell;
- fraction_divider_cell = rt_container_of(notifier, struct rockchip_fraction_divider_clk_cell, notifier);
- cell = &fraction_divider_cell->rk_cell_child->cell;
- if (msg == RT_CLK_MSG_PRE_RATE_CHANGE)
- {
- fraction_divider_cell->rate_change_idx = cell->ops->get_parent(cell);
- if (fraction_divider_cell->rate_change_idx != fraction_divider_cell->mux_frac_idx)
- {
- cell->ops->set_parent(cell, fraction_divider_cell->mux_frac_idx);
- fraction_divider_cell->rate_change_remuxed = 1;
- }
- }
- else if (msg == RT_CLK_MSG_POST_RATE_CHANGE)
- {
- /*
- * The RT_CLK_MSG_POST_RATE_CHANGE notifier runs directly after the
- * divider clock is set in clk_change_rate, so we'll have
- * remuxed back to the original parent before clk_change_rate
- * reaches the mux itself.
- */
- if (fraction_divider_cell->rate_change_remuxed)
- {
- cell->ops->set_parent(cell, fraction_divider_cell->rate_change_idx);
- fraction_divider_cell->rate_change_remuxed = 0;
- }
- }
- return RT_EOK;
- }
- static int match_string(const char * const *array, size_t n, const char *string)
- {
- for (int index = 0; index < n; ++index)
- {
- const char *item = array[index];
- if (!item)
- {
- break;
- }
- if (!rt_strcmp(item, string))
- {
- return index;
- }
- }
- return -RT_EINVAL;
- }
- void rockchip_fraction_divider_clk_cell_init(struct rockchip_clk_cell *rk_cell)
- {
- struct rockchip_fraction_divider_clk_cell *fraction_divider_cell = cell_to_rockchip_fraction_divider_clk_cell(&rk_cell->cell);
- rk_cell->cell.ops = &fraction_divider_cell->ops;
- rk_cell->cell.flags |= RT_CLK_F_SET_RATE_UNGATE;
- if (fraction_divider_cell->rk_cell_child)
- {
- struct rockchip_clk_cell *rk_cell_child = fraction_divider_cell->rk_cell_child;
- struct rt_clk_cell *cell = &rk_cell_child->cell;
- rk_cell_child->cell.flags |= RT_CLK_F_SET_RATE_PARENT;
- fraction_divider_cell->mux_frac_idx = match_string(cell->parent_names, cell->parents_nr,
- rk_cell->cell.name);
- rockchip_mux_clk_cell_init(rk_cell);
- fraction_divider_cell->ops.get_parent = rockchip_mux_clk_ops.get_parent;
- fraction_divider_cell->ops.set_parent = rockchip_mux_clk_ops.set_parent;
- }
- if (rk_cell->gate_offset >= 0)
- {
- fraction_divider_cell->ops.enable = rockchip_gate_clk_ops.enable;
- fraction_divider_cell->ops.disable = rockchip_gate_clk_ops.disable;
- fraction_divider_cell->ops.is_enabled = rockchip_gate_clk_ops.is_enabled;
- }
- fraction_divider_cell->ops.recalc_rate = clk_fd_recalc_rate;
- fraction_divider_cell->ops.round_rate = clk_fd_round_rate;
- fraction_divider_cell->ops.set_rate = clk_fd_set_rate;
- }
- void rockchip_fraction_divider_clk_cell_setup(struct rockchip_clk_cell *rk_cell)
- {
- struct rockchip_fraction_divider_clk_cell *fraction_divider_cell = cell_to_rockchip_fraction_divider_clk_cell(&rk_cell->cell);
- struct rockchip_clk_cell *rk_cell_child = fraction_divider_cell->rk_cell_child;
- if (fraction_divider_cell->mux_frac_idx >= 0)
- {
- fraction_divider_cell->notifier.callback = rockchip_clk_frac_notify;
- rt_clk_notifier_register(rt_clk_cell_get_clk(&rk_cell_child->cell, RT_NULL),
- &fraction_divider_cell->notifier);
- }
- }
|