1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "aml-thermal.h"
6 #include <ddk/debug.h>
7 #include <fbl/auto_call.h>
8 #include <fbl/unique_ptr.h>
9 #include <hw/reg.h>
10 #include <string.h>
11 #include <threads.h>
12 #include <zircon/device/thermal.h>
13 #include <zircon/syscalls/port.h>
14
15 #include <utility>
16
17 namespace thermal {
18
SetTarget(uint32_t opp_idx)19 zx_status_t AmlThermal::SetTarget(uint32_t opp_idx) {
20 if (opp_idx >= MAX_TRIP_POINTS) {
21 return ZX_ERR_INVALID_ARGS;
22 }
23
24 // Get current settings.
25 uint32_t old_voltage = voltage_regulator_->GetVoltage();
26 uint32_t old_frequency = cpufreq_scaling_->GetFrequency();
27
28 // Get new settings.
29 uint32_t new_voltage = opp_info_.opps[opp_idx].volt_mv;
30 uint32_t new_frequency = opp_info_.opps[opp_idx].freq_hz;
31
32 zxlogf(INFO, "Scaling from %d MHz, %u mV, --> %d MHz, %u mV\n",
33 old_frequency / 1000000, old_voltage / 1000,
34 new_frequency / 1000000, new_voltage / 1000);
35
36 // If new settings are same as old, don't do anything.
37 if (new_frequency == old_frequency) {
38 return ZX_OK;
39 }
40
41 zx_status_t status;
42 // Increasing CPU Frequency from current value, so we first change the voltage.
43 if (new_frequency > old_frequency) {
44 status = voltage_regulator_->SetVoltage(new_voltage);
45 if (status != ZX_OK) {
46 zxlogf(ERROR, "aml-thermal: Could not change CPU voltage: %d\n", status);
47 return status;
48 }
49 }
50
51 // Now let's change CPU frequency.
52 status = cpufreq_scaling_->SetFrequency(new_frequency);
53 if (status != ZX_OK) {
54 zxlogf(ERROR, "aml-thermal: Could not change CPU frequemcy: %d\n", status);
55 // Failed to change CPU frequemcy, change back to old
56 // voltage before returning.
57 status = voltage_regulator_->SetVoltage(old_voltage);
58 if (status != ZX_OK) {
59 return status;
60 }
61 return status;
62 }
63
64 // Decreasing CPU Frequency from current value, changing voltage after frequency.
65 if (new_frequency < old_frequency) {
66 status = voltage_regulator_->SetVoltage(new_voltage);
67 if (status != ZX_OK) {
68 zxlogf(ERROR, "aml-thermal: Could not change CPU voltage: %d\n", status);
69 return status;
70 }
71 }
72
73 return ZX_OK;
74 }
75
Create(zx_device_t * device)76 zx_status_t AmlThermal::Create(zx_device_t* device) {
77 // Get the voltage-table & opp metadata.
78 size_t actual;
79 opp_info_t opp_info;
80 zx_status_t status = device_get_metadata(device, VOLTAGE_DUTY_CYCLE_METADATA, &opp_info,
81 sizeof(opp_info_), &actual);
82 if (status != ZX_OK || actual != sizeof(opp_info_)) {
83 zxlogf(ERROR, "aml-thermal: Could not get voltage-table metadata %d\n", status);
84 return status;
85 }
86
87 // Get the thermal policy metadata.
88 thermal_device_info_t thermal_config;
89 status = device_get_metadata(device, THERMAL_CONFIG_METADATA, &thermal_config,
90 sizeof(thermal_device_info_t), &actual);
91 if (status != ZX_OK || actual != sizeof(thermal_device_info_t)) {
92 zxlogf(ERROR, "aml-thermal: Could not get thermal config metadata %d\n", status);
93 return status;
94 }
95
96 fbl::AllocChecker ac;
97 auto tsensor = fbl::make_unique_checked<AmlTSensor>(&ac);
98 if (!ac.check()) {
99 return ZX_ERR_NO_MEMORY;
100 }
101
102 // Initialize Temperature Sensor.
103 status = tsensor->InitSensor(device, thermal_config);
104 if (status != ZX_OK) {
105 zxlogf(ERROR, "aml-thermal: Could not inititalize Temperature Sensor: %d\n", status);
106 return status;
107 }
108
109 // Create the voltage regulator.
110 auto voltage_regulator = fbl::make_unique_checked<AmlVoltageRegulator>(&ac);
111 if (!ac.check()) {
112 return ZX_ERR_NO_MEMORY;
113 }
114
115 // Initialize Temperature Sensor.
116 status = voltage_regulator->Init(device, &opp_info);
117 if (status != ZX_OK) {
118 zxlogf(ERROR, "aml-thermal: Could not inititalize Voltage Regulator: %d\n", status);
119 return status;
120 }
121
122 // Create the CPU frequency scaling object.
123 auto cpufreq_scaling = fbl::make_unique_checked<AmlCpuFrequency>(&ac);
124 if (!ac.check()) {
125 return ZX_ERR_NO_MEMORY;
126 }
127
128 // Initialize CPU frequency scaling.
129 status = cpufreq_scaling->Init(device);
130 if (status != ZX_OK) {
131 zxlogf(ERROR, "aml-thermal: Could not inititalize CPU freq. scaling: %d\n", status);
132 return status;
133 }
134
135 auto thermal_device = fbl::make_unique_checked<AmlThermal>(&ac, device,
136 std::move(tsensor),
137 std::move(voltage_regulator),
138 std::move(cpufreq_scaling),
139 std::move(opp_info),
140 std::move(thermal_config));
141 if (!ac.check()) {
142 return ZX_ERR_NO_MEMORY;
143 }
144
145 status = thermal_device->DdkAdd("thermal");
146 if (status != ZX_OK) {
147 zxlogf(ERROR, "aml-thermal: Could not create thermal device: %d\n", status);
148 return status;
149 }
150
151 // Set the default CPU frequency.
152 // We could be running Zircon only, or thermal daemon might not
153 // run, so we manually set the CPU frequency here.
154 uint32_t opp_idx = thermal_device->thermal_config_.trip_point_info[0].big_cluster_dvfs_opp;
155 status = thermal_device->SetTarget(opp_idx);
156 if (status != ZX_OK) {
157 return status;
158 }
159
160 // devmgr is now in charge of the memory for dev.
161 __UNUSED auto ptr = thermal_device.release();
162 return ZX_OK;
163 }
164
DdkIoctl(uint32_t op,const void * in_buf,size_t in_len,void * out_buf,size_t out_len,size_t * out_actual)165 zx_status_t AmlThermal::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len,
166 void* out_buf, size_t out_len, size_t* out_actual) {
167 switch (op) {
168 case IOCTL_THERMAL_GET_TEMPERATURE: {
169 if (out_len != sizeof(uint32_t)) {
170 return ZX_ERR_INVALID_ARGS;
171 }
172 auto temperature = static_cast<uint32_t*>(out_buf);
173 *temperature = tsensor_->ReadTemperature();
174 *out_actual = sizeof(uint32_t);
175 return ZX_OK;
176 }
177
178 case IOCTL_THERMAL_GET_DEVICE_INFO: {
179 if (out_len != sizeof(thermal_device_info_t)) {
180 return ZX_ERR_INVALID_ARGS;
181 }
182 memcpy(out_buf, &thermal_config_, sizeof(thermal_device_info_t));
183 *out_actual = sizeof(thermal_device_info_t);
184 return ZX_OK;
185 }
186
187 case IOCTL_THERMAL_SET_DVFS_OPP: {
188 if (in_len != sizeof(dvfs_info_t)) {
189 return ZX_ERR_INVALID_ARGS;
190 }
191 auto* dvfs_info = reinterpret_cast<const dvfs_info_t*>(in_buf);
192 if (dvfs_info->power_domain != BIG_CLUSTER_POWER_DOMAIN) {
193 return ZX_ERR_INVALID_ARGS;
194 }
195 return SetTarget(dvfs_info->op_idx);
196 }
197
198 case IOCTL_THERMAL_GET_STATE_CHANGE_PORT: {
199 if (out_len != sizeof(zx_handle_t)) {
200 return ZX_ERR_INVALID_ARGS;
201 }
202 auto* port = reinterpret_cast<zx_handle_t*>(out_buf);
203 *out_actual = sizeof(zx_handle_t);
204 return tsensor_->GetStateChangePort(port);
205 }
206
207 default:
208 return ZX_ERR_NOT_SUPPORTED;
209 }
210 }
211
DdkUnbind()212 void AmlThermal::DdkUnbind() {
213 DdkRemove();
214 }
215
DdkRelease()216 void AmlThermal::DdkRelease() {
217 delete this;
218 }
219
220 } // namespace thermal
221
aml_thermal(void * ctx,zx_device_t * device)222 extern "C" zx_status_t aml_thermal(void* ctx, zx_device_t* device) {
223 return thermal::AmlThermal::Create(device);
224 }
225