1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Qualcomm APCS clock controller driver |
4 | * |
5 | * Copyright (c) 2022, Linaro Limited |
6 | * Author: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> |
7 | */ |
8 | |
9 | #include <linux/bits.h> |
10 | #include <linux/bitfield.h> |
11 | #include <linux/clk-provider.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/module.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/regmap.h> |
16 | |
17 | #define APCS_AUX_OFFSET 0x50 |
18 | |
19 | #define APCS_AUX_DIV_MASK GENMASK(17, 16) |
20 | #define APCS_AUX_DIV_2 0x1 |
21 | |
22 | static int qcom_apcs_msm8996_clk_probe(struct platform_device *pdev) |
23 | { |
24 | struct device *dev = &pdev->dev; |
25 | struct device *parent = dev->parent; |
26 | struct regmap *regmap; |
27 | struct clk_hw *hw; |
28 | unsigned int val; |
29 | int ret = -ENODEV; |
30 | |
31 | regmap = dev_get_regmap(dev: parent, NULL); |
32 | if (!regmap) { |
33 | dev_err(dev, "failed to get regmap: %d\n" , ret); |
34 | return ret; |
35 | } |
36 | |
37 | regmap_read(map: regmap, APCS_AUX_OFFSET, val: &val); |
38 | regmap_update_bits(map: regmap, APCS_AUX_OFFSET, APCS_AUX_DIV_MASK, |
39 | FIELD_PREP(APCS_AUX_DIV_MASK, APCS_AUX_DIV_2)); |
40 | |
41 | /* |
42 | * This clock is used during CPU cluster setup while setting up CPU PLLs. |
43 | * Add hardware mandated delay to make sure that the sys_apcs_aux clock |
44 | * is stable (after setting the divider) before continuing |
45 | * bootstrapping to keep CPUs from ending up in a weird state. |
46 | */ |
47 | udelay(5); |
48 | |
49 | /* |
50 | * As this clocks is a parent of the CPU cluster clocks and is actually |
51 | * used as a parent during CPU clocks setup, we want for it to register |
52 | * as early as possible, without letting fw_devlink to delay probing of |
53 | * either of the drivers. |
54 | * |
55 | * The sys_apcs_aux is a child (divider) of gpll0, but we register it |
56 | * as a fixed rate clock instead to ease bootstrapping procedure. By |
57 | * doing this we make sure that CPU cluster clocks are able to be setup |
58 | * early during the boot process (as it is recommended by Qualcomm). |
59 | */ |
60 | hw = devm_clk_hw_register_fixed_rate(dev, "sys_apcs_aux" , NULL, 0, 300000000); |
61 | if (IS_ERR(ptr: hw)) |
62 | return PTR_ERR(ptr: hw); |
63 | |
64 | return devm_of_clk_add_hw_provider(dev, get: of_clk_hw_simple_get, data: hw); |
65 | } |
66 | |
67 | static struct platform_driver qcom_apcs_msm8996_clk_driver = { |
68 | .probe = qcom_apcs_msm8996_clk_probe, |
69 | .driver = { |
70 | .name = "qcom-apcs-msm8996-clk" , |
71 | }, |
72 | }; |
73 | |
74 | /* Register early enough to fix the clock to be used for other cores */ |
75 | static int __init qcom_apcs_msm8996_clk_init(void) |
76 | { |
77 | return platform_driver_register(&qcom_apcs_msm8996_clk_driver); |
78 | } |
79 | postcore_initcall(qcom_apcs_msm8996_clk_init); |
80 | |
81 | static void __exit qcom_apcs_msm8996_clk_exit(void) |
82 | { |
83 | platform_driver_unregister(&qcom_apcs_msm8996_clk_driver); |
84 | } |
85 | module_exit(qcom_apcs_msm8996_clk_exit); |
86 | |
87 | MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>" ); |
88 | MODULE_LICENSE("GPL" ); |
89 | MODULE_DESCRIPTION("Qualcomm MSM8996 APCS clock driver" ); |
90 | |