xref: /linux/drivers/clk/mxs/clk-frac.c (revision 75bf465f0bc33e9b776a46d6a1b9b990f5fb7c37)
1*fcaf2036SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
223b5e15aSShawn Guo /*
323b5e15aSShawn Guo  * Copyright 2012 Freescale Semiconductor, Inc.
423b5e15aSShawn Guo  */
523b5e15aSShawn Guo 
623b5e15aSShawn Guo #include <linux/clk-provider.h>
723b5e15aSShawn Guo #include <linux/err.h>
823b5e15aSShawn Guo #include <linux/io.h>
923b5e15aSShawn Guo #include <linux/slab.h>
1023b5e15aSShawn Guo #include "clk.h"
1123b5e15aSShawn Guo 
1223b5e15aSShawn Guo /**
1323b5e15aSShawn Guo  * struct clk_frac - mxs fractional divider clock
1423b5e15aSShawn Guo  * @hw: clk_hw for the fractional divider clock
1523b5e15aSShawn Guo  * @reg: register address
1623b5e15aSShawn Guo  * @shift: the divider bit shift
1723b5e15aSShawn Guo  * @width: the divider bit width
1823b5e15aSShawn Guo  * @busy: busy bit shift
1923b5e15aSShawn Guo  *
2023b5e15aSShawn Guo  * The clock is an adjustable fractional divider with a busy bit to wait
2123b5e15aSShawn Guo  * when the divider is adjusted.
2223b5e15aSShawn Guo  */
2323b5e15aSShawn Guo struct clk_frac {
2423b5e15aSShawn Guo 	struct clk_hw hw;
2523b5e15aSShawn Guo 	void __iomem *reg;
2623b5e15aSShawn Guo 	u8 shift;
2723b5e15aSShawn Guo 	u8 width;
2823b5e15aSShawn Guo 	u8 busy;
2923b5e15aSShawn Guo };
3023b5e15aSShawn Guo 
3123b5e15aSShawn Guo #define to_clk_frac(_hw) container_of(_hw, struct clk_frac, hw)
3223b5e15aSShawn Guo 
clk_frac_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)3323b5e15aSShawn Guo static unsigned long clk_frac_recalc_rate(struct clk_hw *hw,
3423b5e15aSShawn Guo 					  unsigned long parent_rate)
3523b5e15aSShawn Guo {
3623b5e15aSShawn Guo 	struct clk_frac *frac = to_clk_frac(hw);
3723b5e15aSShawn Guo 	u32 div;
38d8757433SVictorien Vedrine 	u64 tmp_rate;
3923b5e15aSShawn Guo 
4023b5e15aSShawn Guo 	div = readl_relaxed(frac->reg) >> frac->shift;
4123b5e15aSShawn Guo 	div &= (1 << frac->width) - 1;
4223b5e15aSShawn Guo 
43d8757433SVictorien Vedrine 	tmp_rate = (u64)parent_rate * div;
44d8757433SVictorien Vedrine 	return tmp_rate >> frac->width;
4523b5e15aSShawn Guo }
4623b5e15aSShawn Guo 
clk_frac_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)4723b5e15aSShawn Guo static long clk_frac_round_rate(struct clk_hw *hw, unsigned long rate,
4823b5e15aSShawn Guo 				unsigned long *prate)
4923b5e15aSShawn Guo {
5023b5e15aSShawn Guo 	struct clk_frac *frac = to_clk_frac(hw);
5123b5e15aSShawn Guo 	unsigned long parent_rate = *prate;
5223b5e15aSShawn Guo 	u32 div;
53d8757433SVictorien Vedrine 	u64 tmp, tmp_rate, result;
5423b5e15aSShawn Guo 
5523b5e15aSShawn Guo 	if (rate > parent_rate)
5623b5e15aSShawn Guo 		return -EINVAL;
5723b5e15aSShawn Guo 
5823b5e15aSShawn Guo 	tmp = rate;
5923b5e15aSShawn Guo 	tmp <<= frac->width;
6023b5e15aSShawn Guo 	do_div(tmp, parent_rate);
6123b5e15aSShawn Guo 	div = tmp;
6223b5e15aSShawn Guo 
6323b5e15aSShawn Guo 	if (!div)
6423b5e15aSShawn Guo 		return -EINVAL;
6523b5e15aSShawn Guo 
66d8757433SVictorien Vedrine 	tmp_rate = (u64)parent_rate * div;
67d8757433SVictorien Vedrine 	result = tmp_rate >> frac->width;
68d8757433SVictorien Vedrine 	if ((result << frac->width) < tmp_rate)
69d8757433SVictorien Vedrine 		result += 1;
70d8757433SVictorien Vedrine 	return result;
7123b5e15aSShawn Guo }
7223b5e15aSShawn Guo 
clk_frac_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)7323b5e15aSShawn Guo static int clk_frac_set_rate(struct clk_hw *hw, unsigned long rate,
7423b5e15aSShawn Guo 			     unsigned long parent_rate)
7523b5e15aSShawn Guo {
7623b5e15aSShawn Guo 	struct clk_frac *frac = to_clk_frac(hw);
7723b5e15aSShawn Guo 	unsigned long flags;
7823b5e15aSShawn Guo 	u32 div, val;
7923b5e15aSShawn Guo 	u64 tmp;
8023b5e15aSShawn Guo 
8123b5e15aSShawn Guo 	if (rate > parent_rate)
8223b5e15aSShawn Guo 		return -EINVAL;
8323b5e15aSShawn Guo 
8423b5e15aSShawn Guo 	tmp = rate;
8523b5e15aSShawn Guo 	tmp <<= frac->width;
8623b5e15aSShawn Guo 	do_div(tmp, parent_rate);
8723b5e15aSShawn Guo 	div = tmp;
8823b5e15aSShawn Guo 
8923b5e15aSShawn Guo 	if (!div)
9023b5e15aSShawn Guo 		return -EINVAL;
9123b5e15aSShawn Guo 
9223b5e15aSShawn Guo 	spin_lock_irqsave(&mxs_lock, flags);
9323b5e15aSShawn Guo 
9423b5e15aSShawn Guo 	val = readl_relaxed(frac->reg);
9523b5e15aSShawn Guo 	val &= ~(((1 << frac->width) - 1) << frac->shift);
9623b5e15aSShawn Guo 	val |= div << frac->shift;
9723b5e15aSShawn Guo 	writel_relaxed(val, frac->reg);
9823b5e15aSShawn Guo 
9923b5e15aSShawn Guo 	spin_unlock_irqrestore(&mxs_lock, flags);
10023b5e15aSShawn Guo 
10123b5e15aSShawn Guo 	return mxs_clk_wait(frac->reg, frac->busy);
10223b5e15aSShawn Guo }
10323b5e15aSShawn Guo 
104ed3f2d12SBhumika Goyal static const struct clk_ops clk_frac_ops = {
10523b5e15aSShawn Guo 	.recalc_rate = clk_frac_recalc_rate,
10623b5e15aSShawn Guo 	.round_rate = clk_frac_round_rate,
10723b5e15aSShawn Guo 	.set_rate = clk_frac_set_rate,
10823b5e15aSShawn Guo };
10923b5e15aSShawn Guo 
mxs_clk_frac(const char * name,const char * parent_name,void __iomem * reg,u8 shift,u8 width,u8 busy)11023b5e15aSShawn Guo struct clk *mxs_clk_frac(const char *name, const char *parent_name,
11123b5e15aSShawn Guo 			 void __iomem *reg, u8 shift, u8 width, u8 busy)
11223b5e15aSShawn Guo {
11323b5e15aSShawn Guo 	struct clk_frac *frac;
11423b5e15aSShawn Guo 	struct clk *clk;
11523b5e15aSShawn Guo 	struct clk_init_data init;
11623b5e15aSShawn Guo 
11723b5e15aSShawn Guo 	frac = kzalloc(sizeof(*frac), GFP_KERNEL);
11823b5e15aSShawn Guo 	if (!frac)
11923b5e15aSShawn Guo 		return ERR_PTR(-ENOMEM);
12023b5e15aSShawn Guo 
12123b5e15aSShawn Guo 	init.name = name;
12223b5e15aSShawn Guo 	init.ops = &clk_frac_ops;
12323b5e15aSShawn Guo 	init.flags = CLK_SET_RATE_PARENT;
12423b5e15aSShawn Guo 	init.parent_names = (parent_name ? &parent_name: NULL);
12523b5e15aSShawn Guo 	init.num_parents = (parent_name ? 1 : 0);
12623b5e15aSShawn Guo 
12723b5e15aSShawn Guo 	frac->reg = reg;
12823b5e15aSShawn Guo 	frac->shift = shift;
12923b5e15aSShawn Guo 	frac->width = width;
13023b5e15aSShawn Guo 	frac->busy = busy;
13123b5e15aSShawn Guo 	frac->hw.init = &init;
13223b5e15aSShawn Guo 
13323b5e15aSShawn Guo 	clk = clk_register(NULL, &frac->hw);
13423b5e15aSShawn Guo 	if (IS_ERR(clk))
13523b5e15aSShawn Guo 		kfree(frac);
13623b5e15aSShawn Guo 
13723b5e15aSShawn Guo 	return clk;
13823b5e15aSShawn Guo }
139