19765d2d9SChen-Yu Tsai /* 29765d2d9SChen-Yu Tsai * An RTC driver for Allwinner A31/A23 39765d2d9SChen-Yu Tsai * 49765d2d9SChen-Yu Tsai * Copyright (c) 2014, Chen-Yu Tsai <wens@csie.org> 59765d2d9SChen-Yu Tsai * 69765d2d9SChen-Yu Tsai * based on rtc-sunxi.c 79765d2d9SChen-Yu Tsai * 89765d2d9SChen-Yu Tsai * An RTC driver for Allwinner A10/A20 99765d2d9SChen-Yu Tsai * 109765d2d9SChen-Yu Tsai * Copyright (c) 2013, Carlo Caione <carlo.caione@gmail.com> 119765d2d9SChen-Yu Tsai * 129765d2d9SChen-Yu Tsai * This program is free software; you can redistribute it and/or modify 139765d2d9SChen-Yu Tsai * it under the terms of the GNU General Public License as published by 149765d2d9SChen-Yu Tsai * the Free Software Foundation; either version 2 of the License, or 159765d2d9SChen-Yu Tsai * (at your option) any later version. 169765d2d9SChen-Yu Tsai * 179765d2d9SChen-Yu Tsai * This program is distributed in the hope that it will be useful, but WITHOUT 189765d2d9SChen-Yu Tsai * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 199765d2d9SChen-Yu Tsai * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 209765d2d9SChen-Yu Tsai * more details. 219765d2d9SChen-Yu Tsai */ 229765d2d9SChen-Yu Tsai 233855c2c3SMaxime Ripard #include <linux/clk.h> 243855c2c3SMaxime Ripard #include <linux/clk-provider.h> 259765d2d9SChen-Yu Tsai #include <linux/delay.h> 269765d2d9SChen-Yu Tsai #include <linux/err.h> 279765d2d9SChen-Yu Tsai #include <linux/fs.h> 289765d2d9SChen-Yu Tsai #include <linux/init.h> 299765d2d9SChen-Yu Tsai #include <linux/interrupt.h> 309765d2d9SChen-Yu Tsai #include <linux/io.h> 319765d2d9SChen-Yu Tsai #include <linux/kernel.h> 329765d2d9SChen-Yu Tsai #include <linux/module.h> 339765d2d9SChen-Yu Tsai #include <linux/of.h> 349765d2d9SChen-Yu Tsai #include <linux/of_address.h> 359765d2d9SChen-Yu Tsai #include <linux/of_device.h> 369765d2d9SChen-Yu Tsai #include <linux/platform_device.h> 379765d2d9SChen-Yu Tsai #include <linux/rtc.h> 383855c2c3SMaxime Ripard #include <linux/slab.h> 399765d2d9SChen-Yu Tsai #include <linux/types.h> 409765d2d9SChen-Yu Tsai 419765d2d9SChen-Yu Tsai /* Control register */ 429765d2d9SChen-Yu Tsai #define SUN6I_LOSC_CTRL 0x0000 43fb61bb82SMaxime Ripard #define SUN6I_LOSC_CTRL_KEY (0x16aa << 16) 449765d2d9SChen-Yu Tsai #define SUN6I_LOSC_CTRL_ALM_DHMS_ACC BIT(9) 459765d2d9SChen-Yu Tsai #define SUN6I_LOSC_CTRL_RTC_HMS_ACC BIT(8) 469765d2d9SChen-Yu Tsai #define SUN6I_LOSC_CTRL_RTC_YMD_ACC BIT(7) 47fb61bb82SMaxime Ripard #define SUN6I_LOSC_CTRL_EXT_OSC BIT(0) 489765d2d9SChen-Yu Tsai #define SUN6I_LOSC_CTRL_ACC_MASK GENMASK(9, 7) 499765d2d9SChen-Yu Tsai 503855c2c3SMaxime Ripard #define SUN6I_LOSC_CLK_PRESCAL 0x0008 513855c2c3SMaxime Ripard 529765d2d9SChen-Yu Tsai /* RTC */ 539765d2d9SChen-Yu Tsai #define SUN6I_RTC_YMD 0x0010 549765d2d9SChen-Yu Tsai #define SUN6I_RTC_HMS 0x0014 559765d2d9SChen-Yu Tsai 569765d2d9SChen-Yu Tsai /* Alarm 0 (counter) */ 579765d2d9SChen-Yu Tsai #define SUN6I_ALRM_COUNTER 0x0020 589765d2d9SChen-Yu Tsai #define SUN6I_ALRM_CUR_VAL 0x0024 599765d2d9SChen-Yu Tsai #define SUN6I_ALRM_EN 0x0028 609765d2d9SChen-Yu Tsai #define SUN6I_ALRM_EN_CNT_EN BIT(0) 619765d2d9SChen-Yu Tsai #define SUN6I_ALRM_IRQ_EN 0x002c 629765d2d9SChen-Yu Tsai #define SUN6I_ALRM_IRQ_EN_CNT_IRQ_EN BIT(0) 639765d2d9SChen-Yu Tsai #define SUN6I_ALRM_IRQ_STA 0x0030 649765d2d9SChen-Yu Tsai #define SUN6I_ALRM_IRQ_STA_CNT_IRQ_PEND BIT(0) 659765d2d9SChen-Yu Tsai 669765d2d9SChen-Yu Tsai /* Alarm 1 (wall clock) */ 679765d2d9SChen-Yu Tsai #define SUN6I_ALRM1_EN 0x0044 689765d2d9SChen-Yu Tsai #define SUN6I_ALRM1_IRQ_EN 0x0048 699765d2d9SChen-Yu Tsai #define SUN6I_ALRM1_IRQ_STA 0x004c 709765d2d9SChen-Yu Tsai #define SUN6I_ALRM1_IRQ_STA_WEEK_IRQ_PEND BIT(0) 719765d2d9SChen-Yu Tsai 729765d2d9SChen-Yu Tsai /* Alarm config */ 739765d2d9SChen-Yu Tsai #define SUN6I_ALARM_CONFIG 0x0050 749765d2d9SChen-Yu Tsai #define SUN6I_ALARM_CONFIG_WAKEUP BIT(0) 759765d2d9SChen-Yu Tsai 7617ecd246SMaxime Ripard #define SUN6I_LOSC_OUT_GATING 0x0060 77*09018d4bSMichael Trimarchi #define SUN6I_LOSC_OUT_GATING_EN_OFFSET 0 7817ecd246SMaxime Ripard 799765d2d9SChen-Yu Tsai /* 809765d2d9SChen-Yu Tsai * Get date values 819765d2d9SChen-Yu Tsai */ 829765d2d9SChen-Yu Tsai #define SUN6I_DATE_GET_DAY_VALUE(x) ((x) & 0x0000001f) 839765d2d9SChen-Yu Tsai #define SUN6I_DATE_GET_MON_VALUE(x) (((x) & 0x00000f00) >> 8) 849765d2d9SChen-Yu Tsai #define SUN6I_DATE_GET_YEAR_VALUE(x) (((x) & 0x003f0000) >> 16) 859765d2d9SChen-Yu Tsai #define SUN6I_LEAP_GET_VALUE(x) (((x) & 0x00400000) >> 22) 869765d2d9SChen-Yu Tsai 879765d2d9SChen-Yu Tsai /* 889765d2d9SChen-Yu Tsai * Get time values 899765d2d9SChen-Yu Tsai */ 909765d2d9SChen-Yu Tsai #define SUN6I_TIME_GET_SEC_VALUE(x) ((x) & 0x0000003f) 919765d2d9SChen-Yu Tsai #define SUN6I_TIME_GET_MIN_VALUE(x) (((x) & 0x00003f00) >> 8) 929765d2d9SChen-Yu Tsai #define SUN6I_TIME_GET_HOUR_VALUE(x) (((x) & 0x001f0000) >> 16) 939765d2d9SChen-Yu Tsai 949765d2d9SChen-Yu Tsai /* 959765d2d9SChen-Yu Tsai * Set date values 969765d2d9SChen-Yu Tsai */ 979765d2d9SChen-Yu Tsai #define SUN6I_DATE_SET_DAY_VALUE(x) ((x) & 0x0000001f) 989765d2d9SChen-Yu Tsai #define SUN6I_DATE_SET_MON_VALUE(x) ((x) << 8 & 0x00000f00) 999765d2d9SChen-Yu Tsai #define SUN6I_DATE_SET_YEAR_VALUE(x) ((x) << 16 & 0x003f0000) 1009765d2d9SChen-Yu Tsai #define SUN6I_LEAP_SET_VALUE(x) ((x) << 22 & 0x00400000) 1019765d2d9SChen-Yu Tsai 1029765d2d9SChen-Yu Tsai /* 1039765d2d9SChen-Yu Tsai * Set time values 1049765d2d9SChen-Yu Tsai */ 1059765d2d9SChen-Yu Tsai #define SUN6I_TIME_SET_SEC_VALUE(x) ((x) & 0x0000003f) 1069765d2d9SChen-Yu Tsai #define SUN6I_TIME_SET_MIN_VALUE(x) ((x) << 8 & 0x00003f00) 1079765d2d9SChen-Yu Tsai #define SUN6I_TIME_SET_HOUR_VALUE(x) ((x) << 16 & 0x001f0000) 1089765d2d9SChen-Yu Tsai 1099765d2d9SChen-Yu Tsai /* 1109765d2d9SChen-Yu Tsai * The year parameter passed to the driver is usually an offset relative to 1119765d2d9SChen-Yu Tsai * the year 1900. This macro is used to convert this offset to another one 1129765d2d9SChen-Yu Tsai * relative to the minimum year allowed by the hardware. 1139765d2d9SChen-Yu Tsai * 1149765d2d9SChen-Yu Tsai * The year range is 1970 - 2033. This range is selected to match Allwinner's 1159765d2d9SChen-Yu Tsai * driver, even though it is somewhat limited. 1169765d2d9SChen-Yu Tsai */ 1179765d2d9SChen-Yu Tsai #define SUN6I_YEAR_MIN 1970 1189765d2d9SChen-Yu Tsai #define SUN6I_YEAR_MAX 2033 1199765d2d9SChen-Yu Tsai #define SUN6I_YEAR_OFF (SUN6I_YEAR_MIN - 1900) 1209765d2d9SChen-Yu Tsai 1219765d2d9SChen-Yu Tsai struct sun6i_rtc_dev { 1229765d2d9SChen-Yu Tsai struct rtc_device *rtc; 1239765d2d9SChen-Yu Tsai struct device *dev; 1249765d2d9SChen-Yu Tsai void __iomem *base; 1259765d2d9SChen-Yu Tsai int irq; 1269765d2d9SChen-Yu Tsai unsigned long alarm; 127a9422a19SMaxime Ripard 1283855c2c3SMaxime Ripard struct clk_hw hw; 1293855c2c3SMaxime Ripard struct clk_hw *int_osc; 1303855c2c3SMaxime Ripard struct clk *losc; 13117ecd246SMaxime Ripard struct clk *ext_losc; 1323855c2c3SMaxime Ripard 133a9422a19SMaxime Ripard spinlock_t lock; 1349765d2d9SChen-Yu Tsai }; 1359765d2d9SChen-Yu Tsai 1363855c2c3SMaxime Ripard static struct sun6i_rtc_dev *sun6i_rtc; 1373855c2c3SMaxime Ripard 1383855c2c3SMaxime Ripard static unsigned long sun6i_rtc_osc_recalc_rate(struct clk_hw *hw, 1393855c2c3SMaxime Ripard unsigned long parent_rate) 1403855c2c3SMaxime Ripard { 1413855c2c3SMaxime Ripard struct sun6i_rtc_dev *rtc = container_of(hw, struct sun6i_rtc_dev, hw); 1423855c2c3SMaxime Ripard u32 val; 1433855c2c3SMaxime Ripard 1443855c2c3SMaxime Ripard val = readl(rtc->base + SUN6I_LOSC_CTRL); 1453855c2c3SMaxime Ripard if (val & SUN6I_LOSC_CTRL_EXT_OSC) 1463855c2c3SMaxime Ripard return parent_rate; 1473855c2c3SMaxime Ripard 1483855c2c3SMaxime Ripard val = readl(rtc->base + SUN6I_LOSC_CLK_PRESCAL); 1493855c2c3SMaxime Ripard val &= GENMASK(4, 0); 1503855c2c3SMaxime Ripard 1513855c2c3SMaxime Ripard return parent_rate / (val + 1); 1523855c2c3SMaxime Ripard } 1533855c2c3SMaxime Ripard 1543855c2c3SMaxime Ripard static u8 sun6i_rtc_osc_get_parent(struct clk_hw *hw) 1553855c2c3SMaxime Ripard { 1563855c2c3SMaxime Ripard struct sun6i_rtc_dev *rtc = container_of(hw, struct sun6i_rtc_dev, hw); 1573855c2c3SMaxime Ripard 1583855c2c3SMaxime Ripard return readl(rtc->base + SUN6I_LOSC_CTRL) & SUN6I_LOSC_CTRL_EXT_OSC; 1593855c2c3SMaxime Ripard } 1603855c2c3SMaxime Ripard 1613855c2c3SMaxime Ripard static int sun6i_rtc_osc_set_parent(struct clk_hw *hw, u8 index) 1623855c2c3SMaxime Ripard { 1633855c2c3SMaxime Ripard struct sun6i_rtc_dev *rtc = container_of(hw, struct sun6i_rtc_dev, hw); 1643855c2c3SMaxime Ripard unsigned long flags; 1653855c2c3SMaxime Ripard u32 val; 1663855c2c3SMaxime Ripard 1673855c2c3SMaxime Ripard if (index > 1) 1683855c2c3SMaxime Ripard return -EINVAL; 1693855c2c3SMaxime Ripard 1703855c2c3SMaxime Ripard spin_lock_irqsave(&rtc->lock, flags); 1713855c2c3SMaxime Ripard val = readl(rtc->base + SUN6I_LOSC_CTRL); 1723855c2c3SMaxime Ripard val &= ~SUN6I_LOSC_CTRL_EXT_OSC; 1733855c2c3SMaxime Ripard val |= SUN6I_LOSC_CTRL_KEY; 1743855c2c3SMaxime Ripard val |= index ? SUN6I_LOSC_CTRL_EXT_OSC : 0; 1753855c2c3SMaxime Ripard writel(val, rtc->base + SUN6I_LOSC_CTRL); 1763855c2c3SMaxime Ripard spin_unlock_irqrestore(&rtc->lock, flags); 1773855c2c3SMaxime Ripard 1783855c2c3SMaxime Ripard return 0; 1793855c2c3SMaxime Ripard } 1803855c2c3SMaxime Ripard 1813855c2c3SMaxime Ripard static const struct clk_ops sun6i_rtc_osc_ops = { 1823855c2c3SMaxime Ripard .recalc_rate = sun6i_rtc_osc_recalc_rate, 1833855c2c3SMaxime Ripard 1843855c2c3SMaxime Ripard .get_parent = sun6i_rtc_osc_get_parent, 1853855c2c3SMaxime Ripard .set_parent = sun6i_rtc_osc_set_parent, 1863855c2c3SMaxime Ripard }; 1873855c2c3SMaxime Ripard 1883855c2c3SMaxime Ripard static void __init sun6i_rtc_clk_init(struct device_node *node) 1893855c2c3SMaxime Ripard { 1903855c2c3SMaxime Ripard struct clk_hw_onecell_data *clk_data; 1913855c2c3SMaxime Ripard struct sun6i_rtc_dev *rtc; 1923855c2c3SMaxime Ripard struct clk_init_data init = { 1933855c2c3SMaxime Ripard .ops = &sun6i_rtc_osc_ops, 1943855c2c3SMaxime Ripard }; 19517ecd246SMaxime Ripard const char *clkout_name = "osc32k-out"; 1963855c2c3SMaxime Ripard const char *parents[2]; 1973855c2c3SMaxime Ripard 1983855c2c3SMaxime Ripard rtc = kzalloc(sizeof(*rtc), GFP_KERNEL); 1993855c2c3SMaxime Ripard if (!rtc) 2003855c2c3SMaxime Ripard return; 2013855c2c3SMaxime Ripard 20217ecd246SMaxime Ripard clk_data = kzalloc(sizeof(*clk_data) + (sizeof(*clk_data->hws) * 2), 2033855c2c3SMaxime Ripard GFP_KERNEL); 204e9982024SColin Ian King if (!clk_data) { 205e9982024SColin Ian King kfree(rtc); 2063855c2c3SMaxime Ripard return; 207e9982024SColin Ian King } 208319ff835SAlexey Klimov 2093855c2c3SMaxime Ripard spin_lock_init(&rtc->lock); 2103855c2c3SMaxime Ripard 2113855c2c3SMaxime Ripard rtc->base = of_io_request_and_map(node, 0, of_node_full_name(node)); 212aaa65a9cSWei Yongjun if (IS_ERR(rtc->base)) { 2133855c2c3SMaxime Ripard pr_crit("Can't map RTC registers"); 2141a37c348SColin Ian King goto err; 2153855c2c3SMaxime Ripard } 2163855c2c3SMaxime Ripard 2173855c2c3SMaxime Ripard /* Switch to the external, more precise, oscillator */ 2183855c2c3SMaxime Ripard writel(SUN6I_LOSC_CTRL_KEY | SUN6I_LOSC_CTRL_EXT_OSC, 2193855c2c3SMaxime Ripard rtc->base + SUN6I_LOSC_CTRL); 2203855c2c3SMaxime Ripard 22115829cf4SChen-Yu Tsai /* Yes, I know, this is ugly. */ 22215829cf4SChen-Yu Tsai sun6i_rtc = rtc; 22315829cf4SChen-Yu Tsai 2243855c2c3SMaxime Ripard /* Deal with old DTs */ 2253855c2c3SMaxime Ripard if (!of_get_property(node, "clocks", NULL)) 2261a37c348SColin Ian King goto err; 2273855c2c3SMaxime Ripard 2283855c2c3SMaxime Ripard rtc->int_osc = clk_hw_register_fixed_rate_with_accuracy(NULL, 2293855c2c3SMaxime Ripard "rtc-int-osc", 2303855c2c3SMaxime Ripard NULL, 0, 2313855c2c3SMaxime Ripard 667000, 2323855c2c3SMaxime Ripard 300000000); 2333855c2c3SMaxime Ripard if (IS_ERR(rtc->int_osc)) { 2343855c2c3SMaxime Ripard pr_crit("Couldn't register the internal oscillator\n"); 2353855c2c3SMaxime Ripard return; 2363855c2c3SMaxime Ripard } 2373855c2c3SMaxime Ripard 2383855c2c3SMaxime Ripard parents[0] = clk_hw_get_name(rtc->int_osc); 2393855c2c3SMaxime Ripard parents[1] = of_clk_get_parent_name(node, 0); 2403855c2c3SMaxime Ripard 2413855c2c3SMaxime Ripard rtc->hw.init = &init; 2423855c2c3SMaxime Ripard 2433855c2c3SMaxime Ripard init.parent_names = parents; 2443855c2c3SMaxime Ripard init.num_parents = of_clk_get_parent_count(node) + 1; 24517ecd246SMaxime Ripard of_property_read_string_index(node, "clock-output-names", 0, 24617ecd246SMaxime Ripard &init.name); 2473855c2c3SMaxime Ripard 2483855c2c3SMaxime Ripard rtc->losc = clk_register(NULL, &rtc->hw); 2493855c2c3SMaxime Ripard if (IS_ERR(rtc->losc)) { 2503855c2c3SMaxime Ripard pr_crit("Couldn't register the LOSC clock\n"); 2513855c2c3SMaxime Ripard return; 2523855c2c3SMaxime Ripard } 2533855c2c3SMaxime Ripard 25417ecd246SMaxime Ripard of_property_read_string_index(node, "clock-output-names", 1, 25517ecd246SMaxime Ripard &clkout_name); 25617ecd246SMaxime Ripard rtc->ext_losc = clk_register_gate(NULL, clkout_name, rtc->hw.init->name, 25717ecd246SMaxime Ripard 0, rtc->base + SUN6I_LOSC_OUT_GATING, 258*09018d4bSMichael Trimarchi SUN6I_LOSC_OUT_GATING_EN_OFFSET, 0, 25917ecd246SMaxime Ripard &rtc->lock); 26017ecd246SMaxime Ripard if (IS_ERR(rtc->ext_losc)) { 26117ecd246SMaxime Ripard pr_crit("Couldn't register the LOSC external gate\n"); 26217ecd246SMaxime Ripard return; 26317ecd246SMaxime Ripard } 26417ecd246SMaxime Ripard 26517ecd246SMaxime Ripard clk_data->num = 2; 2663855c2c3SMaxime Ripard clk_data->hws[0] = &rtc->hw; 26717ecd246SMaxime Ripard clk_data->hws[1] = __clk_get_hw(rtc->ext_losc); 2683855c2c3SMaxime Ripard of_clk_add_hw_provider(node, of_clk_hw_onecell_get, clk_data); 2691a37c348SColin Ian King return; 2701a37c348SColin Ian King 2711a37c348SColin Ian King err: 2721a37c348SColin Ian King kfree(clk_data); 2733855c2c3SMaxime Ripard } 2743855c2c3SMaxime Ripard CLK_OF_DECLARE_DRIVER(sun6i_rtc_clk, "allwinner,sun6i-a31-rtc", 2753855c2c3SMaxime Ripard sun6i_rtc_clk_init); 2763855c2c3SMaxime Ripard 2779765d2d9SChen-Yu Tsai static irqreturn_t sun6i_rtc_alarmirq(int irq, void *id) 2789765d2d9SChen-Yu Tsai { 2799765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = (struct sun6i_rtc_dev *) id; 280a9422a19SMaxime Ripard irqreturn_t ret = IRQ_NONE; 2819765d2d9SChen-Yu Tsai u32 val; 2829765d2d9SChen-Yu Tsai 283a9422a19SMaxime Ripard spin_lock(&chip->lock); 2849765d2d9SChen-Yu Tsai val = readl(chip->base + SUN6I_ALRM_IRQ_STA); 2859765d2d9SChen-Yu Tsai 2869765d2d9SChen-Yu Tsai if (val & SUN6I_ALRM_IRQ_STA_CNT_IRQ_PEND) { 2879765d2d9SChen-Yu Tsai val |= SUN6I_ALRM_IRQ_STA_CNT_IRQ_PEND; 2889765d2d9SChen-Yu Tsai writel(val, chip->base + SUN6I_ALRM_IRQ_STA); 2899765d2d9SChen-Yu Tsai 2909765d2d9SChen-Yu Tsai rtc_update_irq(chip->rtc, 1, RTC_AF | RTC_IRQF); 2919765d2d9SChen-Yu Tsai 292a9422a19SMaxime Ripard ret = IRQ_HANDLED; 2939765d2d9SChen-Yu Tsai } 294a9422a19SMaxime Ripard spin_unlock(&chip->lock); 2959765d2d9SChen-Yu Tsai 296a9422a19SMaxime Ripard return ret; 2979765d2d9SChen-Yu Tsai } 2989765d2d9SChen-Yu Tsai 2999765d2d9SChen-Yu Tsai static void sun6i_rtc_setaie(int to, struct sun6i_rtc_dev *chip) 3009765d2d9SChen-Yu Tsai { 3019765d2d9SChen-Yu Tsai u32 alrm_val = 0; 3029765d2d9SChen-Yu Tsai u32 alrm_irq_val = 0; 3039765d2d9SChen-Yu Tsai u32 alrm_wake_val = 0; 304a9422a19SMaxime Ripard unsigned long flags; 3059765d2d9SChen-Yu Tsai 3069765d2d9SChen-Yu Tsai if (to) { 3079765d2d9SChen-Yu Tsai alrm_val = SUN6I_ALRM_EN_CNT_EN; 3089765d2d9SChen-Yu Tsai alrm_irq_val = SUN6I_ALRM_IRQ_EN_CNT_IRQ_EN; 3099765d2d9SChen-Yu Tsai alrm_wake_val = SUN6I_ALARM_CONFIG_WAKEUP; 3109765d2d9SChen-Yu Tsai } else { 3119765d2d9SChen-Yu Tsai writel(SUN6I_ALRM_IRQ_STA_CNT_IRQ_PEND, 3129765d2d9SChen-Yu Tsai chip->base + SUN6I_ALRM_IRQ_STA); 3139765d2d9SChen-Yu Tsai } 3149765d2d9SChen-Yu Tsai 315a9422a19SMaxime Ripard spin_lock_irqsave(&chip->lock, flags); 3169765d2d9SChen-Yu Tsai writel(alrm_val, chip->base + SUN6I_ALRM_EN); 3179765d2d9SChen-Yu Tsai writel(alrm_irq_val, chip->base + SUN6I_ALRM_IRQ_EN); 3189765d2d9SChen-Yu Tsai writel(alrm_wake_val, chip->base + SUN6I_ALARM_CONFIG); 319a9422a19SMaxime Ripard spin_unlock_irqrestore(&chip->lock, flags); 3209765d2d9SChen-Yu Tsai } 3219765d2d9SChen-Yu Tsai 3229765d2d9SChen-Yu Tsai static int sun6i_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) 3239765d2d9SChen-Yu Tsai { 3249765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 3259765d2d9SChen-Yu Tsai u32 date, time; 3269765d2d9SChen-Yu Tsai 3279765d2d9SChen-Yu Tsai /* 3289765d2d9SChen-Yu Tsai * read again in case it changes 3299765d2d9SChen-Yu Tsai */ 3309765d2d9SChen-Yu Tsai do { 3319765d2d9SChen-Yu Tsai date = readl(chip->base + SUN6I_RTC_YMD); 3329765d2d9SChen-Yu Tsai time = readl(chip->base + SUN6I_RTC_HMS); 3339765d2d9SChen-Yu Tsai } while ((date != readl(chip->base + SUN6I_RTC_YMD)) || 3349765d2d9SChen-Yu Tsai (time != readl(chip->base + SUN6I_RTC_HMS))); 3359765d2d9SChen-Yu Tsai 3369765d2d9SChen-Yu Tsai rtc_tm->tm_sec = SUN6I_TIME_GET_SEC_VALUE(time); 3379765d2d9SChen-Yu Tsai rtc_tm->tm_min = SUN6I_TIME_GET_MIN_VALUE(time); 3389765d2d9SChen-Yu Tsai rtc_tm->tm_hour = SUN6I_TIME_GET_HOUR_VALUE(time); 3399765d2d9SChen-Yu Tsai 3409765d2d9SChen-Yu Tsai rtc_tm->tm_mday = SUN6I_DATE_GET_DAY_VALUE(date); 3419765d2d9SChen-Yu Tsai rtc_tm->tm_mon = SUN6I_DATE_GET_MON_VALUE(date); 3429765d2d9SChen-Yu Tsai rtc_tm->tm_year = SUN6I_DATE_GET_YEAR_VALUE(date); 3439765d2d9SChen-Yu Tsai 3449765d2d9SChen-Yu Tsai rtc_tm->tm_mon -= 1; 3459765d2d9SChen-Yu Tsai 3469765d2d9SChen-Yu Tsai /* 3479765d2d9SChen-Yu Tsai * switch from (data_year->min)-relative offset to 3489765d2d9SChen-Yu Tsai * a (1900)-relative one 3499765d2d9SChen-Yu Tsai */ 3509765d2d9SChen-Yu Tsai rtc_tm->tm_year += SUN6I_YEAR_OFF; 3519765d2d9SChen-Yu Tsai 35222652ba7SAlexandre Belloni return 0; 3539765d2d9SChen-Yu Tsai } 3549765d2d9SChen-Yu Tsai 3559765d2d9SChen-Yu Tsai static int sun6i_rtc_getalarm(struct device *dev, struct rtc_wkalrm *wkalrm) 3569765d2d9SChen-Yu Tsai { 3579765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 358a9422a19SMaxime Ripard unsigned long flags; 3599765d2d9SChen-Yu Tsai u32 alrm_st; 3609765d2d9SChen-Yu Tsai u32 alrm_en; 3619765d2d9SChen-Yu Tsai 362a9422a19SMaxime Ripard spin_lock_irqsave(&chip->lock, flags); 3639765d2d9SChen-Yu Tsai alrm_en = readl(chip->base + SUN6I_ALRM_IRQ_EN); 3649765d2d9SChen-Yu Tsai alrm_st = readl(chip->base + SUN6I_ALRM_IRQ_STA); 365a9422a19SMaxime Ripard spin_unlock_irqrestore(&chip->lock, flags); 366a9422a19SMaxime Ripard 3679765d2d9SChen-Yu Tsai wkalrm->enabled = !!(alrm_en & SUN6I_ALRM_EN_CNT_EN); 3689765d2d9SChen-Yu Tsai wkalrm->pending = !!(alrm_st & SUN6I_ALRM_EN_CNT_EN); 3699765d2d9SChen-Yu Tsai rtc_time_to_tm(chip->alarm, &wkalrm->time); 3709765d2d9SChen-Yu Tsai 3719765d2d9SChen-Yu Tsai return 0; 3729765d2d9SChen-Yu Tsai } 3739765d2d9SChen-Yu Tsai 3749765d2d9SChen-Yu Tsai static int sun6i_rtc_setalarm(struct device *dev, struct rtc_wkalrm *wkalrm) 3759765d2d9SChen-Yu Tsai { 3769765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 3779765d2d9SChen-Yu Tsai struct rtc_time *alrm_tm = &wkalrm->time; 3789765d2d9SChen-Yu Tsai struct rtc_time tm_now; 3799765d2d9SChen-Yu Tsai unsigned long time_now = 0; 3809765d2d9SChen-Yu Tsai unsigned long time_set = 0; 3819765d2d9SChen-Yu Tsai unsigned long time_gap = 0; 3829765d2d9SChen-Yu Tsai int ret = 0; 3839765d2d9SChen-Yu Tsai 3849765d2d9SChen-Yu Tsai ret = sun6i_rtc_gettime(dev, &tm_now); 3859765d2d9SChen-Yu Tsai if (ret < 0) { 3869765d2d9SChen-Yu Tsai dev_err(dev, "Error in getting time\n"); 3879765d2d9SChen-Yu Tsai return -EINVAL; 3889765d2d9SChen-Yu Tsai } 3899765d2d9SChen-Yu Tsai 3909765d2d9SChen-Yu Tsai rtc_tm_to_time(alrm_tm, &time_set); 3919765d2d9SChen-Yu Tsai rtc_tm_to_time(&tm_now, &time_now); 3929765d2d9SChen-Yu Tsai if (time_set <= time_now) { 3939765d2d9SChen-Yu Tsai dev_err(dev, "Date to set in the past\n"); 3949765d2d9SChen-Yu Tsai return -EINVAL; 3959765d2d9SChen-Yu Tsai } 3969765d2d9SChen-Yu Tsai 3979765d2d9SChen-Yu Tsai time_gap = time_set - time_now; 3989765d2d9SChen-Yu Tsai 3999765d2d9SChen-Yu Tsai if (time_gap > U32_MAX) { 4009765d2d9SChen-Yu Tsai dev_err(dev, "Date too far in the future\n"); 4019765d2d9SChen-Yu Tsai return -EINVAL; 4029765d2d9SChen-Yu Tsai } 4039765d2d9SChen-Yu Tsai 4049765d2d9SChen-Yu Tsai sun6i_rtc_setaie(0, chip); 4059765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM_COUNTER); 4069765d2d9SChen-Yu Tsai usleep_range(100, 300); 4079765d2d9SChen-Yu Tsai 4089765d2d9SChen-Yu Tsai writel(time_gap, chip->base + SUN6I_ALRM_COUNTER); 4099765d2d9SChen-Yu Tsai chip->alarm = time_set; 4109765d2d9SChen-Yu Tsai 4119765d2d9SChen-Yu Tsai sun6i_rtc_setaie(wkalrm->enabled, chip); 4129765d2d9SChen-Yu Tsai 4139765d2d9SChen-Yu Tsai return 0; 4149765d2d9SChen-Yu Tsai } 4159765d2d9SChen-Yu Tsai 4169765d2d9SChen-Yu Tsai static int sun6i_rtc_wait(struct sun6i_rtc_dev *chip, int offset, 4179765d2d9SChen-Yu Tsai unsigned int mask, unsigned int ms_timeout) 4189765d2d9SChen-Yu Tsai { 4199765d2d9SChen-Yu Tsai const unsigned long timeout = jiffies + msecs_to_jiffies(ms_timeout); 4209765d2d9SChen-Yu Tsai u32 reg; 4219765d2d9SChen-Yu Tsai 4229765d2d9SChen-Yu Tsai do { 4239765d2d9SChen-Yu Tsai reg = readl(chip->base + offset); 4249765d2d9SChen-Yu Tsai reg &= mask; 4259765d2d9SChen-Yu Tsai 4269765d2d9SChen-Yu Tsai if (!reg) 4279765d2d9SChen-Yu Tsai return 0; 4289765d2d9SChen-Yu Tsai 4299765d2d9SChen-Yu Tsai } while (time_before(jiffies, timeout)); 4309765d2d9SChen-Yu Tsai 4319765d2d9SChen-Yu Tsai return -ETIMEDOUT; 4329765d2d9SChen-Yu Tsai } 4339765d2d9SChen-Yu Tsai 4349765d2d9SChen-Yu Tsai static int sun6i_rtc_settime(struct device *dev, struct rtc_time *rtc_tm) 4359765d2d9SChen-Yu Tsai { 4369765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 4379765d2d9SChen-Yu Tsai u32 date = 0; 4389765d2d9SChen-Yu Tsai u32 time = 0; 4399765d2d9SChen-Yu Tsai int year; 4409765d2d9SChen-Yu Tsai 4419765d2d9SChen-Yu Tsai year = rtc_tm->tm_year + 1900; 4429765d2d9SChen-Yu Tsai if (year < SUN6I_YEAR_MIN || year > SUN6I_YEAR_MAX) { 4439765d2d9SChen-Yu Tsai dev_err(dev, "rtc only supports year in range %d - %d\n", 4449765d2d9SChen-Yu Tsai SUN6I_YEAR_MIN, SUN6I_YEAR_MAX); 4459765d2d9SChen-Yu Tsai return -EINVAL; 4469765d2d9SChen-Yu Tsai } 4479765d2d9SChen-Yu Tsai 4489765d2d9SChen-Yu Tsai rtc_tm->tm_year -= SUN6I_YEAR_OFF; 4499765d2d9SChen-Yu Tsai rtc_tm->tm_mon += 1; 4509765d2d9SChen-Yu Tsai 4519765d2d9SChen-Yu Tsai date = SUN6I_DATE_SET_DAY_VALUE(rtc_tm->tm_mday) | 4529765d2d9SChen-Yu Tsai SUN6I_DATE_SET_MON_VALUE(rtc_tm->tm_mon) | 4539765d2d9SChen-Yu Tsai SUN6I_DATE_SET_YEAR_VALUE(rtc_tm->tm_year); 4549765d2d9SChen-Yu Tsai 4559765d2d9SChen-Yu Tsai if (is_leap_year(year)) 4569765d2d9SChen-Yu Tsai date |= SUN6I_LEAP_SET_VALUE(1); 4579765d2d9SChen-Yu Tsai 4589765d2d9SChen-Yu Tsai time = SUN6I_TIME_SET_SEC_VALUE(rtc_tm->tm_sec) | 4599765d2d9SChen-Yu Tsai SUN6I_TIME_SET_MIN_VALUE(rtc_tm->tm_min) | 4609765d2d9SChen-Yu Tsai SUN6I_TIME_SET_HOUR_VALUE(rtc_tm->tm_hour); 4619765d2d9SChen-Yu Tsai 4629765d2d9SChen-Yu Tsai /* Check whether registers are writable */ 4639765d2d9SChen-Yu Tsai if (sun6i_rtc_wait(chip, SUN6I_LOSC_CTRL, 4649765d2d9SChen-Yu Tsai SUN6I_LOSC_CTRL_ACC_MASK, 50)) { 4659765d2d9SChen-Yu Tsai dev_err(dev, "rtc is still busy.\n"); 4669765d2d9SChen-Yu Tsai return -EBUSY; 4679765d2d9SChen-Yu Tsai } 4689765d2d9SChen-Yu Tsai 4699765d2d9SChen-Yu Tsai writel(time, chip->base + SUN6I_RTC_HMS); 4709765d2d9SChen-Yu Tsai 4719765d2d9SChen-Yu Tsai /* 4729765d2d9SChen-Yu Tsai * After writing the RTC HH-MM-SS register, the 4739765d2d9SChen-Yu Tsai * SUN6I_LOSC_CTRL_RTC_HMS_ACC bit is set and it will not 4749765d2d9SChen-Yu Tsai * be cleared until the real writing operation is finished 4759765d2d9SChen-Yu Tsai */ 4769765d2d9SChen-Yu Tsai 4779765d2d9SChen-Yu Tsai if (sun6i_rtc_wait(chip, SUN6I_LOSC_CTRL, 4789765d2d9SChen-Yu Tsai SUN6I_LOSC_CTRL_RTC_HMS_ACC, 50)) { 4799765d2d9SChen-Yu Tsai dev_err(dev, "Failed to set rtc time.\n"); 4809765d2d9SChen-Yu Tsai return -ETIMEDOUT; 4819765d2d9SChen-Yu Tsai } 4829765d2d9SChen-Yu Tsai 4839765d2d9SChen-Yu Tsai writel(date, chip->base + SUN6I_RTC_YMD); 4849765d2d9SChen-Yu Tsai 4859765d2d9SChen-Yu Tsai /* 4869765d2d9SChen-Yu Tsai * After writing the RTC YY-MM-DD register, the 4879765d2d9SChen-Yu Tsai * SUN6I_LOSC_CTRL_RTC_YMD_ACC bit is set and it will not 4889765d2d9SChen-Yu Tsai * be cleared until the real writing operation is finished 4899765d2d9SChen-Yu Tsai */ 4909765d2d9SChen-Yu Tsai 4919765d2d9SChen-Yu Tsai if (sun6i_rtc_wait(chip, SUN6I_LOSC_CTRL, 4929765d2d9SChen-Yu Tsai SUN6I_LOSC_CTRL_RTC_YMD_ACC, 50)) { 4939765d2d9SChen-Yu Tsai dev_err(dev, "Failed to set rtc time.\n"); 4949765d2d9SChen-Yu Tsai return -ETIMEDOUT; 4959765d2d9SChen-Yu Tsai } 4969765d2d9SChen-Yu Tsai 4979765d2d9SChen-Yu Tsai return 0; 4989765d2d9SChen-Yu Tsai } 4999765d2d9SChen-Yu Tsai 5009765d2d9SChen-Yu Tsai static int sun6i_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) 5019765d2d9SChen-Yu Tsai { 5029765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 5039765d2d9SChen-Yu Tsai 5049765d2d9SChen-Yu Tsai if (!enabled) 5059765d2d9SChen-Yu Tsai sun6i_rtc_setaie(enabled, chip); 5069765d2d9SChen-Yu Tsai 5079765d2d9SChen-Yu Tsai return 0; 5089765d2d9SChen-Yu Tsai } 5099765d2d9SChen-Yu Tsai 5109765d2d9SChen-Yu Tsai static const struct rtc_class_ops sun6i_rtc_ops = { 5119765d2d9SChen-Yu Tsai .read_time = sun6i_rtc_gettime, 5129765d2d9SChen-Yu Tsai .set_time = sun6i_rtc_settime, 5139765d2d9SChen-Yu Tsai .read_alarm = sun6i_rtc_getalarm, 5149765d2d9SChen-Yu Tsai .set_alarm = sun6i_rtc_setalarm, 5159765d2d9SChen-Yu Tsai .alarm_irq_enable = sun6i_rtc_alarm_irq_enable 5169765d2d9SChen-Yu Tsai }; 5179765d2d9SChen-Yu Tsai 5189765d2d9SChen-Yu Tsai static int sun6i_rtc_probe(struct platform_device *pdev) 5199765d2d9SChen-Yu Tsai { 5203855c2c3SMaxime Ripard struct sun6i_rtc_dev *chip = sun6i_rtc; 5219765d2d9SChen-Yu Tsai int ret; 5229765d2d9SChen-Yu Tsai 5239765d2d9SChen-Yu Tsai if (!chip) 5243855c2c3SMaxime Ripard return -ENODEV; 5259765d2d9SChen-Yu Tsai 5269765d2d9SChen-Yu Tsai platform_set_drvdata(pdev, chip); 5279765d2d9SChen-Yu Tsai chip->dev = &pdev->dev; 5289765d2d9SChen-Yu Tsai 5299765d2d9SChen-Yu Tsai chip->irq = platform_get_irq(pdev, 0); 5309765d2d9SChen-Yu Tsai if (chip->irq < 0) { 5319765d2d9SChen-Yu Tsai dev_err(&pdev->dev, "No IRQ resource\n"); 5329765d2d9SChen-Yu Tsai return chip->irq; 5339765d2d9SChen-Yu Tsai } 5349765d2d9SChen-Yu Tsai 5359765d2d9SChen-Yu Tsai ret = devm_request_irq(&pdev->dev, chip->irq, sun6i_rtc_alarmirq, 5369765d2d9SChen-Yu Tsai 0, dev_name(&pdev->dev), chip); 5379765d2d9SChen-Yu Tsai if (ret) { 5389765d2d9SChen-Yu Tsai dev_err(&pdev->dev, "Could not request IRQ\n"); 5399765d2d9SChen-Yu Tsai return ret; 5409765d2d9SChen-Yu Tsai } 5419765d2d9SChen-Yu Tsai 5429765d2d9SChen-Yu Tsai /* clear the alarm counter value */ 5439765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM_COUNTER); 5449765d2d9SChen-Yu Tsai 5459765d2d9SChen-Yu Tsai /* disable counter alarm */ 5469765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM_EN); 5479765d2d9SChen-Yu Tsai 5489765d2d9SChen-Yu Tsai /* disable counter alarm interrupt */ 5499765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM_IRQ_EN); 5509765d2d9SChen-Yu Tsai 5519765d2d9SChen-Yu Tsai /* disable week alarm */ 5529765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM1_EN); 5539765d2d9SChen-Yu Tsai 5549765d2d9SChen-Yu Tsai /* disable week alarm interrupt */ 5559765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM1_IRQ_EN); 5569765d2d9SChen-Yu Tsai 5579765d2d9SChen-Yu Tsai /* clear counter alarm pending interrupts */ 5589765d2d9SChen-Yu Tsai writel(SUN6I_ALRM_IRQ_STA_CNT_IRQ_PEND, 5599765d2d9SChen-Yu Tsai chip->base + SUN6I_ALRM_IRQ_STA); 5609765d2d9SChen-Yu Tsai 5619765d2d9SChen-Yu Tsai /* clear week alarm pending interrupts */ 5629765d2d9SChen-Yu Tsai writel(SUN6I_ALRM1_IRQ_STA_WEEK_IRQ_PEND, 5639765d2d9SChen-Yu Tsai chip->base + SUN6I_ALRM1_IRQ_STA); 5649765d2d9SChen-Yu Tsai 5659765d2d9SChen-Yu Tsai /* disable alarm wakeup */ 5669765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALARM_CONFIG); 5679765d2d9SChen-Yu Tsai 5683855c2c3SMaxime Ripard clk_prepare_enable(chip->losc); 569fb61bb82SMaxime Ripard 5705dff3a31SMaxime Ripard chip->rtc = devm_rtc_device_register(&pdev->dev, "rtc-sun6i", 5719765d2d9SChen-Yu Tsai &sun6i_rtc_ops, THIS_MODULE); 5729765d2d9SChen-Yu Tsai if (IS_ERR(chip->rtc)) { 5739765d2d9SChen-Yu Tsai dev_err(&pdev->dev, "unable to register device\n"); 5749765d2d9SChen-Yu Tsai return PTR_ERR(chip->rtc); 5759765d2d9SChen-Yu Tsai } 5769765d2d9SChen-Yu Tsai 5779765d2d9SChen-Yu Tsai dev_info(&pdev->dev, "RTC enabled\n"); 5789765d2d9SChen-Yu Tsai 5799765d2d9SChen-Yu Tsai return 0; 5809765d2d9SChen-Yu Tsai } 5819765d2d9SChen-Yu Tsai 5829765d2d9SChen-Yu Tsai static const struct of_device_id sun6i_rtc_dt_ids[] = { 5839765d2d9SChen-Yu Tsai { .compatible = "allwinner,sun6i-a31-rtc" }, 5849765d2d9SChen-Yu Tsai { /* sentinel */ }, 5859765d2d9SChen-Yu Tsai }; 5869765d2d9SChen-Yu Tsai MODULE_DEVICE_TABLE(of, sun6i_rtc_dt_ids); 5879765d2d9SChen-Yu Tsai 5889765d2d9SChen-Yu Tsai static struct platform_driver sun6i_rtc_driver = { 5899765d2d9SChen-Yu Tsai .probe = sun6i_rtc_probe, 5909765d2d9SChen-Yu Tsai .driver = { 5919765d2d9SChen-Yu Tsai .name = "sun6i-rtc", 5929765d2d9SChen-Yu Tsai .of_match_table = sun6i_rtc_dt_ids, 5939765d2d9SChen-Yu Tsai }, 5949765d2d9SChen-Yu Tsai }; 59537539414SMaxime Ripard builtin_platform_driver(sun6i_rtc_driver); 596