466 lines
13 KiB
C
Executable File
466 lines
13 KiB
C
Executable File
/* sound/soc/samsung/abox/abox_dbg.c
|
|
*
|
|
* ALSA SoC Audio Layer - Samsung Abox Debug driver
|
|
*
|
|
* Copyright (c) 2016 Samsung Electronics Co. Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
/* #define DEBUG */
|
|
#include <linux/io.h>
|
|
#include <linux/device.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/module.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/sched/clock.h>
|
|
#include "abox_dbg.h"
|
|
#include "abox_gic.h"
|
|
|
|
#define ABOX_DBG_DUMP_MAGIC_SRAM 0x3935303030504D44ull /* DMP00059 */
|
|
#define ABOX_DBG_DUMP_MAGIC_DRAM 0x3231303038504D44ull /* DMP80012 */
|
|
#define ABOX_DBG_DUMP_MAGIC_SFR 0x5246533030504D44ull /* DMP00SFR */
|
|
|
|
static struct dentry *abox_dbg_root_dir __read_mostly;
|
|
|
|
struct dentry *abox_dbg_get_root_dir(void)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
|
|
if (abox_dbg_root_dir == NULL)
|
|
abox_dbg_root_dir = debugfs_create_dir("abox", NULL);
|
|
|
|
return abox_dbg_root_dir;
|
|
}
|
|
|
|
void abox_dbg_print_gpr_from_addr(struct device *dev, struct abox_data *data,
|
|
unsigned int *addr)
|
|
{
|
|
int i;
|
|
char version[4];
|
|
|
|
memcpy(version, &data->calliope_version, sizeof(version));
|
|
|
|
dev_info(dev, "========================================\n");
|
|
dev_info(dev, "A-Box CPU register dump (%c%c%c%c)\n",
|
|
version[3], version[2], version[1], version[0]);
|
|
dev_info(dev, "----------------------------------------\n");
|
|
for (i = 0; i <= 14; i++)
|
|
dev_info(dev, "CA7_R%02d : %08x\n", i, *addr++);
|
|
dev_info(dev, "CA7_PC : %08x\n", *addr++);
|
|
dev_info(dev, "========================================\n");
|
|
}
|
|
|
|
void abox_dbg_print_gpr(struct device *dev, struct abox_data *data)
|
|
{
|
|
int i;
|
|
char version[4];
|
|
|
|
memcpy(version, &data->calliope_version, sizeof(version));
|
|
|
|
dev_info(dev, "========================================\n");
|
|
dev_info(dev, "A-Box CPU register dump (%c%c%c%c)\n",
|
|
version[3], version[2], version[1], version[0]);
|
|
dev_info(dev, "----------------------------------------\n");
|
|
for (i = 0; i <= 14; i++) {
|
|
dev_info(dev, "CA7_R%02d : %08x\n", i,
|
|
readl(data->sfr_base + ABOX_CPU_R(i)));
|
|
}
|
|
dev_info(dev, "CA7_PC : %08x\n",
|
|
readl(data->sfr_base + ABOX_CPU_PC));
|
|
dev_info(dev, "CA7_L2C_STATUS : %08x\n",
|
|
readl(data->sfr_base + ABOX_CPU_L2C_STATUS));
|
|
dev_info(dev, "========================================\n");
|
|
}
|
|
|
|
struct abox_dbg_dump_sram {
|
|
unsigned long long magic;
|
|
char dump[SZ_512K];
|
|
} __packed;
|
|
|
|
struct abox_dbg_dump_dram {
|
|
unsigned long long magic;
|
|
char dump[DRAM_FIRMWARE_SIZE];
|
|
} __packed;
|
|
|
|
struct abox_dbg_dump_sfr {
|
|
unsigned long long magic;
|
|
u32 dump[SZ_64K / sizeof(u32)];
|
|
} __packed;
|
|
|
|
struct abox_dbg_dump {
|
|
struct abox_dbg_dump_sram sram;
|
|
struct abox_dbg_dump_dram dram;
|
|
struct abox_dbg_dump_sfr sfr;
|
|
u32 sfr_gic_gicd[SZ_4K / sizeof(u32)];
|
|
unsigned int gpr[17];
|
|
long long time;
|
|
char reason[SZ_32];
|
|
} __packed;
|
|
|
|
struct abox_dbg_dump_min {
|
|
struct abox_dbg_dump_sram sram;
|
|
struct abox_dbg_dump_sfr sfr;
|
|
u32 sfr_gic_gicd[SZ_4K / sizeof(u32)];
|
|
unsigned int gpr[17];
|
|
long long time;
|
|
char reason[SZ_32];
|
|
} __packed;
|
|
|
|
static struct abox_dbg_dump (*p_abox_dbg_dump)[ABOX_DBG_DUMP_COUNT];
|
|
static struct abox_dbg_dump_min (*p_abox_dbg_dump_min)[ABOX_DBG_DUMP_COUNT];
|
|
static struct reserved_mem *abox_rmem;
|
|
|
|
static int __init abox_rmem_setup(struct reserved_mem *rmem)
|
|
{
|
|
pr_info("%s: base=%pa, size=%pa\n", __func__, &rmem->base, &rmem->size);
|
|
|
|
abox_rmem = rmem;
|
|
if (sizeof(*p_abox_dbg_dump) <= abox_rmem->size)
|
|
p_abox_dbg_dump = phys_to_virt(abox_rmem->base);
|
|
else if (sizeof(*p_abox_dbg_dump_min) <= abox_rmem->size)
|
|
p_abox_dbg_dump_min = phys_to_virt(abox_rmem->base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
RESERVEDMEM_OF_DECLARE(abox_rmem, "exynos,abox_rmem", abox_rmem_setup);
|
|
|
|
void abox_dbg_dump_gpr_from_addr(struct device *dev, unsigned int *addr,
|
|
enum abox_dbg_dump_src src, const char *reason)
|
|
{
|
|
int i;
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
if (!abox_is_on()) {
|
|
dev_info(dev, "%s is skipped due to no power\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (p_abox_dbg_dump) {
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src];
|
|
|
|
p_dump->time = sched_clock();
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
for (i = 0; i <= 14; i++)
|
|
p_dump->gpr[i] = *addr++;
|
|
p_dump->gpr[i++] = *addr++;
|
|
} else if (p_abox_dbg_dump_min) {
|
|
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src];
|
|
|
|
p_dump->time = sched_clock();
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
for (i = 0; i <= 14; i++)
|
|
p_dump->gpr[i] = *addr++;
|
|
p_dump->gpr[i++] = *addr++;
|
|
}
|
|
}
|
|
|
|
void abox_dbg_dump_gpr(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason)
|
|
{
|
|
int i;
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
if (!abox_is_on()) {
|
|
dev_info(dev, "%s is skipped due to no power\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (p_abox_dbg_dump) {
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src];
|
|
|
|
p_dump->time = sched_clock();
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
for (i = 0; i <= 14; i++)
|
|
p_dump->gpr[i] = readl(data->sfr_base + ABOX_CPU_R(i));
|
|
p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_PC);
|
|
p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_L2C_STATUS);
|
|
} else if (p_abox_dbg_dump_min) {
|
|
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src];
|
|
|
|
p_dump->time = sched_clock();
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
for (i = 0; i <= 14; i++)
|
|
p_dump->gpr[i] = readl(data->sfr_base + ABOX_CPU_R(i));
|
|
p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_PC);
|
|
p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_L2C_STATUS);
|
|
}
|
|
}
|
|
|
|
void abox_dbg_dump_mem(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason)
|
|
{
|
|
struct abox_gic_data *gic_data = dev_get_drvdata(data->dev_gic);
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
if (!abox_is_on()) {
|
|
dev_info(dev, "%s is skipped due to no power\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (p_abox_dbg_dump) {
|
|
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src];
|
|
|
|
p_dump->time = sched_clock();
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
memcpy_fromio(p_dump->sram.dump, data->sram_base,
|
|
data->sram_size);
|
|
p_dump->sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM;
|
|
memcpy(p_dump->dram.dump, data->dram_base, DRAM_FIRMWARE_SIZE);
|
|
p_dump->dram.magic = ABOX_DBG_DUMP_MAGIC_DRAM;
|
|
memcpy_fromio(p_dump->sfr.dump, data->sfr_base,
|
|
sizeof(p_dump->sfr.dump));
|
|
p_dump->sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR;
|
|
memcpy_fromio(p_dump->sfr_gic_gicd, gic_data->gicd_base,
|
|
sizeof(p_dump->sfr_gic_gicd));
|
|
} else if (p_abox_dbg_dump_min) {
|
|
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src];
|
|
|
|
p_dump->time = sched_clock();
|
|
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
|
|
memcpy_fromio(p_dump->sram.dump, data->sram_base,
|
|
data->sram_size);
|
|
p_dump->sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM;
|
|
memcpy_fromio(p_dump->sfr.dump, data->sfr_base,
|
|
sizeof(p_dump->sfr.dump));
|
|
p_dump->sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR;
|
|
memcpy_fromio(p_dump->sfr_gic_gicd, gic_data->gicd_base,
|
|
sizeof(p_dump->sfr_gic_gicd));
|
|
}
|
|
}
|
|
|
|
void abox_dbg_dump_gpr_mem(struct device *dev, struct abox_data *data,
|
|
enum abox_dbg_dump_src src, const char *reason)
|
|
{
|
|
abox_dbg_dump_gpr(dev, data, src, reason);
|
|
abox_dbg_dump_mem(dev, data, src, reason);
|
|
}
|
|
|
|
struct abox_dbg_dump_simple {
|
|
struct abox_dbg_dump_sram sram;
|
|
struct abox_dbg_dump_sfr sfr;
|
|
u32 sfr_gic_gicd[SZ_4K / sizeof(u32)];
|
|
unsigned int gpr[17];
|
|
long long time;
|
|
char reason[SZ_32];
|
|
};
|
|
|
|
static struct abox_dbg_dump_simple abox_dump_simple;
|
|
|
|
void abox_dbg_dump_simple(struct device *dev, struct abox_data *data,
|
|
const char *reason)
|
|
{
|
|
struct abox_gic_data *gic_data = dev_get_drvdata(data->dev_gic);
|
|
int i;
|
|
|
|
dev_info(dev, "%s\n", __func__);
|
|
|
|
if (!abox_is_on()) {
|
|
dev_info(dev, "%s is skipped due to no power\n", __func__);
|
|
return;
|
|
}
|
|
|
|
abox_dump_simple.time = sched_clock();
|
|
strncpy(abox_dump_simple.reason, reason,
|
|
sizeof(abox_dump_simple.reason) - 1);
|
|
for (i = 0; i <= 14; i++)
|
|
abox_dump_simple.gpr[i] = readl(data->sfr_base + ABOX_CPU_R(i));
|
|
abox_dump_simple.gpr[i++] = readl(data->sfr_base + ABOX_CPU_PC);
|
|
abox_dump_simple.gpr[i++] = readl(data->sfr_base + ABOX_CPU_L2C_STATUS);
|
|
memcpy_fromio(abox_dump_simple.sram.dump, data->sram_base,
|
|
data->sram_size);
|
|
abox_dump_simple.sram.magic = ABOX_DBG_DUMP_MAGIC_SRAM;
|
|
memcpy_fromio(abox_dump_simple.sfr.dump, data->sfr_base,
|
|
sizeof(abox_dump_simple.sfr.dump));
|
|
abox_dump_simple.sfr.magic = ABOX_DBG_DUMP_MAGIC_SFR;
|
|
memcpy_fromio(abox_dump_simple.sfr_gic_gicd, gic_data->gicd_base,
|
|
sizeof(abox_dump_simple.sfr_gic_gicd));
|
|
}
|
|
|
|
static atomic_t abox_error_count = ATOMIC_INIT(0);
|
|
|
|
void abox_dbg_report_status(struct device *dev, bool ok)
|
|
{
|
|
char env[32] = {0,};
|
|
char *envp[2] = {env, NULL};
|
|
|
|
dev_info(dev, "%s\n", __func__);
|
|
|
|
if (ok)
|
|
atomic_set(&abox_error_count, 0);
|
|
else
|
|
atomic_inc(&abox_error_count);
|
|
|
|
snprintf(env, sizeof(env), "ERR_CNT=%d",
|
|
atomic_read(&abox_error_count));
|
|
kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
|
|
}
|
|
|
|
int abox_dbg_get_error_count(struct device *dev)
|
|
{
|
|
int count = atomic_read(&abox_error_count);
|
|
|
|
dev_dbg(dev, "%s: %d\n", __func__, count);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t calliope_sram_read(struct file *file, struct kobject *kobj,
|
|
struct bin_attribute *battr, char *buf,
|
|
loff_t off, size_t size)
|
|
{
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
struct device *dev_abox = dev->parent;
|
|
|
|
dev_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size);
|
|
|
|
if (pm_runtime_get_if_in_use(dev_abox) > 0) {
|
|
memcpy_fromio(buf, battr->private + off, size);
|
|
pm_runtime_put(dev_abox);
|
|
} else {
|
|
memset(buf, 0x0, size);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t calliope_dram_read(struct file *file, struct kobject *kobj,
|
|
struct bin_attribute *battr, char *buf,
|
|
loff_t off, size_t size)
|
|
{
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
|
|
dev_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size);
|
|
|
|
memcpy(buf, battr->private + off, size);
|
|
return size;
|
|
}
|
|
|
|
static ssize_t calliope_priv_read(struct file *file, struct kobject *kobj,
|
|
struct bin_attribute *battr, char *buf,
|
|
loff_t off, size_t size)
|
|
{
|
|
return calliope_dram_read(file, kobj, battr, buf, off, size);
|
|
}
|
|
|
|
/* size will be updated later */
|
|
static BIN_ATTR_RO(calliope_sram, 0);
|
|
static BIN_ATTR_RO(calliope_dram, DRAM_FIRMWARE_SIZE);
|
|
static BIN_ATTR_RO(calliope_priv, PRIVATE_SIZE);
|
|
static struct bin_attribute *calliope_bin_attrs[] = {
|
|
&bin_attr_calliope_sram,
|
|
&bin_attr_calliope_dram,
|
|
&bin_attr_calliope_priv,
|
|
};
|
|
|
|
static ssize_t gpr_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct abox_data *data = dev_get_drvdata(dev->parent);
|
|
char version[4];
|
|
char *pbuf = buf;
|
|
int i;
|
|
|
|
if (!abox_is_on()) {
|
|
dev_info(dev, "%s is skipped due to no power\n", __func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
memcpy(version, &data->calliope_version, sizeof(version));
|
|
|
|
pbuf += sprintf(pbuf, "========================================\n");
|
|
pbuf += sprintf(pbuf, "A-Box CPU register dump (%c%c%c%c)\n",
|
|
version[3], version[2], version[1], version[0]);
|
|
pbuf += sprintf(pbuf, "----------------------------------------\n");
|
|
for (i = 0; i <= 14; i++) {
|
|
pbuf += sprintf(pbuf, "CA7_R%02d : %08x\n", i,
|
|
readl(data->sfr_base + ABOX_CPU_R(i)));
|
|
}
|
|
pbuf += sprintf(pbuf, "CA7_PC : %08x\n",
|
|
readl(data->sfr_base + ABOX_CPU_PC));
|
|
pbuf += sprintf(pbuf, "CA7_L2C_STATUS : %08x\n",
|
|
readl(data->sfr_base + ABOX_CPU_L2C_STATUS));
|
|
pbuf += sprintf(pbuf, "========================================\n");
|
|
|
|
return pbuf - buf;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(gpr);
|
|
|
|
static int samsung_abox_debug_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device *abox_dev = dev->parent;
|
|
struct abox_data *data = dev_get_drvdata(abox_dev);
|
|
int i, ret;
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
if (abox_rmem == NULL)
|
|
return -ENOMEM;
|
|
|
|
dev_info(dev, "%s(%pa) is mapped on %p with size of %pa\n",
|
|
"dump buffer", &abox_rmem->base,
|
|
phys_to_virt(abox_rmem->base), &abox_rmem->size);
|
|
iommu_map(data->iommu_domain, IOVA_DUMP_BUFFER, abox_rmem->base,
|
|
abox_rmem->size, 0);
|
|
data->dump_base = phys_to_virt(abox_rmem->base);
|
|
data->dump_base_phys = abox_rmem->base;
|
|
ret = device_create_file(dev, &dev_attr_gpr);
|
|
bin_attr_calliope_sram.size = data->sram_size;
|
|
bin_attr_calliope_sram.private = data->sram_base;
|
|
bin_attr_calliope_dram.private = data->dram_base;
|
|
bin_attr_calliope_priv.private = data->priv_base;
|
|
for (i = 0; i < ARRAY_SIZE(calliope_bin_attrs); i++) {
|
|
struct bin_attribute *battr = calliope_bin_attrs[i];
|
|
|
|
ret = device_create_bin_file(dev, battr);
|
|
if (ret < 0)
|
|
dev_warn(dev, "Failed to create file: %s\n",
|
|
battr->attr.name);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int samsung_abox_debug_remove(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id samsung_abox_debug_match[] = {
|
|
{
|
|
.compatible = "samsung,abox-debug",
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, samsung_abox_debug_match);
|
|
|
|
static struct platform_driver samsung_abox_debug_driver = {
|
|
.probe = samsung_abox_debug_probe,
|
|
.remove = samsung_abox_debug_remove,
|
|
.driver = {
|
|
.name = "samsung-abox-debug",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(samsung_abox_debug_match),
|
|
},
|
|
};
|
|
|
|
module_platform_driver(samsung_abox_debug_driver);
|
|
|
|
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
|
|
MODULE_DESCRIPTION("Samsung ASoC A-Box Debug Driver");
|
|
MODULE_ALIAS("platform:samsung-abox-debug");
|
|
MODULE_LICENSE("GPL");
|