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#define div_mask(width) ((1 << (width)) - 1) 22 23/** 24 * struct hi6220_clk_divider - divider clock for hi6220 25 * 26 * @hw: handle between common and hardware-specific interfaces 27 * @reg: register containing divider 28 * @shift: shift to the divider bit field 29 * @width: width of the divider bit field 30 * @mask: mask for setting divider rate 31 * @table: the div table that the divider supports 32 * @lock: register lock 33 */ 34struct hi6220_clk_divider { 35 struct clk_hw hw; 36 void __iomem *reg; 37 u8 shift; 38 u8 width; 39 u32 mask; 40 const struct clk_div_table *table; 41 spinlock_t *lock; 42}; 43 44#define to_hi6220_clk_divider(_hw) \ 45 container_of(_hw, struct hi6220_clk_divider, hw) 46 47static unsigned long hi6220_clkdiv_recalc_rate(struct clk_hw *hw, 48 unsigned long parent_rate) 49{ 50 unsigned int val; 51 struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw); 52 53 val = readl_relaxed(dclk->reg) >> dclk->shift; 54 val &= div_mask(dclk->width); 55 56 return divider_recalc_rate(hw, parent_rate, val, dclk->table, 57 CLK_DIVIDER_ROUND_CLOSEST); 58} 59 60static long hi6220_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate, 61 unsigned long *prate) 62{ 63 struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw); 64 65 return divider_round_rate(hw, rate, prate, dclk->table, 66 dclk->width, CLK_DIVIDER_ROUND_CLOSEST); 67} 68 69static 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 96static const struct clk_ops hi6220_clkdiv_ops = { 97 .recalc_rate = hi6220_clkdiv_recalc_rate, 98 .round_rate = hi6220_clkdiv_round_rate, 99 .set_rate = hi6220_clkdiv_set_rate, 100}; 101 102struct 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