blob: 6dacde977326ab8a428df751c05f4ab8f9f5d4e0 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* GXP debugfs support.
*
* Copyright (C) 2021 Google LLC
*/
#include <linux/acpm_dvfs.h>
#include <gcip/gcip-pm.h>
#include "gxp-client.h"
#include "gxp-core-telemetry.h"
#include "gxp-debug-dump.h"
#include "gxp-debugfs.h"
#include "gxp-dma.h"
#include "gxp-firmware-data.h"
#include "gxp-firmware-loader.h"
#include "gxp-firmware.h"
#include "gxp-internal.h"
#include "gxp-notification.h"
#include "gxp-lpm.h"
#include "gxp-mailbox.h"
#include "gxp-pm.h"
#include "gxp-vd.h"
#include "gxp.h"
#if GXP_HAS_MCU
#include "gxp-mcu-platform.h"
#endif
static int gxp_debugfs_lpm_test(void *data, u64 val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
dev_info(gxp->dev, "%llu\n", val);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(gxp_lpm_test_fops, NULL, gxp_debugfs_lpm_test,
"%llu\n");
static int gxp_debugfs_mailbox(void *data, u64 val)
{
int core = 0, retval;
u16 status;
struct gxp_dev *gxp = (struct gxp_dev *)data;
struct gxp_mailbox *mbx;
struct gxp_client *client;
struct gxp_power_states power_states = {
.power = GXP_POWER_STATE_NOM,
.memory = MEMORY_POWER_STATE_UNDEFINED,
};
u16 cmd_code;
int ret;
mutex_lock(&gxp->debugfs_client_lock);
client = gxp->debugfs_client;
#if GXP_HAS_MCU
if (gxp_is_direct_mode(gxp)) {
#endif
core = val / 1000;
if (core >= GXP_NUM_CORES) {
dev_notice(gxp->dev,
"Mailbox for core %d doesn't exist.\n",
core);
ret = -EINVAL;
goto out;
}
if (gxp->mailbox_mgr->mailboxes[core] == NULL) {
dev_notice(
gxp->dev,
"Unable to send mailbox command -- mailbox %d not ready\n",
core);
ret = -EINVAL;
goto out;
}
/* Create a dummy client to access @client->gxp from the `execute_cmd` callback. */
if (!client)
client = gxp_client_create(gxp);
mbx = gxp->mailbox_mgr->mailboxes[core];
cmd_code = GXP_MBOX_CODE_DISPATCH;
#if GXP_HAS_MCU
} else {
if (!client) {
dev_err(gxp->dev,
"You should load firmwares via gxp/firmware_run first\n");
ret = -EIO;
goto out;
}
down_read(&gxp->debugfs_client->semaphore);
if (!gxp_client_has_available_vd(gxp->debugfs_client,
"GXP_MAILBOX_COMMAND")) {
ret = -ENODEV;
up_read(&gxp->debugfs_client->semaphore);
goto out;
}
up_read(&gxp->debugfs_client->semaphore);
mbx = to_mcu_dev(gxp)->mcu.uci.mbx;
if (!mbx) {
dev_err(gxp->dev, "UCI is not initialized.\n");
ret = -EIO;
goto out;
}
cmd_code = CORE_COMMAND;
}
#endif
retval = gxp->mailbox_mgr->execute_cmd(client, mbx, core, cmd_code, 0,
0, 0, 0, 1, power_states, NULL,
&status);
dev_info(
gxp->dev,
"Mailbox Command Sent: core=%d, resp.status=%d, resp.retval=%d\n",
core, status, retval);
ret = 0;
out:
if (client && client != gxp->debugfs_client)
gxp_client_destroy(client);
mutex_unlock(&gxp->debugfs_client_lock);
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(gxp_mailbox_fops, NULL, gxp_debugfs_mailbox, "%llu\n");
static int gxp_firmware_run_set(void *data, u64 val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
struct gxp_client *client;
int ret = 0;
uint core;
bool acquired_block_wakelock;
ret = gxp_firmware_loader_load_if_needed(gxp);
if (ret) {
dev_err(gxp->dev, "Unable to load firmware files\n");
return ret;
}
mutex_lock(&gxp->debugfs_client_lock);
if (val) {
if (gxp->debugfs_client) {
dev_err(gxp->dev, "Firmware is already running!\n");
ret = -EIO;
goto out;
}
/*
* Since this debugfs node destroys, then creates new fw_data,
* and runs firmware on every DSP core, it cannot be run if
* any of the cores already has a VD running on it.
*/
down_write(&gxp->vd_semaphore);
for (core = 0; core < GXP_NUM_CORES; core++) {
if (gxp->core_to_vd[core]) {
dev_err(gxp->dev,
"Unable to run firmware with debugfs while other clients are running\n");
ret = -EBUSY;
up_write(&gxp->vd_semaphore);
goto out;
}
}
up_write(&gxp->vd_semaphore);
client = gxp_client_create(gxp);
if (IS_ERR(client)) {
dev_err(gxp->dev, "Failed to create client\n");
goto out;
}
gxp->debugfs_client = client;
mutex_lock(&gxp->client_list_lock);
list_add(&client->list_entry, &gxp->client_list);
mutex_unlock(&gxp->client_list_lock);
down_write(&client->semaphore);
ret = gxp_client_allocate_virtual_device(client, GXP_NUM_CORES,
0);
if (ret) {
dev_err(gxp->dev, "Failed to allocate VD\n");
goto err_destroy_client;
}
ret = gxp_client_acquire_block_wakelock(
client, &acquired_block_wakelock);
if (ret) {
dev_err(gxp->dev, "Failed to acquire BLOCK wakelock\n");
goto err_destroy_client;
}
ret = gxp_client_acquire_vd_wakelock(client, uud_states);
if (ret) {
dev_err(gxp->dev, "Failed to acquire VD wakelock\n");
goto err_release_block_wakelock;
}
up_write(&client->semaphore);
} else {
if (!gxp->debugfs_client) {
dev_err(gxp->dev, "Firmware is not running!\n");
ret = -EIO;
goto out;
}
/*
* Cleaning up the client will stop the VD it owns and release
* the BLOCK wakelock it is holding.
*/
goto out_destroy_client;
}
out:
mutex_unlock(&gxp->debugfs_client_lock);
return ret;
err_release_block_wakelock:
gxp_client_release_block_wakelock(client);
err_destroy_client:
up_write(&client->semaphore);
out_destroy_client:
mutex_lock(&gxp->client_list_lock);
list_del(&gxp->debugfs_client->list_entry);
mutex_unlock(&gxp->client_list_lock);
/* Destroying a client cleans up any VDss or wakelocks it held. */
gxp_client_destroy(gxp->debugfs_client);
gxp->debugfs_client = NULL;
mutex_unlock(&gxp->debugfs_client_lock);
return ret;
}
static int gxp_firmware_run_get(void *data, u64 *val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
down_read(&gxp->vd_semaphore);
*val = gxp->firmware_mgr->firmware_running;
up_read(&gxp->vd_semaphore);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(gxp_firmware_run_fops, gxp_firmware_run_get,
gxp_firmware_run_set, "%llx\n");
static int gxp_wakelock_set(void *data, u64 val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
int ret = 0;
mutex_lock(&gxp->debugfs_client_lock);
if (val > 0) {
/* Wakelock Acquire */
if (gxp->debugfs_wakelock_held) {
dev_warn(gxp->dev,
"Debugfs wakelock is already held.\n");
ret = -EBUSY;
goto out;
}
ret = gcip_pm_get(gxp->power_mgr->pm);
if (ret) {
dev_err(gxp->dev, "gcip_pm_get failed ret=%d\n", ret);
goto out;
}
gxp->debugfs_wakelock_held = true;
gxp_pm_update_requested_power_states(gxp, off_states,
uud_states);
} else {
/* Wakelock Release */
if (!gxp->debugfs_wakelock_held) {
dev_warn(gxp->dev, "Debugfs wakelock not held.\n");
ret = -EIO;
goto out;
}
gcip_pm_put(gxp->power_mgr->pm);
gxp->debugfs_wakelock_held = false;
gxp_pm_update_requested_power_states(gxp, uud_states,
off_states);
}
out:
mutex_unlock(&gxp->debugfs_client_lock);
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(gxp_wakelock_fops, NULL, gxp_wakelock_set, "%llx\n");
static int gxp_blk_powerstate_set(void *data, u64 val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
int ret = 0;
if (gxp_pm_get_blk_state(gxp) == AUR_OFF) {
dev_warn(
gxp->dev,
"Cannot set block power state when the block is off. Obtain a wakelock to power it on.\n");
return -ENODEV;
}
if (val >= AUR_DVFS_MIN_RATE) {
ret = gxp_pm_blk_set_rate_acpm(gxp, val);
} else {
ret = -EINVAL;
dev_err(gxp->dev, "Incorrect state %llu\n", val);
}
return ret;
}
static int gxp_blk_powerstate_get(void *data, u64 *val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
if (gxp_pm_get_blk_state(gxp) == AUR_OFF) {
dev_warn(
gxp->dev,
"Cannot get block power state when the block is off.\n");
return -ENODEV;
}
*val = gxp_pm_blk_get_state_acpm(gxp);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(gxp_blk_powerstate_fops, gxp_blk_powerstate_get,
gxp_blk_powerstate_set, "%llx\n");
static int gxp_debugfs_coredump(void *data, u64 val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
int core;
if (!gxp_debug_dump_is_enabled()) {
dev_err(gxp->dev, "Debug dump functionality is disabled\n");
return -EINVAL;
}
down_read(&gxp->vd_semaphore);
for (core = 0; core < GXP_NUM_CORES; core++) {
if (gxp_is_fw_running(gxp, core))
gxp_notification_send(gxp, core,
CORE_NOTIF_GENERATE_DEBUG_DUMP);
}
up_read(&gxp->vd_semaphore);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(gxp_coredump_fops, NULL, gxp_debugfs_coredump,
"%llu\n");
static int gxp_log_buff_set(void *data, u64 val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
int i;
struct gxp_coherent_buf *buffers;
u64 *ptr;
mutex_lock(&gxp->core_telemetry_mgr->lock);
if (!gxp->core_telemetry_mgr->logging_buff_data_legacy) {
dev_err(gxp->dev, "Logging buffer has not been created");
mutex_unlock(&gxp->core_telemetry_mgr->lock);
return -ENODEV;
}
buffers = gxp->core_telemetry_mgr->logging_buff_data_legacy->buffers;
for (i = 0; i < GXP_NUM_CORES; i++) {
ptr = buffers[i].vaddr;
*ptr = val;
}
mutex_unlock(&gxp->core_telemetry_mgr->lock);
return 0;
}
static int gxp_log_buff_get(void *data, u64 *val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
struct gxp_coherent_buf *buffers;
mutex_lock(&gxp->core_telemetry_mgr->lock);
if (!gxp->core_telemetry_mgr->logging_buff_data_legacy) {
dev_err(gxp->dev, "Logging buffer has not been created");
mutex_unlock(&gxp->core_telemetry_mgr->lock);
return -ENODEV;
}
buffers = gxp->core_telemetry_mgr->logging_buff_data_legacy->buffers;
*val = *(u64 *)(buffers[0].vaddr);
mutex_unlock(&gxp->core_telemetry_mgr->lock);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(gxp_log_buff_fops, gxp_log_buff_get, gxp_log_buff_set,
"%llu\n");
static int gxp_log_eventfd_signal_set(void *data, u64 val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
int ret = 0;
mutex_lock(&gxp->core_telemetry_mgr->lock);
if (!gxp->core_telemetry_mgr->logging_efd) {
ret = -ENODEV;
goto out;
}
ret = eventfd_signal(gxp->core_telemetry_mgr->logging_efd, 1);
out:
mutex_unlock(&gxp->core_telemetry_mgr->lock);
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(gxp_log_eventfd_signal_fops, NULL,
gxp_log_eventfd_signal_set, "%llu\n");
static int gxp_cmu_mux1_set(void *data, u64 val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
if (IS_ERR_OR_NULL(gxp->cmu.vaddr)) {
dev_err(gxp->dev, "CMU registers are not mapped");
return -ENODEV;
}
if (val > 1) {
dev_err(gxp->dev,
"Incorrect val for cmu_mux1, only 0 and 1 allowed\n");
return -EINVAL;
}
writel(val << 4, gxp->cmu.vaddr + PLL_CON0_PLL_AUR);
return 0;
}
static int gxp_cmu_mux1_get(void *data, u64 *val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
if (IS_ERR_OR_NULL(gxp->cmu.vaddr)) {
dev_err(gxp->dev, "CMU registers are not mapped");
return -ENODEV;
}
*val = readl(gxp->cmu.vaddr + PLL_CON0_PLL_AUR);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(gxp_cmu_mux1_fops, gxp_cmu_mux1_get, gxp_cmu_mux1_set,
"%llu\n");
static int gxp_cmu_mux2_set(void *data, u64 val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
if (IS_ERR_OR_NULL(gxp->cmu.vaddr)) {
dev_err(gxp->dev, "CMU registers are not mapped");
return -ENODEV;
}
if (val > 1) {
dev_err(gxp->dev,
"Incorrect val for cmu_mux2, only 0 and 1 allowed\n");
return -EINVAL;
}
writel(val << 4, gxp->cmu.vaddr + PLL_CON0_NOC_USER);
return 0;
}
static int gxp_cmu_mux2_get(void *data, u64 *val)
{
struct gxp_dev *gxp = (struct gxp_dev *)data;
if (IS_ERR_OR_NULL(gxp->cmu.vaddr)) {
dev_err(gxp->dev, "CMU registers are not mapped");
return -ENODEV;
}
*val = readl(gxp->cmu.vaddr + PLL_CON0_NOC_USER);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(gxp_cmu_mux2_fops, gxp_cmu_mux2_get, gxp_cmu_mux2_set,
"%llu\n");
void gxp_create_debugdir(struct gxp_dev *gxp)
{
gxp->d_entry = debugfs_create_dir(GXP_NAME, NULL);
if (IS_ERR_OR_NULL(gxp->d_entry)) {
dev_warn(gxp->dev, "Create debugfs dir failed: %d",
PTR_ERR_OR_ZERO(gxp->d_entry));
gxp->d_entry = NULL;
}
}
void gxp_create_debugfs(struct gxp_dev *gxp)
{
if (!gxp->d_entry)
return;
mutex_init(&gxp->debugfs_client_lock);
gxp->debugfs_wakelock_held = false;
debugfs_create_file("lpm_test", 0200, gxp->d_entry, gxp,
&gxp_lpm_test_fops);
debugfs_create_file("mailbox", 0200, gxp->d_entry, gxp,
&gxp_mailbox_fops);
debugfs_create_file("firmware_run", 0600, gxp->d_entry, gxp,
&gxp_firmware_run_fops);
debugfs_create_file("wakelock", 0200, gxp->d_entry, gxp,
&gxp_wakelock_fops);
debugfs_create_file("blk_powerstate", 0600, gxp->d_entry, gxp,
&gxp_blk_powerstate_fops);
debugfs_create_file("coredump", 0200, gxp->d_entry, gxp,
&gxp_coredump_fops);
debugfs_create_file("log", 0600, gxp->d_entry, gxp, &gxp_log_buff_fops);
debugfs_create_file("log_eventfd", 0200, gxp->d_entry, gxp,
&gxp_log_eventfd_signal_fops);
debugfs_create_file("cmumux1", 0600, gxp->d_entry, gxp,
&gxp_cmu_mux1_fops);
debugfs_create_file("cmumux2", 0600, gxp->d_entry, gxp,
&gxp_cmu_mux2_fops);
}
void gxp_remove_debugdir(struct gxp_dev *gxp)
{
if (!gxp->d_entry)
return;
debugfs_remove_recursive(gxp->d_entry);
/*
* Now that debugfs is torn down, and no other calls to
* `gxp_firmware_run_set()` can occur, destroy any client that may have
* been left running.
*/
if (gxp->debugfs_client)
gxp_client_destroy(gxp->debugfs_client);
}