1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2024 SpacemiT Technology Co. Ltd 4 * Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org> 5 * 6 * DDN stands for "Divider Denominator Numerator", it's M/N clock with a 7 * constant x2 factor. This clock hardware follows the equation below, 8 * 9 * numerator Fin 10 * 2 * ------------- = ------- 11 * denominator Fout 12 * 13 * Thus, Fout could be calculated with, 14 * 15 * Fin denominator 16 * Fout = ----- * ------------- 17 * 2 numerator 18 */ 19 20 #include <linux/clk-provider.h> 21 #include <linux/rational.h> 22 23 #include "ccu_ddn.h" 24 25 static unsigned long ccu_ddn_calc_rate(unsigned long prate, 26 unsigned long num, unsigned long den) 27 { 28 return prate * den / 2 / num; 29 } 30 31 static unsigned long ccu_ddn_calc_best_rate(struct ccu_ddn *ddn, 32 unsigned long rate, unsigned long prate, 33 unsigned long *num, unsigned long *den) 34 { 35 rational_best_approximation(rate, prate / 2, 36 ddn->den_mask >> ddn->den_shift, 37 ddn->num_mask >> ddn->num_shift, 38 den, num); 39 return ccu_ddn_calc_rate(prate, *num, *den); 40 } 41 42 static long ccu_ddn_round_rate(struct clk_hw *hw, unsigned long rate, 43 unsigned long *prate) 44 { 45 struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); 46 unsigned long num, den; 47 48 return ccu_ddn_calc_best_rate(ddn, rate, *prate, &num, &den); 49 } 50 51 static unsigned long ccu_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate) 52 { 53 struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); 54 unsigned int val, num, den; 55 56 val = ccu_read(&ddn->common, ctrl); 57 58 num = (val & ddn->num_mask) >> ddn->num_shift; 59 den = (val & ddn->den_mask) >> ddn->den_shift; 60 61 return ccu_ddn_calc_rate(prate, num, den); 62 } 63 64 static int ccu_ddn_set_rate(struct clk_hw *hw, unsigned long rate, 65 unsigned long prate) 66 { 67 struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); 68 unsigned long num, den; 69 70 ccu_ddn_calc_best_rate(ddn, rate, prate, &num, &den); 71 72 ccu_update(&ddn->common, ctrl, 73 ddn->num_mask | ddn->den_mask, 74 (num << ddn->num_shift) | (den << ddn->den_shift)); 75 76 return 0; 77 } 78 79 const struct clk_ops spacemit_ccu_ddn_ops = { 80 .recalc_rate = ccu_ddn_recalc_rate, 81 .round_rate = ccu_ddn_round_rate, 82 .set_rate = ccu_ddn_set_rate, 83 }; 84