6db4831e98
Android 14
1301 lines
31 KiB
C
1301 lines
31 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (C) 2016 MediaTek Inc.
|
|
*/
|
|
|
|
|
|
#include <asm/div64.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/pm_wakeup.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/types.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/mfd/mt6358/core.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/io.h>
|
|
#include <asm/div64.h>
|
|
/* For KPOC alarm */
|
|
#include <linux/notifier.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/of.h>
|
|
|
|
#include "../misc/mediatek/include/mt-plat/mtk_boot_common.h"
|
|
#include "../misc/mediatek/include/mt-plat/mtk_reboot.h"
|
|
#include "../misc/mediatek/include/mt-plat/mtk_rtc.h"
|
|
#ifdef pr_fmt
|
|
#undef pr_fmt
|
|
#endif
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#define RTC_NAME "mt-rtc"
|
|
#define IPIMB
|
|
|
|
|
|
/* we map HW YEA 0 (2000) to 1968 not 1970 because 2000 is the leap year */
|
|
#define RTC_MIN_YEAR 1968
|
|
#define RTC_BASE_YEAR 1900
|
|
#define RTC_MIN_YEAR_OFFSET (RTC_MIN_YEAR - RTC_BASE_YEAR)
|
|
|
|
/* Min, Hour, Dom... register offset to RTC_TC_SEC */
|
|
#define RTC_OFFSET_SEC 0
|
|
#define RTC_OFFSET_MIN 1
|
|
#define RTC_OFFSET_HOUR 2
|
|
#define RTC_OFFSET_DOM 3
|
|
#define RTC_OFFSET_DOW 4
|
|
#define RTC_OFFSET_MTH 5
|
|
#define RTC_OFFSET_YEAR 6
|
|
#define RTC_OFFSET_COUNT 7
|
|
|
|
#define RTC_DSN_ID 0x580
|
|
#define RTC_BBPU 0x8
|
|
#define RTC_IRQ_STA 0xa
|
|
#define RTC_IRQ_EN 0xc
|
|
#define RTC_AL_MASK 0x10
|
|
#define RTC_TC_SEC 0x12
|
|
#define RTC_AL_SEC 0x20
|
|
#define RTC_AL_MIN 0x22
|
|
#define RTC_AL_HOU 0x24
|
|
#define RTC_AL_DOM 0x26
|
|
#define RTC_AL_DOW 0x28
|
|
#define RTC_AL_MTH 0x2a
|
|
#define RTC_AL_YEA 0x2c
|
|
#define RTC_OSC32CON 0x2e
|
|
#define RTC_POWERKEY1 0x30
|
|
#define RTC_POWERKEY2 0x32
|
|
#define RTC_PDN1 0x34
|
|
#define RTC_PDN2 0x36
|
|
#define RTC_SPAR0 0x38
|
|
#define RTC_SPAR1 0x3a
|
|
#define RTC_PROT 0x3c
|
|
#define RTC_WRTGR 0x42
|
|
#define RTC_CON 0x44
|
|
|
|
#define RTC_TC_SEC_MASK 0x3f
|
|
#define RTC_TC_MIN_MASK 0x3f
|
|
#define RTC_TC_HOU_MASK 0x1f
|
|
#define RTC_TC_DOM_MASK 0x1f
|
|
#define RTC_TC_DOW_MASK 0x7
|
|
#define RTC_TC_MTH_MASK 0xf
|
|
#define RTC_TC_YEA_MASK 0x7f
|
|
|
|
#define RTC_AL_SEC_MASK 0x3f
|
|
#define RTC_AL_MIN_MASK 0x3f
|
|
#define RTC_AL_HOU_MASK 0x1f
|
|
#define RTC_AL_DOM_MASK 0x1f
|
|
#define RTC_AL_DOW_MASK 0x7
|
|
#define RTC_AL_MTH_MASK 0xf
|
|
#define RTC_AL_YEA_MASK 0x7f
|
|
|
|
#define RTC_PWRON_SEC_SHIFT 0x0
|
|
#define RTC_PWRON_MIN_SHIFT 0x0
|
|
#define RTC_PWRON_HOU_SHIFT 0x6
|
|
#define RTC_PWRON_DOM_SHIFT 0xb
|
|
#define RTC_PWRON_MTH_SHIFT 0x0
|
|
#define RTC_PWRON_YEA_SHIFT 0x8
|
|
|
|
#define RTC_PWRON_SEC_MASK (RTC_AL_SEC_MASK << RTC_PWRON_SEC_SHIFT)
|
|
#define RTC_PWRON_MIN_MASK (RTC_AL_MIN_MASK << RTC_PWRON_MIN_SHIFT)
|
|
#define RTC_PWRON_HOU_MASK (RTC_AL_HOU_MASK << RTC_PWRON_HOU_SHIFT)
|
|
#define RTC_PWRON_DOM_MASK (RTC_AL_DOM_MASK << RTC_PWRON_DOM_SHIFT)
|
|
#define RTC_PWRON_MTH_MASK (RTC_AL_MTH_MASK << RTC_PWRON_MTH_SHIFT)
|
|
#define RTC_PWRON_YEA_MASK (RTC_AL_YEA_MASK << RTC_PWRON_YEA_SHIFT)
|
|
/* Common */
|
|
#define RTC_BBPU_KEY 0x4300
|
|
#define RTC_BBPU_CBUSY BIT(6)
|
|
#define RTC_BBPU_RELOAD BIT(5)
|
|
#define RTC_BBPU_PWREN BIT(0)
|
|
/* MT6357,MT6358 */
|
|
#define RTC_BBPU_AUTO BIT(3)
|
|
#define RTC_BBPU_CLR BIT(1)
|
|
/* MT6359, MT6359p*/
|
|
#define RTC_BBPU_AL_STA BIT(7)
|
|
#define RTC_BBPU_RESET_AL BIT(3)
|
|
#define RTC_BBPU_RESET_SPAR BIT(2)
|
|
|
|
|
|
#define RTC_AL_MASK_DOW BIT(4)
|
|
|
|
#define RTC_IRQ_EN_LP BIT(3)
|
|
#define RTC_IRQ_EN_ONESHOT BIT(2)
|
|
#define RTC_IRQ_EN_AL BIT(0)
|
|
#define RTC_IRQ_EN_ONESHOT_AL (RTC_IRQ_EN_ONESHOT | RTC_IRQ_EN_AL)
|
|
|
|
#define RTC_IRQ_STA_LP BIT(3)
|
|
#define RTC_IRQ_STA_AL BIT(0)
|
|
|
|
#define RTC_PDN1_PWRON_TIME BIT(7)
|
|
#define RTC_PDN2_PWRON_LOGO BIT(15)
|
|
#define RTC_PDN2_PWRON_ALARM BIT(4)
|
|
|
|
#define RTC_POFF_ALM_SET _IOW('p', 0x15, struct rtc_time) /* Set alarm time */
|
|
|
|
|
|
static u16 rtc_alarm_reg[RTC_OFFSET_COUNT][3] = {
|
|
{RTC_AL_SEC, RTC_AL_SEC_MASK, 0},
|
|
{RTC_AL_MIN, RTC_AL_MIN_MASK, 0},
|
|
{RTC_AL_HOU, RTC_AL_HOU_MASK, 0},
|
|
{RTC_AL_DOM, RTC_AL_DOM_MASK, 0},
|
|
{RTC_AL_DOW, RTC_AL_DOW_MASK, 0},
|
|
{RTC_AL_MTH, RTC_AL_MTH_MASK, 0},
|
|
{RTC_AL_YEA, RTC_AL_YEA_MASK, 0},
|
|
};
|
|
|
|
static u16 rtc_pwron_reg[RTC_OFFSET_COUNT][3] = {
|
|
{RTC_SPAR0, RTC_PWRON_SEC_MASK, RTC_PWRON_SEC_SHIFT},
|
|
{RTC_SPAR1, RTC_PWRON_MIN_MASK, RTC_PWRON_MIN_SHIFT},
|
|
{RTC_SPAR1, RTC_PWRON_HOU_MASK, RTC_PWRON_HOU_SHIFT},
|
|
{RTC_SPAR1, RTC_PWRON_DOM_MASK, RTC_PWRON_DOM_SHIFT},
|
|
{0, 0, 0},
|
|
{RTC_PDN2, RTC_PWRON_MTH_MASK, RTC_PWRON_MTH_SHIFT},
|
|
{RTC_PDN2, RTC_PWRON_YEA_MASK, RTC_PWRON_YEA_SHIFT},
|
|
};
|
|
|
|
enum rtc_reg_set {
|
|
RTC_REG,
|
|
RTC_MASK,
|
|
RTC_SHIFT
|
|
};
|
|
|
|
enum rtc_irq_sta {
|
|
RTC_NONE,
|
|
RTC_ALSTA,
|
|
RTC_TCSTA,
|
|
RTC_LPSTA,
|
|
};
|
|
|
|
struct mt6358_rtc {
|
|
struct device *dev;
|
|
struct rtc_device *rtc_dev;
|
|
spinlock_t lock;
|
|
struct regmap *regmap;
|
|
int irq;
|
|
u32 addr_base;
|
|
struct work_struct work;
|
|
struct completion comp;
|
|
};
|
|
|
|
struct tag_bootmode {
|
|
u32 size;
|
|
u32 tag;
|
|
u32 bootmode;
|
|
u32 boottype;
|
|
};
|
|
|
|
static struct mt6358_rtc *mt_rtc;
|
|
static struct wakeup_source *mt6358_rtc_suspend_lock;
|
|
|
|
static int rtc_show_time;
|
|
static int rtc_show_alarm = 1;
|
|
static int apply_lpsd_solution;
|
|
/*for KPOC alarm*/
|
|
static bool rtc_pm_notifier_registered;
|
|
static bool kpoc_alarm;
|
|
static unsigned long rtc_pm_status;
|
|
static int alarm1m15s;
|
|
|
|
module_param(rtc_show_time, int, 0644);
|
|
module_param(rtc_show_alarm, int, 0644);
|
|
|
|
|
|
|
|
void __attribute__((weak)) arch_reset(char mode, const char *cmd)
|
|
{
|
|
pr_info("arch_reset is not ready\n");
|
|
}
|
|
|
|
static int rtc_read(unsigned int reg, unsigned int *val)
|
|
{
|
|
return regmap_read(mt_rtc->regmap, mt_rtc->addr_base + reg, val);
|
|
}
|
|
|
|
static int rtc_write(unsigned int reg, unsigned int val)
|
|
{
|
|
return regmap_write(mt_rtc->regmap, mt_rtc->addr_base + reg, val);
|
|
}
|
|
|
|
static int rtc_update_bits(unsigned int reg,
|
|
unsigned int mask, unsigned int val)
|
|
{
|
|
return regmap_update_bits(mt_rtc->regmap,
|
|
mt_rtc->addr_base + reg, mask, val);
|
|
}
|
|
|
|
static int rtc_field_read(unsigned int reg,
|
|
unsigned int mask, unsigned int shift, unsigned int *val)
|
|
{
|
|
int ret;
|
|
unsigned int reg_val = 0;
|
|
|
|
ret = rtc_read(reg, ®_val);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
reg_val &= mask;
|
|
reg_val >>= shift;
|
|
*val = reg_val;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define BULK_WRITE 0
|
|
#define BULK_READ 1
|
|
|
|
static int rtc_bulk_access(int mode, unsigned int reg, void *val,
|
|
size_t val_count)
|
|
{
|
|
if (mode == BULK_WRITE) {
|
|
return regmap_bulk_write(mt_rtc->regmap,
|
|
mt_rtc->addr_base + reg, val, val_count);
|
|
} else if (mode == BULK_READ) {
|
|
return regmap_bulk_read(mt_rtc->regmap,
|
|
mt_rtc->addr_base + reg, val, val_count);
|
|
} else
|
|
return -EPERM;
|
|
}
|
|
|
|
static int rtc_write_trigger(void)
|
|
{
|
|
int ret, bbpu = 0;
|
|
unsigned long long timeout = sched_clock() + 500000000;
|
|
u32 pwrkey1 = 0, pwrkey2 = 0, sec = 0;
|
|
|
|
ret = rtc_write(RTC_WRTGR, 1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
do {
|
|
ret = rtc_read(RTC_BBPU, &bbpu);
|
|
if (ret < 0)
|
|
break;
|
|
if ((bbpu & RTC_BBPU_CBUSY) == 0)
|
|
break;
|
|
else if (sched_clock() > timeout) {
|
|
rtc_read(RTC_BBPU, &bbpu);
|
|
rtc_read(RTC_POWERKEY1, &pwrkey1);
|
|
rtc_read(RTC_POWERKEY2, &pwrkey2);
|
|
rtc_read(RTC_TC_SEC, &sec);
|
|
pr_err("%s, wait cbusy timeout, %x, %x, %x, %d\n",
|
|
__func__, bbpu, pwrkey1, pwrkey2, sec);
|
|
ret = -ETIMEDOUT;
|
|
break;
|
|
}
|
|
} while (1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mtk_rtc_read_time(struct rtc_time *tm)
|
|
{
|
|
u16 data[RTC_OFFSET_COUNT];
|
|
int ret;
|
|
u32 sec = 0;
|
|
unsigned long long timeout = sched_clock() + 500000000;
|
|
|
|
do {
|
|
|
|
ret = rtc_bulk_access(BULK_READ, RTC_TC_SEC,
|
|
data, RTC_OFFSET_COUNT);
|
|
if (ret < 0)
|
|
goto exit;
|
|
tm->tm_sec = data[RTC_OFFSET_SEC] & RTC_TC_SEC_MASK;
|
|
tm->tm_min = data[RTC_OFFSET_MIN] & RTC_TC_MIN_MASK;
|
|
tm->tm_hour = data[RTC_OFFSET_HOUR] & RTC_TC_HOU_MASK;
|
|
tm->tm_mday = data[RTC_OFFSET_DOM] & RTC_TC_DOM_MASK;
|
|
tm->tm_mon = data[RTC_OFFSET_MTH] & RTC_TC_MTH_MASK;
|
|
tm->tm_year = data[RTC_OFFSET_YEAR] & RTC_TC_YEA_MASK;
|
|
|
|
ret = rtc_read(RTC_TC_SEC, &sec);
|
|
if (ret < 0)
|
|
goto exit;
|
|
if (sched_clock() > timeout) {
|
|
pr_notice("%s, time out\n", __func__);
|
|
break;
|
|
}
|
|
} while (sec < tm->tm_sec);
|
|
|
|
return ret;
|
|
|
|
exit:
|
|
pr_err("%s error\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int mtk_rtc_set_alarm(struct rtc_time *tm)
|
|
{
|
|
int ret, i;
|
|
u16 data[RTC_OFFSET_COUNT];
|
|
|
|
data[RTC_OFFSET_SEC] = tm->tm_sec & RTC_AL_SEC_MASK;
|
|
data[RTC_OFFSET_MIN] = tm->tm_min & RTC_AL_MIN_MASK;
|
|
data[RTC_OFFSET_HOUR] = tm->tm_hour & RTC_AL_HOU_MASK;
|
|
data[RTC_OFFSET_DOM] = tm->tm_mday & RTC_AL_DOM_MASK;
|
|
data[RTC_OFFSET_MTH] = tm->tm_mon & RTC_AL_MTH_MASK;
|
|
data[RTC_OFFSET_YEAR] = tm->tm_year & RTC_AL_YEA_MASK;
|
|
|
|
for (i = RTC_OFFSET_SEC; i < RTC_OFFSET_COUNT; i++) {
|
|
if (i == RTC_OFFSET_DOW)
|
|
continue;
|
|
ret = rtc_update_bits(rtc_alarm_reg[i][RTC_REG],
|
|
rtc_alarm_reg[i][RTC_MASK], data[i]);
|
|
if (ret < 0)
|
|
goto exit;
|
|
}
|
|
ret = rtc_write(RTC_AL_MASK, RTC_AL_MASK_DOW); /* mask DOW */
|
|
if (ret < 0)
|
|
goto exit;
|
|
ret = rtc_write_trigger();
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
ret = rtc_update_bits(RTC_IRQ_EN,
|
|
RTC_IRQ_EN_ONESHOT_AL,
|
|
RTC_IRQ_EN_ONESHOT_AL);
|
|
if (ret < 0)
|
|
goto exit;
|
|
ret = rtc_write_trigger();
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
return ret;
|
|
exit:
|
|
pr_err("%s error\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
bool mtk_rtc_is_pwron_alarm(struct rtc_time *nowtm, struct rtc_time *tm)
|
|
{
|
|
u32 pdn1 = 0;
|
|
u32 data[RTC_OFFSET_COUNT] = {0};
|
|
int ret, i;
|
|
|
|
ret = rtc_read(RTC_PDN1, &pdn1);
|
|
if (ret < 0)
|
|
goto exit;
|
|
pr_notice("pdn1 = 0x%4x\n", pdn1);
|
|
|
|
if (pdn1 & RTC_PDN1_PWRON_TIME) { /* power-on time is available */
|
|
|
|
ret = mtk_rtc_read_time(nowtm);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
for (i = RTC_OFFSET_SEC; i < RTC_OFFSET_COUNT; i++) {
|
|
if (i == RTC_OFFSET_DOW)
|
|
continue;
|
|
ret = rtc_field_read(rtc_pwron_reg[i][RTC_REG],
|
|
rtc_pwron_reg[i][RTC_MASK],
|
|
rtc_pwron_reg[i][RTC_SHIFT], &data[i]);
|
|
if (ret < 0)
|
|
goto exit;
|
|
}
|
|
tm->tm_sec = data[RTC_OFFSET_SEC];
|
|
tm->tm_min = data[RTC_OFFSET_MIN];
|
|
tm->tm_hour = data[RTC_OFFSET_HOUR];
|
|
tm->tm_mday = data[RTC_OFFSET_DOM];
|
|
tm->tm_mon = data[RTC_OFFSET_MTH];
|
|
tm->tm_year = data[RTC_OFFSET_YEAR];
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
exit:
|
|
pr_err("%s error\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
static int mtk_rtc_set_pwron_alarm_time(struct rtc_time *tm)
|
|
{
|
|
u16 data[RTC_OFFSET_COUNT];
|
|
int ret, i;
|
|
|
|
pr_err("%s\n", __func__);
|
|
|
|
data[RTC_OFFSET_SEC] =
|
|
((tm->tm_sec << RTC_PWRON_SEC_SHIFT) & RTC_PWRON_SEC_MASK);
|
|
data[RTC_OFFSET_MIN] =
|
|
((tm->tm_min << RTC_PWRON_MIN_SHIFT) & RTC_PWRON_MIN_MASK);
|
|
data[RTC_OFFSET_HOUR] =
|
|
((tm->tm_hour << RTC_PWRON_HOU_SHIFT) & RTC_PWRON_HOU_MASK);
|
|
data[RTC_OFFSET_DOM] =
|
|
((tm->tm_mday << RTC_PWRON_DOM_SHIFT) & RTC_PWRON_DOM_MASK);
|
|
data[RTC_OFFSET_MTH] =
|
|
((tm->tm_mon << RTC_PWRON_MTH_SHIFT) & RTC_PWRON_MTH_MASK);
|
|
data[RTC_OFFSET_YEAR] =
|
|
((tm->tm_year << RTC_PWRON_YEA_SHIFT) & RTC_PWRON_YEA_MASK);
|
|
|
|
for (i = RTC_OFFSET_SEC; i < RTC_OFFSET_COUNT; i++) {
|
|
if (i == RTC_OFFSET_DOW)
|
|
continue;
|
|
ret = rtc_update_bits(rtc_pwron_reg[i][RTC_REG],
|
|
rtc_pwron_reg[i][RTC_MASK], data[i]);
|
|
if (ret < 0)
|
|
goto exit;
|
|
ret = rtc_write_trigger();
|
|
if (ret < 0)
|
|
goto exit;
|
|
}
|
|
return ret;
|
|
exit:
|
|
pr_err("%s error\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int mtk_rtc_set_pwron_alarm(bool enable, struct rtc_time *tm, bool logo)
|
|
{
|
|
u16 pdn1 = 0, pdn2 = 0;
|
|
int ret;
|
|
|
|
ret = mtk_rtc_set_pwron_alarm_time(tm);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
if (enable)
|
|
pdn1 = RTC_PDN1_PWRON_TIME;
|
|
ret = rtc_update_bits(RTC_PDN1, RTC_PDN1_PWRON_TIME, pdn1);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
if (logo)
|
|
pdn2 = RTC_PDN2_PWRON_LOGO;
|
|
ret = rtc_update_bits(RTC_PDN2, RTC_PDN2_PWRON_LOGO, pdn2);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
ret = rtc_write_trigger();
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
return ret;
|
|
|
|
exit:
|
|
pr_err("%s error\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
void rtc_read_pwron_alarm(struct rtc_wkalrm *alm)
|
|
{
|
|
struct rtc_time *tm;
|
|
u32 pdn1 = 0, pdn2 = 0;
|
|
u16 data[RTC_OFFSET_COUNT];
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
if (alm == NULL)
|
|
return;
|
|
|
|
tm = &alm->time;
|
|
|
|
spin_lock_irqsave(&mt_rtc->lock, flags);
|
|
ret = rtc_read(RTC_PDN1, &pdn1);
|
|
if (ret < 0)
|
|
goto exit;
|
|
ret = rtc_read(RTC_PDN2, &pdn2);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
alm->enabled = (pdn1 & RTC_PDN1_PWRON_TIME ?
|
|
(pdn2 & RTC_PDN2_PWRON_LOGO ? 3 : 2) : 0);
|
|
/* return Power-On Alarm bit */
|
|
alm->pending = !!(pdn2 & RTC_PDN2_PWRON_ALARM);
|
|
|
|
ret = rtc_bulk_access(BULK_READ, RTC_AL_SEC, data, RTC_OFFSET_COUNT);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
tm->tm_sec = data[RTC_OFFSET_SEC] & RTC_AL_SEC_MASK;
|
|
tm->tm_min = data[RTC_OFFSET_MIN] & RTC_AL_MIN_MASK;
|
|
tm->tm_hour = data[RTC_OFFSET_HOUR] & RTC_AL_HOU_MASK;
|
|
tm->tm_mday = data[RTC_OFFSET_DOM] & RTC_AL_DOM_MASK;
|
|
tm->tm_mon = data[RTC_OFFSET_MTH] & RTC_AL_MTH_MASK;
|
|
tm->tm_year = data[RTC_OFFSET_YEAR] & RTC_AL_YEA_MASK;
|
|
|
|
spin_unlock_irqrestore(&mt_rtc->lock, flags);
|
|
|
|
tm->tm_year += RTC_MIN_YEAR_OFFSET;
|
|
tm->tm_mon -= 1;
|
|
|
|
if (rtc_show_alarm) {
|
|
pr_notice("power-on = %04d/%02d/%02d %02d:%02d:%02d (%d)(%d)\n",
|
|
tm->tm_year + RTC_BASE_YEAR, tm->tm_mon + 1,
|
|
tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
|
|
alm->enabled, alm->pending);
|
|
}
|
|
return;
|
|
exit:
|
|
pr_err("%s error\n", __func__);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
#define PM_DUMMY 0xFFFF
|
|
|
|
static int rtc_pm_event(struct notifier_block *notifier, unsigned long pm_event,
|
|
void *unused)
|
|
{
|
|
pr_notice("%s = %lu\n", __func__, pm_event);
|
|
|
|
switch (pm_event) {
|
|
case PM_SUSPEND_PREPARE:
|
|
rtc_pm_status = PM_SUSPEND_PREPARE;
|
|
return NOTIFY_DONE;
|
|
case PM_POST_SUSPEND:
|
|
rtc_pm_status = PM_POST_SUSPEND;
|
|
break;
|
|
default:
|
|
rtc_pm_status = PM_DUMMY;
|
|
break;
|
|
}
|
|
|
|
if (kpoc_alarm) {
|
|
pr_notice("%s trigger reboot\n", __func__);
|
|
complete(&mt_rtc->comp);
|
|
kpoc_alarm = false;
|
|
}
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block rtc_pm_notifier_func = {
|
|
.notifier_call = rtc_pm_event,
|
|
.priority = 0,
|
|
};
|
|
#endif /* CONFIG_PM */
|
|
|
|
static void mtk_rtc_work_queue(struct work_struct *work)
|
|
{
|
|
struct mt6358_rtc *rtc = container_of(work, struct mt6358_rtc, work);
|
|
unsigned long ret;
|
|
unsigned int msecs;
|
|
|
|
ret = wait_for_completion_timeout(&rtc->comp, msecs_to_jiffies(30000));
|
|
if (!ret) {
|
|
pr_notice("%s timeout\n", __func__);
|
|
BUG_ON(1);
|
|
} else {
|
|
msecs = jiffies_to_msecs(ret);
|
|
pr_notice("%s timeleft= %d\n", __func__, msecs);
|
|
rtc_mark_kpoc();
|
|
kernel_restart("kpoc");
|
|
}
|
|
}
|
|
|
|
static void mtk_rtc_reboot(void)
|
|
{
|
|
__pm_stay_awake(mt6358_rtc_suspend_lock);
|
|
|
|
init_completion(&mt_rtc->comp);
|
|
schedule_work_on(cpumask_first(cpu_online_mask), &mt_rtc->work);
|
|
|
|
if (!rtc_pm_notifier_registered)
|
|
goto reboot;
|
|
|
|
if (rtc_pm_status != PM_SUSPEND_PREPARE)
|
|
goto reboot;
|
|
|
|
kpoc_alarm = true;
|
|
|
|
pr_notice("%s:wait\n", __func__);
|
|
return;
|
|
|
|
reboot:
|
|
pr_notice("%s:trigger\n", __func__);
|
|
complete(&mt_rtc->comp);
|
|
}
|
|
|
|
#ifndef USER_BUILD_KERNEL
|
|
void mtk_rtc_lp_exception(void)
|
|
{
|
|
u32 bbpu = 0, irqsta = 0, irqen = 0, osc32 = 0;
|
|
u32 pwrkey1 = 0, pwrkey2 = 0, prot = 0, con = 0, sec1 = 0, sec2 = 0;
|
|
|
|
rtc_read(RTC_BBPU, &bbpu);
|
|
rtc_read(RTC_IRQ_STA, &irqsta);
|
|
rtc_read(RTC_IRQ_EN, &irqen);
|
|
rtc_read(RTC_OSC32CON, &osc32);
|
|
rtc_read(RTC_POWERKEY1, &pwrkey1);
|
|
rtc_read(RTC_POWERKEY2, &pwrkey2);
|
|
rtc_read(RTC_PROT, &prot);
|
|
rtc_read(RTC_CON, &con);
|
|
rtc_read(RTC_TC_SEC, &sec1);
|
|
msleep(2000);
|
|
rtc_read(RTC_TC_SEC, &sec2);
|
|
|
|
pr_emerg("!!! 32K WAS STOPPED !!!\n"
|
|
"RTC_BBPU = 0x%x\n"
|
|
"RTC_IRQ_STA = 0x%x\n"
|
|
"RTC_IRQ_EN = 0x%x\n"
|
|
"RTC_OSC32CON = 0x%x\n"
|
|
"RTC_POWERKEY1 = 0x%x\n"
|
|
"RTC_POWERKEY2 = 0x%x\n"
|
|
"RTC_PROT = 0x%x\n"
|
|
"RTC_CON = 0x%x\n"
|
|
"RTC_TC_SEC = %02d\n"
|
|
"RTC_TC_SEC = %02d\n",
|
|
bbpu, irqsta, irqen, osc32, pwrkey1, pwrkey2, prot, con, sec1,
|
|
sec2);
|
|
}
|
|
#endif
|
|
|
|
static int mtk_rtc_is_alarm_irq(void)
|
|
{
|
|
u32 irqsta = 0, bbpu;
|
|
int ret, val;
|
|
|
|
ret = rtc_read(RTC_IRQ_STA, &irqsta); /* read clear */
|
|
if ((ret == 0) && (irqsta & RTC_IRQ_STA_AL)) {
|
|
bbpu = RTC_BBPU_KEY | RTC_BBPU_PWREN;
|
|
rtc_write(RTC_BBPU, bbpu);
|
|
val = rtc_write_trigger();
|
|
if (val < 0)
|
|
pr_notice("%s error\n", __func__);
|
|
return RTC_ALSTA;
|
|
}
|
|
|
|
#ifndef USER_BUILD_KERNEL
|
|
if ((ret == 0) && (irqsta & RTC_IRQ_STA_LP)) {
|
|
mtk_rtc_lp_exception();
|
|
return RTC_LPSTA;
|
|
}
|
|
#endif
|
|
|
|
return RTC_NONE;
|
|
}
|
|
|
|
static void mtk_rtc_update_pwron_alarm_flag(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = rtc_update_bits(RTC_PDN1, RTC_PDN1_PWRON_TIME, 0);
|
|
if (ret < 0)
|
|
goto exit;
|
|
ret = rtc_update_bits(RTC_PDN2,
|
|
RTC_PDN2_PWRON_ALARM,
|
|
RTC_PDN2_PWRON_ALARM);
|
|
if (ret < 0)
|
|
goto exit;
|
|
ret = rtc_write_trigger();
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
return;
|
|
exit:
|
|
pr_err("%s error\n", __func__);
|
|
}
|
|
|
|
static void mtk_rtc_reset_bbpu_alarm_status(void)
|
|
{
|
|
u32 bbpu = RTC_BBPU_KEY | RTC_BBPU_PWREN;
|
|
int ret;
|
|
|
|
|
|
if (apply_lpsd_solution) {
|
|
pr_notice("%s:lpsd\n", __func__);
|
|
return;
|
|
}
|
|
|
|
#if defined(CONFIG_MTK_PMIC_CHIP_MT6358) || \
|
|
defined(CONFIG_MTK_PMIC_CHIP_MT6357)
|
|
bbpu |= RTC_BBPU_CLR;
|
|
#endif
|
|
#if defined(CONFIG_MTK_PMIC_CHIP_MT6359) || \
|
|
defined(CONFIG_MTK_PMIC_CHIP_MT6359P)
|
|
bbpu |= RTC_BBPU_RESET_AL;
|
|
#endif
|
|
|
|
rtc_write(RTC_BBPU, bbpu);
|
|
ret = rtc_write_trigger();
|
|
if (ret < 0)
|
|
pr_err("%s error\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
static irqreturn_t mtk_rtc_irq_handler(int irq, void *data)
|
|
{
|
|
bool pwron_alm = false, pwron_alarm = false;
|
|
struct rtc_time nowtm, tm;
|
|
int status = RTC_NONE;
|
|
unsigned long flags;
|
|
// modify get_boot_mode
|
|
struct device *dev = NULL;
|
|
struct device_node *boot_node = NULL;
|
|
struct tag_bootmode *tag = NULL;
|
|
int boot_mode = 11;//UNKNOWN_BOOT
|
|
|
|
dev = mt_rtc->dev;
|
|
if (dev != NULL){
|
|
boot_node = of_parse_phandle(dev->of_node, "bootmode", 0);
|
|
if (!boot_node){
|
|
pr_notice("%s: failed to get boot mode phandle\n", __func__);
|
|
}
|
|
else {
|
|
tag = (struct tag_bootmode *)of_get_property(boot_node,
|
|
"atag,boot", NULL);
|
|
if (!tag){
|
|
pr_notice("%s: failed to get atag,boot\n", __func__);
|
|
}
|
|
else
|
|
boot_mode = tag->bootmode;
|
|
}
|
|
}
|
|
|
|
spin_lock_irqsave(&mt_rtc->lock, flags);
|
|
|
|
status = mtk_rtc_is_alarm_irq();
|
|
|
|
pr_notice("%s:%d\n", __func__, status);
|
|
|
|
if (status == RTC_NONE) {
|
|
spin_unlock_irqrestore(&mt_rtc->lock, flags);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
if (status == RTC_LPSTA) {
|
|
spin_unlock_irqrestore(&mt_rtc->lock, flags);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
mtk_rtc_reset_bbpu_alarm_status();
|
|
|
|
pwron_alarm = mtk_rtc_is_pwron_alarm(&nowtm, &tm);
|
|
nowtm.tm_year += RTC_MIN_YEAR;
|
|
tm.tm_year += RTC_MIN_YEAR;
|
|
if (pwron_alarm) {
|
|
time64_t now_time, time;
|
|
|
|
now_time =
|
|
mktime(nowtm.tm_year, nowtm.tm_mon, nowtm.tm_mday,
|
|
nowtm.tm_hour, nowtm.tm_min, nowtm.tm_sec);
|
|
|
|
if (now_time == -1) {
|
|
spin_unlock_irqrestore(&mt_rtc->lock, flags);
|
|
goto out;
|
|
}
|
|
|
|
time =
|
|
mktime(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour,
|
|
tm.tm_min, tm.tm_sec);
|
|
|
|
if (time == -1) {
|
|
spin_unlock_irqrestore(&mt_rtc->lock, flags);
|
|
goto out;
|
|
}
|
|
|
|
/* power on */
|
|
if (now_time >= time - 1 && now_time <= time + 4) {
|
|
if (boot_mode == KERNEL_POWER_OFF_CHARGING_BOOT
|
|
|| boot_mode == LOW_POWER_OFF_CHARGING_BOOT) {
|
|
mtk_rtc_reboot();
|
|
spin_unlock_irqrestore(&mt_rtc->lock, flags);
|
|
disable_irq_nosync(mt_rtc->irq);
|
|
goto out;
|
|
} else {
|
|
mtk_rtc_update_pwron_alarm_flag();
|
|
pwron_alm = true;
|
|
}
|
|
} else if (now_time < time) { /* set power-on alarm */
|
|
time -= 1;
|
|
rtc_time64_to_tm(time, &tm);
|
|
tm.tm_year -= RTC_MIN_YEAR_OFFSET;
|
|
tm.tm_mon += 1;
|
|
mtk_rtc_set_alarm(&tm);
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&mt_rtc->lock, flags);
|
|
out:
|
|
if (mt_rtc->rtc_dev != NULL)
|
|
rtc_update_irq(mt_rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
|
|
|
|
if (rtc_show_alarm)
|
|
pr_notice("%s time is up\n", pwron_alm ? "power-on" : "alarm");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int rtc_ops_read_time(struct device *dev, struct rtc_time *tm)
|
|
{
|
|
unsigned long long time;
|
|
unsigned long flags;
|
|
struct mt6358_rtc *rtc = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
spin_lock_irqsave(&rtc->lock, flags);
|
|
ret = mtk_rtc_read_time(tm);
|
|
if (ret < 0)
|
|
goto exit;
|
|
spin_unlock_irqrestore(&rtc->lock, flags);
|
|
|
|
tm->tm_year += RTC_MIN_YEAR_OFFSET;
|
|
tm->tm_mon--;
|
|
time = rtc_tm_to_time64(tm);
|
|
|
|
do_div(time, 86400);
|
|
time += 4;
|
|
tm->tm_wday = do_div(time, 7); /* 1970/01/01 is Thursday */
|
|
|
|
if (rtc_show_time) {
|
|
pr_notice("read tc time = %04d/%02d/%02d (%d) %02d:%02d:%02d\n",
|
|
tm->tm_year + RTC_BASE_YEAR, tm->tm_mon + 1,
|
|
tm->tm_mday, tm->tm_wday, tm->tm_hour,
|
|
tm->tm_min, tm->tm_sec);
|
|
}
|
|
|
|
return ret;
|
|
|
|
exit:
|
|
spin_unlock_irqrestore(&rtc->lock, flags);
|
|
pr_err("%s error\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int rtc_ops_set_time(struct device *dev, struct rtc_time *tm)
|
|
{
|
|
struct mt6358_rtc *rtc = dev_get_drvdata(dev);
|
|
unsigned long flags;
|
|
u16 data[RTC_OFFSET_COUNT];
|
|
int ret;
|
|
|
|
if (tm->tm_year > 195) {
|
|
pr_err("%s: invalid year %04d > 2095\n",
|
|
__func__, tm->tm_year + RTC_BASE_YEAR);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_notice("set tc time = %04d/%02d/%02d %02d:%02d:%02d\n",
|
|
tm->tm_year + RTC_BASE_YEAR, tm->tm_mon + 1, tm->tm_mday,
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
|
|
tm->tm_year -= RTC_MIN_YEAR_OFFSET;
|
|
tm->tm_mon++;
|
|
|
|
data[RTC_OFFSET_SEC] = tm->tm_sec;
|
|
data[RTC_OFFSET_MIN] = tm->tm_min;
|
|
data[RTC_OFFSET_HOUR] = tm->tm_hour;
|
|
data[RTC_OFFSET_DOM] = tm->tm_mday;
|
|
data[RTC_OFFSET_MTH] = tm->tm_mon;
|
|
data[RTC_OFFSET_YEAR] = tm->tm_year;
|
|
|
|
spin_lock_irqsave(&rtc->lock, flags);
|
|
ret = rtc_bulk_access(BULK_WRITE, RTC_TC_SEC, data, RTC_OFFSET_COUNT);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
ret = rtc_write_trigger();
|
|
if (ret < 0)
|
|
goto exit;
|
|
spin_unlock_irqrestore(&rtc->lock, flags);
|
|
|
|
return ret;
|
|
|
|
exit:
|
|
spin_unlock_irqrestore(&rtc->lock, flags);
|
|
pr_err("%s error\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int rtc_ops_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
|
|
{
|
|
unsigned long flags;
|
|
struct rtc_time *tm = &alm->time;
|
|
struct mt6358_rtc *rtc = dev_get_drvdata(dev);
|
|
u32 irqen = 0, pdn2 = 0;
|
|
u16 data[RTC_OFFSET_COUNT];
|
|
int ret;
|
|
|
|
spin_lock_irqsave(&rtc->lock, flags);
|
|
|
|
ret = rtc_read(RTC_IRQ_EN, &irqen);
|
|
if (ret < 0)
|
|
goto exit;
|
|
alm->enabled = !!(irqen & RTC_IRQ_EN_AL);
|
|
|
|
/* return Power-On Alarm bit */
|
|
ret = rtc_read(RTC_PDN2, &pdn2);
|
|
if (ret < 0)
|
|
goto exit;
|
|
alm->pending = !!(pdn2 & RTC_PDN2_PWRON_ALARM);
|
|
|
|
ret = rtc_bulk_access(BULK_READ, RTC_AL_SEC, data, RTC_OFFSET_COUNT);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
tm->tm_sec = data[RTC_OFFSET_SEC] & RTC_AL_SEC_MASK;
|
|
tm->tm_min = data[RTC_OFFSET_MIN] & RTC_AL_MIN_MASK;
|
|
tm->tm_hour = data[RTC_OFFSET_HOUR] & RTC_AL_HOU_MASK;
|
|
tm->tm_mday = data[RTC_OFFSET_DOM] & RTC_AL_DOM_MASK;
|
|
tm->tm_mon = data[RTC_OFFSET_MTH] & RTC_AL_MTH_MASK;
|
|
tm->tm_year = data[RTC_OFFSET_YEAR] & RTC_AL_YEA_MASK;
|
|
|
|
spin_unlock_irqrestore(&rtc->lock, flags);
|
|
|
|
tm->tm_year += RTC_MIN_YEAR_OFFSET;
|
|
tm->tm_mon--;
|
|
|
|
pr_notice("read al time = %04d/%02d/%02d %02d:%02d:%02d (%d)\n",
|
|
tm->tm_year + RTC_BASE_YEAR, tm->tm_mon + 1, tm->tm_mday,
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec, alm->enabled);
|
|
|
|
return ret;
|
|
|
|
exit:
|
|
spin_unlock_irqrestore(&rtc->lock, flags);
|
|
pr_err("%s error\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static int rtc_ops_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
|
|
{
|
|
unsigned long flags;
|
|
struct rtc_time tm = alm->time;
|
|
ktime_t target;
|
|
struct mt6358_rtc *rtc = dev_get_drvdata(dev);
|
|
u32 irqsta;
|
|
int ret = 0;
|
|
|
|
if (tm.tm_year > 195) {
|
|
pr_err("%s: invalid year %04d > 2095\n",
|
|
__func__, tm.tm_year + RTC_BASE_YEAR);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (alm->enabled == 1) {
|
|
/* Add one more second to postpone wake time. */
|
|
target = rtc_tm_to_ktime(tm);
|
|
target = ktime_add_ns(target, NSEC_PER_SEC);
|
|
tm = rtc_ktime_to_tm(target);
|
|
} else if (alm->enabled == 5) {
|
|
/* Power on system 1 minute earlier */
|
|
alarm1m15s = 1;
|
|
}
|
|
|
|
tm.tm_year -= RTC_MIN_YEAR_OFFSET;
|
|
tm.tm_mon++;
|
|
|
|
pr_notice("set al time = %04d/%02d/%02d %02d:%02d:%02d (%d)\n",
|
|
tm.tm_year + RTC_MIN_YEAR, tm.tm_mon, tm.tm_mday,
|
|
tm.tm_hour, tm.tm_min, tm.tm_sec, alm->enabled);
|
|
|
|
spin_lock_irqsave(&rtc->lock, flags);
|
|
if (alm->enabled == 2) { /* enable power-on alarm */
|
|
ret = mtk_rtc_set_pwron_alarm(true, &tm, false);
|
|
} else if (alm->enabled == 3 || alm->enabled == 5) {
|
|
/* enable power-on alarm with logo */
|
|
ret = mtk_rtc_set_pwron_alarm(true, &tm, true);
|
|
} else if (alm->enabled == 4) { /* disable power-on alarm */
|
|
ret = mtk_rtc_set_pwron_alarm(false, &tm, false);
|
|
alarm1m15s = 0;
|
|
}
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
/* disable alarm and clear Power-On Alarm bit */
|
|
ret = rtc_update_bits(RTC_IRQ_EN, RTC_IRQ_EN_AL, 0);
|
|
if (ret < 0)
|
|
goto exit;
|
|
ret = rtc_update_bits(RTC_PDN2, RTC_PDN2_PWRON_ALARM, 0);
|
|
if (ret < 0)
|
|
goto exit;
|
|
ret = rtc_write_trigger();
|
|
if (ret < 0)
|
|
goto exit;
|
|
ret = rtc_read(RTC_IRQ_STA, &irqsta); /* read clear */
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
if (alm->enabled)
|
|
ret = mtk_rtc_set_alarm(&tm);
|
|
spin_unlock_irqrestore(&rtc->lock, flags);
|
|
|
|
return ret;
|
|
exit:
|
|
spin_unlock_irqrestore(&rtc->lock, flags);
|
|
pr_err("%s error\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
int mtk_set_power_on(struct device *dev, struct rtc_wkalrm *alm)
|
|
{
|
|
int err = 0;
|
|
struct rtc_time tm;
|
|
time64_t now, scheduled;
|
|
|
|
err = rtc_valid_tm(&alm->time);
|
|
if (err != 0)
|
|
return err;
|
|
scheduled = rtc_tm_to_time64(&alm->time);
|
|
|
|
err = rtc_ops_read_time(dev, &tm);
|
|
if (err != 0)
|
|
return err;
|
|
now = rtc_tm_to_time64(&tm);
|
|
|
|
if (scheduled <= now)
|
|
alm->enabled = 4;
|
|
else
|
|
alm->enabled = 3;
|
|
|
|
rtc_ops_set_alarm(dev, alm);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mtk_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
|
|
{
|
|
void __user *uarg = (void __user *) arg;
|
|
int err = 0;
|
|
struct rtc_wkalrm alm;
|
|
|
|
switch (cmd) {
|
|
case RTC_POFF_ALM_SET:
|
|
if (copy_from_user(&alm.time, uarg, sizeof(alm.time)))
|
|
return -EFAULT;
|
|
err = mtk_set_power_on(dev, &alm);
|
|
break;
|
|
default:
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static const struct rtc_class_ops rtc_ops = {
|
|
.ioctl = mtk_rtc_ioctl,
|
|
.read_time = rtc_ops_read_time,
|
|
.set_time = rtc_ops_set_time,
|
|
.read_alarm = rtc_ops_read_alarm,
|
|
.set_alarm = rtc_ops_set_alarm,
|
|
};
|
|
|
|
static void mtk_rtc_set_lp_irq(void)
|
|
{
|
|
unsigned int irqen = 0;
|
|
int ret;
|
|
|
|
#ifndef USER_BUILD_KERNEL
|
|
irqen = RTC_IRQ_EN_LP;
|
|
#endif
|
|
ret = rtc_update_bits(RTC_IRQ_EN, RTC_IRQ_EN_LP, irqen);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
ret = rtc_write_trigger();
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
return;
|
|
exit:
|
|
pr_err("%s error\n", __func__);
|
|
}
|
|
|
|
static int mtk_rtc_pdrv_probe(struct platform_device *pdev)
|
|
{
|
|
#ifndef IPIMB
|
|
struct mt6358_chip *mt6358_chip = dev_get_drvdata(pdev->dev.parent);
|
|
#endif
|
|
struct mt6358_rtc *rtc;
|
|
unsigned long flags;
|
|
int ret;
|
|
#if IS_ENABLED(CONFIG_MTK_RTC)
|
|
struct platform_device *plt_dev;
|
|
#endif
|
|
|
|
rtc = devm_kzalloc(&pdev->dev, sizeof(struct mt6358_rtc), GFP_KERNEL);
|
|
if (!rtc)
|
|
return -ENOMEM;
|
|
|
|
rtc->irq = platform_get_irq(pdev, 0);
|
|
if (rtc->irq <= 0)
|
|
return -EINVAL;
|
|
pr_notice("%s: rtc->irq = %d(%d)\n", __func__, rtc->irq,
|
|
platform_get_irq_byname(pdev, "rtc"));
|
|
|
|
#ifndef IPIMB
|
|
rtc->regmap = mt6358_chip->regmap;
|
|
#else
|
|
rtc->regmap = dev_get_regmap(pdev->dev.parent->parent, NULL);
|
|
#endif
|
|
if (!rtc->regmap) {
|
|
pr_err("%s: get regmap failed\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
rtc->dev = &pdev->dev;
|
|
spin_lock_init(&rtc->lock);
|
|
|
|
mt_rtc = rtc;
|
|
platform_set_drvdata(pdev, rtc);
|
|
|
|
if (of_property_read_u32(pdev->dev.of_node, "base", &rtc->addr_base))
|
|
rtc->addr_base = RTC_DSN_ID;
|
|
pr_notice("%s: rtc->addr_base =0x%x\n", __func__, rtc->addr_base);
|
|
|
|
spin_lock_irqsave(&rtc->lock, flags);
|
|
mtk_rtc_set_lp_irq();
|
|
spin_unlock_irqrestore(&rtc->lock, flags);
|
|
|
|
mt6358_rtc_suspend_lock =
|
|
wakeup_source_register(NULL, "mt6358-rtc suspend wakelock");
|
|
|
|
#ifdef CONFIG_PM
|
|
if (register_pm_notifier(&rtc_pm_notifier_func))
|
|
pr_notice("rtc pm failed\n");
|
|
else
|
|
rtc_pm_notifier_registered = true;
|
|
#endif /* CONFIG_PM */
|
|
|
|
INIT_WORK(&rtc->work, mtk_rtc_work_queue);
|
|
|
|
ret = request_threaded_irq(rtc->irq, NULL,
|
|
mtk_rtc_irq_handler,
|
|
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
|
|
"mt6358-rtc", rtc);
|
|
if (ret) {
|
|
dev_dbg(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
|
|
rtc->irq, ret);
|
|
goto out_dispose_irq;
|
|
}
|
|
|
|
device_init_wakeup(&pdev->dev, 1);
|
|
|
|
/* register rtc device (/dev/rtc0) */
|
|
rtc->rtc_dev = rtc_device_register(RTC_NAME,
|
|
&pdev->dev, &rtc_ops, THIS_MODULE);
|
|
if (IS_ERR(rtc->rtc_dev)) {
|
|
dev_dbg(&pdev->dev, "register rtc device failed\n");
|
|
ret = PTR_ERR(rtc->rtc_dev);
|
|
goto out_free_irq;
|
|
}
|
|
|
|
if (of_property_read_bool(pdev->dev.of_node, "apply-lpsd-solution")) {
|
|
apply_lpsd_solution = 1;
|
|
pr_notice("%s: apply_lpsd_solution\n", __func__);
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_MTK_RTC)
|
|
plt_dev = platform_device_register_data(&pdev->dev, "mtk_rtc_dbg",
|
|
-1, NULL, 0);
|
|
if (IS_ERR(plt_dev))
|
|
dev_notice(&pdev->dev,
|
|
"%s: failed to register mtk_rtc_dbg\n", __func__);
|
|
#endif
|
|
return 0;
|
|
out_free_irq:
|
|
free_irq(rtc->irq, rtc->rtc_dev);
|
|
out_dispose_irq:
|
|
irq_dispose_mapping(rtc->irq);
|
|
return ret;
|
|
}
|
|
|
|
static int mtk_rtc_pdrv_remove(struct platform_device *pdev)
|
|
{
|
|
struct mt6358_rtc *rtc = platform_get_drvdata(pdev);
|
|
|
|
rtc_device_unregister(rtc->rtc_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mtk_rtc_pdrv_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct rtc_time rtc_time_now;
|
|
struct rtc_time rtc_time_alarm;
|
|
ktime_t ktime_now;
|
|
ktime_t ktime_alarm;
|
|
bool is_pwron_alarm;
|
|
|
|
if (alarm1m15s == 1) {
|
|
is_pwron_alarm = mtk_rtc_is_pwron_alarm(&rtc_time_now,
|
|
&rtc_time_alarm);
|
|
if (is_pwron_alarm) {
|
|
rtc_time_now.tm_year += RTC_MIN_YEAR_OFFSET;
|
|
rtc_time_now.tm_mon--;
|
|
rtc_time_alarm.tm_year += RTC_MIN_YEAR_OFFSET;
|
|
rtc_time_alarm.tm_mon--;
|
|
pr_notice("now = %04d/%02d/%02d %02d:%02d:%02d\n",
|
|
rtc_time_now.tm_year + 1900,
|
|
rtc_time_now.tm_mon + 1,
|
|
rtc_time_now.tm_mday,
|
|
rtc_time_now.tm_hour,
|
|
rtc_time_now.tm_min,
|
|
rtc_time_now.tm_sec);
|
|
pr_notice("alarm = %04d/%02d/%02d %02d:%02d:%02d\n",
|
|
rtc_time_alarm.tm_year + 1900,
|
|
rtc_time_alarm.tm_mon + 1,
|
|
rtc_time_alarm.tm_mday,
|
|
rtc_time_alarm.tm_hour,
|
|
rtc_time_alarm.tm_min,
|
|
rtc_time_alarm.tm_sec);
|
|
ktime_now = rtc_tm_to_ktime(rtc_time_now);
|
|
ktime_alarm = rtc_tm_to_ktime(rtc_time_alarm);
|
|
if (ktime_after(ktime_alarm, ktime_now)) {
|
|
/* alarm has not happened */
|
|
ktime_alarm = ktime_sub_ms(ktime_alarm,
|
|
MSEC_PER_SEC * 60);
|
|
if (ktime_after(ktime_alarm, ktime_now))
|
|
pr_notice("Alarm will happen after 1 minute\n");
|
|
else {
|
|
ktime_alarm = ktime_add_ms(ktime_now,
|
|
MSEC_PER_SEC * 15);
|
|
pr_notice("Alarm will happen in 15 seconds\n");
|
|
}
|
|
rtc_time_alarm = rtc_ktime_to_tm(ktime_alarm);
|
|
pr_notice("new alarm = %04d/%02d/%02d %02d:%02d:%02d\n",
|
|
rtc_time_alarm.tm_year + 1900,
|
|
rtc_time_alarm.tm_mon + 1,
|
|
rtc_time_alarm.tm_mday,
|
|
rtc_time_alarm.tm_hour,
|
|
rtc_time_alarm.tm_min,
|
|
rtc_time_alarm.tm_sec);
|
|
rtc_time_alarm.tm_year -= RTC_MIN_YEAR_OFFSET;
|
|
rtc_time_alarm.tm_mon++;
|
|
mtk_rtc_set_pwron_alarm_time(&rtc_time_alarm);
|
|
mtk_rtc_set_alarm(&rtc_time_alarm);
|
|
} else
|
|
pr_notice("Alarm has happened before\n");
|
|
} else
|
|
pr_notice("No power-off alarm is set\n");
|
|
}
|
|
|
|
}
|
|
|
|
static const struct of_device_id mt6358_rtc_of_match[] = {
|
|
{ .compatible = "mediatek,mt6357-rtc", },
|
|
{ .compatible = "mediatek,mt6358-rtc", },
|
|
{ .compatible = "mediatek,mt6359-rtc", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mt6358_rtc_of_match);
|
|
|
|
static struct platform_driver mtk_rtc_pdrv = {
|
|
.probe = mtk_rtc_pdrv_probe,
|
|
.remove = mtk_rtc_pdrv_remove,
|
|
.shutdown = mtk_rtc_pdrv_shutdown,
|
|
.driver = {
|
|
.name = RTC_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = mt6358_rtc_of_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(mtk_rtc_pdrv);
|
|
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("Wilma Wu <wilma.wu@mediatek.com>");
|
|
MODULE_DESCRIPTION("RTC Driver for MediaTek MT6358 PMIC");
|