162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Qualcomm APCS clock controller driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2022, Linaro Limited
662306a36Sopenharmony_ci * Author: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/bits.h>
1062306a36Sopenharmony_ci#include <linux/bitfield.h>
1162306a36Sopenharmony_ci#include <linux/clk-provider.h>
1262306a36Sopenharmony_ci#include <linux/delay.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci#include <linux/regmap.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define APCS_AUX_OFFSET	0x50
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define APCS_AUX_DIV_MASK GENMASK(17, 16)
2062306a36Sopenharmony_ci#define APCS_AUX_DIV_2 0x1
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic int qcom_apcs_msm8996_clk_probe(struct platform_device *pdev)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
2562306a36Sopenharmony_ci	struct device *parent = dev->parent;
2662306a36Sopenharmony_ci	struct regmap *regmap;
2762306a36Sopenharmony_ci	struct clk_hw *hw;
2862306a36Sopenharmony_ci	unsigned int val;
2962306a36Sopenharmony_ci	int ret = -ENODEV;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	regmap = dev_get_regmap(parent, NULL);
3262306a36Sopenharmony_ci	if (!regmap) {
3362306a36Sopenharmony_ci		dev_err(dev, "failed to get regmap: %d\n", ret);
3462306a36Sopenharmony_ci		return ret;
3562306a36Sopenharmony_ci	}
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	regmap_read(regmap, APCS_AUX_OFFSET, &val);
3862306a36Sopenharmony_ci	regmap_update_bits(regmap, APCS_AUX_OFFSET, APCS_AUX_DIV_MASK,
3962306a36Sopenharmony_ci			   FIELD_PREP(APCS_AUX_DIV_MASK, APCS_AUX_DIV_2));
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	/*
4262306a36Sopenharmony_ci	 * This clock is used during CPU cluster setup while setting up CPU PLLs.
4362306a36Sopenharmony_ci	 * Add hardware mandated delay to make sure that the sys_apcs_aux clock
4462306a36Sopenharmony_ci	 * is stable (after setting the divider) before continuing
4562306a36Sopenharmony_ci	 * bootstrapping to keep CPUs from ending up in a weird state.
4662306a36Sopenharmony_ci	 */
4762306a36Sopenharmony_ci	udelay(5);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	/*
5062306a36Sopenharmony_ci	 * As this clocks is a parent of the CPU cluster clocks and is actually
5162306a36Sopenharmony_ci	 * used as a parent during CPU clocks setup, we want for it to register
5262306a36Sopenharmony_ci	 * as early as possible, without letting fw_devlink to delay probing of
5362306a36Sopenharmony_ci	 * either of the drivers.
5462306a36Sopenharmony_ci	 *
5562306a36Sopenharmony_ci	 * The sys_apcs_aux is a child (divider) of gpll0, but we register it
5662306a36Sopenharmony_ci	 * as a fixed rate clock instead to ease bootstrapping procedure. By
5762306a36Sopenharmony_ci	 * doing this we make sure that CPU cluster clocks are able to be setup
5862306a36Sopenharmony_ci	 * early during the boot process (as it is recommended by Qualcomm).
5962306a36Sopenharmony_ci	 */
6062306a36Sopenharmony_ci	hw = devm_clk_hw_register_fixed_rate(dev, "sys_apcs_aux", NULL, 0, 300000000);
6162306a36Sopenharmony_ci	if (IS_ERR(hw))
6262306a36Sopenharmony_ci		return PTR_ERR(hw);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic struct platform_driver qcom_apcs_msm8996_clk_driver = {
6862306a36Sopenharmony_ci	.probe = qcom_apcs_msm8996_clk_probe,
6962306a36Sopenharmony_ci	.driver = {
7062306a36Sopenharmony_ci		.name = "qcom-apcs-msm8996-clk",
7162306a36Sopenharmony_ci	},
7262306a36Sopenharmony_ci};
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci/* Register early enough to fix the clock to be used for other cores */
7562306a36Sopenharmony_cistatic int __init qcom_apcs_msm8996_clk_init(void)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	return platform_driver_register(&qcom_apcs_msm8996_clk_driver);
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_cipostcore_initcall(qcom_apcs_msm8996_clk_init);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic void __exit qcom_apcs_msm8996_clk_exit(void)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	platform_driver_unregister(&qcom_apcs_msm8996_clk_driver);
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_cimodule_exit(qcom_apcs_msm8996_clk_exit);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ciMODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>");
8862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
8962306a36Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm MSM8996 APCS clock driver");
90