// SPDX-License-Identifier: GPL-2.0-only #include #include #include #include #include #include #include #include "ccu_common.h" static DEFINE_IDA(auxiliary_ids); static int spacemit_ccu_register(struct device *dev, struct regmap *regmap, struct regmap *lock_regmap, const struct spacemit_ccu_data *data) { struct clk_hw_onecell_data *clk_data; int i, ret; /* Nothing to do if the CCU does not implement any clocks */ if (!data->hws) return 0; clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, data->num), GFP_KERNEL); if (!clk_data) return -ENOMEM; clk_data->num = data->num; for (i = 0; i < data->num; i++) { struct clk_hw *hw = data->hws[i]; struct ccu_common *common; const char *name; if (!hw) { clk_data->hws[i] = ERR_PTR(-ENOENT); continue; } name = hw->init->name; common = hw_to_ccu_common(hw); common->regmap = regmap; common->lock_regmap = lock_regmap; ret = devm_clk_hw_register(dev, hw); if (ret) { dev_err(dev, "Cannot register clock %d - %s\n", i, name); return ret; } clk_data->hws[i] = hw; } ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data); if (ret) dev_err(dev, "failed to add clock hardware provider (%d)\n", ret); return ret; } static void spacemit_cadev_release(struct device *dev) { struct auxiliary_device *adev = to_auxiliary_dev(dev); ida_free(&auxiliary_ids, adev->id); kfree(to_spacemit_ccu_adev(adev)); } static void spacemit_adev_unregister(void *data) { struct auxiliary_device *adev = data; auxiliary_device_delete(adev); auxiliary_device_uninit(adev); } static int spacemit_ccu_reset_register(struct device *dev, struct regmap *regmap, const char *reset_name) { struct spacemit_ccu_adev *cadev; struct auxiliary_device *adev; int ret; /* Nothing to do if the CCU does not implement a reset controller */ if (!reset_name) return 0; cadev = kzalloc_obj(*cadev); if (!cadev) return -ENOMEM; cadev->regmap = regmap; adev = &cadev->adev; adev->name = reset_name; adev->dev.parent = dev; adev->dev.release = spacemit_cadev_release; adev->dev.of_node = dev->of_node; ret = ida_alloc(&auxiliary_ids, GFP_KERNEL); if (ret < 0) goto err_free_cadev; adev->id = ret; ret = auxiliary_device_init(adev); if (ret) goto err_free_aux_id; ret = auxiliary_device_add(adev); if (ret) { auxiliary_device_uninit(adev); return ret; } return devm_add_action_or_reset(dev, spacemit_adev_unregister, adev); err_free_aux_id: ida_free(&auxiliary_ids, adev->id); err_free_cadev: kfree(cadev); return ret; } int spacemit_ccu_probe(struct platform_device *pdev, const char *compat) { struct regmap *base_regmap, *lock_regmap = NULL; const struct spacemit_ccu_data *data; struct device *dev = &pdev->dev; int ret; base_regmap = device_node_to_regmap(dev->of_node); if (IS_ERR(base_regmap)) return dev_err_probe(dev, PTR_ERR(base_regmap), "failed to get regmap\n"); /* * The lock status of PLLs locate in MPMU region, while PLLs themselves * are in APBS region. Reference to MPMU syscon is required to check PLL * status. */ if (compat && of_device_is_compatible(dev->of_node, compat)) { struct device_node *mpmu = of_parse_phandle(dev->of_node, "spacemit,mpmu", 0); if (!mpmu) return dev_err_probe(dev, -ENODEV, "Cannot parse MPMU region\n"); lock_regmap = device_node_to_regmap(mpmu); of_node_put(mpmu); if (IS_ERR(lock_regmap)) return dev_err_probe(dev, PTR_ERR(lock_regmap), "failed to get lock regmap\n"); } data = of_device_get_match_data(dev); ret = spacemit_ccu_register(dev, base_regmap, lock_regmap, data); if (ret) return dev_err_probe(dev, ret, "failed to register clocks\n"); ret = spacemit_ccu_reset_register(dev, base_regmap, data->reset_name); if (ret) return dev_err_probe(dev, ret, "failed to register resets\n"); return 0; } EXPORT_SYMBOL_NS_GPL(spacemit_ccu_probe, "CLK_SPACEMIT"); MODULE_DESCRIPTION("SpacemiT CCU common clock driver"); MODULE_LICENSE("GPL");