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>
23 #define div_mask(width) ((1 << (width)) - 1)
26 * struct hi6220_clk_divider - divider clock for hi6220
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
36 struct hi6220_clk_divider
{
42 const struct clk_div_table
*table
;
46 #define to_hi6220_clk_divider(_hw) \
47 container_of(_hw, struct hi6220_clk_divider, hw)
49 static unsigned long hi6220_clkdiv_recalc_rate(struct clk_hw
*hw
,
50 unsigned long parent_rate
)
53 struct hi6220_clk_divider
*dclk
= to_hi6220_clk_divider(hw
);
55 val
= readl_relaxed(dclk
->reg
) >> dclk
->shift
;
56 val
&= div_mask(dclk
->width
);
58 return divider_recalc_rate(hw
, parent_rate
, val
, dclk
->table
,
59 CLK_DIVIDER_ROUND_CLOSEST
);
62 static long hi6220_clkdiv_round_rate(struct clk_hw
*hw
, unsigned long rate
,
65 struct hi6220_clk_divider
*dclk
= to_hi6220_clk_divider(hw
);
67 return divider_round_rate(hw
, rate
, prate
, dclk
->table
,
68 dclk
->width
, CLK_DIVIDER_ROUND_CLOSEST
);
71 static int hi6220_clkdiv_set_rate(struct clk_hw
*hw
, unsigned long rate
,
72 unsigned long parent_rate
)
75 unsigned long flags
= 0;
77 struct hi6220_clk_divider
*dclk
= to_hi6220_clk_divider(hw
);
79 value
= divider_get_val(rate
, parent_rate
, dclk
->table
,
80 dclk
->width
, CLK_DIVIDER_ROUND_CLOSEST
);
83 spin_lock_irqsave(dclk
->lock
, flags
);
85 data
= readl_relaxed(dclk
->reg
);
86 data
&= ~(div_mask(dclk
->width
) << dclk
->shift
);
87 data
|= value
<< dclk
->shift
;
90 writel_relaxed(data
, dclk
->reg
);
93 spin_unlock_irqrestore(dclk
->lock
, flags
);
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
,
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
)
108 struct hi6220_clk_divider
*div
;
110 struct clk_init_data init
;
111 struct clk_div_table
*table
;
112 u32 max_div
, min_div
;
115 /* allocate the divider */
116 div
= kzalloc(sizeof(*div
), GFP_KERNEL
);
118 return ERR_PTR(-ENOMEM
);
120 /* Init the divider table */
121 max_div
= div_mask(width
) + 1;
124 table
= kcalloc(max_div
+ 1, sizeof(*table
), GFP_KERNEL
);
127 return ERR_PTR(-ENOMEM
);
130 for (i
= 0; i
< max_div
; i
++) {
131 table
[i
].div
= min_div
+ i
;
132 table
[i
].val
= table
[i
].div
- 1;
136 init
.ops
= &hi6220_clkdiv_ops
;
138 init
.parent_names
= parent_name
? &parent_name
: NULL
;
139 init
.num_parents
= parent_name
? 1 : 0;
141 /* struct hi6220_clk_divider assignments */
145 div
->mask
= mask_bit
? BIT(mask_bit
) : 0;
147 div
->hw
.init
= &init
;
150 /* register the clock */
151 clk
= clk_register(dev
, &div
->hw
);