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 */ 36 struct 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 49 static 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 62 static 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 71 static 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 98 static 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 104 struct 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