|  | // 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 <stdint.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <threads.h> | 
|  | #include <unistd.h> | 
|  | #include <ddk/protocol/scpi.h> | 
|  | #include <ddk/binding.h> | 
|  | #include <ddk/debug.h> | 
|  | #include <ddk/device.h> | 
|  | #include <ddk/driver.h> | 
|  | #include <ddk/protocol/platform-defs.h> | 
|  | #include <ddk/protocol/platform-device.h> | 
|  | #include "aml-scpi.h" | 
|  | #include "aml-mailbox.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_buf          = 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_buf  = rx_buf; | 
|  | channel.rx_size = rx_size; | 
|  |  | 
|  | status = mailbox_send_cmd(&scpi->mailbox, &channel, &mdata); | 
|  | if (rx_buf) { | 
|  | mailbox_status = *(uint32_t*)(rx_buf); | 
|  | } | 
|  | if (status != ZX_OK || mailbox_status != 0) { | 
|  | SCPI_ERROR("mailbox_send_cmd 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; | 
|  | } | 
|  |  | 
|  | status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &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; | 
|  | } | 
|  |  | 
|  | device_add_args_t args = { | 
|  | .version = DEVICE_ADD_ARGS_VERSION, | 
|  | .name = "aml-scpi", | 
|  | .ctx = scpi, | 
|  | .ops = &aml_scpi_device_protocol, | 
|  | .flags = DEVICE_ADD_NON_BINDABLE, | 
|  | }; | 
|  |  | 
|  | status = device_add(parent, &args, NULL); | 
|  | if (status != ZX_OK) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | scpi->scpi.ops = &scpi_ops; | 
|  | scpi->scpi.ctx = scpi; | 
|  |  | 
|  | mtx_init(&scpi->lock, mtx_plain); | 
|  | platform_bus_protocol_t pbus; | 
|  | if ((status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_BUS, &pbus)) != ZX_OK) { | 
|  | SCPI_ERROR("ZX_PROTOCOL_PLATFORM_BUS not available %d \n",status); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | pbus_set_protocol(&pbus, ZX_PROTOCOL_SCPI, &scpi->scpi); | 
|  | return ZX_OK; | 
|  | fail: | 
|  | aml_scpi_release(scpi); | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | 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_PLATFORM_DEV), | 
|  | BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_KHADAS), | 
|  | BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_VIM2), | 
|  | BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_SCPI), | 
|  | ZIRCON_DRIVER_END(aml_scpi) |