| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2019 Amlogic, Inc. All rights reserved. |
| * |
| */ |
| |
| #include <linux/time64.h> |
| #include <linux/ktime.h> |
| #include <linux/timekeeping.h> |
| #include <linux/workqueue.h> |
| |
| #include "debug.h" |
| |
| #ifdef DEBUG_IRQ |
| |
| #define DUMP_SIZE 128 |
| #define DEBUG_ITEMS 3 |
| |
| struct ddr_debug { |
| struct work_struct work; |
| unsigned int *ddr_table; |
| unsigned int *dump_buffer; |
| unsigned int ddr_counter; |
| unsigned int ddr_id; |
| unsigned int buffer_size; |
| bool ddr_type; |
| }; |
| |
| struct ddr_debug ddr[2][DDRMAX]; |
| static bool ddr_debug_enable; |
| |
| static inline unsigned int get_tick_count(void) |
| { |
| struct timespec ts; |
| |
| ktime_get_ts(&ts); |
| return (int)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); |
| } |
| |
| static void dump_data(struct work_struct *p_work) |
| { |
| struct ddr_debug *pddr; |
| int i, diff, average, valid_space = 0, hwptr, swptr, buffer_size; |
| |
| pddr = container_of(p_work, struct ddr_debug, work); |
| if (!pddr) |
| return; |
| buffer_size = pddr->buffer_size; |
| |
| for (i = 0; i < (DUMP_SIZE - 1); i++) { |
| diff = *(pddr->dump_buffer + (i + 1) * DEBUG_ITEMS) - |
| *(pddr->dump_buffer + i * DEBUG_ITEMS); |
| hwptr = *(pddr->dump_buffer + i * DEBUG_ITEMS + 1); |
| swptr = *(pddr->dump_buffer + i * DEBUG_ITEMS + 2); |
| |
| if (pddr->ddr_type == FRDDR) |
| valid_space = (swptr - hwptr + buffer_size) % buffer_size; |
| else |
| valid_space = (hwptr - swptr + buffer_size) % buffer_size; |
| |
| pr_info("%s - %d: [%d] %d, diff %d, hwptr %d, swptr %d, valid %d\n", |
| pddr->ddr_type ? "FRDDR" : "TODDR", |
| pddr->ddr_id, i, *(pddr->dump_buffer + i * DEBUG_ITEMS), |
| diff, hwptr, swptr, valid_space); |
| } |
| average = *(pddr->dump_buffer + (DUMP_SIZE - 1) * DEBUG_ITEMS) - *pddr->dump_buffer; |
| pr_info("%s - %d: total %d, time average %d\n", |
| pddr->ddr_type ? "FRDDR" : "TODDR", |
| pddr->ddr_id, average, (average / (DUMP_SIZE - 1))); |
| } |
| |
| void get_time_stamp(bool ddr_type, unsigned int ddr_id, unsigned int hwptr, |
| unsigned int swptr, unsigned int buffer_size) |
| { |
| struct ddr_debug *pddr = &ddr[ddr_type][ddr_id]; |
| unsigned int *ptable; |
| |
| if (ddr_debug_enable) { |
| /* if enable debugging, malloc debug buffer*/ |
| if (!pddr->ddr_table) { |
| pddr->ddr_table = kmalloc_array(DUMP_SIZE * DEBUG_ITEMS, |
| sizeof(int), GFP_KERNEL); |
| pddr->dump_buffer = kmalloc_array(DUMP_SIZE * DEBUG_ITEMS, |
| sizeof(int), GFP_KERNEL); |
| pddr->ddr_counter = 0; |
| pddr->ddr_type = ddr_type; |
| pddr->ddr_id = ddr_id; |
| if (!pddr->ddr_table || !pddr->dump_buffer) |
| return; |
| INIT_WORK(&pddr->work, dump_data); |
| pr_info("start debug ddr type [%d], id [%d]\n", ddr_type, ddr_id); |
| pddr->buffer_size = buffer_size; |
| } |
| |
| ptable = pddr->ddr_table + (pddr->ddr_counter * DEBUG_ITEMS); |
| *ptable++ = get_tick_count(); /* system time */ |
| *ptable++ = hwptr; /* hw pointer */ |
| *ptable = swptr; /* app pointer */ |
| pddr->ddr_counter++; |
| if (pddr->ddr_counter == DUMP_SIZE) { |
| pddr->ddr_counter = 0; |
| memcpy(pddr->dump_buffer, pddr->ddr_table, |
| (DUMP_SIZE * sizeof(int) * DEBUG_ITEMS)); |
| schedule_work(&pddr->work); |
| } |
| } else if (pddr->ddr_table) { |
| /* if disable debugging, free debug buffer*/ |
| cancel_work_sync(&pddr->work); |
| kfree(pddr->ddr_table); |
| pddr->ddr_table = NULL; |
| kfree(pddr->dump_buffer); |
| pddr->dump_buffer = NULL; |
| pddr->ddr_counter = 0; |
| pddr->ddr_type = 0; |
| pddr->ddr_id = 0; |
| pr_info("stop debug ddr type [%d], id [%d]\n", ddr_type, ddr_id); |
| } |
| } |
| |
| void set_ddr_debug(bool debug_enable) |
| { |
| pr_info("debug ddr %s\n", debug_enable ? "enable" : "disable"); |
| ddr_debug_enable = debug_enable; |
| } |
| |
| bool get_ddr_debug(void) |
| { |
| return ddr_debug_enable; |
| } |
| |
| #endif |
| |