| |
| |
| |
| |
| |
| |
| |
| |
| #include <linux/clk-provider.h> |
| #include <linux/regmap.h> |
| |
| #include "sun4i_tcon.h" |
| #include "sun4i_dotclock.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); |
| |