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 <ddk/binding.h>
6 #include <ddk/debug.h>
7 #include <ddk/device.h>
8 #include <ddk/driver.h>
9 #include <ddk/platform-defs.h>
10 #include <ddk/protocol/mailbox.h>
11 #include <ddk/protocol/platform/device.h>
12 #include <fbl/auto_lock.h>
13 #include <fbl/unique_ptr.h>
14 #include <stdint.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19 #include <zircon/assert.h>
20 #include <zircon/device/thermal.h>
21
22 #include "aml-scpi.h"
23
24 namespace scpi {
25
GetMailbox(uint32_t cmd,mailbox_type_t * mailbox)26 zx_status_t AmlSCPI::GetMailbox(uint32_t cmd,
27 mailbox_type_t* mailbox) {
28 if (!mailbox || !VALID_CMD(cmd)) {
29 return ZX_ERR_INVALID_ARGS;
30 }
31
32 for (uint32_t i = 0; i < countof(aml_low_priority_cmds); i++) {
33 if (cmd == aml_low_priority_cmds[i]) {
34 *mailbox = MAILBOX_TYPE_AP_NS_LOW_PRIORITY_MAILBOX;
35 return ZX_OK;
36 }
37 }
38
39 for (uint32_t i = 0; i < countof(aml_high_priority_cmds); i++) {
40 if (cmd == aml_high_priority_cmds[i]) {
41 *mailbox = MAILBOX_TYPE_AP_NS_HIGH_PRIORITY_MAILBOX;
42 return ZX_OK;
43 }
44 }
45
46 for (uint32_t i = 0; i < countof(aml_secure_cmds); i++) {
47 if (cmd == aml_secure_cmds[i]) {
48 *mailbox = MAILBOX_TYPE_AP_SECURE_MAILBOX;
49 return ZX_OK;
50 }
51 }
52 *mailbox = MAILBOX_TYPE_INVALID_MAILBOX;
53 return ZX_ERR_NOT_FOUND;
54 }
55
ExecuteCommand(void * rx_buf,size_t rx_size,void * tx_buf,size_t tx_size,uint32_t cmd,uint32_t client_id)56 zx_status_t AmlSCPI::ExecuteCommand(void* rx_buf, size_t rx_size,
57 void* tx_buf, size_t tx_size,
58 uint32_t cmd, uint32_t client_id) {
59 uint32_t mailbox_status = 0;
60 mailbox_data_buf_t mdata;
61 mdata.cmd = PACK_SCPI_CMD(cmd, client_id, 0);
62 mdata.tx_buffer = tx_buf;
63 mdata.tx_size = tx_size;
64
65 mailbox_channel_t channel;
66 zx_status_t status = GetMailbox(cmd, &channel.mailbox);
67 if (status != ZX_OK) {
68 SCPI_ERROR("aml_scpi_get_mailbox failed - error status %d\n", status);
69 return status;
70 }
71
72 channel.rx_buffer = rx_buf;
73 channel.rx_size = rx_size;
74
75 status = mailbox_.SendCommand(&channel, &mdata);
76 if (rx_buf) {
77 mailbox_status = *(uint32_t*)(rx_buf);
78 }
79 if (status != ZX_OK || mailbox_status != 0) {
80 SCPI_ERROR("mailbox_send_command failed - error status %d\n", status);
81 return status;
82 }
83 return ZX_OK;
84 }
85
ScpiGetDvfsIdx(uint8_t power_domain,uint16_t * idx)86 zx_status_t AmlSCPI::ScpiGetDvfsIdx(uint8_t power_domain, uint16_t* idx) {
87 struct {
88 uint32_t status;
89 uint8_t idx;
90 } __PACKED aml_dvfs_idx_info;
91
92 if (!idx || power_domain >= MAX_DVFS_DOMAINS) {
93 return ZX_ERR_INVALID_ARGS;
94 }
95
96 zx_status_t status = ExecuteCommand(&aml_dvfs_idx_info, sizeof(aml_dvfs_idx_info),
97 &power_domain, sizeof(power_domain),
98 SCPI_CMD_GET_DVFS, SCPI_CL_DVFS);
99 if (status != ZX_OK) {
100 return status;
101 }
102
103 *idx = aml_dvfs_idx_info.idx;
104
105 SCPI_INFO("Current Operation point %x\n", aml_dvfs_idx_info.idx);
106 return ZX_OK;
107 }
108
ScpiSetDvfsIdx(uint8_t power_domain,uint16_t idx)109 zx_status_t AmlSCPI::ScpiSetDvfsIdx(uint8_t power_domain, uint16_t idx) {
110 struct {
111 uint8_t power_domain;
112 uint16_t idx;
113 } __PACKED aml_dvfs_idx_info;
114
115 if (power_domain >= MAX_DVFS_DOMAINS) {
116 return ZX_ERR_INVALID_ARGS;
117 }
118
119 aml_dvfs_idx_info.power_domain = power_domain;
120 aml_dvfs_idx_info.idx = idx;
121
122 SCPI_INFO("OPP index for cluster %d to %d\n", power_domain, idx);
123 return ExecuteCommand(NULL, 0,
124 &aml_dvfs_idx_info, sizeof(aml_dvfs_idx_info),
125 SCPI_CMD_SET_DVFS, SCPI_CL_DVFS);
126 }
127
ScpiGetDvfsInfo(uint8_t power_domain,scpi_opp_t * out_opps)128 zx_status_t AmlSCPI::ScpiGetDvfsInfo(uint8_t power_domain, scpi_opp_t* out_opps) {
129 zx_status_t status;
130 struct {
131 uint32_t status;
132 uint8_t reserved;
133 uint8_t operating_points;
134 uint16_t latency;
135 scpi_opp_entry_t opp[MAX_DVFS_OPPS];
136 } __PACKED aml_dvfs_info;
137
138 if (!out_opps || power_domain >= MAX_DVFS_DOMAINS) {
139 return ZX_ERR_INVALID_ARGS;
140 }
141
142 fbl::AutoLock mailbox_lock(&lock_);
143
144 // dvfs info already populated
145 if (scpi_opp[power_domain]) {
146 memcpy(out_opps, scpi_opp[power_domain], sizeof(scpi_opp_t));
147 return ZX_OK;
148 }
149
150 status = ExecuteCommand(&aml_dvfs_info, sizeof(aml_dvfs_info),
151 &power_domain, sizeof(power_domain),
152 SCPI_CMD_GET_DVFS_INFO, SCPI_CL_DVFS);
153 if (status != ZX_OK) {
154 return status;
155 }
156
157 out_opps->count = aml_dvfs_info.operating_points;
158 out_opps->latency = aml_dvfs_info.latency;
159
160 if (out_opps->count > MAX_DVFS_OPPS) {
161 SCPI_ERROR("Number of operating_points greater than MAX_DVFS_OPPS\n");
162 status = ZX_ERR_INVALID_ARGS;
163 return status;
164 }
165
166 zxlogf(INFO, "Cluster %u details\n", power_domain);
167 zxlogf(INFO, "Number of operating_points %u\n", aml_dvfs_info.operating_points);
168 zxlogf(INFO, "latency %u uS\n", aml_dvfs_info.latency);
169
170 for (uint32_t i = 0; i < out_opps->count; i++) {
171 out_opps->opp[i].freq_hz = aml_dvfs_info.opp[i].freq_hz;
172 out_opps->opp[i].volt_mv = aml_dvfs_info.opp[i].volt_mv;
173 zxlogf(INFO, "Operating point %d - ", i);
174 zxlogf(INFO, "Freq %.4f Ghz ", (out_opps->opp[i].freq_hz) / (double)1000000000);
175 zxlogf(INFO, "Voltage %.4f V\n", (out_opps->opp[i].volt_mv) / (double)1000);
176 }
177
178 scpi_opp[power_domain] = static_cast<scpi_opp_t*>(calloc(1, sizeof(scpi_opp_t)));
179 if (!scpi_opp[power_domain]) {
180 status = ZX_ERR_NO_MEMORY;
181 return status;
182 }
183
184 memcpy(scpi_opp[power_domain], out_opps, sizeof(scpi_opp_t));
185 return ZX_OK;
186 }
187
ScpiGetSensorValue(uint32_t sensor_id,uint32_t * sensor_value)188 zx_status_t AmlSCPI::ScpiGetSensorValue(uint32_t sensor_id, uint32_t* sensor_value) {
189 struct {
190 uint32_t status;
191 uint16_t sensor_value;
192 } __PACKED aml_sensor_val;
193
194 if (!sensor_value) {
195 return ZX_ERR_INVALID_ARGS;
196 }
197
198 zx_status_t status = ExecuteCommand(&aml_sensor_val, sizeof(aml_sensor_val),
199 &sensor_id, sizeof(sensor_id),
200 SCPI_CMD_SENSOR_VALUE, SCPI_CL_THERMAL);
201 if (status != ZX_OK) {
202 return status;
203 }
204 *sensor_value = aml_sensor_val.sensor_value;
205 return ZX_OK;
206 }
207
ScpiGetSensor(const char * name,uint32_t * sensor_value)208 zx_status_t AmlSCPI::ScpiGetSensor(const char* name, uint32_t* sensor_value) {
209 struct {
210 uint32_t status;
211 uint16_t num_sensors;
212 } __PACKED aml_sensor_cap;
213
214 struct {
215 uint32_t status;
216 uint16_t sensor;
217 uint8_t sensor_class;
218 uint8_t trigger;
219 char sensor_name[20];
220 } __PACKED aml_sensor_info;
221
222 if (sensor_value == NULL) {
223 return ZX_ERR_INVALID_ARGS;
224 }
225
226 // First let's find information about all sensors
227 zx_status_t status = ExecuteCommand(&aml_sensor_cap, sizeof(aml_sensor_cap),
228 NULL, 0,
229 SCPI_CMD_SENSOR_CAPABILITIES, SCPI_CL_THERMAL);
230 if (status != ZX_OK) {
231 return status;
232 }
233
234 // Loop through all the sensors
235 for (uint32_t sensor_id = 0; sensor_id < aml_sensor_cap.num_sensors; sensor_id++) {
236
237 status = ExecuteCommand(&aml_sensor_info, sizeof(aml_sensor_info),
238 &sensor_id, sizeof(sensor_id),
239 SCPI_CMD_SENSOR_INFO, SCPI_CL_THERMAL);
240 if (status != ZX_OK) {
241 return status;
242 }
243 if (!strncmp(name, aml_sensor_info.sensor_name, sizeof(aml_sensor_info.sensor_name))) {
244 *sensor_value = sensor_id;
245 break;
246 }
247 }
248 return ZX_OK;
249 }
250
DdkUnbind()251 void AmlSCPI::DdkUnbind() {
252 DdkRemove();
253 }
254
DdkRelease()255 void AmlSCPI::DdkRelease() {
256 delete this;
257 }
258
Bind()259 zx_status_t AmlSCPI::Bind() {
260 zx_device_prop_t props[] = {
261 {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_AMLOGIC},
262 {BIND_PLATFORM_DEV_PID, 0, PDEV_PID_AMLOGIC_S912},
263 {BIND_PLATFORM_DEV_DID, 0, PDEV_DID_AMLOGIC_THERMAL},
264 };
265
266 device_add_args_t args = {};
267 args.version = DEVICE_ADD_ARGS_VERSION;
268 args.name = "aml-scpi";
269 args.ctx = this;
270 args.ops = &ddk_device_proto_;
271 args.proto_id = ddk_proto_id_;
272 args.proto_ops = ddk_proto_ops_;
273 args.props = props;
274 args.prop_count = countof(props);
275
276 return pdev_.DeviceAdd(0, &args, &zxdev_);
277 }
278
Create(zx_device_t * parent)279 zx_status_t AmlSCPI::Create(zx_device_t* parent) {
280 fbl::AllocChecker ac;
281 auto scpi_device = fbl::make_unique_checked<AmlSCPI>(&ac, parent);
282 if (!ac.check()) {
283 return ZX_ERR_NO_MEMORY;
284 }
285
286 zx_status_t status = ZX_ERR_INTERNAL;
287 // Get ZX_PROTOCOL_MAILBOX protocol.
288 if (!scpi_device->mailbox_.is_valid()) {
289 zxlogf(ERROR, "dwmac: could not obtain ZX_PROTOCOL_MAILBOX protocol: %d\n", status);
290 return status;
291 }
292
293 mtx_init(&scpi_device->lock_, mtx_plain);
294
295 status = scpi_device->Bind();
296 if (status != ZX_OK) {
297 zxlogf(ERROR, "aml-scpi driver failed to get added: %d\n", status);
298 return status;
299 } else {
300 zxlogf(INFO, "aml-scpi driver added\n");
301 }
302
303 // scpi_device intentionally leaked as it is now held by DevMgr
304 __UNUSED auto ptr = scpi_device.release();
305
306 return ZX_OK;
307 }
308
aml_scpi_bind(void * ctx,zx_device_t * parent)309 zx_status_t aml_scpi_bind(void* ctx, zx_device_t* parent) {
310 return scpi::AmlSCPI::Create(parent);
311 }
312
__anone3c160330702() 313 static zx_driver_ops_t driver_ops = []() {
314 zx_driver_ops_t ops;
315 ops.version = DRIVER_OPS_VERSION;
316 ops.bind = aml_scpi_bind;
317 return ops;
318 }();
319
320 } // namespace scpi
321
322 // clang-format off
323 ZIRCON_DRIVER_BEGIN(aml_scpi, scpi::driver_ops, "zircon", "0.1", 4)
324 BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_MAILBOX),
325 BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC),
326 BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_S912),
327 BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_SCPI),
328 ZIRCON_DRIVER_END(aml_scpi)
329