1 // SPDX-License-Identifier: GPL-2.0 2 3 #include <linux/arm-smccc.h> 4 #include <linux/bitfield.h> 5 #include <linux/clk-provider.h> 6 #include <linux/module.h> 7 #include <linux/platform_device.h> 8 #include <linux/pm_domain.h> 9 #include <linux/slab.h> 10 11 #define AIROHA_SIP_AVS_HANDLE 0x82000301 12 #define AIROHA_AVS_OP_BASE 0xddddddd0 13 #define AIROHA_AVS_OP_MASK GENMASK(1, 0) 14 #define AIROHA_AVS_OP_FREQ_DYN_ADJ (AIROHA_AVS_OP_BASE | \ 15 FIELD_PREP(AIROHA_AVS_OP_MASK, 0x1)) 16 #define AIROHA_AVS_OP_GET_FREQ (AIROHA_AVS_OP_BASE | \ 17 FIELD_PREP(AIROHA_AVS_OP_MASK, 0x2)) 18 19 struct airoha_cpu_pmdomain_priv { 20 struct clk_hw hw; 21 struct generic_pm_domain pd; 22 }; 23 24 static long airoha_cpu_pmdomain_clk_round(struct clk_hw *hw, unsigned long rate, 25 unsigned long *parent_rate) 26 { 27 return rate; 28 } 29 30 static unsigned long airoha_cpu_pmdomain_clk_get(struct clk_hw *hw, 31 unsigned long parent_rate) 32 { 33 struct arm_smccc_res res; 34 35 arm_smccc_1_1_invoke(AIROHA_SIP_AVS_HANDLE, AIROHA_AVS_OP_GET_FREQ, 36 0, 0, 0, 0, 0, 0, &res); 37 38 /* SMCCC returns freq in MHz */ 39 return (int)(res.a0 * 1000 * 1000); 40 } 41 42 /* Airoha CPU clk SMCC is always enabled */ 43 static int airoha_cpu_pmdomain_clk_is_enabled(struct clk_hw *hw) 44 { 45 return true; 46 } 47 48 static const struct clk_ops airoha_cpu_pmdomain_clk_ops = { 49 .recalc_rate = airoha_cpu_pmdomain_clk_get, 50 .is_enabled = airoha_cpu_pmdomain_clk_is_enabled, 51 .round_rate = airoha_cpu_pmdomain_clk_round, 52 }; 53 54 static int airoha_cpu_pmdomain_set_performance_state(struct generic_pm_domain *domain, 55 unsigned int state) 56 { 57 struct arm_smccc_res res; 58 59 arm_smccc_1_1_invoke(AIROHA_SIP_AVS_HANDLE, AIROHA_AVS_OP_FREQ_DYN_ADJ, 60 0, state, 0, 0, 0, 0, &res); 61 62 /* SMC signal correct apply by unsetting BIT 0 */ 63 return res.a0 & BIT(0) ? -EINVAL : 0; 64 } 65 66 static int airoha_cpu_pmdomain_probe(struct platform_device *pdev) 67 { 68 struct airoha_cpu_pmdomain_priv *priv; 69 struct device *dev = &pdev->dev; 70 const struct clk_init_data init = { 71 .name = "cpu", 72 .ops = &airoha_cpu_pmdomain_clk_ops, 73 /* Clock with no set_rate, can't cache */ 74 .flags = CLK_GET_RATE_NOCACHE, 75 }; 76 struct generic_pm_domain *pd; 77 int ret; 78 79 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 80 if (!priv) 81 return -ENOMEM; 82 83 /* Init and register a get-only clk for Cpufreq */ 84 priv->hw.init = &init; 85 ret = devm_clk_hw_register(dev, &priv->hw); 86 if (ret) 87 return ret; 88 89 ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, 90 &priv->hw); 91 if (ret) 92 return ret; 93 94 /* Init and register a PD for CPU */ 95 pd = &priv->pd; 96 pd->name = "cpu_pd"; 97 pd->flags = GENPD_FLAG_ALWAYS_ON; 98 pd->set_performance_state = airoha_cpu_pmdomain_set_performance_state; 99 100 ret = pm_genpd_init(pd, NULL, false); 101 if (ret) 102 return ret; 103 104 ret = of_genpd_add_provider_simple(dev->of_node, pd); 105 if (ret) 106 goto err_add_provider; 107 108 platform_set_drvdata(pdev, priv); 109 110 return 0; 111 112 err_add_provider: 113 pm_genpd_remove(pd); 114 115 return ret; 116 } 117 118 static void airoha_cpu_pmdomain_remove(struct platform_device *pdev) 119 { 120 struct airoha_cpu_pmdomain_priv *priv = platform_get_drvdata(pdev); 121 122 of_genpd_del_provider(pdev->dev.of_node); 123 pm_genpd_remove(&priv->pd); 124 } 125 126 static const struct of_device_id airoha_cpu_pmdomain_of_match[] = { 127 { .compatible = "airoha,en7581-cpufreq" }, 128 { }, 129 }; 130 MODULE_DEVICE_TABLE(of, airoha_cpu_pmdomain_of_match); 131 132 static struct platform_driver airoha_cpu_pmdomain_driver = { 133 .probe = airoha_cpu_pmdomain_probe, 134 .remove = airoha_cpu_pmdomain_remove, 135 .driver = { 136 .name = "airoha-cpu-pmdomain", 137 .of_match_table = airoha_cpu_pmdomain_of_match, 138 }, 139 }; 140 module_platform_driver(airoha_cpu_pmdomain_driver); 141 142 MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>"); 143 MODULE_DESCRIPTION("CPU PM domain driver for Airoha SoCs"); 144 MODULE_LICENSE("GPL"); 145