|  | /* | 
|  | * Realtek RTD129x RTC | 
|  | * | 
|  | * Copyright (c) 2017 Andreas Färber | 
|  | * | 
|  | * SPDX-License-Identifier: GPL-2.0+ | 
|  | */ | 
|  |  | 
|  | #include <linux/clk.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/of_address.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/rtc.h> | 
|  | #include <linux/spinlock.h> | 
|  |  | 
|  | #define RTD_RTCSEC		0x00 | 
|  | #define RTD_RTCMIN		0x04 | 
|  | #define RTD_RTCHR		0x08 | 
|  | #define RTD_RTCDATE1		0x0c | 
|  | #define RTD_RTCDATE2		0x10 | 
|  | #define RTD_RTCACR		0x28 | 
|  | #define RTD_RTCEN		0x2c | 
|  | #define RTD_RTCCR		0x30 | 
|  |  | 
|  | #define RTD_RTCSEC_RTCSEC_MASK		0x7f | 
|  |  | 
|  | #define RTD_RTCMIN_RTCMIN_MASK		0x3f | 
|  |  | 
|  | #define RTD_RTCHR_RTCHR_MASK		0x1f | 
|  |  | 
|  | #define RTD_RTCDATE1_RTCDATE1_MASK	0xff | 
|  |  | 
|  | #define RTD_RTCDATE2_RTCDATE2_MASK	0x7f | 
|  |  | 
|  | #define RTD_RTCACR_RTCPWR		BIT(7) | 
|  |  | 
|  | #define RTD_RTCEN_RTCEN_MASK		0xff | 
|  |  | 
|  | #define RTD_RTCCR_RTCRST		BIT(6) | 
|  |  | 
|  | struct rtd119x_rtc { | 
|  | void __iomem *base; | 
|  | struct clk *clk; | 
|  | struct rtc_device *rtcdev; | 
|  | unsigned int base_year; | 
|  | }; | 
|  |  | 
|  | static inline int rtd119x_rtc_days_in_year(int year) | 
|  | { | 
|  | return 365 + (is_leap_year(year) ? 1 : 0); | 
|  | } | 
|  |  | 
|  | static void rtd119x_rtc_reset(struct device *dev) | 
|  | { | 
|  | struct rtd119x_rtc *data = dev_get_drvdata(dev); | 
|  | u32 val; | 
|  |  | 
|  | val = readl_relaxed(data->base + RTD_RTCCR); | 
|  | val |= RTD_RTCCR_RTCRST; | 
|  | writel_relaxed(val, data->base + RTD_RTCCR); | 
|  |  | 
|  | val &= ~RTD_RTCCR_RTCRST; | 
|  | writel(val, data->base + RTD_RTCCR); | 
|  | } | 
|  |  | 
|  | static void rtd119x_rtc_set_enabled(struct device *dev, bool enable) | 
|  | { | 
|  | struct rtd119x_rtc *data = dev_get_drvdata(dev); | 
|  | u32 val; | 
|  |  | 
|  | val = readl_relaxed(data->base + RTD_RTCEN); | 
|  | if (enable) { | 
|  | if ((val & RTD_RTCEN_RTCEN_MASK) == 0x5a) | 
|  | return; | 
|  | writel_relaxed(0x5a, data->base + RTD_RTCEN); | 
|  | } else { | 
|  | writel_relaxed(0, data->base + RTD_RTCEN); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int rtd119x_rtc_read_time(struct device *dev, struct rtc_time *tm) | 
|  | { | 
|  | struct rtd119x_rtc *data = dev_get_drvdata(dev); | 
|  | s32 day; | 
|  | u32 sec; | 
|  | unsigned int year; | 
|  | int tries = 0; | 
|  |  | 
|  | while (true) { | 
|  | tm->tm_sec = (readl_relaxed(data->base + RTD_RTCSEC) & RTD_RTCSEC_RTCSEC_MASK) >> 1; | 
|  | tm->tm_min  = readl_relaxed(data->base + RTD_RTCMIN) & RTD_RTCMIN_RTCMIN_MASK; | 
|  | tm->tm_hour = readl_relaxed(data->base + RTD_RTCHR) & RTD_RTCHR_RTCHR_MASK; | 
|  | day  =  readl_relaxed(data->base + RTD_RTCDATE1) & RTD_RTCDATE1_RTCDATE1_MASK; | 
|  | day |= (readl_relaxed(data->base + RTD_RTCDATE2) & RTD_RTCDATE2_RTCDATE2_MASK) << 8; | 
|  | sec  = (readl_relaxed(data->base + RTD_RTCSEC) & RTD_RTCSEC_RTCSEC_MASK) >> 1; | 
|  | tries++; | 
|  |  | 
|  | if (sec == tm->tm_sec) | 
|  | break; | 
|  |  | 
|  | if (tries >= 3) | 
|  | return -EINVAL; | 
|  | } | 
|  | if (tries > 1) | 
|  | dev_dbg(dev, "%s: needed %i tries\n", __func__, tries); | 
|  |  | 
|  | year = data->base_year; | 
|  | while (day >= rtd119x_rtc_days_in_year(year)) { | 
|  | day -= rtd119x_rtc_days_in_year(year); | 
|  | year++; | 
|  | } | 
|  | tm->tm_year = year - 1900; | 
|  | tm->tm_yday = day; | 
|  |  | 
|  | tm->tm_mon = 0; | 
|  | while (day >= rtc_month_days(tm->tm_mon, year)) { | 
|  | day -= rtc_month_days(tm->tm_mon, year); | 
|  | tm->tm_mon++; | 
|  | } | 
|  | tm->tm_mday = day + 1; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int rtd119x_rtc_set_time(struct device *dev, struct rtc_time *tm) | 
|  | { | 
|  | struct rtd119x_rtc *data = dev_get_drvdata(dev); | 
|  | unsigned int day; | 
|  | int i; | 
|  |  | 
|  | if (1900 + tm->tm_year < data->base_year) | 
|  | return -EINVAL; | 
|  |  | 
|  | day = 0; | 
|  | for (i = data->base_year; i < 1900 + tm->tm_year; i++) | 
|  | day += rtd119x_rtc_days_in_year(i); | 
|  |  | 
|  | day += tm->tm_yday; | 
|  | if (day > 0x7fff) | 
|  | return -EINVAL; | 
|  |  | 
|  | rtd119x_rtc_set_enabled(dev, false); | 
|  |  | 
|  | writel_relaxed((tm->tm_sec << 1) & RTD_RTCSEC_RTCSEC_MASK, data->base + RTD_RTCSEC); | 
|  | writel_relaxed(tm->tm_min & RTD_RTCMIN_RTCMIN_MASK, data->base + RTD_RTCMIN); | 
|  | writel_relaxed(tm->tm_hour & RTD_RTCHR_RTCHR_MASK, data->base + RTD_RTCHR); | 
|  | writel_relaxed(day & RTD_RTCDATE1_RTCDATE1_MASK, data->base + RTD_RTCDATE1); | 
|  | writel_relaxed((day >> 8) & RTD_RTCDATE2_RTCDATE2_MASK, data->base + RTD_RTCDATE2); | 
|  |  | 
|  | rtd119x_rtc_set_enabled(dev, true); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct rtc_class_ops rtd119x_rtc_ops = { | 
|  | .read_time	= rtd119x_rtc_read_time, | 
|  | .set_time	= rtd119x_rtc_set_time, | 
|  | }; | 
|  |  | 
|  | static const struct of_device_id rtd119x_rtc_dt_ids[] = { | 
|  | { .compatible = "realtek,rtd1295-rtc" }, | 
|  | { } | 
|  | }; | 
|  |  | 
|  | static int rtd119x_rtc_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct rtd119x_rtc *data; | 
|  | struct resource *res; | 
|  | u32 val; | 
|  | int ret; | 
|  |  | 
|  | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); | 
|  | if (!data) | 
|  | return -ENOMEM; | 
|  |  | 
|  | platform_set_drvdata(pdev, data); | 
|  | data->base_year = 2014; | 
|  |  | 
|  | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | data->base = devm_ioremap_resource(&pdev->dev, res); | 
|  | if (IS_ERR(data->base)) | 
|  | return PTR_ERR(data->base); | 
|  |  | 
|  | data->clk = of_clk_get(pdev->dev.of_node, 0); | 
|  | if (IS_ERR(data->clk)) | 
|  | return PTR_ERR(data->clk); | 
|  |  | 
|  | ret = clk_prepare_enable(data->clk); | 
|  | if (ret) { | 
|  | clk_put(data->clk); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | val = readl_relaxed(data->base + RTD_RTCACR); | 
|  | if (!(val & RTD_RTCACR_RTCPWR)) { | 
|  | writel_relaxed(RTD_RTCACR_RTCPWR, data->base + RTD_RTCACR); | 
|  |  | 
|  | rtd119x_rtc_reset(&pdev->dev); | 
|  |  | 
|  | writel_relaxed(0, data->base + RTD_RTCMIN); | 
|  | writel_relaxed(0, data->base + RTD_RTCHR); | 
|  | writel_relaxed(0, data->base + RTD_RTCDATE1); | 
|  | writel_relaxed(0, data->base + RTD_RTCDATE2); | 
|  | } | 
|  |  | 
|  | rtd119x_rtc_set_enabled(&pdev->dev, true); | 
|  |  | 
|  | data->rtcdev = devm_rtc_device_register(&pdev->dev, "rtc", | 
|  | &rtd119x_rtc_ops, THIS_MODULE); | 
|  | if (IS_ERR(data->rtcdev)) { | 
|  | dev_err(&pdev->dev, "failed to register rtc device"); | 
|  | clk_disable_unprepare(data->clk); | 
|  | clk_put(data->clk); | 
|  | return PTR_ERR(data->rtcdev); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int rtd119x_rtc_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct rtd119x_rtc *data = platform_get_drvdata(pdev); | 
|  |  | 
|  | rtd119x_rtc_set_enabled(&pdev->dev, false); | 
|  |  | 
|  | clk_disable_unprepare(data->clk); | 
|  | clk_put(data->clk); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct platform_driver rtd119x_rtc_driver = { | 
|  | .probe = rtd119x_rtc_probe, | 
|  | .remove = rtd119x_rtc_remove, | 
|  | .driver = { | 
|  | .name = "rtd1295-rtc", | 
|  | .of_match_table	= rtd119x_rtc_dt_ids, | 
|  | }, | 
|  | }; | 
|  | builtin_platform_driver(rtd119x_rtc_driver); |