2 * Hisilicon hi6220 SoC divider clock driver
4 * Copyright (c) 2015 Hisilicon Limited.
6 * Author: Bintian Wang <bintian.wang@huawei.com>
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.
14 #include <linux/kernel.h>
15 #include <linux/clk-provider.h>
16 #include <linux/slab.h>
18 #include <linux/err.h>
19 #include <linux/spinlock.h>
21 #define div_mask(width) ((1 << (width)) - 1)
24 * struct hi6220_clk_divider - divider clock for hi6220
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
34 struct hi6220_clk_divider
{
40 const struct clk_div_table
*table
;
44 #define to_hi6220_clk_divider(_hw) \
45 container_of(_hw, struct hi6220_clk_divider, hw)
47 static unsigned long hi6220_clkdiv_recalc_rate(struct clk_hw
*hw
,
48 unsigned long parent_rate
)
51 struct hi6220_clk_divider
*dclk
= to_hi6220_clk_divider(hw
);
53 val
= readl_relaxed(dclk
->reg
) >> dclk
->shift
;
54 val
&= div_mask(dclk
->width
);
56 return divider_recalc_rate(hw
, parent_rate
, val
, dclk
->table
,
57 CLK_DIVIDER_ROUND_CLOSEST
);
60 static long hi6220_clkdiv_round_rate(struct clk_hw
*hw
, unsigned long rate
,
63 struct hi6220_clk_divider
*dclk
= to_hi6220_clk_divider(hw
);
65 return divider_round_rate(hw
, rate
, prate
, dclk
->table
,
66 dclk
->width
, CLK_DIVIDER_ROUND_CLOSEST
);
69 static int hi6220_clkdiv_set_rate(struct clk_hw
*hw
, unsigned long rate
,
70 unsigned long parent_rate
)
73 unsigned long flags
= 0;
75 struct hi6220_clk_divider
*dclk
= to_hi6220_clk_divider(hw
);
77 value
= divider_get_val(rate
, parent_rate
, dclk
->table
,
78 dclk
->width
, CLK_DIVIDER_ROUND_CLOSEST
);
81 spin_lock_irqsave(dclk
->lock
, flags
);
83 data
= readl_relaxed(dclk
->reg
);
84 data
&= ~(div_mask(dclk
->width
) << dclk
->shift
);
85 data
|= value
<< dclk
->shift
;
88 writel_relaxed(data
, dclk
->reg
);
91 spin_unlock_irqrestore(dclk
->lock
, flags
);
96 static 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
,
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
)
106 struct hi6220_clk_divider
*div
;
108 struct clk_init_data init
;
109 struct clk_div_table
*table
;
110 u32 max_div
, min_div
;
113 /* allocate the divider */
114 div
= kzalloc(sizeof(*div
), GFP_KERNEL
);
116 return ERR_PTR(-ENOMEM
);
118 /* Init the divider table */
119 max_div
= div_mask(width
) + 1;
122 table
= kcalloc(max_div
+ 1, sizeof(*table
), GFP_KERNEL
);
125 return ERR_PTR(-ENOMEM
);
128 for (i
= 0; i
< max_div
; i
++) {
129 table
[i
].div
= min_div
+ i
;
130 table
[i
].val
= table
[i
].div
- 1;
134 init
.ops
= &hi6220_clkdiv_ops
;
136 init
.parent_names
= parent_name
? &parent_name
: NULL
;
137 init
.num_parents
= parent_name
? 1 : 0;
139 /* struct hi6220_clk_divider assignments */
143 div
->mask
= mask_bit
? BIT(mask_bit
) : 0;
145 div
->hw
.init
= &init
;
148 /* register the clock */
149 clk
= clk_register(dev
, &div
->hw
);