1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 29765d2d9SChen-Yu Tsai /* 39765d2d9SChen-Yu Tsai * An RTC driver for Allwinner A31/A23 49765d2d9SChen-Yu Tsai * 59765d2d9SChen-Yu Tsai * Copyright (c) 2014, Chen-Yu Tsai <wens@csie.org> 69765d2d9SChen-Yu Tsai * 79765d2d9SChen-Yu Tsai * based on rtc-sunxi.c 89765d2d9SChen-Yu Tsai * 99765d2d9SChen-Yu Tsai * An RTC driver for Allwinner A10/A20 109765d2d9SChen-Yu Tsai * 119765d2d9SChen-Yu Tsai * Copyright (c) 2013, Carlo Caione <carlo.caione@gmail.com> 129765d2d9SChen-Yu Tsai */ 139765d2d9SChen-Yu Tsai 143855c2c3SMaxime Ripard #include <linux/clk.h> 153855c2c3SMaxime Ripard #include <linux/clk-provider.h> 16d91612d7SSamuel Holland #include <linux/clk/sunxi-ng.h> 179765d2d9SChen-Yu Tsai #include <linux/delay.h> 189765d2d9SChen-Yu Tsai #include <linux/err.h> 199765d2d9SChen-Yu Tsai #include <linux/fs.h> 209765d2d9SChen-Yu Tsai #include <linux/init.h> 219765d2d9SChen-Yu Tsai #include <linux/interrupt.h> 229765d2d9SChen-Yu Tsai #include <linux/io.h> 239765d2d9SChen-Yu Tsai #include <linux/kernel.h> 249765d2d9SChen-Yu Tsai #include <linux/module.h> 259765d2d9SChen-Yu Tsai #include <linux/of.h> 269765d2d9SChen-Yu Tsai #include <linux/of_address.h> 279765d2d9SChen-Yu Tsai #include <linux/of_device.h> 289765d2d9SChen-Yu Tsai #include <linux/platform_device.h> 299765d2d9SChen-Yu Tsai #include <linux/rtc.h> 303855c2c3SMaxime Ripard #include <linux/slab.h> 319765d2d9SChen-Yu Tsai #include <linux/types.h> 329765d2d9SChen-Yu Tsai 339765d2d9SChen-Yu Tsai /* Control register */ 349765d2d9SChen-Yu Tsai #define SUN6I_LOSC_CTRL 0x0000 35fb61bb82SMaxime Ripard #define SUN6I_LOSC_CTRL_KEY (0x16aa << 16) 36b60ff2cfSOndrej Jirman #define SUN6I_LOSC_CTRL_AUTO_SWT_BYPASS BIT(15) 379765d2d9SChen-Yu Tsai #define SUN6I_LOSC_CTRL_ALM_DHMS_ACC BIT(9) 389765d2d9SChen-Yu Tsai #define SUN6I_LOSC_CTRL_RTC_HMS_ACC BIT(8) 399765d2d9SChen-Yu Tsai #define SUN6I_LOSC_CTRL_RTC_YMD_ACC BIT(7) 40b60ff2cfSOndrej Jirman #define SUN6I_LOSC_CTRL_EXT_LOSC_EN BIT(4) 41fb61bb82SMaxime Ripard #define SUN6I_LOSC_CTRL_EXT_OSC BIT(0) 429765d2d9SChen-Yu Tsai #define SUN6I_LOSC_CTRL_ACC_MASK GENMASK(9, 7) 439765d2d9SChen-Yu Tsai 443855c2c3SMaxime Ripard #define SUN6I_LOSC_CLK_PRESCAL 0x0008 453855c2c3SMaxime Ripard 469765d2d9SChen-Yu Tsai /* RTC */ 479765d2d9SChen-Yu Tsai #define SUN6I_RTC_YMD 0x0010 489765d2d9SChen-Yu Tsai #define SUN6I_RTC_HMS 0x0014 499765d2d9SChen-Yu Tsai 509765d2d9SChen-Yu Tsai /* Alarm 0 (counter) */ 519765d2d9SChen-Yu Tsai #define SUN6I_ALRM_COUNTER 0x0020 527878fec4SAndre Przywara /* This holds the remaining alarm seconds on older SoCs (current value) */ 537878fec4SAndre Przywara #define SUN6I_ALRM_COUNTER_HMS 0x0024 549765d2d9SChen-Yu Tsai #define SUN6I_ALRM_EN 0x0028 559765d2d9SChen-Yu Tsai #define SUN6I_ALRM_EN_CNT_EN BIT(0) 569765d2d9SChen-Yu Tsai #define SUN6I_ALRM_IRQ_EN 0x002c 579765d2d9SChen-Yu Tsai #define SUN6I_ALRM_IRQ_EN_CNT_IRQ_EN BIT(0) 589765d2d9SChen-Yu Tsai #define SUN6I_ALRM_IRQ_STA 0x0030 599765d2d9SChen-Yu Tsai #define SUN6I_ALRM_IRQ_STA_CNT_IRQ_PEND BIT(0) 609765d2d9SChen-Yu Tsai 619765d2d9SChen-Yu Tsai /* Alarm 1 (wall clock) */ 629765d2d9SChen-Yu Tsai #define SUN6I_ALRM1_EN 0x0044 639765d2d9SChen-Yu Tsai #define SUN6I_ALRM1_IRQ_EN 0x0048 649765d2d9SChen-Yu Tsai #define SUN6I_ALRM1_IRQ_STA 0x004c 659765d2d9SChen-Yu Tsai #define SUN6I_ALRM1_IRQ_STA_WEEK_IRQ_PEND BIT(0) 669765d2d9SChen-Yu Tsai 679765d2d9SChen-Yu Tsai /* Alarm config */ 689765d2d9SChen-Yu Tsai #define SUN6I_ALARM_CONFIG 0x0050 699765d2d9SChen-Yu Tsai #define SUN6I_ALARM_CONFIG_WAKEUP BIT(0) 709765d2d9SChen-Yu Tsai 7117ecd246SMaxime Ripard #define SUN6I_LOSC_OUT_GATING 0x0060 7209018d4bSMichael Trimarchi #define SUN6I_LOSC_OUT_GATING_EN_OFFSET 0 7317ecd246SMaxime Ripard 74581d6d8fSSamuel Holland /* General-purpose data */ 75581d6d8fSSamuel Holland #define SUN6I_GP_DATA 0x0100 76581d6d8fSSamuel Holland #define SUN6I_GP_DATA_SIZE 0x20 77581d6d8fSSamuel Holland 789765d2d9SChen-Yu Tsai /* 799765d2d9SChen-Yu Tsai * Get date values 809765d2d9SChen-Yu Tsai */ 819765d2d9SChen-Yu Tsai #define SUN6I_DATE_GET_DAY_VALUE(x) ((x) & 0x0000001f) 829765d2d9SChen-Yu Tsai #define SUN6I_DATE_GET_MON_VALUE(x) (((x) & 0x00000f00) >> 8) 839765d2d9SChen-Yu Tsai #define SUN6I_DATE_GET_YEAR_VALUE(x) (((x) & 0x003f0000) >> 16) 849765d2d9SChen-Yu Tsai #define SUN6I_LEAP_GET_VALUE(x) (((x) & 0x00400000) >> 22) 859765d2d9SChen-Yu Tsai 869765d2d9SChen-Yu Tsai /* 879765d2d9SChen-Yu Tsai * Get time values 889765d2d9SChen-Yu Tsai */ 899765d2d9SChen-Yu Tsai #define SUN6I_TIME_GET_SEC_VALUE(x) ((x) & 0x0000003f) 909765d2d9SChen-Yu Tsai #define SUN6I_TIME_GET_MIN_VALUE(x) (((x) & 0x00003f00) >> 8) 919765d2d9SChen-Yu Tsai #define SUN6I_TIME_GET_HOUR_VALUE(x) (((x) & 0x001f0000) >> 16) 929765d2d9SChen-Yu Tsai 939765d2d9SChen-Yu Tsai /* 949765d2d9SChen-Yu Tsai * Set date values 959765d2d9SChen-Yu Tsai */ 969765d2d9SChen-Yu Tsai #define SUN6I_DATE_SET_DAY_VALUE(x) ((x) & 0x0000001f) 979765d2d9SChen-Yu Tsai #define SUN6I_DATE_SET_MON_VALUE(x) ((x) << 8 & 0x00000f00) 989765d2d9SChen-Yu Tsai #define SUN6I_DATE_SET_YEAR_VALUE(x) ((x) << 16 & 0x003f0000) 999765d2d9SChen-Yu Tsai #define SUN6I_LEAP_SET_VALUE(x) ((x) << 22 & 0x00400000) 1009765d2d9SChen-Yu Tsai 1019765d2d9SChen-Yu Tsai /* 1029765d2d9SChen-Yu Tsai * Set time values 1039765d2d9SChen-Yu Tsai */ 1049765d2d9SChen-Yu Tsai #define SUN6I_TIME_SET_SEC_VALUE(x) ((x) & 0x0000003f) 1059765d2d9SChen-Yu Tsai #define SUN6I_TIME_SET_MIN_VALUE(x) ((x) << 8 & 0x00003f00) 1069765d2d9SChen-Yu Tsai #define SUN6I_TIME_SET_HOUR_VALUE(x) ((x) << 16 & 0x001f0000) 1079765d2d9SChen-Yu Tsai 1089765d2d9SChen-Yu Tsai /* 1099765d2d9SChen-Yu Tsai * The year parameter passed to the driver is usually an offset relative to 1109765d2d9SChen-Yu Tsai * the year 1900. This macro is used to convert this offset to another one 1119765d2d9SChen-Yu Tsai * relative to the minimum year allowed by the hardware. 1129765d2d9SChen-Yu Tsai * 1139765d2d9SChen-Yu Tsai * The year range is 1970 - 2033. This range is selected to match Allwinner's 1149765d2d9SChen-Yu Tsai * driver, even though it is somewhat limited. 1159765d2d9SChen-Yu Tsai */ 1169765d2d9SChen-Yu Tsai #define SUN6I_YEAR_MIN 1970 1179765d2d9SChen-Yu Tsai #define SUN6I_YEAR_OFF (SUN6I_YEAR_MIN - 1900) 1189765d2d9SChen-Yu Tsai 119648c151aSAndre Przywara #define SECS_PER_DAY (24 * 3600ULL) 120648c151aSAndre Przywara 121403a3c3dSChen-Yu Tsai /* 122403a3c3dSChen-Yu Tsai * There are other differences between models, including: 123403a3c3dSChen-Yu Tsai * 124403a3c3dSChen-Yu Tsai * - number of GPIO pins that can be configured to hold a certain level 125403a3c3dSChen-Yu Tsai * - crypto-key related registers (H5, H6) 126403a3c3dSChen-Yu Tsai * - boot process related (super standby, secondary processor entry address) 127403a3c3dSChen-Yu Tsai * registers (R40, H6) 128403a3c3dSChen-Yu Tsai * - SYS power domain controls (R40) 129403a3c3dSChen-Yu Tsai * - DCXO controls (H6) 130403a3c3dSChen-Yu Tsai * - RC oscillator calibration (H6) 131403a3c3dSChen-Yu Tsai * 132403a3c3dSChen-Yu Tsai * These functions are not covered by this driver. 133403a3c3dSChen-Yu Tsai */ 134403a3c3dSChen-Yu Tsai struct sun6i_rtc_clk_data { 135403a3c3dSChen-Yu Tsai unsigned long rc_osc_rate; 136403a3c3dSChen-Yu Tsai unsigned int fixed_prescaler : 16; 137403a3c3dSChen-Yu Tsai unsigned int has_prescaler : 1; 138403a3c3dSChen-Yu Tsai unsigned int has_out_clk : 1; 139b60ff2cfSOndrej Jirman unsigned int has_losc_en : 1; 140b60ff2cfSOndrej Jirman unsigned int has_auto_swt : 1; 141403a3c3dSChen-Yu Tsai }; 142403a3c3dSChen-Yu Tsai 143648c151aSAndre Przywara #define RTC_LINEAR_DAY BIT(0) 144648c151aSAndre Przywara 1459765d2d9SChen-Yu Tsai struct sun6i_rtc_dev { 1469765d2d9SChen-Yu Tsai struct rtc_device *rtc; 147403a3c3dSChen-Yu Tsai const struct sun6i_rtc_clk_data *data; 1489765d2d9SChen-Yu Tsai void __iomem *base; 1499765d2d9SChen-Yu Tsai int irq; 1509f6cd82eSAndre Przywara time64_t alarm; 151648c151aSAndre Przywara unsigned long flags; 152a9422a19SMaxime Ripard 1533855c2c3SMaxime Ripard struct clk_hw hw; 1543855c2c3SMaxime Ripard struct clk_hw *int_osc; 1553855c2c3SMaxime Ripard struct clk *losc; 15617ecd246SMaxime Ripard struct clk *ext_losc; 1573855c2c3SMaxime Ripard 158a9422a19SMaxime Ripard spinlock_t lock; 1599765d2d9SChen-Yu Tsai }; 1609765d2d9SChen-Yu Tsai 1613855c2c3SMaxime Ripard static struct sun6i_rtc_dev *sun6i_rtc; 1623855c2c3SMaxime Ripard 1633855c2c3SMaxime Ripard static unsigned long sun6i_rtc_osc_recalc_rate(struct clk_hw *hw, 1643855c2c3SMaxime Ripard unsigned long parent_rate) 1653855c2c3SMaxime Ripard { 1663855c2c3SMaxime Ripard struct sun6i_rtc_dev *rtc = container_of(hw, struct sun6i_rtc_dev, hw); 167403a3c3dSChen-Yu Tsai u32 val = 0; 1683855c2c3SMaxime Ripard 1693855c2c3SMaxime Ripard val = readl(rtc->base + SUN6I_LOSC_CTRL); 1703855c2c3SMaxime Ripard if (val & SUN6I_LOSC_CTRL_EXT_OSC) 1713855c2c3SMaxime Ripard return parent_rate; 1723855c2c3SMaxime Ripard 173403a3c3dSChen-Yu Tsai if (rtc->data->fixed_prescaler) 174403a3c3dSChen-Yu Tsai parent_rate /= rtc->data->fixed_prescaler; 175403a3c3dSChen-Yu Tsai 176403a3c3dSChen-Yu Tsai if (rtc->data->has_prescaler) { 1773855c2c3SMaxime Ripard val = readl(rtc->base + SUN6I_LOSC_CLK_PRESCAL); 1783855c2c3SMaxime Ripard val &= GENMASK(4, 0); 179403a3c3dSChen-Yu Tsai } 1803855c2c3SMaxime Ripard 1813855c2c3SMaxime Ripard return parent_rate / (val + 1); 1823855c2c3SMaxime Ripard } 1833855c2c3SMaxime Ripard 1843855c2c3SMaxime Ripard static u8 sun6i_rtc_osc_get_parent(struct clk_hw *hw) 1853855c2c3SMaxime Ripard { 1863855c2c3SMaxime Ripard struct sun6i_rtc_dev *rtc = container_of(hw, struct sun6i_rtc_dev, hw); 1873855c2c3SMaxime Ripard 1883855c2c3SMaxime Ripard return readl(rtc->base + SUN6I_LOSC_CTRL) & SUN6I_LOSC_CTRL_EXT_OSC; 1893855c2c3SMaxime Ripard } 1903855c2c3SMaxime Ripard 1913855c2c3SMaxime Ripard static int sun6i_rtc_osc_set_parent(struct clk_hw *hw, u8 index) 1923855c2c3SMaxime Ripard { 1933855c2c3SMaxime Ripard struct sun6i_rtc_dev *rtc = container_of(hw, struct sun6i_rtc_dev, hw); 1943855c2c3SMaxime Ripard unsigned long flags; 1953855c2c3SMaxime Ripard u32 val; 1963855c2c3SMaxime Ripard 1973855c2c3SMaxime Ripard if (index > 1) 1983855c2c3SMaxime Ripard return -EINVAL; 1993855c2c3SMaxime Ripard 2003855c2c3SMaxime Ripard spin_lock_irqsave(&rtc->lock, flags); 2013855c2c3SMaxime Ripard val = readl(rtc->base + SUN6I_LOSC_CTRL); 2023855c2c3SMaxime Ripard val &= ~SUN6I_LOSC_CTRL_EXT_OSC; 2033855c2c3SMaxime Ripard val |= SUN6I_LOSC_CTRL_KEY; 2043855c2c3SMaxime Ripard val |= index ? SUN6I_LOSC_CTRL_EXT_OSC : 0; 205b60ff2cfSOndrej Jirman if (rtc->data->has_losc_en) { 206b60ff2cfSOndrej Jirman val &= ~SUN6I_LOSC_CTRL_EXT_LOSC_EN; 207b60ff2cfSOndrej Jirman val |= index ? SUN6I_LOSC_CTRL_EXT_LOSC_EN : 0; 208b60ff2cfSOndrej Jirman } 2093855c2c3SMaxime Ripard writel(val, rtc->base + SUN6I_LOSC_CTRL); 2103855c2c3SMaxime Ripard spin_unlock_irqrestore(&rtc->lock, flags); 2113855c2c3SMaxime Ripard 2123855c2c3SMaxime Ripard return 0; 2133855c2c3SMaxime Ripard } 2143855c2c3SMaxime Ripard 2153855c2c3SMaxime Ripard static const struct clk_ops sun6i_rtc_osc_ops = { 2163855c2c3SMaxime Ripard .recalc_rate = sun6i_rtc_osc_recalc_rate, 2173855c2c3SMaxime Ripard 2183855c2c3SMaxime Ripard .get_parent = sun6i_rtc_osc_get_parent, 2193855c2c3SMaxime Ripard .set_parent = sun6i_rtc_osc_set_parent, 2203855c2c3SMaxime Ripard }; 2213855c2c3SMaxime Ripard 222403a3c3dSChen-Yu Tsai static void __init sun6i_rtc_clk_init(struct device_node *node, 223403a3c3dSChen-Yu Tsai const struct sun6i_rtc_clk_data *data) 2243855c2c3SMaxime Ripard { 2253855c2c3SMaxime Ripard struct clk_hw_onecell_data *clk_data; 2263855c2c3SMaxime Ripard struct sun6i_rtc_dev *rtc; 2273855c2c3SMaxime Ripard struct clk_init_data init = { 2283855c2c3SMaxime Ripard .ops = &sun6i_rtc_osc_ops, 229459b6ea0SChen-Yu Tsai .name = "losc", 2303855c2c3SMaxime Ripard }; 231c56afc18SChen-Yu Tsai const char *iosc_name = "rtc-int-osc"; 23217ecd246SMaxime Ripard const char *clkout_name = "osc32k-out"; 2333855c2c3SMaxime Ripard const char *parents[2]; 234b60ff2cfSOndrej Jirman u32 reg; 2353855c2c3SMaxime Ripard 2363855c2c3SMaxime Ripard rtc = kzalloc(sizeof(*rtc), GFP_KERNEL); 2373855c2c3SMaxime Ripard if (!rtc) 2383855c2c3SMaxime Ripard return; 2393855c2c3SMaxime Ripard 240403a3c3dSChen-Yu Tsai rtc->data = data; 241c56afc18SChen-Yu Tsai clk_data = kzalloc(struct_size(clk_data, hws, 3), GFP_KERNEL); 242e9982024SColin Ian King if (!clk_data) { 243e9982024SColin Ian King kfree(rtc); 2443855c2c3SMaxime Ripard return; 245e9982024SColin Ian King } 246319ff835SAlexey Klimov 2473855c2c3SMaxime Ripard spin_lock_init(&rtc->lock); 2483855c2c3SMaxime Ripard 2493855c2c3SMaxime Ripard rtc->base = of_io_request_and_map(node, 0, of_node_full_name(node)); 250aaa65a9cSWei Yongjun if (IS_ERR(rtc->base)) { 2513855c2c3SMaxime Ripard pr_crit("Can't map RTC registers"); 2521a37c348SColin Ian King goto err; 2533855c2c3SMaxime Ripard } 2543855c2c3SMaxime Ripard 255b60ff2cfSOndrej Jirman reg = SUN6I_LOSC_CTRL_KEY; 256b60ff2cfSOndrej Jirman if (rtc->data->has_auto_swt) { 257b60ff2cfSOndrej Jirman /* Bypass auto-switch to int osc, on ext losc failure */ 258b60ff2cfSOndrej Jirman reg |= SUN6I_LOSC_CTRL_AUTO_SWT_BYPASS; 259b60ff2cfSOndrej Jirman writel(reg, rtc->base + SUN6I_LOSC_CTRL); 260b60ff2cfSOndrej Jirman } 261b60ff2cfSOndrej Jirman 262ec98a875SJernej Skrabec /* Switch to the external, more precise, oscillator, if present */ 263*4d9890acSRob Herring if (of_property_present(node, "clocks")) { 264b60ff2cfSOndrej Jirman reg |= SUN6I_LOSC_CTRL_EXT_OSC; 265b60ff2cfSOndrej Jirman if (rtc->data->has_losc_en) 266b60ff2cfSOndrej Jirman reg |= SUN6I_LOSC_CTRL_EXT_LOSC_EN; 267ec98a875SJernej Skrabec } 268b60ff2cfSOndrej Jirman writel(reg, rtc->base + SUN6I_LOSC_CTRL); 2693855c2c3SMaxime Ripard 27015829cf4SChen-Yu Tsai /* Yes, I know, this is ugly. */ 27115829cf4SChen-Yu Tsai sun6i_rtc = rtc; 27215829cf4SChen-Yu Tsai 273c56afc18SChen-Yu Tsai of_property_read_string_index(node, "clock-output-names", 2, 274c56afc18SChen-Yu Tsai &iosc_name); 275c56afc18SChen-Yu Tsai 2763855c2c3SMaxime Ripard rtc->int_osc = clk_hw_register_fixed_rate_with_accuracy(NULL, 277c56afc18SChen-Yu Tsai iosc_name, 2783855c2c3SMaxime Ripard NULL, 0, 279403a3c3dSChen-Yu Tsai rtc->data->rc_osc_rate, 2803855c2c3SMaxime Ripard 300000000); 2813855c2c3SMaxime Ripard if (IS_ERR(rtc->int_osc)) { 2823855c2c3SMaxime Ripard pr_crit("Couldn't register the internal oscillator\n"); 28328d21191SDinghao Liu goto err; 2843855c2c3SMaxime Ripard } 2853855c2c3SMaxime Ripard 2863855c2c3SMaxime Ripard parents[0] = clk_hw_get_name(rtc->int_osc); 287ec98a875SJernej Skrabec /* If there is no external oscillator, this will be NULL and ... */ 2883855c2c3SMaxime Ripard parents[1] = of_clk_get_parent_name(node, 0); 2893855c2c3SMaxime Ripard 2903855c2c3SMaxime Ripard rtc->hw.init = &init; 2913855c2c3SMaxime Ripard 2923855c2c3SMaxime Ripard init.parent_names = parents; 293ec98a875SJernej Skrabec /* ... number of clock parents will be 1. */ 2943855c2c3SMaxime Ripard init.num_parents = of_clk_get_parent_count(node) + 1; 29517ecd246SMaxime Ripard of_property_read_string_index(node, "clock-output-names", 0, 29617ecd246SMaxime Ripard &init.name); 2973855c2c3SMaxime Ripard 2983855c2c3SMaxime Ripard rtc->losc = clk_register(NULL, &rtc->hw); 2993855c2c3SMaxime Ripard if (IS_ERR(rtc->losc)) { 3003855c2c3SMaxime Ripard pr_crit("Couldn't register the LOSC clock\n"); 30128d21191SDinghao Liu goto err_register; 3023855c2c3SMaxime Ripard } 3033855c2c3SMaxime Ripard 30417ecd246SMaxime Ripard of_property_read_string_index(node, "clock-output-names", 1, 30517ecd246SMaxime Ripard &clkout_name); 30621ef77deSStephen Boyd rtc->ext_losc = clk_register_gate(NULL, clkout_name, init.name, 30717ecd246SMaxime Ripard 0, rtc->base + SUN6I_LOSC_OUT_GATING, 30809018d4bSMichael Trimarchi SUN6I_LOSC_OUT_GATING_EN_OFFSET, 0, 30917ecd246SMaxime Ripard &rtc->lock); 31017ecd246SMaxime Ripard if (IS_ERR(rtc->ext_losc)) { 31117ecd246SMaxime Ripard pr_crit("Couldn't register the LOSC external gate\n"); 31228d21191SDinghao Liu goto err_register; 31317ecd246SMaxime Ripard } 31417ecd246SMaxime Ripard 315344f4030SSamuel Holland clk_data->num = 3; 3163855c2c3SMaxime Ripard clk_data->hws[0] = &rtc->hw; 31717ecd246SMaxime Ripard clk_data->hws[1] = __clk_get_hw(rtc->ext_losc); 318c56afc18SChen-Yu Tsai clk_data->hws[2] = rtc->int_osc; 3193855c2c3SMaxime Ripard of_clk_add_hw_provider(node, of_clk_hw_onecell_get, clk_data); 3201a37c348SColin Ian King return; 3211a37c348SColin Ian King 32228d21191SDinghao Liu err_register: 32328d21191SDinghao Liu clk_hw_unregister_fixed_rate(rtc->int_osc); 3241a37c348SColin Ian King err: 3251a37c348SColin Ian King kfree(clk_data); 3263855c2c3SMaxime Ripard } 327403a3c3dSChen-Yu Tsai 328403a3c3dSChen-Yu Tsai static const struct sun6i_rtc_clk_data sun6i_a31_rtc_data = { 329403a3c3dSChen-Yu Tsai .rc_osc_rate = 667000, /* datasheet says 600 ~ 700 KHz */ 330403a3c3dSChen-Yu Tsai .has_prescaler = 1, 331403a3c3dSChen-Yu Tsai }; 332403a3c3dSChen-Yu Tsai 333403a3c3dSChen-Yu Tsai static void __init sun6i_a31_rtc_clk_init(struct device_node *node) 334403a3c3dSChen-Yu Tsai { 335403a3c3dSChen-Yu Tsai sun6i_rtc_clk_init(node, &sun6i_a31_rtc_data); 336403a3c3dSChen-Yu Tsai } 337403a3c3dSChen-Yu Tsai CLK_OF_DECLARE_DRIVER(sun6i_a31_rtc_clk, "allwinner,sun6i-a31-rtc", 338403a3c3dSChen-Yu Tsai sun6i_a31_rtc_clk_init); 3393855c2c3SMaxime Ripard 3407cd1acaeSChen-Yu Tsai static const struct sun6i_rtc_clk_data sun8i_a23_rtc_data = { 3417cd1acaeSChen-Yu Tsai .rc_osc_rate = 667000, /* datasheet says 600 ~ 700 KHz */ 3427cd1acaeSChen-Yu Tsai .has_prescaler = 1, 3437cd1acaeSChen-Yu Tsai .has_out_clk = 1, 3447cd1acaeSChen-Yu Tsai }; 3457cd1acaeSChen-Yu Tsai 3467cd1acaeSChen-Yu Tsai static void __init sun8i_a23_rtc_clk_init(struct device_node *node) 3477cd1acaeSChen-Yu Tsai { 3487cd1acaeSChen-Yu Tsai sun6i_rtc_clk_init(node, &sun8i_a23_rtc_data); 3497cd1acaeSChen-Yu Tsai } 3507cd1acaeSChen-Yu Tsai CLK_OF_DECLARE_DRIVER(sun8i_a23_rtc_clk, "allwinner,sun8i-a23-rtc", 3517cd1acaeSChen-Yu Tsai sun8i_a23_rtc_clk_init); 3527cd1acaeSChen-Yu Tsai 3537cd1acaeSChen-Yu Tsai static const struct sun6i_rtc_clk_data sun8i_h3_rtc_data = { 3547cd1acaeSChen-Yu Tsai .rc_osc_rate = 16000000, 3557cd1acaeSChen-Yu Tsai .fixed_prescaler = 32, 3567cd1acaeSChen-Yu Tsai .has_prescaler = 1, 3577cd1acaeSChen-Yu Tsai .has_out_clk = 1, 3587cd1acaeSChen-Yu Tsai }; 3597cd1acaeSChen-Yu Tsai 3607cd1acaeSChen-Yu Tsai static void __init sun8i_h3_rtc_clk_init(struct device_node *node) 3617cd1acaeSChen-Yu Tsai { 3627cd1acaeSChen-Yu Tsai sun6i_rtc_clk_init(node, &sun8i_h3_rtc_data); 3637cd1acaeSChen-Yu Tsai } 3647cd1acaeSChen-Yu Tsai CLK_OF_DECLARE_DRIVER(sun8i_h3_rtc_clk, "allwinner,sun8i-h3-rtc", 3657cd1acaeSChen-Yu Tsai sun8i_h3_rtc_clk_init); 3667cd1acaeSChen-Yu Tsai /* As far as we are concerned, clocks for H5 are the same as H3 */ 3677cd1acaeSChen-Yu Tsai CLK_OF_DECLARE_DRIVER(sun50i_h5_rtc_clk, "allwinner,sun50i-h5-rtc", 3687cd1acaeSChen-Yu Tsai sun8i_h3_rtc_clk_init); 3697cd1acaeSChen-Yu Tsai 37060d9f050SJernej Skrabec static const struct sun6i_rtc_clk_data sun50i_h6_rtc_data = { 37160d9f050SJernej Skrabec .rc_osc_rate = 16000000, 37260d9f050SJernej Skrabec .fixed_prescaler = 32, 37360d9f050SJernej Skrabec .has_prescaler = 1, 37460d9f050SJernej Skrabec .has_out_clk = 1, 37560d9f050SJernej Skrabec .has_losc_en = 1, 37660d9f050SJernej Skrabec .has_auto_swt = 1, 37760d9f050SJernej Skrabec }; 37860d9f050SJernej Skrabec 37960d9f050SJernej Skrabec static void __init sun50i_h6_rtc_clk_init(struct device_node *node) 38060d9f050SJernej Skrabec { 38160d9f050SJernej Skrabec sun6i_rtc_clk_init(node, &sun50i_h6_rtc_data); 38260d9f050SJernej Skrabec } 38360d9f050SJernej Skrabec CLK_OF_DECLARE_DRIVER(sun50i_h6_rtc_clk, "allwinner,sun50i-h6-rtc", 38460d9f050SJernej Skrabec sun50i_h6_rtc_clk_init); 38560d9f050SJernej Skrabec 386111bf02bSChen-Yu Tsai /* 387111bf02bSChen-Yu Tsai * The R40 user manual is self-conflicting on whether the prescaler is 388111bf02bSChen-Yu Tsai * fixed or configurable. The clock diagram shows it as fixed, but there 389111bf02bSChen-Yu Tsai * is also a configurable divider in the RTC block. 390111bf02bSChen-Yu Tsai */ 391111bf02bSChen-Yu Tsai static const struct sun6i_rtc_clk_data sun8i_r40_rtc_data = { 392111bf02bSChen-Yu Tsai .rc_osc_rate = 16000000, 393111bf02bSChen-Yu Tsai .fixed_prescaler = 512, 394111bf02bSChen-Yu Tsai }; 395111bf02bSChen-Yu Tsai static void __init sun8i_r40_rtc_clk_init(struct device_node *node) 396111bf02bSChen-Yu Tsai { 397111bf02bSChen-Yu Tsai sun6i_rtc_clk_init(node, &sun8i_r40_rtc_data); 398111bf02bSChen-Yu Tsai } 399111bf02bSChen-Yu Tsai CLK_OF_DECLARE_DRIVER(sun8i_r40_rtc_clk, "allwinner,sun8i-r40-rtc", 400111bf02bSChen-Yu Tsai sun8i_r40_rtc_clk_init); 401111bf02bSChen-Yu Tsai 4027cd1acaeSChen-Yu Tsai static const struct sun6i_rtc_clk_data sun8i_v3_rtc_data = { 4037cd1acaeSChen-Yu Tsai .rc_osc_rate = 32000, 4047cd1acaeSChen-Yu Tsai .has_out_clk = 1, 4057cd1acaeSChen-Yu Tsai }; 4067cd1acaeSChen-Yu Tsai 4077cd1acaeSChen-Yu Tsai static void __init sun8i_v3_rtc_clk_init(struct device_node *node) 4087cd1acaeSChen-Yu Tsai { 4097cd1acaeSChen-Yu Tsai sun6i_rtc_clk_init(node, &sun8i_v3_rtc_data); 4107cd1acaeSChen-Yu Tsai } 4117cd1acaeSChen-Yu Tsai CLK_OF_DECLARE_DRIVER(sun8i_v3_rtc_clk, "allwinner,sun8i-v3-rtc", 4127cd1acaeSChen-Yu Tsai sun8i_v3_rtc_clk_init); 4137cd1acaeSChen-Yu Tsai 4149765d2d9SChen-Yu Tsai static irqreturn_t sun6i_rtc_alarmirq(int irq, void *id) 4159765d2d9SChen-Yu Tsai { 4169765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = (struct sun6i_rtc_dev *) id; 417a9422a19SMaxime Ripard irqreturn_t ret = IRQ_NONE; 4189765d2d9SChen-Yu Tsai u32 val; 4199765d2d9SChen-Yu Tsai 420a9422a19SMaxime Ripard spin_lock(&chip->lock); 4219765d2d9SChen-Yu Tsai val = readl(chip->base + SUN6I_ALRM_IRQ_STA); 4229765d2d9SChen-Yu Tsai 4239765d2d9SChen-Yu Tsai if (val & SUN6I_ALRM_IRQ_STA_CNT_IRQ_PEND) { 4249765d2d9SChen-Yu Tsai val |= SUN6I_ALRM_IRQ_STA_CNT_IRQ_PEND; 4259765d2d9SChen-Yu Tsai writel(val, chip->base + SUN6I_ALRM_IRQ_STA); 4269765d2d9SChen-Yu Tsai 4279765d2d9SChen-Yu Tsai rtc_update_irq(chip->rtc, 1, RTC_AF | RTC_IRQF); 4289765d2d9SChen-Yu Tsai 429a9422a19SMaxime Ripard ret = IRQ_HANDLED; 4309765d2d9SChen-Yu Tsai } 431a9422a19SMaxime Ripard spin_unlock(&chip->lock); 4329765d2d9SChen-Yu Tsai 433a9422a19SMaxime Ripard return ret; 4349765d2d9SChen-Yu Tsai } 4359765d2d9SChen-Yu Tsai 4369765d2d9SChen-Yu Tsai static void sun6i_rtc_setaie(int to, struct sun6i_rtc_dev *chip) 4379765d2d9SChen-Yu Tsai { 4389765d2d9SChen-Yu Tsai u32 alrm_val = 0; 4399765d2d9SChen-Yu Tsai u32 alrm_irq_val = 0; 4409765d2d9SChen-Yu Tsai u32 alrm_wake_val = 0; 441a9422a19SMaxime Ripard unsigned long flags; 4429765d2d9SChen-Yu Tsai 4439765d2d9SChen-Yu Tsai if (to) { 4449765d2d9SChen-Yu Tsai alrm_val = SUN6I_ALRM_EN_CNT_EN; 4459765d2d9SChen-Yu Tsai alrm_irq_val = SUN6I_ALRM_IRQ_EN_CNT_IRQ_EN; 4469765d2d9SChen-Yu Tsai alrm_wake_val = SUN6I_ALARM_CONFIG_WAKEUP; 4479765d2d9SChen-Yu Tsai } else { 4489765d2d9SChen-Yu Tsai writel(SUN6I_ALRM_IRQ_STA_CNT_IRQ_PEND, 4499765d2d9SChen-Yu Tsai chip->base + SUN6I_ALRM_IRQ_STA); 4509765d2d9SChen-Yu Tsai } 4519765d2d9SChen-Yu Tsai 452a9422a19SMaxime Ripard spin_lock_irqsave(&chip->lock, flags); 4539765d2d9SChen-Yu Tsai writel(alrm_val, chip->base + SUN6I_ALRM_EN); 4549765d2d9SChen-Yu Tsai writel(alrm_irq_val, chip->base + SUN6I_ALRM_IRQ_EN); 4559765d2d9SChen-Yu Tsai writel(alrm_wake_val, chip->base + SUN6I_ALARM_CONFIG); 456a9422a19SMaxime Ripard spin_unlock_irqrestore(&chip->lock, flags); 4579765d2d9SChen-Yu Tsai } 4589765d2d9SChen-Yu Tsai 4599765d2d9SChen-Yu Tsai static int sun6i_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) 4609765d2d9SChen-Yu Tsai { 4619765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 4629765d2d9SChen-Yu Tsai u32 date, time; 4639765d2d9SChen-Yu Tsai 4649765d2d9SChen-Yu Tsai /* 4659765d2d9SChen-Yu Tsai * read again in case it changes 4669765d2d9SChen-Yu Tsai */ 4679765d2d9SChen-Yu Tsai do { 4689765d2d9SChen-Yu Tsai date = readl(chip->base + SUN6I_RTC_YMD); 4699765d2d9SChen-Yu Tsai time = readl(chip->base + SUN6I_RTC_HMS); 4709765d2d9SChen-Yu Tsai } while ((date != readl(chip->base + SUN6I_RTC_YMD)) || 4719765d2d9SChen-Yu Tsai (time != readl(chip->base + SUN6I_RTC_HMS))); 4729765d2d9SChen-Yu Tsai 473648c151aSAndre Przywara if (chip->flags & RTC_LINEAR_DAY) { 474648c151aSAndre Przywara /* 475648c151aSAndre Przywara * Newer chips store a linear day number, the manual 476648c151aSAndre Przywara * does not mandate any epoch base. The BSP driver uses 477648c151aSAndre Przywara * the UNIX epoch, let's just copy that, as it's the 478648c151aSAndre Przywara * easiest anyway. 479648c151aSAndre Przywara */ 480648c151aSAndre Przywara rtc_time64_to_tm((date & 0xffff) * SECS_PER_DAY, rtc_tm); 481648c151aSAndre Przywara } else { 4829765d2d9SChen-Yu Tsai rtc_tm->tm_mday = SUN6I_DATE_GET_DAY_VALUE(date); 483648c151aSAndre Przywara rtc_tm->tm_mon = SUN6I_DATE_GET_MON_VALUE(date) - 1; 4849765d2d9SChen-Yu Tsai rtc_tm->tm_year = SUN6I_DATE_GET_YEAR_VALUE(date); 4859765d2d9SChen-Yu Tsai 4869765d2d9SChen-Yu Tsai /* 4879765d2d9SChen-Yu Tsai * switch from (data_year->min)-relative offset to 4889765d2d9SChen-Yu Tsai * a (1900)-relative one 4899765d2d9SChen-Yu Tsai */ 4909765d2d9SChen-Yu Tsai rtc_tm->tm_year += SUN6I_YEAR_OFF; 491648c151aSAndre Przywara } 492648c151aSAndre Przywara 493648c151aSAndre Przywara rtc_tm->tm_sec = SUN6I_TIME_GET_SEC_VALUE(time); 494648c151aSAndre Przywara rtc_tm->tm_min = SUN6I_TIME_GET_MIN_VALUE(time); 495648c151aSAndre Przywara rtc_tm->tm_hour = SUN6I_TIME_GET_HOUR_VALUE(time); 4969765d2d9SChen-Yu Tsai 49722652ba7SAlexandre Belloni return 0; 4989765d2d9SChen-Yu Tsai } 4999765d2d9SChen-Yu Tsai 5009765d2d9SChen-Yu Tsai static int sun6i_rtc_getalarm(struct device *dev, struct rtc_wkalrm *wkalrm) 5019765d2d9SChen-Yu Tsai { 5029765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 503a9422a19SMaxime Ripard unsigned long flags; 5049765d2d9SChen-Yu Tsai u32 alrm_st; 5059765d2d9SChen-Yu Tsai u32 alrm_en; 5069765d2d9SChen-Yu Tsai 507a9422a19SMaxime Ripard spin_lock_irqsave(&chip->lock, flags); 5089765d2d9SChen-Yu Tsai alrm_en = readl(chip->base + SUN6I_ALRM_IRQ_EN); 5099765d2d9SChen-Yu Tsai alrm_st = readl(chip->base + SUN6I_ALRM_IRQ_STA); 510a9422a19SMaxime Ripard spin_unlock_irqrestore(&chip->lock, flags); 511a9422a19SMaxime Ripard 5129765d2d9SChen-Yu Tsai wkalrm->enabled = !!(alrm_en & SUN6I_ALRM_EN_CNT_EN); 5139765d2d9SChen-Yu Tsai wkalrm->pending = !!(alrm_st & SUN6I_ALRM_EN_CNT_EN); 51499b7ac9cSAlexandre Belloni rtc_time64_to_tm(chip->alarm, &wkalrm->time); 5159765d2d9SChen-Yu Tsai 5169765d2d9SChen-Yu Tsai return 0; 5179765d2d9SChen-Yu Tsai } 5189765d2d9SChen-Yu Tsai 5199765d2d9SChen-Yu Tsai static int sun6i_rtc_setalarm(struct device *dev, struct rtc_wkalrm *wkalrm) 5209765d2d9SChen-Yu Tsai { 5219765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 5229765d2d9SChen-Yu Tsai struct rtc_time *alrm_tm = &wkalrm->time; 5239765d2d9SChen-Yu Tsai struct rtc_time tm_now; 5247878fec4SAndre Przywara time64_t time_set; 5257878fec4SAndre Przywara u32 counter_val, counter_val_hms; 5269f6cd82eSAndre Przywara int ret; 5279765d2d9SChen-Yu Tsai 5287878fec4SAndre Przywara time_set = rtc_tm_to_time64(alrm_tm); 5297878fec4SAndre Przywara 5307878fec4SAndre Przywara if (chip->flags & RTC_LINEAR_DAY) { 5317878fec4SAndre Przywara /* 5327878fec4SAndre Przywara * The alarm registers hold the actual alarm time, encoded 5337878fec4SAndre Przywara * in the same way (linear day + HMS) as the current time. 5347878fec4SAndre Przywara */ 5357878fec4SAndre Przywara counter_val_hms = SUN6I_TIME_SET_SEC_VALUE(alrm_tm->tm_sec) | 5367878fec4SAndre Przywara SUN6I_TIME_SET_MIN_VALUE(alrm_tm->tm_min) | 5377878fec4SAndre Przywara SUN6I_TIME_SET_HOUR_VALUE(alrm_tm->tm_hour); 5387878fec4SAndre Przywara /* The division will cut off the H:M:S part of alrm_tm. */ 5397878fec4SAndre Przywara counter_val = div_u64(rtc_tm_to_time64(alrm_tm), SECS_PER_DAY); 5407878fec4SAndre Przywara } else { 5417878fec4SAndre Przywara /* The alarm register holds the number of seconds left. */ 5427878fec4SAndre Przywara time64_t time_now; 5437878fec4SAndre Przywara 5449765d2d9SChen-Yu Tsai ret = sun6i_rtc_gettime(dev, &tm_now); 5459765d2d9SChen-Yu Tsai if (ret < 0) { 5469765d2d9SChen-Yu Tsai dev_err(dev, "Error in getting time\n"); 5479765d2d9SChen-Yu Tsai return -EINVAL; 5489765d2d9SChen-Yu Tsai } 5499765d2d9SChen-Yu Tsai 55099b7ac9cSAlexandre Belloni time_now = rtc_tm_to_time64(&tm_now); 5519765d2d9SChen-Yu Tsai if (time_set <= time_now) { 5529765d2d9SChen-Yu Tsai dev_err(dev, "Date to set in the past\n"); 5539765d2d9SChen-Yu Tsai return -EINVAL; 5549765d2d9SChen-Yu Tsai } 5559f6cd82eSAndre Przywara if ((time_set - time_now) > U32_MAX) { 5569765d2d9SChen-Yu Tsai dev_err(dev, "Date too far in the future\n"); 5579765d2d9SChen-Yu Tsai return -EINVAL; 5589765d2d9SChen-Yu Tsai } 5599765d2d9SChen-Yu Tsai 5607878fec4SAndre Przywara counter_val = time_set - time_now; 5617878fec4SAndre Przywara } 5627878fec4SAndre Przywara 5639765d2d9SChen-Yu Tsai sun6i_rtc_setaie(0, chip); 5649765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM_COUNTER); 5657878fec4SAndre Przywara if (chip->flags & RTC_LINEAR_DAY) 5667878fec4SAndre Przywara writel(0, chip->base + SUN6I_ALRM_COUNTER_HMS); 5679765d2d9SChen-Yu Tsai usleep_range(100, 300); 5689765d2d9SChen-Yu Tsai 5697878fec4SAndre Przywara writel(counter_val, chip->base + SUN6I_ALRM_COUNTER); 5707878fec4SAndre Przywara if (chip->flags & RTC_LINEAR_DAY) 5717878fec4SAndre Przywara writel(counter_val_hms, chip->base + SUN6I_ALRM_COUNTER_HMS); 5729765d2d9SChen-Yu Tsai chip->alarm = time_set; 5739765d2d9SChen-Yu Tsai 5749765d2d9SChen-Yu Tsai sun6i_rtc_setaie(wkalrm->enabled, chip); 5759765d2d9SChen-Yu Tsai 5769765d2d9SChen-Yu Tsai return 0; 5779765d2d9SChen-Yu Tsai } 5789765d2d9SChen-Yu Tsai 5799765d2d9SChen-Yu Tsai static int sun6i_rtc_wait(struct sun6i_rtc_dev *chip, int offset, 5809765d2d9SChen-Yu Tsai unsigned int mask, unsigned int ms_timeout) 5819765d2d9SChen-Yu Tsai { 5829765d2d9SChen-Yu Tsai const unsigned long timeout = jiffies + msecs_to_jiffies(ms_timeout); 5839765d2d9SChen-Yu Tsai u32 reg; 5849765d2d9SChen-Yu Tsai 5859765d2d9SChen-Yu Tsai do { 5869765d2d9SChen-Yu Tsai reg = readl(chip->base + offset); 5879765d2d9SChen-Yu Tsai reg &= mask; 5889765d2d9SChen-Yu Tsai 5899765d2d9SChen-Yu Tsai if (!reg) 5909765d2d9SChen-Yu Tsai return 0; 5919765d2d9SChen-Yu Tsai 5929765d2d9SChen-Yu Tsai } while (time_before(jiffies, timeout)); 5939765d2d9SChen-Yu Tsai 5949765d2d9SChen-Yu Tsai return -ETIMEDOUT; 5959765d2d9SChen-Yu Tsai } 5969765d2d9SChen-Yu Tsai 5979765d2d9SChen-Yu Tsai static int sun6i_rtc_settime(struct device *dev, struct rtc_time *rtc_tm) 5989765d2d9SChen-Yu Tsai { 5999765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 6009765d2d9SChen-Yu Tsai u32 date = 0; 6019765d2d9SChen-Yu Tsai u32 time = 0; 6029765d2d9SChen-Yu Tsai 603648c151aSAndre Przywara time = SUN6I_TIME_SET_SEC_VALUE(rtc_tm->tm_sec) | 604648c151aSAndre Przywara SUN6I_TIME_SET_MIN_VALUE(rtc_tm->tm_min) | 605648c151aSAndre Przywara SUN6I_TIME_SET_HOUR_VALUE(rtc_tm->tm_hour); 606648c151aSAndre Przywara 607648c151aSAndre Przywara if (chip->flags & RTC_LINEAR_DAY) { 608648c151aSAndre Przywara /* The division will cut off the H:M:S part of rtc_tm. */ 609648c151aSAndre Przywara date = div_u64(rtc_tm_to_time64(rtc_tm), SECS_PER_DAY); 610648c151aSAndre Przywara } else { 6119765d2d9SChen-Yu Tsai rtc_tm->tm_year -= SUN6I_YEAR_OFF; 6129765d2d9SChen-Yu Tsai rtc_tm->tm_mon += 1; 6139765d2d9SChen-Yu Tsai 6149765d2d9SChen-Yu Tsai date = SUN6I_DATE_SET_DAY_VALUE(rtc_tm->tm_mday) | 6159765d2d9SChen-Yu Tsai SUN6I_DATE_SET_MON_VALUE(rtc_tm->tm_mon) | 6169765d2d9SChen-Yu Tsai SUN6I_DATE_SET_YEAR_VALUE(rtc_tm->tm_year); 6179765d2d9SChen-Yu Tsai 6188ae79be7SAlexandre Belloni if (is_leap_year(rtc_tm->tm_year + SUN6I_YEAR_MIN)) 6199765d2d9SChen-Yu Tsai date |= SUN6I_LEAP_SET_VALUE(1); 620648c151aSAndre Przywara } 6219765d2d9SChen-Yu Tsai 6229765d2d9SChen-Yu Tsai /* Check whether registers are writable */ 6239765d2d9SChen-Yu Tsai if (sun6i_rtc_wait(chip, SUN6I_LOSC_CTRL, 6249765d2d9SChen-Yu Tsai SUN6I_LOSC_CTRL_ACC_MASK, 50)) { 6259765d2d9SChen-Yu Tsai dev_err(dev, "rtc is still busy.\n"); 6269765d2d9SChen-Yu Tsai return -EBUSY; 6279765d2d9SChen-Yu Tsai } 6289765d2d9SChen-Yu Tsai 6299765d2d9SChen-Yu Tsai writel(time, chip->base + SUN6I_RTC_HMS); 6309765d2d9SChen-Yu Tsai 6319765d2d9SChen-Yu Tsai /* 6329765d2d9SChen-Yu Tsai * After writing the RTC HH-MM-SS register, the 6339765d2d9SChen-Yu Tsai * SUN6I_LOSC_CTRL_RTC_HMS_ACC bit is set and it will not 6349765d2d9SChen-Yu Tsai * be cleared until the real writing operation is finished 6359765d2d9SChen-Yu Tsai */ 6369765d2d9SChen-Yu Tsai 6379765d2d9SChen-Yu Tsai if (sun6i_rtc_wait(chip, SUN6I_LOSC_CTRL, 6389765d2d9SChen-Yu Tsai SUN6I_LOSC_CTRL_RTC_HMS_ACC, 50)) { 6399765d2d9SChen-Yu Tsai dev_err(dev, "Failed to set rtc time.\n"); 6409765d2d9SChen-Yu Tsai return -ETIMEDOUT; 6419765d2d9SChen-Yu Tsai } 6429765d2d9SChen-Yu Tsai 6439765d2d9SChen-Yu Tsai writel(date, chip->base + SUN6I_RTC_YMD); 6449765d2d9SChen-Yu Tsai 6459765d2d9SChen-Yu Tsai /* 6469765d2d9SChen-Yu Tsai * After writing the RTC YY-MM-DD register, the 6479765d2d9SChen-Yu Tsai * SUN6I_LOSC_CTRL_RTC_YMD_ACC bit is set and it will not 6489765d2d9SChen-Yu Tsai * be cleared until the real writing operation is finished 6499765d2d9SChen-Yu Tsai */ 6509765d2d9SChen-Yu Tsai 6519765d2d9SChen-Yu Tsai if (sun6i_rtc_wait(chip, SUN6I_LOSC_CTRL, 6529765d2d9SChen-Yu Tsai SUN6I_LOSC_CTRL_RTC_YMD_ACC, 50)) { 6539765d2d9SChen-Yu Tsai dev_err(dev, "Failed to set rtc time.\n"); 6549765d2d9SChen-Yu Tsai return -ETIMEDOUT; 6559765d2d9SChen-Yu Tsai } 6569765d2d9SChen-Yu Tsai 6579765d2d9SChen-Yu Tsai return 0; 6589765d2d9SChen-Yu Tsai } 6599765d2d9SChen-Yu Tsai 6609765d2d9SChen-Yu Tsai static int sun6i_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) 6619765d2d9SChen-Yu Tsai { 6629765d2d9SChen-Yu Tsai struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 6639765d2d9SChen-Yu Tsai 6649765d2d9SChen-Yu Tsai if (!enabled) 6659765d2d9SChen-Yu Tsai sun6i_rtc_setaie(enabled, chip); 6669765d2d9SChen-Yu Tsai 6679765d2d9SChen-Yu Tsai return 0; 6689765d2d9SChen-Yu Tsai } 6699765d2d9SChen-Yu Tsai 6709765d2d9SChen-Yu Tsai static const struct rtc_class_ops sun6i_rtc_ops = { 6719765d2d9SChen-Yu Tsai .read_time = sun6i_rtc_gettime, 6729765d2d9SChen-Yu Tsai .set_time = sun6i_rtc_settime, 6739765d2d9SChen-Yu Tsai .read_alarm = sun6i_rtc_getalarm, 6749765d2d9SChen-Yu Tsai .set_alarm = sun6i_rtc_setalarm, 6759765d2d9SChen-Yu Tsai .alarm_irq_enable = sun6i_rtc_alarm_irq_enable 6769765d2d9SChen-Yu Tsai }; 6779765d2d9SChen-Yu Tsai 678581d6d8fSSamuel Holland static int sun6i_rtc_nvmem_read(void *priv, unsigned int offset, void *_val, size_t bytes) 679581d6d8fSSamuel Holland { 680581d6d8fSSamuel Holland struct sun6i_rtc_dev *chip = priv; 681581d6d8fSSamuel Holland u32 *val = _val; 682581d6d8fSSamuel Holland int i; 683581d6d8fSSamuel Holland 684581d6d8fSSamuel Holland for (i = 0; i < bytes / 4; ++i) 685581d6d8fSSamuel Holland val[i] = readl(chip->base + SUN6I_GP_DATA + offset + 4 * i); 686581d6d8fSSamuel Holland 687581d6d8fSSamuel Holland return 0; 688581d6d8fSSamuel Holland } 689581d6d8fSSamuel Holland 690581d6d8fSSamuel Holland static int sun6i_rtc_nvmem_write(void *priv, unsigned int offset, void *_val, size_t bytes) 691581d6d8fSSamuel Holland { 692581d6d8fSSamuel Holland struct sun6i_rtc_dev *chip = priv; 693581d6d8fSSamuel Holland u32 *val = _val; 694581d6d8fSSamuel Holland int i; 695581d6d8fSSamuel Holland 696581d6d8fSSamuel Holland for (i = 0; i < bytes / 4; ++i) 697581d6d8fSSamuel Holland writel(val[i], chip->base + SUN6I_GP_DATA + offset + 4 * i); 698581d6d8fSSamuel Holland 699581d6d8fSSamuel Holland return 0; 700581d6d8fSSamuel Holland } 701581d6d8fSSamuel Holland 702581d6d8fSSamuel Holland static struct nvmem_config sun6i_rtc_nvmem_cfg = { 703581d6d8fSSamuel Holland .type = NVMEM_TYPE_BATTERY_BACKED, 704581d6d8fSSamuel Holland .reg_read = sun6i_rtc_nvmem_read, 705581d6d8fSSamuel Holland .reg_write = sun6i_rtc_nvmem_write, 706581d6d8fSSamuel Holland .size = SUN6I_GP_DATA_SIZE, 707581d6d8fSSamuel Holland .word_size = 4, 708581d6d8fSSamuel Holland .stride = 4, 709581d6d8fSSamuel Holland }; 710581d6d8fSSamuel Holland 711d76a81d0SAlejandro González #ifdef CONFIG_PM_SLEEP 712d76a81d0SAlejandro González /* Enable IRQ wake on suspend, to wake up from RTC. */ 713d76a81d0SAlejandro González static int sun6i_rtc_suspend(struct device *dev) 714d76a81d0SAlejandro González { 715d76a81d0SAlejandro González struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 716d76a81d0SAlejandro González 717d76a81d0SAlejandro González if (device_may_wakeup(dev)) 718d76a81d0SAlejandro González enable_irq_wake(chip->irq); 719d76a81d0SAlejandro González 720d76a81d0SAlejandro González return 0; 721d76a81d0SAlejandro González } 722d76a81d0SAlejandro González 723d76a81d0SAlejandro González /* Disable IRQ wake on resume. */ 724d76a81d0SAlejandro González static int sun6i_rtc_resume(struct device *dev) 725d76a81d0SAlejandro González { 726d76a81d0SAlejandro González struct sun6i_rtc_dev *chip = dev_get_drvdata(dev); 727d76a81d0SAlejandro González 728d76a81d0SAlejandro González if (device_may_wakeup(dev)) 729d76a81d0SAlejandro González disable_irq_wake(chip->irq); 730d76a81d0SAlejandro González 731d76a81d0SAlejandro González return 0; 732d76a81d0SAlejandro González } 733d76a81d0SAlejandro González #endif 734d76a81d0SAlejandro González 735d76a81d0SAlejandro González static SIMPLE_DEV_PM_OPS(sun6i_rtc_pm_ops, 736d76a81d0SAlejandro González sun6i_rtc_suspend, sun6i_rtc_resume); 737d76a81d0SAlejandro González 7382ca03e29SSamuel Holland static void sun6i_rtc_bus_clk_cleanup(void *data) 7392ca03e29SSamuel Holland { 7402ca03e29SSamuel Holland struct clk *bus_clk = data; 7412ca03e29SSamuel Holland 7422ca03e29SSamuel Holland clk_disable_unprepare(bus_clk); 7432ca03e29SSamuel Holland } 7442ca03e29SSamuel Holland 7459765d2d9SChen-Yu Tsai static int sun6i_rtc_probe(struct platform_device *pdev) 7469765d2d9SChen-Yu Tsai { 7473855c2c3SMaxime Ripard struct sun6i_rtc_dev *chip = sun6i_rtc; 7482ca03e29SSamuel Holland struct device *dev = &pdev->dev; 7492ca03e29SSamuel Holland struct clk *bus_clk; 7509765d2d9SChen-Yu Tsai int ret; 7519765d2d9SChen-Yu Tsai 7522ca03e29SSamuel Holland bus_clk = devm_clk_get_optional(dev, "bus"); 7532ca03e29SSamuel Holland if (IS_ERR(bus_clk)) 7542ca03e29SSamuel Holland return PTR_ERR(bus_clk); 7552ca03e29SSamuel Holland 7562ca03e29SSamuel Holland if (bus_clk) { 7572ca03e29SSamuel Holland ret = clk_prepare_enable(bus_clk); 7582ca03e29SSamuel Holland if (ret) 7592ca03e29SSamuel Holland return ret; 7602ca03e29SSamuel Holland 7612ca03e29SSamuel Holland ret = devm_add_action_or_reset(dev, sun6i_rtc_bus_clk_cleanup, 7622ca03e29SSamuel Holland bus_clk); 7632ca03e29SSamuel Holland if (ret) 7642ca03e29SSamuel Holland return ret; 7652ca03e29SSamuel Holland } 7662ca03e29SSamuel Holland 767814691c7SSamuel Holland if (!chip) { 768814691c7SSamuel Holland chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); 7699765d2d9SChen-Yu Tsai if (!chip) 770814691c7SSamuel Holland return -ENOMEM; 771814691c7SSamuel Holland 772814691c7SSamuel Holland spin_lock_init(&chip->lock); 773814691c7SSamuel Holland 774814691c7SSamuel Holland chip->base = devm_platform_ioremap_resource(pdev, 0); 775814691c7SSamuel Holland if (IS_ERR(chip->base)) 776814691c7SSamuel Holland return PTR_ERR(chip->base); 777d91612d7SSamuel Holland 778d91612d7SSamuel Holland if (IS_REACHABLE(CONFIG_SUN6I_RTC_CCU)) { 779d91612d7SSamuel Holland ret = sun6i_rtc_ccu_probe(dev, chip->base); 780d91612d7SSamuel Holland if (ret) 781d91612d7SSamuel Holland return ret; 782d91612d7SSamuel Holland } 783814691c7SSamuel Holland } 7849765d2d9SChen-Yu Tsai 7859765d2d9SChen-Yu Tsai platform_set_drvdata(pdev, chip); 7869765d2d9SChen-Yu Tsai 787648c151aSAndre Przywara chip->flags = (unsigned long)of_device_get_match_data(&pdev->dev); 788648c151aSAndre Przywara 7899765d2d9SChen-Yu Tsai chip->irq = platform_get_irq(pdev, 0); 790faac9102SStephen Boyd if (chip->irq < 0) 7919765d2d9SChen-Yu Tsai return chip->irq; 7929765d2d9SChen-Yu Tsai 7939765d2d9SChen-Yu Tsai ret = devm_request_irq(&pdev->dev, chip->irq, sun6i_rtc_alarmirq, 7949765d2d9SChen-Yu Tsai 0, dev_name(&pdev->dev), chip); 7959765d2d9SChen-Yu Tsai if (ret) { 7969765d2d9SChen-Yu Tsai dev_err(&pdev->dev, "Could not request IRQ\n"); 7979765d2d9SChen-Yu Tsai return ret; 7989765d2d9SChen-Yu Tsai } 7999765d2d9SChen-Yu Tsai 8009765d2d9SChen-Yu Tsai /* clear the alarm counter value */ 8019765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM_COUNTER); 8029765d2d9SChen-Yu Tsai 8039765d2d9SChen-Yu Tsai /* disable counter alarm */ 8049765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM_EN); 8059765d2d9SChen-Yu Tsai 8069765d2d9SChen-Yu Tsai /* disable counter alarm interrupt */ 8079765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM_IRQ_EN); 8089765d2d9SChen-Yu Tsai 8099765d2d9SChen-Yu Tsai /* disable week alarm */ 8109765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM1_EN); 8119765d2d9SChen-Yu Tsai 8129765d2d9SChen-Yu Tsai /* disable week alarm interrupt */ 8139765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALRM1_IRQ_EN); 8149765d2d9SChen-Yu Tsai 8159765d2d9SChen-Yu Tsai /* clear counter alarm pending interrupts */ 8169765d2d9SChen-Yu Tsai writel(SUN6I_ALRM_IRQ_STA_CNT_IRQ_PEND, 8179765d2d9SChen-Yu Tsai chip->base + SUN6I_ALRM_IRQ_STA); 8189765d2d9SChen-Yu Tsai 8199765d2d9SChen-Yu Tsai /* clear week alarm pending interrupts */ 8209765d2d9SChen-Yu Tsai writel(SUN6I_ALRM1_IRQ_STA_WEEK_IRQ_PEND, 8219765d2d9SChen-Yu Tsai chip->base + SUN6I_ALRM1_IRQ_STA); 8229765d2d9SChen-Yu Tsai 8239765d2d9SChen-Yu Tsai /* disable alarm wakeup */ 8249765d2d9SChen-Yu Tsai writel(0, chip->base + SUN6I_ALARM_CONFIG); 8259765d2d9SChen-Yu Tsai 8263855c2c3SMaxime Ripard clk_prepare_enable(chip->losc); 827fb61bb82SMaxime Ripard 828d76a81d0SAlejandro González device_init_wakeup(&pdev->dev, 1); 829d76a81d0SAlejandro González 8308ae79be7SAlexandre Belloni chip->rtc = devm_rtc_allocate_device(&pdev->dev); 8318ae79be7SAlexandre Belloni if (IS_ERR(chip->rtc)) 8329765d2d9SChen-Yu Tsai return PTR_ERR(chip->rtc); 8338ae79be7SAlexandre Belloni 8348ae79be7SAlexandre Belloni chip->rtc->ops = &sun6i_rtc_ops; 835648c151aSAndre Przywara if (chip->flags & RTC_LINEAR_DAY) 836648c151aSAndre Przywara chip->rtc->range_max = (65536 * SECS_PER_DAY) - 1; 837648c151aSAndre Przywara else 8388ae79be7SAlexandre Belloni chip->rtc->range_max = 2019686399LL; /* 2033-12-31 23:59:59 */ 8398ae79be7SAlexandre Belloni 840fdcfd854SBartosz Golaszewski ret = devm_rtc_register_device(chip->rtc); 8418ae79be7SAlexandre Belloni if (ret) 8428ae79be7SAlexandre Belloni return ret; 8439765d2d9SChen-Yu Tsai 844581d6d8fSSamuel Holland sun6i_rtc_nvmem_cfg.priv = chip; 845581d6d8fSSamuel Holland ret = devm_rtc_nvmem_register(chip->rtc, &sun6i_rtc_nvmem_cfg); 846581d6d8fSSamuel Holland if (ret) 847581d6d8fSSamuel Holland return ret; 848581d6d8fSSamuel Holland 8499765d2d9SChen-Yu Tsai dev_info(&pdev->dev, "RTC enabled\n"); 8509765d2d9SChen-Yu Tsai 8519765d2d9SChen-Yu Tsai return 0; 8529765d2d9SChen-Yu Tsai } 8539765d2d9SChen-Yu Tsai 854403a3c3dSChen-Yu Tsai /* 855403a3c3dSChen-Yu Tsai * As far as RTC functionality goes, all models are the same. The 856403a3c3dSChen-Yu Tsai * datasheets claim that different models have different number of 857403a3c3dSChen-Yu Tsai * registers available for non-volatile storage, but experiments show 858403a3c3dSChen-Yu Tsai * that all SoCs have 16 registers available for this purpose. 859403a3c3dSChen-Yu Tsai */ 8609765d2d9SChen-Yu Tsai static const struct of_device_id sun6i_rtc_dt_ids[] = { 8619765d2d9SChen-Yu Tsai { .compatible = "allwinner,sun6i-a31-rtc" }, 8627cd1acaeSChen-Yu Tsai { .compatible = "allwinner,sun8i-a23-rtc" }, 8637cd1acaeSChen-Yu Tsai { .compatible = "allwinner,sun8i-h3-rtc" }, 864d6624cc7SMaxime Ripard { .compatible = "allwinner,sun8i-r40-rtc" }, 8657cd1acaeSChen-Yu Tsai { .compatible = "allwinner,sun8i-v3-rtc" }, 8667cd1acaeSChen-Yu Tsai { .compatible = "allwinner,sun50i-h5-rtc" }, 867b60ff2cfSOndrej Jirman { .compatible = "allwinner,sun50i-h6-rtc" }, 8688a937203SAndre Przywara { .compatible = "allwinner,sun50i-h616-rtc", 8698a937203SAndre Przywara .data = (void *)RTC_LINEAR_DAY }, 870b9d98238SIcenowy Zheng { .compatible = "allwinner,sun50i-r329-rtc", 871b9d98238SIcenowy Zheng .data = (void *)RTC_LINEAR_DAY }, 8729765d2d9SChen-Yu Tsai { /* sentinel */ }, 8739765d2d9SChen-Yu Tsai }; 8749765d2d9SChen-Yu Tsai MODULE_DEVICE_TABLE(of, sun6i_rtc_dt_ids); 8759765d2d9SChen-Yu Tsai 8769765d2d9SChen-Yu Tsai static struct platform_driver sun6i_rtc_driver = { 8779765d2d9SChen-Yu Tsai .probe = sun6i_rtc_probe, 8789765d2d9SChen-Yu Tsai .driver = { 8799765d2d9SChen-Yu Tsai .name = "sun6i-rtc", 8809765d2d9SChen-Yu Tsai .of_match_table = sun6i_rtc_dt_ids, 881d76a81d0SAlejandro González .pm = &sun6i_rtc_pm_ops, 8829765d2d9SChen-Yu Tsai }, 8839765d2d9SChen-Yu Tsai }; 88437539414SMaxime Ripard builtin_platform_driver(sun6i_rtc_driver); 885