/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2021 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USER_SIZE 16 #define LBAT_SERVICE_DBG 0 static DEFINE_MUTEX(lbat_mutex); static struct list_head lbat_hv_list = LIST_HEAD_INIT(lbat_hv_list); static struct list_head lbat_lv_list = LIST_HEAD_INIT(lbat_lv_list); /* workqueue for SW de-bounce */ static struct workqueue_struct *lbat_wq; static unsigned int user_count; enum lbat_thd_type { LBAT_HV, LBAT_LV, }; struct lbat_thd_t { unsigned int thd_volt; struct lbat_user *user; struct list_head list; }; static struct lbat_thd_t *cur_hv_ptr; static struct lbat_thd_t *cur_lv_ptr; static struct lbat_user *lbat_user_table[USER_SIZE]; static unsigned int r_ratio[2]; static unsigned int VOLT_TO_RAW(unsigned int volt) { return (volt << 12) / (1800 * r_ratio[0] / r_ratio[1]); } static void lbat_max_en_setting(int en_val) { pmic_set_register_value(PMIC_AUXADC_LBAT_DET_MAX, en_val); pmic_set_register_value(PMIC_AUXADC_LBAT_IRQ_EN_MAX, en_val); } static void lbat_min_en_setting(int en_val) { pmic_set_register_value(PMIC_AUXADC_LBAT_DET_MIN, en_val); pmic_set_register_value(PMIC_AUXADC_LBAT_IRQ_EN_MIN, en_val); } static void lbat_irq_enable(void) { if (cur_hv_ptr != NULL) lbat_max_en_setting(1); if (cur_lv_ptr != NULL) lbat_min_en_setting(1); pmic_set_register_value(PMIC_AUXADC_LBAT_EN, 1); } static void lbat_irq_disable(void) { pmic_set_register_value(PMIC_AUXADC_LBAT_EN, 0); lbat_max_en_setting(0); lbat_min_en_setting(0); } static int hv_list_cmp(void *priv, struct list_head *a, struct list_head *b) { struct lbat_thd_t *thd_a, *thd_b; thd_a = list_entry(a, struct lbat_thd_t, list); thd_b = list_entry(b, struct lbat_thd_t, list); return thd_a->thd_volt - thd_b->thd_volt; } static int lv_list_cmp(void *priv, struct list_head *a, struct list_head *b) { struct lbat_thd_t *thd_a, *thd_b; thd_a = list_entry(a, struct lbat_thd_t, list); thd_b = list_entry(b, struct lbat_thd_t, list); return thd_b->thd_volt - thd_a->thd_volt; } static int modify_lbat_list(enum lbat_thd_type type, struct lbat_thd_t *thd) { if (!thd) return -EINVAL; switch (type) { case LBAT_HV: list_move(&thd->list, &lbat_hv_list); list_sort(NULL, &lbat_hv_list, hv_list_cmp); thd = list_first_entry(&lbat_hv_list, struct lbat_thd_t, list); if (cur_hv_ptr != thd) { cur_hv_ptr = thd; pmic_set_register_value(PMIC_AUXADC_LBAT_VOLT_MAX, VOLT_TO_RAW(cur_hv_ptr->thd_volt)); } break; case LBAT_LV: list_move(&thd->list, &lbat_lv_list); list_sort(NULL, &lbat_lv_list, lv_list_cmp); thd = list_first_entry(&lbat_lv_list, struct lbat_thd_t, list); if (cur_lv_ptr != thd) { cur_lv_ptr = thd; pmic_set_register_value(PMIC_AUXADC_LBAT_VOLT_MIN, VOLT_TO_RAW(cur_lv_ptr->thd_volt)); } break; } return 0; } static inline int list_is_first(const struct list_head *list, const struct list_head *head) { return list->prev == head; } /* * After execute lbat_user's callback, set next thd node to wait event */ static void lbat_hv_set_next_thd(struct lbat_user *user, struct lbat_thd_t *thd) { /* restore user->thd_list */ list_move(&thd->list, &user->thd_list); list_sort(NULL, &user->thd_list, lv_list_cmp); /* HV is triggered */ if (!list_is_first(&thd->list, &user->thd_list)) /* Not first */ modify_lbat_list(LBAT_HV, list_prev_entry(thd, list)); if (!list_is_last(&thd->list, &user->thd_list)) /* Not last */ modify_lbat_list(LBAT_LV, list_next_entry(thd, list)); } static void lbat_lv_set_next_thd(struct lbat_user *user, struct lbat_thd_t *thd) { /* restore user->thd_list */ list_move(&thd->list, &user->thd_list); list_sort(NULL, &user->thd_list, lv_list_cmp); /* LV is triggered */ if (!list_is_first(&thd->list, &user->thd_list)) /* Not first */ modify_lbat_list(LBAT_HV, list_prev_entry(thd, list)); if (!list_is_last(&thd->list, &user->thd_list)) /* Not last */ modify_lbat_list(LBAT_LV, list_next_entry(thd, list)); } static void lbat_set_next_thd(struct lbat_user *user, struct lbat_thd_t *thd) { if (thd == user->hv_thd) { modify_lbat_list(LBAT_LV, user->lv1_thd); if (!list_empty(&user->lv2_thd->list)) list_del_init(&user->lv2_thd->list); } else if (thd == user->lv1_thd) { modify_lbat_list(LBAT_HV, user->hv_thd); if (list_empty(&user->lv2_thd->list)) modify_lbat_list(LBAT_LV, user->lv2_thd); } } /* * Execute user's callback and set its next threshold if reach deb_times, * otherwise ignore this event and reset lbat_list */ // workaround for mt6877 static void lbat_deb_handler(struct work_struct *work) { enum lbat_thd_type type; unsigned int deb_times; struct lbat_user *user = container_of(work, struct lbat_user, deb_work); mutex_lock(&lbat_mutex); if (user->deb_thd_ptr == user->hv_thd) { type = LBAT_HV; deb_times = user->hv_deb_times; } else { type = LBAT_LV; deb_times = user->lv_deb_times; } if (user->deb_cnt >= deb_times) { /* execute user's callback after de-bounce */ user->callback(user->deb_thd_ptr->thd_volt); lbat_set_next_thd(user, user->deb_thd_ptr); } else { /* ignore this event and reset lbat_list */ modify_lbat_list(type, user->deb_thd_ptr); } /* de-bounce done, reset deb_cnt and deb_thd_ptr */ user->deb_cnt = 0; user->deb_thd_ptr = NULL; lbat_irq_disable(); udelay(200); lbat_irq_enable(); mutex_unlock(&lbat_mutex); } //static void lbat_timer_func(unsigned long data) static void lbat_timer_func(struct timer_list *t) { unsigned int deb_prd = 0; unsigned int deb_times = 0; // struct lbat_user *user = (struct lbat_user *)data; struct lbat_user *user = from_timer(user, t, deb_timer); if (user->deb_thd_ptr == user->hv_thd) { /* LBAT user HV de-bounce */ if (lbat_read_volt() < user->deb_thd_ptr->thd_volt) { /* queue deb_work to reset lbat_list */ goto wq_handler; } deb_prd = user->hv_deb_prd; deb_times = user->hv_deb_times; } else if (user->deb_thd_ptr == user->lv1_thd || user->deb_thd_ptr == user->lv2_thd) { /* LBAT user LV de-bounce */ if (lbat_read_volt() > user->deb_thd_ptr->thd_volt) { /* queue deb_work to reset lbat_list */ goto wq_handler; } deb_prd = user->lv_deb_prd; deb_times = user->lv_deb_times; } else { pr_notice("[%s] LBAT de-bounce threshold not match\n", __func__); return; } user->deb_cnt++; #if LBAT_SERVICE_DBG pr_info("[%s] name:%s, thd_volt:%d, de-bounce times:%d\n", __func__, user->name, user->deb_thd_ptr->thd_volt, user->deb_cnt); #endif if (user->deb_cnt < deb_times) { mod_timer(&user->deb_timer, jiffies + msecs_to_jiffies(deb_prd)); return; } wq_handler: /* queue deb_work to execute user's callback or reset lbat_list */ queue_work(lbat_wq, &user->deb_work); } static void lbat_user_init_timer(struct lbat_user *user) { user->deb_cnt = 0; user->hv_deb_prd = 0; user->hv_deb_times = 0; user->lv_deb_prd = 0; user->lv_deb_times = 0; //init_timer(&user->deb_timer); //user->deb_timer.data = (unsigned long)user; //user->deb_timer.expires = 0; //user->deb_timer.function = lbat_timer_func; timer_setup(&user->deb_timer, lbat_timer_func, 0); } static int lbat_user_update(struct lbat_user *user) { struct lbat_thd_t *thd; /* * add lv_thd to lbat_lv_list * and assign first entry of lv_list to cur_lv_ptr */ if (list_empty(&user->thd_list)) thd = user->lv1_thd; else { thd = list_first_entry(&user->thd_list, struct lbat_thd_t, list); thd = list_next_entry(thd, list); } modify_lbat_list(LBAT_LV, thd); if (user_count == 0) lbat_irq_enable(); lbat_user_table[user_count++] = user; return 0; } static struct lbat_thd_t *lbat_thd_init(unsigned int thd_volt, struct lbat_user *user) { struct lbat_thd_t *thd = NULL; if (thd_volt == 0) return NULL; thd = kzalloc(sizeof(*thd), GFP_KERNEL); if (thd == NULL) return NULL; thd->thd_volt = thd_volt; thd->user = user; INIT_LIST_HEAD(&thd->list); return thd; } struct lbat_user *lbat_user_register_ext(const char *name, unsigned int *thd_volt_arr, unsigned int thd_volt_size, void (*callback)(unsigned int thd_volt)) { int i, ret; struct lbat_thd_t *thd; struct lbat_user *user; mutex_lock(&lbat_mutex); user = kzalloc(sizeof(*user), GFP_KERNEL); if (user == NULL) { ret = -10; goto out; } strncpy(user->name, name, ARRAY_SIZE(user->name) - 1); if (thd_volt_arr[0] >= 5400 || thd_volt_arr[thd_volt_size - 1] <= 2000) { ret = -EINVAL; goto out; } else if (callback == NULL) { ret = -EINVAL; goto out; } INIT_LIST_HEAD(&user->thd_list); thd = lbat_thd_init(thd_volt_arr[0], user); for (i = 0; i < thd_volt_size; i++) { thd = lbat_thd_init(thd_volt_arr[i], user); list_add_tail(&thd->list, &user->thd_list); } user->callback = callback; lbat_user_init_timer(user); INIT_WORK(&user->deb_work, lbat_deb_handler); pr_info("[%s] name=%s, thd_volt_max=%d, thd_volt_min=%d\n", __func__, user->name, thd_volt_arr[0], thd_volt_arr[thd_volt_size - 1]); ret = lbat_user_update(user); out: if (ret) pr_notice("[%s] error ret=%d\n", __func__, ret); mutex_unlock(&lbat_mutex); return user; } EXPORT_SYMBOL(lbat_user_register_ext); struct lbat_user *lbat_user_register(const char *name, unsigned int hv_thd_volt, unsigned int lv1_thd_volt, unsigned int lv2_thd_volt, void (*callback)(unsigned int thd_volt)) { int ret = 0; struct lbat_user *user; mutex_lock(&lbat_mutex); user = kzalloc(sizeof(*user), GFP_KERNEL); if (user == NULL) { ret = -10; goto out; } strncpy(user->name, name, ARRAY_SIZE(user->name) - 1); if (hv_thd_volt >= 5400 || lv1_thd_volt <= 2650) { ret = -11; goto out; } else if (hv_thd_volt < lv1_thd_volt || lv1_thd_volt < lv2_thd_volt) { ret = -12; goto out; } else if (callback == NULL) { ret = -13; goto out; } INIT_LIST_HEAD(&user->thd_list); user->hv_thd = lbat_thd_init(hv_thd_volt, user); user->lv1_thd = lbat_thd_init(lv1_thd_volt, user); user->lv2_thd = lbat_thd_init(lv2_thd_volt, user); user->callback = callback; lbat_user_init_timer(user); INIT_WORK(&user->deb_work, lbat_deb_handler); pr_info("[%s] name=%s, hv=%d, lv1=%d, lv2=%d\n", __func__, name, hv_thd_volt, lv1_thd_volt, lv2_thd_volt); ret = lbat_user_update(user); out: if (ret) pr_notice("[%s] error ret=%d\n", __func__, ret); mutex_unlock(&lbat_mutex); return user; } EXPORT_SYMBOL(lbat_user_register); int lbat_user_set_debounce(struct lbat_user *user, unsigned int hv_deb_prd, unsigned int hv_deb_times, unsigned int lv_deb_prd, unsigned int lv_deb_times) { if (IS_ERR(user)) return PTR_ERR(user); user->hv_deb_prd = hv_deb_prd; user->hv_deb_times = hv_deb_times; user->lv_deb_prd = lv_deb_prd; user->lv_deb_times = lv_deb_times; return 0; } EXPORT_SYMBOL(lbat_user_set_debounce); static irqreturn_t bat_h_int_handler(int irq, void *data) { struct lbat_user *user = NULL; if (cur_hv_ptr == NULL) { lbat_max_en_setting(0); return IRQ_NONE; } mutex_lock(&lbat_mutex); pr_info("[%s] cur_thd_volt=%d\n", __func__, cur_hv_ptr->thd_volt); user = cur_hv_ptr->user; list_del_init(&cur_hv_ptr->list); if (user->hv_deb_times) { user->deb_cnt = 0; user->deb_thd_ptr = cur_hv_ptr; mod_timer(&user->deb_timer, jiffies + msecs_to_jiffies(user->hv_deb_prd)); } else { user->callback(cur_hv_ptr->thd_volt); if (list_empty(&user->thd_list)) lbat_set_next_thd(user, cur_hv_ptr); else lbat_hv_set_next_thd(user, cur_hv_ptr); } /* Since cur_hv_ptr is removed, assign new thd for cur_hv_ptr */ if (list_empty(&lbat_hv_list)) { cur_hv_ptr = NULL; goto out; } cur_hv_ptr = list_first_entry( &lbat_hv_list, struct lbat_thd_t, list); pmic_set_register_value(PMIC_AUXADC_LBAT_VOLT_MAX, VOLT_TO_RAW(cur_hv_ptr->thd_volt)); out: lbat_irq_disable(); udelay(200); lbat_irq_enable(); mutex_unlock(&lbat_mutex); return IRQ_HANDLED; } static irqreturn_t bat_l_int_handler(int irq, void *data) { struct lbat_user *user = NULL; if (cur_lv_ptr == NULL) { lbat_min_en_setting(0); return IRQ_NONE; } mutex_lock(&lbat_mutex); pr_info("[%s] cur_thd_volt=%d\n", __func__, cur_lv_ptr->thd_volt); user = cur_lv_ptr->user; list_del_init(&cur_lv_ptr->list); if (user->lv_deb_times) { user->deb_cnt = 0; user->deb_thd_ptr = cur_lv_ptr; mod_timer(&user->deb_timer, jiffies + msecs_to_jiffies(user->lv_deb_prd)); } else { user->callback(cur_lv_ptr->thd_volt); if (list_empty(&user->thd_list)) lbat_set_next_thd(user, cur_lv_ptr); else lbat_lv_set_next_thd(user, cur_lv_ptr); } /* Since cur_lv_ptr is removed, assign new thd for cur_lv_ptr */ if (list_empty(&lbat_lv_list)) { cur_lv_ptr = NULL; goto out; } cur_lv_ptr = list_first_entry( &lbat_lv_list, struct lbat_thd_t, list); pmic_set_register_value(PMIC_AUXADC_LBAT_VOLT_MIN, VOLT_TO_RAW(cur_lv_ptr->thd_volt)); out: lbat_irq_disable(); udelay(200); lbat_irq_enable(); mutex_unlock(&lbat_mutex); return IRQ_HANDLED; } void lbat_suspend(void) { lbat_irq_disable(); } void lbat_resume(void) { lbat_irq_enable(); } int lbat_service_init(struct platform_device *pdev) { int ret = 0; struct device_node *np = NULL; pr_info("[%s]\n", __func__); /* Selects debounce as 8 */ pmic_set_register_value(PMIC_AUXADC_LBAT_DEBT_MAX_SEL, 3); /* Selects debounce as 1 */ pmic_set_register_value(PMIC_AUXADC_LBAT_DEBT_MIN_SEL, 0); /* Set LBAT_PRD as 15ms */ pmic_set_register_value(PMIC_AUXADC_LBAT_DET_PRD_SEL, 0); ret = devm_request_threaded_irq(&pdev->dev, platform_get_irq_byname(pdev, "bat_h"), NULL, bat_h_int_handler, IRQF_TRIGGER_NONE, "bat_h", NULL); if (ret < 0) dev_notice(&pdev->dev, "request bat_h irq fail\n"); ret = devm_request_threaded_irq(&pdev->dev, platform_get_irq_byname(pdev, "bat_l"), NULL, bat_l_int_handler, IRQF_TRIGGER_NONE, "bat_l", NULL); if (ret < 0) dev_notice(&pdev->dev, "request bat_l irq fail\n"); lbat_wq = create_singlethread_workqueue("lbat_service"); /* get LBAT r_ratio */ np = of_find_node_by_name(NULL, "batadc"); if (!np) { pr_notice("[%s] get batadc node fail\n", __func__); r_ratio[0] = 7; r_ratio[1] = 2; return 0; } ret = of_property_read_u32_array(np, "resistance-ratio", r_ratio, 2); pr_info("[%s] r_ratio = %d/%d\n", __func__, r_ratio[0], r_ratio[1]); return ret; } unsigned int lbat_read_raw(void) { return pmic_get_register_value(PMIC_AUXADC_ADC_OUT_LBAT); } unsigned int lbat_read_volt(void) { unsigned int raw_data = lbat_read_raw(); return (raw_data * 1800 * r_ratio[0] / r_ratio[1]) >> 12; } /* * Lbat service debug */ void lbat_dump_reg(void) { pr_notice("AUXADC_LBAT_VOLT_MAX = 0x%x, AUXADC_LBAT_VOLT_MIN = 0x%x, RG_INT_EN_BAT_H = %d, RG_INT_EN_BAT_L = %d\n" , pmic_get_register_value(PMIC_AUXADC_LBAT_VOLT_MAX) , pmic_get_register_value(PMIC_AUXADC_LBAT_VOLT_MIN) , pmic_get_register_value(PMIC_RG_INT_EN_BAT_H) , pmic_get_register_value(PMIC_RG_INT_EN_BAT_L)); pr_notice("AUXADC_LBAT_EN = %d, AUXADC_LBAT_DET_MAX = %d, AUXADC_LBAT_IRQ_EN_MAX = %d, AUXADC_LBAT_DET_MIN = %d, AUXADC_LBAT_IRQ_EN_MIN = %d\n" , pmic_get_register_value(PMIC_AUXADC_LBAT_EN) , pmic_get_register_value(PMIC_AUXADC_LBAT_DET_MAX) , pmic_get_register_value(PMIC_AUXADC_LBAT_IRQ_EN_MAX) , pmic_get_register_value(PMIC_AUXADC_LBAT_DET_MIN) , pmic_get_register_value(PMIC_AUXADC_LBAT_IRQ_EN_MIN)); pr_notice("AUXADC_LBAT_DEBT_MAX_SEL=%d, AUXADC_LBAT_DEBT_MIN_SEL=%d\n" , pmic_get_register_value(PMIC_AUXADC_LBAT_DEBT_MAX_SEL) , pmic_get_register_value(PMIC_AUXADC_LBAT_DEBT_MIN_SEL)); } static void lbat_dump_thd_list(struct seq_file *s) { unsigned int len = 0; char str[128] = ""; struct lbat_thd_t *thd = NULL; if (list_empty(&lbat_hv_list) && list_empty(&lbat_lv_list)) { pr_notice("[%s] no entry in lbat list\n", __func__); seq_puts(s, "no entry in lbat list\n"); return; } mutex_lock(&lbat_mutex); list_for_each_entry(thd, &lbat_hv_list, list) { len += snprintf(str + len, sizeof(str) - len, "%shv_list, thd_volt:%d, user:%s\n", (thd == cur_hv_ptr || thd == cur_lv_ptr) ? "->" : " ", thd->thd_volt, thd->user->name); pr_notice("%s", str); seq_printf(s, "%s", str); strncpy(str, "", strlen(str)); len = 0; } pr_notice("\n"); seq_puts(s, "\n"); list_for_each_entry(thd, &lbat_lv_list, list) { len += snprintf(str + len, sizeof(str) - len, "%slv_list, thd_volt:%d, user:%s\n", (thd == cur_hv_ptr || thd == cur_lv_ptr) ? "->" : " ", thd->thd_volt, thd->user->name); pr_notice("%s", str); seq_printf(s, "%s", str); strncpy(str, "", strlen(str)); len = 0; } pr_notice("\n"); mutex_unlock(&lbat_mutex); } static void lbat_dbg_dump_reg(struct seq_file *s) { lbat_dump_reg(); seq_printf(s, "AUXADC_LBAT_VOLT_MAX = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_VOLT_MAX)); seq_printf(s, "AUXADC_LBAT_VOLT_MIN = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_VOLT_MIN)); seq_printf(s, "RG_INT_EN_BAT_H = 0x%x\n", pmic_get_register_value(PMIC_RG_INT_EN_BAT_H)); seq_printf(s, "RG_INT_EN_BAT_L = 0x%x\n", pmic_get_register_value(PMIC_RG_INT_EN_BAT_L)); seq_printf(s, "AUXADC_LBAT_EN = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_EN)); seq_printf(s, "AUXADC_LBAT_DET_MAX = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_DET_MAX)); seq_printf(s, "AUXADC_LBAT_IRQ_EN_MAX = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_IRQ_EN_MAX)); seq_printf(s, "AUXADC_LBAT_DET_MIN = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_DET_MIN)); seq_printf(s, "AUXADC_LBAT_IRQ_EN_MIN = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_IRQ_EN_MIN)); seq_printf(s, "AUXADC_LBAT_DEBT_MAX_SEL = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_DEBT_MAX_SEL)); seq_printf(s, "AUXADC_LBAT_DEBT_MIN_SEL = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_DEBT_MIN_SEL)); seq_printf(s, "AUXADC_ADC_RDY_LBAT = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_ADC_RDY_LBAT)); seq_printf(s, "AUXADC_ADC_OUT_LBAT = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_ADC_OUT_LBAT)); } static void lbat_dump_user_table(struct seq_file *s) { unsigned int i = 0; struct lbat_user *user = NULL; struct lbat_thd_t *thd = NULL; mutex_lock(&lbat_mutex); for (i = 0; i < user_count; i++) { user = lbat_user_table[i]; if (list_empty(&user->thd_list)) { seq_printf(s, "%2d:%20s, %d, %d, %d, (%d,%d,%d,%d), %pf\n", i, user->name, user->hv_thd->thd_volt, user->lv1_thd->thd_volt, user->lv2_thd->thd_volt, user->hv_deb_prd, user->hv_deb_times, user->lv_deb_prd, user->lv_deb_times, user->callback); } else { seq_printf(s, "%2d:%20s,", i, user->name); list_for_each_entry(thd, &user->thd_list, list) { seq_printf(s, " %d,", thd->thd_volt); } seq_printf(s, " %pf\n", user->callback); } } mutex_unlock(&lbat_mutex); } struct lbat_dbg_st { unsigned int dbg_id; }; enum { LBAT_DBG_DUMP_LIST, LBAT_DBG_DUMP_REG, LBAT_DBG_DUMP_TABLE, LBAT_DBG_MAX, }; static struct lbat_dbg_st dbg_data[LBAT_DBG_MAX]; static int lbat_dbg_show(struct seq_file *s, void *unused) { struct lbat_dbg_st *dbg_st = s->private; switch (dbg_st->dbg_id) { case LBAT_DBG_DUMP_LIST: lbat_dump_thd_list(s); break; case LBAT_DBG_DUMP_REG: lbat_dbg_dump_reg(s); break; case LBAT_DBG_DUMP_TABLE: lbat_dump_user_table(s); break; default: break; } return 0; } static int lbat_dbg_open(struct inode *inode, struct file *file) { if (file->f_mode & FMODE_READ) return single_open(file, lbat_dbg_show, inode->i_private); file->private_data = inode->i_private; return 0; } static ssize_t lbat_dbg_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *position) { return count; } static int lbat_dbg_release(struct inode *inode, struct file *file) { if (file->f_mode & FMODE_READ) return single_release(inode, file); return 0; } static const struct file_operations lbat_dbg_fops = { .open = lbat_dbg_open, .read = seq_read, .write = lbat_dbg_write, .llseek = seq_lseek, .release = lbat_dbg_release, }; int lbat_debug_init(struct dentry *debug_dir) { struct dentry *lbat_dbg_dir = NULL; if (IS_ERR(debug_dir) || !debug_dir) { pr_notice("dir mtk_pmic does not exist\n"); return -1; } lbat_dbg_dir = debugfs_create_dir("lbat_dbg", debug_dir); if (IS_ERR(lbat_dbg_dir) || !lbat_dbg_dir) { pr_notice("fail mkdir /sys/kernel/debug/mtk_pmic/lbat_dbg\n"); return -1; } /* lbat service debug init */ dbg_data[0].dbg_id = LBAT_DBG_DUMP_LIST; /* file type is regular file(S_IFREG), permission is read(444) */ debugfs_create_file("lbat_dump_list", (S_IFREG | 0444), lbat_dbg_dir, (void *)&dbg_data[0], &lbat_dbg_fops); dbg_data[1].dbg_id = LBAT_DBG_DUMP_REG; debugfs_create_file("lbat_dump_reg", (S_IFREG | 0444), lbat_dbg_dir, (void *)&dbg_data[1], &lbat_dbg_fops); dbg_data[2].dbg_id = LBAT_DBG_DUMP_TABLE; debugfs_create_file("lbat_dump_table", (S_IFREG | 0444), lbat_dbg_dir, (void *)&dbg_data[2], &lbat_dbg_fops); return 0; }