207 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			207 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| /*
 | |
|  * Copyright (C) 2016 Free Electrons
 | |
|  * Copyright (C) 2016 NextThing Co
 | |
|  *
 | |
|  * Maxime Ripard <maxime.ripard@free-electrons.com>
 | |
|  */
 | |
| 
 | |
| #include <linux/clk-provider.h>
 | |
| #include <linux/regmap.h>
 | |
| 
 | |
| #include "sun4i_tcon.h"
 | |
| #include "sun4i_tcon_dclk.h"
 | |
| 
 | |
| struct sun4i_dclk {
 | |
| 	struct clk_hw		hw;
 | |
| 	struct regmap		*regmap;
 | |
| 	struct sun4i_tcon	*tcon;
 | |
| };
 | |
| 
 | |
| static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw)
 | |
| {
 | |
| 	return container_of(hw, struct sun4i_dclk, hw);
 | |
| }
 | |
| 
 | |
| static void sun4i_dclk_disable(struct clk_hw *hw)
 | |
| {
 | |
| 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 | |
| 
 | |
| 	regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
 | |
| 			   BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0);
 | |
| }
 | |
| 
 | |
| static int sun4i_dclk_enable(struct clk_hw *hw)
 | |
| {
 | |
| 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 | |
| 
 | |
| 	return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
 | |
| 				  BIT(SUN4I_TCON0_DCLK_GATE_BIT),
 | |
| 				  BIT(SUN4I_TCON0_DCLK_GATE_BIT));
 | |
| }
 | |
| 
 | |
| static int sun4i_dclk_is_enabled(struct clk_hw *hw)
 | |
| {
 | |
| 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 | |
| 	u32 val;
 | |
| 
 | |
| 	regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
 | |
| 
 | |
| 	return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT);
 | |
| }
 | |
| 
 | |
| static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw,
 | |
| 					    unsigned long parent_rate)
 | |
| {
 | |
| 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 | |
| 	u32 val;
 | |
| 
 | |
| 	regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
 | |
| 
 | |
| 	val >>= SUN4I_TCON0_DCLK_DIV_SHIFT;
 | |
| 	val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1;
 | |
| 
 | |
| 	if (!val)
 | |
| 		val = 1;
 | |
| 
 | |
| 	return parent_rate / val;
 | |
| }
 | |
| 
 | |
| static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
 | |
| 				  unsigned long *parent_rate)
 | |
| {
 | |
| 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 | |
| 	struct sun4i_tcon *tcon = dclk->tcon;
 | |
| 	unsigned long best_parent = 0;
 | |
| 	u8 best_div = 1;
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) {
 | |
| 		u64 ideal = (u64)rate * i;
 | |
| 		unsigned long rounded;
 | |
| 
 | |
| 		/*
 | |
| 		 * ideal has overflowed the max value that can be stored in an
 | |
| 		 * unsigned long, and every clk operation we might do on a
 | |
| 		 * truncated u64 value will give us incorrect results.
 | |
| 		 * Let's just stop there since bigger dividers will result in
 | |
| 		 * the same overflow issue.
 | |
| 		 */
 | |
| 		if (ideal > ULONG_MAX)
 | |
| 			goto out;
 | |
| 
 | |
| 		rounded = clk_hw_round_rate(clk_hw_get_parent(hw),
 | |
| 					    ideal);
 | |
| 
 | |
| 		if (rounded == ideal) {
 | |
| 			best_parent = rounded;
 | |
| 			best_div = i;
 | |
| 			goto out;
 | |
| 		}
 | |
| 
 | |
| 		if (abs(rate - rounded / i) <
 | |
| 		    abs(rate - best_parent / best_div)) {
 | |
| 			best_parent = rounded;
 | |
| 			best_div = i;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| out:
 | |
| 	*parent_rate = best_parent;
 | |
| 
 | |
| 	return best_parent / best_div;
 | |
| }
 | |
| 
 | |
| static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate,
 | |
| 			       unsigned long parent_rate)
 | |
| {
 | |
| 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 | |
| 	u8 div = parent_rate / rate;
 | |
| 
 | |
| 	return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
 | |
| 				  GENMASK(6, 0), div);
 | |
| }
 | |
| 
 | |
| static int sun4i_dclk_get_phase(struct clk_hw *hw)
 | |
| {
 | |
| 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 | |
| 	u32 val;
 | |
| 
 | |
| 	regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val);
 | |
| 
 | |
| 	val >>= 28;
 | |
| 	val &= 3;
 | |
| 
 | |
| 	return val * 120;
 | |
| }
 | |
| 
 | |
| static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees)
 | |
| {
 | |
| 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 | |
| 	u32 val = degrees / 120;
 | |
| 
 | |
| 	val <<= 28;
 | |
| 
 | |
| 	regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG,
 | |
| 			   GENMASK(29, 28),
 | |
| 			   val);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct clk_ops sun4i_dclk_ops = {
 | |
| 	.disable	= sun4i_dclk_disable,
 | |
| 	.enable		= sun4i_dclk_enable,
 | |
| 	.is_enabled	= sun4i_dclk_is_enabled,
 | |
| 
 | |
| 	.recalc_rate	= sun4i_dclk_recalc_rate,
 | |
| 	.round_rate	= sun4i_dclk_round_rate,
 | |
| 	.set_rate	= sun4i_dclk_set_rate,
 | |
| 
 | |
| 	.get_phase	= sun4i_dclk_get_phase,
 | |
| 	.set_phase	= sun4i_dclk_set_phase,
 | |
| };
 | |
| 
 | |
| int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon)
 | |
| {
 | |
| 	const char *clk_name, *parent_name;
 | |
| 	struct clk_init_data init;
 | |
| 	struct sun4i_dclk *dclk;
 | |
| 	int ret;
 | |
| 
 | |
| 	parent_name = __clk_get_name(tcon->sclk0);
 | |
| 	ret = of_property_read_string_index(dev->of_node,
 | |
| 					    "clock-output-names", 0,
 | |
| 					    &clk_name);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
 | |
| 	if (!dclk)
 | |
| 		return -ENOMEM;
 | |
| 	dclk->tcon = tcon;
 | |
| 
 | |
| 	init.name = clk_name;
 | |
| 	init.ops = &sun4i_dclk_ops;
 | |
| 	init.parent_names = &parent_name;
 | |
| 	init.num_parents = 1;
 | |
| 	init.flags = CLK_SET_RATE_PARENT;
 | |
| 
 | |
| 	dclk->regmap = tcon->regs;
 | |
| 	dclk->hw.init = &init;
 | |
| 
 | |
| 	tcon->dclk = clk_register(dev, &dclk->hw);
 | |
| 	if (IS_ERR(tcon->dclk))
 | |
| 		return PTR_ERR(tcon->dclk);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL(sun4i_dclk_create);
 | |
| 
 | |
| int sun4i_dclk_free(struct sun4i_tcon *tcon)
 | |
| {
 | |
| 	clk_unregister(tcon->dclk);
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL(sun4i_dclk_free);
 |