6db4831e98
Android 14
498 lines
12 KiB
C
498 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
* Author Wy Chuang<wy.chuang@mediatek.com>
|
|
*/
|
|
|
|
#include <linux/list.h>
|
|
#include <linux/device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/platform_device.h>
|
|
#include "mtk_battery.h"
|
|
|
|
int fix_coverity;
|
|
|
|
static void wake_up_gauge_coulomb(struct mtk_battery *gm)
|
|
{
|
|
unsigned long flags = 0;
|
|
struct mtk_coulomb_service *cs;
|
|
|
|
cs = &gm->cs;
|
|
|
|
if (cs == NULL || cs->init == false)
|
|
return;
|
|
|
|
bm_debug("%s %d %d\n",
|
|
__func__,
|
|
cs->wlock->active,
|
|
cs->coulomb_thread_timeout);
|
|
|
|
mutex_lock(&cs->hw_coulomb_lock);
|
|
gauge_set_property(GAUGE_PROP_COULOMB_HT_INTERRUPT, 300);
|
|
gauge_set_property(GAUGE_PROP_COULOMB_LT_INTERRUPT, 300);
|
|
mutex_unlock(&cs->hw_coulomb_lock);
|
|
spin_lock_irqsave(&cs->slock, flags);
|
|
if (cs->wlock->active == 0)
|
|
__pm_stay_awake(cs->wlock);
|
|
spin_unlock_irqrestore(&cs->slock, flags);
|
|
|
|
cs->coulomb_thread_timeout = true;
|
|
wake_up(&cs->wait_que);
|
|
bm_debug("%s end\n", __func__);
|
|
}
|
|
|
|
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(struct mtk_battery *gm)
|
|
{
|
|
struct list_head *pos;
|
|
struct list_head *phead;
|
|
struct gauge_consumer *ptr;
|
|
int car;
|
|
struct mtk_coulomb_service *cs;
|
|
|
|
cs = &gm->cs;
|
|
if (cs->init == false)
|
|
return;
|
|
bm_debug("%s %d %d\n",
|
|
__func__,
|
|
cs->wlock->active,
|
|
cs->coulomb_thread_timeout);
|
|
|
|
phead = &cs->coulomb_head_plus;
|
|
mutex_lock(&cs->coulomb_lock);
|
|
gauge_get_property(GAUGE_PROP_COULOMB, &car);
|
|
if (list_empty(phead) != true) {
|
|
bm_debug("dump plus list start\n");
|
|
list_for_each(pos, phead) {
|
|
ptr = container_of(pos, struct gauge_consumer, list);
|
|
bm_debug(
|
|
"+dump list name:%s start:%ld end:%ld car:%d int:%d\n",
|
|
ptr->name,
|
|
ptr->start, ptr->end, car, ptr->variable);
|
|
}
|
|
}
|
|
|
|
phead = &cs->coulomb_head_minus;
|
|
if (list_empty(phead) != true) {
|
|
bm_debug("dump minus list start\n");
|
|
list_for_each(pos, phead) {
|
|
ptr = container_of(pos, struct gauge_consumer, list);
|
|
bm_debug(
|
|
"-dump list name:%s start:%ld end:%ld car:%d int:%d\n",
|
|
ptr->name,
|
|
ptr->start, ptr->end, car, ptr->variable);
|
|
}
|
|
}
|
|
mutex_unlock(&cs->coulomb_lock);
|
|
}
|
|
|
|
void gauge_coulomb_before_reset(struct mtk_battery *gm)
|
|
{
|
|
struct mtk_coulomb_service *cs;
|
|
int val;
|
|
|
|
cs = &gm->cs;
|
|
|
|
if (cs->init == false) {
|
|
bm_err("[%s]gauge_coulomb service is not rdy\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
mutex_lock(&cs->coulomb_lock);
|
|
mutex_lock(&cs->hw_coulomb_lock);
|
|
gauge_set_property(GAUGE_PROP_COULOMB_HT_INTERRUPT, 0);
|
|
gauge_set_property(GAUGE_PROP_COULOMB_LT_INTERRUPT, 0);
|
|
mutex_unlock(&cs->hw_coulomb_lock);
|
|
mutex_unlock(&cs->coulomb_lock);
|
|
|
|
gauge_get_property(GAUGE_PROP_COULOMB, &val);
|
|
cs->reset_coulomb = val;
|
|
bm_err("%s car=%ld\n",
|
|
__func__,
|
|
cs->reset_coulomb);
|
|
gauge_coulomb_dump_list(gm);
|
|
}
|
|
|
|
void gauge_coulomb_after_reset(struct mtk_battery *gm)
|
|
{
|
|
struct list_head *pos;
|
|
struct list_head *phead;
|
|
struct gauge_consumer *ptr;
|
|
unsigned long now;
|
|
unsigned long duraction;
|
|
struct mtk_coulomb_service *cs;
|
|
|
|
cs = &gm->cs;
|
|
|
|
if (cs->init == false)
|
|
return;
|
|
bm_err("%s\n", __func__);
|
|
now = cs->reset_coulomb;
|
|
mutex_lock(&cs->coulomb_lock);
|
|
|
|
/* check plus list */
|
|
phead = &cs->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;
|
|
bm_debug("[%s]+ %s %ld %ld %d\n",
|
|
__func__,
|
|
ptr->name,
|
|
ptr->start, ptr->end, ptr->variable);
|
|
}
|
|
|
|
/* check minus list */
|
|
phead = &cs->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;
|
|
bm_debug("[%s]- %s %ld %ld %d\n",
|
|
__func__,
|
|
ptr->name,
|
|
ptr->start, ptr->end, ptr->variable);
|
|
}
|
|
|
|
mutex_unlock(&cs->coulomb_lock);
|
|
gauge_coulomb_dump_list(gm);
|
|
wake_up_gauge_coulomb(gm);
|
|
}
|
|
|
|
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;
|
|
int val;
|
|
struct mtk_coulomb_service *cs = NULL;
|
|
struct mtk_battery *gm;
|
|
|
|
gm = get_mtk_battery();
|
|
|
|
if (gm != NULL)
|
|
cs = &gm->cs;
|
|
|
|
if (car == 0)
|
|
return;
|
|
|
|
if (cs == NULL)
|
|
return;
|
|
|
|
if (cs->init == false)
|
|
return;
|
|
|
|
mutex_lock(&cs->coulomb_lock);
|
|
gauge_get_property(GAUGE_PROP_COULOMB, &val);
|
|
car_now = val;
|
|
/* del from old list */
|
|
if (list_empty(&coulomb->list) != true) {
|
|
bm_debug("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 = &cs->coulomb_head_plus;
|
|
else
|
|
phead = &cs->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_lock(&cs->hw_coulomb_lock);
|
|
gauge_set_property(GAUGE_PROP_COULOMB_HT_INTERRUPT, hw_car);
|
|
mutex_unlock(&cs->hw_coulomb_lock);
|
|
} 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_lock(&cs->hw_coulomb_lock);
|
|
gauge_set_property(GAUGE_PROP_COULOMB_LT_INTERRUPT, hw_car);
|
|
mutex_unlock(&cs->hw_coulomb_lock);
|
|
}
|
|
mutex_unlock(&cs->coulomb_lock);
|
|
|
|
if (wake == true)
|
|
wake_up_gauge_coulomb(gm);
|
|
|
|
bm_debug("%s dev:%s name:%s s:%ld e:%ld v:%d car:%d w:%d\n",
|
|
__func__,
|
|
dev_name(coulomb->dev), coulomb->name, coulomb->start, coulomb->end,
|
|
coulomb->variable, car, wake);
|
|
}
|
|
|
|
void gauge_coulomb_stop(struct gauge_consumer *coulomb)
|
|
{
|
|
struct mtk_coulomb_service *cs;
|
|
struct mtk_battery *gm;
|
|
|
|
gm = get_mtk_battery();
|
|
|
|
if (gm == NULL)
|
|
return;
|
|
|
|
cs = &gm->cs;
|
|
|
|
if (cs == NULL)
|
|
return;
|
|
|
|
bm_debug("%s name: %s %ld %ld %d\n",
|
|
__func__,
|
|
coulomb->name, coulomb->start, coulomb->end,
|
|
coulomb->variable);
|
|
|
|
mutex_lock(&cs->coulomb_lock);
|
|
list_del_init(&coulomb->list);
|
|
mutex_unlock(&cs->coulomb_lock);
|
|
|
|
}
|
|
|
|
static void gauge_coulomb_int_handler(struct mtk_coulomb_service *cs)
|
|
{
|
|
int car, hw_car;
|
|
struct list_head *pos;
|
|
struct list_head *phead;
|
|
struct gauge_consumer *ptr = NULL;
|
|
struct mtk_battery *gm;
|
|
|
|
gm = get_mtk_battery();
|
|
gauge_get_property(GAUGE_PROP_COULOMB, &car);
|
|
bm_debug("[%s] car:%d preCar:%d\n",
|
|
__func__,
|
|
car, cs->pre_coulomb);
|
|
|
|
if (list_empty(&cs->coulomb_head_plus) != true) {
|
|
pos = cs->coulomb_head_plus.next;
|
|
phead = &cs->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);
|
|
bm_debug(
|
|
"[%s]+ %s s:%ld e:%ld car:%d %d int:%d timeout\n",
|
|
__func__,
|
|
ptr->name,
|
|
ptr->start, ptr->end, car,
|
|
cs->pre_coulomb, ptr->variable);
|
|
if (ptr->callback) {
|
|
mutex_unlock(&cs->coulomb_lock);
|
|
ptr->callback(ptr);
|
|
mutex_lock(&cs->coulomb_lock);
|
|
pos = cs->coulomb_head_plus.next;
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
|
|
if (list_empty(&cs->coulomb_head_plus) != true) {
|
|
pos = cs->coulomb_head_plus.next;
|
|
ptr = container_of(pos, struct gauge_consumer, list);
|
|
hw_car = ptr->end - car;
|
|
bm_debug(
|
|
"[%s]+ %s %ld %ld %d now:%d dif:%d\n",
|
|
__func__,
|
|
ptr->name,
|
|
ptr->start, ptr->end,
|
|
ptr->variable, car, hw_car);
|
|
mutex_lock(&cs->hw_coulomb_lock);
|
|
gauge_set_property(GAUGE_PROP_COULOMB_HT_INTERRUPT,
|
|
hw_car);
|
|
mutex_unlock(&cs->hw_coulomb_lock);
|
|
} else
|
|
bm_debug("+ list is empty\n");
|
|
} else
|
|
bm_debug("+ list is empty\n");
|
|
|
|
if (list_empty(&cs->coulomb_head_minus) != true) {
|
|
pos = cs->coulomb_head_minus.next;
|
|
phead = &cs->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);
|
|
bm_debug(
|
|
"[%s]- %s s:%ld e:%ld car:%d %d int:%d timeout\n",
|
|
__func__,
|
|
ptr->name,
|
|
ptr->start, ptr->end,
|
|
car, cs->pre_coulomb, ptr->variable);
|
|
if (ptr->callback) {
|
|
mutex_unlock(&cs->coulomb_lock);
|
|
ptr->callback(ptr);
|
|
mutex_lock(&cs->coulomb_lock);
|
|
pos = cs->coulomb_head_minus.next;
|
|
}
|
|
|
|
} else
|
|
break;
|
|
}
|
|
|
|
if (list_empty(&cs->coulomb_head_minus) != true) {
|
|
pos = cs->coulomb_head_minus.next;
|
|
ptr = container_of(pos, struct gauge_consumer, list);
|
|
hw_car = car - ptr->end;
|
|
bm_debug(
|
|
"[%s]- %s %ld %ld %d now:%d dif:%d\n",
|
|
__func__,
|
|
ptr->name,
|
|
ptr->start, ptr->end,
|
|
ptr->variable, car, hw_car);
|
|
mutex_lock(&cs->hw_coulomb_lock);
|
|
gauge_set_property(GAUGE_PROP_COULOMB_LT_INTERRUPT,
|
|
hw_car);
|
|
mutex_unlock(&cs->hw_coulomb_lock);
|
|
} else
|
|
bm_debug("- list is empty\n");
|
|
} else
|
|
bm_debug("- list is empty\n");
|
|
|
|
cs->pre_coulomb = car;
|
|
}
|
|
|
|
static int gauge_coulomb_thread(void *arg)
|
|
{
|
|
struct mtk_coulomb_service *cs = (struct mtk_coulomb_service *)arg;
|
|
unsigned long flags = 0;
|
|
struct timespec start, end, duraction;
|
|
struct mtk_battery *gm;
|
|
|
|
gm = get_mtk_battery();
|
|
bm_debug("[%s]=>\n", __func__);
|
|
|
|
if (gm == NULL)
|
|
fix_coverity = 1;
|
|
|
|
while (1) {
|
|
wait_event(cs->wait_que, (cs->coulomb_thread_timeout == true));
|
|
cs->coulomb_thread_timeout = false;
|
|
get_monotonic_boottime(&start);
|
|
|
|
mutex_lock(&cs->coulomb_lock);
|
|
gauge_coulomb_int_handler(cs);
|
|
mutex_unlock(&cs->coulomb_lock);
|
|
|
|
spin_lock_irqsave(&cs->slock, flags);
|
|
__pm_relax(cs->wlock);
|
|
spin_unlock_irqrestore(&cs->slock, flags);
|
|
|
|
get_monotonic_boottime(&end);
|
|
duraction = timespec_sub(end, start);
|
|
|
|
if (fix_coverity == 1)
|
|
break;
|
|
|
|
bm_debug(
|
|
"%s time:%d ms\n",
|
|
__func__,
|
|
(int)(duraction.tv_nsec / 1000000));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t coulomb_irq(int irq, void *data)
|
|
{
|
|
struct mtk_battery *gm = data;
|
|
|
|
bm_debug("%s\n", __func__);
|
|
wake_up_gauge_coulomb(gm);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
void gauge_coulomb_service_init(struct mtk_battery *gm)
|
|
{
|
|
int val;
|
|
struct mtk_coulomb_service *cs;
|
|
int ret;
|
|
|
|
bm_debug("%s\n", __func__);
|
|
cs = &gm->cs;
|
|
INIT_LIST_HEAD(&cs->coulomb_head_minus);
|
|
INIT_LIST_HEAD(&cs->coulomb_head_plus);
|
|
mutex_init(&cs->coulomb_lock);
|
|
mutex_init(&cs->hw_coulomb_lock);
|
|
spin_lock_init(&cs->slock);
|
|
cs->wlock = wakeup_source_register(NULL, "gauge coulomb wakelock");
|
|
init_waitqueue_head(&cs->wait_que);
|
|
kthread_run(gauge_coulomb_thread, cs, "gauge_coulomb_thread");
|
|
|
|
ret = devm_request_threaded_irq(&gm->gauge->pdev->dev,
|
|
gm->gauge->irq_no[COULOMB_H_IRQ],
|
|
NULL, coulomb_irq,
|
|
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
|
|
"mtk_gauge_coulomb_high",
|
|
gm);
|
|
//disable_irq_nosync(gm->gauge->coulomb_h_irq);
|
|
if (ret)
|
|
bm_err("failed to request coulomb h irq\n");
|
|
|
|
ret = devm_request_threaded_irq(&gm->gauge->pdev->dev,
|
|
gm->gauge->irq_no[COULOMB_L_IRQ],
|
|
NULL, coulomb_irq,
|
|
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
|
|
"mtk_gauge_coulomb_low",
|
|
gm);
|
|
//disable_irq_nosync(gm->gauge->coulomb_l_irq);
|
|
if (ret)
|
|
bm_err("failed to request coulomb l irq\n");
|
|
|
|
gauge_get_property(GAUGE_PROP_COULOMB, &val);
|
|
cs->pre_coulomb = val;
|
|
cs->init = true;
|
|
}
|