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