/* SPDX-License-Identifier: GPL-2.0+ */ #include "linux/kernel.h" #include #include #include "linux/string.h" #include #include #include "ipq-avm.h" #define SKIP_UNTIL_SPACE(a) { while (*(a) && *(a) != ' ' && *(a) != '\t') (a)++; } #define SKIP_UNTIL_CHAR(a, character) { while (*(a) && *(a) != (character)) (a)++; } /** * format ([~],)... */ static const char *extract_param(const char *bitformat, char *name, int name_len, unsigned int *start_bit, unsigned int *end_bit, unsigned int *neg) { const char *p, *end_separate; int len; if (bitformat == NULL) { return NULL; } SKIP_SPACE(bitformat); p = bitformat; end_separate = p; SKIP_UNTIL_CHAR(end_separate, ')'); if (*end_separate != ')') { return NULL; } SKIP_UNTIL_CHAR(p, '('); if (p > end_separate || *p != '(') { return NULL; } len = min_t(size_t, p - bitformat, name_len - 1); if (len) { memcpy(name, bitformat, len); name[len] = 0; } p++; SKIP_SPACE(p); if (*p == '~') { *neg = 1; p++; } else { *neg = 0; } sscanf(p, "%u", start_bit); SKIP_UNTIL_CHAR(p, ','); if (p > end_separate) { *end_bit = *start_bit; } else { p++; SKIP_SPACE(p); sscanf(p, "%u", end_bit); if (*end_bit < *start_bit) { unsigned int tmp; tmp = *end_bit; *end_bit = *start_bit; *start_bit = tmp; } } return end_separate + 1; } #define PREFIX_IPQ8074_STRING "8074_" #define PREFIX_IPQ4019_STR "4019_" /** */ char *snprint_register_bitformat(char *erg, int erg_len, unsigned int value, const char *bitformat) { unsigned int start_bit, end_bit, neg; char *start = erg; char name[64], *pname; snprintf_add(erg, erg_len, "0x%08x", value); while ((bitformat = extract_param(bitformat, name, sizeof(name), &start_bit, &end_bit, &neg))) { /*--- pr_info("%s %u %u %u\n", name, start_bit, end_bit, neg); ---*/ pname = name; if (strstr(name, PREFIX_IPQ4019_STR) == name) { if (!is_IPQ4019()) continue; pname = name + sizeof(PREFIX_IPQ4019_STR) - 1; } else if (strstr(name, PREFIX_IPQ8074_STRING) == name) { if (!is_IPQ807x()) continue; pname = name + sizeof(PREFIX_IPQ8074_STRING) - 1; } if (end_bit == start_bit) { if ((value & (1 << start_bit)) == ((!neg) << start_bit)) { snprintf_add(erg, erg_len, " %s", pname); } } else { snprintf_add(erg, erg_len, " %s=0x%x", pname, (value >> start_bit) & ((1 << (end_bit - start_bit + 1)) - 1)); } } return start; } /** */ int debugcmd_scan_args(const char *buf, unsigned int args[], int argc, int as_hex) { int i, scanned = 0; if (buf == NULL) { return 0; } for (i = 0; i < argc; i++) { if (*buf) { const char *format; SKIP_SPACE(buf); if (as_hex) { format = "%x"; } else { format = "%u"; } sscanf(buf, format, &args[i]); scanned++; SKIP_UNTIL_SPACE(buf); } } return scanned; } /** * cmd_table: letzte Eintrag mit cmd: NULL! * ret: type of table */ unsigned char debugcmd_get_param(const struct _debugcmd_profile *pcmd_table, const char **_p, unsigned int args[], unsigned int max_args, unsigned int *scanned) { unsigned int i; const char *p; for (i = 0; pcmd_table[i].cmd != NULL; i++) { const struct _debugcmd_profile *pcmd = &pcmd_table[i]; p = strstr(*_p, pcmd->cmd); if (p) { p += strlen(pcmd->cmd); if (pcmd->argc > max_args) pr_err("%s: max_args=%u smaller than argc=%u\n", __func__, max_args, pcmd->argc); else max_args = pcmd->argc; *scanned = debugcmd_scan_args(p, args, max_args, pcmd->ashex); return pcmd->type; } } for (i = 0; pcmd_table[i].cmd != NULL; i++) { const struct _debugcmd_profile *pcmd = &pcmd_table[i]; pr_err("\t%s %s\n", pcmd->cmd, pcmd->help ? pcmd->help : ""); } return '?'; } static DEFINE_SPINLOCK(fsclk_lock); /** * it is possible to detect toggeling without changing gpio-function-mode */ static unsigned long measure_pin_toggeling_per_timewindow(struct gpio_chip *pgpio_chip, unsigned int offset, unsigned long msecs) { unsigned int value, oldvalue = 0; unsigned long long end_time_diff; unsigned long end_time, toggle = 0; int (*pgpio_get)(struct gpio_chip *chip, unsigned int offset) = pgpio_chip->get; end_time_diff = (unsigned long long)avm_get_cyclefreq() * msecs; do_div(end_time_diff, 1000); oldvalue = value = pgpio_get(pgpio_chip, offset); end_time = avm_get_cycles() + (unsigned long)end_time_diff; for (;;) { value = pgpio_get(pgpio_chip, offset); if (value != oldvalue) { oldvalue = value; toggle++; } if (avm_get_cycles() >= end_time) break; } return toggle; } #define print_output(txt, txt_size, args...) ((txt) ? snprintf(txt, txt_size, args) : pr_err(args)) #define MEASURE_TIME_MS 100 #define VALUE_TO_FREQ(number) ((number) * (1000 / MEASURE_TIME_MS) / 2) /** * \brief: * measure clk and fs for tdm-interface * ret: 0 ok */ int measure_tdmclkfs(struct gpio_chip *pgpio_chip, const char *prefix, char *txt, size_t txt_size) { unsigned long flags; int ret = 0; unsigned long sum_clk, sum_fs, clk_freq, fs_freq; if (pgpio_chip == NULL || pgpio_chip->get == NULL) { print_output(txt, txt_size, "%sno valid gpio_chip for tmd-clk and tdm-fs-pins - please add to devicetree\n", prefix); return (-EINVAL); } spin_lock_irqsave(&fsclk_lock, flags); sum_fs = measure_pin_toggeling_per_timewindow(pgpio_chip, 0, MEASURE_TIME_MS); sum_clk = measure_pin_toggeling_per_timewindow(pgpio_chip, 1, MEASURE_TIME_MS); spin_unlock_irqrestore(&fsclk_lock, flags); clk_freq = VALUE_TO_FREQ(sum_clk); fs_freq = VALUE_TO_FREQ(sum_fs); print_output(txt, txt_size, "%sTDM: FS: %lu Hz CLK: %lu Hz\n", prefix, fs_freq, clk_freq); if (fs_freq <= (8000 / 2)) ret = 1; if (clk_freq <= (1024000 / 2)) ret |= 2; return ret; }