1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * LPASS Audio CC and Always ON CC Glitch Free Mux clock driver 4 * 5 * Copyright (c) 2020 Linaro Ltd. 6 * Author: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> 7 */ 8 9 #include <linux/kernel.h> 10 #include <linux/module.h> 11 #include <linux/clk-provider.h> 12 #include <linux/io.h> 13 #include <linux/slab.h> 14 #include <linux/err.h> 15 #include <linux/pm_clock.h> 16 #include <linux/pm_runtime.h> 17 #include <linux/device.h> 18 #include <linux/of.h> 19 #include <linux/platform_device.h> 20 #include <dt-bindings/clock/qcom,sm8250-lpass-audiocc.h> 21 #include <dt-bindings/clock/qcom,sm8250-lpass-aoncc.h> 22 23 struct lpass_gfm { 24 struct device *dev; 25 void __iomem *base; 26 }; 27 28 struct clk_gfm { 29 unsigned int mux_reg; 30 unsigned int mux_mask; 31 struct clk_hw hw; 32 struct lpass_gfm *priv; 33 void __iomem *gfm_mux; 34 }; 35 36 #define to_clk_gfm(_hw) container_of(_hw, struct clk_gfm, hw) 37 38 static u8 clk_gfm_get_parent(struct clk_hw *hw) 39 { 40 struct clk_gfm *clk = to_clk_gfm(hw); 41 42 return readl(clk->gfm_mux) & clk->mux_mask; 43 } 44 45 static int clk_gfm_set_parent(struct clk_hw *hw, u8 index) 46 { 47 struct clk_gfm *clk = to_clk_gfm(hw); 48 unsigned int val; 49 50 val = readl(clk->gfm_mux); 51 52 if (index) 53 val |= clk->mux_mask; 54 else 55 val &= ~clk->mux_mask; 56 57 58 writel(val, clk->gfm_mux); 59 60 return 0; 61 } 62 63 static const struct clk_ops clk_gfm_ops = { 64 .get_parent = clk_gfm_get_parent, 65 .set_parent = clk_gfm_set_parent, 66 .determine_rate = __clk_mux_determine_rate, 67 }; 68 69 static struct clk_gfm lpass_gfm_va_mclk = { 70 .mux_reg = 0x20000, 71 .mux_mask = BIT(0), 72 .hw.init = &(struct clk_init_data) { 73 .name = "VA_MCLK", 74 .ops = &clk_gfm_ops, 75 .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, 76 .num_parents = 2, 77 .parent_data = (const struct clk_parent_data[]){ 78 { 79 .index = 0, 80 .fw_name = "LPASS_CLK_ID_TX_CORE_MCLK", 81 }, { 82 .index = 1, 83 .fw_name = "LPASS_CLK_ID_VA_CORE_MCLK", 84 }, 85 }, 86 }, 87 }; 88 89 static struct clk_gfm lpass_gfm_tx_npl = { 90 .mux_reg = 0x20000, 91 .mux_mask = BIT(0), 92 .hw.init = &(struct clk_init_data) { 93 .name = "TX_NPL", 94 .ops = &clk_gfm_ops, 95 .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, 96 .parent_data = (const struct clk_parent_data[]){ 97 { 98 .index = 0, 99 .fw_name = "LPASS_CLK_ID_TX_CORE_NPL_MCLK", 100 }, { 101 .index = 1, 102 .fw_name = "LPASS_CLK_ID_VA_CORE_2X_MCLK", 103 }, 104 }, 105 .num_parents = 2, 106 }, 107 }; 108 109 static struct clk_gfm lpass_gfm_wsa_mclk = { 110 .mux_reg = 0x220d8, 111 .mux_mask = BIT(0), 112 .hw.init = &(struct clk_init_data) { 113 .name = "WSA_MCLK", 114 .ops = &clk_gfm_ops, 115 .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, 116 .parent_data = (const struct clk_parent_data[]){ 117 { 118 .index = 0, 119 .fw_name = "LPASS_CLK_ID_TX_CORE_MCLK", 120 }, { 121 .index = 1, 122 .fw_name = "LPASS_CLK_ID_WSA_CORE_MCLK", 123 }, 124 }, 125 .num_parents = 2, 126 }, 127 }; 128 129 static struct clk_gfm lpass_gfm_wsa_npl = { 130 .mux_reg = 0x220d8, 131 .mux_mask = BIT(0), 132 .hw.init = &(struct clk_init_data) { 133 .name = "WSA_NPL", 134 .ops = &clk_gfm_ops, 135 .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, 136 .parent_data = (const struct clk_parent_data[]){ 137 { 138 .index = 0, 139 .fw_name = "LPASS_CLK_ID_TX_CORE_NPL_MCLK", 140 }, { 141 .index = 1, 142 .fw_name = "LPASS_CLK_ID_WSA_CORE_NPL_MCLK", 143 }, 144 }, 145 .num_parents = 2, 146 }, 147 }; 148 149 static struct clk_gfm lpass_gfm_rx_mclk_mclk2 = { 150 .mux_reg = 0x240d8, 151 .mux_mask = BIT(0), 152 .hw.init = &(struct clk_init_data) { 153 .name = "RX_MCLK_MCLK2", 154 .ops = &clk_gfm_ops, 155 .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, 156 .parent_data = (const struct clk_parent_data[]){ 157 { 158 .index = 0, 159 .fw_name = "LPASS_CLK_ID_TX_CORE_MCLK", 160 }, { 161 .index = 1, 162 .fw_name = "LPASS_CLK_ID_RX_CORE_MCLK", 163 }, 164 }, 165 .num_parents = 2, 166 }, 167 }; 168 169 static struct clk_gfm lpass_gfm_rx_npl = { 170 .mux_reg = 0x240d8, 171 .mux_mask = BIT(0), 172 .hw.init = &(struct clk_init_data) { 173 .name = "RX_NPL", 174 .ops = &clk_gfm_ops, 175 .flags = CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE, 176 .parent_data = (const struct clk_parent_data[]){ 177 { 178 .index = 0, 179 .fw_name = "LPASS_CLK_ID_TX_CORE_NPL_MCLK", 180 }, { 181 .index = 1, 182 .fw_name = "LPASS_CLK_ID_RX_CORE_NPL_MCLK", 183 }, 184 }, 185 .num_parents = 2, 186 }, 187 }; 188 189 static struct clk_gfm *aoncc_gfm_clks[] = { 190 [LPASS_CDC_VA_MCLK] = &lpass_gfm_va_mclk, 191 [LPASS_CDC_TX_NPL] = &lpass_gfm_tx_npl, 192 }; 193 194 static struct clk_hw_onecell_data aoncc_hw_onecell_data = { 195 .hws = { 196 [LPASS_CDC_VA_MCLK] = &lpass_gfm_va_mclk.hw, 197 [LPASS_CDC_TX_NPL] = &lpass_gfm_tx_npl.hw, 198 }, 199 .num = ARRAY_SIZE(aoncc_gfm_clks), 200 }; 201 202 static struct clk_gfm *audiocc_gfm_clks[] = { 203 [LPASS_CDC_WSA_NPL] = &lpass_gfm_wsa_npl, 204 [LPASS_CDC_WSA_MCLK] = &lpass_gfm_wsa_mclk, 205 [LPASS_CDC_RX_NPL] = &lpass_gfm_rx_npl, 206 [LPASS_CDC_RX_MCLK_MCLK2] = &lpass_gfm_rx_mclk_mclk2, 207 }; 208 209 static struct clk_hw_onecell_data audiocc_hw_onecell_data = { 210 .hws = { 211 [LPASS_CDC_WSA_NPL] = &lpass_gfm_wsa_npl.hw, 212 [LPASS_CDC_WSA_MCLK] = &lpass_gfm_wsa_mclk.hw, 213 [LPASS_CDC_RX_NPL] = &lpass_gfm_rx_npl.hw, 214 [LPASS_CDC_RX_MCLK_MCLK2] = &lpass_gfm_rx_mclk_mclk2.hw, 215 }, 216 .num = ARRAY_SIZE(audiocc_gfm_clks), 217 }; 218 219 struct lpass_gfm_data { 220 struct clk_hw_onecell_data *onecell_data; 221 struct clk_gfm **gfm_clks; 222 }; 223 224 static struct lpass_gfm_data audiocc_data = { 225 .onecell_data = &audiocc_hw_onecell_data, 226 .gfm_clks = audiocc_gfm_clks, 227 }; 228 229 static struct lpass_gfm_data aoncc_data = { 230 .onecell_data = &aoncc_hw_onecell_data, 231 .gfm_clks = aoncc_gfm_clks, 232 }; 233 234 static int lpass_gfm_clk_driver_probe(struct platform_device *pdev) 235 { 236 const struct lpass_gfm_data *data; 237 struct device *dev = &pdev->dev; 238 struct clk_gfm *gfm; 239 struct lpass_gfm *cc; 240 int err, i; 241 242 data = of_device_get_match_data(dev); 243 if (!data) 244 return -EINVAL; 245 246 cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL); 247 if (!cc) 248 return -ENOMEM; 249 250 cc->base = devm_platform_ioremap_resource(pdev, 0); 251 if (IS_ERR(cc->base)) 252 return PTR_ERR(cc->base); 253 254 err = devm_pm_runtime_enable(dev); 255 if (err) 256 return err; 257 258 err = devm_pm_clk_create(dev); 259 if (err) 260 return err; 261 262 err = of_pm_clk_add_clks(dev); 263 if (err < 0) { 264 dev_dbg(dev, "Failed to get lpass core voting clocks\n"); 265 return err; 266 } 267 268 for (i = 0; i < data->onecell_data->num; i++) { 269 if (!data->gfm_clks[i]) 270 continue; 271 272 gfm = data->gfm_clks[i]; 273 gfm->priv = cc; 274 gfm->gfm_mux = cc->base; 275 gfm->gfm_mux = gfm->gfm_mux + data->gfm_clks[i]->mux_reg; 276 277 err = devm_clk_hw_register(dev, &data->gfm_clks[i]->hw); 278 if (err) 279 return err; 280 281 } 282 283 err = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, 284 data->onecell_data); 285 if (err) 286 return err; 287 288 return 0; 289 } 290 291 static const struct of_device_id lpass_gfm_clk_match_table[] = { 292 { 293 .compatible = "qcom,sm8250-lpass-aoncc", 294 .data = &aoncc_data, 295 }, 296 { 297 .compatible = "qcom,sm8250-lpass-audiocc", 298 .data = &audiocc_data, 299 }, 300 { } 301 }; 302 MODULE_DEVICE_TABLE(of, lpass_gfm_clk_match_table); 303 304 static const struct dev_pm_ops lpass_gfm_pm_ops = { 305 SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL) 306 }; 307 308 static struct platform_driver lpass_gfm_clk_driver = { 309 .probe = lpass_gfm_clk_driver_probe, 310 .driver = { 311 .name = "lpass-gfm-clk", 312 .of_match_table = lpass_gfm_clk_match_table, 313 .pm = &lpass_gfm_pm_ops, 314 }, 315 }; 316 module_platform_driver(lpass_gfm_clk_driver); 317 MODULE_LICENSE("GPL v2"); 318 MODULE_DESCRIPTION("QTI SM8250 LPASS Glitch Free Mux clock driver"); 319