18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Driver for MediaTek SoC based RTC
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2017 Sean Wang <sean.wang@mediatek.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/clk.h>
98c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/of_address.h>
128c2ecf20Sopenharmony_ci#include <linux/of_device.h>
138c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
148c2ecf20Sopenharmony_ci#include <linux/rtc.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define MTK_RTC_DEV KBUILD_MODNAME
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#define MTK_RTC_PWRCHK1		0x4
198c2ecf20Sopenharmony_ci#define	RTC_PWRCHK1_MAGIC	0xc6
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define MTK_RTC_PWRCHK2		0x8
228c2ecf20Sopenharmony_ci#define	RTC_PWRCHK2_MAGIC	0x9a
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define MTK_RTC_KEY		0xc
258c2ecf20Sopenharmony_ci#define	RTC_KEY_MAGIC		0x59
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define MTK_RTC_PROT1		0x10
288c2ecf20Sopenharmony_ci#define	RTC_PROT1_MAGIC		0xa3
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define MTK_RTC_PROT2		0x14
318c2ecf20Sopenharmony_ci#define	RTC_PROT2_MAGIC		0x57
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define MTK_RTC_PROT3		0x18
348c2ecf20Sopenharmony_ci#define	RTC_PROT3_MAGIC		0x67
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#define MTK_RTC_PROT4		0x1c
378c2ecf20Sopenharmony_ci#define	RTC_PROT4_MAGIC		0xd2
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci#define MTK_RTC_CTL		0x20
408c2ecf20Sopenharmony_ci#define	RTC_RC_STOP		BIT(0)
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define MTK_RTC_DEBNCE		0x2c
438c2ecf20Sopenharmony_ci#define	RTC_DEBNCE_MASK		GENMASK(2, 0)
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci#define MTK_RTC_INT		0x30
468c2ecf20Sopenharmony_ci#define RTC_INT_AL_STA		BIT(4)
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci/*
498c2ecf20Sopenharmony_ci * Ranges from 0x40 to 0x78 provide RTC time setup for year, month,
508c2ecf20Sopenharmony_ci * day of month, day of week, hour, minute and second.
518c2ecf20Sopenharmony_ci */
528c2ecf20Sopenharmony_ci#define MTK_RTC_TREG(_t, _f)	(0x40 + (0x4 * (_f)) + ((_t) * 0x20))
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci#define MTK_RTC_AL_CTL		0x7c
558c2ecf20Sopenharmony_ci#define	RTC_AL_EN		BIT(0)
568c2ecf20Sopenharmony_ci#define	RTC_AL_ALL		GENMASK(7, 0)
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci/*
598c2ecf20Sopenharmony_ci * The offset is used in the translation for the year between in struct
608c2ecf20Sopenharmony_ci * rtc_time and in hardware register MTK_RTC_TREG(x,MTK_YEA)
618c2ecf20Sopenharmony_ci */
628c2ecf20Sopenharmony_ci#define MTK_RTC_TM_YR_OFFSET	100
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci/*
658c2ecf20Sopenharmony_ci * The lowest value for the valid tm_year. RTC hardware would take incorrectly
668c2ecf20Sopenharmony_ci * tm_year 100 as not a leap year and thus it is also required being excluded
678c2ecf20Sopenharmony_ci * from the valid options.
688c2ecf20Sopenharmony_ci */
698c2ecf20Sopenharmony_ci#define MTK_RTC_TM_YR_L		(MTK_RTC_TM_YR_OFFSET + 1)
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci/*
728c2ecf20Sopenharmony_ci * The most year the RTC can hold is 99 and the next to 99 in year register
738c2ecf20Sopenharmony_ci * would be wraparound to 0, for MT7622.
748c2ecf20Sopenharmony_ci */
758c2ecf20Sopenharmony_ci#define MTK_RTC_HW_YR_LIMIT	99
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci/* The highest value for the valid tm_year */
788c2ecf20Sopenharmony_ci#define MTK_RTC_TM_YR_H		(MTK_RTC_TM_YR_OFFSET + MTK_RTC_HW_YR_LIMIT)
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci/* Simple macro helps to check whether the hardware supports the tm_year */
818c2ecf20Sopenharmony_ci#define MTK_RTC_TM_YR_VALID(_y)	((_y) >= MTK_RTC_TM_YR_L && \
828c2ecf20Sopenharmony_ci				 (_y) <= MTK_RTC_TM_YR_H)
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci/* Types of the function the RTC provides are time counter and alarm. */
858c2ecf20Sopenharmony_cienum {
868c2ecf20Sopenharmony_ci	MTK_TC,
878c2ecf20Sopenharmony_ci	MTK_AL,
888c2ecf20Sopenharmony_ci};
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci/* Indexes are used for the pointer to relevant registers in MTK_RTC_TREG */
918c2ecf20Sopenharmony_cienum {
928c2ecf20Sopenharmony_ci	MTK_YEA,
938c2ecf20Sopenharmony_ci	MTK_MON,
948c2ecf20Sopenharmony_ci	MTK_DOM,
958c2ecf20Sopenharmony_ci	MTK_DOW,
968c2ecf20Sopenharmony_ci	MTK_HOU,
978c2ecf20Sopenharmony_ci	MTK_MIN,
988c2ecf20Sopenharmony_ci	MTK_SEC
998c2ecf20Sopenharmony_ci};
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistruct mtk_rtc {
1028c2ecf20Sopenharmony_ci	struct rtc_device *rtc;
1038c2ecf20Sopenharmony_ci	void __iomem *base;
1048c2ecf20Sopenharmony_ci	int irq;
1058c2ecf20Sopenharmony_ci	struct clk *clk;
1068c2ecf20Sopenharmony_ci};
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic void mtk_w32(struct mtk_rtc *rtc, u32 reg, u32 val)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	writel_relaxed(val, rtc->base + reg);
1118c2ecf20Sopenharmony_ci}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic u32 mtk_r32(struct mtk_rtc *rtc, u32 reg)
1148c2ecf20Sopenharmony_ci{
1158c2ecf20Sopenharmony_ci	return readl_relaxed(rtc->base + reg);
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_cistatic void mtk_rmw(struct mtk_rtc *rtc, u32 reg, u32 mask, u32 set)
1198c2ecf20Sopenharmony_ci{
1208c2ecf20Sopenharmony_ci	u32 val;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	val = mtk_r32(rtc, reg);
1238c2ecf20Sopenharmony_ci	val &= ~mask;
1248c2ecf20Sopenharmony_ci	val |= set;
1258c2ecf20Sopenharmony_ci	mtk_w32(rtc, reg, val);
1268c2ecf20Sopenharmony_ci}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic void mtk_set(struct mtk_rtc *rtc, u32 reg, u32 val)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	mtk_rmw(rtc, reg, 0, val);
1318c2ecf20Sopenharmony_ci}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cistatic void mtk_clr(struct mtk_rtc *rtc, u32 reg, u32 val)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	mtk_rmw(rtc, reg, val, 0);
1368c2ecf20Sopenharmony_ci}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_cistatic void mtk_rtc_hw_init(struct mtk_rtc *hw)
1398c2ecf20Sopenharmony_ci{
1408c2ecf20Sopenharmony_ci	/* The setup of the init sequence is for allowing RTC got to work */
1418c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_PWRCHK1, RTC_PWRCHK1_MAGIC);
1428c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_PWRCHK2, RTC_PWRCHK2_MAGIC);
1438c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_KEY, RTC_KEY_MAGIC);
1448c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_PROT1, RTC_PROT1_MAGIC);
1458c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_PROT2, RTC_PROT2_MAGIC);
1468c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_PROT3, RTC_PROT3_MAGIC);
1478c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_PROT4, RTC_PROT4_MAGIC);
1488c2ecf20Sopenharmony_ci	mtk_rmw(hw, MTK_RTC_DEBNCE, RTC_DEBNCE_MASK, 0);
1498c2ecf20Sopenharmony_ci	mtk_clr(hw, MTK_RTC_CTL, RTC_RC_STOP);
1508c2ecf20Sopenharmony_ci}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_cistatic void mtk_rtc_get_alarm_or_time(struct mtk_rtc *hw, struct rtc_time *tm,
1538c2ecf20Sopenharmony_ci				      int time_alarm)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	u32 year, mon, mday, wday, hour, min, sec;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	/*
1588c2ecf20Sopenharmony_ci	 * Read again until the field of the second is not changed which
1598c2ecf20Sopenharmony_ci	 * ensures all fields in the consistent state. Note that MTK_SEC must
1608c2ecf20Sopenharmony_ci	 * be read first. In this way, it guarantees the others remain not
1618c2ecf20Sopenharmony_ci	 * changed when the results for two MTK_SEC consecutive reads are same.
1628c2ecf20Sopenharmony_ci	 */
1638c2ecf20Sopenharmony_ci	do {
1648c2ecf20Sopenharmony_ci		sec = mtk_r32(hw, MTK_RTC_TREG(time_alarm, MTK_SEC));
1658c2ecf20Sopenharmony_ci		min = mtk_r32(hw, MTK_RTC_TREG(time_alarm, MTK_MIN));
1668c2ecf20Sopenharmony_ci		hour = mtk_r32(hw, MTK_RTC_TREG(time_alarm, MTK_HOU));
1678c2ecf20Sopenharmony_ci		wday = mtk_r32(hw, MTK_RTC_TREG(time_alarm, MTK_DOW));
1688c2ecf20Sopenharmony_ci		mday = mtk_r32(hw, MTK_RTC_TREG(time_alarm, MTK_DOM));
1698c2ecf20Sopenharmony_ci		mon = mtk_r32(hw, MTK_RTC_TREG(time_alarm, MTK_MON));
1708c2ecf20Sopenharmony_ci		year = mtk_r32(hw, MTK_RTC_TREG(time_alarm, MTK_YEA));
1718c2ecf20Sopenharmony_ci	} while (sec != mtk_r32(hw, MTK_RTC_TREG(time_alarm, MTK_SEC)));
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	tm->tm_sec  = sec;
1748c2ecf20Sopenharmony_ci	tm->tm_min  = min;
1758c2ecf20Sopenharmony_ci	tm->tm_hour = hour;
1768c2ecf20Sopenharmony_ci	tm->tm_wday = wday;
1778c2ecf20Sopenharmony_ci	tm->tm_mday = mday;
1788c2ecf20Sopenharmony_ci	tm->tm_mon  = mon - 1;
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	/* Rebase to the absolute year which userspace queries */
1818c2ecf20Sopenharmony_ci	tm->tm_year = year + MTK_RTC_TM_YR_OFFSET;
1828c2ecf20Sopenharmony_ci}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_cistatic void mtk_rtc_set_alarm_or_time(struct mtk_rtc *hw, struct rtc_time *tm,
1858c2ecf20Sopenharmony_ci				      int time_alarm)
1868c2ecf20Sopenharmony_ci{
1878c2ecf20Sopenharmony_ci	u32 year;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	/* Rebase to the relative year which RTC hardware requires */
1908c2ecf20Sopenharmony_ci	year = tm->tm_year - MTK_RTC_TM_YR_OFFSET;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_TREG(time_alarm, MTK_YEA), year);
1938c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_TREG(time_alarm, MTK_MON), tm->tm_mon + 1);
1948c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_TREG(time_alarm, MTK_DOW), tm->tm_wday);
1958c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_TREG(time_alarm, MTK_DOM), tm->tm_mday);
1968c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_TREG(time_alarm, MTK_HOU), tm->tm_hour);
1978c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_TREG(time_alarm, MTK_MIN), tm->tm_min);
1988c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_TREG(time_alarm, MTK_SEC), tm->tm_sec);
1998c2ecf20Sopenharmony_ci}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_cistatic irqreturn_t mtk_rtc_alarmirq(int irq, void *id)
2028c2ecf20Sopenharmony_ci{
2038c2ecf20Sopenharmony_ci	struct mtk_rtc *hw = (struct mtk_rtc *)id;
2048c2ecf20Sopenharmony_ci	u32 irq_sta;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	irq_sta = mtk_r32(hw, MTK_RTC_INT);
2078c2ecf20Sopenharmony_ci	if (irq_sta & RTC_INT_AL_STA) {
2088c2ecf20Sopenharmony_ci		/* Stop alarm also implicitly disables the alarm interrupt */
2098c2ecf20Sopenharmony_ci		mtk_w32(hw, MTK_RTC_AL_CTL, 0);
2108c2ecf20Sopenharmony_ci		rtc_update_irq(hw->rtc, 1, RTC_IRQF | RTC_AF);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci		/* Ack alarm interrupt status */
2138c2ecf20Sopenharmony_ci		mtk_w32(hw, MTK_RTC_INT, RTC_INT_AL_STA);
2148c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
2158c2ecf20Sopenharmony_ci	}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	return IRQ_NONE;
2188c2ecf20Sopenharmony_ci}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_cistatic int mtk_rtc_gettime(struct device *dev, struct rtc_time *tm)
2218c2ecf20Sopenharmony_ci{
2228c2ecf20Sopenharmony_ci	struct mtk_rtc *hw = dev_get_drvdata(dev);
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	mtk_rtc_get_alarm_or_time(hw, tm, MTK_TC);
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	return 0;
2278c2ecf20Sopenharmony_ci}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_cistatic int mtk_rtc_settime(struct device *dev, struct rtc_time *tm)
2308c2ecf20Sopenharmony_ci{
2318c2ecf20Sopenharmony_ci	struct mtk_rtc *hw = dev_get_drvdata(dev);
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	if (!MTK_RTC_TM_YR_VALID(tm->tm_year))
2348c2ecf20Sopenharmony_ci		return -EINVAL;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	/* Stop time counter before setting a new one*/
2378c2ecf20Sopenharmony_ci	mtk_set(hw, MTK_RTC_CTL, RTC_RC_STOP);
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	mtk_rtc_set_alarm_or_time(hw, tm, MTK_TC);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	/* Restart the time counter */
2428c2ecf20Sopenharmony_ci	mtk_clr(hw, MTK_RTC_CTL, RTC_RC_STOP);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	return 0;
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_cistatic int mtk_rtc_getalarm(struct device *dev, struct rtc_wkalrm *wkalrm)
2488c2ecf20Sopenharmony_ci{
2498c2ecf20Sopenharmony_ci	struct mtk_rtc *hw = dev_get_drvdata(dev);
2508c2ecf20Sopenharmony_ci	struct rtc_time *alrm_tm = &wkalrm->time;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	mtk_rtc_get_alarm_or_time(hw, alrm_tm, MTK_AL);
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	wkalrm->enabled = !!(mtk_r32(hw, MTK_RTC_AL_CTL) & RTC_AL_EN);
2558c2ecf20Sopenharmony_ci	wkalrm->pending = !!(mtk_r32(hw, MTK_RTC_INT) & RTC_INT_AL_STA);
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	return 0;
2588c2ecf20Sopenharmony_ci}
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_cistatic int mtk_rtc_setalarm(struct device *dev, struct rtc_wkalrm *wkalrm)
2618c2ecf20Sopenharmony_ci{
2628c2ecf20Sopenharmony_ci	struct mtk_rtc *hw = dev_get_drvdata(dev);
2638c2ecf20Sopenharmony_ci	struct rtc_time *alrm_tm = &wkalrm->time;
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	if (!MTK_RTC_TM_YR_VALID(alrm_tm->tm_year))
2668c2ecf20Sopenharmony_ci		return -EINVAL;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	/*
2698c2ecf20Sopenharmony_ci	 * Stop the alarm also implicitly including disables interrupt before
2708c2ecf20Sopenharmony_ci	 * setting a new one.
2718c2ecf20Sopenharmony_ci	 */
2728c2ecf20Sopenharmony_ci	mtk_clr(hw, MTK_RTC_AL_CTL, RTC_AL_EN);
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	/*
2758c2ecf20Sopenharmony_ci	 * Avoid contention between mtk_rtc_setalarm and IRQ handler so that
2768c2ecf20Sopenharmony_ci	 * disabling the interrupt and awaiting for pending IRQ handler to
2778c2ecf20Sopenharmony_ci	 * complete.
2788c2ecf20Sopenharmony_ci	 */
2798c2ecf20Sopenharmony_ci	synchronize_irq(hw->irq);
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	mtk_rtc_set_alarm_or_time(hw, alrm_tm, MTK_AL);
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	/* Restart the alarm with the new setup */
2848c2ecf20Sopenharmony_ci	mtk_w32(hw, MTK_RTC_AL_CTL, RTC_AL_ALL);
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	return 0;
2878c2ecf20Sopenharmony_ci}
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_cistatic const struct rtc_class_ops mtk_rtc_ops = {
2908c2ecf20Sopenharmony_ci	.read_time		= mtk_rtc_gettime,
2918c2ecf20Sopenharmony_ci	.set_time		= mtk_rtc_settime,
2928c2ecf20Sopenharmony_ci	.read_alarm		= mtk_rtc_getalarm,
2938c2ecf20Sopenharmony_ci	.set_alarm		= mtk_rtc_setalarm,
2948c2ecf20Sopenharmony_ci};
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_cistatic const struct of_device_id mtk_rtc_match[] = {
2978c2ecf20Sopenharmony_ci	{ .compatible = "mediatek,mt7622-rtc" },
2988c2ecf20Sopenharmony_ci	{ .compatible = "mediatek,soc-rtc" },
2998c2ecf20Sopenharmony_ci	{},
3008c2ecf20Sopenharmony_ci};
3018c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, mtk_rtc_match);
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_cistatic int mtk_rtc_probe(struct platform_device *pdev)
3048c2ecf20Sopenharmony_ci{
3058c2ecf20Sopenharmony_ci	struct mtk_rtc *hw;
3068c2ecf20Sopenharmony_ci	int ret;
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	hw = devm_kzalloc(&pdev->dev, sizeof(*hw), GFP_KERNEL);
3098c2ecf20Sopenharmony_ci	if (!hw)
3108c2ecf20Sopenharmony_ci		return -ENOMEM;
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, hw);
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci	hw->base = devm_platform_ioremap_resource(pdev, 0);
3158c2ecf20Sopenharmony_ci	if (IS_ERR(hw->base))
3168c2ecf20Sopenharmony_ci		return PTR_ERR(hw->base);
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	hw->clk = devm_clk_get(&pdev->dev, "rtc");
3198c2ecf20Sopenharmony_ci	if (IS_ERR(hw->clk)) {
3208c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "No clock\n");
3218c2ecf20Sopenharmony_ci		return PTR_ERR(hw->clk);
3228c2ecf20Sopenharmony_ci	}
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(hw->clk);
3258c2ecf20Sopenharmony_ci	if (ret)
3268c2ecf20Sopenharmony_ci		return ret;
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	hw->irq = platform_get_irq(pdev, 0);
3298c2ecf20Sopenharmony_ci	if (hw->irq < 0) {
3308c2ecf20Sopenharmony_ci		ret = hw->irq;
3318c2ecf20Sopenharmony_ci		goto err;
3328c2ecf20Sopenharmony_ci	}
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	ret = devm_request_irq(&pdev->dev, hw->irq, mtk_rtc_alarmirq,
3358c2ecf20Sopenharmony_ci			       0, dev_name(&pdev->dev), hw);
3368c2ecf20Sopenharmony_ci	if (ret) {
3378c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Can't request IRQ\n");
3388c2ecf20Sopenharmony_ci		goto err;
3398c2ecf20Sopenharmony_ci	}
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	mtk_rtc_hw_init(hw);
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	device_init_wakeup(&pdev->dev, true);
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	hw->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
3468c2ecf20Sopenharmony_ci					   &mtk_rtc_ops, THIS_MODULE);
3478c2ecf20Sopenharmony_ci	if (IS_ERR(hw->rtc)) {
3488c2ecf20Sopenharmony_ci		ret = PTR_ERR(hw->rtc);
3498c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Unable to register device\n");
3508c2ecf20Sopenharmony_ci		goto err;
3518c2ecf20Sopenharmony_ci	}
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	return 0;
3548c2ecf20Sopenharmony_cierr:
3558c2ecf20Sopenharmony_ci	clk_disable_unprepare(hw->clk);
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	return ret;
3588c2ecf20Sopenharmony_ci}
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_cistatic int mtk_rtc_remove(struct platform_device *pdev)
3618c2ecf20Sopenharmony_ci{
3628c2ecf20Sopenharmony_ci	struct mtk_rtc *hw = platform_get_drvdata(pdev);
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci	clk_disable_unprepare(hw->clk);
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci	return 0;
3678c2ecf20Sopenharmony_ci}
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
3708c2ecf20Sopenharmony_cistatic int mtk_rtc_suspend(struct device *dev)
3718c2ecf20Sopenharmony_ci{
3728c2ecf20Sopenharmony_ci	struct mtk_rtc *hw = dev_get_drvdata(dev);
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_ci	if (device_may_wakeup(dev))
3758c2ecf20Sopenharmony_ci		enable_irq_wake(hw->irq);
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ci	return 0;
3788c2ecf20Sopenharmony_ci}
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_cistatic int mtk_rtc_resume(struct device *dev)
3818c2ecf20Sopenharmony_ci{
3828c2ecf20Sopenharmony_ci	struct mtk_rtc *hw = dev_get_drvdata(dev);
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci	if (device_may_wakeup(dev))
3858c2ecf20Sopenharmony_ci		disable_irq_wake(hw->irq);
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	return 0;
3888c2ecf20Sopenharmony_ci}
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(mtk_rtc_pm_ops, mtk_rtc_suspend, mtk_rtc_resume);
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci#define MTK_RTC_PM_OPS (&mtk_rtc_pm_ops)
3938c2ecf20Sopenharmony_ci#else	/* CONFIG_PM */
3948c2ecf20Sopenharmony_ci#define MTK_RTC_PM_OPS NULL
3958c2ecf20Sopenharmony_ci#endif	/* CONFIG_PM */
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_cistatic struct platform_driver mtk_rtc_driver = {
3988c2ecf20Sopenharmony_ci	.probe	= mtk_rtc_probe,
3998c2ecf20Sopenharmony_ci	.remove	= mtk_rtc_remove,
4008c2ecf20Sopenharmony_ci	.driver = {
4018c2ecf20Sopenharmony_ci		.name = MTK_RTC_DEV,
4028c2ecf20Sopenharmony_ci		.of_match_table = mtk_rtc_match,
4038c2ecf20Sopenharmony_ci		.pm = MTK_RTC_PM_OPS,
4048c2ecf20Sopenharmony_ci	},
4058c2ecf20Sopenharmony_ci};
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_cimodule_platform_driver(mtk_rtc_driver);
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MediaTek SoC based RTC Driver");
4108c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>");
4118c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
412