Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Hisilicon hi6220 SoC divider clock driver
0004  *
0005  * Copyright (c) 2015 Hisilicon Limited.
0006  *
0007  * Author: Bintian Wang <bintian.wang@huawei.com>
0008  */
0009 
0010 #include <linux/kernel.h>
0011 #include <linux/clk-provider.h>
0012 #include <linux/slab.h>
0013 #include <linux/io.h>
0014 #include <linux/err.h>
0015 #include <linux/spinlock.h>
0016 
0017 #include "clk.h"
0018 
0019 #define div_mask(width) ((1 << (width)) - 1)
0020 
0021 /**
0022  * struct hi6220_clk_divider - divider clock for hi6220
0023  *
0024  * @hw:     handle between common and hardware-specific interfaces
0025  * @reg:    register containing divider
0026  * @shift:  shift to the divider bit field
0027  * @width:  width of the divider bit field
0028  * @mask:   mask for setting divider rate
0029  * @table:  the div table that the divider supports
0030  * @lock:   register lock
0031  */
0032 struct hi6220_clk_divider {
0033     struct clk_hw   hw;
0034     void __iomem    *reg;
0035     u8      shift;
0036     u8      width;
0037     u32     mask;
0038     const struct clk_div_table *table;
0039     spinlock_t  *lock;
0040 };
0041 
0042 #define to_hi6220_clk_divider(_hw)  \
0043     container_of(_hw, struct hi6220_clk_divider, hw)
0044 
0045 static unsigned long hi6220_clkdiv_recalc_rate(struct clk_hw *hw,
0046                     unsigned long parent_rate)
0047 {
0048     unsigned int val;
0049     struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw);
0050 
0051     val = readl_relaxed(dclk->reg) >> dclk->shift;
0052     val &= div_mask(dclk->width);
0053 
0054     return divider_recalc_rate(hw, parent_rate, val, dclk->table,
0055                    CLK_DIVIDER_ROUND_CLOSEST, dclk->width);
0056 }
0057 
0058 static long hi6220_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate,
0059                     unsigned long *prate)
0060 {
0061     struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw);
0062 
0063     return divider_round_rate(hw, rate, prate, dclk->table,
0064                   dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
0065 }
0066 
0067 static int hi6220_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate,
0068                     unsigned long parent_rate)
0069 {
0070     int value;
0071     unsigned long flags = 0;
0072     u32 data;
0073     struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw);
0074 
0075     value = divider_get_val(rate, parent_rate, dclk->table,
0076                 dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
0077 
0078     if (dclk->lock)
0079         spin_lock_irqsave(dclk->lock, flags);
0080 
0081     data = readl_relaxed(dclk->reg);
0082     data &= ~(div_mask(dclk->width) << dclk->shift);
0083     data |= value << dclk->shift;
0084     data |= dclk->mask;
0085 
0086     writel_relaxed(data, dclk->reg);
0087 
0088     if (dclk->lock)
0089         spin_unlock_irqrestore(dclk->lock, flags);
0090 
0091     return 0;
0092 }
0093 
0094 static const struct clk_ops hi6220_clkdiv_ops = {
0095     .recalc_rate = hi6220_clkdiv_recalc_rate,
0096     .round_rate = hi6220_clkdiv_round_rate,
0097     .set_rate = hi6220_clkdiv_set_rate,
0098 };
0099 
0100 struct clk *hi6220_register_clkdiv(struct device *dev, const char *name,
0101     const char *parent_name, unsigned long flags, void __iomem *reg,
0102     u8 shift, u8 width, u32 mask_bit, spinlock_t *lock)
0103 {
0104     struct hi6220_clk_divider *div;
0105     struct clk *clk;
0106     struct clk_init_data init;
0107     struct clk_div_table *table;
0108     u32 max_div, min_div;
0109     int i;
0110 
0111     /* allocate the divider */
0112     div = kzalloc(sizeof(*div), GFP_KERNEL);
0113     if (!div)
0114         return ERR_PTR(-ENOMEM);
0115 
0116     /* Init the divider table */
0117     max_div = div_mask(width) + 1;
0118     min_div = 1;
0119 
0120     table = kcalloc(max_div + 1, sizeof(*table), GFP_KERNEL);
0121     if (!table) {
0122         kfree(div);
0123         return ERR_PTR(-ENOMEM);
0124     }
0125 
0126     for (i = 0; i < max_div; i++) {
0127         table[i].div = min_div + i;
0128         table[i].val = table[i].div - 1;
0129     }
0130 
0131     init.name = name;
0132     init.ops = &hi6220_clkdiv_ops;
0133     init.flags = flags;
0134     init.parent_names = parent_name ? &parent_name : NULL;
0135     init.num_parents = parent_name ? 1 : 0;
0136 
0137     /* struct hi6220_clk_divider assignments */
0138     div->reg = reg;
0139     div->shift = shift;
0140     div->width = width;
0141     div->mask = mask_bit ? BIT(mask_bit) : 0;
0142     div->lock = lock;
0143     div->hw.init = &init;
0144     div->table = table;
0145 
0146     /* register the clock */
0147     clk = clk_register(dev, &div->hw);
0148     if (IS_ERR(clk)) {
0149         kfree(table);
0150         kfree(div);
0151     }
0152 
0153     return clk;
0154 }