// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include "wmt_build_in_adapter.h" #include #include #include #include #include #include #include #include #include #include "conn_dbg.h" /*device tree mode*/ #ifdef CONFIG_OF #include #include #include #include #endif #include #include #define CONNADP_LOG_LOUD 4 #define CONNADP_LOG_DBG 3 #define CONNADP_LOG_INFO 2 #define CONNADP_LOG_WARN 1 #define CONNADP_LOG_ERR 0 /******************************************************************************* * Connsys adaptation layer logging utility ******************************************************************************/ static unsigned int gConnAdpDbgLvl = CONNADP_LOG_INFO; #define CONNADP_LOUD_FUNC(fmt, arg...) \ do { \ if (gConnAdpDbgLvl >= CONNADP_LOG_LOUD) \ pr_info("[L]%s:" fmt, __func__, ##arg); \ } while (0) #define CONNADP_DBG_FUNC(fmt, arg...) \ do { \ if (gConnAdpDbgLvl >= CONNADP_LOG_DBG) \ pr_info("[D]%s:" fmt, __func__, ##arg); \ } while (0) #define CONNADP_INFO_FUNC(fmt, arg...) \ do { \ if (gConnAdpDbgLvl >= CONNADP_LOG_INFO) \ pr_info("[I]%s:" fmt, __func__, ##arg); \ } while (0) #define CONNADP_WARN_FUNC(fmt, arg...) \ do { \ if (gConnAdpDbgLvl >= CONNADP_LOG_WARN) \ pr_info("[W]%s:" fmt, __func__, ##arg); \ } while (0) #define CONNADP_ERR_FUNC(fmt, arg...) \ do { \ if (gConnAdpDbgLvl >= CONNADP_LOG_ERR) \ pr_info("[E]%s(%d):" fmt, __func__, __LINE__, ##arg); \ } while (0) /* device node related macro */ #define CONN_DBG_DEV_NUM 1 #define CONN_DBG_DRVIER_NAME "conn_dbg_drv" #define CONN_DBG_DEVICE_NAME "conn_dbg_dev" #define CONN_DBG_DEV_MAJOR 156 /* device node related */ static int gConnDbgMajor = CONN_DBG_DEV_MAJOR; static struct class *pConnDbgClass; static struct device *pConnDbgDev; static struct cdev gConnDbgdev; /******************************************************************************* * Bridging from platform -> wmt_drv.ko ******************************************************************************/ static struct wmt_platform_bridge bridge; static struct wmt_platform_dbg_bridge g_dbg_bridge; #define CONN_DBG_LOG_BUF_SIZE 256 static spinlock_t conn_dbg_log_lock; static enum conn_dbg_log_type conn_dbg_actvie_log_type; static char conn_dbg_log_buf[CONN_DBG_LOG_TYPE_NUM][CONN_DBG_LOG_BUF_SIZE]; static void conn_dbg_get_local_time(u64 *sec, unsigned long *nsec) { if (sec != NULL && nsec != NULL) { *sec = local_clock(); *nsec = do_div(*sec, 1000000000)/1000; } else pr_info("The input parameters error when get local time\n"); } int conn_dbg_add_log(enum conn_dbg_log_type type, const char *buf) { unsigned long flag; int space; u64 sec; unsigned long nsec; char temp[CONN_DBG_LOG_BUF_SIZE]; if (type >= CONN_DBG_LOG_TYPE_NUM || buf == NULL) { pr_info("%s type %d or buf %x is invalid\n", __func__, type, buf); return -1; } pr_info("%s type = %d, log = %s\n", __func__, type, buf); conn_dbg_get_local_time(&sec, &nsec); if (snprintf(temp, CONN_DBG_LOG_BUF_SIZE, "[%llu.%06lu]%s", sec, nsec, buf) < 0) { pr_info("%s snprintf error\n", __func__); return -2; } spin_lock_irqsave(&conn_dbg_log_lock, flag); space = CONN_DBG_LOG_BUF_SIZE - strlen(conn_dbg_log_buf[type]) - 1; if (space > 0) strncat(conn_dbg_log_buf[type], temp, space); else pr_info("%s buffer is full\n", __func__); spin_unlock_irqrestore(&conn_dbg_log_lock, flag); return 0; } EXPORT_SYMBOL(conn_dbg_add_log); static ssize_t conn_dbg_read_log(struct file *filp, char __user *buffer, size_t count, loff_t *f_pos) { unsigned long flag; int dump_len = 0; int ret = 0; char temp[CONN_DBG_LOG_BUF_SIZE]; if (*f_pos < 0 || conn_dbg_actvie_log_type >= CONN_DBG_LOG_TYPE_NUM) return -EFAULT; /* copy data to temp buffer because copy_to_user might sleep */ spin_lock_irqsave(&conn_dbg_log_lock, flag); memcpy(temp, conn_dbg_log_buf[conn_dbg_actvie_log_type], CONN_DBG_LOG_BUF_SIZE); spin_unlock_irqrestore(&conn_dbg_log_lock, flag); dump_len = strlen(temp) - *f_pos; if (dump_len > 0 && dump_len < CONN_DBG_LOG_BUF_SIZE - *f_pos) { if (dump_len > count) dump_len = count; pr_info("%s f_pos=%d, dump_len=%d, %s", __func__, *f_pos, dump_len, &temp[*f_pos]); ret = copy_to_user(buffer, &temp[*f_pos], dump_len); if (ret) { pr_info("%s copy_to_user failed, ret = %d", __func__, ret); ret = -EFAULT; } else { *f_pos += dump_len; ret = dump_len; } } return ret; } ssize_t conn_dbg_dev_write(struct file *filp, const char __user *buffer, size_t count, loff_t *f_pos) { if (g_dbg_bridge.write_cb) return g_dbg_bridge.write_cb(filp, buffer, count, f_pos); return 0; } ssize_t conn_dbg_dev_read(struct file *filp, char __user *buffer, size_t count, loff_t *f_pos) { ssize_t ret, ret2; ret = conn_dbg_read_log(filp, buffer, count, f_pos); if (ret > 0) { count -= ret; buffer += ret; } if (g_dbg_bridge.read_cb) { ret2 = g_dbg_bridge.read_cb(filp, buffer, count, f_pos); if (ret2 > 0) ret = ret > 0 ? ret + ret2 : ret2; } return ret; } static const struct file_operations gConnDbgDevFops = { .read = conn_dbg_dev_read, .write = conn_dbg_dev_write, }; static int conn_dbg_log_init(void) { int i; spin_lock_init(&conn_dbg_log_lock); for (i = 0; i < CONN_DBG_LOG_TYPE_NUM; i++) memset(conn_dbg_log_buf[i], 0, CONN_DBG_LOG_BUF_SIZE); return 0; } static int conn_dbg_dev_init(void) { dev_t dev_id = MKDEV(gConnDbgMajor, 0); int ret = 0; ret = register_chrdev_region(dev_id, CONN_DBG_DEV_NUM, CONN_DBG_DRVIER_NAME); if (ret) { pr_info("%s fail to register chrdev.(%d)\n", __func__, ret); return -1; } cdev_init(&gConnDbgdev, &gConnDbgDevFops); gConnDbgdev.owner = THIS_MODULE; ret = cdev_add(&gConnDbgdev, dev_id, CONN_DBG_DEV_NUM); if (ret) { pr_info("cdev_add() fails (%d)\n", ret); goto err1; } pConnDbgClass = class_create(THIS_MODULE, CONN_DBG_DEVICE_NAME); if (IS_ERR(pConnDbgClass)) { pr_info("class create fail, error code(%ld)\n", PTR_ERR(pConnDbgClass)); goto err2; } pConnDbgDev = device_create(pConnDbgClass, NULL, dev_id, NULL, CONN_DBG_DEVICE_NAME); if (IS_ERR(pConnDbgDev)) { pr_info("device create fail, error code(%ld)\n", PTR_ERR(pConnDbgDev)); goto err3; } return 0; err3: pr_info("[%s] err3", __func__); if (pConnDbgClass) { class_destroy(pConnDbgClass); pConnDbgClass = NULL; } err2: pr_info("[%s] err2", __func__); cdev_del(&gConnDbgdev); err1: pr_info("[%s] err1", __func__); unregister_chrdev_region(dev_id, CONN_DBG_DEV_NUM); return -1; } static int conn_dbg_dev_deinit(void) { dev_t dev_id = MKDEV(gConnDbgMajor, 0); if (pConnDbgDev) { device_destroy(pConnDbgClass, dev_id); pConnDbgDev = NULL; } if (pConnDbgClass) { class_destroy(pConnDbgClass); pConnDbgClass = NULL; } cdev_del(&gConnDbgdev); unregister_chrdev_region(dev_id, CONN_DBG_DEV_NUM); return 0; } void wmt_export_platform_bridge_register(struct wmt_platform_bridge *cb) { if (unlikely(!cb)) return; bridge.thermal_query_cb = cb->thermal_query_cb; bridge.trigger_assert_cb = cb->trigger_assert_cb; bridge.clock_fail_dump_cb = cb->clock_fail_dump_cb; bridge.conninfra_reg_readable_cb = cb->conninfra_reg_readable_cb; bridge.conninfra_reg_is_bus_hang_cb = cb->conninfra_reg_is_bus_hang_cb; conn_dbg_dev_init(); conn_dbg_log_init(); CONNADP_INFO_FUNC("\n"); } EXPORT_SYMBOL(wmt_export_platform_bridge_register); void wmt_export_platform_bridge_unregister(void) { memset(&bridge, 0, sizeof(struct wmt_platform_bridge)); CONNADP_INFO_FUNC("\n"); } EXPORT_SYMBOL(wmt_export_platform_bridge_unregister); void wmt_export_platform_dbg_bridge_register(const struct wmt_platform_dbg_bridge *cb) { if (unlikely(!cb)) return; if (cb->write_cb != NULL && cb->read_cb != NULL) { g_dbg_bridge.write_cb = cb->write_cb; g_dbg_bridge.read_cb = cb->read_cb; } } EXPORT_SYMBOL(wmt_export_platform_dbg_bridge_register); void wmt_export_platform_dbg_bridge_unregister(void) { if (g_dbg_bridge.write_cb && g_dbg_bridge.read_cb) conn_dbg_dev_deinit(); memset(&g_dbg_bridge, 0, sizeof(struct wmt_platform_dbg_bridge)); } EXPORT_SYMBOL(wmt_export_platform_dbg_bridge_unregister); int mtk_wcn_cmb_stub_query_ctrl(void) { CONNADP_DBG_FUNC("\n"); if (unlikely(!bridge.thermal_query_cb)) { CONNADP_WARN_FUNC("Thermal query not registered\n"); return -1; } else return bridge.thermal_query_cb(); } int mtk_wcn_cmb_stub_trigger_assert(void) { CONNADP_DBG_FUNC("\n"); /* dump backtrace for checking assert reason */ dump_stack(); if (unlikely(!bridge.trigger_assert_cb)) { CONNADP_WARN_FUNC("Trigger assert not registered\n"); return -1; } else return bridge.trigger_assert_cb(); } void mtk_wcn_cmb_stub_clock_fail_dump(void) { CONNADP_DBG_FUNC("\n"); if (unlikely(!bridge.clock_fail_dump_cb)) CONNADP_WARN_FUNC("Clock fail dump not registered\n"); else bridge.clock_fail_dump_cb(); } int mtk_wcn_conninfra_reg_readable(void) { static DEFINE_RATELIMIT_STATE(_rs, 5*HZ, 1); if (unlikely(!bridge.conninfra_reg_readable_cb)) { if (__ratelimit(&_rs)) CONNADP_WARN_FUNC("reg_readable not registered\n"); return -1; } else return bridge.conninfra_reg_readable_cb(); } int mtk_wcn_conninfra_is_bus_hang(void) { static DEFINE_RATELIMIT_STATE(_rs, 5*HZ, 1); if (unlikely(!bridge.conninfra_reg_is_bus_hang_cb)) { if (__ratelimit(&_rs)) CONNADP_WARN_FUNC("is_bus_hang not registered\n"); return -1; } else return bridge.conninfra_reg_is_bus_hang_cb(); } /******************************************************************************* * SDIO integration with platform MMC driver ******************************************************************************/ static void mtk_wcn_cmb_sdio_request_eirq(msdc_sdio_irq_handler_t irq_handler, void *data); static void mtk_wcn_cmb_sdio_enable_eirq(void); static void mtk_wcn_cmb_sdio_disable_eirq(void); static void mtk_wcn_cmb_sdio_register_pm(pm_callback_t pm_cb, void *data); struct sdio_ops mt_sdio_ops[4] = { {NULL, NULL, NULL, NULL}, {NULL, NULL, NULL, NULL}, {mtk_wcn_cmb_sdio_request_eirq, mtk_wcn_cmb_sdio_enable_eirq, mtk_wcn_cmb_sdio_disable_eirq, mtk_wcn_cmb_sdio_register_pm}, {mtk_wcn_cmb_sdio_request_eirq, mtk_wcn_cmb_sdio_enable_eirq, mtk_wcn_cmb_sdio_disable_eirq, mtk_wcn_cmb_sdio_register_pm} }; static atomic_t sdio_claim_irq_enable_flag; static atomic_t irq_enable_flag; static msdc_sdio_irq_handler_t mtk_wcn_cmb_sdio_eirq_handler; static void *mtk_wcn_cmb_sdio_eirq_data; unsigned int wifi_irq = 0xffffffff; EXPORT_SYMBOL(wifi_irq); pm_callback_t mtk_wcn_cmb_sdio_pm_cb; EXPORT_SYMBOL(mtk_wcn_cmb_sdio_pm_cb); void *mtk_wcn_cmb_sdio_pm_data; EXPORT_SYMBOL(mtk_wcn_cmb_sdio_pm_data); static int _mtk_wcn_sdio_irq_flag_set(int flag) { if (flag != 0) atomic_set(&sdio_claim_irq_enable_flag, 1); else atomic_set(&sdio_claim_irq_enable_flag, 0); CONNADP_DBG_FUNC("sdio_claim_irq_enable_flag:%d\n", atomic_read(&sdio_claim_irq_enable_flag)); return atomic_read(&sdio_claim_irq_enable_flag); } int wmt_export_mtk_wcn_sdio_irq_flag_set(int flag) { return _mtk_wcn_sdio_irq_flag_set(flag); } EXPORT_SYMBOL(wmt_export_mtk_wcn_sdio_irq_flag_set); static irqreturn_t mtk_wcn_cmb_sdio_eirq_handler_stub(int irq, void *data) { if ((mtk_wcn_cmb_sdio_eirq_handler != NULL) && (atomic_read(&sdio_claim_irq_enable_flag) != 0)) mtk_wcn_cmb_sdio_eirq_handler(mtk_wcn_cmb_sdio_eirq_data); return IRQ_HANDLED; } static void mtk_wcn_cmb_sdio_request_eirq(msdc_sdio_irq_handler_t irq_handler, void *data) { #ifdef CONFIG_OF struct device_node *node; int ret = -EINVAL; CONNADP_INFO_FUNC("enter\n"); _mtk_wcn_sdio_irq_flag_set(0); atomic_set(&irq_enable_flag, 1); mtk_wcn_cmb_sdio_eirq_data = data; mtk_wcn_cmb_sdio_eirq_handler = irq_handler; node = (struct device_node *)of_find_compatible_node(NULL, NULL, "mediatek,connectivity-combo"); if (node) { wifi_irq = irq_of_parse_and_map(node, 0);/* get wifi eint num */ ret = request_irq(wifi_irq, mtk_wcn_cmb_sdio_eirq_handler_stub, IRQF_TRIGGER_LOW, "WIFI-eint", NULL); CONNADP_DBG_FUNC("WIFI EINT irq %d !!\n", wifi_irq); if (ret) CONNADP_WARN_FUNC("WIFI EINT LINE NOT AVAILABLE!!\n"); else mtk_wcn_cmb_sdio_disable_eirq();/*state:power off*/ } else CONNADP_WARN_FUNC("can't find connectivity compatible node\n"); CONNADP_INFO_FUNC("exit\n"); #else CONNADP_ERR_FUNC("not implemented\n"); #endif } static void mtk_wcn_cmb_sdio_register_pm(pm_callback_t pm_cb, void *data) { CONNADP_DBG_FUNC("cmb_sdio_register_pm (0x%p, 0x%p)\n", pm_cb, data); /* register pm change callback */ mtk_wcn_cmb_sdio_pm_cb = pm_cb; mtk_wcn_cmb_sdio_pm_data = data; } static void mtk_wcn_cmb_sdio_enable_eirq(void) { if (atomic_read(&irq_enable_flag)) CONNADP_DBG_FUNC("wifi eint has been enabled\n"); else { atomic_set(&irq_enable_flag, 1); if (wifi_irq != 0xfffffff) { enable_irq(wifi_irq); CONNADP_DBG_FUNC(" enable WIFI EINT %d!\n", wifi_irq); } } } static void mtk_wcn_cmb_sdio_disable_eirq(void) { if (!atomic_read(&irq_enable_flag)) CONNADP_DBG_FUNC("wifi eint has been disabled!\n"); else { if (wifi_irq != 0xfffffff) { disable_irq_nosync(wifi_irq); CONNADP_DBG_FUNC("disable WIFI EINT %d!\n", wifi_irq); } atomic_set(&irq_enable_flag, 0); } } void wmt_export_mtk_wcn_cmb_sdio_disable_eirq(void) { mtk_wcn_cmb_sdio_disable_eirq(); } EXPORT_SYMBOL(wmt_export_mtk_wcn_cmb_sdio_disable_eirq);