linux/drivers/clk/hisilicon/clkdivider-hi6220.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Hisilicon hi6220 SoC divider clock driver
   4 *
   5 * Copyright (c) 2015 Hisilicon Limited.
   6 *
   7 * Author: Bintian Wang <bintian.wang@huawei.com>
   8 */
   9
  10#include <linux/kernel.h>
  11#include <linux/clk-provider.h>
  12#include <linux/slab.h>
  13#include <linux/io.h>
  14#include <linux/err.h>
  15#include <linux/spinlock.h>
  16
  17#include "clk.h"
  18
  19#define div_mask(width) ((1 << (width)) - 1)
  20
  21/**
  22 * struct hi6220_clk_divider - divider clock for hi6220
  23 *
  24 * @hw:         handle between common and hardware-specific interfaces
  25 * @reg:        register containing divider
  26 * @shift:      shift to the divider bit field
  27 * @width:      width of the divider bit field
  28 * @mask:       mask for setting divider rate
  29 * @table:      the div table that the divider supports
  30 * @lock:       register lock
  31 */
  32struct hi6220_clk_divider {
  33        struct clk_hw   hw;
  34        void __iomem    *reg;
  35        u8              shift;
  36        u8              width;
  37        u32             mask;
  38        const struct clk_div_table *table;
  39        spinlock_t      *lock;
  40};
  41
  42#define to_hi6220_clk_divider(_hw)      \
  43        container_of(_hw, struct hi6220_clk_divider, hw)
  44
  45static unsigned long hi6220_clkdiv_recalc_rate(struct clk_hw *hw,
  46                                        unsigned long parent_rate)
  47{
  48        unsigned int val;
  49        struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw);
  50
  51        val = readl_relaxed(dclk->reg) >> dclk->shift;
  52        val &= div_mask(dclk->width);
  53
  54        return divider_recalc_rate(hw, parent_rate, val, dclk->table,
  55                                   CLK_DIVIDER_ROUND_CLOSEST, dclk->width);
  56}
  57
  58static long hi6220_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate,
  59                                        unsigned long *prate)
  60{
  61        struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw);
  62
  63        return divider_round_rate(hw, rate, prate, dclk->table,
  64                                  dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
  65}
  66
  67static int hi6220_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate,
  68                                        unsigned long parent_rate)
  69{
  70        int value;
  71        unsigned long flags = 0;
  72        u32 data;
  73        struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw);
  74
  75        value = divider_get_val(rate, parent_rate, dclk->table,
  76                                dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
  77
  78        if (dclk->lock)
  79                spin_lock_irqsave(dclk->lock, flags);
  80
  81        data = readl_relaxed(dclk->reg);
  82        data &= ~(div_mask(dclk->width) << dclk->shift);
  83        data |= value << dclk->shift;
  84        data |= dclk->mask;
  85
  86        writel_relaxed(data, dclk->reg);
  87
  88        if (dclk->lock)
  89                spin_unlock_irqrestore(dclk->lock, flags);
  90
  91        return 0;
  92}
  93
  94static const struct clk_ops hi6220_clkdiv_ops = {
  95        .recalc_rate = hi6220_clkdiv_recalc_rate,
  96        .round_rate = hi6220_clkdiv_round_rate,
  97        .set_rate = hi6220_clkdiv_set_rate,
  98};
  99
 100struct clk *hi6220_register_clkdiv(struct device *dev, const char *name,
 101        const char *parent_name, unsigned long flags, void __iomem *reg,
 102        u8 shift, u8 width, u32 mask_bit, spinlock_t *lock)
 103{
 104        struct hi6220_clk_divider *div;
 105        struct clk *clk;
 106        struct clk_init_data init;
 107        struct clk_div_table *table;
 108        u32 max_div, min_div;
 109        int i;
 110
 111        /* allocate the divider */
 112        div = kzalloc(sizeof(*div), GFP_KERNEL);
 113        if (!div)
 114                return ERR_PTR(-ENOMEM);
 115
 116        /* Init the divider table */
 117        max_div = div_mask(width) + 1;
 118        min_div = 1;
 119
 120        table = kcalloc(max_div + 1, sizeof(*table), GFP_KERNEL);
 121        if (!table) {
 122                kfree(div);
 123                return ERR_PTR(-ENOMEM);
 124        }
 125
 126        for (i = 0; i < max_div; i++) {
 127                table[i].div = min_div + i;
 128                table[i].val = table[i].div - 1;
 129        }
 130
 131        init.name = name;
 132        init.ops = &hi6220_clkdiv_ops;
 133        init.flags = flags;
 134        init.parent_names = parent_name ? &parent_name : NULL;
 135        init.num_parents = parent_name ? 1 : 0;
 136
 137        /* struct hi6220_clk_divider assignments */
 138        div->reg = reg;
 139        div->shift = shift;
 140        div->width = width;
 141        div->mask = mask_bit ? BIT(mask_bit) : 0;
 142        div->lock = lock;
 143        div->hw.init = &init;
 144        div->table = table;
 145
 146        /* register the clock */
 147        clk = clk_register(dev, &div->hw);
 148        if (IS_ERR(clk)) {
 149                kfree(table);
 150                kfree(div);
 151        }
 152
 153        return clk;
 154}
 155