18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * An RTC driver for Allwinner A10/A20 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2013, Carlo Caione <carlo.caione@gmail.com> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/delay.h> 98c2ecf20Sopenharmony_ci#include <linux/err.h> 108c2ecf20Sopenharmony_ci#include <linux/fs.h> 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 138c2ecf20Sopenharmony_ci#include <linux/io.h> 148c2ecf20Sopenharmony_ci#include <linux/kernel.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/of.h> 178c2ecf20Sopenharmony_ci#include <linux/of_address.h> 188c2ecf20Sopenharmony_ci#include <linux/of_device.h> 198c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 208c2ecf20Sopenharmony_ci#include <linux/rtc.h> 218c2ecf20Sopenharmony_ci#include <linux/types.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define SUNXI_LOSC_CTRL 0x0000 248c2ecf20Sopenharmony_ci#define SUNXI_LOSC_CTRL_RTC_HMS_ACC BIT(8) 258c2ecf20Sopenharmony_ci#define SUNXI_LOSC_CTRL_RTC_YMD_ACC BIT(7) 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define SUNXI_RTC_YMD 0x0004 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define SUNXI_RTC_HMS 0x0008 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define SUNXI_ALRM_DHMS 0x000c 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define SUNXI_ALRM_EN 0x0014 348c2ecf20Sopenharmony_ci#define SUNXI_ALRM_EN_CNT_EN BIT(8) 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#define SUNXI_ALRM_IRQ_EN 0x0018 378c2ecf20Sopenharmony_ci#define SUNXI_ALRM_IRQ_EN_CNT_IRQ_EN BIT(0) 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci#define SUNXI_ALRM_IRQ_STA 0x001c 408c2ecf20Sopenharmony_ci#define SUNXI_ALRM_IRQ_STA_CNT_IRQ_PEND BIT(0) 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define SUNXI_MASK_DH 0x0000001f 438c2ecf20Sopenharmony_ci#define SUNXI_MASK_SM 0x0000003f 448c2ecf20Sopenharmony_ci#define SUNXI_MASK_M 0x0000000f 458c2ecf20Sopenharmony_ci#define SUNXI_MASK_LY 0x00000001 468c2ecf20Sopenharmony_ci#define SUNXI_MASK_D 0x00000ffe 478c2ecf20Sopenharmony_ci#define SUNXI_MASK_M 0x0000000f 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci#define SUNXI_GET(x, mask, shift) (((x) & ((mask) << (shift))) \ 508c2ecf20Sopenharmony_ci >> (shift)) 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci#define SUNXI_SET(x, mask, shift) (((x) & (mask)) << (shift)) 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* 558c2ecf20Sopenharmony_ci * Get date values 568c2ecf20Sopenharmony_ci */ 578c2ecf20Sopenharmony_ci#define SUNXI_DATE_GET_DAY_VALUE(x) SUNXI_GET(x, SUNXI_MASK_DH, 0) 588c2ecf20Sopenharmony_ci#define SUNXI_DATE_GET_MON_VALUE(x) SUNXI_GET(x, SUNXI_MASK_M, 8) 598c2ecf20Sopenharmony_ci#define SUNXI_DATE_GET_YEAR_VALUE(x, mask) SUNXI_GET(x, mask, 16) 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci/* 628c2ecf20Sopenharmony_ci * Get time values 638c2ecf20Sopenharmony_ci */ 648c2ecf20Sopenharmony_ci#define SUNXI_TIME_GET_SEC_VALUE(x) SUNXI_GET(x, SUNXI_MASK_SM, 0) 658c2ecf20Sopenharmony_ci#define SUNXI_TIME_GET_MIN_VALUE(x) SUNXI_GET(x, SUNXI_MASK_SM, 8) 668c2ecf20Sopenharmony_ci#define SUNXI_TIME_GET_HOUR_VALUE(x) SUNXI_GET(x, SUNXI_MASK_DH, 16) 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci/* 698c2ecf20Sopenharmony_ci * Get alarm values 708c2ecf20Sopenharmony_ci */ 718c2ecf20Sopenharmony_ci#define SUNXI_ALRM_GET_SEC_VALUE(x) SUNXI_GET(x, SUNXI_MASK_SM, 0) 728c2ecf20Sopenharmony_ci#define SUNXI_ALRM_GET_MIN_VALUE(x) SUNXI_GET(x, SUNXI_MASK_SM, 8) 738c2ecf20Sopenharmony_ci#define SUNXI_ALRM_GET_HOUR_VALUE(x) SUNXI_GET(x, SUNXI_MASK_DH, 16) 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci/* 768c2ecf20Sopenharmony_ci * Set date values 778c2ecf20Sopenharmony_ci */ 788c2ecf20Sopenharmony_ci#define SUNXI_DATE_SET_DAY_VALUE(x) SUNXI_DATE_GET_DAY_VALUE(x) 798c2ecf20Sopenharmony_ci#define SUNXI_DATE_SET_MON_VALUE(x) SUNXI_SET(x, SUNXI_MASK_M, 8) 808c2ecf20Sopenharmony_ci#define SUNXI_DATE_SET_YEAR_VALUE(x, mask) SUNXI_SET(x, mask, 16) 818c2ecf20Sopenharmony_ci#define SUNXI_LEAP_SET_VALUE(x, shift) SUNXI_SET(x, SUNXI_MASK_LY, shift) 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci/* 848c2ecf20Sopenharmony_ci * Set time values 858c2ecf20Sopenharmony_ci */ 868c2ecf20Sopenharmony_ci#define SUNXI_TIME_SET_SEC_VALUE(x) SUNXI_TIME_GET_SEC_VALUE(x) 878c2ecf20Sopenharmony_ci#define SUNXI_TIME_SET_MIN_VALUE(x) SUNXI_SET(x, SUNXI_MASK_SM, 8) 888c2ecf20Sopenharmony_ci#define SUNXI_TIME_SET_HOUR_VALUE(x) SUNXI_SET(x, SUNXI_MASK_DH, 16) 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci/* 918c2ecf20Sopenharmony_ci * Set alarm values 928c2ecf20Sopenharmony_ci */ 938c2ecf20Sopenharmony_ci#define SUNXI_ALRM_SET_SEC_VALUE(x) SUNXI_ALRM_GET_SEC_VALUE(x) 948c2ecf20Sopenharmony_ci#define SUNXI_ALRM_SET_MIN_VALUE(x) SUNXI_SET(x, SUNXI_MASK_SM, 8) 958c2ecf20Sopenharmony_ci#define SUNXI_ALRM_SET_HOUR_VALUE(x) SUNXI_SET(x, SUNXI_MASK_DH, 16) 968c2ecf20Sopenharmony_ci#define SUNXI_ALRM_SET_DAY_VALUE(x) SUNXI_SET(x, SUNXI_MASK_D, 21) 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci/* 998c2ecf20Sopenharmony_ci * Time unit conversions 1008c2ecf20Sopenharmony_ci */ 1018c2ecf20Sopenharmony_ci#define SEC_IN_MIN 60 1028c2ecf20Sopenharmony_ci#define SEC_IN_HOUR (60 * SEC_IN_MIN) 1038c2ecf20Sopenharmony_ci#define SEC_IN_DAY (24 * SEC_IN_HOUR) 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci/* 1068c2ecf20Sopenharmony_ci * The year parameter passed to the driver is usually an offset relative to 1078c2ecf20Sopenharmony_ci * the year 1900. This macro is used to convert this offset to another one 1088c2ecf20Sopenharmony_ci * relative to the minimum year allowed by the hardware. 1098c2ecf20Sopenharmony_ci */ 1108c2ecf20Sopenharmony_ci#define SUNXI_YEAR_OFF(x) ((x)->min - 1900) 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci/* 1138c2ecf20Sopenharmony_ci * min and max year are arbitrary set considering the limited range of the 1148c2ecf20Sopenharmony_ci * hardware register field 1158c2ecf20Sopenharmony_ci */ 1168c2ecf20Sopenharmony_cistruct sunxi_rtc_data_year { 1178c2ecf20Sopenharmony_ci unsigned int min; /* min year allowed */ 1188c2ecf20Sopenharmony_ci unsigned int max; /* max year allowed */ 1198c2ecf20Sopenharmony_ci unsigned int mask; /* mask for the year field */ 1208c2ecf20Sopenharmony_ci unsigned char leap_shift; /* bit shift to get the leap year */ 1218c2ecf20Sopenharmony_ci}; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic const struct sunxi_rtc_data_year data_year_param[] = { 1248c2ecf20Sopenharmony_ci [0] = { 1258c2ecf20Sopenharmony_ci .min = 2010, 1268c2ecf20Sopenharmony_ci .max = 2073, 1278c2ecf20Sopenharmony_ci .mask = 0x3f, 1288c2ecf20Sopenharmony_ci .leap_shift = 22, 1298c2ecf20Sopenharmony_ci }, 1308c2ecf20Sopenharmony_ci [1] = { 1318c2ecf20Sopenharmony_ci .min = 1970, 1328c2ecf20Sopenharmony_ci .max = 2225, 1338c2ecf20Sopenharmony_ci .mask = 0xff, 1348c2ecf20Sopenharmony_ci .leap_shift = 24, 1358c2ecf20Sopenharmony_ci }, 1368c2ecf20Sopenharmony_ci}; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistruct sunxi_rtc_dev { 1398c2ecf20Sopenharmony_ci struct rtc_device *rtc; 1408c2ecf20Sopenharmony_ci struct device *dev; 1418c2ecf20Sopenharmony_ci const struct sunxi_rtc_data_year *data_year; 1428c2ecf20Sopenharmony_ci void __iomem *base; 1438c2ecf20Sopenharmony_ci int irq; 1448c2ecf20Sopenharmony_ci}; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic irqreturn_t sunxi_rtc_alarmirq(int irq, void *id) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci struct sunxi_rtc_dev *chip = (struct sunxi_rtc_dev *) id; 1498c2ecf20Sopenharmony_ci u32 val; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci val = readl(chip->base + SUNXI_ALRM_IRQ_STA); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci if (val & SUNXI_ALRM_IRQ_STA_CNT_IRQ_PEND) { 1548c2ecf20Sopenharmony_ci val |= SUNXI_ALRM_IRQ_STA_CNT_IRQ_PEND; 1558c2ecf20Sopenharmony_ci writel(val, chip->base + SUNXI_ALRM_IRQ_STA); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci rtc_update_irq(chip->rtc, 1, RTC_AF | RTC_IRQF); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci return IRQ_NONE; 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic void sunxi_rtc_setaie(unsigned int to, struct sunxi_rtc_dev *chip) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci u32 alrm_val = 0; 1688c2ecf20Sopenharmony_ci u32 alrm_irq_val = 0; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci if (to) { 1718c2ecf20Sopenharmony_ci alrm_val = readl(chip->base + SUNXI_ALRM_EN); 1728c2ecf20Sopenharmony_ci alrm_val |= SUNXI_ALRM_EN_CNT_EN; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci alrm_irq_val = readl(chip->base + SUNXI_ALRM_IRQ_EN); 1758c2ecf20Sopenharmony_ci alrm_irq_val |= SUNXI_ALRM_IRQ_EN_CNT_IRQ_EN; 1768c2ecf20Sopenharmony_ci } else { 1778c2ecf20Sopenharmony_ci writel(SUNXI_ALRM_IRQ_STA_CNT_IRQ_PEND, 1788c2ecf20Sopenharmony_ci chip->base + SUNXI_ALRM_IRQ_STA); 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci writel(alrm_val, chip->base + SUNXI_ALRM_EN); 1828c2ecf20Sopenharmony_ci writel(alrm_irq_val, chip->base + SUNXI_ALRM_IRQ_EN); 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_cistatic int sunxi_rtc_getalarm(struct device *dev, struct rtc_wkalrm *wkalrm) 1868c2ecf20Sopenharmony_ci{ 1878c2ecf20Sopenharmony_ci struct sunxi_rtc_dev *chip = dev_get_drvdata(dev); 1888c2ecf20Sopenharmony_ci struct rtc_time *alrm_tm = &wkalrm->time; 1898c2ecf20Sopenharmony_ci u32 alrm; 1908c2ecf20Sopenharmony_ci u32 alrm_en; 1918c2ecf20Sopenharmony_ci u32 date; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci alrm = readl(chip->base + SUNXI_ALRM_DHMS); 1948c2ecf20Sopenharmony_ci date = readl(chip->base + SUNXI_RTC_YMD); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci alrm_tm->tm_sec = SUNXI_ALRM_GET_SEC_VALUE(alrm); 1978c2ecf20Sopenharmony_ci alrm_tm->tm_min = SUNXI_ALRM_GET_MIN_VALUE(alrm); 1988c2ecf20Sopenharmony_ci alrm_tm->tm_hour = SUNXI_ALRM_GET_HOUR_VALUE(alrm); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci alrm_tm->tm_mday = SUNXI_DATE_GET_DAY_VALUE(date); 2018c2ecf20Sopenharmony_ci alrm_tm->tm_mon = SUNXI_DATE_GET_MON_VALUE(date); 2028c2ecf20Sopenharmony_ci alrm_tm->tm_year = SUNXI_DATE_GET_YEAR_VALUE(date, 2038c2ecf20Sopenharmony_ci chip->data_year->mask); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci alrm_tm->tm_mon -= 1; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci /* 2088c2ecf20Sopenharmony_ci * switch from (data_year->min)-relative offset to 2098c2ecf20Sopenharmony_ci * a (1900)-relative one 2108c2ecf20Sopenharmony_ci */ 2118c2ecf20Sopenharmony_ci alrm_tm->tm_year += SUNXI_YEAR_OFF(chip->data_year); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci alrm_en = readl(chip->base + SUNXI_ALRM_IRQ_EN); 2148c2ecf20Sopenharmony_ci if (alrm_en & SUNXI_ALRM_EN_CNT_EN) 2158c2ecf20Sopenharmony_ci wkalrm->enabled = 1; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci return 0; 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cistatic int sunxi_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) 2218c2ecf20Sopenharmony_ci{ 2228c2ecf20Sopenharmony_ci struct sunxi_rtc_dev *chip = dev_get_drvdata(dev); 2238c2ecf20Sopenharmony_ci u32 date, time; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci /* 2268c2ecf20Sopenharmony_ci * read again in case it changes 2278c2ecf20Sopenharmony_ci */ 2288c2ecf20Sopenharmony_ci do { 2298c2ecf20Sopenharmony_ci date = readl(chip->base + SUNXI_RTC_YMD); 2308c2ecf20Sopenharmony_ci time = readl(chip->base + SUNXI_RTC_HMS); 2318c2ecf20Sopenharmony_ci } while ((date != readl(chip->base + SUNXI_RTC_YMD)) || 2328c2ecf20Sopenharmony_ci (time != readl(chip->base + SUNXI_RTC_HMS))); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci rtc_tm->tm_sec = SUNXI_TIME_GET_SEC_VALUE(time); 2358c2ecf20Sopenharmony_ci rtc_tm->tm_min = SUNXI_TIME_GET_MIN_VALUE(time); 2368c2ecf20Sopenharmony_ci rtc_tm->tm_hour = SUNXI_TIME_GET_HOUR_VALUE(time); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci rtc_tm->tm_mday = SUNXI_DATE_GET_DAY_VALUE(date); 2398c2ecf20Sopenharmony_ci rtc_tm->tm_mon = SUNXI_DATE_GET_MON_VALUE(date); 2408c2ecf20Sopenharmony_ci rtc_tm->tm_year = SUNXI_DATE_GET_YEAR_VALUE(date, 2418c2ecf20Sopenharmony_ci chip->data_year->mask); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci rtc_tm->tm_mon -= 1; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci /* 2468c2ecf20Sopenharmony_ci * switch from (data_year->min)-relative offset to 2478c2ecf20Sopenharmony_ci * a (1900)-relative one 2488c2ecf20Sopenharmony_ci */ 2498c2ecf20Sopenharmony_ci rtc_tm->tm_year += SUNXI_YEAR_OFF(chip->data_year); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci return 0; 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic int sunxi_rtc_setalarm(struct device *dev, struct rtc_wkalrm *wkalrm) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci struct sunxi_rtc_dev *chip = dev_get_drvdata(dev); 2578c2ecf20Sopenharmony_ci struct rtc_time *alrm_tm = &wkalrm->time; 2588c2ecf20Sopenharmony_ci struct rtc_time tm_now; 2598c2ecf20Sopenharmony_ci u32 alrm; 2608c2ecf20Sopenharmony_ci time64_t diff; 2618c2ecf20Sopenharmony_ci unsigned long time_gap; 2628c2ecf20Sopenharmony_ci unsigned long time_gap_day; 2638c2ecf20Sopenharmony_ci unsigned long time_gap_hour; 2648c2ecf20Sopenharmony_ci unsigned long time_gap_min; 2658c2ecf20Sopenharmony_ci int ret; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci ret = sunxi_rtc_gettime(dev, &tm_now); 2688c2ecf20Sopenharmony_ci if (ret < 0) { 2698c2ecf20Sopenharmony_ci dev_err(dev, "Error in getting time\n"); 2708c2ecf20Sopenharmony_ci return -EINVAL; 2718c2ecf20Sopenharmony_ci } 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci diff = rtc_tm_sub(alrm_tm, &tm_now); 2748c2ecf20Sopenharmony_ci if (diff <= 0) { 2758c2ecf20Sopenharmony_ci dev_err(dev, "Date to set in the past\n"); 2768c2ecf20Sopenharmony_ci return -EINVAL; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci if (diff > 255 * SEC_IN_DAY) { 2808c2ecf20Sopenharmony_ci dev_err(dev, "Day must be in the range 0 - 255\n"); 2818c2ecf20Sopenharmony_ci return -EINVAL; 2828c2ecf20Sopenharmony_ci } 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci time_gap = diff; 2858c2ecf20Sopenharmony_ci time_gap_day = time_gap / SEC_IN_DAY; 2868c2ecf20Sopenharmony_ci time_gap -= time_gap_day * SEC_IN_DAY; 2878c2ecf20Sopenharmony_ci time_gap_hour = time_gap / SEC_IN_HOUR; 2888c2ecf20Sopenharmony_ci time_gap -= time_gap_hour * SEC_IN_HOUR; 2898c2ecf20Sopenharmony_ci time_gap_min = time_gap / SEC_IN_MIN; 2908c2ecf20Sopenharmony_ci time_gap -= time_gap_min * SEC_IN_MIN; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci sunxi_rtc_setaie(0, chip); 2938c2ecf20Sopenharmony_ci writel(0, chip->base + SUNXI_ALRM_DHMS); 2948c2ecf20Sopenharmony_ci usleep_range(100, 300); 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci alrm = SUNXI_ALRM_SET_SEC_VALUE(time_gap) | 2978c2ecf20Sopenharmony_ci SUNXI_ALRM_SET_MIN_VALUE(time_gap_min) | 2988c2ecf20Sopenharmony_ci SUNXI_ALRM_SET_HOUR_VALUE(time_gap_hour) | 2998c2ecf20Sopenharmony_ci SUNXI_ALRM_SET_DAY_VALUE(time_gap_day); 3008c2ecf20Sopenharmony_ci writel(alrm, chip->base + SUNXI_ALRM_DHMS); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci writel(0, chip->base + SUNXI_ALRM_IRQ_EN); 3038c2ecf20Sopenharmony_ci writel(SUNXI_ALRM_IRQ_EN_CNT_IRQ_EN, chip->base + SUNXI_ALRM_IRQ_EN); 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci sunxi_rtc_setaie(wkalrm->enabled, chip); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci return 0; 3088c2ecf20Sopenharmony_ci} 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_cistatic int sunxi_rtc_wait(struct sunxi_rtc_dev *chip, int offset, 3118c2ecf20Sopenharmony_ci unsigned int mask, unsigned int ms_timeout) 3128c2ecf20Sopenharmony_ci{ 3138c2ecf20Sopenharmony_ci const unsigned long timeout = jiffies + msecs_to_jiffies(ms_timeout); 3148c2ecf20Sopenharmony_ci u32 reg; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci do { 3178c2ecf20Sopenharmony_ci reg = readl(chip->base + offset); 3188c2ecf20Sopenharmony_ci reg &= mask; 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci if (reg == mask) 3218c2ecf20Sopenharmony_ci return 0; 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci } while (time_before(jiffies, timeout)); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci return -ETIMEDOUT; 3268c2ecf20Sopenharmony_ci} 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_cistatic int sunxi_rtc_settime(struct device *dev, struct rtc_time *rtc_tm) 3298c2ecf20Sopenharmony_ci{ 3308c2ecf20Sopenharmony_ci struct sunxi_rtc_dev *chip = dev_get_drvdata(dev); 3318c2ecf20Sopenharmony_ci u32 date = 0; 3328c2ecf20Sopenharmony_ci u32 time = 0; 3338c2ecf20Sopenharmony_ci unsigned int year; 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci /* 3368c2ecf20Sopenharmony_ci * the input rtc_tm->tm_year is the offset relative to 1900. We use 3378c2ecf20Sopenharmony_ci * the SUNXI_YEAR_OFF macro to rebase it with respect to the min year 3388c2ecf20Sopenharmony_ci * allowed by the hardware 3398c2ecf20Sopenharmony_ci */ 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci year = rtc_tm->tm_year + 1900; 3428c2ecf20Sopenharmony_ci if (year < chip->data_year->min || year > chip->data_year->max) { 3438c2ecf20Sopenharmony_ci dev_err(dev, "rtc only supports year in range %u - %u\n", 3448c2ecf20Sopenharmony_ci chip->data_year->min, chip->data_year->max); 3458c2ecf20Sopenharmony_ci return -EINVAL; 3468c2ecf20Sopenharmony_ci } 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci rtc_tm->tm_year -= SUNXI_YEAR_OFF(chip->data_year); 3498c2ecf20Sopenharmony_ci rtc_tm->tm_mon += 1; 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci date = SUNXI_DATE_SET_DAY_VALUE(rtc_tm->tm_mday) | 3528c2ecf20Sopenharmony_ci SUNXI_DATE_SET_MON_VALUE(rtc_tm->tm_mon) | 3538c2ecf20Sopenharmony_ci SUNXI_DATE_SET_YEAR_VALUE(rtc_tm->tm_year, 3548c2ecf20Sopenharmony_ci chip->data_year->mask); 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci if (is_leap_year(year)) 3578c2ecf20Sopenharmony_ci date |= SUNXI_LEAP_SET_VALUE(1, chip->data_year->leap_shift); 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci time = SUNXI_TIME_SET_SEC_VALUE(rtc_tm->tm_sec) | 3608c2ecf20Sopenharmony_ci SUNXI_TIME_SET_MIN_VALUE(rtc_tm->tm_min) | 3618c2ecf20Sopenharmony_ci SUNXI_TIME_SET_HOUR_VALUE(rtc_tm->tm_hour); 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci writel(0, chip->base + SUNXI_RTC_HMS); 3648c2ecf20Sopenharmony_ci writel(0, chip->base + SUNXI_RTC_YMD); 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci writel(time, chip->base + SUNXI_RTC_HMS); 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci /* 3698c2ecf20Sopenharmony_ci * After writing the RTC HH-MM-SS register, the 3708c2ecf20Sopenharmony_ci * SUNXI_LOSC_CTRL_RTC_HMS_ACC bit is set and it will not 3718c2ecf20Sopenharmony_ci * be cleared until the real writing operation is finished 3728c2ecf20Sopenharmony_ci */ 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci if (sunxi_rtc_wait(chip, SUNXI_LOSC_CTRL, 3758c2ecf20Sopenharmony_ci SUNXI_LOSC_CTRL_RTC_HMS_ACC, 50)) { 3768c2ecf20Sopenharmony_ci dev_err(dev, "Failed to set rtc time.\n"); 3778c2ecf20Sopenharmony_ci return -1; 3788c2ecf20Sopenharmony_ci } 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci writel(date, chip->base + SUNXI_RTC_YMD); 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci /* 3838c2ecf20Sopenharmony_ci * After writing the RTC YY-MM-DD register, the 3848c2ecf20Sopenharmony_ci * SUNXI_LOSC_CTRL_RTC_YMD_ACC bit is set and it will not 3858c2ecf20Sopenharmony_ci * be cleared until the real writing operation is finished 3868c2ecf20Sopenharmony_ci */ 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_ci if (sunxi_rtc_wait(chip, SUNXI_LOSC_CTRL, 3898c2ecf20Sopenharmony_ci SUNXI_LOSC_CTRL_RTC_YMD_ACC, 50)) { 3908c2ecf20Sopenharmony_ci dev_err(dev, "Failed to set rtc time.\n"); 3918c2ecf20Sopenharmony_ci return -1; 3928c2ecf20Sopenharmony_ci } 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci return 0; 3958c2ecf20Sopenharmony_ci} 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_cistatic int sunxi_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) 3988c2ecf20Sopenharmony_ci{ 3998c2ecf20Sopenharmony_ci struct sunxi_rtc_dev *chip = dev_get_drvdata(dev); 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci if (!enabled) 4028c2ecf20Sopenharmony_ci sunxi_rtc_setaie(enabled, chip); 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci return 0; 4058c2ecf20Sopenharmony_ci} 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_cistatic const struct rtc_class_ops sunxi_rtc_ops = { 4088c2ecf20Sopenharmony_ci .read_time = sunxi_rtc_gettime, 4098c2ecf20Sopenharmony_ci .set_time = sunxi_rtc_settime, 4108c2ecf20Sopenharmony_ci .read_alarm = sunxi_rtc_getalarm, 4118c2ecf20Sopenharmony_ci .set_alarm = sunxi_rtc_setalarm, 4128c2ecf20Sopenharmony_ci .alarm_irq_enable = sunxi_rtc_alarm_irq_enable 4138c2ecf20Sopenharmony_ci}; 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_cistatic const struct of_device_id sunxi_rtc_dt_ids[] = { 4168c2ecf20Sopenharmony_ci { .compatible = "allwinner,sun4i-a10-rtc", .data = &data_year_param[0] }, 4178c2ecf20Sopenharmony_ci { .compatible = "allwinner,sun7i-a20-rtc", .data = &data_year_param[1] }, 4188c2ecf20Sopenharmony_ci { /* sentinel */ }, 4198c2ecf20Sopenharmony_ci}; 4208c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, sunxi_rtc_dt_ids); 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_cistatic int sunxi_rtc_probe(struct platform_device *pdev) 4238c2ecf20Sopenharmony_ci{ 4248c2ecf20Sopenharmony_ci struct sunxi_rtc_dev *chip; 4258c2ecf20Sopenharmony_ci int ret; 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); 4288c2ecf20Sopenharmony_ci if (!chip) 4298c2ecf20Sopenharmony_ci return -ENOMEM; 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, chip); 4328c2ecf20Sopenharmony_ci chip->dev = &pdev->dev; 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_ci chip->rtc = devm_rtc_allocate_device(&pdev->dev); 4358c2ecf20Sopenharmony_ci if (IS_ERR(chip->rtc)) 4368c2ecf20Sopenharmony_ci return PTR_ERR(chip->rtc); 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci chip->base = devm_platform_ioremap_resource(pdev, 0); 4398c2ecf20Sopenharmony_ci if (IS_ERR(chip->base)) 4408c2ecf20Sopenharmony_ci return PTR_ERR(chip->base); 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci chip->irq = platform_get_irq(pdev, 0); 4438c2ecf20Sopenharmony_ci if (chip->irq < 0) 4448c2ecf20Sopenharmony_ci return chip->irq; 4458c2ecf20Sopenharmony_ci ret = devm_request_irq(&pdev->dev, chip->irq, sunxi_rtc_alarmirq, 4468c2ecf20Sopenharmony_ci 0, dev_name(&pdev->dev), chip); 4478c2ecf20Sopenharmony_ci if (ret) { 4488c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Could not request IRQ\n"); 4498c2ecf20Sopenharmony_ci return ret; 4508c2ecf20Sopenharmony_ci } 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci chip->data_year = of_device_get_match_data(&pdev->dev); 4538c2ecf20Sopenharmony_ci if (!chip->data_year) { 4548c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unable to setup RTC data\n"); 4558c2ecf20Sopenharmony_ci return -ENODEV; 4568c2ecf20Sopenharmony_ci } 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci /* clear the alarm count value */ 4598c2ecf20Sopenharmony_ci writel(0, chip->base + SUNXI_ALRM_DHMS); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci /* disable alarm, not generate irq pending */ 4628c2ecf20Sopenharmony_ci writel(0, chip->base + SUNXI_ALRM_EN); 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci /* disable alarm week/cnt irq, unset to cpu */ 4658c2ecf20Sopenharmony_ci writel(0, chip->base + SUNXI_ALRM_IRQ_EN); 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_ci /* clear alarm week/cnt irq pending */ 4688c2ecf20Sopenharmony_ci writel(SUNXI_ALRM_IRQ_STA_CNT_IRQ_PEND, chip->base + 4698c2ecf20Sopenharmony_ci SUNXI_ALRM_IRQ_STA); 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_ci chip->rtc->ops = &sunxi_rtc_ops; 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci return rtc_register_device(chip->rtc); 4748c2ecf20Sopenharmony_ci} 4758c2ecf20Sopenharmony_ci 4768c2ecf20Sopenharmony_cistatic struct platform_driver sunxi_rtc_driver = { 4778c2ecf20Sopenharmony_ci .probe = sunxi_rtc_probe, 4788c2ecf20Sopenharmony_ci .driver = { 4798c2ecf20Sopenharmony_ci .name = "sunxi-rtc", 4808c2ecf20Sopenharmony_ci .of_match_table = sunxi_rtc_dt_ids, 4818c2ecf20Sopenharmony_ci }, 4828c2ecf20Sopenharmony_ci}; 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_cimodule_platform_driver(sunxi_rtc_driver); 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("sunxi RTC driver"); 4878c2ecf20Sopenharmony_ciMODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>"); 4888c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 489