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

clk: ti: add am33xx/am43xx spread spectrum clock support

The patch enables spread spectrum clocking (SSC) for MPU and LCD PLLs.
As reported by the TI spruh73x/spruhl7x RM, SSC is only supported for
the DISP/LCD and MPU PLLs on am33xx/am43xx. SSC is not supported for
DDR, PER, and CORE PLLs.

Calculating the required values and setting the registers accordingly
was taken from the set_mpu_spreadspectrum routine contained in the
arch/arm/mach-omap2/am33xx/clock_am33xx.c file of the u-boot project.

In locked condition, DPLL output clock = CLKINP *[M/N]. In case of
SSC enabled, the reference manual explains that there is a restriction
of range of M values. Since the omap2_dpll_round_rate routine attempts
to select the minimum possible N, the value of M obtained is not
guaranteed to be within the range required. With the new "ti,min-div"
parameter it is possible to increase N and consequently M to satisfy the
constraint imposed by SSC.

Signed-off-by: Dario Binacchi <dariobin@libero.it>
Reviewed-by: Tero Kristo <kristo@kernel.org>
Link: https://lore.kernel.org/r/20210606202253.31649-6-dariobin@libero.it
Signed-off-by: Stephen Boyd <sboyd@kernel.org>

authored by

Dario Binacchi and committed by
Stephen Boyd
0899431f 2fdf0b88

+146
+39
drivers/clk/ti/dpll.c
··· 290 290 struct clk_init_data *init = NULL; 291 291 const char **parent_names = NULL; 292 292 struct dpll_data *dd = NULL; 293 + int ssc_clk_index; 293 294 u8 dpll_mode = 0; 295 + u32 min_div; 294 296 295 297 dd = kmemdup(ddt, sizeof(*dd), GFP_KERNEL); 296 298 clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL); ··· 347 345 if (dd->autoidle_mask) { 348 346 if (ti_clk_get_reg_addr(node, 3, &dd->autoidle_reg)) 349 347 goto cleanup; 348 + 349 + ssc_clk_index = 4; 350 + } else { 351 + ssc_clk_index = 3; 352 + } 353 + 354 + if (dd->ssc_deltam_int_mask && dd->ssc_deltam_frac_mask && 355 + dd->ssc_modfreq_mant_mask && dd->ssc_modfreq_exp_mask) { 356 + if (ti_clk_get_reg_addr(node, ssc_clk_index++, 357 + &dd->ssc_deltam_reg)) 358 + goto cleanup; 359 + 360 + if (ti_clk_get_reg_addr(node, ssc_clk_index++, 361 + &dd->ssc_modfreq_reg)) 362 + goto cleanup; 363 + 364 + of_property_read_u32(node, "ti,ssc-modfreq-hz", 365 + &dd->ssc_modfreq); 366 + of_property_read_u32(node, "ti,ssc-deltam", &dd->ssc_deltam); 367 + dd->ssc_downspread = 368 + of_property_read_bool(node, "ti,ssc-downspread"); 350 369 } 351 370 352 371 if (of_property_read_bool(node, "ti,low-power-stop")) ··· 378 355 379 356 if (of_property_read_bool(node, "ti,lock")) 380 357 dpll_mode |= 1 << DPLL_LOCKED; 358 + 359 + if (!of_property_read_u32(node, "ti,min-div", &min_div) && 360 + min_div > dd->min_divider) 361 + dd->min_divider = min_div; 381 362 382 363 if (dpll_mode) 383 364 dd->modes = dpll_mode; ··· 612 585 const struct dpll_data dd = { 613 586 .idlest_mask = 0x1, 614 587 .enable_mask = 0x7, 588 + .ssc_enable_mask = 0x1 << 12, 589 + .ssc_downspread_mask = 0x1 << 14, 615 590 .mult_mask = 0x7ff << 8, 616 591 .div1_mask = 0x7f, 592 + .ssc_deltam_int_mask = 0x3 << 18, 593 + .ssc_deltam_frac_mask = 0x3ffff, 594 + .ssc_modfreq_mant_mask = 0x7f, 595 + .ssc_modfreq_exp_mask = 0x7 << 8, 617 596 .max_multiplier = 2047, 618 597 .max_divider = 128, 619 598 .min_divider = 1, ··· 678 645 const struct dpll_data dd = { 679 646 .idlest_mask = 0x1, 680 647 .enable_mask = 0x7, 648 + .ssc_enable_mask = 0x1 << 12, 649 + .ssc_downspread_mask = 0x1 << 14, 681 650 .mult_mask = 0x7ff << 8, 682 651 .div1_mask = 0x7f, 652 + .ssc_deltam_int_mask = 0x3 << 18, 653 + .ssc_deltam_frac_mask = 0x3ffff, 654 + .ssc_modfreq_mant_mask = 0x7f, 655 + .ssc_modfreq_exp_mask = 0x7 << 8, 683 656 .max_multiplier = 2047, 684 657 .max_divider = 128, 685 658 .min_divider = 1,
+85
drivers/clk/ti/dpll3xxx.c
··· 292 292 } 293 293 294 294 /** 295 + * omap3_noncore_dpll_ssc_program - set spread-spectrum clocking registers 296 + * @clk: struct clk * of DPLL to set 297 + * 298 + * Enable the DPLL spread spectrum clocking if frequency modulation and 299 + * frequency spreading have been set, otherwise disable it. 300 + */ 301 + static void omap3_noncore_dpll_ssc_program(struct clk_hw_omap *clk) 302 + { 303 + struct dpll_data *dd = clk->dpll_data; 304 + unsigned long ref_rate; 305 + u32 v, ctrl, mod_freq_divider, exponent, mantissa; 306 + u32 deltam_step, deltam_ceil; 307 + 308 + ctrl = ti_clk_ll_ops->clk_readl(&dd->control_reg); 309 + 310 + if (dd->ssc_modfreq && dd->ssc_deltam) { 311 + ctrl |= dd->ssc_enable_mask; 312 + 313 + if (dd->ssc_downspread) 314 + ctrl |= dd->ssc_downspread_mask; 315 + else 316 + ctrl &= ~dd->ssc_downspread_mask; 317 + 318 + ref_rate = clk_hw_get_rate(dd->clk_ref); 319 + mod_freq_divider = 320 + (ref_rate / dd->last_rounded_n) / (4 * dd->ssc_modfreq); 321 + if (dd->ssc_modfreq > (ref_rate / 70)) 322 + pr_warn("clock: SSC modulation frequency of DPLL %s greater than %ld\n", 323 + __clk_get_name(clk->hw.clk), ref_rate / 70); 324 + 325 + exponent = 0; 326 + mantissa = mod_freq_divider; 327 + while ((mantissa > 127) && (exponent < 7)) { 328 + exponent++; 329 + mantissa /= 2; 330 + } 331 + if (mantissa > 127) 332 + mantissa = 127; 333 + 334 + v = ti_clk_ll_ops->clk_readl(&dd->ssc_modfreq_reg); 335 + v &= ~(dd->ssc_modfreq_mant_mask | dd->ssc_modfreq_exp_mask); 336 + v |= mantissa << __ffs(dd->ssc_modfreq_mant_mask); 337 + v |= exponent << __ffs(dd->ssc_modfreq_exp_mask); 338 + ti_clk_ll_ops->clk_writel(v, &dd->ssc_modfreq_reg); 339 + 340 + deltam_step = dd->last_rounded_m * dd->ssc_deltam; 341 + deltam_step /= 10; 342 + if (dd->ssc_downspread) 343 + deltam_step /= 2; 344 + 345 + deltam_step <<= __ffs(dd->ssc_deltam_int_mask); 346 + deltam_step /= 100; 347 + deltam_step /= mod_freq_divider; 348 + if (deltam_step > 0xFFFFF) 349 + deltam_step = 0xFFFFF; 350 + 351 + deltam_ceil = (deltam_step & dd->ssc_deltam_int_mask) >> 352 + __ffs(dd->ssc_deltam_int_mask); 353 + if (deltam_step & dd->ssc_deltam_frac_mask) 354 + deltam_ceil++; 355 + 356 + if ((dd->ssc_downspread && 357 + ((dd->last_rounded_m - (2 * deltam_ceil)) < 20 || 358 + dd->last_rounded_m > 2045)) || 359 + ((dd->last_rounded_m - deltam_ceil) < 20 || 360 + (dd->last_rounded_m + deltam_ceil) > 2045)) 361 + pr_warn("clock: SSC multiplier of DPLL %s is out of range\n", 362 + __clk_get_name(clk->hw.clk)); 363 + 364 + v = ti_clk_ll_ops->clk_readl(&dd->ssc_deltam_reg); 365 + v &= ~(dd->ssc_deltam_int_mask | dd->ssc_deltam_frac_mask); 366 + v |= deltam_step << __ffs(dd->ssc_deltam_int_mask | 367 + dd->ssc_deltam_frac_mask); 368 + ti_clk_ll_ops->clk_writel(v, &dd->ssc_deltam_reg); 369 + } else { 370 + ctrl &= ~dd->ssc_enable_mask; 371 + } 372 + 373 + ti_clk_ll_ops->clk_writel(ctrl, &dd->control_reg); 374 + } 375 + 376 + /** 295 377 * omap3_noncore_dpll_program - set non-core DPLL M,N values directly 296 378 * @clk: struct clk * of DPLL to set 297 379 * @freqsel: FREQSEL value to set ··· 471 389 472 390 ti_clk_ll_ops->clk_writel(v, &dd->control_reg); 473 391 } 392 + 393 + if (dd->ssc_enable_mask) 394 + omap3_noncore_dpll_ssc_program(clk); 474 395 475 396 /* We let the clock framework set the other output dividers later */ 476 397
+22
include/linux/clk/ti.h
··· 63 63 * @auto_recal_bit: bitshift of the driftguard enable bit in @control_reg 64 64 * @recal_en_bit: bitshift of the PRM_IRQENABLE_* bit for recalibration IRQs 65 65 * @recal_st_bit: bitshift of the PRM_IRQSTATUS_* bit for recalibration IRQs 66 + * @ssc_deltam_reg: register containing the DPLL SSC frequency spreading 67 + * @ssc_modfreq_reg: register containing the DPLL SSC modulation frequency 68 + * @ssc_modfreq_mant_mask: mask of the mantissa component in @ssc_modfreq_reg 69 + * @ssc_modfreq_exp_mask: mask of the exponent component in @ssc_modfreq_reg 70 + * @ssc_enable_mask: mask of the DPLL SSC enable bit in @control_reg 71 + * @ssc_downspread_mask: mask of the DPLL SSC low frequency only bit in 72 + * @control_reg 73 + * @ssc_modfreq: the DPLL SSC frequency modulation in kHz 74 + * @ssc_deltam: the DPLL SSC frequency spreading in permille (10th of percent) 75 + * @ssc_downspread: require the only low frequency spread of the DPLL in SSC 76 + * mode 66 77 * @flags: DPLL type/features (see below) 67 78 * 68 79 * Possible values for @flags: ··· 121 110 u8 auto_recal_bit; 122 111 u8 recal_en_bit; 123 112 u8 recal_st_bit; 113 + struct clk_omap_reg ssc_deltam_reg; 114 + struct clk_omap_reg ssc_modfreq_reg; 115 + u32 ssc_deltam_int_mask; 116 + u32 ssc_deltam_frac_mask; 117 + u32 ssc_modfreq_mant_mask; 118 + u32 ssc_modfreq_exp_mask; 119 + u32 ssc_enable_mask; 120 + u32 ssc_downspread_mask; 121 + u32 ssc_modfreq; 122 + u32 ssc_deltam; 123 + bool ssc_downspread; 124 124 u8 flags; 125 125 }; 126 126