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