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 */ 32 struct 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 45 static 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 58 static int hi6220_clkdiv_determine_rate(struct clk_hw *hw, 59 struct clk_rate_request *req) 60 { 61 struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw); 62 63 req->rate = divider_round_rate(hw, req->rate, &req->best_parent_rate, dclk->table, 64 dclk->width, CLK_DIVIDER_ROUND_CLOSEST); 65 66 return 0; 67 } 68 69 static int hi6220_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate, 70 unsigned long parent_rate) 71 { 72 int value; 73 unsigned long flags = 0; 74 u32 data; 75 struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw); 76 77 value = divider_get_val(rate, parent_rate, dclk->table, 78 dclk->width, CLK_DIVIDER_ROUND_CLOSEST); 79 80 if (dclk->lock) 81 spin_lock_irqsave(dclk->lock, flags); 82 83 data = readl_relaxed(dclk->reg); 84 data &= ~(div_mask(dclk->width) << dclk->shift); 85 data |= value << dclk->shift; 86 data |= dclk->mask; 87 88 writel_relaxed(data, dclk->reg); 89 90 if (dclk->lock) 91 spin_unlock_irqrestore(dclk->lock, flags); 92 93 return 0; 94 } 95 96 static const struct clk_ops hi6220_clkdiv_ops = { 97 .recalc_rate = hi6220_clkdiv_recalc_rate, 98 .determine_rate = hi6220_clkdiv_determine_rate, 99 .set_rate = hi6220_clkdiv_set_rate, 100 }; 101 102 struct clk *hi6220_register_clkdiv(struct device *dev, const char *name, 103 const char *parent_name, unsigned long flags, void __iomem *reg, 104 u8 shift, u8 width, u32 mask_bit, spinlock_t *lock) 105 { 106 struct hi6220_clk_divider *div; 107 struct clk *clk; 108 struct clk_init_data init; 109 struct clk_div_table *table; 110 u32 max_div, min_div; 111 int i; 112 113 /* allocate the divider */ 114 div = kzalloc(sizeof(*div), GFP_KERNEL); 115 if (!div) 116 return ERR_PTR(-ENOMEM); 117 118 /* Init the divider table */ 119 max_div = div_mask(width) + 1; 120 min_div = 1; 121 122 table = kcalloc(max_div + 1, sizeof(*table), GFP_KERNEL); 123 if (!table) { 124 kfree(div); 125 return ERR_PTR(-ENOMEM); 126 } 127 128 for (i = 0; i < max_div; i++) { 129 table[i].div = min_div + i; 130 table[i].val = table[i].div - 1; 131 } 132 133 init.name = name; 134 init.ops = &hi6220_clkdiv_ops; 135 init.flags = flags; 136 init.parent_names = parent_name ? &parent_name : NULL; 137 init.num_parents = parent_name ? 1 : 0; 138 139 /* struct hi6220_clk_divider assignments */ 140 div->reg = reg; 141 div->shift = shift; 142 div->width = width; 143 div->mask = mask_bit ? BIT(mask_bit) : 0; 144 div->lock = lock; 145 div->hw.init = &init; 146 div->table = table; 147 148 /* register the clock */ 149 clk = clk_register(dev, &div->hw); 150 if (IS_ERR(clk)) { 151 kfree(table); 152 kfree(div); 153 } 154 155 return clk; 156 } 157