1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2016 Maxime Ripard 4 * Maxime Ripard <maxime.ripard@free-electrons.com> 5 */ 6 7 #include <linux/clk-provider.h> 8 #include <linux/io.h> 9 10 #include "ccu_gate.h" 11 #include "ccu_div.h" 12 13 static int ccu_div_determine_rate_helper(struct ccu_mux_internal *mux, 14 struct clk_rate_request *req, 15 void *data) 16 { 17 struct ccu_div *cd = data; 18 int ret; 19 20 if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV) 21 req->rate *= cd->fixed_post_div; 22 23 if (cd->div.flags & CLK_DIVIDER_READ_ONLY) { 24 unsigned long val; 25 u32 reg; 26 27 reg = readl(cd->common.base + cd->common.reg); 28 val = reg >> cd->div.shift; 29 val &= (1 << cd->div.width) - 1; 30 31 ret = divider_ro_determine_rate(&cd->common.hw, req, cd->div.table, 32 cd->div.width, cd->div.flags, val); 33 34 } else { 35 ret = divider_determine_rate(&cd->common.hw, req, cd->div.table, 36 cd->div.width, cd->div.flags); 37 } 38 39 if (ret) 40 return ret; 41 42 if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV) 43 req->rate /= cd->fixed_post_div; 44 45 return 0; 46 } 47 48 static void ccu_div_disable(struct clk_hw *hw) 49 { 50 struct ccu_div *cd = hw_to_ccu_div(hw); 51 52 return ccu_gate_helper_disable(&cd->common, cd->enable); 53 } 54 55 static int ccu_div_enable(struct clk_hw *hw) 56 { 57 struct ccu_div *cd = hw_to_ccu_div(hw); 58 59 return ccu_gate_helper_enable(&cd->common, cd->enable); 60 } 61 62 static int ccu_div_is_enabled(struct clk_hw *hw) 63 { 64 struct ccu_div *cd = hw_to_ccu_div(hw); 65 66 return ccu_gate_helper_is_enabled(&cd->common, cd->enable); 67 } 68 69 static unsigned long ccu_div_recalc_rate(struct clk_hw *hw, 70 unsigned long parent_rate) 71 { 72 struct ccu_div *cd = hw_to_ccu_div(hw); 73 unsigned long val; 74 u32 reg; 75 76 reg = readl(cd->common.base + cd->common.reg); 77 val = reg >> cd->div.shift; 78 val &= (1 << cd->div.width) - 1; 79 80 parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1, 81 parent_rate); 82 83 val = divider_recalc_rate(hw, parent_rate, val, cd->div.table, 84 cd->div.flags, cd->div.width); 85 86 if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV) 87 val /= cd->fixed_post_div; 88 89 return val; 90 } 91 92 static int ccu_div_determine_rate(struct clk_hw *hw, 93 struct clk_rate_request *req) 94 { 95 struct ccu_div *cd = hw_to_ccu_div(hw); 96 97 return ccu_mux_helper_determine_rate(&cd->common, &cd->mux, 98 req, ccu_div_determine_rate_helper, cd); 99 } 100 101 static int ccu_div_set_rate(struct clk_hw *hw, unsigned long rate, 102 unsigned long parent_rate) 103 { 104 struct ccu_div *cd = hw_to_ccu_div(hw); 105 unsigned long flags; 106 unsigned long val; 107 u32 reg; 108 109 parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1, 110 parent_rate); 111 112 if (cd->common.features & CCU_FEATURE_FIXED_POSTDIV) 113 rate *= cd->fixed_post_div; 114 115 val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width, 116 cd->div.flags); 117 118 spin_lock_irqsave(cd->common.lock, flags); 119 120 reg = readl(cd->common.base + cd->common.reg); 121 reg &= ~GENMASK(cd->div.width + cd->div.shift - 1, cd->div.shift); 122 if (cd->common.features & CCU_FEATURE_UPDATE_BIT) 123 reg |= CCU_SUNXI_UPDATE_BIT; 124 125 writel(reg | (val << cd->div.shift), 126 cd->common.base + cd->common.reg); 127 128 spin_unlock_irqrestore(cd->common.lock, flags); 129 130 return 0; 131 } 132 133 static u8 ccu_div_get_parent(struct clk_hw *hw) 134 { 135 struct ccu_div *cd = hw_to_ccu_div(hw); 136 137 return ccu_mux_helper_get_parent(&cd->common, &cd->mux); 138 } 139 140 static int ccu_div_set_parent(struct clk_hw *hw, u8 index) 141 { 142 struct ccu_div *cd = hw_to_ccu_div(hw); 143 144 return ccu_mux_helper_set_parent(&cd->common, &cd->mux, index); 145 } 146 147 const struct clk_ops ccu_div_ops = { 148 .disable = ccu_div_disable, 149 .enable = ccu_div_enable, 150 .is_enabled = ccu_div_is_enabled, 151 152 .get_parent = ccu_div_get_parent, 153 .set_parent = ccu_div_set_parent, 154 155 .determine_rate = ccu_div_determine_rate, 156 .recalc_rate = ccu_div_recalc_rate, 157 .set_rate = ccu_div_set_rate, 158 }; 159 EXPORT_SYMBOL_NS_GPL(ccu_div_ops, "SUNXI_CCU"); 160 161 const struct clk_ops ccu_rodiv_ops = { 162 .disable = ccu_div_disable, 163 .enable = ccu_div_enable, 164 .is_enabled = ccu_div_is_enabled, 165 166 .get_parent = ccu_div_get_parent, 167 .set_parent = ccu_div_set_parent, 168 169 .determine_rate = ccu_div_determine_rate, 170 .recalc_rate = ccu_div_recalc_rate, 171 }; 172 EXPORT_SYMBOL_NS_GPL(ccu_rodiv_ops, "SUNXI_CCU"); 173