unplugged-kernel/drivers/power/supply/mediatek/battery/mtk_gauge_coulomb_service.c

551 lines
12 KiB
C
Raw Permalink Normal View History

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2021 MediaTek Inc.
*/
#ifndef _DEA_MODIFY_
#include <mt-plat/upmu_common.h>
#include <mt-plat/v1/mtk_battery.h>
#include <linux/list.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#endif
#include "mtk_gauge_class.h"
#include <mtk_battery_internal.h>
void __attribute__ ((weak))
pmic_register_interrupt_callback(unsigned int intNo,
void (EINT_FUNC_PTR) (void))
{
/*work around for mt6768*/
}
static struct list_head coulomb_head_plus = LIST_HEAD_INIT(coulomb_head_plus);
static struct list_head coulomb_head_minus = LIST_HEAD_INIT(coulomb_head_minus);
static struct mutex coulomb_lock;
static struct mutex hw_coulomb_lock;
static unsigned long reset_coulomb;
static spinlock_t slock;
static struct wakeup_source *wlock;
static wait_queue_head_t wait_que;
static bool coulomb_thread_timeout;
static int fgclog_level;
static int pre_coulomb;
static bool init;
static int coulomb_lock_cnt, hw_coulomb_lock_cnt;
int fix_coverity;
#define FTLOG_ERROR_LEVEL 1
#define FTLOG_DEBUG_LEVEL 2
#define FTLOG_TRACE_LEVEL 3
#define ft_err(fmt, args...) \
do { \
if (fgclog_level >= FTLOG_ERROR_LEVEL) { \
pr_notice(fmt, ##args); \
} \
} while (0)
#define ft_debug(fmt, args...) \
do { \
if (fgclog_level >= FTLOG_DEBUG_LEVEL) { \
pr_notice(fmt, ##args); \
} \
} while (0)
#define ft_trace(fmt, args...)\
do { \
if (fgclog_level >= FTLOG_TRACE_LEVEL) { \
pr_notice(fmt, ##args);\
} \
} while (0)
void mutex_coulomb_lock(void)
{
mutex_lock(&coulomb_lock);
coulomb_lock_cnt++;
}
void mutex_coulomb_unlock(void)
{
coulomb_lock_cnt--;
mutex_unlock(&coulomb_lock);
}
void mutex_hw_coulomb_lock(void)
{
mutex_lock(&hw_coulomb_lock);
hw_coulomb_lock_cnt++;
}
void mutex_hw_coulomb_unlock(void)
{
hw_coulomb_lock_cnt--;
mutex_unlock(&hw_coulomb_lock);
}
void wake_up_gauge_coulomb(void)
{
unsigned long flags = 0;
if (init == false) {
ft_err("[%s]gauge_coulomb service is not rdy\n", __func__);
return;
}
if (is_fg_disabled()) {
gauge_set_coulomb_interrupt1_ht(0);
gauge_set_coulomb_interrupt1_lt(0);
return;
}
ft_err("%s %d %d %d %d\n",
__func__,
wlock->active,
coulomb_thread_timeout,
coulomb_lock_cnt,
hw_coulomb_lock_cnt);
mutex_hw_coulomb_lock();
gauge_set_coulomb_interrupt1_ht(300);
gauge_set_coulomb_interrupt1_lt(300);
mutex_hw_coulomb_unlock();
spin_lock_irqsave(&slock, flags);
if (wlock->active == 0)
__pm_stay_awake(wlock);
spin_unlock_irqrestore(&slock, flags);
coulomb_thread_timeout = true;
wake_up(&wait_que);
ft_debug("%s end\n", __func__);
}
void gauge_coulomb_set_log_level(int x)
{
fgclog_level = x;
}
void gauge_coulomb_consumer_init(
struct gauge_consumer *coulomb, struct device *dev, char *name)
{
coulomb->name = name;
INIT_LIST_HEAD(&coulomb->list);
coulomb->dev = dev;
}
void gauge_coulomb_dump_list(void)
{
struct list_head *pos;
struct list_head *phead = &coulomb_head_plus;
struct gauge_consumer *ptr;
int car;
if (init == false) {
ft_err("[%s]gauge_coulomb service is not rdy\n", __func__);
return;
}
ft_debug("%s %d %d\n",
__func__,
wlock->active,
coulomb_thread_timeout);
mutex_coulomb_lock();
car = gauge_get_coulomb();
if (list_empty(phead) != true) {
ft_debug("dump plus list start\n");
list_for_each(pos, phead) {
ptr = container_of(pos, struct gauge_consumer, list);
ft_debug(
"+dump list name:%s start:%ld end:%ld car:%d int:%d\n",
ptr->name,
ptr->start, ptr->end, car, ptr->variable);
}
}
phead = &coulomb_head_minus;
if (list_empty(phead) != true) {
ft_debug("dump minus list start\n");
list_for_each(pos, phead) {
ptr = container_of(pos, struct gauge_consumer, list);
ft_debug(
"-dump list name:%s start:%ld end:%ld car:%d int:%d\n",
ptr->name,
ptr->start, ptr->end, car, ptr->variable);
}
}
mutex_coulomb_unlock();
}
void gauge_coulomb_before_reset(void)
{
if (init == false) {
ft_err("[%s]gauge_coulomb service is not rdy\n", __func__);
return;
}
mutex_coulomb_lock();
mutex_hw_coulomb_lock();
gauge_set_coulomb_interrupt1_ht(0);
gauge_set_coulomb_interrupt1_lt(0);
mutex_hw_coulomb_unlock();
mutex_coulomb_unlock();
reset_coulomb = gauge_get_coulomb();
ft_err("%s car=%ld\n",
__func__,
reset_coulomb);
gauge_coulomb_dump_list();
}
void gauge_coulomb_after_reset(void)
{
struct list_head *pos;
struct list_head *phead;
struct gauge_consumer *ptr;
unsigned long now = reset_coulomb;
unsigned long duraction;
if (init == false) {
ft_err("[%s]gauge_coulomb service is not rdy\n", __func__);
return;
}
ft_err("%s\n",
__func__);
mutex_coulomb_lock();
/* check plus list */
phead = &coulomb_head_plus;
list_for_each(pos, phead) {
ptr = container_of(pos, struct gauge_consumer, list);
ptr->start = 0;
duraction = ptr->end - now;
ptr->end = duraction;
ptr->variable = duraction;
ft_debug("[%s]+ %s %ld %ld %d\n",
__func__,
ptr->name,
ptr->start, ptr->end, ptr->variable);
}
/* check minus list */
phead = &coulomb_head_minus;
list_for_each(pos, phead) {
ptr = container_of(pos, struct gauge_consumer, list);
ptr->start = 0;
duraction = ptr->end - now;
ptr->end = duraction;
ptr->variable = duraction;
ft_debug("[%s]- %s %ld %ld %d\n",
__func__,
ptr->name,
ptr->start, ptr->end, ptr->variable);
}
mutex_coulomb_unlock();
gauge_coulomb_dump_list();
wake_up_gauge_coulomb();
}
void gauge_coulomb_start(struct gauge_consumer *coulomb, int car)
{
struct list_head *pos;
struct list_head *phead;
struct gauge_consumer *ptr = NULL;
int hw_car, now_car;
bool wake = false;
int car_now;
if (init == false) {
ft_err("[%s]gauge_coulomb service is not rdy\n", __func__);
return;
}
if (is_fg_disabled()) {
gauge_set_coulomb_interrupt1_ht(0);
gauge_set_coulomb_interrupt1_lt(0);
return;
}
if (car == 0)
return;
mutex_coulomb_lock();
car_now = gauge_get_coulomb();
/* del from old list */
if (list_empty(&coulomb->list) != true) {
ft_trace("coulomb_start del name:%s s:%ld e:%ld v:%d car:%d\n",
coulomb->name,
coulomb->start, coulomb->end, coulomb->variable, car_now);
list_del_init(&coulomb->list);
}
coulomb->start = car_now;
coulomb->end = coulomb->start + car;
coulomb->variable = car;
now_car = coulomb->start;
if (car > 0)
phead = &coulomb_head_plus;
else
phead = &coulomb_head_minus;
/* add node to list */
list_for_each(pos, phead) {
ptr = container_of(pos, struct gauge_consumer, list);
if (car > 0) {
if (coulomb->end < ptr->end)
break;
} else
if (coulomb->end > ptr->end)
break;
}
list_add(&coulomb->list, pos->prev);
if (car > 0) {
list_for_each(pos, phead) {
ptr = container_of(pos, struct gauge_consumer, list);
if (ptr->end - now_car <= 0)
wake = true;
else
break;
}
hw_car = ptr->end - now_car;
mutex_hw_coulomb_lock();
gauge_set_coulomb_interrupt1_ht(hw_car);
mutex_hw_coulomb_unlock();
} else {
list_for_each(pos, phead) {
ptr = container_of(pos, struct gauge_consumer, list);
if (ptr->end - now_car >= 0)
wake = true;
else
break;
}
hw_car = now_car - ptr->end;
mutex_hw_coulomb_lock();
gauge_set_coulomb_interrupt1_lt(hw_car);
mutex_hw_coulomb_unlock();
}
mutex_coulomb_unlock();
if (wake == true)
wake_up_gauge_coulomb();
ft_debug("coulomb_start dev:%s name:%s s:%ld e:%ld v:%d car:%d w:%d\n",
dev_name(coulomb->dev), coulomb->name, coulomb->start, coulomb->end,
coulomb->variable, car, wake);
}
void gauge_coulomb_stop(struct gauge_consumer *coulomb)
{
if (init == false) {
ft_err("[%s]gauge_coulomb service is not rdy\n", __func__);
return;
}
if (is_fg_disabled()) {
gauge_set_coulomb_interrupt1_ht(0);
gauge_set_coulomb_interrupt1_lt(0);
fix_coverity = 1;
return;
}
ft_debug("coulomb_stop name:%s %ld %ld %d\n",
coulomb->name, coulomb->start, coulomb->end,
coulomb->variable);
mutex_coulomb_lock();
list_del_init(&coulomb->list);
mutex_coulomb_unlock();
}
static struct timespec sstart[10];
void gauge_coulomb_int_handler(void)
{
int car, hw_car;
struct list_head *pos;
struct list_head *phead;
struct gauge_consumer *ptr = NULL;
get_monotonic_boottime(&sstart[0]);
car = gauge_get_coulomb();
ft_trace("[%s] car:%d preCar:%d\n",
__func__,
car, pre_coulomb);
get_monotonic_boottime(&sstart[1]);
if (list_empty(&coulomb_head_plus) != true) {
pos = coulomb_head_plus.next;
phead = &coulomb_head_plus;
for (pos = phead->next; pos != phead;) {
struct list_head *ptmp;
ptr = container_of(pos, struct gauge_consumer, list);
if (ptr->end <= car) {
ptmp = pos;
pos = pos->next;
list_del_init(ptmp);
ft_trace(
"[%s]+ %s s:%ld e:%ld car:%d %d int:%d timeout\n",
__func__,
ptr->name,
ptr->start, ptr->end, car,
pre_coulomb, ptr->variable);
if (ptr->callback) {
mutex_coulomb_unlock();
ptr->callback(ptr);
mutex_coulomb_lock();
pos = coulomb_head_plus.next;
}
} else
break;
}
if (list_empty(&coulomb_head_plus) != true) {
pos = coulomb_head_plus.next;
ptr = container_of(pos, struct gauge_consumer, list);
hw_car = ptr->end - car;
ft_trace(
"[%s]+ %s %ld %ld %d now:%d dif:%d\n",
__func__,
ptr->name,
ptr->start, ptr->end,
ptr->variable, car, hw_car);
mutex_hw_coulomb_lock();
gauge_set_coulomb_interrupt1_ht(hw_car);
mutex_hw_coulomb_unlock();
} else
ft_trace("+ list is empty\n");
} else
ft_trace("+ list is empty\n");
if (list_empty(&coulomb_head_minus) != true) {
pos = coulomb_head_minus.next;
phead = &coulomb_head_minus;
for (pos = phead->next; pos != phead;) {
struct list_head *ptmp;
ptr = container_of(pos, struct gauge_consumer, list);
if (ptr->end >= car) {
ptmp = pos;
pos = pos->next;
list_del_init(ptmp);
ft_trace(
"[%s]- %s s:%ld e:%ld car:%d %d int:%d timeout\n",
__func__,
ptr->name,
ptr->start, ptr->end,
car, pre_coulomb, ptr->variable);
if (ptr->callback) {
mutex_coulomb_unlock();
ptr->callback(ptr);
mutex_coulomb_lock();
pos = coulomb_head_minus.next;
}
} else
break;
}
if (list_empty(&coulomb_head_minus) != true) {
pos = coulomb_head_minus.next;
ptr = container_of(pos, struct gauge_consumer, list);
hw_car = car - ptr->end;
ft_trace(
"[%s]- %s %ld %ld %d now:%d dif:%d\n",
__func__,
ptr->name,
ptr->start, ptr->end,
ptr->variable, car, hw_car);
mutex_hw_coulomb_lock();
gauge_set_coulomb_interrupt1_lt(hw_car);
mutex_hw_coulomb_unlock();
} else
ft_trace("- list is empty\n");
} else
ft_trace("- list is empty\n");
pre_coulomb = car;
get_monotonic_boottime(&sstart[2]);
sstart[0] = timespec_sub(sstart[1], sstart[0]);
sstart[1] = timespec_sub(sstart[2], sstart[1]);
}
static int gauge_coulomb_thread(void *arg)
{
unsigned long flags = 0;
struct timespec start, end, duraction;
int ret = 0;
while (1) {
ret = wait_event_interruptible(wait_que, (coulomb_thread_timeout == true));
coulomb_thread_timeout = false;
get_monotonic_boottime(&start);
ft_trace("[%s]=>\n", __func__);
mutex_coulomb_lock();
gauge_coulomb_int_handler();
mutex_coulomb_unlock();
spin_lock_irqsave(&slock, flags);
__pm_relax(wlock);
spin_unlock_irqrestore(&slock, flags);
get_monotonic_boottime(&end);
duraction = timespec_sub(end, start);
ft_trace(
"%s time:%d ms %d %d, ret=%d\n",
__func__,
(int)(duraction.tv_nsec / 1000000),
(int)(sstart[0].tv_nsec / 1000000),
(int)(sstart[1].tv_nsec / 1000000), ret);
if (fix_coverity == 1)
break;
}
return 0;
}
void gauge_coulomb_service_init(void)
{
ft_trace("gauge coulomb_service_init\n");
INIT_LIST_HEAD(&coulomb_head_minus);
INIT_LIST_HEAD(&coulomb_head_plus);
mutex_init(&coulomb_lock);
mutex_init(&hw_coulomb_lock);
spin_lock_init(&slock);
//wakeup_source_init(wlock, "gauge coulomb wakelock");
wlock = wakeup_source_register(NULL, "gauge coulomb wakelock");
init_waitqueue_head(&wait_que);
kthread_run(gauge_coulomb_thread, NULL, "gauge_coulomb_thread");
pmic_register_interrupt_callback(
FG_BAT1_INT_L_NO, wake_up_gauge_coulomb);
pmic_register_interrupt_callback(
FG_BAT1_INT_H_NO, wake_up_gauge_coulomb);
pre_coulomb = gauge_get_coulomb();
init = true;
#ifdef _DEA_MODIFY_
wait_que.function = gauge_coulomb_int_handler;
INIT_LIST_HEAD(&wait_que.list);
wait_que.name = "gauge coulomb service";
#endif
}