blob: 4603f58350af0e19e8f5bd28f7359b338dea1b5f [file] [log] [blame]
/*
* tfa_log.c tfa98xx logging in sysfs
*
* Copyright (c) 2015 NXP Semiconductors
*
* Author: Michael Kim <michael.kim@nxp.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include "tfa98xx_tfafieldnames.h"
#include "tfa_internal.h"
#include "tfa_service.h"
#include "tfa_container.h"
#include "tfa98xx_genregs_N1C.h"
#define TFA_CLASS_NAME "nxp"
#define TFA_LOG_DEV_NAME "tfa_log"
#define TFA_LOG_MAX_COUNT 4
#define DESC_MAXX_LOG "maxium of X"
#define DESC_MAXT_LOG "maximum of T"
#define DESC_OVERXMAX_COUNT "counter of X > Xmax"
#define DESC_OVERTMAX_COUNT "counter of T > Tmax"
#define TFA_LOG_IN_SEPARATE_NODES
#if defined(TFA_LOG_IN_SEPARATE_NODES)
#define FILESIZE_LOG 10
#else
#define FILESIZE_LOG (10 * TFA_LOG_MAX_COUNT)
#endif
/* ---------------------------------------------------------------------- */
#if defined(TFA_LOG_IN_SEPARATE_NODES)
static ssize_t tfa_data_maxx_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t tfa_data_maxx_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size);
static DEVICE_ATTR(data_maxx, S_IRUGO | S_IWUSR | S_IWGRP,
tfa_data_maxx_show, tfa_data_maxx_store);
static ssize_t tfa_data_maxt_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t tfa_data_maxt_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size);
static DEVICE_ATTR(data_maxt, S_IRUGO | S_IWUSR | S_IWGRP,
tfa_data_maxt_show, tfa_data_maxt_store);
static ssize_t tfa_count_overxmax_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t tfa_count_overxmax_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size);
static DEVICE_ATTR(count_overxmax, S_IRUGO | S_IWUSR | S_IWGRP,
tfa_count_overxmax_show, tfa_count_overxmax_store);
static ssize_t tfa_count_overtmax_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t tfa_count_overtmax_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size);
static DEVICE_ATTR(count_overtmax, S_IRUGO | S_IWUSR | S_IWGRP,
tfa_count_overtmax_show, tfa_count_overtmax_store);
#else
static ssize_t tfa_data_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t tfa_data_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size);
static DEVICE_ATTR(data, S_IRUGO | S_IWUSR | S_IWGRP,
tfa_data_show, tfa_data_store);
#endif
static ssize_t tfa_log_enable_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t tfa_log_enable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size);
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
tfa_log_enable_show, tfa_log_enable_store);
/*
* to check the data in debug log, in the middle by force
* without hurting scheme to reset after reading
*/
static ssize_t tfa_log_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t tfa_log_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size);
static DEVICE_ATTR(log, S_IRUGO | S_IWUSR | S_IWGRP,
tfa_log_show, tfa_log_store);
static struct attribute *tfa_log_attr[] = {
#if defined(TFA_LOG_IN_SEPARATE_NODES)
&dev_attr_data_maxx.attr,
&dev_attr_data_maxt.attr,
&dev_attr_count_overxmax.attr,
&dev_attr_count_overtmax.attr,
#else
&dev_attr_data.attr,
#endif
&dev_attr_enable.attr,
&dev_attr_log.attr,
NULL,
};
static struct attribute_group tfa_log_attr_grp = {
.attrs = tfa_log_attr,
};
struct tfa_log {
char *desc;
uint16_t prev_value;
bool is_max;
bool is_counter;
bool is_dirty;
};
/* ---------------------------------------------------------------------- */
extern struct class *g_nxp_class;
struct device *g_tfa_log_dev;
static int cur_status;
static struct tfa_log blackbox[TFA_LOG_MAX_COUNT];
static bool blackbox_enabled;
/* ---------------------------------------------------------------------- */
/* temporarily until API is ready for driver */
static enum tfa98xx_error
tfa_read_log(tfa98xx_handle_t handle,
uint16_t index, uint16_t *value, bool reset);
static enum tfa98xx_error
tfa_read_log(tfa98xx_handle_t handle,
uint16_t index, uint16_t *value, bool reset)
{
pr_info("%s [%d]: %s\n", __func__, index, blackbox[index].desc);
if (blackbox[index].is_dirty) {
pr_info("%s: it's read before updated\n", __func__);
*value = 0; /* no meaningful data to be updated */
return TFA98XX_ERROR_OK;
}
*value = blackbox[index].prev_value;
if (*value == 0xffff) {
pr_info("%s: invalid data\n", __func__);
return TFA98XX_ERROR_BAD_PARAMETER;
}
if (reset) {
/* reset the last data */
blackbox[index].prev_value = 0;
blackbox[index].is_dirty = 1;
}
return TFA98XX_ERROR_OK;
}
static int tfa_configure_log(void)
{
int idx, devcount = tfa98xx_cnt_max_device();
enum tfa98xx_error err;
int is_handle_open = 0;
uint8_t cmd_buf[3];
cmd_buf[0] = 0;
cmd_buf[1] = 0;
/* 0 - disable logger, 1 - enable logger */
cmd_buf[2] = (blackbox_enabled) ? 1 : 0;
for (idx = 0; idx < devcount; idx++) {
pr_info("%s: set blackbox (%d)\n",
__func__, blackbox_enabled);
is_handle_open = tfa98xx_handle_is_open(idx);
if (!is_handle_open) {
err = tfa98xx_open(idx);
if (err) {
pr_info("cannot open handle (%d)\n", err);
return err;
}
}
err = tfa_dsp_cmd_id_write
(idx, MODULE_SPEAKERBOOST,
SB_PARAM_SET_DATA_LOGGER, 3, cmd_buf);
if (err)
pr_err("%s: error in setting blackbox, err = %d\n",
__func__, err);
if (!is_handle_open)
tfa98xx_close(idx);
}
return TFA98XX_ERROR_OK;
}
static int tfa_update_log(void)
{
int i, idx, devcount = tfa98xx_cnt_max_device();
enum tfa98xx_error err;
int is_handle_open = 0;
uint8_t cmd_buf[(TFA_LOG_MAX_COUNT + 1) * TFACONT_MAXDEVS * 3];
int data[(TFA_LOG_MAX_COUNT + 1) * TFACONT_MAXDEVS];
int read_size;
if (!blackbox_enabled) {
pr_info("%s: blackbox is inactive\n", __func__);
return TFA98XX_ERROR_OK;
}
read_size = (TFA_LOG_MAX_COUNT + 1) * devcount * 3;
for (idx = 0; idx < devcount; idx++) {
pr_info("%s: read from blackbox\n", __func__);
is_handle_open = tfa98xx_handle_is_open(idx);
if (!is_handle_open) {
err = tfa98xx_open(idx);
if (err) {
pr_info("cannot open handle (%d)\n", err);
return err;
}
}
err = tfa_dsp_cmd_id_write_read
(idx, MODULE_SPEAKERBOOST,
SB_PARAM_GET_DATA_LOGGER,
read_size, cmd_buf);
if (err) {
pr_err("%s: failed to read data from blackbox, err = %d\n",
__func__, err);
return err;
}
tfa98xx_convert_bytes2data(read_size, cmd_buf, data);
/* maximum x */
data[1] = (int)(data[1] / (TFA2_FW_X_DATA_SCALE / 1000));
/* maximum t */
data[2] = (int)(data[2] / TFA2_FW_T_DATA_SCALE);
for (i = 0; i < TFA_LOG_MAX_COUNT; i++)
pr_info("blackbox: data[%d] = %d\n", i, data[i + 1]);
if (!is_handle_open)
tfa98xx_close(idx);
}
for (i = 0; i < TFA_LOG_MAX_COUNT; i++) {
blackbox[i].is_dirty = 0;
if (blackbox[i].is_max)
blackbox[i].prev_value =
(data[i + 1] > blackbox[i].prev_value)
? data[i + 1] : blackbox[i].prev_value;
if (blackbox[i].is_counter)
blackbox[i].prev_value += data[i + 1];
}
return TFA98XX_ERROR_OK;
}
/* ---------------------------------------------------------------------- */
#if defined(TFA_LOG_IN_SEPARATE_NODES)
static ssize_t tfa_data_maxx_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int idx, devcount = tfa98xx_cnt_max_device();
uint16_t value = 0xffff;
int size;
int ret = 0;
char read_string[FILESIZE_LOG] = {0};
pr_info("%s: begin\n", __func__);
pr_info("%s: read from driver: %s\n",
__func__, blackbox[0].desc);
for (idx = 0; idx < devcount; idx++) {
/* reset the data in driver after reading */
ret = tfa_read_log(idx, 0, &value, true);
if (ret) {
pr_info("%s: failed to read data from driver\n",
__func__);
continue;
}
if (idx == 0)
snprintf(read_string,
FILESIZE_LOG, "%d", value);
else
snprintf(read_string,
FILESIZE_LOG, "%s %d",
read_string, value);
}
pr_info("%s: %s\n", __func__, read_string);
if (ret)
size = snprintf(buf, 10 + 1, "data_error");
else
size = snprintf(buf,
strlen(read_string) + 1,
"%s", read_string);
if (size <= 0) {
pr_err("%s: failed to show in sysfs file", __func__);
return -EINVAL;
}
pr_info("%s: end (%d)\n", __func__, ret);
return size;
}
static ssize_t tfa_data_maxx_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
pr_info("%s: not allowed to write log data: %s\n",
__func__, blackbox[0].desc);
return size;
}
static ssize_t tfa_data_maxt_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int idx, devcount = tfa98xx_cnt_max_device();
uint16_t value = 0xffff;
int size;
int ret = 0;
char read_string[FILESIZE_LOG] = {0};
pr_info("%s: begin\n", __func__);
pr_info("%s: read from driver: %s\n",
__func__, blackbox[1].desc);
for (idx = 0; idx < devcount; idx++) {
/* reset the data in driver after reading */
ret = tfa_read_log(idx, 1, &value, true);
if (ret) {
pr_info("%s: failed to read data from driver\n",
__func__);
continue;
}
if (idx == 0)
snprintf(read_string,
FILESIZE_LOG, "%d", value);
else
snprintf(read_string,
FILESIZE_LOG, "%s %d",
read_string, value);
}
pr_info("%s: %s\n", __func__, read_string);
if (ret)
size = snprintf(buf, 10 + 1, "data_error");
else
size = snprintf(buf,
strlen(read_string) + 1,
"%s", read_string);
if (size <= 0) {
pr_err("%s: failed to show in sysfs file", __func__);
return -EINVAL;
}
pr_info("%s: end (%d)\n", __func__, ret);
return size;
}
static ssize_t tfa_data_maxt_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
pr_info("%s: not allowed to write log data: %s\n",
__func__, blackbox[1].desc);
return size;
}
static ssize_t tfa_count_overxmax_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int idx, devcount = tfa98xx_cnt_max_device();
uint16_t value = 0xffff;
int size;
int ret = 0;
char read_string[FILESIZE_LOG] = {0};
pr_info("%s: begin\n", __func__);
pr_info("%s: read from driver: %s\n",
__func__, blackbox[2].desc);
for (idx = 0; idx < devcount; idx++) {
/* reset the data in driver after reading */
ret = tfa_read_log(idx, 2, &value, true);
if (ret) {
pr_info("%s: failed to read data from driver\n",
__func__);
continue;
}
if (idx == 0)
snprintf(read_string,
FILESIZE_LOG, "%d", value);
else
snprintf(read_string,
FILESIZE_LOG, "%s %d",
read_string, value);
}
pr_info("%s: %s\n", __func__, read_string);
if (ret)
size = snprintf(buf, 10 + 1, "data_error");
else
size = snprintf(buf,
strlen(read_string) + 1,
"%s", read_string);
if (size <= 0) {
pr_err("%s: failed to show in sysfs file", __func__);
return -EINVAL;
}
pr_info("%s: end (%d)\n", __func__, ret);
return size;
}
static ssize_t tfa_count_overxmax_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
pr_info("%s: not allowed to write log data: %s\n",
__func__, blackbox[2].desc);
return size;
}
static ssize_t tfa_count_overtmax_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int idx, devcount = tfa98xx_cnt_max_device();
uint16_t value = 0xffff;
int size;
int ret = 0;
char read_string[FILESIZE_LOG] = {0};
pr_info("%s: begin\n", __func__);
pr_info("%s: read from driver: %s\n",
__func__, blackbox[3].desc);
for (idx = 0; idx < devcount; idx++) {
/* reset the data in driver after reading */
ret = tfa_read_log(idx, 3, &value, true);
if (ret) {
pr_info("%s: failed to read data from driver\n",
__func__);
continue;
}
if (idx == 0)
snprintf(read_string,
FILESIZE_LOG, "%d", value);
else
snprintf(read_string,
FILESIZE_LOG, "%s %d",
read_string, value);
}
pr_info("%s: %s\n", __func__, read_string);
if (ret)
size = snprintf(buf, 10 + 1, "data_error");
else
size = snprintf(buf,
strlen(read_string) + 1,
"%s", read_string);
if (size <= 0) {
pr_err("%s: failed to show in sysfs file", __func__);
return -EINVAL;
}
pr_info("%s: end (%d)\n", __func__, ret);
return size;
}
static ssize_t tfa_count_overtmax_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
pr_info("%s: not allowed to write log data: %s\n",
__func__, blackbox[3].desc);
return size;
}
#else /* TFA_LOG_IN_SEPARATE_NODES */
static ssize_t tfa_data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int i, idx, devcount = tfa98xx_cnt_max_device();
uint16_t value[TFA_LOG_MAX_COUNT] = {0xffff};
int size;
int ret = 0;
char read_string[FILESIZE_LOG] = {0};
pr_info("%s: begin\n", __func__);
for (i = 0; i < TFA_LOG_MAX_COUNT; i++) {
pr_info("%s: read from driver: %s\n",
__func__, blackbox[i].desc);
for (idx = 0; idx < devcount; idx++) {
/* reset the data in driver after reading */
ret = tfa_read_log(idx, i, &value[i], true);
if (ret) {
pr_info("%s: failed to read data from driver\n",
__func__);
continue;
}
if (idx == 0)
snprintf(read_string,
FILESIZE_LOG, "%d", value[i]);
else
snprintf(read_string,
FILESIZE_LOG, "%s %d",
read_string, value[i]);
}
pr_info("%s: %s\n", __func__, read_string);
}
if (ret)
size = snprintf(buf, 10 + 1, "data_error");
else
size = snprintf(buf,
strlen(read_string) + 1,
"%s", read_string);
if (size <= 0) {
pr_err("%s: failed to show in sysfs file", __func__);
return -EINVAL;
}
pr_info("%s: end (%d)\n", __func__, ret);
return size;
}
static ssize_t tfa_data_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
pr_info("%s: not allowed to write log data\n",
__func__);
return size;
}
#endif /* TFA_LOG_IN_SEPARATE_NODES */
static ssize_t tfa_log_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int size;
size = snprintf(buf, 25, "%s\n", blackbox_enabled ?
"blackbox is active" : "blackbox is inactive");
return size;
}
static ssize_t tfa_log_enable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int ret = 0;
int status;
/* Compare string, excluding the trailing \0 and the potentials eol */
if (!sysfs_streq(buf, "1") && !sysfs_streq(buf, "0")) {
pr_debug("%s: invalid value to write\n",
__func__);
return -EINVAL;
}
ret = kstrtou32(buf, 10, &status);
blackbox_enabled = (status) ? true : false;
tfa_configure_log();
return size;
}
static ssize_t tfa_log_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int size;
size = snprintf(buf, 25, "%s\n", cur_status ?
"sysfs log is active" : "sysfs log is inactive");
return size;
}
static ssize_t tfa_log_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int i, idx, devcount = tfa98xx_cnt_max_device();
uint16_t value[TFA_LOG_MAX_COUNT] = {0xffff};
int ret = 0, ret2 = 0;
int status;
char read_string[FILESIZE_LOG] = {0};
/* Compare string, excluding the trailing \0 and the potentials eol */
if (!sysfs_streq(buf, "1") && !sysfs_streq(buf, "0")) {
pr_debug("%s: invalid value to write\n",
__func__);
return -EINVAL;
}
ret = kstrtou32(buf, 10, &status);
if (!status) {
pr_info("%s: do nothing\n", __func__);
return -EINVAL;
}
if (cur_status)
pr_info("%s: prior writing still runs\n", __func__);
pr_info("%s: begin\n", __func__);
cur_status = status; /* run - changed to active */
for (i = 0; i < TFA_LOG_MAX_COUNT; i++) {
pr_info("%s: read from driver: %s\n",
__func__, blackbox[i].desc);
for (idx = 0; idx < devcount; idx++) {
/* reset the data in driver after reading */
ret2 = tfa_read_log(idx, i, &value[i], false);
if (ret2) {
pr_info("%s: failed to read data from driver\n",
__func__);
continue;
}
cur_status = 0; /* done - changed to inactive */
if (idx == 0)
snprintf(read_string,
FILESIZE_LOG, "%d",
blackbox[i].prev_value);
else
snprintf(read_string,
FILESIZE_LOG, "%s %d",
read_string, blackbox[i].prev_value);
}
pr_info("%s: %s\n", __func__, read_string);
}
pr_info("%s: end (%d)\n", __func__, ret2);
return size;
}
static int __init tfa98xx_log_init(void)
{
int ret = 0;
if (!g_nxp_class)
g_nxp_class = class_create(THIS_MODULE, TFA_CLASS_NAME);
if (g_nxp_class) {
g_tfa_log_dev = device_create(g_nxp_class,
NULL, 2, NULL, TFA_LOG_DEV_NAME);
if (!IS_ERR(g_tfa_log_dev)) {
ret = sysfs_create_group(&g_tfa_log_dev->kobj,
&tfa_log_attr_grp);
if (ret)
pr_err("%s: failed to create sysfs group. ret (%d)\n",
__func__, ret);
} else {
class_destroy(g_nxp_class);
}
}
#if defined(TFA_BLACKBOX_LOGGING)
blackbox_enabled = true; /* enable by default */
#else
blackbox_enabled = false; /* control with sysfs node */
#endif
tfa_log_register(tfa_configure_log, tfa_update_log);
/* maximum x */
blackbox[0].desc = DESC_MAXX_LOG;
blackbox[0].is_max = true;
blackbox[0].is_counter = false;
/* maximum t */
blackbox[1].desc = DESC_MAXT_LOG;
blackbox[1].is_max = true;
blackbox[1].is_counter = false;
/* counter x > x_max */
blackbox[2].desc = DESC_OVERXMAX_COUNT;
blackbox[2].is_max = false;
blackbox[2].is_counter = true;
/* counter t > t_max */
blackbox[3].desc = DESC_OVERTMAX_COUNT;
blackbox[3].is_max = false;
blackbox[3].is_counter = true;
pr_info("%s: g_nxp_class=%p\n", __func__, g_nxp_class);
pr_info("%s: initialized\n", __func__);
return ret;
}
module_init(tfa98xx_log_init);
static void __exit tfa98xx_log_exit(void)
{
device_destroy(g_nxp_class, 2);
class_destroy(g_nxp_class);
pr_info("exited\n");
}
module_exit(tfa98xx_log_exit);
MODULE_DESCRIPTION("ASoC TFA98XX logging driver");
MODULE_LICENSE("GPL");