ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/marvell/linux/drivers/rtc/rtc-88pm80x.c b/marvell/linux/drivers/rtc/rtc-88pm80x.c
new file mode 100644
index 0000000..8352b6e
--- /dev/null
+++ b/marvell/linux/drivers/rtc/rtc-88pm80x.c
@@ -0,0 +1,628 @@
+// 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");