xref: /openbmc/linux/drivers/clk/nuvoton/clk-ma35d1-divider.c (revision 2612e3bbc0386368a850140a6c9b990cd496a5ec)
1691521a3SJacky Huang // SPDX-License-Identifier: GPL-2.0-only
2691521a3SJacky Huang /*
3691521a3SJacky Huang  * Copyright (C) 2023 Nuvoton Technology Corp.
4691521a3SJacky Huang  * Author: Chi-Fang Li <cfli0@nuvoton.com>
5691521a3SJacky Huang  */
6691521a3SJacky Huang 
7691521a3SJacky Huang #include <linux/clk-provider.h>
8691521a3SJacky Huang #include <linux/device.h>
9691521a3SJacky Huang #include <linux/regmap.h>
10691521a3SJacky Huang #include <linux/spinlock.h>
11691521a3SJacky Huang 
12*a5e3f372SJacky Huang #include "clk-ma35d1.h"
13*a5e3f372SJacky Huang 
14691521a3SJacky Huang struct ma35d1_adc_clk_div {
15691521a3SJacky Huang 	struct clk_hw hw;
16691521a3SJacky Huang 	void __iomem *reg;
17691521a3SJacky Huang 	u8 shift;
18691521a3SJacky Huang 	u8 width;
19691521a3SJacky Huang 	u32 mask;
20691521a3SJacky Huang 	const struct clk_div_table *table;
21691521a3SJacky Huang 	/* protects concurrent access to clock divider registers */
22691521a3SJacky Huang 	spinlock_t *lock;
23691521a3SJacky Huang };
24691521a3SJacky Huang 
to_ma35d1_adc_clk_div(struct clk_hw * _hw)25691521a3SJacky Huang static inline struct ma35d1_adc_clk_div *to_ma35d1_adc_clk_div(struct clk_hw *_hw)
26691521a3SJacky Huang {
27691521a3SJacky Huang 	return container_of(_hw, struct ma35d1_adc_clk_div, hw);
28691521a3SJacky Huang }
29691521a3SJacky Huang 
ma35d1_clkdiv_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)30691521a3SJacky Huang static unsigned long ma35d1_clkdiv_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
31691521a3SJacky Huang {
32691521a3SJacky Huang 	unsigned int val;
33691521a3SJacky Huang 	struct ma35d1_adc_clk_div *dclk = to_ma35d1_adc_clk_div(hw);
34691521a3SJacky Huang 
35691521a3SJacky Huang 	val = readl_relaxed(dclk->reg) >> dclk->shift;
36691521a3SJacky Huang 	val &= clk_div_mask(dclk->width);
37691521a3SJacky Huang 	val += 1;
38691521a3SJacky Huang 	return divider_recalc_rate(hw, parent_rate, val, dclk->table,
39691521a3SJacky Huang 				   CLK_DIVIDER_ROUND_CLOSEST, dclk->width);
40691521a3SJacky Huang }
41691521a3SJacky Huang 
ma35d1_clkdiv_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)42691521a3SJacky Huang static long ma35d1_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate)
43691521a3SJacky Huang {
44691521a3SJacky Huang 	struct ma35d1_adc_clk_div *dclk = to_ma35d1_adc_clk_div(hw);
45691521a3SJacky Huang 
46691521a3SJacky Huang 	return divider_round_rate(hw, rate, prate, dclk->table,
47691521a3SJacky Huang 				  dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
48691521a3SJacky Huang }
49691521a3SJacky Huang 
ma35d1_clkdiv_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)50691521a3SJacky Huang static int ma35d1_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
51691521a3SJacky Huang {
52691521a3SJacky Huang 	int value;
53691521a3SJacky Huang 	unsigned long flags = 0;
54691521a3SJacky Huang 	u32 data;
55691521a3SJacky Huang 	struct ma35d1_adc_clk_div *dclk = to_ma35d1_adc_clk_div(hw);
56691521a3SJacky Huang 
57691521a3SJacky Huang 	value = divider_get_val(rate, parent_rate, dclk->table,
58691521a3SJacky Huang 				dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
59691521a3SJacky Huang 
60691521a3SJacky Huang 	spin_lock_irqsave(dclk->lock, flags);
61691521a3SJacky Huang 
62691521a3SJacky Huang 	data = readl_relaxed(dclk->reg);
63691521a3SJacky Huang 	data &= ~(clk_div_mask(dclk->width) << dclk->shift);
64691521a3SJacky Huang 	data |= (value - 1) << dclk->shift;
65691521a3SJacky Huang 	data |= dclk->mask;
66691521a3SJacky Huang 	writel_relaxed(data, dclk->reg);
67691521a3SJacky Huang 
68691521a3SJacky Huang 	spin_unlock_irqrestore(dclk->lock, flags);
69691521a3SJacky Huang 	return 0;
70691521a3SJacky Huang }
71691521a3SJacky Huang 
72691521a3SJacky Huang static const struct clk_ops ma35d1_adc_clkdiv_ops = {
73691521a3SJacky Huang 	.recalc_rate = ma35d1_clkdiv_recalc_rate,
74691521a3SJacky Huang 	.round_rate = ma35d1_clkdiv_round_rate,
75691521a3SJacky Huang 	.set_rate = ma35d1_clkdiv_set_rate,
76691521a3SJacky Huang };
77691521a3SJacky Huang 
ma35d1_reg_adc_clkdiv(struct device * dev,const char * name,struct clk_hw * parent_hw,spinlock_t * lock,unsigned long flags,void __iomem * reg,u8 shift,u8 width,u32 mask_bit)78691521a3SJacky Huang struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev, const char *name,
79691521a3SJacky Huang 				     struct clk_hw *parent_hw, spinlock_t *lock,
80691521a3SJacky Huang 				     unsigned long flags, void __iomem *reg,
81691521a3SJacky Huang 				     u8 shift, u8 width, u32 mask_bit)
82691521a3SJacky Huang {
83691521a3SJacky Huang 	struct ma35d1_adc_clk_div *div;
84691521a3SJacky Huang 	struct clk_init_data init;
85691521a3SJacky Huang 	struct clk_div_table *table;
86691521a3SJacky Huang 	struct clk_parent_data pdata = { .index = 0 };
87691521a3SJacky Huang 	u32 max_div, min_div;
88691521a3SJacky Huang 	struct clk_hw *hw;
89691521a3SJacky Huang 	int ret;
90691521a3SJacky Huang 	int i;
91691521a3SJacky Huang 
92691521a3SJacky Huang 	div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
93691521a3SJacky Huang 	if (!div)
94691521a3SJacky Huang 		return ERR_PTR(-ENOMEM);
95691521a3SJacky Huang 
96691521a3SJacky Huang 	max_div = clk_div_mask(width) + 1;
97691521a3SJacky Huang 	min_div = 1;
98691521a3SJacky Huang 
99691521a3SJacky Huang 	table = devm_kcalloc(dev, max_div + 1, sizeof(*table), GFP_KERNEL);
100691521a3SJacky Huang 	if (!table)
101691521a3SJacky Huang 		return ERR_PTR(-ENOMEM);
102691521a3SJacky Huang 
103691521a3SJacky Huang 	for (i = 0; i < max_div; i++) {
104691521a3SJacky Huang 		table[i].val = min_div + i;
105691521a3SJacky Huang 		table[i].div = 2 * table[i].val;
106691521a3SJacky Huang 	}
107691521a3SJacky Huang 	table[max_div].val = 0;
108691521a3SJacky Huang 	table[max_div].div = 0;
109691521a3SJacky Huang 
110691521a3SJacky Huang 	memset(&init, 0, sizeof(init));
111691521a3SJacky Huang 	init.name = name;
112691521a3SJacky Huang 	init.ops = &ma35d1_adc_clkdiv_ops;
113691521a3SJacky Huang 	init.flags |= flags;
114691521a3SJacky Huang 	pdata.hw = parent_hw;
115691521a3SJacky Huang 	init.parent_data = &pdata;
116691521a3SJacky Huang 	init.num_parents = 1;
117691521a3SJacky Huang 
118691521a3SJacky Huang 	div->reg = reg;
119691521a3SJacky Huang 	div->shift = shift;
120691521a3SJacky Huang 	div->width = width;
121691521a3SJacky Huang 	div->mask = mask_bit ? BIT(mask_bit) : 0;
122691521a3SJacky Huang 	div->lock = lock;
123691521a3SJacky Huang 	div->hw.init = &init;
124691521a3SJacky Huang 	div->table = table;
125691521a3SJacky Huang 
126691521a3SJacky Huang 	hw = &div->hw;
127691521a3SJacky Huang 	ret = devm_clk_hw_register(dev, hw);
128691521a3SJacky Huang 	if (ret)
129691521a3SJacky Huang 		return ERR_PTR(ret);
130691521a3SJacky Huang 	return hw;
131691521a3SJacky Huang }
132691521a3SJacky Huang EXPORT_SYMBOL_GPL(ma35d1_reg_adc_clkdiv);
133