Commit | Line | Data |
---|---|---|
5ea42859 SN |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (c) 2019 Samsung Electronics Co., Ltd. | |
4 | * http://www.samsung.com/ | |
352bfbb3 | 5 | * Copyright (c) 2020 Krzysztof Kozlowski <krzk@kernel.org> |
5ea42859 | 6 | * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> |
352bfbb3 | 7 | * Author: Krzysztof Kozlowski <krzk@kernel.org> |
5ea42859 SN |
8 | * |
9 | * Samsung Exynos SoC Adaptive Supply Voltage support | |
10 | */ | |
11 | ||
12 | #include <linux/cpu.h> | |
13 | #include <linux/device.h> | |
14 | #include <linux/errno.h> | |
5ea42859 | 15 | #include <linux/of.h> |
5ea42859 SN |
16 | #include <linux/pm_opp.h> |
17 | #include <linux/regmap.h> | |
18 | #include <linux/soc/samsung/exynos-chipid.h> | |
19 | ||
20 | #include "exynos-asv.h" | |
21 | #include "exynos5422-asv.h" | |
22 | ||
23 | #define MHZ 1000000U | |
24 | ||
25 | static int exynos_asv_update_cpu_opps(struct exynos_asv *asv, | |
26 | struct device *cpu) | |
27 | { | |
28 | struct exynos_asv_subsys *subsys = NULL; | |
29 | struct dev_pm_opp *opp; | |
30 | unsigned int opp_freq; | |
31 | int i; | |
32 | ||
33 | for (i = 0; i < ARRAY_SIZE(asv->subsys); i++) { | |
34 | if (of_device_is_compatible(cpu->of_node, | |
35 | asv->subsys[i].cpu_dt_compat)) { | |
36 | subsys = &asv->subsys[i]; | |
37 | break; | |
38 | } | |
39 | } | |
40 | if (!subsys) | |
41 | return -EINVAL; | |
42 | ||
43 | for (i = 0; i < subsys->table.num_rows; i++) { | |
44 | unsigned int new_volt, volt; | |
45 | int ret; | |
46 | ||
47 | opp_freq = exynos_asv_opp_get_frequency(subsys, i); | |
48 | ||
49 | opp = dev_pm_opp_find_freq_exact(cpu, opp_freq * MHZ, true); | |
50 | if (IS_ERR(opp)) { | |
51 | dev_info(asv->dev, "cpu%d opp%d, freq: %u missing\n", | |
52 | cpu->id, i, opp_freq); | |
53 | ||
54 | continue; | |
55 | } | |
56 | ||
57 | volt = dev_pm_opp_get_voltage(opp); | |
58 | new_volt = asv->opp_get_voltage(subsys, i, volt); | |
59 | dev_pm_opp_put(opp); | |
60 | ||
61 | if (new_volt == volt) | |
62 | continue; | |
63 | ||
64 | ret = dev_pm_opp_adjust_voltage(cpu, opp_freq * MHZ, | |
65 | new_volt, new_volt, new_volt); | |
66 | if (ret < 0) | |
67 | dev_err(asv->dev, | |
68 | "Failed to adjust OPP %u Hz/%u uV for cpu%d\n", | |
69 | opp_freq, new_volt, cpu->id); | |
70 | else | |
71 | dev_dbg(asv->dev, | |
72 | "Adjusted OPP %u Hz/%u -> %u uV, cpu%d\n", | |
73 | opp_freq, volt, new_volt, cpu->id); | |
74 | } | |
75 | ||
76 | return 0; | |
77 | } | |
78 | ||
79 | static int exynos_asv_update_opps(struct exynos_asv *asv) | |
80 | { | |
81 | struct opp_table *last_opp_table = NULL; | |
82 | struct device *cpu; | |
83 | int ret, cpuid; | |
84 | ||
85 | for_each_possible_cpu(cpuid) { | |
86 | struct opp_table *opp_table; | |
87 | ||
88 | cpu = get_cpu_device(cpuid); | |
89 | if (!cpu) | |
90 | continue; | |
91 | ||
92 | opp_table = dev_pm_opp_get_opp_table(cpu); | |
dd461cd9 | 93 | if (IS_ERR(opp_table)) |
5ea42859 SN |
94 | continue; |
95 | ||
96 | if (!last_opp_table || opp_table != last_opp_table) { | |
97 | last_opp_table = opp_table; | |
98 | ||
99 | ret = exynos_asv_update_cpu_opps(asv, cpu); | |
100 | if (ret < 0) | |
101 | dev_err(asv->dev, "Couldn't udate OPPs for cpu%d\n", | |
102 | cpuid); | |
103 | } | |
104 | ||
105 | dev_pm_opp_put_opp_table(opp_table); | |
106 | } | |
107 | ||
108 | return 0; | |
109 | } | |
110 | ||
352bfbb3 | 111 | int exynos_asv_init(struct device *dev, struct regmap *regmap) |
5ea42859 SN |
112 | { |
113 | int (*probe_func)(struct exynos_asv *asv); | |
114 | struct exynos_asv *asv; | |
115 | struct device *cpu_dev; | |
116 | u32 product_id = 0; | |
117 | int ret, i; | |
118 | ||
352bfbb3 | 119 | asv = devm_kzalloc(dev, sizeof(*asv), GFP_KERNEL); |
5ea42859 SN |
120 | if (!asv) |
121 | return -ENOMEM; | |
122 | ||
352bfbb3 KK |
123 | asv->chipid_regmap = regmap; |
124 | asv->dev = dev; | |
4561560d KK |
125 | ret = regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_PRO_ID, |
126 | &product_id); | |
127 | if (ret < 0) { | |
352bfbb3 | 128 | dev_err(dev, "Cannot read revision from ChipID: %d\n", ret); |
4561560d KK |
129 | return -ENODEV; |
130 | } | |
5ea42859 SN |
131 | |
132 | switch (product_id & EXYNOS_MASK) { | |
133 | case 0xE5422000: | |
134 | probe_func = exynos5422_asv_init; | |
135 | break; | |
136 | default: | |
352bfbb3 KK |
137 | dev_dbg(dev, "No ASV support for this SoC\n"); |
138 | devm_kfree(dev, asv); | |
139 | return 0; | |
5ea42859 SN |
140 | } |
141 | ||
0458b882 MS |
142 | cpu_dev = get_cpu_device(0); |
143 | ret = dev_pm_opp_get_opp_count(cpu_dev); | |
144 | if (ret < 0) | |
145 | return -EPROBE_DEFER; | |
146 | ||
352bfbb3 | 147 | ret = of_property_read_u32(dev->of_node, "samsung,asv-bin", |
5ea42859 SN |
148 | &asv->of_bin); |
149 | if (ret < 0) | |
150 | asv->of_bin = -EINVAL; | |
151 | ||
5ea42859 SN |
152 | for (i = 0; i < ARRAY_SIZE(asv->subsys); i++) |
153 | asv->subsys[i].asv = asv; | |
154 | ||
155 | ret = probe_func(asv); | |
156 | if (ret < 0) | |
157 | return ret; | |
158 | ||
159 | return exynos_asv_update_opps(asv); | |
160 | } |