Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4.18-rc4 200 lines 4.4 kB view raw
1/* 2 * Copyright (C) 2016 Free Electrons 3 * Copyright (C) 2016 NextThing Co 4 * 5 * Maxime Ripard <maxime.ripard@free-electrons.com> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License as 9 * published by the Free Software Foundation; either version 2 of 10 * the License, or (at your option) any later version. 11 */ 12 13#include <linux/clk-provider.h> 14#include <linux/regmap.h> 15 16#include "sun4i_tcon.h" 17#include "sun4i_dotclock.h" 18 19struct sun4i_dclk { 20 struct clk_hw hw; 21 struct regmap *regmap; 22 struct sun4i_tcon *tcon; 23}; 24 25static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw) 26{ 27 return container_of(hw, struct sun4i_dclk, hw); 28} 29 30static void sun4i_dclk_disable(struct clk_hw *hw) 31{ 32 struct sun4i_dclk *dclk = hw_to_dclk(hw); 33 34 regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, 35 BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0); 36} 37 38static int sun4i_dclk_enable(struct clk_hw *hw) 39{ 40 struct sun4i_dclk *dclk = hw_to_dclk(hw); 41 42 return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, 43 BIT(SUN4I_TCON0_DCLK_GATE_BIT), 44 BIT(SUN4I_TCON0_DCLK_GATE_BIT)); 45} 46 47static int sun4i_dclk_is_enabled(struct clk_hw *hw) 48{ 49 struct sun4i_dclk *dclk = hw_to_dclk(hw); 50 u32 val; 51 52 regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); 53 54 return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT); 55} 56 57static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw, 58 unsigned long parent_rate) 59{ 60 struct sun4i_dclk *dclk = hw_to_dclk(hw); 61 u32 val; 62 63 regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); 64 65 val >>= SUN4I_TCON0_DCLK_DIV_SHIFT; 66 val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1; 67 68 if (!val) 69 val = 1; 70 71 return parent_rate / val; 72} 73 74static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate, 75 unsigned long *parent_rate) 76{ 77 struct sun4i_dclk *dclk = hw_to_dclk(hw); 78 struct sun4i_tcon *tcon = dclk->tcon; 79 unsigned long best_parent = 0; 80 u8 best_div = 1; 81 int i; 82 83 for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) { 84 unsigned long ideal = rate * i; 85 unsigned long rounded; 86 87 rounded = clk_hw_round_rate(clk_hw_get_parent(hw), 88 ideal); 89 90 if (rounded == ideal) { 91 best_parent = rounded; 92 best_div = i; 93 goto out; 94 } 95 96 if (abs(rate - rounded / i) < 97 abs(rate - best_parent / best_div)) { 98 best_parent = rounded; 99 best_div = i; 100 } 101 } 102 103out: 104 *parent_rate = best_parent; 105 106 return best_parent / best_div; 107} 108 109static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate, 110 unsigned long parent_rate) 111{ 112 struct sun4i_dclk *dclk = hw_to_dclk(hw); 113 u8 div = parent_rate / rate; 114 115 return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, 116 GENMASK(6, 0), div); 117} 118 119static int sun4i_dclk_get_phase(struct clk_hw *hw) 120{ 121 struct sun4i_dclk *dclk = hw_to_dclk(hw); 122 u32 val; 123 124 regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val); 125 126 val >>= 28; 127 val &= 3; 128 129 return val * 120; 130} 131 132static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees) 133{ 134 struct sun4i_dclk *dclk = hw_to_dclk(hw); 135 u32 val = degrees / 120; 136 137 val <<= 28; 138 139 regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG, 140 GENMASK(29, 28), 141 val); 142 143 return 0; 144} 145 146static const struct clk_ops sun4i_dclk_ops = { 147 .disable = sun4i_dclk_disable, 148 .enable = sun4i_dclk_enable, 149 .is_enabled = sun4i_dclk_is_enabled, 150 151 .recalc_rate = sun4i_dclk_recalc_rate, 152 .round_rate = sun4i_dclk_round_rate, 153 .set_rate = sun4i_dclk_set_rate, 154 155 .get_phase = sun4i_dclk_get_phase, 156 .set_phase = sun4i_dclk_set_phase, 157}; 158 159int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon) 160{ 161 const char *clk_name, *parent_name; 162 struct clk_init_data init; 163 struct sun4i_dclk *dclk; 164 int ret; 165 166 parent_name = __clk_get_name(tcon->sclk0); 167 ret = of_property_read_string_index(dev->of_node, 168 "clock-output-names", 0, 169 &clk_name); 170 if (ret) 171 return ret; 172 173 dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL); 174 if (!dclk) 175 return -ENOMEM; 176 dclk->tcon = tcon; 177 178 init.name = clk_name; 179 init.ops = &sun4i_dclk_ops; 180 init.parent_names = &parent_name; 181 init.num_parents = 1; 182 init.flags = CLK_SET_RATE_PARENT; 183 184 dclk->regmap = tcon->regs; 185 dclk->hw.init = &init; 186 187 tcon->dclk = clk_register(dev, &dclk->hw); 188 if (IS_ERR(tcon->dclk)) 189 return PTR_ERR(tcon->dclk); 190 191 return 0; 192} 193EXPORT_SYMBOL(sun4i_dclk_create); 194 195int sun4i_dclk_free(struct sun4i_tcon *tcon) 196{ 197 clk_unregister(tcon->dclk); 198 return 0; 199} 200EXPORT_SYMBOL(sun4i_dclk_free);