You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1116 lines
26 KiB
1116 lines
26 KiB
/* |
|
* Copyright (C) 2018 MediaTek, Inc. |
|
* Author: Wilma Wu <wilma.wu@mediatek.com> |
|
* |
|
* This software is licensed under the terms of the GNU General Public |
|
* License version 2, as published by the Free Software Foundation, and |
|
* may be copied, distributed, and modified under those terms. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
*/ |
|
|
|
#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 "../misc/mediatek/include/mt-plat/mtk_boot_common.h" |
|
#include "../misc/mediatek/include/mt-plat/mtk_reboot.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) |
|
|
|
#define RTC_BBPU_KEY 0x4300 |
|
#define RTC_BBPU_CBUSY BIT(6) |
|
#define RTC_BBPU_RELOAD BIT(5) |
|
#define RTC_BBPU_AUTO BIT(3) |
|
#define RTC_BBPU_CLR BIT(1) |
|
#define RTC_BBPU_PWREN BIT(0) |
|
#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) |
|
|
|
|
|
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; |
|
}; |
|
static struct mt6358_rtc *mt_rtc; |
|
|
|
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; |
|
|
|
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; |
|
|
|
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; |
|
|
|
} 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); |
|
kernel_restart("kpoc"); |
|
} |
|
} |
|
|
|
static void mtk_rtc_reboot(void) |
|
{ |
|
pm_stay_awake(mt_rtc->dev); |
|
|
|
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); |
|
mdelay(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; |
|
int ret; |
|
|
|
|
|
if (apply_lpsd_solution) { |
|
pr_notice("%s:lpsd\n", __func__); |
|
return; |
|
} |
|
|
|
bbpu = RTC_BBPU_KEY | RTC_BBPU_PWREN | RTC_BBPU_RESET_AL; |
|
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; |
|
|
|
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); |
|
time = |
|
mktime(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, |
|
tm.tm_min, tm.tm_sec); |
|
|
|
/* power on */ |
|
if (now_time >= time - 1 && now_time <= time + 4) { |
|
if (get_boot_mode() == KERNEL_POWER_OFF_CHARGING_BOOT |
|
|| get_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); |
|
} |
|
|
|
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) { /* 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); |
|
} |
|
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; |
|
} |
|
|
|
static const struct rtc_class_ops rtc_ops = { |
|
.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; |
|
|
|
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); |
|
|
|
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__); |
|
} |
|
|
|
#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); |
|
|
|
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 const struct of_device_id mt6358_rtc_of_match[] = { |
|
{ .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, |
|
.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");
|
|
|