Jetpack/kernel/nvidia/drivers/platform/tegra/nvadsp/os.c

2106 lines
51 KiB
C

/*
* os.c
*
* ADSP OS management
* Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
*
* Copyright (C) 2014-2020, NVIDIA Corporation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/dma-buf.h>
#include <linux/dma-mapping.h>
#include <linux/firmware.h>
#include <linux/tegra_nvadsp.h>
#include <soc/tegra/chip-id.h>
#include <linux/elf.h>
#include <linux/device.h>
#include <linux/clk.h>
#include <linux/clk/tegra.h>
#include <linux/irqchip/tegra-agic.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/tegra-firmwares.h>
#include <linux/reset.h>
#include <linux/poll.h>
#include <linux/version.h>
#include <asm/uaccess.h>
#include <soc/tegra/chip-id.h>
#include "ape_actmon.h"
#include "os.h"
#include "dev.h"
#include "dram_app_mem_manager.h"
#include "adsp_console_dbfs.h"
#include "hwmailbox.h"
#include "log_state.h"
#define NVADSP_ELF "adsp.elf"
#define NVADSP_FIRMWARE NVADSP_ELF
#define MAILBOX_REGION ".mbox_shared_data"
#define DEBUG_RAM_REGION ".debug_mem_logs"
/* Maximum number of LOAD MAPPINGS supported */
#define NM_LOAD_MAPPINGS 20
#define EOT 0x04 /* End of Transmission */
#define SOH 0x01 /* Start of Header */
#define BELL 0x07 /* Bell character */
#define ADSP_TAG "\n[ADSP OS]"
#define UART_BAUD_RATE 9600
/* Intiialize with FIXED rate, once OS boots up DFS will set required freq */
#define ADSP_TO_APE_CLK_RATIO 2
/* 13.5 MHz, should be changed at bringup time */
#define APE_CLK_FIX_RATE 13500
/*
* ADSP CLK = APE_CLK * ADSP_TO_APE_CLK_RATIO
* or
* ADSP CLK = APE_CLK >> ADSP_TO_APE_CLK_RATIO
*/
#define ADSP_CLK_FIX_RATE (APE_CLK_FIX_RATE * ADSP_TO_APE_CLK_RATIO)
/* total number of crashes allowed on adsp */
#define ALLOWED_CRASHES 1
#define DISABLE_MBOX2_FULL_INT 0x0
#define ENABLE_MBOX2_FULL_INT 0xFFFFFFFF
#define LOGGER_TIMEOUT 20 /* in ms */
#define ADSP_WFI_TIMEOUT 800 /* in ms */
#define LOGGER_COMPLETE_TIMEOUT 500 /* in ms */
#define SEARCH_SOH_RETRY 2
#define DUMP_BUFF 128
struct nvadsp_debug_log {
struct device *dev;
char *debug_ram_rdr;
int debug_ram_sz;
int ram_iter;
atomic_t is_opened;
wait_queue_head_t wait_queue;
struct completion complete;
};
struct nvadsp_os_data {
void __iomem *unit_fpga_reset_reg;
const struct firmware *os_firmware;
struct platform_device *pdev;
struct global_sym_info *adsp_glo_sym_tbl;
void __iomem *hwmailbox_base;
struct resource **dram_region;
struct nvadsp_debug_log logger;
struct nvadsp_cnsl console;
struct work_struct restart_os_work;
int adsp_num_crashes;
bool adsp_os_fw_loaded;
struct mutex fw_load_lock;
bool os_running;
struct mutex os_run_lock;
dma_addr_t adsp_os_addr;
size_t adsp_os_size;
dma_addr_t app_alloc_addr;
size_t app_size;
int num_start; /* registers number of time start called */
};
static struct nvadsp_os_data priv;
struct nvadsp_mappings {
phys_addr_t da;
void *va;
int len;
};
static struct nvadsp_mappings adsp_map[NM_LOAD_MAPPINGS];
static int map_idx;
static struct nvadsp_mbox adsp_com_mbox;
static DECLARE_COMPLETION(entered_wfi);
static void __nvadsp_os_stop(bool);
static irqreturn_t adsp_wdt_handler(int irq, void *arg);
static irqreturn_t adsp_wfi_handler(int irq, void *arg);
/*
* set by adsp audio driver through exported api nvadsp_set_adma_dump_reg
* used to dump adma registers incase of failures for debug
*/
static void (*nvadsp_tegra_adma_dump_ch_reg)(void);
#ifdef CONFIG_DEBUG_FS
static int adsp_logger_open(struct inode *inode, struct file *file)
{
struct nvadsp_debug_log *logger = inode->i_private;
int ret = -EBUSY;
char *start;
int i;
mutex_lock(&priv.os_run_lock);
if (!priv.num_start) {
mutex_unlock(&priv.os_run_lock);
goto err_ret;
}
mutex_unlock(&priv.os_run_lock);
/*
* checks if os_opened decrements to zero and if returns true. If true
* then there has been no open.
*/
if (!atomic_dec_and_test(&logger->is_opened)) {
atomic_inc(&logger->is_opened);
goto err_ret;
}
/* loop till writer is initilized with SOH */
for (i = 0; i < SEARCH_SOH_RETRY; i++) {
ret = wait_event_interruptible_timeout(logger->wait_queue,
memchr(logger->debug_ram_rdr, SOH,
logger->debug_ram_sz),
msecs_to_jiffies(LOGGER_TIMEOUT));
if (ret == -ERESTARTSYS) /* check if interrupted */
goto err;
start = memchr(logger->debug_ram_rdr, SOH,
logger->debug_ram_sz);
if (start)
break;
}
if (i == SEARCH_SOH_RETRY) {
ret = -EINVAL;
goto err;
}
/* maxdiff can be 0, therefore valid */
logger->ram_iter = start - logger->debug_ram_rdr;
file->private_data = logger;
return 0;
err:
/* reset to 1 so as to mention the node is free */
atomic_set(&logger->is_opened, 1);
err_ret:
return ret;
}
static int adsp_logger_flush(struct file *file, fl_owner_t id)
{
struct nvadsp_debug_log *logger = file->private_data;
struct device *dev = logger->dev;
dev_dbg(dev, "%s\n", __func__);
/* reset to 1 so as to mention the node is free */
atomic_set(&logger->is_opened, 1);
return 0;
}
static int adsp_logger_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t adsp_logger_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct nvadsp_debug_log *logger = file->private_data;
struct device *dev = logger->dev;
ssize_t ret_num_char = 1;
char last_char;
loop:
last_char = logger->debug_ram_rdr[logger->ram_iter];
if ((last_char != EOT) && (last_char != 0)) {
#if CONFIG_ADSP_DRAM_LOG_WITH_TAG
if ((last_char == '\n') || (last_char == '\r')) {
size_t num_char = min(count, sizeof(ADSP_TAG) - 1);
if (copy_to_user(buf, ADSP_TAG, num_char)) {
dev_err(dev, "%s failed in copying tag\n", __func__);
ret_num_char = -EFAULT;
goto exit;
}
ret_num_char = num_char;
} else
#endif
if (copy_to_user(buf, &last_char, 1)) {
dev_err(dev, "%s failed in copying character\n", __func__);
ret_num_char = -EFAULT;
goto exit;
}
logger->ram_iter =
(logger->ram_iter + 1) % logger->debug_ram_sz;
goto exit;
}
complete(&logger->complete);
ret_num_char = wait_event_interruptible_timeout(logger->wait_queue,
logger->debug_ram_rdr[logger->ram_iter] != EOT,
msecs_to_jiffies(LOGGER_TIMEOUT));
if (ret_num_char == -ERESTARTSYS) {
goto exit;
}
goto loop;
exit:
return ret_num_char;
}
static const struct file_operations adsp_logger_operations = {
.read = adsp_logger_read,
.open = adsp_logger_open,
.release = adsp_logger_release,
.llseek = generic_file_llseek,
.flush = adsp_logger_flush,
};
static int adsp_create_debug_logger(struct dentry *adsp_debugfs_root)
{
struct nvadsp_debug_log *logger = &priv.logger;
struct device *dev = &priv.pdev->dev;
int ret = 0;
if (IS_ERR_OR_NULL(adsp_debugfs_root)) {
ret = -ENOENT;
goto err_out;
}
atomic_set(&logger->is_opened, 1);
init_waitqueue_head(&logger->wait_queue);
init_completion(&logger->complete);
if (!debugfs_create_file("adsp_logger", S_IRUGO,
adsp_debugfs_root, logger,
&adsp_logger_operations)) {
dev_err(dev, "unable to create adsp logger debug fs file\n");
ret = -ENOENT;
}
err_out:
return ret;
}
#endif
bool is_adsp_dram_addr(u64 addr)
{
int i;
struct resource **dram = priv.dram_region;
for (i = 0; i < ADSP_MAX_DRAM_MAP; i++) {
if ((dram[i]->start) && (addr >= dram[i]->start) &&
(addr <= dram[i]->end))
return true;
}
return false;
}
int nvadsp_add_load_mappings(phys_addr_t pa, void *mapping, int len)
{
if (map_idx >= NM_LOAD_MAPPINGS)
return -EINVAL;
adsp_map[map_idx].da = pa;
adsp_map[map_idx].va = mapping;
adsp_map[map_idx].len = len;
map_idx++;
return 0;
}
void *nvadsp_da_to_va_mappings(u64 da, int len)
{
void *ptr = NULL;
int i;
for (i = 0; i < map_idx; i++) {
int offset = da - adsp_map[i].da;
/* try next carveout if da is too small */
if (offset < 0)
continue;
/* try next carveout if da is too large */
if (offset + len > adsp_map[i].len)
continue;
ptr = adsp_map[i].va + offset;
break;
}
return ptr;
}
void *nvadsp_alloc_coherent(size_t size, dma_addr_t *da, gfp_t flags)
{
struct device *dev;
void *va = NULL;
if (!priv.pdev) {
pr_err("ADSP Driver is not initialized\n");
goto end;
}
dev = &priv.pdev->dev;
va = dma_alloc_coherent(dev, size, da, flags);
if (!va) {
dev_err(dev, "unable to allocate the memory for size %lu\n",
size);
goto end;
}
WARN(!is_adsp_dram_addr(*da), "bus addr %llx beyond %x\n",
*da, UINT_MAX);
end:
return va;
}
EXPORT_SYMBOL(nvadsp_alloc_coherent);
void nvadsp_free_coherent(size_t size, void *va, dma_addr_t da)
{
struct device *dev;
if (!priv.pdev) {
pr_err("ADSP Driver is not initialized\n");
return;
}
dev = &priv.pdev->dev;
dma_free_coherent(dev, size, va, da);
}
EXPORT_SYMBOL(nvadsp_free_coherent);
struct elf32_shdr *
nvadsp_get_section(const struct firmware *fw, char *sec_name)
{
int i;
struct device *dev = &priv.pdev->dev;
const u8 *elf_data = fw->data;
struct elf32_hdr *ehdr = (struct elf32_hdr *)elf_data;
struct elf32_shdr *shdr;
const char *name_table;
/* look for the resource table and handle it */
shdr = (struct elf32_shdr *)(elf_data + ehdr->e_shoff);
name_table = elf_data + shdr[ehdr->e_shstrndx].sh_offset;
for (i = 0; i < ehdr->e_shnum; i++, shdr++)
if (!strcmp(name_table + shdr->sh_name, sec_name)) {
dev_dbg(dev, "found the section %s\n",
name_table + shdr->sh_name);
return shdr;
}
return NULL;
}
static inline void __maybe_unused dump_global_symbol_table(void)
{
struct device *dev = &priv.pdev->dev;
struct global_sym_info *table = priv.adsp_glo_sym_tbl;
int num_ent;
int i;
if (!table) {
dev_err(dev, "no table not created\n");
return;
}
num_ent = table[0].addr;
dev_info(dev, "total number of entries in global symbol table %d\n",
num_ent);
pr_info("NAME ADDRESS TYPE\n");
for (i = 1; i < num_ent; i++)
pr_info("%s %x %s\n", table[i].name, table[i].addr,
ELF32_ST_TYPE(table[i].info) == STT_FUNC ?
"STT_FUNC" : "STT_OBJECT");
}
static int
__maybe_unused create_global_symbol_table(const struct firmware *fw)
{
int i;
struct device *dev = &priv.pdev->dev;
struct elf32_shdr *sym_shdr = nvadsp_get_section(fw, ".symtab");
struct elf32_shdr *str_shdr = nvadsp_get_section(fw, ".strtab");
const u8 *elf_data = fw->data;
const char *name_table;
/* The first entry stores the number of entries in the array */
int num_ent = 1;
struct elf32_sym *sym;
struct elf32_sym *last_sym;
sym = (struct elf32_sym *)(elf_data + sym_shdr->sh_offset);
name_table = elf_data + str_shdr->sh_offset;
num_ent += sym_shdr->sh_size / sizeof(struct elf32_sym);
priv.adsp_glo_sym_tbl = devm_kzalloc(dev,
sizeof(struct global_sym_info) * num_ent, GFP_KERNEL);
if (!priv.adsp_glo_sym_tbl)
return -ENOMEM;
last_sym = sym + num_ent;
for (i = 1; sym < last_sym; sym++) {
unsigned char info = sym->st_info;
unsigned char type = ELF32_ST_TYPE(info);
if ((ELF32_ST_BIND(sym->st_info) == STB_GLOBAL) &&
((type == STT_OBJECT) || (type == STT_FUNC))) {
char *name = priv.adsp_glo_sym_tbl[i].name;
strlcpy(name, name_table + sym->st_name, SYM_NAME_SZ);
priv.adsp_glo_sym_tbl[i].addr = sym->st_value;
priv.adsp_glo_sym_tbl[i].info = info;
i++;
}
}
priv.adsp_glo_sym_tbl[0].addr = i;
return 0;
}
struct global_sym_info * __maybe_unused find_global_symbol(const char *sym_name)
{
struct device *dev = &priv.pdev->dev;
struct global_sym_info *table = priv.adsp_glo_sym_tbl;
int num_ent;
int i;
if (unlikely(!table)) {
dev_err(dev, "symbol table not present\n");
return NULL;
}
num_ent = table[0].addr;
for (i = 1; i < num_ent; i++) {
if (!strncmp(table[i].name, sym_name, SYM_NAME_SZ))
return &table[i];
}
return NULL;
}
static void *get_mailbox_shared_region(const struct firmware *fw)
{
struct device *dev;
struct elf32_shdr *shdr;
int addr;
int size;
if (!priv.pdev) {
pr_err("ADSP Driver is not initialized\n");
return ERR_PTR(-EINVAL);
}
dev = &priv.pdev->dev;
shdr = nvadsp_get_section(fw, MAILBOX_REGION);
if (!shdr) {
dev_err(dev, "section %s not found\n", MAILBOX_REGION);
return ERR_PTR(-EINVAL);
}
dev_dbg(dev, "the shared section is present at 0x%x\n", shdr->sh_addr);
addr = shdr->sh_addr;
size = shdr->sh_size;
return nvadsp_da_to_va_mappings(addr, size);
}
static void copy_io_in_l(void *to, const void *from, int sz)
{
int i;
for (i = 0; i < sz; i += 4) {
int val = *(int *)(from + i);
writel(val, to + i);
}
}
static int nvadsp_os_elf_load(const struct firmware *fw)
{
struct device *dev = &priv.pdev->dev;
struct nvadsp_drv_data *drv_data = platform_get_drvdata(priv.pdev);
struct elf32_hdr *ehdr;
struct elf32_phdr *phdr;
int i, ret = 0;
const u8 *elf_data = fw->data;
ehdr = (struct elf32_hdr *)elf_data;
phdr = (struct elf32_phdr *)(elf_data + ehdr->e_phoff);
/* go through the available ELF segments */
for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
void *va;
u32 da = phdr->p_paddr;
u32 memsz = phdr->p_memsz;
u32 filesz = phdr->p_filesz;
u32 offset = phdr->p_offset;
if (phdr->p_type != PT_LOAD)
continue;
dev_dbg(dev, "phdr: type %d da 0x%x memsz 0x%x filesz 0x%x\n",
phdr->p_type, da, memsz, filesz);
va = nvadsp_da_to_va_mappings(da, filesz);
if (!va) {
dev_err(dev, "no va for da 0x%x filesz 0x%x\n",
da, filesz);
ret = -EINVAL;
break;
}
if (filesz > memsz) {
dev_err(dev, "bad phdr filesz 0x%x memsz 0x%x\n",
filesz, memsz);
ret = -EINVAL;
break;
}
if (offset + filesz > fw->size) {
dev_err(dev, "truncated fw: need 0x%x avail 0x%zx\n",
offset + filesz, fw->size);
ret = -EINVAL;
break;
}
/* put the segment where the remote processor expects it */
if (filesz) {
if (is_adsp_dram_addr(da))
memcpy(va, elf_data + offset, filesz);
else if ((da == drv_data->evp_base[ADSP_EVP_BASE]) &&
(filesz == drv_data->evp_base[ADSP_EVP_SIZE])) {
drv_data->state.evp_ptr = va;
memcpy(drv_data->state.evp,
elf_data + offset, filesz);
} else {
dev_err(dev, "can't load mem pa:0x%x va:%p\n",
da, va);
ret = -EINVAL;
break;
}
}
}
return ret;
}
static int allocate_memory_for_adsp_os(void)
{
struct platform_device *pdev = priv.pdev;
struct device *dev = &pdev->dev;
#if defined(CONFIG_TEGRA_NVADSP_ON_SMMU)
dma_addr_t addr;
#else
phys_addr_t addr;
#endif
void *dram_va;
size_t size;
int ret = 0;
addr = priv.adsp_os_addr;
size = priv.adsp_os_size;
#if defined(CONFIG_TEGRA_NVADSP_ON_SMMU)
dram_va = dma_alloc_at_coherent(dev, size, &addr, GFP_KERNEL);
if (!dram_va) {
dev_err(dev, "unable to allocate SMMU pages\n");
ret = -ENOMEM;
goto end;
}
#else
dram_va = ioremap_nocache(addr, size);
if (!dram_va) {
dev_err(dev, "remap failed for addr 0x%llx\n", addr);
ret = -ENOMEM;
goto end;
}
#endif
nvadsp_add_load_mappings(addr, dram_va, size);
end:
return ret;
}
static void deallocate_memory_for_adsp_os(struct device *dev)
{
#if defined(CONFIG_TEGRA_NVADSP_ON_SMMU)
void *va = nvadsp_da_to_va_mappings(priv.adsp_os_addr,
priv.adsp_os_size);
dma_free_coherent(dev, priv.adsp_os_addr, va, priv.adsp_os_size);
#endif
}
static void nvadsp_set_shared_mem(struct platform_device *pdev,
struct nvadsp_shared_mem *shared_mem,
uint32_t dynamic_app_support)
{
struct nvadsp_drv_data *drv_data = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
struct nvadsp_os_args *os_args;
enum tegra_chipid chip_id;
shared_mem->os_args.dynamic_app_support = dynamic_app_support;
/* set logger strcuture with required properties */
priv.logger.debug_ram_rdr = shared_mem->os_args.logger;
priv.logger.debug_ram_sz = sizeof(shared_mem->os_args.logger);
priv.logger.dev = dev;
priv.adsp_os_fw_loaded = true;
chip_id = tegra_get_chipid();
os_args = &shared_mem->os_args;
os_args->chip_id = chip_id;
drv_data->shared_adsp_os_data = shared_mem;
}
static int __nvadsp_os_secload(struct platform_device *pdev)
{
struct nvadsp_drv_data *drv_data = platform_get_drvdata(pdev);
dma_addr_t addr = drv_data->adsp_mem[ACSR_ADDR];
size_t size = drv_data->adsp_mem[ACSR_SIZE];
struct device *dev = &pdev->dev;
void *dram_va;
dram_va = dma_alloc_at_coherent(dev, size, &addr, GFP_KERNEL);
if (!dram_va) {
dev_err(dev, "unable to allocate shared region\n");
return -ENOMEM;
}
nvadsp_set_shared_mem(pdev, dram_va, 0);
return 0;
}
static int nvadsp_firmware_load(struct platform_device *pdev)
{
struct nvadsp_shared_mem *shared_mem;
struct device *dev = &pdev->dev;
const struct firmware *fw;
int ret = 0;
ret = request_firmware(&fw, NVADSP_FIRMWARE, dev);
if (ret < 0) {
dev_err(dev, "reqest firmware for %s failed with %d\n",
NVADSP_FIRMWARE, ret);
goto end;
}
#ifdef CONFIG_ANDROID
ret = create_global_symbol_table(fw);
if (ret) {
dev_err(dev, "unable to create global symbol table\n");
goto release_firmware;
}
#endif
ret = allocate_memory_for_adsp_os();
if (ret) {
dev_err(dev, "unable to allocate memory for adsp os\n");
goto release_firmware;
}
dev_info(dev, "Loading ADSP OS firmware %s\n", NVADSP_FIRMWARE);
ret = nvadsp_os_elf_load(fw);
if (ret) {
dev_err(dev, "failed to load %s\n", NVADSP_FIRMWARE);
goto deallocate_os_memory;
}
shared_mem = get_mailbox_shared_region(fw);
nvadsp_set_shared_mem(pdev, shared_mem, 1);
ret = dram_app_mem_init(priv.app_alloc_addr, priv.app_size);
if (ret) {
dev_err(dev, "Memory allocation dynamic apps failed\n");
goto deallocate_os_memory;
}
priv.os_firmware = fw;
return 0;
deallocate_os_memory:
deallocate_memory_for_adsp_os(dev);
release_firmware:
release_firmware(fw);
end:
return ret;
}
int nvadsp_os_load(void)
{
struct nvadsp_drv_data *drv_data;
struct device *dev;
int ret = 0;
if (!priv.pdev) {
pr_err("ADSP Driver is not initialized\n");
ret = -EINVAL;
goto end;
}
mutex_lock(&priv.fw_load_lock);
if (priv.adsp_os_fw_loaded)
goto end;
drv_data = platform_get_drvdata(priv.pdev);
dev = &priv.pdev->dev;
if (drv_data->adsp_os_secload) {
dev_info(dev, "ADSP OS firmware already loaded\n");
ret = __nvadsp_os_secload(priv.pdev);
} else {
ret = nvadsp_firmware_load(priv.pdev);
}
if (ret == 0) {
priv.adsp_os_fw_loaded = true;
#ifdef CONFIG_DEBUG_FS
wake_up(&priv.logger.wait_queue);
#endif
}
end:
mutex_unlock(&priv.fw_load_lock);
return ret;
}
EXPORT_SYMBOL(nvadsp_os_load);
/*
* Static adsp freq to emc freq lookup table
*
* arg:
* adspfreq - adsp freq in KHz
* return:
* 0 - min emc freq
* > 0 - expected emc freq at this adsp freq
*/
u32 adsp_to_emc_freq(u32 adspfreq)
{
/*
* Vote on memory bus frequency based on adsp frequency
* cpu rate is in kHz, emc rate is in Hz
*/
if (adspfreq >= 204800)
return 102000; /* adsp >= 204.8 MHz, emc 102 MHz */
else
return 0; /* emc min */
}
static int nvadsp_set_ape_emc_freq(struct nvadsp_drv_data *drv_data)
{
unsigned long ape_emc_freq;
struct device *dev = &priv.pdev->dev;
int ret;
#ifdef CONFIG_TEGRA_ADSP_DFS
/* pass adsp freq in KHz. adsp_emc_freq in Hz */
ape_emc_freq = adsp_to_emc_freq(drv_data->adsp_freq / 1000) * 1000;
#else
ape_emc_freq = drv_data->ape_emc_freq * 1000; /* in Hz */
#endif
dev_dbg(dev, "requested adsp cpu freq %luKHz",
drv_data->adsp_freq / 1000);
dev_dbg(dev, "emc freq %luHz\n", ape_emc_freq / 1000);
/*
* ape_emc_freq is not required to set if adsp_freq
* is lesser than 204.8 MHz
*/
if (!ape_emc_freq)
return 0;
ret = tegra_bwmgr_set_emc(drv_data->bwmgr, ape_emc_freq * 1000,
TEGRA_BWMGR_SET_EMC_FLOOR);
if (ret)
dev_err(dev, "failed to set emc freq rate:%d\n", ret);
dev_dbg(dev, "ape.emc freq %luKHz\n",
tegra_bwmgr_get_emc_rate() / 1000);
return ret;
}
static int nvadsp_set_ape_freq(struct nvadsp_drv_data *drv_data)
{
unsigned long ape_freq = drv_data->ape_freq * 1000; /* in Hz*/
struct device *dev = &priv.pdev->dev;
int ret;
#ifdef CONFIG_TEGRA_ADSP_ACTMON
ape_freq = drv_data->adsp_freq / ADSP_TO_APE_CLK_RATIO;
#endif
dev_dbg(dev, "ape freq %luKHz", ape_freq / 1000);
if (!ape_freq)
return 0;
ret = clk_set_rate(drv_data->ape_clk, ape_freq);
dev_dbg(dev, "ape freq %luKHz\n",
clk_get_rate(drv_data->ape_clk) / 1000);
return ret;
}
static int nvadsp_t210_set_clks_and_prescalar(struct nvadsp_drv_data *drv_data)
{
struct nvadsp_shared_mem *shared_mem = drv_data->shared_adsp_os_data;
struct nvadsp_os_args *os_args = &shared_mem->os_args;
struct device *dev = &priv.pdev->dev;
unsigned long max_adsp_freq;
unsigned long adsp_freq;
u32 max_index;
u32 cur_index;
int ret = 0;
adsp_freq = drv_data->adsp_freq * 1000; /* in Hz*/
max_adsp_freq = clk_round_rate(drv_data->adsp_cpu_abus_clk,
ULONG_MAX);
max_index = max_adsp_freq / MIN_ADSP_FREQ;
cur_index = adsp_freq / MIN_ADSP_FREQ;
if (!adsp_freq)
/* Set max adsp boot freq */
cur_index = max_index;
if (adsp_freq % MIN_ADSP_FREQ) {
if (cur_index >= max_index)
cur_index = max_index;
else
cur_index++;
} else if (cur_index >= max_index)
cur_index = max_index;
/*
* timer interval = (prescalar + 1) * (count + 1) / periph_freq
* therefore for 0 count,
* 1 / TIMER_CLK_HZ = (prescalar + 1) / periph_freq
* Hence, prescalar = periph_freq / TIMER_CLK_HZ - 1
*/
os_args->timer_prescalar = cur_index - 1;
adsp_freq = cur_index * MIN_ADSP_FREQ;
ret = clk_set_rate(drv_data->adsp_cpu_abus_clk, adsp_freq);
if (ret)
goto end;
drv_data->adsp_freq = adsp_freq / 1000; /* adsp_freq in KHz*/
drv_data->adsp_freq_hz = adsp_freq;
/* adspos uses os_args->adsp_freq_hz for EDF */
os_args->adsp_freq_hz = adsp_freq;
end:
dev_dbg(dev, "adsp cpu freq %luKHz\n",
clk_get_rate(drv_data->adsp_cpu_abus_clk) / 1000);
dev_dbg(dev, "timer prescalar %x\n", os_args->timer_prescalar);
return ret;
}
static int nvadsp_set_adsp_clks(struct nvadsp_drv_data *drv_data)
{
struct nvadsp_shared_mem *shared_mem = drv_data->shared_adsp_os_data;
struct nvadsp_os_args *os_args = &shared_mem->os_args;
struct platform_device *pdev = drv_data->pdev;
struct device *dev = &pdev->dev;
unsigned long max_adsp_freq;
unsigned long adsp_freq;
int ret = 0;
adsp_freq = drv_data->adsp_freq_hz; /* in Hz*/
/* round rate shall be used with adsp parent clk i.e. aclk */
max_adsp_freq = clk_round_rate(drv_data->aclk_clk, ULONG_MAX);
/* Set max adsp boot freq */
if (!adsp_freq)
adsp_freq = max_adsp_freq;
/* set rate shall be used with adsp parent clk i.e. aclk */
ret = clk_set_rate(drv_data->aclk_clk, adsp_freq);
if (ret) {
dev_err(dev, "setting adsp_freq:%luHz failed.\n", adsp_freq);
dev_err(dev, "max_adsp_freq:%luHz\n", max_adsp_freq);
goto end;
}
drv_data->adsp_freq = adsp_freq / 1000; /* adsp_freq in KHz*/
drv_data->adsp_freq_hz = adsp_freq;
/* adspos uses os_args->adsp_freq_hz for EDF */
os_args->adsp_freq_hz = adsp_freq;
end:
dev_dbg(dev, "adsp cpu freq %luKHz\n",
clk_get_rate(drv_data->adsp_clk) / 1000);
return ret;
}
static int __deassert_adsp(struct nvadsp_drv_data *d)
{
struct platform_device *pdev = d->pdev;
struct device *dev = &pdev->dev;
int ret = 0;
/*
* The ADSP_ALL reset in BPMP-FW is overloaded to de-assert
* all 7 resets i.e. ADSP, ADSPINTF, ADSPDBG, ADSPNEON, ADSPPERIPH,
* ADSPSCU and ADSPWDT resets. The BPMP-FW also takes care
* of specific de-assert sequence and delays between them.
* So de-resetting only ADSP reset is sufficient to de-reset
* all ADSP sub-modules.
*/
ret = reset_control_deassert(d->adspall_rst);
if (ret)
dev_err(dev, "failed to deassert adsp\n");
return ret;
}
static int nvadsp_deassert_adsp(struct nvadsp_drv_data *drv_data)
{
int ret = -EINVAL;
if (drv_data->deassert_adsp)
ret = drv_data->deassert_adsp(drv_data);
return ret;
}
static int __assert_adsp(struct nvadsp_drv_data *d)
{
struct platform_device *pdev = d->pdev;
struct device *dev = &pdev->dev;
int ret = 0;
/*
* The ADSP_ALL reset in BPMP-FW is overloaded to assert
* all 7 resets i.e. ADSP, ADSPINTF, ADSPDBG, ADSPNEON,
* ADSPPERIPH, ADSPSCU and ADSPWDT resets. So resetting
* only ADSP reset is sufficient to reset all ADSP sub-modules.
*/
ret = reset_control_assert(d->adspall_rst);
if (ret)
dev_err(dev, "failed to assert adsp\n");
return ret;
}
static int nvadsp_assert_adsp(struct nvadsp_drv_data *drv_data)
{
int ret = -EINVAL;
if (drv_data->assert_adsp)
ret = drv_data->assert_adsp(drv_data);
return ret;
}
static int nvadsp_set_boot_freqs(struct nvadsp_drv_data *drv_data)
{
struct device *dev = &priv.pdev->dev;
struct device_node *node = dev->of_node;
int ret = 0;
/* on Unit-FPGA do not set clocks, return success */
if (drv_data->adsp_unit_fpga)
return 0;
if (of_device_is_compatible(node, "nvidia,tegra210-adsp")) {
if (drv_data->adsp_cpu_abus_clk) {
ret = nvadsp_t210_set_clks_and_prescalar(drv_data);
if (ret)
goto end;
} else {
ret = -EINVAL;
goto end;
}
} else {
if (drv_data->adsp_clk) {
ret = nvadsp_set_adsp_clks(drv_data);
if (ret)
goto end;
} else {
ret = -EINVAL;
goto end;
}
}
if (drv_data->ape_clk) {
ret = nvadsp_set_ape_freq(drv_data);
if (ret)
goto end;
}
if (drv_data->bwmgr) {
ret = nvadsp_set_ape_emc_freq(drv_data);
if (ret)
goto end;
}
end:
return ret;
}
static int wait_for_adsp_os_load_complete(void)
{
struct device *dev = &priv.pdev->dev;
uint32_t data;
status_t ret;
ret = nvadsp_mbox_recv(&adsp_com_mbox, &data,
true, ADSP_OS_LOAD_TIMEOUT);
if (ret) {
dev_err(dev, "ADSP OS loading timed out\n");
goto end;
}
dev_dbg(dev, "ADSP has been %s\n",
data == ADSP_OS_BOOT_COMPLETE ? "BOOTED" : "RESUMED");
switch (data) {
case ADSP_OS_BOOT_COMPLETE:
ret = load_adsp_static_apps();
break;
case ADSP_OS_RESUME:
default:
break;
}
end:
return ret;
}
static int __nvadsp_os_start(void)
{
struct nvadsp_drv_data *drv_data;
struct device *dev;
int ret = 0;
dev = &priv.pdev->dev;
drv_data = platform_get_drvdata(priv.pdev);
dev_dbg(dev, "ADSP is booting on %s\n",
drv_data->adsp_unit_fpga ? "UNIT-FPGA" : "SILICON");
nvadsp_assert_adsp(drv_data);
if (!drv_data->adsp_os_secload) {
dev_dbg(dev, "Copying EVP...\n");
copy_io_in_l(drv_data->state.evp_ptr,
drv_data->state.evp,
AMC_EVP_SIZE);
}
dev_dbg(dev, "Setting freqs\n");
ret = nvadsp_set_boot_freqs(drv_data);
if (ret) {
dev_err(dev, "failed to set boot freqs\n");
goto end;
}
dev_dbg(dev, "De-asserting adsp\n");
ret = nvadsp_deassert_adsp(drv_data);
if (ret) {
dev_err(dev, "failed to deassert ADSP\n");
goto end;
}
dev_dbg(dev, "Waiting for ADSP OS to boot up...\n");
ret = wait_for_adsp_os_load_complete();
if (ret) {
dev_err(dev, "Unable to start ADSP OS\n");
goto end;
}
dev_dbg(dev, "ADSP OS boot up... Done!\n");
#ifdef CONFIG_TEGRA_ADSP_DFS
ret = adsp_dfs_core_init(priv.pdev);
if (ret) {
dev_err(dev, "adsp dfs initialization failed\n");
goto err;
}
#endif
#ifdef CONFIG_TEGRA_ADSP_ACTMON
ret = ape_actmon_init(priv.pdev);
if (ret) {
dev_err(dev, "ape actmon initialization failed\n");
goto err;
}
#endif
#ifdef CONFIG_TEGRA_ADSP_CPUSTAT
ret = adsp_cpustat_init(priv.pdev);
if (ret) {
dev_err(dev, "adsp cpustat initialisation failed\n");
goto err;
}
#endif
end:
return ret;
#if defined(CONFIG_TEGRA_ADSP_DFS) || defined(CONFIG_TEGRA_ADSP_CPUSTAT)
err:
__nvadsp_os_stop(true);
return ret;
#endif
}
static void dump_adsp_logs(void)
{
int i = 0;
char buff[DUMP_BUFF] = { };
int buff_iter = 0;
char last_char;
struct nvadsp_debug_log *logger = &priv.logger;
struct device *dev = &priv.pdev->dev;
char *ptr = logger->debug_ram_rdr;
dev_err(dev, "Dumping ADSP logs ........\n");
for (i = 0; i < logger->debug_ram_sz; i++) {
last_char = *(ptr + i);
if ((last_char != EOT) && (last_char != 0)) {
if ((last_char == '\n') || (last_char == '\r') ||
(buff_iter == DUMP_BUFF)) {
dev_err(dev, "[ADSP OS] %s\n", buff);
memset(buff, 0, sizeof(buff));
buff_iter = 0;
} else {
buff[buff_iter++] = last_char;
}
}
}
dev_err(dev, "End of ADSP log dump .....\n");
}
static void print_agic_irq_states(void)
{
struct nvadsp_drv_data *drv_data = platform_get_drvdata(priv.pdev);
int start_irq = drv_data->chip_data->start_irq;
int end_irq = drv_data->chip_data->end_irq;
struct device *dev = &priv.pdev->dev;
int i;
for (i = start_irq; i < end_irq; i++) {
dev_info(dev, "irq %d is %s and %s\n", i,
tegra_agic_irq_is_pending(i) ?
"pending" : "not pending",
tegra_agic_irq_is_active(i) ?
"active" : "not active");
}
}
static void print_arm_mode_regs(void)
{
struct nvadsp_exception_context *excep_context;
struct arm_fault_frame_shared *shared_frame;
struct arm_mode_regs_shared *shared_regs;
struct nvadsp_shared_mem *shared_mem;
struct device *dev = &priv.pdev->dev;
struct nvadsp_drv_data *drv_data;
drv_data = platform_get_drvdata(priv.pdev);
shared_mem = drv_data->shared_adsp_os_data;
excep_context = &shared_mem->exception_context;
shared_frame = &excep_context->frame;
shared_regs = &excep_context->regs;
dev_err(dev, "dumping arm mode register data...\n");
dev_err(dev, "%c fiq r13 0x%08x r14 0x%08x\n",
((shared_frame->spsr & MODE_MASK) == MODE_FIQ) ? '*' : ' ',
shared_regs->fiq_r13, shared_regs->fiq_r14);
dev_err(dev, "%c irq r13 0x%08x r14 0x%08x\n",
((shared_frame->spsr & MODE_MASK) == MODE_IRQ) ? '*' : ' ',
shared_regs->irq_r13, shared_regs->irq_r14);
dev_err(dev, "%c svc r13 0x%08x r14 0x%08x\n",
((shared_frame->spsr & MODE_MASK) == MODE_SVC) ? '*' : ' ',
shared_regs->svc_r13, shared_regs->svc_r14);
dev_err(dev, "%c und r13 0x%08x r14 0x%08x\n",
((shared_frame->spsr & MODE_MASK) == MODE_UND) ? '*' : ' ',
shared_regs->und_r13, shared_regs->und_r14);
dev_err(dev, "%c sys r13 0x%08x r14 0x%08x\n",
((shared_frame->spsr & MODE_MASK) == MODE_SYS) ? '*' : ' ',
shared_regs->sys_r13, shared_regs->sys_r14);
dev_err(dev, "%c abt r13 0x%08x r14 0x%08x\n",
((shared_frame->spsr & MODE_MASK) == MODE_ABT) ? '*' : ' ',
shared_regs->abt_r13, shared_regs->abt_r14);
}
static void print_arm_fault_frame(void)
{
struct nvadsp_exception_context *excep_context;
struct arm_fault_frame_shared *shared_frame;
struct nvadsp_shared_mem *shared_mem;
struct device *dev = &priv.pdev->dev;
struct nvadsp_drv_data *drv_data;
drv_data = platform_get_drvdata(priv.pdev);
shared_mem = drv_data->shared_adsp_os_data;
excep_context = &shared_mem->exception_context;
shared_frame = &excep_context->frame;
dev_err(dev, "dumping fault frame...\n");
dev_err(dev, "r0 0x%08x r1 0x%08x r2 0x%08x r3 0x%08x\n",
shared_frame->r[0], shared_frame->r[1], shared_frame->r[2],
shared_frame->r[3]);
dev_err(dev, "r4 0x%08x r5 0x%08x r6 0x%08x r7 0x%08x\n",
shared_frame->r[4], shared_frame->r[5], shared_frame->r[6],
shared_frame->r[7]);
dev_err(dev, "r8 0x%08x r9 0x%08x r10 0x%08x r11 0x%08x\n",
shared_frame->r[8], shared_frame->r[9], shared_frame->r[10],
shared_frame->r[11]);
dev_err(dev, "r12 0x%08x usp 0x%08x ulr 0x%08x pc 0x%08x\n",
shared_frame->r[12], shared_frame->usp, shared_frame->ulr,
shared_frame->pc);
dev_err(dev, "spsr 0x%08x\n", shared_frame->spsr);
}
static void dump_thread_name(struct platform_device *pdev, u32 val)
{
dev_info(&pdev->dev, "%s: adsp current thread: %c%c%c%c\n",
__func__,
(val >> 24) & 0xFF, (val >> 16) & 0xFF,
(val >> 8) & 0xFF, (val >> 0) & 0xFF);
}
static void dump_irq_num(struct platform_device *pdev, u32 val)
{
dev_info(&pdev->dev, "%s: adsp current/last irq : %d\n",
__func__, val);
}
static void get_adsp_state(void)
{
struct nvadsp_drv_data *drv_data;
struct device *dev;
uint32_t val;
char *msg;
if (!priv.pdev) {
pr_err("ADSP Driver is not initialized\n");
return;
}
drv_data = platform_get_drvdata(priv.pdev);
dev = &priv.pdev->dev;
if (drv_data->chip_data->adsp_state_hwmbox == -1) {
dev_info(dev, "%s: No state hwmbox available\n", __func__);
return;
}
val = hwmbox_readl(drv_data->chip_data->adsp_state_hwmbox);
dev_info(dev, "%s: adsp state hwmbox value: 0x%X\n", __func__, val);
switch (val) {
case ADSP_LOADER_MAIN_ENTRY:
msg = "loader_main: entry to loader_main";
break;
case ADSP_LOADER_MAIN_CACHE_DISABLE_COMPLETE:
msg = "loader_main: Cache has been disabled";
break;
case ADSP_LOADER_MAIN_CONFIGURE_MMU_COMPLETE:
msg = "loader_main: MMU configuration is complete";
break;
case ADSP_LOADER_MAIN_CACHE_ENABLE_COMPLETE:
msg = "loader_main: Cache has been enabled";
break;
case ADSP_LOADER_MAIN_FPU_ENABLE_COMPLETE:
msg = "loader_main: FPU has been enabled";
break;
case ADSP_LOADER_MAIN_DECOMPRESSION_COMPLETE:
msg = "loader_main: ADSP FW decompression is complete";
break;
case ADSP_LOADER_MAIN_EXIT:
msg = "loader_main: exiting loader_main function";
break;
case ADSP_START_ENTRY_AT_RESET:
msg = "start: ADSP is at reset";
break;
case ADSP_START_CPU_EARLY_INIT:
msg = "start: ADSP to do cpu_early_init";
break;
case ADSP_START_FIRST_BOOT:
msg = "start: ADSP is booting for first time,"
"initializing DATA and clearing BSS";
break;
case ADSP_START_LK_MAIN_ENTRY:
msg = "start: ADSP about to enter lk_main";
break;
case ADSP_LK_MAIN_ENTRY:
msg = "lk_main: entry to lk_main";
break;
case ADSP_LK_MAIN_EARLY_THREAD_INIT_COMPLETE:
msg = "lk_main: early_thread_init has been completed";
break;
case ADSP_LK_MAIN_EARLY_ARCH_INIT_COMPLETE:
msg = "lk_main: early_arch_init has been completed";
break;
case ADSP_LK_MAIN_EARLY_PLATFORM_INIT_COMPLETE:
msg = "lk_main: early_platform_init has been completed";
break;
case ADSP_LK_MAIN_EARLY_TARGET_INIT_COMPLETE:
msg = "lk_main: early_target_init has been completed";
break;
case ADSP_LK_MAIN_CONSTRUCTOR_INIT_COMPLETE:
msg = "lk_main: constructors has been called";
break;
case ADSP_LK_MAIN_HEAP_INIT_COMPLETE:
msg = "lk_main: heap has been initialized";
break;
case ADSP_LK_MAIN_KERNEL_INIT_COMPLETE:
msg = "lk_main: ADSP kernel has been initialized";
break;
case ADSP_LK_MAIN_CPU_RESUME_ENTRY:
msg = "lk_main: ADSP is about to resume from suspend";
break;
case ADSP_BOOTSTRAP2_ARCH_INIT_COMPLETE:
msg = "bootstrap2: ADSP arch_init is complete";
break;
case ADSP_BOOTSTRAP2_PLATFORM_INIT_COMPLETE:
msg = "bootstrap2: platform has been initialized";
break;
case ADSP_BOOTSTRAP2_TARGET_INIT_COMPLETE:
msg = "bootstrap2: target has been initialized";
break;
case ADSP_BOOTSTRAP2_APP_MODULE_INIT_COMPLETE:
msg = "bootstrap2: APP modules initialized";
break;
case ADSP_BOOTSTRAP2_APP_INIT_COMPLETE:
msg = "bootstrap2: APP init is complete";
break;
case ADSP_BOOTSTRAP2_STATIC_APP_INIT_COMPLETE:
msg = "bootstrap2: Static apps has been initialized";
break;
case ADSP_BOOTSTRAP2_OS_LOAD_COMPLETE:
msg = "bootstrap2: ADSP OS successfully loaded";
break;
case ADSP_SUSPEND_BEGINS:
msg = "suspend: begins";
break;
case ADSP_SUSPEND_MBX_SEND_COMPLETE:
msg = "suspend: mbox send complete";
break;
case ADSP_SUSPEND_DISABLED_TIMERS:
msg = "suspend: timers disabled";
break;
case ADSP_SUSPEND_DISABLED_INTS:
msg = "suspend: interrupts disabled";
break;
case ADSP_SUSPEND_ARAM_SAVED:
msg = "suspend: aram saved";
break;
case ADSP_SUSPEND_AMC_SAVED:
msg = "suspend: amc saved";
break;
case ADSP_SUSPEND_AMISC_SAVED:
msg = "suspend: amisc saved";
break;
case ADSP_SUSPEND_L1_CACHE_DISABLED:
msg = "suspend: l1 cache disabled";
break;
case ADSP_SUSPEND_L2_CACHE_DISABLED:
msg = "suspend: l2 cache disabled";
break;
case ADSP_RESUME_ADSP:
msg = "resume: beings";
break;
case ADSP_RESUME_AMISC_RESTORED:
msg = "resume: amisc restored";
break;
case ADSP_RESUME_AMC_RESTORED:
msg = "resume: amc restored";
break;
case ADSP_RESUME_ARAM_RESTORED:
msg = "resume: aram restored";
break;
case ADSP_RESUME_COMPLETE:
msg = "resume: complete";
break;
case ADSP_WFI_ENTER:
msg = "WFI: Entering WFI";
break;
case ADSP_WFI_EXIT:
msg = "WFI: Exiting WFI, Failed to Enter";
break;
case ADSP_DFS_MBOX_RECV:
msg = "DFS: mbox received";
break;
case ADSP_DFS_MBOX_SENT:
msg = "DFS: mbox sent";
break;
default:
msg = "Unrecognized ADSP state!!";
break;
}
dev_info(dev, "%s: %s\n", __func__, msg);
val = hwmbox_readl(drv_data->chip_data->adsp_thread_hwmbox);
dump_thread_name(priv.pdev, val);
val = hwmbox_readl(drv_data->chip_data->adsp_irq_hwmbox);
dump_irq_num(priv.pdev, val);
}
void dump_adsp_sys(void)
{
if (!priv.pdev) {
pr_err("ADSP Driver is not initialized\n");
return;
}
dump_adsp_logs();
dump_mailbox_regs();
print_arm_fault_frame();
print_arm_mode_regs();
get_adsp_state();
if (nvadsp_tegra_adma_dump_ch_reg)
(*nvadsp_tegra_adma_dump_ch_reg)();
print_agic_irq_states();
}
EXPORT_SYMBOL(dump_adsp_sys);
static void nvadsp_free_os_interrupts(struct nvadsp_os_data *priv)
{
struct nvadsp_drv_data *drv_data = platform_get_drvdata(priv->pdev);
int wdt_virq = drv_data->agic_irqs[WDT_VIRQ];
int wfi_virq = drv_data->agic_irqs[WFI_VIRQ];
struct device *dev = &priv->pdev->dev;
devm_free_irq(dev, wdt_virq, priv);
devm_free_irq(dev, wfi_virq, priv);
}
static int nvadsp_setup_os_interrupts(struct nvadsp_os_data *priv)
{
struct nvadsp_drv_data *drv_data = platform_get_drvdata(priv->pdev);
int wdt_virq = drv_data->agic_irqs[WDT_VIRQ];
int wfi_virq = drv_data->agic_irqs[WFI_VIRQ];
struct device *dev = &priv->pdev->dev;
int ret;
ret = devm_request_irq(dev, wdt_virq, adsp_wdt_handler,
IRQF_TRIGGER_RISING, "adsp watchdog", priv);
if (ret) {
dev_err(dev, "failed to get adsp watchdog interrupt\n");
goto end;
}
ret = devm_request_irq(dev, wfi_virq, adsp_wfi_handler,
IRQF_TRIGGER_RISING, "adsp wfi", priv);
if (ret) {
dev_err(dev, "cannot request for wfi interrupt\n");
goto free_interrupts;
}
writel(DISABLE_MBOX2_FULL_INT,
priv->hwmailbox_base + drv_data->chip_data->hwmb.hwmbox2_reg);
end:
return ret;
free_interrupts:
nvadsp_free_os_interrupts(priv);
return ret;
}
static void free_interrupts(struct nvadsp_os_data *priv)
{
nvadsp_free_os_interrupts(priv);
nvadsp_free_hwmbox_interrupts(priv->pdev);
nvadsp_free_amc_interrupts(priv->pdev);
}
static int setup_interrupts(struct nvadsp_os_data *priv)
{
int ret;
ret = nvadsp_setup_os_interrupts(priv);
if (ret)
goto err;
ret = nvadsp_setup_hwmbox_interrupts(priv->pdev);
if (ret)
goto free_os_interrupts;
ret = nvadsp_setup_amc_interrupts(priv->pdev);
if (ret)
goto free_hwmbox_interrupts;
return ret;
free_hwmbox_interrupts:
nvadsp_free_hwmbox_interrupts(priv->pdev);
free_os_interrupts:
nvadsp_free_os_interrupts(priv);
err:
return ret;
}
void nvadsp_set_adma_dump_reg(void (*cb_adma_regdump)(void))
{
nvadsp_tegra_adma_dump_ch_reg = cb_adma_regdump;
pr_info("%s: callback for adma reg dump is sent to %p\n",
__func__, nvadsp_tegra_adma_dump_ch_reg);
}
EXPORT_SYMBOL(nvadsp_set_adma_dump_reg);
int nvadsp_os_start(void)
{
struct nvadsp_drv_data *drv_data;
struct device *dev;
int ret = 0;
if (!priv.pdev) {
pr_err("ADSP Driver is not initialized\n");
ret = -EINVAL;
goto end;
}
drv_data = platform_get_drvdata(priv.pdev);
dev = &priv.pdev->dev;
/* check if fw is loaded then start the adsp os */
if (!priv.adsp_os_fw_loaded) {
dev_err(dev, "Call to nvadsp_os_load not made\n");
ret = -EINVAL;
goto end;
}
mutex_lock(&priv.os_run_lock);
/* if adsp is started/running exit gracefully */
if (priv.os_running)
goto unlock;
#ifdef CONFIG_PM
ret = pm_runtime_get_sync(&priv.pdev->dev);
if (ret < 0)
goto unlock;
#endif
ret = setup_interrupts(&priv);
if (ret < 0)
goto unlock;
ret = __nvadsp_os_start();
if (ret) {
priv.os_running = drv_data->adsp_os_running = false;
/* if start fails call pm suspend of adsp driver */
dev_err(dev, "adsp failed to boot with ret = %d\n", ret);
dump_adsp_sys();
free_interrupts(&priv);
#ifdef CONFIG_PM
pm_runtime_put_sync(&priv.pdev->dev);
#endif
goto unlock;
}
priv.os_running = drv_data->adsp_os_running = true;
priv.num_start++;
#if defined(CONFIG_TEGRA_ADSP_FILEIO)
if (!drv_data->adspff_init) {
ret = adspff_init(priv.pdev);
if (!ret)
drv_data->adspff_init = true;
}
#endif
#ifdef CONFIG_TEGRA_ADSP_LPTHREAD
if (!drv_data->lpthread_initialized) {
ret = adsp_lpthread_entry(priv.pdev);
if (ret)
dev_err(dev, "adsp_lpthread_entry failed ret = %d\n",
ret);
}
#endif
drv_data->adsp_os_suspended = false;
#ifdef CONFIG_DEBUG_FS
wake_up(&priv.logger.wait_queue);
#endif
#ifdef CONFIG_TEGRA_ADSP_LPTHREAD
adsp_lpthread_set_suspend(drv_data->adsp_os_suspended);
#endif
unlock:
mutex_unlock(&priv.os_run_lock);
end:
return ret;
}
EXPORT_SYMBOL(nvadsp_os_start);
static int __nvadsp_os_suspend(void)
{
struct device *dev = &priv.pdev->dev;
struct nvadsp_drv_data *drv_data;
int ret;
drv_data = platform_get_drvdata(priv.pdev);
#ifdef CONFIG_TEGRA_ADSP_ACTMON
ape_actmon_exit(priv.pdev);
#endif
#ifdef CONFIG_TEGRA_ADSP_DFS
adsp_dfs_core_exit(priv.pdev);
#endif
#ifdef CONFIG_TEGRA_ADSP_CPUSTAT
adsp_cpustat_exit(priv.pdev);
#endif
ret = nvadsp_mbox_send(&adsp_com_mbox, ADSP_OS_SUSPEND,
NVADSP_MBOX_SMSG, true, UINT_MAX);
if (ret) {
dev_err(dev, "failed to send with adsp com mbox\n");
goto out;
}
dev_dbg(dev, "Waiting for ADSP OS suspend...\n");
ret = wait_for_completion_timeout(&entered_wfi,
msecs_to_jiffies(ADSP_WFI_TIMEOUT));
if (WARN_ON(ret <= 0)) {
dev_err(dev, "Unable to suspend ADSP OS err = %d\n", ret);
ret = (ret < 0) ? ret : -ETIMEDOUT;
goto out;
}
ret = 0;
dev_dbg(dev, "ADSP OS suspended!\n");
drv_data->adsp_os_suspended = true;
#ifdef CONFIG_TEGRA_ADSP_LPTHREAD
adsp_lpthread_set_suspend(drv_data->adsp_os_suspended);
#endif
nvadsp_assert_adsp(drv_data);
out:
return ret;
}
static void __nvadsp_os_stop(bool reload)
{
const struct firmware *fw = priv.os_firmware;
struct nvadsp_drv_data *drv_data;
struct device *dev;
int err = 0;
dev = &priv.pdev->dev;
drv_data = platform_get_drvdata(priv.pdev);
#ifdef CONFIG_TEGRA_ADSP_ACTMON
ape_actmon_exit(priv.pdev);
#endif
#ifdef CONFIG_TEGRA_ADSP_DFS
adsp_dfs_core_exit(priv.pdev);
#endif
#ifdef CONFIG_TEGRA_ADSP_CPUSTAT
adsp_cpustat_exit(priv.pdev);
#endif
#if defined(CONFIG_TEGRA_ADSP_FILEIO)
if (drv_data->adspff_init) {
adspff_exit();
drv_data->adspff_init = false;
}
#endif
writel(ENABLE_MBOX2_FULL_INT,
priv.hwmailbox_base + drv_data->chip_data->hwmb.hwmbox2_reg);
err = wait_for_completion_timeout(&entered_wfi,
msecs_to_jiffies(ADSP_WFI_TIMEOUT));
writel(DISABLE_MBOX2_FULL_INT,
priv.hwmailbox_base + drv_data->chip_data->hwmb.hwmbox2_reg);
/*
* ADSP needs to be in WFI/WFE state to properly reset it.
* However, when ADSPOS is getting stopped on error path,
* it cannot gaurantee that ADSP is in WFI/WFE state.
* Reset it in either case. On failure, whole APE reset is
* required (happens on next APE power domain cycle).
*/
nvadsp_assert_adsp(drv_data);
/* Don't reload ADSPOS if ADSP state is not WFI/WFE */
if (WARN_ON(err <= 0)) {
dev_err(dev, "%s: unable to enter wfi state err = %d\n",
__func__, err);
goto end;
}
if (reload && !drv_data->adsp_os_secload) {
struct nvadsp_debug_log *logger = &priv.logger;
#ifdef CONFIG_DEBUG_FS
wake_up(&logger->wait_queue);
/* wait for LOGGER_TIMEOUT to complete filling the buffer */
wait_for_completion_timeout(&logger->complete,
msecs_to_jiffies(LOGGER_COMPLETE_TIMEOUT));
#endif
/*
* move ram iterator to 0, since after restart the iterator
* will be pointing to initial position of start.
*/
logger->debug_ram_rdr[0] = EOT;
logger->ram_iter = 0;
/* load a fresh copy of adsp.elf */
if (nvadsp_os_elf_load(fw))
dev_err(dev, "failed to reload %s\n", NVADSP_FIRMWARE);
}
end:
return;
}
void nvadsp_os_stop(void)
{
struct nvadsp_drv_data *drv_data;
struct device *dev;
if (!priv.pdev) {
pr_err("ADSP Driver is not initialized\n");
return;
}
dev = &priv.pdev->dev;
drv_data = platform_get_drvdata(priv.pdev);
mutex_lock(&priv.os_run_lock);
/* check if os is running else exit */
if (!priv.os_running)
goto end;
__nvadsp_os_stop(true);
priv.os_running = drv_data->adsp_os_running = false;
free_interrupts(&priv);
#ifdef CONFIG_PM
if (pm_runtime_put_sync(dev) < 0)
dev_err(dev, "failed in pm_runtime_put_sync\n");
#endif
end:
mutex_unlock(&priv.os_run_lock);
}
EXPORT_SYMBOL(nvadsp_os_stop);
int nvadsp_os_suspend(void)
{
struct nvadsp_drv_data *drv_data;
int ret = -EINVAL;
if (!priv.pdev) {
pr_err("ADSP Driver is not initialized\n");
goto end;
}
drv_data = platform_get_drvdata(priv.pdev);
mutex_lock(&priv.os_run_lock);
/* check if os is running else exit */
if (!priv.os_running) {
ret = 0;
goto unlock;
}
ret = __nvadsp_os_suspend();
if (!ret) {
#ifdef CONFIG_PM
struct device *dev = &priv.pdev->dev;
free_interrupts(&priv);
ret = pm_runtime_put_sync(&priv.pdev->dev);
if (ret < 0)
dev_err(dev, "failed in pm_runtime_put_sync\n");
#endif
priv.os_running = drv_data->adsp_os_running = false;
} else {
dev_err(&priv.pdev->dev, "suspend failed with %d\n", ret);
dump_adsp_sys();
}
unlock:
mutex_unlock(&priv.os_run_lock);
end:
return ret;
}
EXPORT_SYMBOL(nvadsp_os_suspend);
static void nvadsp_os_restart(struct work_struct *work)
{
struct nvadsp_os_data *data =
container_of(work, struct nvadsp_os_data, restart_os_work);
struct nvadsp_drv_data *drv_data = platform_get_drvdata(data->pdev);
int wdt_virq = drv_data->agic_irqs[WDT_VIRQ];
int wdt_irq = drv_data->chip_data->wdt_irq;
struct device *dev = &data->pdev->dev;
disable_irq(wdt_virq);
dump_adsp_sys();
nvadsp_os_stop();
if (tegra_agic_irq_is_active(wdt_irq)) {
dev_info(dev, "wdt interrupt is active hence clearing\n");
tegra_agic_clear_active(wdt_irq);
}
if (tegra_agic_irq_is_pending(wdt_irq)) {
dev_info(dev, "wdt interrupt is pending hence clearing\n");
tegra_agic_clear_pending(wdt_irq);
}
dev_info(dev, "wdt interrupt is not pending or active...enabling\n");
enable_irq(wdt_virq);
data->adsp_num_crashes++;
if (data->adsp_num_crashes >= ALLOWED_CRASHES) {
/* making pdev NULL so that externally start is not called */
priv.pdev = NULL;
dev_crit(dev, "ADSP has crashed too many times(%d)\n",
data->adsp_num_crashes);
return;
}
if (nvadsp_os_start())
dev_crit(dev, "Unable to restart ADSP OS\n");
}
static irqreturn_t adsp_wfi_handler(int irq, void *arg)
{
struct nvadsp_os_data *data = arg;
struct device *dev = &data->pdev->dev;
dev_dbg(dev, "%s\n", __func__);
complete(&entered_wfi);
return IRQ_HANDLED;
}
static irqreturn_t adsp_wdt_handler(int irq, void *arg)
{
struct nvadsp_os_data *data = arg;
struct nvadsp_drv_data *drv_data;
struct device *dev = &data->pdev->dev;
drv_data = platform_get_drvdata(data->pdev);
drv_data->adsp_crashed = true;
wake_up_interruptible(&drv_data->adsp_health_waitq);
if (!drv_data->adsp_unit_fpga) {
dev_crit(dev, "ADSP OS Hanged or Crashed! Restarting...\n");
schedule_work(&data->restart_os_work);
} else {
dev_crit(dev, "ADSP OS Hanged or Crashed!\n");
}
return IRQ_HANDLED;
}
void nvadsp_get_os_version(char *buf, int buf_size)
{
struct nvadsp_drv_data *drv_data;
struct nvadsp_shared_mem *shared_mem;
struct nvadsp_os_info *os_info;
memset(buf, 0, buf_size);
if (!priv.pdev)
return;
drv_data = platform_get_drvdata(priv.pdev);
shared_mem = drv_data->shared_adsp_os_data;
if (shared_mem) {
os_info = &shared_mem->os_info;
strlcpy(buf, os_info->version, buf_size);
} else {
strlcpy(buf, "unavailable", buf_size);
}
}
EXPORT_SYMBOL(nvadsp_get_os_version);
#ifdef CONFIG_DEBUG_FS
static int show_os_version(struct seq_file *s, void *data)
{
char ver_buf[MAX_OS_VERSION_BUF] = "";
nvadsp_get_os_version(ver_buf, MAX_OS_VERSION_BUF);
seq_printf(s, "version=\"%s\"\n", ver_buf);
return 0;
}
static int os_version_open(struct inode *inode, struct file *file)
{
return single_open(file, show_os_version, inode->i_private);
}
static const struct file_operations version_fops = {
.open = os_version_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#define RO_MODE S_IRUSR
static int adsp_create_os_version(struct dentry *adsp_debugfs_root)
{
struct device *dev = &priv.pdev->dev;
struct dentry *d;
d = debugfs_create_file("adspos_version", RO_MODE, adsp_debugfs_root,
NULL, &version_fops);
if (!d) {
dev_err(dev, "failed to create adsp_version\n");
return -EINVAL;
}
return 0;
}
static unsigned int adsp_health_poll(struct file *file,
poll_table *wait)
{
struct nvadsp_drv_data *drv_data = platform_get_drvdata(priv.pdev);
poll_wait(file, &drv_data->adsp_health_waitq, wait);
if (drv_data->adsp_crashed)
return POLLIN | POLLRDNORM;
return 0;
}
static const struct file_operations adsp_health_fops = {
.poll = adsp_health_poll,
};
static int adsp_create_adsp_health(struct dentry *adsp_debugfs_root)
{
struct device *dev = &priv.pdev->dev;
struct dentry *d;
d = debugfs_create_file("adsp_health", RO_MODE, adsp_debugfs_root,
NULL, &adsp_health_fops);
if (!d) {
dev_err(dev, "failed to create adsp_health\n");
return -EINVAL;
}
return 0;
}
#endif
static ssize_t tegrafw_read_adsp(struct device *dev,
char *data, size_t size)
{
nvadsp_get_os_version(data, size);
return strlen(data);
}
int __init nvadsp_os_probe(struct platform_device *pdev)
{
struct nvadsp_drv_data *drv_data = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
uint64_t dma_mask;
#endif
uint16_t com_mid = ADSP_COM_MBOX_ID;
int ret = 0;
priv.unit_fpga_reset_reg = drv_data->base_regs[UNIT_FPGA_RST];
priv.hwmailbox_base = drv_data->base_regs[hwmb_reg_idx()];
priv.dram_region = drv_data->dram_region;
priv.adsp_os_addr = drv_data->adsp_mem[ADSP_OS_ADDR];
priv.adsp_os_size = drv_data->adsp_mem[ADSP_OS_SIZE];
priv.app_alloc_addr = drv_data->adsp_mem[ADSP_APP_ADDR];
priv.app_size = drv_data->adsp_mem[ADSP_APP_SIZE];
if (of_device_is_compatible(dev->of_node, "nvidia,tegra210-adsp")) {
drv_data->assert_adsp = __assert_adsp;
drv_data->deassert_adsp = __deassert_adsp;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
ret = of_property_read_u64(dev->of_node, "dma-mask", &dma_mask);
if (ret) {
dev_err(&pdev->dev, "Missing property dma-mask\n");
goto end;
} else {
dma_set_mask_and_coherent(&pdev->dev, dma_mask);
}
#endif
ret = nvadsp_os_init(pdev);
if (ret) {
dev_err(dev, "failed to init os\n");
goto end;
}
ret = nvadsp_mbox_open(&adsp_com_mbox, &com_mid, "adsp_com_mbox",
NULL, NULL);
if (ret) {
dev_err(dev, "failed to open adsp com mbox\n");
goto end;
}
INIT_WORK(&priv.restart_os_work, nvadsp_os_restart);
mutex_init(&priv.fw_load_lock);
mutex_init(&priv.os_run_lock);
priv.pdev = pdev;
#ifdef CONFIG_DEBUG_FS
priv.logger.dev = &pdev->dev;
if (adsp_create_debug_logger(drv_data->adsp_debugfs_root))
dev_err(dev, "unable to create adsp debug logger file\n");
#ifdef CONFIG_TEGRA_ADSP_CONSOLE
priv.console.dev = &pdev->dev;
if (adsp_create_cnsl(drv_data->adsp_debugfs_root, &priv.console))
dev_err(dev, "unable to create adsp console file\n");
#endif /* CONFIG_TEGRA_ADSP_CONSOLE */
if (adsp_create_os_version(drv_data->adsp_debugfs_root))
dev_err(dev, "unable to create adsp_version file\n");
if (adsp_create_adsp_health(drv_data->adsp_debugfs_root))
dev_err(dev, "unable to create adsp_health file\n");
drv_data->adsp_crashed = false;
init_waitqueue_head(&drv_data->adsp_health_waitq);
#endif /* CONFIG_DEBUG_FS */
devm_tegrafw_register(dev, "APE", TFW_DONT_CACHE,
tegrafw_read_adsp, NULL);
end:
return ret;
}