/* 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 #define VOLT_TO_RAW(volt) (((volt) << 12) / 5400) #define RAW_TO_VOLT(thd) (((thd) * 5400) >> 12) #define USER_SIZE 16 #define DEF_H_DEB 150 /* 150ms */ #define DEF_L_DEB 0 /* no de-bounce */ #define LBAT_PRD 15 /* 15ms */ #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 void lbat_max_en_setting(int en_val) { pmic_set_register_value(PMIC_AUXADC_LBAT_EN_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_EN_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); } static void lbat_irq_disable(void) { 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_add(&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_add(&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; } /* * After execute lbat_user's callback, set next thd node to wait event */ 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 */ 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 = 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; /*workaround for mt6768*/ //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) { /* * add lv_thd to lbat_lv_list * and assign first entry of lv_list to cur_lv_ptr */ modify_lbat_list(LBAT_LV, user->lv1_thd); if (user_count == 0) lbat_min_en_setting(1); 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; 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(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 = NULL; mutex_lock(&lbat_mutex); user = kzalloc(sizeof(*user), GFP_KERNEL); if (user == NULL) { ret = -10; goto out; } strncpy(user->name, name, strlen(user->name)); 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; } 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] hv=%d, lv1=%d, lv2=%d\n", __func__, 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; 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); lbat_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; 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); lbat_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; pr_info("[%s]\n", __func__); pmic_set_register_value(PMIC_AUXADC_LBAT_DEBT_MAX, DEF_H_DEB / LBAT_PRD); pmic_set_register_value(PMIC_AUXADC_LBAT_DEBT_MIN, DEF_L_DEB / LBAT_PRD); pmic_set_register_value( PMIC_AUXADC_LBAT_DET_PRD_15_0, LBAT_PRD); pmic_set_register_value( PMIC_AUXADC_LBAT_DET_PRD_19_16, (LBAT_PRD & 0xF0000) >> 16); 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"); 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_TO_VOLT(raw_data); } /* * 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_MAX = %d, AUXADC_LBAT_IRQ_EN_MAX = %d, AUXADC_LBAT_EN_MIN = %d, AUXADC_LBAT_IRQ_EN_MIN = %d\n" , pmic_get_register_value(PMIC_AUXADC_LBAT_EN_MAX) , pmic_get_register_value(PMIC_AUXADC_LBAT_IRQ_EN_MAX) , pmic_get_register_value(PMIC_AUXADC_LBAT_EN_MIN) , pmic_get_register_value(PMIC_AUXADC_LBAT_IRQ_EN_MIN)); pr_notice("AUXADC_LBAT_DEBT_MAX=%d, AUXADC_LBAT_DEBT_MIN=%d\n" , pmic_get_register_value(PMIC_AUXADC_LBAT_DEBT_MAX) , pmic_get_register_value(PMIC_AUXADC_LBAT_DEBT_MIN)); } 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_MAX = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_EN_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_EN_MIN = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_EN_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 = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_DEBT_MAX)); seq_printf(s, "AUXADC_LBAT_DEBT_MIN = 0x%x\n", pmic_get_register_value(PMIC_AUXADC_LBAT_DEBT_MIN)); 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; struct lbat_user *user; mutex_lock(&lbat_mutex); for (i = 0; i < user_count; i++) { user = lbat_user_table[i]; 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); } 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; 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; }