456 lines
10 KiB
C
456 lines
10 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (c) 2021 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/init.h> /* For init/exit macros */
|
|
#include <linux/module.h> /* For MODULE_ marcros */
|
|
#include <linux/list.h>
|
|
#include <linux/device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_wakeup.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include "mtk_gauge_time_service.h"
|
|
|
|
|
|
static struct list_head gtimer_head = LIST_HEAD_INIT(gtimer_head);
|
|
|
|
static bool gtimer_thread_timeout;
|
|
static int ftlog_level;
|
|
|
|
static struct mutex gtimer_lock;
|
|
static spinlock_t slock;
|
|
static struct wakeup_source *wlock;
|
|
static wait_queue_head_t wait_que;
|
|
static struct hrtimer gtimer_kthread_timer;
|
|
static struct timespec gtimer_suspend_time;
|
|
|
|
#define FTLOG_ERROR_LEVEL 1
|
|
#define FTLOG_DEBUG_LEVEL 2
|
|
#define FTLOG_TRACE_LEVEL 3
|
|
|
|
#define ft_err(fmt, args...) \
|
|
do { \
|
|
if (ftlog_level >= FTLOG_ERROR_LEVEL) { \
|
|
pr_notice(fmt, ##args); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define ft_debug(fmt, args...) \
|
|
do { \
|
|
if (ftlog_level >= FTLOG_DEBUG_LEVEL) { \
|
|
pr_notice(fmt, ##args); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define ft_trace(fmt, args...)\
|
|
do { \
|
|
if (ftlog_level >= FTLOG_TRACE_LEVEL) { \
|
|
pr_notice(fmt, ##args);\
|
|
} \
|
|
} while (0)
|
|
|
|
#define ft_info(fmt, args...)\
|
|
do { \
|
|
} while (0)
|
|
|
|
|
|
void mutex_gtimer_lock(void)
|
|
{
|
|
mutex_lock(>imer_lock);
|
|
}
|
|
|
|
void mutex_gtimer_unlock(void)
|
|
{
|
|
mutex_unlock(>imer_lock);
|
|
}
|
|
|
|
void gtimer_set_log_level(int x)
|
|
{
|
|
ftlog_level = x;
|
|
}
|
|
|
|
void gtimer_dump_list(void)
|
|
{
|
|
struct list_head *pos;
|
|
struct list_head *phead = >imer_head;
|
|
struct gtimer *ptr;
|
|
struct timespec time_now;
|
|
|
|
mutex_gtimer_lock();
|
|
get_monotonic_boottime(&time_now);
|
|
|
|
ft_debug("dump gtimer list start %ld\n", time_now.tv_sec);
|
|
list_for_each(pos, phead) {
|
|
ptr = container_of(pos, struct gtimer, list);
|
|
ft_debug("dump list name:%s time:%ld int:%d\n", ptr->name,
|
|
ptr->endtime.tv_sec, ptr->interval);
|
|
}
|
|
ft_debug("dump list end\n");
|
|
mutex_gtimer_unlock();
|
|
}
|
|
|
|
void wake_up_gtimer(void)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&slock, flags);
|
|
if (wlock->active == 0)
|
|
__pm_stay_awake(wlock);
|
|
spin_unlock_irqrestore(&slock, flags);
|
|
|
|
gtimer_thread_timeout = true;
|
|
wake_up(&wait_que);
|
|
ft_debug("%s\n", __func__);
|
|
}
|
|
|
|
void gtimer_start_timer(int sec)
|
|
{
|
|
ktime_t ktime = ktime_set(sec, 0);
|
|
|
|
ft_debug("%s %d",
|
|
__func__,
|
|
sec);
|
|
hrtimer_start(>imer_kthread_timer, ktime, HRTIMER_MODE_REL);
|
|
}
|
|
|
|
void gtimer_init(struct gtimer *timer, struct device *dev, char *name)
|
|
{
|
|
timer->name = name;
|
|
INIT_LIST_HEAD(&timer->list);
|
|
timer->dev = dev;
|
|
}
|
|
|
|
void gtimer_start(struct gtimer *timer, int sec)
|
|
{
|
|
struct list_head *pos;
|
|
struct list_head *phead = >imer_head;
|
|
struct gtimer *ptr;
|
|
struct timespec time, time_now;
|
|
bool wakeup = false;
|
|
int time_interval;
|
|
|
|
mutex_gtimer_lock();
|
|
|
|
hrtimer_cancel(>imer_kthread_timer);
|
|
|
|
time.tv_sec = sec;
|
|
time.tv_nsec = 0;
|
|
timer->interval = sec;
|
|
|
|
get_monotonic_boottime(&time_now);
|
|
|
|
timer->endtime = timespec_add(time_now, time);
|
|
|
|
ft_debug("%s dev:%s name:%s %ld %ld %d\n",
|
|
__func__,
|
|
dev_name(timer->dev), timer->name,
|
|
time_now.tv_sec, timer->endtime.tv_sec, sec);
|
|
|
|
if (list_empty(&timer->list) != true) {
|
|
ft_debug("%s dev:%s name:%s time:%ld %ld int:%d is not empty\n",
|
|
__func__,
|
|
dev_name(timer->dev), timer->name,
|
|
time_now.tv_sec, timer->endtime.tv_sec, sec);
|
|
list_del_init(&timer->list);
|
|
}
|
|
|
|
list_for_each(pos, phead) {
|
|
ptr = container_of(pos, struct gtimer, list);
|
|
if (timespec_compare(&timer->endtime, &ptr->endtime) < 0)
|
|
break;
|
|
}
|
|
|
|
list_add(&timer->list, pos->prev);
|
|
|
|
pos = gtimer_head.next;
|
|
if (list_empty(pos) != true) {
|
|
ptr = container_of(pos, struct gtimer, list);
|
|
if (timespec_compare(&ptr->endtime, &time_now) < 0)
|
|
wakeup = true;
|
|
else {
|
|
time = timespec_sub(ptr->endtime, time_now);
|
|
if (time.tv_sec < 1)
|
|
time_interval = 1;
|
|
else
|
|
time_interval = time.tv_sec;
|
|
gtimer_start_timer(time_interval);
|
|
}
|
|
}
|
|
mutex_gtimer_unlock();
|
|
|
|
if (wakeup == true)
|
|
wake_up_gtimer();
|
|
|
|
}
|
|
|
|
void gtimer_stop(struct gtimer *timer)
|
|
{
|
|
mutex_gtimer_lock();
|
|
if (list_empty(&timer->list) != true)
|
|
list_del_init(&timer->list);
|
|
mutex_gtimer_unlock();
|
|
}
|
|
|
|
|
|
static void gtimer_handler(void)
|
|
{
|
|
struct list_head *pos = gtimer_head.next;
|
|
struct list_head *phead = >imer_head;
|
|
struct gtimer *ptr;
|
|
struct timespec time;
|
|
int time_interval;
|
|
|
|
hrtimer_cancel(>imer_kthread_timer);
|
|
|
|
ft_info("%s\n", __func__);
|
|
for (pos = phead->next; pos != phead;) {
|
|
struct list_head *ptmp;
|
|
|
|
get_monotonic_boottime(&time);
|
|
ptr = container_of(pos, struct gtimer, list);
|
|
|
|
ft_info("%s name:%s %ld %ld %d %d\n",
|
|
__func__,
|
|
ptr->name, time.tv_sec,
|
|
ptr->endtime.tv_sec, ptr->interval,
|
|
timespec_compare(&time, &ptr->endtime));
|
|
|
|
if (timespec_compare(&time, &ptr->endtime) >= 0) {
|
|
ptmp = pos;
|
|
pos = pos->next;
|
|
list_del_init(ptmp);
|
|
ft_debug("%s name:%s %ld %d\n",
|
|
__func__,
|
|
ptr->name,
|
|
ptr->endtime.tv_sec, ptr->interval);
|
|
if (ptr->callback) {
|
|
mutex_gtimer_unlock();
|
|
ptr->callback(ptr);
|
|
mutex_gtimer_lock();
|
|
pos = gtimer_head.next;
|
|
}
|
|
} else
|
|
pos = pos->next;
|
|
}
|
|
|
|
pos = gtimer_head.next;
|
|
if (list_empty(pos) != true) {
|
|
ptr = container_of(pos, struct gtimer, list);
|
|
|
|
time = timespec_sub(ptr->endtime, time);
|
|
if (time.tv_sec < 1)
|
|
time_interval = 1;
|
|
else
|
|
time_interval = time.tv_sec;
|
|
gtimer_start_timer(time_interval);
|
|
}
|
|
}
|
|
|
|
|
|
static int gtimer_thread(void *arg)
|
|
{
|
|
unsigned long flags;
|
|
struct timespec stime, endtime, duraction;
|
|
|
|
while (1) {
|
|
wait_event(wait_que, (gtimer_thread_timeout == true));
|
|
gtimer_thread_timeout = false;
|
|
get_monotonic_boottime(&stime);
|
|
mutex_gtimer_lock();
|
|
|
|
gtimer_handler();
|
|
|
|
spin_lock_irqsave(&slock, flags);
|
|
__pm_relax(wlock);
|
|
spin_unlock_irqrestore(&slock, flags);
|
|
|
|
mutex_gtimer_unlock();
|
|
get_monotonic_boottime(&endtime);
|
|
duraction = timespec_sub(endtime, stime);
|
|
if (duraction.tv_sec == -56789)
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void gtimer_suspend(void)
|
|
{
|
|
ft_err("%s\n", __func__);
|
|
hrtimer_cancel(>imer_kthread_timer);
|
|
}
|
|
|
|
static void gtimer_resume(void)
|
|
{
|
|
struct list_head *pos = gtimer_head.next;
|
|
struct gtimer *ptr;
|
|
struct timespec time, diff;
|
|
int time_interval;
|
|
|
|
get_monotonic_boottime(&time);
|
|
ft_err("%s %ld\n",
|
|
__func__,
|
|
time.tv_sec);
|
|
gtimer_dump_list();
|
|
|
|
pos = gtimer_head.next;
|
|
if (list_empty(pos) != true) {
|
|
ptr = container_of(pos, struct gtimer, list);
|
|
|
|
if (timespec_compare(&time, &ptr->endtime) >= 0) {
|
|
ft_err("%s now:%ld expired:%s %ld\n",
|
|
__func__,
|
|
time.tv_sec,
|
|
ptr->name, ptr->endtime.tv_sec);
|
|
wake_up_gtimer();
|
|
} else {
|
|
diff = timespec_sub(ptr->endtime, time);
|
|
if (diff.tv_sec < 1)
|
|
time_interval = 1;
|
|
else
|
|
time_interval = diff.tv_sec;
|
|
gtimer_start_timer(time_interval);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static int gtimer_pm_event(
|
|
struct notifier_block *notifier, unsigned long pm_event, void *unused)
|
|
{
|
|
switch (pm_event) {
|
|
case PM_HIBERNATION_PREPARE: /* Going to hibernate */
|
|
case PM_RESTORE_PREPARE: /* Going to restore a saved image */
|
|
case PM_SUSPEND_PREPARE: /* Going to suspend the system */
|
|
get_monotonic_boottime(>imer_suspend_time);
|
|
ft_err("[%s] pm_event %lu %ld\n",
|
|
__func__, pm_event,
|
|
gtimer_suspend_time.tv_sec);
|
|
gtimer_suspend();
|
|
return NOTIFY_DONE;
|
|
|
|
case PM_POST_SUSPEND: /* Suspend finished */
|
|
case PM_POST_RESTORE: /* Restore failed */
|
|
ft_err("[%s] pm_event %lu\n", __func__, pm_event);
|
|
gtimer_resume();
|
|
return NOTIFY_DONE;
|
|
|
|
case PM_POST_HIBERNATION: /* Hibernation finished */
|
|
ft_err("[%s] pm_event %lu\n", __func__, pm_event);
|
|
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block gtimer_pm_notifier_block = {
|
|
.notifier_call = gtimer_pm_event,
|
|
.priority = 0,
|
|
};
|
|
|
|
enum hrtimer_restart gtimer_kthread_hrtimer_func(struct hrtimer *timer)
|
|
{
|
|
wake_up_gtimer();
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
signed int get_dynamic_period(
|
|
int first_use, int first_wakeup_time, int battery_capacity_level)
|
|
{
|
|
struct timespec duraction;
|
|
struct list_head *pos = gtimer_head.next;
|
|
struct gtimer *ptr;
|
|
signed int sec = 4800;
|
|
|
|
pos = gtimer_head.next;
|
|
if (list_empty(pos) != true) {
|
|
ptr = container_of(pos, struct gtimer, list);
|
|
|
|
duraction = timespec_sub(ptr->endtime, gtimer_suspend_time);
|
|
sec = duraction.tv_sec + 1;
|
|
if (sec <= 10)
|
|
sec = 10;
|
|
ft_err("%s time:now:%ld next:%ld diff:%d\n",
|
|
__func__,
|
|
gtimer_suspend_time.tv_sec, ptr->endtime.tv_sec, sec);
|
|
} else
|
|
ft_err("%s time:%d\n",
|
|
__func__,
|
|
sec);
|
|
|
|
return sec;
|
|
}
|
|
|
|
|
|
static int gauge_timer_service_probe(struct platform_device *pdev)
|
|
{
|
|
mutex_init(>imer_lock);
|
|
spin_lock_init(&slock);
|
|
wlock = wakeup_source_register(NULL, "gtime timer wakelock");
|
|
init_waitqueue_head(&wait_que);
|
|
|
|
|
|
hrtimer_init(>imer_kthread_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
gtimer_kthread_timer.function = gtimer_kthread_hrtimer_func;
|
|
|
|
kthread_run(gtimer_thread, NULL, "gauge_timer_thread");
|
|
|
|
register_pm_notifier(>imer_pm_notifier_block);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gauge_timer_service_remove(struct platform_device *pdev)
|
|
{
|
|
struct mt6355_gauge *mt = platform_get_drvdata(pdev);
|
|
|
|
if (mt)
|
|
devm_kfree(&pdev->dev, mt);
|
|
return 0;
|
|
}
|
|
|
|
static void gauge_timer_service_shutdown(struct platform_device *dev)
|
|
{
|
|
}
|
|
|
|
|
|
static const struct of_device_id gauge_timer_service_of_match[] = {
|
|
{.compatible = "mediatek,gauge_timer_service",},
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, gauge_timer_service_of_match);
|
|
|
|
static struct platform_driver gauge_timer_service_driver = {
|
|
.probe = gauge_timer_service_probe,
|
|
.remove = gauge_timer_service_remove,
|
|
.shutdown = gauge_timer_service_shutdown,
|
|
.driver = {
|
|
.name = "gauge_timer_service",
|
|
.of_match_table = gauge_timer_service_of_match,
|
|
},
|
|
};
|
|
|
|
static int __init gauge_timer_service_init(void)
|
|
{
|
|
return platform_driver_register(&gauge_timer_service_driver);
|
|
}
|
|
device_initcall(gauge_timer_service_init);
|
|
|
|
static void __exit gauge_timer_service_exit(void)
|
|
{
|
|
platform_driver_unregister(&gauge_timer_service_driver);
|
|
}
|
|
module_exit(gauge_timer_service_exit);
|
|
|
|
MODULE_AUTHOR("wy.chuang <wy.chuang@mediatek.com>");
|
|
MODULE_DESCRIPTION("MTK Gauge time service Driver");
|
|
MODULE_LICENSE("GPL");
|
|
|