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

clk: clk-si544: Implement small frequency change support

The Si544 supports changing frequencies "on the fly" when the change is
less than 950 ppm from the current center frequency. The driver now
uses the small adjustment routine for implementing this.

Signed-off-by: Mike Looijmans <mike.looijmans@topic.nl>
Signed-off-by: Stephen Boyd <sboyd@kernel.org>

authored by

Mike Looijmans and committed by
Stephen Boyd
dc59c133 a188339c

+93 -11
+93 -11
drivers/clk/clk-si544.c
··· 7 7 8 8 #include <linux/clk-provider.h> 9 9 #include <linux/delay.h> 10 + #include <linux/math64.h> 10 11 #include <linux/module.h> 11 12 #include <linux/i2c.h> 12 13 #include <linux/regmap.h> ··· 51 50 /* Lowest frequency synthesizeable using only the HS divider */ 52 51 #define MIN_HSDIV_FREQ (FVCO_MIN / HS_DIV_MAX) 53 52 53 + /* Range and interpretation of the adjustment value */ 54 + #define DELTA_M_MAX 8161512 55 + #define DELTA_M_FRAC_NUM 19 56 + #define DELTA_M_FRAC_DEN 20000 57 + 54 58 enum si544_speed_grade { 55 59 si544a, 56 60 si544b, ··· 77 71 * @hs_div: 1st divider, 5..2046, must be even when >33 78 72 * @ls_div_bits: 2nd divider, as 2^x, range 0..5 79 73 * If ls_div_bits is non-zero, hs_div must be even 74 + * @delta_m: Frequency shift for small -950..+950 ppm changes, 24 bit 80 75 */ 81 76 struct clk_si544_muldiv { 82 77 u32 fb_div_frac; 83 78 u16 fb_div_int; 84 79 u16 hs_div; 85 80 u8 ls_div_bits; 81 + s32 delta_m; 86 82 }; 87 83 88 84 /* Enables or disables the output driver */ ··· 142 134 settings->fb_div_int = reg[4] | (reg[5] & 0x07) << 8; 143 135 settings->fb_div_frac = reg[0] | reg[1] << 8 | reg[2] << 16 | 144 136 reg[3] << 24; 137 + 138 + err = regmap_bulk_read(data->regmap, SI544_REG_ADPLL_DELTA_M0, reg, 3); 139 + if (err) 140 + return err; 141 + 142 + /* Interpret as 24-bit signed number */ 143 + settings->delta_m = reg[0] << 8 | reg[1] << 16 | reg[2] << 24; 144 + settings->delta_m >>= 8; 145 + 145 146 return 0; 147 + } 148 + 149 + static int si544_set_delta_m(struct clk_si544 *data, s32 delta_m) 150 + { 151 + u8 reg[3]; 152 + 153 + reg[0] = delta_m; 154 + reg[1] = delta_m >> 8; 155 + reg[2] = delta_m >> 16; 156 + 157 + return regmap_bulk_write(data->regmap, SI544_REG_ADPLL_DELTA_M0, 158 + reg, 3); 146 159 } 147 160 148 161 static int si544_set_muldiv(struct clk_si544 *data, ··· 267 238 do_div(vco, FXO); 268 239 settings->fb_div_frac = vco; 269 240 241 + /* Reset the frequency adjustment */ 242 + settings->delta_m = 0; 243 + 270 244 return 0; 271 245 } 272 246 273 247 /* Calculate resulting frequency given the register settings */ 274 - static unsigned long si544_calc_rate(struct clk_si544_muldiv *settings) 248 + static unsigned long si544_calc_center_rate( 249 + const struct clk_si544_muldiv *settings) 275 250 { 276 251 u32 d = settings->hs_div * BIT(settings->ls_div_bits); 277 252 u64 vco; ··· 292 259 do_div(vco, d); 293 260 294 261 return vco; 262 + } 263 + 264 + static unsigned long si544_calc_rate(const struct clk_si544_muldiv *settings) 265 + { 266 + unsigned long rate = si544_calc_center_rate(settings); 267 + s64 delta = (s64)rate * (DELTA_M_FRAC_NUM * settings->delta_m); 268 + 269 + /* 270 + * The clock adjustment is much smaller than 1 Hz, round to the 271 + * nearest multiple. Apparently div64_s64 rounds towards zero, hence 272 + * check the sign and adjust into the proper direction. 273 + */ 274 + if (settings->delta_m < 0) 275 + delta -= ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN) / 2; 276 + else 277 + delta += ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN) / 2; 278 + delta = div64_s64(delta, ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN)); 279 + 280 + return rate + delta; 295 281 } 296 282 297 283 static unsigned long si544_recalc_rate(struct clk_hw *hw, ··· 331 279 unsigned long *parent_rate) 332 280 { 333 281 struct clk_si544 *data = to_clk_si544(hw); 334 - struct clk_si544_muldiv settings; 335 - int err; 336 282 337 283 if (!is_valid_frequency(data, rate)) 338 284 return -EINVAL; 339 285 340 - err = si544_calc_muldiv(&settings, rate); 341 - if (err) 342 - return err; 343 - 344 - return si544_calc_rate(&settings); 286 + /* The accuracy is less than 1 Hz, so any rate is possible */ 287 + return rate; 345 288 } 346 289 347 - /* 348 - * Update output frequency for "big" frequency changes 349 - */ 290 + /* Calculates the maximum "small" change, 950 * rate / 1000000 */ 291 + static unsigned long si544_max_delta(unsigned long rate) 292 + { 293 + u64 num = rate; 294 + 295 + num *= DELTA_M_FRAC_NUM; 296 + do_div(num, DELTA_M_FRAC_DEN); 297 + 298 + return num; 299 + } 300 + 301 + static s32 si544_calc_delta(s32 delta, s32 max_delta) 302 + { 303 + s64 n = (s64)delta * DELTA_M_MAX; 304 + 305 + return div_s64(n, max_delta); 306 + } 307 + 350 308 static int si544_set_rate(struct clk_hw *hw, unsigned long rate, 351 309 unsigned long parent_rate) 352 310 { 353 311 struct clk_si544 *data = to_clk_si544(hw); 354 312 struct clk_si544_muldiv settings; 313 + unsigned long center; 314 + long max_delta; 315 + long delta; 355 316 unsigned int old_oe_state; 356 317 int err; 357 318 358 319 if (!is_valid_frequency(data, rate)) 359 320 return -EINVAL; 360 321 322 + /* Try using the frequency adjustment feature for a <= 950ppm change */ 323 + err = si544_get_muldiv(data, &settings); 324 + if (err) 325 + return err; 326 + 327 + center = si544_calc_center_rate(&settings); 328 + max_delta = si544_max_delta(center); 329 + delta = rate - center; 330 + 331 + if (abs(delta) <= max_delta) 332 + return si544_set_delta_m(data, 333 + si544_calc_delta(delta, max_delta)); 334 + 335 + /* Too big for the delta adjustment, need to reprogram */ 361 336 err = si544_calc_muldiv(&settings, rate); 362 337 if (err) 363 338 return err; ··· 400 321 if (err < 0) 401 322 return err; 402 323 324 + err = si544_set_delta_m(data, settings.delta_m); 325 + if (err < 0) 326 + return err; 403 327 404 328 err = si544_set_muldiv(data, &settings); 405 329 if (err < 0)