1239268Sgonzo// SPDX-License-Identifier: GPL-2.0 2244469Scognet/* 3239268Sgonzo * Copyright (c) 2022, 2023 Linaro Ltd. 4239268Sgonzo */ 5239268Sgonzo#include <linux/bitfield.h> 6239268Sgonzo#include <linux/clk.h> 7239268Sgonzo#include <linux/clk-provider.h> 8239268Sgonzo#include <linux/interconnect-clk.h> 9239268Sgonzo#include <linux/interconnect-provider.h> 10239268Sgonzo#include <linux/of.h> 11239268Sgonzo#include <linux/module.h> 12239268Sgonzo#include <linux/platform_device.h> 13239268Sgonzo#include <linux/regmap.h> 14239268Sgonzo 15239268Sgonzo#include <dt-bindings/interconnect/qcom,msm8996-cbf.h> 16239268Sgonzo 17239268Sgonzo#include "clk-alpha-pll.h" 18239268Sgonzo#include "clk-regmap.h" 19239268Sgonzo 20239268Sgonzo/* Need to match the order of clocks in DT binding */ 21239268Sgonzoenum { 22239268Sgonzo DT_XO, 23239268Sgonzo DT_APCS_AUX, 24239268Sgonzo}; 25239268Sgonzo 26239268Sgonzoenum { 27239268Sgonzo CBF_XO_INDEX, 28239268Sgonzo CBF_PLL_INDEX, 29239268Sgonzo CBF_DIV_INDEX, 30239268Sgonzo CBF_APCS_AUX_INDEX, 31239268Sgonzo}; 32239268Sgonzo 33239268Sgonzo#define DIV_THRESHOLD 600000000 34239268Sgonzo 35239268Sgonzo#define CBF_MUX_OFFSET 0x18 36239268Sgonzo#define CBF_MUX_PARENT_MASK GENMASK(1, 0) 37239268Sgonzo#define CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK GENMASK(5, 4) 38239268Sgonzo#define CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL \ 39239268Sgonzo FIELD_PREP(CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK, 0x03) 40239268Sgonzo#define CBF_MUX_AUTO_CLK_SEL_BIT BIT(6) 41239268Sgonzo 42239268Sgonzo#define CBF_PLL_OFFSET 0xf000 43239268Sgonzo 44244469Scognetstatic struct alpha_pll_config cbfpll_config = { 45239268Sgonzo .l = 72, 46239268Sgonzo .config_ctl_val = 0x200d4828, 47239268Sgonzo .config_ctl_hi_val = 0x006, 48239268Sgonzo .test_ctl_val = 0x1c000000, 49246713Skib .test_ctl_hi_val = 0x00004000, 50239268Sgonzo .pre_div_mask = BIT(12), 51239268Sgonzo .post_div_mask = 0x3 << 8, 52246713Skib .post_div_val = 0x1 << 8, 53239268Sgonzo .main_output_mask = BIT(0), 54239268Sgonzo .early_output_mask = BIT(3), 55239268Sgonzo}; 56239268Sgonzo 57239268Sgonzostatic struct clk_alpha_pll cbf_pll = { 58244469Scognet .offset = CBF_PLL_OFFSET, 59244469Scognet .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_HUAYRA_APSS], 60239268Sgonzo .flags = SUPPORTS_DYNAMIC_UPDATE | SUPPORTS_FSM_MODE, 61239268Sgonzo .clkr.hw.init = &(struct clk_init_data){ 62239268Sgonzo .name = "cbf_pll", 63239268Sgonzo .parent_data = (const struct clk_parent_data[]) { 64239268Sgonzo { .index = DT_XO, }, 65239268Sgonzo }, 66239268Sgonzo .num_parents = 1, 67239268Sgonzo .ops = &clk_alpha_pll_hwfsm_ops, 68239268Sgonzo }, 69239268Sgonzo}; 70239268Sgonzo 71239268Sgonzostatic struct clk_fixed_factor cbf_pll_postdiv = { 72239268Sgonzo .mult = 1, 73239268Sgonzo .div = 2, 74239268Sgonzo .hw.init = &(struct clk_init_data){ 75239268Sgonzo .name = "cbf_pll_postdiv", 76239268Sgonzo .parent_hws = (const struct clk_hw*[]){ 77239268Sgonzo &cbf_pll.clkr.hw 78239268Sgonzo }, 79239268Sgonzo .num_parents = 1, 80239268Sgonzo .ops = &clk_fixed_factor_ops, 81239268Sgonzo .flags = CLK_SET_RATE_PARENT, 82239268Sgonzo }, 83239268Sgonzo}; 84239268Sgonzo 85239268Sgonzostatic const struct clk_parent_data cbf_mux_parent_data[] = { 86239268Sgonzo { .index = DT_XO }, 87239268Sgonzo { .hw = &cbf_pll.clkr.hw }, 88239268Sgonzo { .hw = &cbf_pll_postdiv.hw }, 89239268Sgonzo { .index = DT_APCS_AUX }, 90239268Sgonzo}; 91239268Sgonzo 92239268Sgonzostruct clk_cbf_8996_mux { 93239268Sgonzo u32 reg; 94239268Sgonzo struct notifier_block nb; 95239268Sgonzo struct clk_regmap clkr; 96239268Sgonzo}; 97244469Scognet 98244469Scognetstatic struct clk_cbf_8996_mux *to_clk_cbf_8996_mux(struct clk_regmap *clkr) 99244469Scognet{ 100244469Scognet return container_of(clkr, struct clk_cbf_8996_mux, clkr); 101244469Scognet} 102244469Scognet 103244469Scognetstatic int cbf_clk_notifier_cb(struct notifier_block *nb, unsigned long event, 104239268Sgonzo void *data); 105244469Scognet 106239268Sgonzostatic u8 clk_cbf_8996_mux_get_parent(struct clk_hw *hw) 107239268Sgonzo{ 108239268Sgonzo struct clk_regmap *clkr = to_clk_regmap(hw); 109239268Sgonzo struct clk_cbf_8996_mux *mux = to_clk_cbf_8996_mux(clkr); 110239268Sgonzo u32 val; 111239268Sgonzo 112246713Skib regmap_read(clkr->regmap, mux->reg, &val); 113239268Sgonzo 114239268Sgonzo return FIELD_GET(CBF_MUX_PARENT_MASK, val); 115239268Sgonzo} 116239268Sgonzo 117239268Sgonzostatic int clk_cbf_8996_mux_set_parent(struct clk_hw *hw, u8 index) 118239268Sgonzo{ 119239268Sgonzo struct clk_regmap *clkr = to_clk_regmap(hw); 120239268Sgonzo struct clk_cbf_8996_mux *mux = to_clk_cbf_8996_mux(clkr); 121239268Sgonzo u32 val; 122239268Sgonzo 123239268Sgonzo val = FIELD_PREP(CBF_MUX_PARENT_MASK, index); 124239268Sgonzo 125239268Sgonzo return regmap_update_bits(clkr->regmap, mux->reg, CBF_MUX_PARENT_MASK, val); 126239268Sgonzo} 127239268Sgonzo 128239268Sgonzostatic int clk_cbf_8996_mux_determine_rate(struct clk_hw *hw, 129239268Sgonzo struct clk_rate_request *req) 130239268Sgonzo{ 131239268Sgonzo struct clk_hw *parent; 132239268Sgonzo 133239268Sgonzo if (req->rate < (DIV_THRESHOLD / cbf_pll_postdiv.div)) 134239268Sgonzo return -EINVAL; 135239268Sgonzo 136239268Sgonzo if (req->rate < DIV_THRESHOLD) 137239268Sgonzo parent = clk_hw_get_parent_by_index(hw, CBF_DIV_INDEX); 138239268Sgonzo else 139239268Sgonzo parent = clk_hw_get_parent_by_index(hw, CBF_PLL_INDEX); 140239268Sgonzo 141239268Sgonzo if (!parent) 142239268Sgonzo return -EINVAL; 143239268Sgonzo 144239268Sgonzo req->best_parent_rate = clk_hw_round_rate(parent, req->rate); 145239268Sgonzo req->best_parent_hw = parent; 146239268Sgonzo 147239268Sgonzo return 0; 148239268Sgonzo} 149239268Sgonzo 150239268Sgonzostatic const struct clk_ops clk_cbf_8996_mux_ops = { 151239268Sgonzo .set_parent = clk_cbf_8996_mux_set_parent, 152239268Sgonzo .get_parent = clk_cbf_8996_mux_get_parent, 153239268Sgonzo .determine_rate = clk_cbf_8996_mux_determine_rate, 154239268Sgonzo}; 155239268Sgonzo 156239268Sgonzostatic struct clk_cbf_8996_mux cbf_mux = { 157246713Skib .reg = CBF_MUX_OFFSET, 158239268Sgonzo .nb.notifier_call = cbf_clk_notifier_cb, 159239268Sgonzo .clkr.hw.init = &(struct clk_init_data) { 160239268Sgonzo .name = "cbf_mux", 161244469Scognet .parent_data = cbf_mux_parent_data, 162244469Scognet .num_parents = ARRAY_SIZE(cbf_mux_parent_data), 163239268Sgonzo .ops = &clk_cbf_8996_mux_ops, 164246713Skib /* CPU clock is critical and should never be gated */ 165246713Skib .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, 166239268Sgonzo }, 167239268Sgonzo}; 168239268Sgonzo 169239268Sgonzostatic int cbf_clk_notifier_cb(struct notifier_block *nb, unsigned long event, 170239268Sgonzo void *data) 171239268Sgonzo{ 172239268Sgonzo struct clk_notifier_data *cnd = data; 173239268Sgonzo 174239268Sgonzo switch (event) { 175239268Sgonzo case PRE_RATE_CHANGE: 176239268Sgonzo /* 177246713Skib * Avoid overvolting. clk_core_set_rate_nolock() walks from top 178246713Skib * to bottom, so it will change the rate of the PLL before 179239268Sgonzo * chaging the parent of PMUX. This can result in pmux getting 180254061Scognet * clocked twice the expected rate. 181246713Skib * 182239268Sgonzo * Manually switch to PLL/2 here. 183246713Skib */ 184246713Skib if (cnd->old_rate > DIV_THRESHOLD && 185246713Skib cnd->new_rate < DIV_THRESHOLD) 186246713Skib clk_cbf_8996_mux_set_parent(&cbf_mux.clkr.hw, CBF_DIV_INDEX); 187239268Sgonzo break; 188244469Scognet case ABORT_RATE_CHANGE: 189244469Scognet /* Revert manual change */ 190244469Scognet if (cnd->new_rate < DIV_THRESHOLD && 191244469Scognet cnd->old_rate > DIV_THRESHOLD) 192244469Scognet clk_cbf_8996_mux_set_parent(&cbf_mux.clkr.hw, CBF_PLL_INDEX); 193252652Sgonzo break; 194244469Scognet default: 195252652Sgonzo break; 196252652Sgonzo } 197244469Scognet 198244469Scognet return notifier_from_errno(0); 199244469Scognet}; 200244469Scognet 201244469Scognetstatic struct clk_hw *cbf_msm8996_hw_clks[] = { 202252652Sgonzo &cbf_pll_postdiv.hw, 203244469Scognet}; 204252652Sgonzo 205252652Sgonzostatic struct clk_regmap *cbf_msm8996_clks[] = { 206252652Sgonzo &cbf_pll.clkr, 207252652Sgonzo &cbf_mux.clkr, 208252652Sgonzo}; 209252652Sgonzo 210252652Sgonzostatic const struct regmap_config cbf_msm8996_regmap_config = { 211252652Sgonzo .reg_bits = 32, 212252652Sgonzo .reg_stride = 4, 213244469Scognet .val_bits = 32, 214244469Scognet .max_register = 0x10000, 215244469Scognet .fast_io = true, 216244469Scognet .val_format_endian = REGMAP_ENDIAN_LITTLE, 217244469Scognet}; 218244469Scognet 219244469Scognet#ifdef CONFIG_INTERCONNECT 220244469Scognet 221252652Sgonzo/* Random ID that doesn't clash with main qnoc and OSM */ 222244469Scognet#define CBF_MASTER_NODE 2000 223244469Scognet 224244469Scognetstatic int qcom_msm8996_cbf_icc_register(struct platform_device *pdev, struct clk_hw *cbf_hw) 225244469Scognet{ 226244469Scognet struct device *dev = &pdev->dev; 227267992Shselasky struct clk *clk = devm_clk_hw_get_clk(dev, cbf_hw, "cbf"); 228267992Shselasky const struct icc_clk_data data[] = { 229244469Scognet { .clk = clk, .name = "cbf", }, 230267992Shselasky }; 231244469Scognet struct icc_provider *provider; 232269136Sian 233269136Sian provider = icc_clk_register(dev, CBF_MASTER_NODE, ARRAY_SIZE(data), data); 234269136Sian if (IS_ERR(provider)) 235269136Sian return PTR_ERR(provider); 236269136Sian 237269136Sian platform_set_drvdata(pdev, provider); 238269136Sian 239269136Sian return 0; 240269136Sian} 241269136Sian 242269136Sianstatic void qcom_msm8996_cbf_icc_remove(struct platform_device *pdev) 243269136Sian{ 244269206Sian struct icc_provider *provider = platform_get_drvdata(pdev); 245269206Sian 246239268Sgonzo icc_clk_unregister(provider); 247239268Sgonzo} 248269136Sian#define qcom_msm8996_cbf_icc_sync_state icc_sync_state 249269136Sian#else 250269136Sianstatic int qcom_msm8996_cbf_icc_register(struct platform_device *pdev, struct clk_hw *cbf_hw) 251269136Sian{ 252239268Sgonzo dev_warn(&pdev->dev, "CONFIG_INTERCONNECT is disabled, CBF clock is fixed\n"); 253239268Sgonzo 254239268Sgonzo return 0; 255239268Sgonzo} 256239268Sgonzo#define qcom_msm8996_cbf_icc_remove(pdev) { } 257239268Sgonzo#define qcom_msm8996_cbf_icc_sync_state NULL 258239268Sgonzo#endif 259239268Sgonzo 260239268Sgonzostatic int qcom_msm8996_cbf_probe(struct platform_device *pdev) 261269206Sian{ 262269206Sian void __iomem *base; 263269206Sian struct regmap *regmap; 264269206Sian struct device *dev = &pdev->dev; 265269206Sian int i, ret; 266269206Sian 267269206Sian base = devm_platform_ioremap_resource(pdev, 0); 268269206Sian if (IS_ERR(base)) 269269206Sian return PTR_ERR(base); 270269206Sian 271269206Sian regmap = devm_regmap_init_mmio(dev, base, &cbf_msm8996_regmap_config); 272269206Sian if (IS_ERR(regmap)) 273269206Sian return PTR_ERR(regmap); 274269206Sian 275269206Sian /* Select GPLL0 for 300MHz for the CBF clock */ 276269206Sian regmap_write(regmap, CBF_MUX_OFFSET, 0x3); 277269206Sian 278269206Sian /* Ensure write goes through before PLLs are reconfigured */ 279269206Sian udelay(5); 280269206Sian 281239268Sgonzo /* Set the auto clock sel always-on source to GPLL0/2 (300MHz) */ 282239268Sgonzo regmap_update_bits(regmap, CBF_MUX_OFFSET, 283239268Sgonzo CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_MASK, 284239268Sgonzo CBF_MUX_AUTO_CLK_SEL_ALWAYS_ON_GPLL0_SEL); 285239268Sgonzo 286239268Sgonzo clk_alpha_pll_configure(&cbf_pll, regmap, &cbfpll_config); 287239268Sgonzo 288239268Sgonzo /* Wait for PLL(s) to lock */ 289239268Sgonzo udelay(50); 290239268Sgonzo 291239268Sgonzo /* Enable auto clock selection for CBF */ 292239268Sgonzo regmap_update_bits(regmap, CBF_MUX_OFFSET, 293239268Sgonzo CBF_MUX_AUTO_CLK_SEL_BIT, 294239268Sgonzo CBF_MUX_AUTO_CLK_SEL_BIT); 295239268Sgonzo 296239268Sgonzo /* Ensure write goes through before muxes are switched */ 297239268Sgonzo udelay(5); 298239268Sgonzo 299239268Sgonzo /* Switch CBF to use the primary PLL */ 300239268Sgonzo regmap_update_bits(regmap, CBF_MUX_OFFSET, CBF_MUX_PARENT_MASK, 0x1); 301239268Sgonzo 302239268Sgonzo if (of_device_is_compatible(dev->of_node, "qcom,msm8996pro-cbf")) { 303239268Sgonzo cbfpll_config.post_div_val = 0x3 << 8; 304239268Sgonzo cbf_pll_postdiv.div = 4; 305239268Sgonzo } 306254061Scognet 307239268Sgonzo for (i = 0; i < ARRAY_SIZE(cbf_msm8996_hw_clks); i++) { 308239268Sgonzo ret = devm_clk_hw_register(dev, cbf_msm8996_hw_clks[i]); 309239268Sgonzo if (ret) 310239268Sgonzo return ret; 311239268Sgonzo } 312239268Sgonzo 313239268Sgonzo for (i = 0; i < ARRAY_SIZE(cbf_msm8996_clks); i++) { 314269206Sian ret = devm_clk_register_regmap(dev, cbf_msm8996_clks[i]); 315269206Sian if (ret) 316239268Sgonzo return ret; 317239268Sgonzo } 318239268Sgonzo 319239268Sgonzo ret = devm_clk_notifier_register(dev, cbf_mux.clkr.hw.clk, &cbf_mux.nb); 320239268Sgonzo if (ret) 321239268Sgonzo return ret; 322239268Sgonzo 323239268Sgonzo ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &cbf_mux.clkr.hw); 324239268Sgonzo if (ret) 325239268Sgonzo return ret; 326239268Sgonzo 327239268Sgonzo return qcom_msm8996_cbf_icc_register(pdev, &cbf_mux.clkr.hw); 328239268Sgonzo} 329239268Sgonzo 330239268Sgonzostatic void qcom_msm8996_cbf_remove(struct platform_device *pdev) 331239268Sgonzo{ 332239268Sgonzo qcom_msm8996_cbf_icc_remove(pdev); 333239268Sgonzo} 334239268Sgonzo 335239268Sgonzostatic const struct of_device_id qcom_msm8996_cbf_match_table[] = { 336239268Sgonzo { .compatible = "qcom,msm8996-cbf" }, 337239268Sgonzo { .compatible = "qcom,msm8996pro-cbf" }, 338239268Sgonzo { /* sentinel */ }, 339239268Sgonzo}; 340239268SgonzoMODULE_DEVICE_TABLE(of, qcom_msm8996_cbf_match_table); 341239268Sgonzo 342239268Sgonzostatic struct platform_driver qcom_msm8996_cbf_driver = { 343239268Sgonzo .probe = qcom_msm8996_cbf_probe, 344239268Sgonzo .remove_new = qcom_msm8996_cbf_remove, 345239268Sgonzo .driver = { 346239268Sgonzo .name = "qcom-msm8996-cbf", 347239268Sgonzo .of_match_table = qcom_msm8996_cbf_match_table, 348239268Sgonzo .sync_state = qcom_msm8996_cbf_icc_sync_state, 349239268Sgonzo }, 350239268Sgonzo}; 351239268Sgonzo 352239268Sgonzo/* Register early enough to fix the clock to be used for other cores */ 353239268Sgonzostatic int __init qcom_msm8996_cbf_init(void) 354239268Sgonzo{ 355239268Sgonzo return platform_driver_register(&qcom_msm8996_cbf_driver); 356239268Sgonzo} 357239268Sgonzopostcore_initcall(qcom_msm8996_cbf_init); 358239268Sgonzo 359239268Sgonzostatic void __exit qcom_msm8996_cbf_exit(void) 360239268Sgonzo{ 361239268Sgonzo platform_driver_unregister(&qcom_msm8996_cbf_driver); 362239268Sgonzo} 363239268Sgonzomodule_exit(qcom_msm8996_cbf_exit); 364239268Sgonzo 365239268SgonzoMODULE_DESCRIPTION("QCOM MSM8996 CPU Bus Fabric Clock Driver"); 366239268SgonzoMODULE_LICENSE("GPL"); 367239268Sgonzo