kernel-brax3-ubuntu-touch/drivers/misc/mediatek/connectivity/common/wmt_build_in_adapter.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

512 lines
13 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <linux/kernel.h>
#include "wmt_build_in_adapter.h"
#include <linux/string.h>
#include <linux/printk.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/cdev.h>
#include <linux/sched/clock.h>
#include "conn_dbg.h"
/*device tree mode*/
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/irqreturn.h>
#include <linux/of_address.h>
#endif
#include <linux/interrupt.h>
#include <linux/ratelimit.h>
#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);