linux/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright 2015 Maxime Ripard
   4 *
   5 * Maxime Ripard <maxime.ripard@free-electrons.com>
   6 */
   7
   8#include <linux/clk-provider.h>
   9#include <linux/io.h>
  10#include <linux/of.h>
  11#include <linux/of_address.h>
  12#include <linux/slab.h>
  13#include <linux/spinlock.h>
  14
  15#define TCON_CH1_SCLK2_PARENTS          4
  16
  17#define TCON_CH1_SCLK2_GATE_BIT         BIT(31)
  18#define TCON_CH1_SCLK2_MUX_MASK         3
  19#define TCON_CH1_SCLK2_MUX_SHIFT        24
  20#define TCON_CH1_SCLK2_DIV_MASK         0xf
  21#define TCON_CH1_SCLK2_DIV_SHIFT        0
  22
  23#define TCON_CH1_SCLK1_GATE_BIT         BIT(15)
  24#define TCON_CH1_SCLK1_HALF_BIT         BIT(11)
  25
  26struct tcon_ch1_clk {
  27        struct clk_hw   hw;
  28        spinlock_t      lock;
  29        void __iomem    *reg;
  30};
  31
  32#define hw_to_tclk(hw)  container_of(hw, struct tcon_ch1_clk, hw)
  33
  34static void tcon_ch1_disable(struct clk_hw *hw)
  35{
  36        struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
  37        unsigned long flags;
  38        u32 reg;
  39
  40        spin_lock_irqsave(&tclk->lock, flags);
  41        reg = readl(tclk->reg);
  42        reg &= ~(TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT);
  43        writel(reg, tclk->reg);
  44        spin_unlock_irqrestore(&tclk->lock, flags);
  45}
  46
  47static int tcon_ch1_enable(struct clk_hw *hw)
  48{
  49        struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
  50        unsigned long flags;
  51        u32 reg;
  52
  53        spin_lock_irqsave(&tclk->lock, flags);
  54        reg = readl(tclk->reg);
  55        reg |= TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT;
  56        writel(reg, tclk->reg);
  57        spin_unlock_irqrestore(&tclk->lock, flags);
  58
  59        return 0;
  60}
  61
  62static int tcon_ch1_is_enabled(struct clk_hw *hw)
  63{
  64        struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
  65        u32 reg;
  66
  67        reg = readl(tclk->reg);
  68        return reg & (TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT);
  69}
  70
  71static u8 tcon_ch1_get_parent(struct clk_hw *hw)
  72{
  73        struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
  74        u32 reg;
  75
  76        reg = readl(tclk->reg) >> TCON_CH1_SCLK2_MUX_SHIFT;
  77        reg &= reg >> TCON_CH1_SCLK2_MUX_MASK;
  78
  79        return reg;
  80}
  81
  82static int tcon_ch1_set_parent(struct clk_hw *hw, u8 index)
  83{
  84        struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
  85        unsigned long flags;
  86        u32 reg;
  87
  88        spin_lock_irqsave(&tclk->lock, flags);
  89        reg = readl(tclk->reg);
  90        reg &= ~(TCON_CH1_SCLK2_MUX_MASK << TCON_CH1_SCLK2_MUX_SHIFT);
  91        reg |= index << TCON_CH1_SCLK2_MUX_SHIFT;
  92        writel(reg, tclk->reg);
  93        spin_unlock_irqrestore(&tclk->lock, flags);
  94
  95        return 0;
  96};
  97
  98static unsigned long tcon_ch1_calc_divider(unsigned long rate,
  99                                           unsigned long parent_rate,
 100                                           u8 *div,
 101                                           bool *half)
 102{
 103        unsigned long best_rate = 0;
 104        u8 best_m = 0, m;
 105        bool is_double;
 106
 107        for (m = 1; m < 16; m++) {
 108                u8 d;
 109
 110                for (d = 1; d < 3; d++) {
 111                        unsigned long tmp_rate;
 112
 113                        tmp_rate = parent_rate / m / d;
 114
 115                        if (tmp_rate > rate)
 116                                continue;
 117
 118                        if (!best_rate ||
 119                            (rate - tmp_rate) < (rate - best_rate)) {
 120                                best_rate = tmp_rate;
 121                                best_m = m;
 122                                is_double = d;
 123                        }
 124                }
 125        }
 126
 127        if (div && half) {
 128                *div = best_m;
 129                *half = is_double;
 130        }
 131
 132        return best_rate;
 133}
 134
 135static int tcon_ch1_determine_rate(struct clk_hw *hw,
 136                                   struct clk_rate_request *req)
 137{
 138        long best_rate = -EINVAL;
 139        int i;
 140
 141        for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
 142                unsigned long parent_rate;
 143                unsigned long tmp_rate;
 144                struct clk_hw *parent;
 145
 146                parent = clk_hw_get_parent_by_index(hw, i);
 147                if (!parent)
 148                        continue;
 149
 150                parent_rate = clk_hw_get_rate(parent);
 151
 152                tmp_rate = tcon_ch1_calc_divider(req->rate, parent_rate,
 153                                                 NULL, NULL);
 154
 155                if (best_rate < 0 ||
 156                    (req->rate - tmp_rate) < (req->rate - best_rate)) {
 157                        best_rate = tmp_rate;
 158                        req->best_parent_rate = parent_rate;
 159                        req->best_parent_hw = parent;
 160                }
 161        }
 162
 163        if (best_rate < 0)
 164                return best_rate;
 165
 166        req->rate = best_rate;
 167        return 0;
 168}
 169
 170static unsigned long tcon_ch1_recalc_rate(struct clk_hw *hw,
 171                                          unsigned long parent_rate)
 172{
 173        struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
 174        u32 reg;
 175
 176        reg = readl(tclk->reg);
 177
 178        parent_rate /= (reg & TCON_CH1_SCLK2_DIV_MASK) + 1;
 179
 180        if (reg & TCON_CH1_SCLK1_HALF_BIT)
 181                parent_rate /= 2;
 182
 183        return parent_rate;
 184}
 185
 186static int tcon_ch1_set_rate(struct clk_hw *hw, unsigned long rate,
 187                             unsigned long parent_rate)
 188{
 189        struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
 190        unsigned long flags;
 191        bool half;
 192        u8 div_m;
 193        u32 reg;
 194
 195        tcon_ch1_calc_divider(rate, parent_rate, &div_m, &half);
 196
 197        spin_lock_irqsave(&tclk->lock, flags);
 198        reg = readl(tclk->reg);
 199        reg &= ~(TCON_CH1_SCLK2_DIV_MASK | TCON_CH1_SCLK1_HALF_BIT);
 200        reg |= (div_m - 1) & TCON_CH1_SCLK2_DIV_MASK;
 201
 202        if (half)
 203                reg |= TCON_CH1_SCLK1_HALF_BIT;
 204
 205        writel(reg, tclk->reg);
 206        spin_unlock_irqrestore(&tclk->lock, flags);
 207
 208        return 0;
 209}
 210
 211static const struct clk_ops tcon_ch1_ops = {
 212        .disable        = tcon_ch1_disable,
 213        .enable         = tcon_ch1_enable,
 214        .is_enabled     = tcon_ch1_is_enabled,
 215
 216        .get_parent     = tcon_ch1_get_parent,
 217        .set_parent     = tcon_ch1_set_parent,
 218
 219        .determine_rate = tcon_ch1_determine_rate,
 220        .recalc_rate    = tcon_ch1_recalc_rate,
 221        .set_rate       = tcon_ch1_set_rate,
 222};
 223
 224static void __init tcon_ch1_setup(struct device_node *node)
 225{
 226        const char *parents[TCON_CH1_SCLK2_PARENTS];
 227        const char *clk_name = node->name;
 228        struct clk_init_data init;
 229        struct tcon_ch1_clk *tclk;
 230        struct resource res;
 231        struct clk *clk;
 232        void __iomem *reg;
 233        int ret;
 234
 235        of_property_read_string(node, "clock-output-names", &clk_name);
 236
 237        reg = of_io_request_and_map(node, 0, of_node_full_name(node));
 238        if (IS_ERR(reg)) {
 239                pr_err("%s: Could not map the clock registers\n", clk_name);
 240                return;
 241        }
 242
 243        ret = of_clk_parent_fill(node, parents, TCON_CH1_SCLK2_PARENTS);
 244        if (ret != TCON_CH1_SCLK2_PARENTS) {
 245                pr_err("%s Could not retrieve the parents\n", clk_name);
 246                goto err_unmap;
 247        }
 248
 249        tclk = kzalloc(sizeof(*tclk), GFP_KERNEL);
 250        if (!tclk)
 251                goto err_unmap;
 252
 253        init.name = clk_name;
 254        init.ops = &tcon_ch1_ops;
 255        init.parent_names = parents;
 256        init.num_parents = TCON_CH1_SCLK2_PARENTS;
 257        init.flags = CLK_SET_RATE_PARENT;
 258
 259        tclk->reg = reg;
 260        tclk->hw.init = &init;
 261        spin_lock_init(&tclk->lock);
 262
 263        clk = clk_register(NULL, &tclk->hw);
 264        if (IS_ERR(clk)) {
 265                pr_err("%s: Couldn't register the clock\n", clk_name);
 266                goto err_free_data;
 267        }
 268
 269        ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
 270        if (ret) {
 271                pr_err("%s: Couldn't register our clock provider\n", clk_name);
 272                goto err_unregister_clk;
 273        }
 274
 275        return;
 276
 277err_unregister_clk:
 278        clk_unregister(clk);
 279err_free_data:
 280        kfree(tclk);
 281err_unmap:
 282        iounmap(reg);
 283        of_address_to_resource(node, 0, &res);
 284        release_mem_region(res.start, resource_size(&res));
 285}
 286
 287CLK_OF_DECLARE(tcon_ch1, "allwinner,sun4i-a10-tcon-ch1-clk",
 288               tcon_ch1_setup);
 289