linux/drivers/rtc/rtc-rtd119x.c
<<
>>
Prefs
   1/*
   2 * Realtek RTD129x RTC
   3 *
   4 * Copyright (c) 2017 Andreas Färber
   5 *
   6 * SPDX-License-Identifier: GPL-2.0+
   7 */
   8
   9#include <linux/clk.h>
  10#include <linux/io.h>
  11#include <linux/module.h>
  12#include <linux/of.h>
  13#include <linux/of_address.h>
  14#include <linux/platform_device.h>
  15#include <linux/rtc.h>
  16#include <linux/spinlock.h>
  17
  18#define RTD_RTCSEC              0x00
  19#define RTD_RTCMIN              0x04
  20#define RTD_RTCHR               0x08
  21#define RTD_RTCDATE1            0x0c
  22#define RTD_RTCDATE2            0x10
  23#define RTD_RTCACR              0x28
  24#define RTD_RTCEN               0x2c
  25#define RTD_RTCCR               0x30
  26
  27#define RTD_RTCSEC_RTCSEC_MASK          0x7f
  28
  29#define RTD_RTCMIN_RTCMIN_MASK          0x3f
  30
  31#define RTD_RTCHR_RTCHR_MASK            0x1f
  32
  33#define RTD_RTCDATE1_RTCDATE1_MASK      0xff
  34
  35#define RTD_RTCDATE2_RTCDATE2_MASK      0x7f
  36
  37#define RTD_RTCACR_RTCPWR               BIT(7)
  38
  39#define RTD_RTCEN_RTCEN_MASK            0xff
  40
  41#define RTD_RTCCR_RTCRST                BIT(6)
  42
  43struct rtd119x_rtc {
  44        void __iomem *base;
  45        struct clk *clk;
  46        struct rtc_device *rtcdev;
  47        unsigned int base_year;
  48};
  49
  50static inline int rtd119x_rtc_days_in_year(int year)
  51{
  52        return 365 + (is_leap_year(year) ? 1 : 0);
  53}
  54
  55static void rtd119x_rtc_reset(struct device *dev)
  56{
  57        struct rtd119x_rtc *data = dev_get_drvdata(dev);
  58        u32 val;
  59
  60        val = readl_relaxed(data->base + RTD_RTCCR);
  61        val |= RTD_RTCCR_RTCRST;
  62        writel_relaxed(val, data->base + RTD_RTCCR);
  63
  64        val &= ~RTD_RTCCR_RTCRST;
  65        writel(val, data->base + RTD_RTCCR);
  66}
  67
  68static void rtd119x_rtc_set_enabled(struct device *dev, bool enable)
  69{
  70        struct rtd119x_rtc *data = dev_get_drvdata(dev);
  71        u32 val;
  72
  73        val = readl_relaxed(data->base + RTD_RTCEN);
  74        if (enable) {
  75                if ((val & RTD_RTCEN_RTCEN_MASK) == 0x5a)
  76                        return;
  77                writel_relaxed(0x5a, data->base + RTD_RTCEN);
  78        } else {
  79                writel_relaxed(0, data->base + RTD_RTCEN);
  80        }
  81}
  82
  83static int rtd119x_rtc_read_time(struct device *dev, struct rtc_time *tm)
  84{
  85        struct rtd119x_rtc *data = dev_get_drvdata(dev);
  86        s32 day;
  87        u32 sec;
  88        unsigned int year;
  89        int tries = 0;
  90
  91        while (true) {
  92                tm->tm_sec = (readl_relaxed(data->base + RTD_RTCSEC) & RTD_RTCSEC_RTCSEC_MASK) >> 1;
  93                tm->tm_min  = readl_relaxed(data->base + RTD_RTCMIN) & RTD_RTCMIN_RTCMIN_MASK;
  94                tm->tm_hour = readl_relaxed(data->base + RTD_RTCHR) & RTD_RTCHR_RTCHR_MASK;
  95                day  =  readl_relaxed(data->base + RTD_RTCDATE1) & RTD_RTCDATE1_RTCDATE1_MASK;
  96                day |= (readl_relaxed(data->base + RTD_RTCDATE2) & RTD_RTCDATE2_RTCDATE2_MASK) << 8;
  97                sec  = (readl_relaxed(data->base + RTD_RTCSEC) & RTD_RTCSEC_RTCSEC_MASK) >> 1;
  98                tries++;
  99
 100                if (sec == tm->tm_sec)
 101                        break;
 102
 103                if (tries >= 3)
 104                        return -EINVAL;
 105        }
 106        if (tries > 1)
 107                dev_dbg(dev, "%s: needed %i tries\n", __func__, tries);
 108
 109        year = data->base_year;
 110        while (day >= rtd119x_rtc_days_in_year(year)) {
 111                day -= rtd119x_rtc_days_in_year(year);
 112                year++;
 113        }
 114        tm->tm_year = year - 1900;
 115        tm->tm_yday = day;
 116
 117        tm->tm_mon = 0;
 118        while (day >= rtc_month_days(tm->tm_mon, year)) {
 119                day -= rtc_month_days(tm->tm_mon, year);
 120                tm->tm_mon++;
 121        }
 122        tm->tm_mday = day + 1;
 123
 124        return 0;
 125}
 126
 127static int rtd119x_rtc_set_time(struct device *dev, struct rtc_time *tm)
 128{
 129        struct rtd119x_rtc *data = dev_get_drvdata(dev);
 130        unsigned int day;
 131        int i;
 132
 133        if (1900 + tm->tm_year < data->base_year)
 134                return -EINVAL;
 135
 136        day = 0;
 137        for (i = data->base_year; i < 1900 + tm->tm_year; i++)
 138                day += rtd119x_rtc_days_in_year(i);
 139
 140        day += tm->tm_yday;
 141        if (day > 0x7fff)
 142                return -EINVAL;
 143
 144        rtd119x_rtc_set_enabled(dev, false);
 145
 146        writel_relaxed((tm->tm_sec << 1) & RTD_RTCSEC_RTCSEC_MASK, data->base + RTD_RTCSEC);
 147        writel_relaxed(tm->tm_min & RTD_RTCMIN_RTCMIN_MASK, data->base + RTD_RTCMIN);
 148        writel_relaxed(tm->tm_hour & RTD_RTCHR_RTCHR_MASK, data->base + RTD_RTCHR);
 149        writel_relaxed(day & RTD_RTCDATE1_RTCDATE1_MASK, data->base + RTD_RTCDATE1);
 150        writel_relaxed((day >> 8) & RTD_RTCDATE2_RTCDATE2_MASK, data->base + RTD_RTCDATE2);
 151
 152        rtd119x_rtc_set_enabled(dev, true);
 153
 154        return 0;
 155}
 156
 157static const struct rtc_class_ops rtd119x_rtc_ops = {
 158        .read_time      = rtd119x_rtc_read_time,
 159        .set_time       = rtd119x_rtc_set_time,
 160};
 161
 162static const struct of_device_id rtd119x_rtc_dt_ids[] = {
 163         { .compatible = "realtek,rtd1295-rtc" },
 164         { }
 165};
 166
 167static int rtd119x_rtc_probe(struct platform_device *pdev)
 168{
 169        struct rtd119x_rtc *data;
 170        struct resource *res;
 171        u32 val;
 172        int ret;
 173
 174        data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
 175        if (!data)
 176                return -ENOMEM;
 177
 178        platform_set_drvdata(pdev, data);
 179        data->base_year = 2014;
 180
 181        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 182        data->base = devm_ioremap_resource(&pdev->dev, res);
 183        if (IS_ERR(data->base))
 184                return PTR_ERR(data->base);
 185
 186        data->clk = of_clk_get(pdev->dev.of_node, 0);
 187        if (IS_ERR(data->clk))
 188                return PTR_ERR(data->clk);
 189
 190        ret = clk_prepare_enable(data->clk);
 191        if (ret) {
 192                clk_put(data->clk);
 193                return ret;
 194        }
 195
 196        val = readl_relaxed(data->base + RTD_RTCACR);
 197        if (!(val & RTD_RTCACR_RTCPWR)) {
 198                writel_relaxed(RTD_RTCACR_RTCPWR, data->base + RTD_RTCACR);
 199
 200                rtd119x_rtc_reset(&pdev->dev);
 201
 202                writel_relaxed(0, data->base + RTD_RTCMIN);
 203                writel_relaxed(0, data->base + RTD_RTCHR);
 204                writel_relaxed(0, data->base + RTD_RTCDATE1);
 205                writel_relaxed(0, data->base + RTD_RTCDATE2);
 206        }
 207
 208        rtd119x_rtc_set_enabled(&pdev->dev, true);
 209
 210        data->rtcdev = devm_rtc_device_register(&pdev->dev, "rtc",
 211                                                &rtd119x_rtc_ops, THIS_MODULE);
 212        if (IS_ERR(data->rtcdev)) {
 213                dev_err(&pdev->dev, "failed to register rtc device");
 214                clk_disable_unprepare(data->clk);
 215                clk_put(data->clk);
 216                return PTR_ERR(data->rtcdev);
 217        }
 218
 219        return 0;
 220}
 221
 222static int rtd119x_rtc_remove(struct platform_device *pdev)
 223{
 224        struct rtd119x_rtc *data = platform_get_drvdata(pdev);
 225
 226        rtd119x_rtc_set_enabled(&pdev->dev, false);
 227
 228        clk_disable_unprepare(data->clk);
 229        clk_put(data->clk);
 230
 231        return 0;
 232}
 233
 234static struct platform_driver rtd119x_rtc_driver = {
 235        .probe = rtd119x_rtc_probe,
 236        .remove = rtd119x_rtc_remove,
 237        .driver = {
 238                .name = "rtd1295-rtc",
 239                .of_match_table = rtd119x_rtc_dt_ids,
 240        },
 241};
 242builtin_platform_driver(rtd119x_rtc_driver);
 243