| /* |
| * 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); |