18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Qualcomm APCS clock controller driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2017, Linaro Limited
68c2ecf20Sopenharmony_ci * Author: Georgi Djakov <georgi.djakov@linaro.org>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/clk.h>
108c2ecf20Sopenharmony_ci#include <linux/clk-provider.h>
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/slab.h>
148c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
158c2ecf20Sopenharmony_ci#include <linux/regmap.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include "clk-regmap.h"
188c2ecf20Sopenharmony_ci#include "clk-regmap-mux-div.h"
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistatic const u32 gpll0_a53cc_map[] = { 4, 5 };
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cistatic const struct clk_parent_data pdata[] = {
238c2ecf20Sopenharmony_ci	{ .fw_name = "aux", .name = "gpll0_vote", },
248c2ecf20Sopenharmony_ci	{ .fw_name = "pll", .name = "a53pll", },
258c2ecf20Sopenharmony_ci};
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci/*
288c2ecf20Sopenharmony_ci * We use the notifier function for switching to a temporary safe configuration
298c2ecf20Sopenharmony_ci * (mux and divider), while the A53 PLL is reconfigured.
308c2ecf20Sopenharmony_ci */
318c2ecf20Sopenharmony_cistatic int a53cc_notifier_cb(struct notifier_block *nb, unsigned long event,
328c2ecf20Sopenharmony_ci			     void *data)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	int ret = 0;
358c2ecf20Sopenharmony_ci	struct clk_regmap_mux_div *md = container_of(nb,
368c2ecf20Sopenharmony_ci						     struct clk_regmap_mux_div,
378c2ecf20Sopenharmony_ci						     clk_nb);
388c2ecf20Sopenharmony_ci	if (event == PRE_RATE_CHANGE)
398c2ecf20Sopenharmony_ci		/* set the mux and divider to safe frequency (400mhz) */
408c2ecf20Sopenharmony_ci		ret = mux_div_set_src_div(md, 4, 3);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	return notifier_from_errno(ret);
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistatic int qcom_apcs_msm8916_clk_probe(struct platform_device *pdev)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
488c2ecf20Sopenharmony_ci	struct device *parent = dev->parent;
498c2ecf20Sopenharmony_ci	struct clk_regmap_mux_div *a53cc;
508c2ecf20Sopenharmony_ci	struct regmap *regmap;
518c2ecf20Sopenharmony_ci	struct clk_init_data init = { };
528c2ecf20Sopenharmony_ci	int ret = -ENODEV;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	regmap = dev_get_regmap(parent, NULL);
558c2ecf20Sopenharmony_ci	if (!regmap) {
568c2ecf20Sopenharmony_ci		dev_err(dev, "failed to get regmap: %d\n", ret);
578c2ecf20Sopenharmony_ci		return ret;
588c2ecf20Sopenharmony_ci	}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	a53cc = devm_kzalloc(dev, sizeof(*a53cc), GFP_KERNEL);
618c2ecf20Sopenharmony_ci	if (!a53cc)
628c2ecf20Sopenharmony_ci		return -ENOMEM;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	init.name = "a53mux";
658c2ecf20Sopenharmony_ci	init.parent_data = pdata;
668c2ecf20Sopenharmony_ci	init.num_parents = ARRAY_SIZE(pdata);
678c2ecf20Sopenharmony_ci	init.ops = &clk_regmap_mux_div_ops;
688c2ecf20Sopenharmony_ci	init.flags = CLK_SET_RATE_PARENT;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	a53cc->clkr.hw.init = &init;
718c2ecf20Sopenharmony_ci	a53cc->clkr.regmap = regmap;
728c2ecf20Sopenharmony_ci	a53cc->reg_offset = 0x50;
738c2ecf20Sopenharmony_ci	a53cc->hid_width = 5;
748c2ecf20Sopenharmony_ci	a53cc->hid_shift = 0;
758c2ecf20Sopenharmony_ci	a53cc->src_width = 3;
768c2ecf20Sopenharmony_ci	a53cc->src_shift = 8;
778c2ecf20Sopenharmony_ci	a53cc->parent_map = gpll0_a53cc_map;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	a53cc->pclk = devm_clk_get(parent, NULL);
808c2ecf20Sopenharmony_ci	if (IS_ERR(a53cc->pclk)) {
818c2ecf20Sopenharmony_ci		ret = PTR_ERR(a53cc->pclk);
828c2ecf20Sopenharmony_ci		if (ret != -EPROBE_DEFER)
838c2ecf20Sopenharmony_ci			dev_err(dev, "failed to get clk: %d\n", ret);
848c2ecf20Sopenharmony_ci		return ret;
858c2ecf20Sopenharmony_ci	}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	a53cc->clk_nb.notifier_call = a53cc_notifier_cb;
888c2ecf20Sopenharmony_ci	ret = clk_notifier_register(a53cc->pclk, &a53cc->clk_nb);
898c2ecf20Sopenharmony_ci	if (ret) {
908c2ecf20Sopenharmony_ci		dev_err(dev, "failed to register clock notifier: %d\n", ret);
918c2ecf20Sopenharmony_ci		return ret;
928c2ecf20Sopenharmony_ci	}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	ret = devm_clk_register_regmap(dev, &a53cc->clkr);
958c2ecf20Sopenharmony_ci	if (ret) {
968c2ecf20Sopenharmony_ci		dev_err(dev, "failed to register regmap clock: %d\n", ret);
978c2ecf20Sopenharmony_ci		goto err;
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
1018c2ecf20Sopenharmony_ci					  &a53cc->clkr.hw);
1028c2ecf20Sopenharmony_ci	if (ret) {
1038c2ecf20Sopenharmony_ci		dev_err(dev, "failed to add clock provider: %d\n", ret);
1048c2ecf20Sopenharmony_ci		goto err;
1058c2ecf20Sopenharmony_ci	}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, a53cc);
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	return 0;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cierr:
1128c2ecf20Sopenharmony_ci	clk_notifier_unregister(a53cc->pclk, &a53cc->clk_nb);
1138c2ecf20Sopenharmony_ci	return ret;
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic int qcom_apcs_msm8916_clk_remove(struct platform_device *pdev)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	struct clk_regmap_mux_div *a53cc = platform_get_drvdata(pdev);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	clk_notifier_unregister(a53cc->pclk, &a53cc->clk_nb);
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	return 0;
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic struct platform_driver qcom_apcs_msm8916_clk_driver = {
1268c2ecf20Sopenharmony_ci	.probe = qcom_apcs_msm8916_clk_probe,
1278c2ecf20Sopenharmony_ci	.remove = qcom_apcs_msm8916_clk_remove,
1288c2ecf20Sopenharmony_ci	.driver = {
1298c2ecf20Sopenharmony_ci		.name = "qcom-apcs-msm8916-clk",
1308c2ecf20Sopenharmony_ci	},
1318c2ecf20Sopenharmony_ci};
1328c2ecf20Sopenharmony_cimodule_platform_driver(qcom_apcs_msm8916_clk_driver);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ciMODULE_AUTHOR("Georgi Djakov <georgi.djakov@linaro.org>");
1358c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
1368c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm MSM8916 APCS clock driver");
137