18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/* drivers/rtc/rtc-goldfish.c
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2007 Google, Inc.
58c2ecf20Sopenharmony_ci * Copyright (C) 2017 Imagination Technologies Ltd.
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/io.h>
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/of.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include <linux/rtc.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#define TIMER_TIME_LOW		0x00	/* get low bits of current time  */
158c2ecf20Sopenharmony_ci					/*   and update TIMER_TIME_HIGH  */
168c2ecf20Sopenharmony_ci#define TIMER_TIME_HIGH	0x04	/* get high bits of time at last */
178c2ecf20Sopenharmony_ci					/*   TIMER_TIME_LOW read         */
188c2ecf20Sopenharmony_ci#define TIMER_ALARM_LOW	0x08	/* set low bits of alarm and     */
198c2ecf20Sopenharmony_ci					/*   activate it                 */
208c2ecf20Sopenharmony_ci#define TIMER_ALARM_HIGH	0x0c	/* set high bits of next alarm   */
218c2ecf20Sopenharmony_ci#define TIMER_IRQ_ENABLED	0x10
228c2ecf20Sopenharmony_ci#define TIMER_CLEAR_ALARM	0x14
238c2ecf20Sopenharmony_ci#define TIMER_ALARM_STATUS	0x18
248c2ecf20Sopenharmony_ci#define TIMER_CLEAR_INTERRUPT	0x1c
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistruct goldfish_rtc {
278c2ecf20Sopenharmony_ci	void __iomem *base;
288c2ecf20Sopenharmony_ci	int irq;
298c2ecf20Sopenharmony_ci	struct rtc_device *rtc;
308c2ecf20Sopenharmony_ci};
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistatic int goldfish_rtc_read_alarm(struct device *dev,
338c2ecf20Sopenharmony_ci				   struct rtc_wkalrm *alrm)
348c2ecf20Sopenharmony_ci{
358c2ecf20Sopenharmony_ci	u64 rtc_alarm;
368c2ecf20Sopenharmony_ci	u64 rtc_alarm_low;
378c2ecf20Sopenharmony_ci	u64 rtc_alarm_high;
388c2ecf20Sopenharmony_ci	void __iomem *base;
398c2ecf20Sopenharmony_ci	struct goldfish_rtc *rtcdrv;
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	rtcdrv = dev_get_drvdata(dev);
428c2ecf20Sopenharmony_ci	base = rtcdrv->base;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	rtc_alarm_low = readl(base + TIMER_ALARM_LOW);
458c2ecf20Sopenharmony_ci	rtc_alarm_high = readl(base + TIMER_ALARM_HIGH);
468c2ecf20Sopenharmony_ci	rtc_alarm = (rtc_alarm_high << 32) | rtc_alarm_low;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	do_div(rtc_alarm, NSEC_PER_SEC);
498c2ecf20Sopenharmony_ci	memset(alrm, 0, sizeof(struct rtc_wkalrm));
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	rtc_time64_to_tm(rtc_alarm, &alrm->time);
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	if (readl(base + TIMER_ALARM_STATUS))
548c2ecf20Sopenharmony_ci		alrm->enabled = 1;
558c2ecf20Sopenharmony_ci	else
568c2ecf20Sopenharmony_ci		alrm->enabled = 0;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	return 0;
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic int goldfish_rtc_set_alarm(struct device *dev,
628c2ecf20Sopenharmony_ci				  struct rtc_wkalrm *alrm)
638c2ecf20Sopenharmony_ci{
648c2ecf20Sopenharmony_ci	struct goldfish_rtc *rtcdrv;
658c2ecf20Sopenharmony_ci	u64 rtc_alarm64;
668c2ecf20Sopenharmony_ci	u64 rtc_status_reg;
678c2ecf20Sopenharmony_ci	void __iomem *base;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	rtcdrv = dev_get_drvdata(dev);
708c2ecf20Sopenharmony_ci	base = rtcdrv->base;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	if (alrm->enabled) {
738c2ecf20Sopenharmony_ci		rtc_alarm64 = rtc_tm_to_time64(&alrm->time) * NSEC_PER_SEC;
748c2ecf20Sopenharmony_ci		writel((rtc_alarm64 >> 32), base + TIMER_ALARM_HIGH);
758c2ecf20Sopenharmony_ci		writel(rtc_alarm64, base + TIMER_ALARM_LOW);
768c2ecf20Sopenharmony_ci		writel(1, base + TIMER_IRQ_ENABLED);
778c2ecf20Sopenharmony_ci	} else {
788c2ecf20Sopenharmony_ci		/*
798c2ecf20Sopenharmony_ci		 * if this function was called with enabled=0
808c2ecf20Sopenharmony_ci		 * then it could mean that the application is
818c2ecf20Sopenharmony_ci		 * trying to cancel an ongoing alarm
828c2ecf20Sopenharmony_ci		 */
838c2ecf20Sopenharmony_ci		rtc_status_reg = readl(base + TIMER_ALARM_STATUS);
848c2ecf20Sopenharmony_ci		if (rtc_status_reg)
858c2ecf20Sopenharmony_ci			writel(1, base + TIMER_CLEAR_ALARM);
868c2ecf20Sopenharmony_ci	}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	return 0;
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic int goldfish_rtc_alarm_irq_enable(struct device *dev,
928c2ecf20Sopenharmony_ci					 unsigned int enabled)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	void __iomem *base;
958c2ecf20Sopenharmony_ci	struct goldfish_rtc *rtcdrv;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	rtcdrv = dev_get_drvdata(dev);
988c2ecf20Sopenharmony_ci	base = rtcdrv->base;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	if (enabled)
1018c2ecf20Sopenharmony_ci		writel(1, base + TIMER_IRQ_ENABLED);
1028c2ecf20Sopenharmony_ci	else
1038c2ecf20Sopenharmony_ci		writel(0, base + TIMER_IRQ_ENABLED);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	return 0;
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic irqreturn_t goldfish_rtc_interrupt(int irq, void *dev_id)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	struct goldfish_rtc *rtcdrv = dev_id;
1118c2ecf20Sopenharmony_ci	void __iomem *base = rtcdrv->base;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	writel(1, base + TIMER_CLEAR_INTERRUPT);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	rtc_update_irq(rtcdrv->rtc, 1, RTC_IRQF | RTC_AF);
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1188c2ecf20Sopenharmony_ci}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic int goldfish_rtc_read_time(struct device *dev, struct rtc_time *tm)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	struct goldfish_rtc *rtcdrv;
1238c2ecf20Sopenharmony_ci	void __iomem *base;
1248c2ecf20Sopenharmony_ci	u64 time_high;
1258c2ecf20Sopenharmony_ci	u64 time_low;
1268c2ecf20Sopenharmony_ci	u64 time;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	rtcdrv = dev_get_drvdata(dev);
1298c2ecf20Sopenharmony_ci	base = rtcdrv->base;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	time_low = readl(base + TIMER_TIME_LOW);
1328c2ecf20Sopenharmony_ci	time_high = readl(base + TIMER_TIME_HIGH);
1338c2ecf20Sopenharmony_ci	time = (time_high << 32) | time_low;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	do_div(time, NSEC_PER_SEC);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	rtc_time64_to_tm(time, tm);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	return 0;
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic int goldfish_rtc_set_time(struct device *dev, struct rtc_time *tm)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	struct goldfish_rtc *rtcdrv;
1458c2ecf20Sopenharmony_ci	void __iomem *base;
1468c2ecf20Sopenharmony_ci	u64 now64;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	rtcdrv = dev_get_drvdata(dev);
1498c2ecf20Sopenharmony_ci	base = rtcdrv->base;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	now64 = rtc_tm_to_time64(tm) * NSEC_PER_SEC;
1528c2ecf20Sopenharmony_ci	writel((now64 >> 32), base + TIMER_TIME_HIGH);
1538c2ecf20Sopenharmony_ci	writel(now64, base + TIMER_TIME_LOW);
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	return 0;
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_cistatic const struct rtc_class_ops goldfish_rtc_ops = {
1598c2ecf20Sopenharmony_ci	.read_time	= goldfish_rtc_read_time,
1608c2ecf20Sopenharmony_ci	.set_time	= goldfish_rtc_set_time,
1618c2ecf20Sopenharmony_ci	.read_alarm	= goldfish_rtc_read_alarm,
1628c2ecf20Sopenharmony_ci	.set_alarm	= goldfish_rtc_set_alarm,
1638c2ecf20Sopenharmony_ci	.alarm_irq_enable = goldfish_rtc_alarm_irq_enable
1648c2ecf20Sopenharmony_ci};
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic int goldfish_rtc_probe(struct platform_device *pdev)
1678c2ecf20Sopenharmony_ci{
1688c2ecf20Sopenharmony_ci	struct goldfish_rtc *rtcdrv;
1698c2ecf20Sopenharmony_ci	int err;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	rtcdrv = devm_kzalloc(&pdev->dev, sizeof(*rtcdrv), GFP_KERNEL);
1728c2ecf20Sopenharmony_ci	if (!rtcdrv)
1738c2ecf20Sopenharmony_ci		return -ENOMEM;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, rtcdrv);
1768c2ecf20Sopenharmony_ci	rtcdrv->base = devm_platform_ioremap_resource(pdev, 0);
1778c2ecf20Sopenharmony_ci	if (IS_ERR(rtcdrv->base))
1788c2ecf20Sopenharmony_ci		return PTR_ERR(rtcdrv->base);
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	rtcdrv->irq = platform_get_irq(pdev, 0);
1818c2ecf20Sopenharmony_ci	if (rtcdrv->irq < 0)
1828c2ecf20Sopenharmony_ci		return -ENODEV;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	rtcdrv->rtc = devm_rtc_allocate_device(&pdev->dev);
1858c2ecf20Sopenharmony_ci	if (IS_ERR(rtcdrv->rtc))
1868c2ecf20Sopenharmony_ci		return PTR_ERR(rtcdrv->rtc);
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	rtcdrv->rtc->ops = &goldfish_rtc_ops;
1898c2ecf20Sopenharmony_ci	rtcdrv->rtc->range_max = U64_MAX / NSEC_PER_SEC;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	err = devm_request_irq(&pdev->dev, rtcdrv->irq,
1928c2ecf20Sopenharmony_ci			       goldfish_rtc_interrupt,
1938c2ecf20Sopenharmony_ci			       0, pdev->name, rtcdrv);
1948c2ecf20Sopenharmony_ci	if (err)
1958c2ecf20Sopenharmony_ci		return err;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	return rtc_register_device(rtcdrv->rtc);
1988c2ecf20Sopenharmony_ci}
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_cistatic const struct of_device_id goldfish_rtc_of_match[] = {
2018c2ecf20Sopenharmony_ci	{ .compatible = "google,goldfish-rtc", },
2028c2ecf20Sopenharmony_ci	{},
2038c2ecf20Sopenharmony_ci};
2048c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, goldfish_rtc_of_match);
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cistatic struct platform_driver goldfish_rtc = {
2078c2ecf20Sopenharmony_ci	.probe = goldfish_rtc_probe,
2088c2ecf20Sopenharmony_ci	.driver = {
2098c2ecf20Sopenharmony_ci		.name = "goldfish_rtc",
2108c2ecf20Sopenharmony_ci		.of_match_table = goldfish_rtc_of_match,
2118c2ecf20Sopenharmony_ci	}
2128c2ecf20Sopenharmony_ci};
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_cimodule_platform_driver(goldfish_rtc);
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
217