1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2019 Samsung Electronics Co., Ltd. |
4 | * http://www.samsung.com/ |
5 | * Copyright (c) 2020 Krzysztof Kozlowski <krzk@kernel.org> |
6 | * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> |
7 | * Author: Krzysztof Kozlowski <krzk@kernel.org> |
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> |
15 | #include <linux/of.h> |
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(device: 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, level: i); |
48 | |
49 | opp = dev_pm_opp_find_freq_exact(dev: cpu, freq: opp_freq * MHZ, available: true); |
50 | if (IS_ERR(ptr: 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(dev: cpu, freq: opp_freq * MHZ, |
65 | u_volt: new_volt, u_volt_min: new_volt, u_volt_max: 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(cpu: cpuid); |
89 | if (!cpu) |
90 | continue; |
91 | |
92 | opp_table = dev_pm_opp_get_opp_table(dev: cpu); |
93 | if (IS_ERR(ptr: opp_table)) |
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 | |
111 | int exynos_asv_init(struct device *dev, struct regmap *regmap) |
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 | |
119 | asv = devm_kzalloc(dev, size: sizeof(*asv), GFP_KERNEL); |
120 | if (!asv) |
121 | return -ENOMEM; |
122 | |
123 | asv->chipid_regmap = regmap; |
124 | asv->dev = dev; |
125 | ret = regmap_read(map: asv->chipid_regmap, EXYNOS_CHIPID_REG_PRO_ID, |
126 | val: &product_id); |
127 | if (ret < 0) { |
128 | dev_err(dev, "Cannot read revision from ChipID: %d\n" , ret); |
129 | return -ENODEV; |
130 | } |
131 | |
132 | switch (product_id & EXYNOS_MASK) { |
133 | case 0xE5422000: |
134 | probe_func = exynos5422_asv_init; |
135 | break; |
136 | default: |
137 | dev_dbg(dev, "No ASV support for this SoC\n" ); |
138 | devm_kfree(dev, p: asv); |
139 | return 0; |
140 | } |
141 | |
142 | cpu_dev = get_cpu_device(cpu: 0); |
143 | ret = dev_pm_opp_get_opp_count(dev: cpu_dev); |
144 | if (ret < 0) |
145 | return -EPROBE_DEFER; |
146 | |
147 | ret = of_property_read_u32(np: dev->of_node, propname: "samsung,asv-bin" , |
148 | out_value: &asv->of_bin); |
149 | if (ret < 0) |
150 | asv->of_bin = -EINVAL; |
151 | |
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 | } |
161 | |