rtc: pcf8523: let the core handle the alarm resolution
[linux-2.6-microblaze.git] / drivers / rtc / rtc-sun6i.c
index 711832c..5b3e4da 100644 (file)
@@ -13,6 +13,7 @@
 
 #include <linux/clk.h>
 #include <linux/clk-provider.h>
+#include <linux/clk/sunxi-ng.h>
 #include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/fs.h>
@@ -48,7 +49,8 @@
 
 /* Alarm 0 (counter) */
 #define SUN6I_ALRM_COUNTER                     0x0020
-#define SUN6I_ALRM_CUR_VAL                     0x0024
+/* This holds the remaining alarm seconds on older SoCs (current value) */
+#define SUN6I_ALRM_COUNTER_HMS                 0x0024
 #define SUN6I_ALRM_EN                          0x0028
 #define SUN6I_ALRM_EN_CNT_EN                   BIT(0)
 #define SUN6I_ALRM_IRQ_EN                      0x002c
 #define SUN6I_YEAR_MIN                         1970
 #define SUN6I_YEAR_OFF                         (SUN6I_YEAR_MIN - 1900)
 
+#define SECS_PER_DAY                           (24 * 3600ULL)
+
 /*
  * There are other differences between models, including:
  *
@@ -133,12 +137,15 @@ struct sun6i_rtc_clk_data {
        unsigned int has_auto_swt : 1;
 };
 
+#define RTC_LINEAR_DAY BIT(0)
+
 struct sun6i_rtc_dev {
        struct rtc_device *rtc;
        const struct sun6i_rtc_clk_data *data;
        void __iomem *base;
        int irq;
-       unsigned long alarm;
+       time64_t alarm;
+       unsigned long flags;
 
        struct clk_hw hw;
        struct clk_hw *int_osc;
@@ -363,23 +370,6 @@ CLK_OF_DECLARE_DRIVER(sun8i_h3_rtc_clk, "allwinner,sun8i-h3-rtc",
 CLK_OF_DECLARE_DRIVER(sun50i_h5_rtc_clk, "allwinner,sun50i-h5-rtc",
                      sun8i_h3_rtc_clk_init);
 
-static const struct sun6i_rtc_clk_data sun50i_h6_rtc_data = {
-       .rc_osc_rate = 16000000,
-       .fixed_prescaler = 32,
-       .has_prescaler = 1,
-       .has_out_clk = 1,
-       .export_iosc = 1,
-       .has_losc_en = 1,
-       .has_auto_swt = 1,
-};
-
-static void __init sun50i_h6_rtc_clk_init(struct device_node *node)
-{
-       sun6i_rtc_clk_init(node, &sun50i_h6_rtc_data);
-}
-CLK_OF_DECLARE_DRIVER(sun50i_h6_rtc_clk, "allwinner,sun50i-h6-rtc",
-                     sun50i_h6_rtc_clk_init);
-
 /*
  * The R40 user manual is self-conflicting on whether the prescaler is
  * fixed or configurable. The clock diagram shows it as fixed, but there
@@ -467,22 +457,30 @@ static int sun6i_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
        } while ((date != readl(chip->base + SUN6I_RTC_YMD)) ||
                 (time != readl(chip->base + SUN6I_RTC_HMS)));
 
+       if (chip->flags & RTC_LINEAR_DAY) {
+               /*
+                * Newer chips store a linear day number, the manual
+                * does not mandate any epoch base. The BSP driver uses
+                * the UNIX epoch, let's just copy that, as it's the
+                * easiest anyway.
+                */
+               rtc_time64_to_tm((date & 0xffff) * SECS_PER_DAY, rtc_tm);
+       } else {
+               rtc_tm->tm_mday = SUN6I_DATE_GET_DAY_VALUE(date);
+               rtc_tm->tm_mon  = SUN6I_DATE_GET_MON_VALUE(date) - 1;
+               rtc_tm->tm_year = SUN6I_DATE_GET_YEAR_VALUE(date);
+
+               /*
+                * switch from (data_year->min)-relative offset to
+                * a (1900)-relative one
+                */
+               rtc_tm->tm_year += SUN6I_YEAR_OFF;
+       }
+
        rtc_tm->tm_sec  = SUN6I_TIME_GET_SEC_VALUE(time);
        rtc_tm->tm_min  = SUN6I_TIME_GET_MIN_VALUE(time);
        rtc_tm->tm_hour = SUN6I_TIME_GET_HOUR_VALUE(time);
 
-       rtc_tm->tm_mday = SUN6I_DATE_GET_DAY_VALUE(date);
-       rtc_tm->tm_mon  = SUN6I_DATE_GET_MON_VALUE(date);
-       rtc_tm->tm_year = SUN6I_DATE_GET_YEAR_VALUE(date);
-
-       rtc_tm->tm_mon  -= 1;
-
-       /*
-        * switch from (data_year->min)-relative offset to
-        * a (1900)-relative one
-        */
-       rtc_tm->tm_year += SUN6I_YEAR_OFF;
-
        return 0;
 }
 
@@ -510,36 +508,54 @@ static int sun6i_rtc_setalarm(struct device *dev, struct rtc_wkalrm *wkalrm)
        struct sun6i_rtc_dev *chip = dev_get_drvdata(dev);
        struct rtc_time *alrm_tm = &wkalrm->time;
        struct rtc_time tm_now;
-       unsigned long time_now = 0;
-       unsigned long time_set = 0;
-       unsigned long time_gap = 0;
-       int ret = 0;
-
-       ret = sun6i_rtc_gettime(dev, &tm_now);
-       if (ret < 0) {
-               dev_err(dev, "Error in getting time\n");
-               return -EINVAL;
-       }
+       time64_t time_set;
+       u32 counter_val, counter_val_hms;
+       int ret;
 
        time_set = rtc_tm_to_time64(alrm_tm);
-       time_now = rtc_tm_to_time64(&tm_now);
-       if (time_set <= time_now) {
-               dev_err(dev, "Date to set in the past\n");
-               return -EINVAL;
-       }
-
-       time_gap = time_set - time_now;
 
-       if (time_gap > U32_MAX) {
-               dev_err(dev, "Date too far in the future\n");
-               return -EINVAL;
+       if (chip->flags & RTC_LINEAR_DAY) {
+               /*
+                * The alarm registers hold the actual alarm time, encoded
+                * in the same way (linear day + HMS) as the current time.
+                */
+               counter_val_hms = SUN6I_TIME_SET_SEC_VALUE(alrm_tm->tm_sec)  |
+                                 SUN6I_TIME_SET_MIN_VALUE(alrm_tm->tm_min)  |
+                                 SUN6I_TIME_SET_HOUR_VALUE(alrm_tm->tm_hour);
+               /* The division will cut off the H:M:S part of alrm_tm. */
+               counter_val = div_u64(rtc_tm_to_time64(alrm_tm), SECS_PER_DAY);
+       } else {
+               /* The alarm register holds the number of seconds left. */
+               time64_t time_now;
+
+               ret = sun6i_rtc_gettime(dev, &tm_now);
+               if (ret < 0) {
+                       dev_err(dev, "Error in getting time\n");
+                       return -EINVAL;
+               }
+
+               time_now = rtc_tm_to_time64(&tm_now);
+               if (time_set <= time_now) {
+                       dev_err(dev, "Date to set in the past\n");
+                       return -EINVAL;
+               }
+               if ((time_set - time_now) > U32_MAX) {
+                       dev_err(dev, "Date too far in the future\n");
+                       return -EINVAL;
+               }
+
+               counter_val = time_set - time_now;
        }
 
        sun6i_rtc_setaie(0, chip);
        writel(0, chip->base + SUN6I_ALRM_COUNTER);
+       if (chip->flags & RTC_LINEAR_DAY)
+               writel(0, chip->base + SUN6I_ALRM_COUNTER_HMS);
        usleep_range(100, 300);
 
-       writel(time_gap, chip->base + SUN6I_ALRM_COUNTER);
+       writel(counter_val, chip->base + SUN6I_ALRM_COUNTER);
+       if (chip->flags & RTC_LINEAR_DAY)
+               writel(counter_val_hms, chip->base + SUN6I_ALRM_COUNTER_HMS);
        chip->alarm = time_set;
 
        sun6i_rtc_setaie(wkalrm->enabled, chip);
@@ -571,20 +587,25 @@ static int sun6i_rtc_settime(struct device *dev, struct rtc_time *rtc_tm)
        u32 date = 0;
        u32 time = 0;
 
-       rtc_tm->tm_year -= SUN6I_YEAR_OFF;
-       rtc_tm->tm_mon += 1;
-
-       date = SUN6I_DATE_SET_DAY_VALUE(rtc_tm->tm_mday) |
-               SUN6I_DATE_SET_MON_VALUE(rtc_tm->tm_mon)  |
-               SUN6I_DATE_SET_YEAR_VALUE(rtc_tm->tm_year);
-
-       if (is_leap_year(rtc_tm->tm_year + SUN6I_YEAR_MIN))
-               date |= SUN6I_LEAP_SET_VALUE(1);
-
        time = SUN6I_TIME_SET_SEC_VALUE(rtc_tm->tm_sec)  |
                SUN6I_TIME_SET_MIN_VALUE(rtc_tm->tm_min)  |
                SUN6I_TIME_SET_HOUR_VALUE(rtc_tm->tm_hour);
 
+       if (chip->flags & RTC_LINEAR_DAY) {
+               /* The division will cut off the H:M:S part of rtc_tm. */
+               date = div_u64(rtc_tm_to_time64(rtc_tm), SECS_PER_DAY);
+       } else {
+               rtc_tm->tm_year -= SUN6I_YEAR_OFF;
+               rtc_tm->tm_mon += 1;
+
+               date = SUN6I_DATE_SET_DAY_VALUE(rtc_tm->tm_mday) |
+                       SUN6I_DATE_SET_MON_VALUE(rtc_tm->tm_mon)  |
+                       SUN6I_DATE_SET_YEAR_VALUE(rtc_tm->tm_year);
+
+               if (is_leap_year(rtc_tm->tm_year + SUN6I_YEAR_MIN))
+                       date |= SUN6I_LEAP_SET_VALUE(1);
+       }
+
        /* Check whether registers are writable */
        if (sun6i_rtc_wait(chip, SUN6I_LOSC_CTRL,
                           SUN6I_LOSC_CTRL_ACC_MASK, 50)) {
@@ -668,11 +689,35 @@ static int sun6i_rtc_resume(struct device *dev)
 static SIMPLE_DEV_PM_OPS(sun6i_rtc_pm_ops,
        sun6i_rtc_suspend, sun6i_rtc_resume);
 
+static void sun6i_rtc_bus_clk_cleanup(void *data)
+{
+       struct clk *bus_clk = data;
+
+       clk_disable_unprepare(bus_clk);
+}
+
 static int sun6i_rtc_probe(struct platform_device *pdev)
 {
        struct sun6i_rtc_dev *chip = sun6i_rtc;
+       struct device *dev = &pdev->dev;
+       struct clk *bus_clk;
        int ret;
 
+       bus_clk = devm_clk_get_optional(dev, "bus");
+       if (IS_ERR(bus_clk))
+               return PTR_ERR(bus_clk);
+
+       if (bus_clk) {
+               ret = clk_prepare_enable(bus_clk);
+               if (ret)
+                       return ret;
+
+               ret = devm_add_action_or_reset(dev, sun6i_rtc_bus_clk_cleanup,
+                                              bus_clk);
+               if (ret)
+                       return ret;
+       }
+
        if (!chip) {
                chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
                if (!chip)
@@ -683,10 +728,18 @@ static int sun6i_rtc_probe(struct platform_device *pdev)
                chip->base = devm_platform_ioremap_resource(pdev, 0);
                if (IS_ERR(chip->base))
                        return PTR_ERR(chip->base);
+
+               if (IS_REACHABLE(CONFIG_SUN6I_RTC_CCU)) {
+                       ret = sun6i_rtc_ccu_probe(dev, chip->base);
+                       if (ret)
+                               return ret;
+               }
        }
 
        platform_set_drvdata(pdev, chip);
 
+       chip->flags = (unsigned long)of_device_get_match_data(&pdev->dev);
+
        chip->irq = platform_get_irq(pdev, 0);
        if (chip->irq < 0)
                return chip->irq;
@@ -733,7 +786,10 @@ static int sun6i_rtc_probe(struct platform_device *pdev)
                return PTR_ERR(chip->rtc);
 
        chip->rtc->ops = &sun6i_rtc_ops;
-       chip->rtc->range_max = 2019686399LL; /* 2033-12-31 23:59:59 */
+       if (chip->flags & RTC_LINEAR_DAY)
+               chip->rtc->range_max = (65536 * SECS_PER_DAY) - 1;
+       else
+               chip->rtc->range_max = 2019686399LL; /* 2033-12-31 23:59:59 */
 
        ret = devm_rtc_register_device(chip->rtc);
        if (ret)
@@ -758,6 +814,8 @@ static const struct of_device_id sun6i_rtc_dt_ids[] = {
        { .compatible = "allwinner,sun8i-v3-rtc" },
        { .compatible = "allwinner,sun50i-h5-rtc" },
        { .compatible = "allwinner,sun50i-h6-rtc" },
+       { .compatible = "allwinner,sun50i-h616-rtc",
+               .data = (void *)RTC_LINEAR_DAY },
        { /* sentinel */ },
 };
 MODULE_DEVICE_TABLE(of, sun6i_rtc_dt_ids);