1 // SPDX-License-Identifier: GPL-2.0-only 2 3 #include <linux/clk-provider.h> 4 #include <linux/device/devres.h> 5 #include <linux/mfd/syscon.h> 6 #include <linux/module.h> 7 #include <linux/of.h> 8 #include <linux/slab.h> 9 #include <soc/spacemit/ccu.h> 10 11 #include "ccu_common.h" 12 13 static DEFINE_IDA(auxiliary_ids); 14 static int spacemit_ccu_register(struct device *dev, 15 struct regmap *regmap, 16 struct regmap *lock_regmap, 17 const struct spacemit_ccu_data *data) 18 { 19 struct clk_hw_onecell_data *clk_data; 20 int i, ret; 21 22 /* Nothing to do if the CCU does not implement any clocks */ 23 if (!data->hws) 24 return 0; 25 26 clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, data->num), 27 GFP_KERNEL); 28 if (!clk_data) 29 return -ENOMEM; 30 31 clk_data->num = data->num; 32 33 for (i = 0; i < data->num; i++) { 34 struct clk_hw *hw = data->hws[i]; 35 struct ccu_common *common; 36 const char *name; 37 38 if (!hw) { 39 clk_data->hws[i] = ERR_PTR(-ENOENT); 40 continue; 41 } 42 43 name = hw->init->name; 44 45 common = hw_to_ccu_common(hw); 46 common->regmap = regmap; 47 common->lock_regmap = lock_regmap; 48 49 ret = devm_clk_hw_register(dev, hw); 50 if (ret) { 51 dev_err(dev, "Cannot register clock %d - %s\n", 52 i, name); 53 return ret; 54 } 55 56 clk_data->hws[i] = hw; 57 } 58 59 ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data); 60 if (ret) 61 dev_err(dev, "failed to add clock hardware provider (%d)\n", ret); 62 63 return ret; 64 } 65 66 static void spacemit_cadev_release(struct device *dev) 67 { 68 struct auxiliary_device *adev = to_auxiliary_dev(dev); 69 70 ida_free(&auxiliary_ids, adev->id); 71 kfree(to_spacemit_ccu_adev(adev)); 72 } 73 74 static void spacemit_adev_unregister(void *data) 75 { 76 struct auxiliary_device *adev = data; 77 78 auxiliary_device_delete(adev); 79 auxiliary_device_uninit(adev); 80 } 81 82 static int spacemit_ccu_reset_register(struct device *dev, 83 struct regmap *regmap, 84 const char *reset_name) 85 { 86 struct spacemit_ccu_adev *cadev; 87 struct auxiliary_device *adev; 88 int ret; 89 90 /* Nothing to do if the CCU does not implement a reset controller */ 91 if (!reset_name) 92 return 0; 93 94 cadev = kzalloc(sizeof(*cadev), GFP_KERNEL); 95 if (!cadev) 96 return -ENOMEM; 97 98 cadev->regmap = regmap; 99 100 adev = &cadev->adev; 101 adev->name = reset_name; 102 adev->dev.parent = dev; 103 adev->dev.release = spacemit_cadev_release; 104 adev->dev.of_node = dev->of_node; 105 ret = ida_alloc(&auxiliary_ids, GFP_KERNEL); 106 if (ret < 0) 107 goto err_free_cadev; 108 adev->id = ret; 109 110 ret = auxiliary_device_init(adev); 111 if (ret) 112 goto err_free_aux_id; 113 114 ret = auxiliary_device_add(adev); 115 if (ret) { 116 auxiliary_device_uninit(adev); 117 return ret; 118 } 119 120 return devm_add_action_or_reset(dev, spacemit_adev_unregister, adev); 121 122 err_free_aux_id: 123 ida_free(&auxiliary_ids, adev->id); 124 err_free_cadev: 125 kfree(cadev); 126 127 return ret; 128 } 129 130 int spacemit_ccu_probe(struct platform_device *pdev, const char *compat) 131 { 132 struct regmap *base_regmap, *lock_regmap = NULL; 133 const struct spacemit_ccu_data *data; 134 struct device *dev = &pdev->dev; 135 int ret; 136 137 base_regmap = device_node_to_regmap(dev->of_node); 138 if (IS_ERR(base_regmap)) 139 return dev_err_probe(dev, PTR_ERR(base_regmap), 140 "failed to get regmap\n"); 141 142 /* 143 * The lock status of PLLs locate in MPMU region, while PLLs themselves 144 * are in APBS region. Reference to MPMU syscon is required to check PLL 145 * status. 146 */ 147 if (compat && of_device_is_compatible(dev->of_node, compat)) { 148 struct device_node *mpmu = of_parse_phandle(dev->of_node, 149 "spacemit,mpmu", 0); 150 if (!mpmu) 151 return dev_err_probe(dev, -ENODEV, 152 "Cannot parse MPMU region\n"); 153 154 lock_regmap = device_node_to_regmap(mpmu); 155 of_node_put(mpmu); 156 157 if (IS_ERR(lock_regmap)) 158 return dev_err_probe(dev, PTR_ERR(lock_regmap), 159 "failed to get lock regmap\n"); 160 } 161 162 data = of_device_get_match_data(dev); 163 164 ret = spacemit_ccu_register(dev, base_regmap, lock_regmap, data); 165 if (ret) 166 return dev_err_probe(dev, ret, "failed to register clocks\n"); 167 168 ret = spacemit_ccu_reset_register(dev, base_regmap, data->reset_name); 169 if (ret) 170 return dev_err_probe(dev, ret, "failed to register resets\n"); 171 172 return 0; 173 } 174 EXPORT_SYMBOL_NS_GPL(spacemit_ccu_probe, "CLK_SPACEMIT"); 175 176 MODULE_DESCRIPTION("SpacemiT CCU common clock driver"); 177 MODULE_LICENSE("GPL"); 178