| // 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 "aml-scpi.h" |
| |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/platform-defs.h> |
| #include <ddk/protocol/mailbox.h> |
| #include <ddk/protocol/platform/device.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.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; |
| uint8_t padding[3]; |
| } __PACKED aml_dvfs_idx_info; |
| |
| if (!idx || power_domain >= fuchsia_hardware_thermal_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; |
| uint8_t padding; |
| } __PACKED aml_dvfs_idx_info; |
| |
| if (power_domain >= fuchsia_hardware_thermal_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[fuchsia_hardware_thermal_MAX_DVFS_OPPS]; |
| } __PACKED aml_dvfs_info; |
| |
| if (!out_opps || power_domain >= fuchsia_hardware_thermal_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; |
| } |
| |
| uint32_t power_domain_scpi = 0; |
| status = ExecuteCommand(&aml_dvfs_info, sizeof(aml_dvfs_info), &power_domain_scpi, |
| sizeof(power_domain_scpi), SCPI_CMD_GET_DVFS_INFO, SCPI_CL_DVFS); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| ZX_DEBUG_ASSERT(power_domain_scpi <= UINT8_MAX); |
| power_domain = static_cast<uint8_t>(power_domain_scpi); |
| |
| out_opps->count = aml_dvfs_info.operating_points; |
| out_opps->latency = aml_dvfs_info.latency; |
| |
| if (out_opps->count > fuchsia_hardware_thermal_MAX_DVFS_OPPS) { |
| SCPI_ERROR("Number of operating_points greater than fuchsia_hardware_thermal_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_uv = aml_dvfs_info.opp[i].volt_uv; |
| 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_uv) / (double)1000); |
| } |
| |
| scpi_opp[power_domain] = static_cast<scpi_opp_t*>(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; |
| uint16_t padding; |
| } __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; |
| uint16_t padding; |
| } __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::DdkUnbindNew(ddk::UnbindTxn txn) { txn.Reply(); } |
| |
| 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_DID, 0, PDEV_DID_AMLOGIC_SCPI}, |
| }; |
| |
| return DdkAdd("aml-scpi", 0, props, fbl::count_of(props)); |
| } |
| |
| zx_status_t AmlSCPI::Create(zx_device_t* parent) { |
| fbl::AllocChecker ac; |
| auto scpi_device = fbl::make_unique_checked<AmlSCPI>(&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 constexpr 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", 2) |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_MAILBOX), |
| ZIRCON_DRIVER_END(aml_scpi) |