// Copyright 2018 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aml-scpi.h" namespace scpi { zx_status_t AmlSCPI::GetMailbox(uint32_t cmd, mailbox_type_t* mailbox) { if (!mailbox || !VALID_CMD(cmd)) { return ZX_ERR_INVALID_ARGS; } for (uint32_t i = 0; i < countof(aml_low_priority_cmds); i++) { if (cmd == aml_low_priority_cmds[i]) { *mailbox = MAILBOX_TYPE_AP_NS_LOW_PRIORITY_MAILBOX; return ZX_OK; } } for (uint32_t i = 0; i < countof(aml_high_priority_cmds); i++) { if (cmd == aml_high_priority_cmds[i]) { *mailbox = MAILBOX_TYPE_AP_NS_HIGH_PRIORITY_MAILBOX; return ZX_OK; } } for (uint32_t i = 0; i < countof(aml_secure_cmds); i++) { if (cmd == aml_secure_cmds[i]) { *mailbox = MAILBOX_TYPE_AP_SECURE_MAILBOX; return ZX_OK; } } *mailbox = MAILBOX_TYPE_INVALID_MAILBOX; return ZX_ERR_NOT_FOUND; } zx_status_t AmlSCPI::ExecuteCommand(void* rx_buf, size_t rx_size, void* tx_buf, size_t tx_size, uint32_t cmd, uint32_t client_id) { uint32_t mailbox_status = 0; mailbox_data_buf_t mdata; mdata.cmd = PACK_SCPI_CMD(cmd, client_id, 0); mdata.tx_buffer = tx_buf; mdata.tx_size = tx_size; mailbox_channel_t channel; zx_status_t status = GetMailbox(cmd, &channel.mailbox); if (status != ZX_OK) { SCPI_ERROR("aml_scpi_get_mailbox failed - error status %d\n", status); return status; } channel.rx_buffer = rx_buf; channel.rx_size = rx_size; status = mailbox_.SendCommand(&channel, &mdata); if (rx_buf) { mailbox_status = *(uint32_t*)(rx_buf); } if (status != ZX_OK || mailbox_status != 0) { SCPI_ERROR("mailbox_send_command failed - error status %d\n", status); return status; } return ZX_OK; } zx_status_t AmlSCPI::ScpiGetDvfsIdx(uint8_t power_domain, uint16_t* idx) { struct { uint32_t status; uint8_t idx; } __PACKED aml_dvfs_idx_info; if (!idx || power_domain >= MAX_DVFS_DOMAINS) { return ZX_ERR_INVALID_ARGS; } zx_status_t status = ExecuteCommand(&aml_dvfs_idx_info, sizeof(aml_dvfs_idx_info), &power_domain, sizeof(power_domain), SCPI_CMD_GET_DVFS, SCPI_CL_DVFS); if (status != ZX_OK) { return status; } *idx = aml_dvfs_idx_info.idx; SCPI_INFO("Current Operation point %x\n", aml_dvfs_idx_info.idx); return ZX_OK; } zx_status_t AmlSCPI::ScpiSetDvfsIdx(uint8_t power_domain, uint16_t idx) { struct { uint8_t power_domain; uint16_t idx; } __PACKED aml_dvfs_idx_info; if (power_domain >= MAX_DVFS_DOMAINS) { return ZX_ERR_INVALID_ARGS; } aml_dvfs_idx_info.power_domain = power_domain; aml_dvfs_idx_info.idx = idx; SCPI_INFO("OPP index for cluster %d to %d\n", power_domain, idx); return ExecuteCommand(NULL, 0, &aml_dvfs_idx_info, sizeof(aml_dvfs_idx_info), SCPI_CMD_SET_DVFS, SCPI_CL_DVFS); } zx_status_t AmlSCPI::ScpiGetDvfsInfo(uint8_t power_domain, scpi_opp_t* out_opps) { zx_status_t status; struct { uint32_t status; uint8_t reserved; uint8_t operating_points; uint16_t latency; scpi_opp_entry_t opp[MAX_DVFS_OPPS]; } __PACKED aml_dvfs_info; if (!out_opps || power_domain >= MAX_DVFS_DOMAINS) { return ZX_ERR_INVALID_ARGS; } fbl::AutoLock mailbox_lock(&lock_); // dvfs info already populated if (scpi_opp[power_domain]) { memcpy(out_opps, scpi_opp[power_domain], sizeof(scpi_opp_t)); return ZX_OK; } status = ExecuteCommand(&aml_dvfs_info, sizeof(aml_dvfs_info), &power_domain, sizeof(power_domain), SCPI_CMD_GET_DVFS_INFO, SCPI_CL_DVFS); if (status != ZX_OK) { return status; } out_opps->count = aml_dvfs_info.operating_points; out_opps->latency = aml_dvfs_info.latency; if (out_opps->count > MAX_DVFS_OPPS) { SCPI_ERROR("Number of operating_points greater than MAX_DVFS_OPPS\n"); status = ZX_ERR_INVALID_ARGS; return status; } zxlogf(INFO, "Cluster %u details\n", power_domain); zxlogf(INFO, "Number of operating_points %u\n", aml_dvfs_info.operating_points); zxlogf(INFO, "latency %u uS\n", aml_dvfs_info.latency); for (uint32_t i = 0; i < out_opps->count; i++) { out_opps->opp[i].freq_hz = aml_dvfs_info.opp[i].freq_hz; out_opps->opp[i].volt_mv = aml_dvfs_info.opp[i].volt_mv; zxlogf(INFO, "Operating point %d - ", i); zxlogf(INFO, "Freq %.4f Ghz ", (out_opps->opp[i].freq_hz) / (double)1000000000); zxlogf(INFO, "Voltage %.4f V\n", (out_opps->opp[i].volt_mv) / (double)1000); } scpi_opp[power_domain] = static_cast(calloc(1, sizeof(scpi_opp_t))); if (!scpi_opp[power_domain]) { status = ZX_ERR_NO_MEMORY; return status; } memcpy(scpi_opp[power_domain], out_opps, sizeof(scpi_opp_t)); return ZX_OK; } zx_status_t AmlSCPI::ScpiGetSensorValue(uint32_t sensor_id, uint32_t* sensor_value) { struct { uint32_t status; uint16_t sensor_value; } __PACKED aml_sensor_val; if (!sensor_value) { return ZX_ERR_INVALID_ARGS; } zx_status_t status = ExecuteCommand(&aml_sensor_val, sizeof(aml_sensor_val), &sensor_id, sizeof(sensor_id), SCPI_CMD_SENSOR_VALUE, SCPI_CL_THERMAL); if (status != ZX_OK) { return status; } *sensor_value = aml_sensor_val.sensor_value; return ZX_OK; } zx_status_t AmlSCPI::ScpiGetSensor(const char* name, uint32_t* sensor_value) { struct { uint32_t status; uint16_t num_sensors; } __PACKED aml_sensor_cap; struct { uint32_t status; uint16_t sensor; uint8_t sensor_class; uint8_t trigger; char sensor_name[20]; } __PACKED aml_sensor_info; if (sensor_value == NULL) { return ZX_ERR_INVALID_ARGS; } // First let's find information about all sensors zx_status_t status = ExecuteCommand(&aml_sensor_cap, sizeof(aml_sensor_cap), NULL, 0, SCPI_CMD_SENSOR_CAPABILITIES, SCPI_CL_THERMAL); if (status != ZX_OK) { return status; } // Loop through all the sensors for (uint32_t sensor_id = 0; sensor_id < aml_sensor_cap.num_sensors; sensor_id++) { status = ExecuteCommand(&aml_sensor_info, sizeof(aml_sensor_info), &sensor_id, sizeof(sensor_id), SCPI_CMD_SENSOR_INFO, SCPI_CL_THERMAL); if (status != ZX_OK) { return status; } if (!strncmp(name, aml_sensor_info.sensor_name, sizeof(aml_sensor_info.sensor_name))) { *sensor_value = sensor_id; break; } } return ZX_OK; } void AmlSCPI::DdkUnbind() { DdkRemove(); } void AmlSCPI::DdkRelease() { delete this; } zx_status_t AmlSCPI::Bind() { zx_device_prop_t props[] = { {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_AMLOGIC}, {BIND_PLATFORM_DEV_PID, 0, PDEV_PID_AMLOGIC_S912}, {BIND_PLATFORM_DEV_DID, 0, PDEV_DID_AMLOGIC_THERMAL}, }; device_add_args_t args = {}; args.version = DEVICE_ADD_ARGS_VERSION; args.name = "aml-scpi"; args.ctx = this; args.ops = &ddk_device_proto_; args.proto_id = ddk_proto_id_; args.proto_ops = ddk_proto_ops_; args.props = props; args.prop_count = countof(props); return pdev_.DeviceAdd(0, &args, &zxdev_); } zx_status_t AmlSCPI::Create(zx_device_t* parent) { fbl::AllocChecker ac; auto scpi_device = fbl::make_unique_checked(&ac, parent); if (!ac.check()) { return ZX_ERR_NO_MEMORY; } zx_status_t status = ZX_ERR_INTERNAL; // Get ZX_PROTOCOL_MAILBOX protocol. if (!scpi_device->mailbox_.is_valid()) { zxlogf(ERROR, "dwmac: could not obtain ZX_PROTOCOL_MAILBOX protocol: %d\n", status); return status; } mtx_init(&scpi_device->lock_, mtx_plain); status = scpi_device->Bind(); if (status != ZX_OK) { zxlogf(ERROR, "aml-scpi driver failed to get added: %d\n", status); return status; } else { zxlogf(INFO, "aml-scpi driver added\n"); } // scpi_device intentionally leaked as it is now held by DevMgr __UNUSED auto ptr = scpi_device.release(); return ZX_OK; } zx_status_t aml_scpi_bind(void* ctx, zx_device_t* parent) { return scpi::AmlSCPI::Create(parent); } static zx_driver_ops_t driver_ops = []() { zx_driver_ops_t ops; ops.version = DRIVER_OPS_VERSION; ops.bind = aml_scpi_bind; return ops; }(); } // namespace scpi // clang-format off ZIRCON_DRIVER_BEGIN(aml_scpi, scpi::driver_ops, "zircon", "0.1", 4) BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_MAILBOX), BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC), BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_S912), BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_SCPI), ZIRCON_DRIVER_END(aml_scpi)