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, unsigned long num, 26 unsigned long den, unsigned int pre_div) 27 { 28 return prate * den / pre_div / 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 / ddn->pre_div, 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, ddn->pre_div); 40 } 41 42 static int ccu_ddn_determine_rate(struct clk_hw *hw, 43 struct clk_rate_request *req) 44 { 45 struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); 46 unsigned long num, den; 47 48 req->rate = ccu_ddn_calc_best_rate(ddn, req->rate, 49 req->best_parent_rate, &num, &den); 50 51 return 0; 52 } 53 54 static unsigned long ccu_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate) 55 { 56 struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); 57 unsigned int val, num, den; 58 59 val = ccu_read(&ddn->common, ctrl); 60 61 num = (val & ddn->num_mask) >> ddn->num_shift; 62 den = (val & ddn->den_mask) >> ddn->den_shift; 63 64 return ccu_ddn_calc_rate(prate, num, den, ddn->pre_div); 65 } 66 67 static int ccu_ddn_set_rate(struct clk_hw *hw, unsigned long rate, 68 unsigned long prate) 69 { 70 struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); 71 unsigned long num, den; 72 73 ccu_ddn_calc_best_rate(ddn, rate, prate, &num, &den); 74 75 ccu_update(&ddn->common, ctrl, 76 ddn->num_mask | ddn->den_mask, 77 (num << ddn->num_shift) | (den << ddn->den_shift)); 78 79 return 0; 80 } 81 82 const struct clk_ops spacemit_ccu_ddn_ops = { 83 .recalc_rate = ccu_ddn_recalc_rate, 84 .determine_rate = ccu_ddn_determine_rate, 85 .set_rate = ccu_ddn_set_rate, 86 }; 87