linux/drivers/clk/imx/clk-composite-8m.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright 2018 NXP
   4 */
   5
   6#include <linux/clk-provider.h>
   7#include <linux/errno.h>
   8#include <linux/io.h>
   9#include <linux/slab.h>
  10
  11#include "clk.h"
  12
  13#define PCG_PREDIV_SHIFT        16
  14#define PCG_PREDIV_WIDTH        3
  15#define PCG_PREDIV_MAX          8
  16
  17#define PCG_DIV_SHIFT           0
  18#define PCG_CORE_DIV_WIDTH      3
  19#define PCG_DIV_WIDTH           6
  20#define PCG_DIV_MAX             64
  21
  22#define PCG_PCS_SHIFT           24
  23#define PCG_PCS_MASK            0x7
  24
  25#define PCG_CGC_SHIFT           28
  26
  27static unsigned long imx8m_clk_composite_divider_recalc_rate(struct clk_hw *hw,
  28                                                unsigned long parent_rate)
  29{
  30        struct clk_divider *divider = to_clk_divider(hw);
  31        unsigned long prediv_rate;
  32        unsigned int prediv_value;
  33        unsigned int div_value;
  34
  35        prediv_value = readl(divider->reg) >> divider->shift;
  36        prediv_value &= clk_div_mask(divider->width);
  37
  38        prediv_rate = divider_recalc_rate(hw, parent_rate, prediv_value,
  39                                                NULL, divider->flags,
  40                                                divider->width);
  41
  42        div_value = readl(divider->reg) >> PCG_DIV_SHIFT;
  43        div_value &= clk_div_mask(PCG_DIV_WIDTH);
  44
  45        return divider_recalc_rate(hw, prediv_rate, div_value, NULL,
  46                                   divider->flags, PCG_DIV_WIDTH);
  47}
  48
  49static int imx8m_clk_composite_compute_dividers(unsigned long rate,
  50                                                unsigned long parent_rate,
  51                                                int *prediv, int *postdiv)
  52{
  53        int div1, div2;
  54        int error = INT_MAX;
  55        int ret = -EINVAL;
  56
  57        *prediv = 1;
  58        *postdiv = 1;
  59
  60        for (div1 = 1; div1 <= PCG_PREDIV_MAX; div1++) {
  61                for (div2 = 1; div2 <= PCG_DIV_MAX; div2++) {
  62                        int new_error = ((parent_rate / div1) / div2) - rate;
  63
  64                        if (abs(new_error) < abs(error)) {
  65                                *prediv = div1;
  66                                *postdiv = div2;
  67                                error = new_error;
  68                                ret = 0;
  69                        }
  70                }
  71        }
  72        return ret;
  73}
  74
  75static long imx8m_clk_composite_divider_round_rate(struct clk_hw *hw,
  76                                                unsigned long rate,
  77                                                unsigned long *prate)
  78{
  79        int prediv_value;
  80        int div_value;
  81
  82        imx8m_clk_composite_compute_dividers(rate, *prate,
  83                                                &prediv_value, &div_value);
  84        rate = DIV_ROUND_UP(*prate, prediv_value);
  85
  86        return DIV_ROUND_UP(rate, div_value);
  87
  88}
  89
  90static int imx8m_clk_composite_divider_set_rate(struct clk_hw *hw,
  91                                        unsigned long rate,
  92                                        unsigned long parent_rate)
  93{
  94        struct clk_divider *divider = to_clk_divider(hw);
  95        unsigned long flags;
  96        int prediv_value;
  97        int div_value;
  98        int ret;
  99        u32 val;
 100
 101        ret = imx8m_clk_composite_compute_dividers(rate, parent_rate,
 102                                                &prediv_value, &div_value);
 103        if (ret)
 104                return -EINVAL;
 105
 106        spin_lock_irqsave(divider->lock, flags);
 107
 108        val = readl(divider->reg);
 109        val &= ~((clk_div_mask(divider->width) << divider->shift) |
 110                        (clk_div_mask(PCG_DIV_WIDTH) << PCG_DIV_SHIFT));
 111
 112        val |= (u32)(prediv_value  - 1) << divider->shift;
 113        val |= (u32)(div_value - 1) << PCG_DIV_SHIFT;
 114        writel(val, divider->reg);
 115
 116        spin_unlock_irqrestore(divider->lock, flags);
 117
 118        return ret;
 119}
 120
 121static const struct clk_ops imx8m_clk_composite_divider_ops = {
 122        .recalc_rate = imx8m_clk_composite_divider_recalc_rate,
 123        .round_rate = imx8m_clk_composite_divider_round_rate,
 124        .set_rate = imx8m_clk_composite_divider_set_rate,
 125};
 126
 127struct clk_hw *imx8m_clk_hw_composite_flags(const char *name,
 128                                        const char * const *parent_names,
 129                                        int num_parents, void __iomem *reg,
 130                                        u32 composite_flags,
 131                                        unsigned long flags)
 132{
 133        struct clk_hw *hw = ERR_PTR(-ENOMEM), *mux_hw;
 134        struct clk_hw *div_hw, *gate_hw;
 135        struct clk_divider *div = NULL;
 136        struct clk_gate *gate = NULL;
 137        struct clk_mux *mux = NULL;
 138        const struct clk_ops *divider_ops;
 139
 140        mux = kzalloc(sizeof(*mux), GFP_KERNEL);
 141        if (!mux)
 142                goto fail;
 143
 144        mux_hw = &mux->hw;
 145        mux->reg = reg;
 146        mux->shift = PCG_PCS_SHIFT;
 147        mux->mask = PCG_PCS_MASK;
 148        mux->lock = &imx_ccm_lock;
 149
 150        div = kzalloc(sizeof(*div), GFP_KERNEL);
 151        if (!div)
 152                goto fail;
 153
 154        div_hw = &div->hw;
 155        div->reg = reg;
 156        if (composite_flags & IMX_COMPOSITE_CORE) {
 157                div->shift = PCG_DIV_SHIFT;
 158                div->width = PCG_CORE_DIV_WIDTH;
 159                divider_ops = &clk_divider_ops;
 160        } else {
 161                div->shift = PCG_PREDIV_SHIFT;
 162                div->width = PCG_PREDIV_WIDTH;
 163                divider_ops = &imx8m_clk_composite_divider_ops;
 164        }
 165
 166        div->lock = &imx_ccm_lock;
 167        div->flags = CLK_DIVIDER_ROUND_CLOSEST;
 168
 169        gate = kzalloc(sizeof(*gate), GFP_KERNEL);
 170        if (!gate)
 171                goto fail;
 172
 173        gate_hw = &gate->hw;
 174        gate->reg = reg;
 175        gate->bit_idx = PCG_CGC_SHIFT;
 176        gate->lock = &imx_ccm_lock;
 177
 178        hw = clk_hw_register_composite(NULL, name, parent_names, num_parents,
 179                        mux_hw, &clk_mux_ops, div_hw,
 180                        divider_ops, gate_hw, &clk_gate_ops, flags);
 181        if (IS_ERR(hw))
 182                goto fail;
 183
 184        return hw;
 185
 186fail:
 187        kfree(gate);
 188        kfree(div);
 189        kfree(mux);
 190        return ERR_CAST(hw);
 191}
 192