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