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