linux/drivers/gpu/drm/sun4i/sun4i_dotclock.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright (C) 2016 Free Electrons
   4 * Copyright (C) 2016 NextThing Co
   5 *
   6 * Maxime Ripard <maxime.ripard@free-electrons.com>
   7 */
   8
   9#include <linux/clk-provider.h>
  10#include <linux/regmap.h>
  11
  12#include "sun4i_tcon.h"
  13#include "sun4i_dotclock.h"
  14
  15struct sun4i_dclk {
  16        struct clk_hw           hw;
  17        struct regmap           *regmap;
  18        struct sun4i_tcon       *tcon;
  19};
  20
  21static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw)
  22{
  23        return container_of(hw, struct sun4i_dclk, hw);
  24}
  25
  26static void sun4i_dclk_disable(struct clk_hw *hw)
  27{
  28        struct sun4i_dclk *dclk = hw_to_dclk(hw);
  29
  30        regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
  31                           BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0);
  32}
  33
  34static int sun4i_dclk_enable(struct clk_hw *hw)
  35{
  36        struct sun4i_dclk *dclk = hw_to_dclk(hw);
  37
  38        return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
  39                                  BIT(SUN4I_TCON0_DCLK_GATE_BIT),
  40                                  BIT(SUN4I_TCON0_DCLK_GATE_BIT));
  41}
  42
  43static int sun4i_dclk_is_enabled(struct clk_hw *hw)
  44{
  45        struct sun4i_dclk *dclk = hw_to_dclk(hw);
  46        u32 val;
  47
  48        regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
  49
  50        return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT);
  51}
  52
  53static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw,
  54                                            unsigned long parent_rate)
  55{
  56        struct sun4i_dclk *dclk = hw_to_dclk(hw);
  57        u32 val;
  58
  59        regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
  60
  61        val >>= SUN4I_TCON0_DCLK_DIV_SHIFT;
  62        val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1;
  63
  64        if (!val)
  65                val = 1;
  66
  67        return parent_rate / val;
  68}
  69
  70static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
  71                                  unsigned long *parent_rate)
  72{
  73        struct sun4i_dclk *dclk = hw_to_dclk(hw);
  74        struct sun4i_tcon *tcon = dclk->tcon;
  75        unsigned long best_parent = 0;
  76        u8 best_div = 1;
  77        int i;
  78
  79        for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) {
  80                u64 ideal = (u64)rate * i;
  81                unsigned long rounded;
  82
  83                /*
  84                 * ideal has overflowed the max value that can be stored in an
  85                 * unsigned long, and every clk operation we might do on a
  86                 * truncated u64 value will give us incorrect results.
  87                 * Let's just stop there since bigger dividers will result in
  88                 * the same overflow issue.
  89                 */
  90                if (ideal > ULONG_MAX)
  91                        goto out;
  92
  93                rounded = clk_hw_round_rate(clk_hw_get_parent(hw),
  94                                            ideal);
  95
  96                if (rounded == ideal) {
  97                        best_parent = rounded;
  98                        best_div = i;
  99                        goto out;
 100                }
 101
 102                if (abs(rate - rounded / i) <
 103                    abs(rate - best_parent / best_div)) {
 104                        best_parent = rounded;
 105                        best_div = i;
 106                }
 107        }
 108
 109out:
 110        *parent_rate = best_parent;
 111
 112        return best_parent / best_div;
 113}
 114
 115static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate,
 116                               unsigned long parent_rate)
 117{
 118        struct sun4i_dclk *dclk = hw_to_dclk(hw);
 119        u8 div = parent_rate / rate;
 120
 121        return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
 122                                  GENMASK(6, 0), div);
 123}
 124
 125static int sun4i_dclk_get_phase(struct clk_hw *hw)
 126{
 127        struct sun4i_dclk *dclk = hw_to_dclk(hw);
 128        u32 val;
 129
 130        regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val);
 131
 132        val >>= 28;
 133        val &= 3;
 134
 135        return val * 120;
 136}
 137
 138static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees)
 139{
 140        struct sun4i_dclk *dclk = hw_to_dclk(hw);
 141        u32 val = degrees / 120;
 142
 143        val <<= 28;
 144
 145        regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG,
 146                           GENMASK(29, 28),
 147                           val);
 148
 149        return 0;
 150}
 151
 152static const struct clk_ops sun4i_dclk_ops = {
 153        .disable        = sun4i_dclk_disable,
 154        .enable         = sun4i_dclk_enable,
 155        .is_enabled     = sun4i_dclk_is_enabled,
 156
 157        .recalc_rate    = sun4i_dclk_recalc_rate,
 158        .round_rate     = sun4i_dclk_round_rate,
 159        .set_rate       = sun4i_dclk_set_rate,
 160
 161        .get_phase      = sun4i_dclk_get_phase,
 162        .set_phase      = sun4i_dclk_set_phase,
 163};
 164
 165int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon)
 166{
 167        const char *clk_name, *parent_name;
 168        struct clk_init_data init;
 169        struct sun4i_dclk *dclk;
 170        int ret;
 171
 172        parent_name = __clk_get_name(tcon->sclk0);
 173        ret = of_property_read_string_index(dev->of_node,
 174                                            "clock-output-names", 0,
 175                                            &clk_name);
 176        if (ret)
 177                return ret;
 178
 179        dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
 180        if (!dclk)
 181                return -ENOMEM;
 182        dclk->tcon = tcon;
 183
 184        init.name = clk_name;
 185        init.ops = &sun4i_dclk_ops;
 186        init.parent_names = &parent_name;
 187        init.num_parents = 1;
 188        init.flags = CLK_SET_RATE_PARENT;
 189
 190        dclk->regmap = tcon->regs;
 191        dclk->hw.init = &init;
 192
 193        tcon->dclk = clk_register(dev, &dclk->hw);
 194        if (IS_ERR(tcon->dclk))
 195                return PTR_ERR(tcon->dclk);
 196
 197        return 0;
 198}
 199EXPORT_SYMBOL(sun4i_dclk_create);
 200
 201int sun4i_dclk_free(struct sun4i_tcon *tcon)
 202{
 203        clk_unregister(tcon->dclk);
 204        return 0;
 205}
 206EXPORT_SYMBOL(sun4i_dclk_free);
 207