blob: 8352b6e27089221c5ccbaa2a6cda1f1c16fc44e8 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Real Time Clock driver for Marvell 88PM80x PMIC
*
* Copyright (c) 2012 Marvell International Ltd.
* Wenzeng Chen<wzch@marvell.com>
* Qiao Zhou <zhouqiao@marvell.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/regmap.h>
#include <linux/mfd/core.h>
#include <linux/mfd/88pm80x.h>
#include <linux/mfd/pm802.h>
#include <linux/mfd/pm813.h>
#include <linux/rtc.h>
#include <linux/reboot.h>
#include <linux/sysfs.h>
#define PM800_RTC_COUNTER1 (0xD1)
#define PM800_RTC_COUNTER2 (0xD2)
#define PM800_RTC_COUNTER3 (0xD3)
#define PM800_RTC_COUNTER4 (0xD4)
#define PM800_RTC_EXPIRE1_1 (0xD5)
#define PM800_RTC_EXPIRE1_2 (0xD6)
#define PM800_RTC_EXPIRE1_3 (0xD7)
#define PM800_RTC_EXPIRE1_4 (0xD8)
#define PM800_RTC_TRIM1 (0xD9)
#define PM800_RTC_TRIM2 (0xDA)
#define PM800_RTC_TRIM3 (0xDB)
#define PM800_RTC_TRIM4 (0xDC)
#define PM800_RTC_EXPIRE2_1 (0xDD)
#define PM800_RTC_EXPIRE2_2 (0xDE)
#define PM800_RTC_EXPIRE2_3 (0xDF)
#define PM800_RTC_EXPIRE2_4 (0xE0)
#define PM800_POWER_DOWN_LOG1 (0xE5)
#define PM800_POWER_DOWN_LOG2 (0xE6)
#define PM800_CHG_WAKEUP (1 << 1)
/*for save RTC offset*/
#define PM800_USER_DATA1 (0xEA)
#define PM800_USER_DATA2 (0xEB)
#define PM800_USER_DATA3 (0xEC)
#define PM800_USER_DATA4 (0xED)
#define PM800_USER_DATA5 (0xEE)
#define PM800_USER_DATA6 (0xEF)
struct pm80x_rtc_info {
struct pm80x_chip *chip;
struct regmap *map;
struct rtc_device *rtc_dev;
struct device *dev;
struct delayed_work calib_work;
int irq;
int vrtc;
int (*sync) (s64 ticks);
#ifdef CONFIG_RTC_DRV_SA1100
struct delayed_work sa1100_sync_work;
#endif
};
#ifdef CONFIG_RTC_DRV_SA1100
extern int sync_time_to_soc(s64 ticks);
#endif
static void deferred_restart(struct work_struct *dummy)
{
kernel_restart("charger-alarm");
BUG();
}
static DECLARE_WORK(restart_work, deferred_restart);
#ifdef CONFIG_RTC_DRV_SA1100
static int pm80x_rtc_read_time(struct device *dev, struct rtc_time *tm);
static void sa1100_sync_fn(struct work_struct *work)
{
struct pm80x_rtc_info *info =
container_of(work, struct pm80x_rtc_info, sa1100_sync_work.work);
s64 ticks;
struct rtc_time tm;
int ret;
ret = pm80x_rtc_read_time(info->dev, &tm);
if (ret < 0) {
dev_err(info->dev, "Failed to read time.\n");
return;
}
ticks = rtc_tm_to_time64(&tm);
if (info->sync)
info->sync(ticks);
}
#endif
static irqreturn_t rtc_update_handler(int irq, void *data)
{
struct pm80x_rtc_info *info = (struct pm80x_rtc_info *)data;
int mask;
/*
* write 1 to clear PM800_ALARM (bit 5 of 0xd0),
* which indicates the alarm event has happened;
*
* write 1 to PM800_ALARM_WAKEUP (bit 4 of 0xd0),
* which is must for "expired-alarm powers up system",
* it triggers falling edge of RTC_ALARM_WU signal,
* while the "power down" triggers the rising edge of RTC_ALARM_WU signal;
*
* write 0 to PM800_ALARM1_EN (bit 0 0f 0xd0) to disable alarm;
*/
mask = PM800_ALARM | PM800_ALARM_WAKEUP;
regmap_update_bits(info->map, PM800_RTC_CONTROL, mask | PM800_ALARM1_EN, mask);
rtc_update_irq(info->rtc_dev, 1, RTC_AF);
if (strstr(saved_command_line, "androidboot.mode=charger")) {
/*
* this bit indicates the system powers up because of "reboot" command
* in uboot, so it boots up the generic Android instead of entering
* power off charge feature
*/
regmap_update_bits(info->map, PM800_USER_DATA6, (1 << 1), (1 << 1));
schedule_work(&restart_work);
}
return IRQ_HANDLED;
}
static int pm80x_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
struct pm80x_rtc_info *info = dev_get_drvdata(dev);
if (enabled)
regmap_update_bits(info->map, PM800_RTC_CONTROL,
PM800_ALARM1_EN, PM800_ALARM1_EN);
else
regmap_update_bits(info->map, PM800_RTC_CONTROL,
PM800_ALARM1_EN, 0);
return 0;
}
static int pm80x_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct pm80x_rtc_info *info = dev_get_drvdata(dev);
unsigned char buf[5];
s64 ticks, data, base;
u32 reg;
buf[4] = 0x0;
if (info->chip->type == CHIP_PM801 || info->chip->type == CHIP_PM800)
reg = PM800_USER_DATA1;
else
reg = PM802_RTC_EXPIRE2_1;
if (CHIP_PM802 == info->chip->type) {
if (CHIP_PM802_ID_B1 == info->chip->chip_id ||
CHIP_PM802_ID_B0 == info->chip->chip_id) {
/* F1[7:4] */
regmap_raw_read(info->map, PM802_RTC_MISC14, &buf[4], 1);
buf[4] = (buf[4] >> 4) & 0xf;
} else {
/* CD[7:0] */
regmap_raw_read(info->map, PM802S_RTC_SPARED, &buf[4], 1);
}
} else if (CHIP_PM813 == info->chip->type) {
if (CHIP_PM813_ID == info->chip->chip_id) {
/* F6[7:0] */
regmap_raw_read(info->map, PM813_RTC_MISC19, &buf[4], 1);
} else {
/* CD[7:0] */
regmap_raw_read(info->map, PM813S_RTC_SPARED, &buf[4], 1);
}
}
regmap_raw_read(info->map, reg, buf, 4);
/* convert bits[3:0] to signed number */
base = (((s64)buf[4]) << 60) >> 28;
base |= (((u32)buf[3]) << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
dev_dbg(info->dev, "%x-%x-%x-%x-%x\n", buf[0], buf[1], buf[2], buf[3], buf[4]);
/* load 32-bit read-only counter */
regmap_raw_read(info->map, PM800_RTC_COUNTER1, buf, 4);
data = (((u32)buf[3]) << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
ticks = base + data;
dev_dbg(info->dev, "get base:0x%llx, RO count:0x%llx, ticks:0x%llx\n",
base, data, ticks);
rtc_time64_to_tm(ticks, tm);
return 0;
}
static int pm80x_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
struct pm80x_rtc_info *info = dev_get_drvdata(dev);
unsigned char buf[5];
s64 ticks, data, base;
u32 reg;
buf[4] = 0x0;
if (info->chip->type == CHIP_PM801 || info->chip->type == CHIP_PM800)
reg = PM800_USER_DATA1;
else
reg = PM802_RTC_EXPIRE2_1;
if (CHIP_PM801 == info->chip->type || CHIP_PM800 == info->chip->type) {
if ((tm->tm_year < 100) || (tm->tm_year > 205)) {
dev_err(info->dev,
"PM801: Set time %d out of range. Please set time between 2000 to 2105.\n",
1900 + tm->tm_year);
return -EINVAL;
}
} else {
if ((tm->tm_year < 70) || (tm->tm_year > 300)) {
dev_err(info->dev,
"PM8XX: Set time %d out of range. Please set time between 1970 to 2200.\n",
1900 + tm->tm_year);
return -EINVAL;
}
}
ticks = rtc_tm_to_time64(tm);
/* load 32-bit read-only counter */
regmap_raw_read(info->map, PM800_RTC_COUNTER1, buf, 4);
data = (((u32)buf[3]) << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
base = ticks - data;
dev_dbg(info->dev, "set base:0x%llx, RO count:0x%llx, ticks:0x%llx\n",
base, data, ticks);
buf[0] = base & 0xFF;
buf[1] = (base >> 8) & 0xFF;
buf[2] = (base >> 16) & 0xFF;
buf[3] = (base >> 24) & 0xFF;
regmap_raw_write(info->map, reg, buf, 4);
buf[4] = (base >> 32) & 0xFF;
if (CHIP_PM802 == info->chip->type) {
if (CHIP_PM802_ID_B1 == info->chip->chip_id ||
CHIP_PM802_ID_B0 == info->chip->chip_id) {
regmap_raw_read(info->map, PM802_RTC_MISC14, buf, 1);
buf[0] &= 0x0f;
buf[0] |= ((buf[4] & 0xf) << 4);
/* F1[7:4] */
regmap_raw_write(info->map, PM802_RTC_MISC14, &buf[0], 1);
} else {
/* CD[7:0] */
regmap_raw_write(info->map, PM802S_RTC_SPARED, &buf[4], 1);
}
} else if (CHIP_PM813 == info->chip->type) {
if (CHIP_PM813_ID == info->chip->chip_id) {
/* F6[7:0] */
regmap_raw_write(info->map, PM813_RTC_MISC19, &buf[4], 1);
} else {
/* CD[7:0] */
regmap_raw_write(info->map, PM813S_RTC_SPARED, &buf[4], 1);
}
}
if (info->sync)
info->sync(ticks);
return 0;
}
static int pm80x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct pm80x_rtc_info *info = dev_get_drvdata(dev);
unsigned char buf[5];
s64 ticks, base, data;
int ret;
u32 reg;
buf[4] = 0x0;
if (info->chip->type == CHIP_PM801 || info->chip->type == CHIP_PM800)
reg = PM800_USER_DATA1;
else
reg = PM802_RTC_EXPIRE2_1;
if (CHIP_PM802 == info->chip->type) {
if (CHIP_PM802_ID_B1 == info->chip->chip_id ||
CHIP_PM802_ID_B0 == info->chip->chip_id) {
/* F1[7:4] */
regmap_raw_read(info->map, PM802_RTC_MISC14, &buf[4], 1);
buf[4] = (buf[4] >> 4) & 0xf;
} else {
/* CD[7:0] */
regmap_raw_read(info->map, PM802S_RTC_SPARED, &buf[4], 1);
}
} else if (CHIP_PM813 == info->chip->type) {
if (CHIP_PM813_ID == info->chip->chip_id) {
/* F6[7:0] */
regmap_raw_read(info->map, PM813_RTC_MISC19, &buf[4], 1);
} else {
/* CD[7:0] */
regmap_raw_read(info->map, PM813S_RTC_SPARED, &buf[4], 1);
}
}
regmap_raw_read(info->map, reg, buf, 4);
base = (((s64)buf[4]) << 60) >> 28;
base |= (((u32)buf[3]) << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
dev_dbg(info->dev, "%x-%x-%x-%x-%x\n", buf[0], buf[1], buf[2], buf[3], buf[4]);
regmap_raw_read(info->map, PM800_RTC_EXPIRE1_1, buf, 4);
data = (((u32)buf[3]) << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
ticks = base + data;
dev_dbg(info->dev, "get base:0x%llx, RO count:0x%llx, ticks:0x%llx\n",
base, data, ticks);
rtc_time64_to_tm(ticks, &alrm->time);
regmap_read(info->map, PM800_RTC_CONTROL, &ret);
alrm->enabled = (ret & PM800_ALARM1_EN) ? 1 : 0;
alrm->pending = (ret & (PM800_ALARM | PM800_ALARM_WAKEUP)) ? 1 : 0;
return 0;
}
static int pm80x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct pm80x_rtc_info *info = dev_get_drvdata(dev);
s64 ticks, base, data;
unsigned char buf[5];
int mask;
u32 reg;
buf[4] = 0x0;
if (info->chip->type == CHIP_PM801 || info->chip->type == CHIP_PM800)
reg = PM800_USER_DATA1;
else
reg = PM802_RTC_EXPIRE2_1;
regmap_update_bits(info->map, PM800_RTC_CONTROL, PM800_ALARM1_EN, 0);
if (CHIP_PM802 == info->chip->type) {
if (CHIP_PM802_ID_B1 == info->chip->chip_id ||
CHIP_PM802_ID_B0 == info->chip->chip_id) {
/* F1[7:4] */
regmap_raw_read(info->map, PM802_RTC_MISC14, &buf[4], 1);
buf[4] = (buf[4] >> 4) & 0xf;
} else {
/* CD[7:0] */
regmap_raw_read(info->map, PM802S_RTC_SPARED, &buf[4], 1);
}
} else if (CHIP_PM813 == info->chip->type) {
if (CHIP_PM813_ID == info->chip->chip_id) {
/* F6[7:0] */
regmap_raw_read(info->map, PM813_RTC_MISC19, &buf[4], 1);
} else {
/* CD[7:0] */
regmap_raw_read(info->map, PM813S_RTC_SPARED, &buf[4], 1);
}
}
regmap_raw_read(info->map, reg, buf, 4);
base = (((s64)buf[4]) << 60) >> 28;
base |= (((u32)buf[3]) << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
dev_dbg(info->dev, "%x-%x-%x-%x-%x\n", buf[0], buf[1], buf[2], buf[3], buf[4]);
/* get new ticks for alarm */
ticks = rtc_tm_to_time64(&alrm->time);
dev_dbg(info->dev, "%s, alarm time: %llu\n", __func__, ticks);
data = ticks - base;
buf[0] = data & 0xff;
buf[1] = (data >> 8) & 0xff;
buf[2] = (data >> 16) & 0xff;
buf[3] = (data >> 24) & 0xff;
regmap_raw_write(info->map, PM800_RTC_EXPIRE1_1, buf, 4);
if (alrm->enabled) {
mask = PM800_ALARM | PM800_ALARM_WAKEUP | PM800_ALARM1_EN;
regmap_update_bits(info->map, PM800_RTC_CONTROL, mask, mask);
} else {
mask = PM800_ALARM | PM800_ALARM_WAKEUP | PM800_ALARM1_EN;
regmap_update_bits(info->map, PM800_RTC_CONTROL, mask,
PM800_ALARM | PM800_ALARM_WAKEUP);
}
return 0;
}
static const struct rtc_class_ops pm80x_rtc_ops = {
.read_time = pm80x_rtc_read_time,
.set_time = pm80x_rtc_set_time,
.read_alarm = pm80x_rtc_read_alarm,
.set_alarm = pm80x_rtc_set_alarm,
.alarm_irq_enable = pm80x_rtc_alarm_irq_enable,
};
#ifdef CONFIG_PM_SLEEP
static int pm80x_rtc_suspend(struct device *dev)
{
return pm80x_dev_suspend(dev);
}
static int pm80x_rtc_resume(struct device *dev)
{
return pm80x_dev_resume(dev);
}
#endif
static SIMPLE_DEV_PM_OPS(pm80x_rtc_pm_ops, pm80x_rtc_suspend, pm80x_rtc_resume);
static struct delayed_work sync_work;
static unsigned int print_tstamp_delay = 30;
static void sync_timestamp(struct work_struct *work)
{
struct timespec64 ts;
struct rtc_time tm;
ktime_get_real_ts64(&ts);
rtc_time64_to_tm(ts.tv_sec - sys_tz.tz_minuteswest * 60, &tm);
pr_info("Timestamp is: %02d-%02d %02d:%02d:%02d.%03lu UTC\n",
tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
tm.tm_sec, ts.tv_nsec);
/* print timestamp every 30 minutes */
schedule_delayed_work(&sync_work, print_tstamp_delay*60*HZ);
}
#ifdef CONFIG_SYSFS
static ssize_t tstamp_show_delay(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", print_tstamp_delay);
}
static ssize_t tstamp_store_delay(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
char *end;
unsigned long new = simple_strtoul(buf, &end, 0);
if (end == buf)
return -EINVAL;
print_tstamp_delay = new;
cancel_delayed_work(&sync_work);
schedule_delayed_work(&sync_work, print_tstamp_delay*60*HZ);
return size;
}
static DEVICE_ATTR(tstamp_delay, 0664, tstamp_show_delay, tstamp_store_delay);
static int add_tstamp_delay(struct device *dev)
{
return device_create_file(dev, &dev_attr_tstamp_delay);
}
static void remove_tstamp_delay(struct device *dev)
{
device_remove_file(dev, &dev_attr_tstamp_delay);
}
#else
#define add_tstamp_delay(dev) 0
#define remove_tstamp_delay(dev) do {} while (0)
#endif /* CONFIG_SYSFS */
static int pm80x_rtc_probe(struct platform_device *pdev)
{
struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
struct pm80x_rtc_pdata *pdata = NULL;
struct pm80x_rtc_info *info;
struct rtc_time tm;
int ret;
INIT_DEFERRABLE_WORK(&sync_work, sync_timestamp);
schedule_delayed_work(&sync_work, 1*60*HZ);
add_tstamp_delay(&pdev->dev);
pdata = pdev->dev.platform_data;
if (IS_ENABLED(CONFIG_OF)) {
if (!pdata) {
pdata = devm_kzalloc(&pdev->dev,
sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
}
} else if (!pdata) {
return -EINVAL;
}
info =
devm_kzalloc(&pdev->dev, sizeof(struct pm80x_rtc_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->irq = platform_get_irq(pdev, 0);
if (info->irq < 0) {
dev_err(&pdev->dev, "No IRQ resource!\n");
ret = -EINVAL;
goto out;
}
info->chip = chip;
info->map = chip->regmap;
if (!info->map) {
dev_err(&pdev->dev, "no regmap!\n");
ret = -EINVAL;
goto out;
}
info->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, info);
ret = pm80x_rtc_read_time(&pdev->dev, &tm);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to read initial time.\n");
goto out;
}
if (CHIP_PM801 == info->chip->type || CHIP_PM800 == info->chip->type) {
if ((tm.tm_year < 100) || (tm.tm_year > 205)) {
tm.tm_year = 100;
tm.tm_mon = 0;
tm.tm_mday = 1;
tm.tm_hour = 0;
tm.tm_min = 0;
tm.tm_sec = 0;
ret = pm80x_rtc_set_time(&pdev->dev, &tm);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to set initial time.\n");
goto out;
}
}
} else {
if ((tm.tm_year < 70) || (tm.tm_year > 1157)) {
tm.tm_year = 70;
tm.tm_mon = 0;
tm.tm_mday = 1;
tm.tm_hour = 0;
tm.tm_min = 0;
tm.tm_sec = 0;
ret = pm80x_rtc_set_time(&pdev->dev, &tm);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to set initial time.\n");
goto out;
}
}
}
info->sync = NULL; /*initialize*/
#ifdef CONFIG_RTC_DRV_SA1100
info->sync = sync_time_to_soc;
INIT_DELAYED_WORK(&info->sa1100_sync_work, sa1100_sync_fn);
schedule_delayed_work(&info->sa1100_sync_work, 2 * HZ);
#endif
dev_info(&pdev->dev, "%d-%d-%d-->%d: %d: %d\n",
tm.tm_year, tm.tm_mon, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
info->rtc_dev = devm_rtc_device_register(&pdev->dev, "88pm80x-rtc",
&pm80x_rtc_ops, THIS_MODULE);
if (IS_ERR(info->rtc_dev)) {
ret = PTR_ERR(info->rtc_dev);
dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
goto out;
}
/*
* enable internal XO instead of internal 3.25MHz clock since it can
* free running in PMIC power-down state.
*/
regmap_update_bits(info->map, PM800_RTC_CONTROL, PM800_RTC1_USE_XO,
PM800_RTC1_USE_XO);
/* remeber whether this power up is caused by PMIC RTC or not. */
info->rtc_dev->dev.platform_data = &pdata->rtc_wakeup;
ret = pm80x_request_irq(chip, info->irq, rtc_update_handler,
IRQF_ONESHOT | IRQF_NO_SUSPEND , "rtc", info);
if (ret < 0) {
dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
info->irq, ret);
goto out;
}
device_init_wakeup(&pdev->dev, 1);
return 0;
out:
return ret;
}
static int pm80x_rtc_remove(struct platform_device *pdev)
{
struct pm80x_rtc_info *info = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
pm80x_free_irq(info->chip, info->irq, info);
remove_tstamp_delay(&pdev->dev);
return 0;
}
static void pm80x_rtc_shutdown(struct platform_device *pdev)
{
struct timespec64 now;
struct rtc_time tm;
struct pm80x_rtc_info *info = platform_get_drvdata(pdev);
/* sync timekeeping time to pmic rtc */
ktime_get_real_ts64(&now);
if (now.tv_nsec < (NSEC_PER_SEC >> 1))
rtc_time64_to_tm(now.tv_sec, &tm);
else
rtc_time64_to_tm(now.tv_sec + 1, &tm);
pm80x_rtc_set_time(info->dev, &tm);
pm80x_free_irq(info->chip, info->irq, info);
}
static struct platform_driver pm80x_rtc_driver = {
.driver = {
.name = "88pm80x-rtc",
.owner = THIS_MODULE,
.pm = &pm80x_rtc_pm_ops,
},
.probe = pm80x_rtc_probe,
.remove = pm80x_rtc_remove,
.shutdown = pm80x_rtc_shutdown,
};
module_platform_driver(pm80x_rtc_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Marvell 88PM80x RTC driver");
MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
MODULE_ALIAS("platform:88pm80x-rtc");