/* * tfa_log.c tfa98xx logging in sysfs * * Copyright (c) 2015 NXP Semiconductors * * Author: Michael Kim */ #include #include #include #include #include #include #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, FILESIZE_LOG, "data error"); else size = snprintf(buf, FILESIZE_LOG, "%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, FILESIZE_LOG, "data error"); else size = snprintf(buf, FILESIZE_LOG, "%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, FILESIZE_LOG, "data error"); else size = snprintf(buf, FILESIZE_LOG, "%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, FILESIZE_LOG, "data error"); else size = snprintf(buf, FILESIZE_LOG, "%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, FILESIZE_LOG, "data error"); else size = snprintf(buf, FILESIZE_LOG, "%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");