18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Faraday Technology FTRTC010 driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2009 Janos Laube <janos.dev@gmail.com> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Original code for older kernel 2.6.15 are from Stormlinksemi 88c2ecf20Sopenharmony_ci * first update from Janos Laube for > 2.6.29 kernels 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * checkpatch fixes and usage of rtc-lib code 118c2ecf20Sopenharmony_ci * Hans Ulli Kroll <ulli.kroll@googlemail.com> 128c2ecf20Sopenharmony_ci */ 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <linux/rtc.h> 158c2ecf20Sopenharmony_ci#include <linux/io.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci#include <linux/kernel.h> 198c2ecf20Sopenharmony_ci#include <linux/module.h> 208c2ecf20Sopenharmony_ci#include <linux/mod_devicetable.h> 218c2ecf20Sopenharmony_ci#include <linux/clk.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define DRV_NAME "rtc-ftrtc010" 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hans Ulli Kroll <ulli.kroll@googlemail.com>"); 268c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("RTC driver for Gemini SoC"); 278c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 288c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistruct ftrtc010_rtc { 318c2ecf20Sopenharmony_ci struct rtc_device *rtc_dev; 328c2ecf20Sopenharmony_ci void __iomem *rtc_base; 338c2ecf20Sopenharmony_ci int rtc_irq; 348c2ecf20Sopenharmony_ci struct clk *pclk; 358c2ecf20Sopenharmony_ci struct clk *extclk; 368c2ecf20Sopenharmony_ci}; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cienum ftrtc010_rtc_offsets { 398c2ecf20Sopenharmony_ci FTRTC010_RTC_SECOND = 0x00, 408c2ecf20Sopenharmony_ci FTRTC010_RTC_MINUTE = 0x04, 418c2ecf20Sopenharmony_ci FTRTC010_RTC_HOUR = 0x08, 428c2ecf20Sopenharmony_ci FTRTC010_RTC_DAYS = 0x0C, 438c2ecf20Sopenharmony_ci FTRTC010_RTC_ALARM_SECOND = 0x10, 448c2ecf20Sopenharmony_ci FTRTC010_RTC_ALARM_MINUTE = 0x14, 458c2ecf20Sopenharmony_ci FTRTC010_RTC_ALARM_HOUR = 0x18, 468c2ecf20Sopenharmony_ci FTRTC010_RTC_RECORD = 0x1C, 478c2ecf20Sopenharmony_ci FTRTC010_RTC_CR = 0x20, 488c2ecf20Sopenharmony_ci}; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic irqreturn_t ftrtc010_rtc_interrupt(int irq, void *dev) 518c2ecf20Sopenharmony_ci{ 528c2ecf20Sopenharmony_ci return IRQ_HANDLED; 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci/* 568c2ecf20Sopenharmony_ci * Looks like the RTC in the Gemini SoC is (totaly) broken 578c2ecf20Sopenharmony_ci * We can't read/write directly the time from RTC registers. 588c2ecf20Sopenharmony_ci * We must do some "offset" calculation to get the real time 598c2ecf20Sopenharmony_ci * 608c2ecf20Sopenharmony_ci * This FIX works pretty fine and Stormlinksemi aka Cortina-Networks does 618c2ecf20Sopenharmony_ci * the same thing, without the rtc-lib.c calls. 628c2ecf20Sopenharmony_ci */ 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic int ftrtc010_rtc_read_time(struct device *dev, struct rtc_time *tm) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci struct ftrtc010_rtc *rtc = dev_get_drvdata(dev); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci u32 days, hour, min, sec, offset; 698c2ecf20Sopenharmony_ci timeu64_t time; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci sec = readl(rtc->rtc_base + FTRTC010_RTC_SECOND); 728c2ecf20Sopenharmony_ci min = readl(rtc->rtc_base + FTRTC010_RTC_MINUTE); 738c2ecf20Sopenharmony_ci hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR); 748c2ecf20Sopenharmony_ci days = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); 758c2ecf20Sopenharmony_ci offset = readl(rtc->rtc_base + FTRTC010_RTC_RECORD); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci time = offset + days * 86400 + hour * 3600 + min * 60 + sec; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci rtc_time64_to_tm(time, tm); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci return 0; 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic int ftrtc010_rtc_set_time(struct device *dev, struct rtc_time *tm) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci struct ftrtc010_rtc *rtc = dev_get_drvdata(dev); 878c2ecf20Sopenharmony_ci u32 sec, min, hour, day, offset; 888c2ecf20Sopenharmony_ci timeu64_t time; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci time = rtc_tm_to_time64(tm); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci sec = readl(rtc->rtc_base + FTRTC010_RTC_SECOND); 938c2ecf20Sopenharmony_ci min = readl(rtc->rtc_base + FTRTC010_RTC_MINUTE); 948c2ecf20Sopenharmony_ci hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR); 958c2ecf20Sopenharmony_ci day = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci offset = time - (day * 86400 + hour * 3600 + min * 60 + sec); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci writel(offset, rtc->rtc_base + FTRTC010_RTC_RECORD); 1008c2ecf20Sopenharmony_ci writel(0x01, rtc->rtc_base + FTRTC010_RTC_CR); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci return 0; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_cistatic const struct rtc_class_ops ftrtc010_rtc_ops = { 1068c2ecf20Sopenharmony_ci .read_time = ftrtc010_rtc_read_time, 1078c2ecf20Sopenharmony_ci .set_time = ftrtc010_rtc_set_time, 1088c2ecf20Sopenharmony_ci}; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic int ftrtc010_rtc_probe(struct platform_device *pdev) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci u32 days, hour, min, sec; 1138c2ecf20Sopenharmony_ci struct ftrtc010_rtc *rtc; 1148c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1158c2ecf20Sopenharmony_ci struct resource *res; 1168c2ecf20Sopenharmony_ci int ret; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); 1198c2ecf20Sopenharmony_ci if (unlikely(!rtc)) 1208c2ecf20Sopenharmony_ci return -ENOMEM; 1218c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, rtc); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci rtc->pclk = devm_clk_get(dev, "PCLK"); 1248c2ecf20Sopenharmony_ci if (IS_ERR(rtc->pclk)) { 1258c2ecf20Sopenharmony_ci dev_err(dev, "could not get PCLK\n"); 1268c2ecf20Sopenharmony_ci } else { 1278c2ecf20Sopenharmony_ci ret = clk_prepare_enable(rtc->pclk); 1288c2ecf20Sopenharmony_ci if (ret) { 1298c2ecf20Sopenharmony_ci dev_err(dev, "failed to enable PCLK\n"); 1308c2ecf20Sopenharmony_ci return ret; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci rtc->extclk = devm_clk_get(dev, "EXTCLK"); 1348c2ecf20Sopenharmony_ci if (IS_ERR(rtc->extclk)) { 1358c2ecf20Sopenharmony_ci dev_err(dev, "could not get EXTCLK\n"); 1368c2ecf20Sopenharmony_ci } else { 1378c2ecf20Sopenharmony_ci ret = clk_prepare_enable(rtc->extclk); 1388c2ecf20Sopenharmony_ci if (ret) { 1398c2ecf20Sopenharmony_ci dev_err(dev, "failed to enable EXTCLK\n"); 1408c2ecf20Sopenharmony_ci return ret; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 1458c2ecf20Sopenharmony_ci if (!res) 1468c2ecf20Sopenharmony_ci return -ENODEV; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci rtc->rtc_irq = res->start; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1518c2ecf20Sopenharmony_ci if (!res) 1528c2ecf20Sopenharmony_ci return -ENODEV; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci rtc->rtc_base = devm_ioremap(dev, res->start, 1558c2ecf20Sopenharmony_ci resource_size(res)); 1568c2ecf20Sopenharmony_ci if (!rtc->rtc_base) 1578c2ecf20Sopenharmony_ci return -ENOMEM; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci rtc->rtc_dev = devm_rtc_allocate_device(dev); 1608c2ecf20Sopenharmony_ci if (IS_ERR(rtc->rtc_dev)) 1618c2ecf20Sopenharmony_ci return PTR_ERR(rtc->rtc_dev); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci rtc->rtc_dev->ops = &ftrtc010_rtc_ops; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci sec = readl(rtc->rtc_base + FTRTC010_RTC_SECOND); 1668c2ecf20Sopenharmony_ci min = readl(rtc->rtc_base + FTRTC010_RTC_MINUTE); 1678c2ecf20Sopenharmony_ci hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR); 1688c2ecf20Sopenharmony_ci days = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci rtc->rtc_dev->range_min = (u64)days * 86400 + hour * 3600 + 1718c2ecf20Sopenharmony_ci min * 60 + sec; 1728c2ecf20Sopenharmony_ci rtc->rtc_dev->range_max = U32_MAX + rtc->rtc_dev->range_min; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci ret = devm_request_irq(dev, rtc->rtc_irq, ftrtc010_rtc_interrupt, 1758c2ecf20Sopenharmony_ci IRQF_SHARED, pdev->name, dev); 1768c2ecf20Sopenharmony_ci if (unlikely(ret)) 1778c2ecf20Sopenharmony_ci return ret; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci return rtc_register_device(rtc->rtc_dev); 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic int ftrtc010_rtc_remove(struct platform_device *pdev) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci struct ftrtc010_rtc *rtc = platform_get_drvdata(pdev); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci if (!IS_ERR(rtc->extclk)) 1878c2ecf20Sopenharmony_ci clk_disable_unprepare(rtc->extclk); 1888c2ecf20Sopenharmony_ci if (!IS_ERR(rtc->pclk)) 1898c2ecf20Sopenharmony_ci clk_disable_unprepare(rtc->pclk); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci return 0; 1928c2ecf20Sopenharmony_ci} 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_cistatic const struct of_device_id ftrtc010_rtc_dt_match[] = { 1958c2ecf20Sopenharmony_ci { .compatible = "cortina,gemini-rtc" }, 1968c2ecf20Sopenharmony_ci { .compatible = "faraday,ftrtc010" }, 1978c2ecf20Sopenharmony_ci { } 1988c2ecf20Sopenharmony_ci}; 1998c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ftrtc010_rtc_dt_match); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cistatic struct platform_driver ftrtc010_rtc_driver = { 2028c2ecf20Sopenharmony_ci .driver = { 2038c2ecf20Sopenharmony_ci .name = DRV_NAME, 2048c2ecf20Sopenharmony_ci .of_match_table = ftrtc010_rtc_dt_match, 2058c2ecf20Sopenharmony_ci }, 2068c2ecf20Sopenharmony_ci .probe = ftrtc010_rtc_probe, 2078c2ecf20Sopenharmony_ci .remove = ftrtc010_rtc_remove, 2088c2ecf20Sopenharmony_ci}; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_cimodule_platform_driver_probe(ftrtc010_rtc_driver, ftrtc010_rtc_probe); 211