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