blob: 3d43e7524243e5c25fef288ff92190daa49277aa [file] [log] [blame]
// 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/protocol/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_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;
}
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);
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_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)