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