| // 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 "aml-mailbox.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/platform/device.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/device/thermal.h> |
| |
| static scpi_opp_t* scpi_opp[MAX_DVFS_DOMAINS]; |
| |
| static zx_status_t aml_scpi_get_mailbox(uint32_t cmd, |
| uint32_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 = 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 = 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 = AP_SECURE_MAILBOX; |
| return ZX_OK; |
| } |
| } |
| *mailbox = INVALID_MAILBOX; |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| static zx_status_t aml_scpi_execute_cmd(aml_scpi_t* scpi, |
| 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 = aml_scpi_get_mailbox(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_send_command(&scpi->mailbox, &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; |
| } |
| |
| static zx_status_t aml_scpi_get_dvfs_info(void* ctx, uint8_t power_domain, scpi_opp_t* opps) { |
| aml_scpi_t* scpi = ctx; |
| 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 (!opps || power_domain >= MAX_DVFS_DOMAINS) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| mtx_lock(&scpi->lock); |
| |
| // dvfs info already populated |
| if (scpi_opp[power_domain]) { |
| memcpy(opps, scpi_opp[power_domain], sizeof(scpi_opp_t)); |
| mtx_unlock(&scpi->lock); |
| return ZX_OK; |
| } |
| |
| status = aml_scpi_execute_cmd(scpi, |
| &aml_dvfs_info, sizeof(aml_dvfs_info), |
| &power_domain, sizeof(power_domain), |
| SCPI_CMD_GET_DVFS_INFO, SCPI_CL_DVFS); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| opps->count = aml_dvfs_info.operating_points; |
| opps->latency = aml_dvfs_info.latency; |
| |
| if (opps->count > MAX_DVFS_OPPS) { |
| SCPI_ERROR("Number of operating_points greater than MAX_DVFS_OPPS\n"); |
| status = ZX_ERR_INVALID_ARGS; |
| goto fail; |
| } |
| |
| 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 < opps->count; i++) { |
| opps->opp[i].freq_hz = aml_dvfs_info.opp[i].freq_hz; |
| opps->opp[i].volt_mv = aml_dvfs_info.opp[i].volt_mv; |
| zxlogf(INFO, "Operating point %d - ", i); |
| zxlogf(INFO, "Freq %.4f Ghz ", (opps->opp[i].freq_hz) / (double)1000000000); |
| zxlogf(INFO, "Voltage %.4f V\n", (opps->opp[i].volt_mv) / (double)1000); |
| } |
| |
| scpi_opp[power_domain] = calloc(1, sizeof(scpi_opp_t)); |
| if (!scpi_opp[power_domain]) { |
| status = ZX_ERR_NO_MEMORY; |
| goto fail; |
| } |
| |
| memcpy(scpi_opp[power_domain], opps, sizeof(scpi_opp_t)); |
| status = ZX_OK; |
| |
| fail: |
| mtx_unlock(&scpi->lock); |
| return status; |
| } |
| |
| static zx_status_t aml_scpi_get_dvfs_idx(void* ctx, uint8_t power_domain, uint16_t* idx) { |
| aml_scpi_t* scpi = ctx; |
| 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 = aml_scpi_execute_cmd(scpi, |
| &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_ERROR("Current Operation point %x\n", aml_dvfs_idx_info.idx); |
| return ZX_OK; |
| } |
| |
| static zx_status_t aml_scpi_set_dvfs_idx(void* ctx, uint8_t power_domain, uint16_t idx) { |
| aml_scpi_t* scpi = ctx; |
| 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 aml_scpi_execute_cmd(scpi, |
| NULL, 0, |
| &aml_dvfs_idx_info, sizeof(aml_dvfs_idx_info), |
| SCPI_CMD_SET_DVFS, SCPI_CL_DVFS); |
| } |
| |
| static zx_status_t aml_scpi_get_sensor_value(void* ctx, uint32_t sensor_id, |
| uint32_t* sensor_value) { |
| aml_scpi_t* scpi = ctx; |
| struct { |
| uint32_t status; |
| uint16_t sensor_value; |
| } __PACKED aml_sensor_val; |
| |
| if (!sensor_value) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_status_t status = aml_scpi_execute_cmd(scpi, |
| &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; |
| } |
| |
| static zx_status_t aml_scpi_get_sensor(void* ctx, const char* name, |
| uint32_t* sensor_value) { |
| aml_scpi_t* scpi = ctx; |
| struct { |
| uint32_t status; |
| uint16_t num_sensors; |
| } __PACKED aml_sensor_cap; |
| struct { |
| uint32_t status; |
| uint16_t sensor; |
| uint8_t 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 = aml_scpi_execute_cmd(scpi, |
| &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 = aml_scpi_execute_cmd(scpi, |
| &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; |
| } |
| |
| static void aml_scpi_release(void* ctx) { |
| aml_scpi_t* scpi = ctx; |
| free(scpi); |
| } |
| |
| static zx_protocol_device_t aml_scpi_device_protocol = { |
| .version = DEVICE_OPS_VERSION, |
| .release = aml_scpi_release, |
| }; |
| |
| static scpi_protocol_ops_t scpi_ops = { |
| .get_sensor = aml_scpi_get_sensor, |
| .get_sensor_value = aml_scpi_get_sensor_value, |
| .get_dvfs_info = aml_scpi_get_dvfs_info, |
| .get_dvfs_idx = aml_scpi_get_dvfs_idx, |
| .set_dvfs_idx = aml_scpi_set_dvfs_idx, |
| }; |
| |
| static zx_status_t aml_scpi_bind(void* ctx, zx_device_t* parent) { |
| zx_status_t status = ZX_OK; |
| |
| aml_scpi_t* scpi = calloc(1, sizeof(aml_scpi_t)); |
| if (!scpi) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| mtx_init(&scpi->lock, mtx_plain); |
| |
| status = device_get_protocol(parent, ZX_PROTOCOL_PDEV, &scpi->pdev); |
| if (status != ZX_OK) { |
| SCPI_ERROR("Could not get parent protocol\n"); |
| goto fail; |
| } |
| status = device_get_protocol(parent, ZX_PROTOCOL_MAILBOX, &scpi->mailbox); |
| if (status != ZX_OK) { |
| SCPI_ERROR("Could not get Mailbox protocol\n"); |
| goto fail; |
| } |
| |
| 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 = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "aml-scpi", |
| .ctx = scpi, |
| .ops = &aml_scpi_device_protocol, |
| .proto_id = ZX_PROTOCOL_SCPI, |
| .proto_ops = &scpi_ops, |
| .props = props, |
| .prop_count = countof(props), |
| }; |
| |
| status = pdev_device_add(&scpi->pdev, 0, &args, NULL); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| return ZX_OK; |
| fail: |
| aml_scpi_release(scpi); |
| |
| ZX_ASSERT(status != ZX_OK); |
| return status; |
| } |
| |
| static zx_driver_ops_t aml_scpi_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = aml_scpi_bind, |
| }; |
| |
| ZIRCON_DRIVER_BEGIN(aml_scpi, aml_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) |