18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci#include <linux/bcd.h> 38c2ecf20Sopenharmony_ci#include <linux/delay.h> 48c2ecf20Sopenharmony_ci#include <linux/export.h> 58c2ecf20Sopenharmony_ci#include <linux/mc146818rtc.h> 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI 88c2ecf20Sopenharmony_ci#include <linux/acpi.h> 98c2ecf20Sopenharmony_ci#endif 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci/* 128c2ecf20Sopenharmony_ci * Execute a function while the UIP (Update-in-progress) bit of the RTC is 138c2ecf20Sopenharmony_ci * unset. 148c2ecf20Sopenharmony_ci * 158c2ecf20Sopenharmony_ci * Warning: callback may be executed more then once. 168c2ecf20Sopenharmony_ci */ 178c2ecf20Sopenharmony_cibool mc146818_avoid_UIP(void (*callback)(unsigned char seconds, void *param), 188c2ecf20Sopenharmony_ci void *param) 198c2ecf20Sopenharmony_ci{ 208c2ecf20Sopenharmony_ci int i; 218c2ecf20Sopenharmony_ci unsigned long flags; 228c2ecf20Sopenharmony_ci unsigned char seconds; 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci for (i = 0; i < 10; i++) { 258c2ecf20Sopenharmony_ci spin_lock_irqsave(&rtc_lock, flags); 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci /* 288c2ecf20Sopenharmony_ci * Check whether there is an update in progress during which the 298c2ecf20Sopenharmony_ci * readout is unspecified. The maximum update time is ~2ms. Poll 308c2ecf20Sopenharmony_ci * every msec for completion. 318c2ecf20Sopenharmony_ci * 328c2ecf20Sopenharmony_ci * Store the second value before checking UIP so a long lasting 338c2ecf20Sopenharmony_ci * NMI which happens to hit after the UIP check cannot make 348c2ecf20Sopenharmony_ci * an update cycle invisible. 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_ci seconds = CMOS_READ(RTC_SECONDS); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) { 398c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 408c2ecf20Sopenharmony_ci mdelay(1); 418c2ecf20Sopenharmony_ci continue; 428c2ecf20Sopenharmony_ci } 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci /* Revalidate the above readout */ 458c2ecf20Sopenharmony_ci if (seconds != CMOS_READ(RTC_SECONDS)) { 468c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 478c2ecf20Sopenharmony_ci continue; 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci if (callback) 518c2ecf20Sopenharmony_ci callback(seconds, param); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci /* 548c2ecf20Sopenharmony_ci * Check for the UIP bit again. If it is set now then 558c2ecf20Sopenharmony_ci * the above values may contain garbage. 568c2ecf20Sopenharmony_ci */ 578c2ecf20Sopenharmony_ci if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) { 588c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 598c2ecf20Sopenharmony_ci mdelay(1); 608c2ecf20Sopenharmony_ci continue; 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci /* 648c2ecf20Sopenharmony_ci * A NMI might have interrupted the above sequence so check 658c2ecf20Sopenharmony_ci * whether the seconds value has changed which indicates that 668c2ecf20Sopenharmony_ci * the NMI took longer than the UIP bit was set. Unlikely, but 678c2ecf20Sopenharmony_ci * possible and there is also virt... 688c2ecf20Sopenharmony_ci */ 698c2ecf20Sopenharmony_ci if (seconds != CMOS_READ(RTC_SECONDS)) { 708c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 718c2ecf20Sopenharmony_ci continue; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci return true; 768c2ecf20Sopenharmony_ci } 778c2ecf20Sopenharmony_ci return false; 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(mc146818_avoid_UIP); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci/* 828c2ecf20Sopenharmony_ci * If the UIP (Update-in-progress) bit of the RTC is set for more then 838c2ecf20Sopenharmony_ci * 10ms, the RTC is apparently broken or not present. 848c2ecf20Sopenharmony_ci */ 858c2ecf20Sopenharmony_cibool mc146818_does_rtc_work(void) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci int i; 888c2ecf20Sopenharmony_ci unsigned char val; 898c2ecf20Sopenharmony_ci unsigned long flags; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci for (i = 0; i < 10; i++) { 928c2ecf20Sopenharmony_ci spin_lock_irqsave(&rtc_lock, flags); 938c2ecf20Sopenharmony_ci val = CMOS_READ(RTC_FREQ_SELECT); 948c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci if ((val & RTC_UIP) == 0) 978c2ecf20Sopenharmony_ci return true; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci mdelay(1); 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci return false; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(mc146818_does_rtc_work); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ciint mc146818_get_time(struct rtc_time *time) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci unsigned char ctrl; 1098c2ecf20Sopenharmony_ci unsigned long flags; 1108c2ecf20Sopenharmony_ci unsigned int iter_count = 0; 1118c2ecf20Sopenharmony_ci unsigned char century = 0; 1128c2ecf20Sopenharmony_ci bool retry; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci#ifdef CONFIG_MACH_DECSTATION 1158c2ecf20Sopenharmony_ci unsigned int real_year; 1168c2ecf20Sopenharmony_ci#endif 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ciagain: 1198c2ecf20Sopenharmony_ci if (iter_count > 10) { 1208c2ecf20Sopenharmony_ci memset(time, 0, sizeof(*time)); 1218c2ecf20Sopenharmony_ci return -EIO; 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci iter_count++; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci spin_lock_irqsave(&rtc_lock, flags); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci /* 1288c2ecf20Sopenharmony_ci * Check whether there is an update in progress during which the 1298c2ecf20Sopenharmony_ci * readout is unspecified. The maximum update time is ~2ms. Poll 1308c2ecf20Sopenharmony_ci * every msec for completion. 1318c2ecf20Sopenharmony_ci * 1328c2ecf20Sopenharmony_ci * Store the second value before checking UIP so a long lasting NMI 1338c2ecf20Sopenharmony_ci * which happens to hit after the UIP check cannot make an update 1348c2ecf20Sopenharmony_ci * cycle invisible. 1358c2ecf20Sopenharmony_ci */ 1368c2ecf20Sopenharmony_ci time->tm_sec = CMOS_READ(RTC_SECONDS); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) { 1398c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 1408c2ecf20Sopenharmony_ci mdelay(1); 1418c2ecf20Sopenharmony_ci goto again; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci /* Revalidate the above readout */ 1458c2ecf20Sopenharmony_ci if (time->tm_sec != CMOS_READ(RTC_SECONDS)) { 1468c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 1478c2ecf20Sopenharmony_ci goto again; 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci /* 1518c2ecf20Sopenharmony_ci * Only the values that we read from the RTC are set. We leave 1528c2ecf20Sopenharmony_ci * tm_wday, tm_yday and tm_isdst untouched. Even though the 1538c2ecf20Sopenharmony_ci * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated 1548c2ecf20Sopenharmony_ci * by the RTC when initially set to a non-zero value. 1558c2ecf20Sopenharmony_ci */ 1568c2ecf20Sopenharmony_ci time->tm_min = CMOS_READ(RTC_MINUTES); 1578c2ecf20Sopenharmony_ci time->tm_hour = CMOS_READ(RTC_HOURS); 1588c2ecf20Sopenharmony_ci time->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH); 1598c2ecf20Sopenharmony_ci time->tm_mon = CMOS_READ(RTC_MONTH); 1608c2ecf20Sopenharmony_ci time->tm_year = CMOS_READ(RTC_YEAR); 1618c2ecf20Sopenharmony_ci#ifdef CONFIG_MACH_DECSTATION 1628c2ecf20Sopenharmony_ci real_year = CMOS_READ(RTC_DEC_YEAR); 1638c2ecf20Sopenharmony_ci#endif 1648c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI 1658c2ecf20Sopenharmony_ci if (acpi_gbl_FADT.header.revision >= FADT2_REVISION_ID && 1668c2ecf20Sopenharmony_ci acpi_gbl_FADT.century) 1678c2ecf20Sopenharmony_ci century = CMOS_READ(acpi_gbl_FADT.century); 1688c2ecf20Sopenharmony_ci#endif 1698c2ecf20Sopenharmony_ci ctrl = CMOS_READ(RTC_CONTROL); 1708c2ecf20Sopenharmony_ci /* 1718c2ecf20Sopenharmony_ci * Check for the UIP bit again. If it is set now then 1728c2ecf20Sopenharmony_ci * the above values may contain garbage. 1738c2ecf20Sopenharmony_ci */ 1748c2ecf20Sopenharmony_ci retry = CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP; 1758c2ecf20Sopenharmony_ci /* 1768c2ecf20Sopenharmony_ci * A NMI might have interrupted the above sequence so check whether 1778c2ecf20Sopenharmony_ci * the seconds value has changed which indicates that the NMI took 1788c2ecf20Sopenharmony_ci * longer than the UIP bit was set. Unlikely, but possible and 1798c2ecf20Sopenharmony_ci * there is also virt... 1808c2ecf20Sopenharmony_ci */ 1818c2ecf20Sopenharmony_ci retry |= time->tm_sec != CMOS_READ(RTC_SECONDS); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci if (retry) 1868c2ecf20Sopenharmony_ci goto again; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) 1898c2ecf20Sopenharmony_ci { 1908c2ecf20Sopenharmony_ci time->tm_sec = bcd2bin(time->tm_sec); 1918c2ecf20Sopenharmony_ci time->tm_min = bcd2bin(time->tm_min); 1928c2ecf20Sopenharmony_ci time->tm_hour = bcd2bin(time->tm_hour); 1938c2ecf20Sopenharmony_ci time->tm_mday = bcd2bin(time->tm_mday); 1948c2ecf20Sopenharmony_ci time->tm_mon = bcd2bin(time->tm_mon); 1958c2ecf20Sopenharmony_ci time->tm_year = bcd2bin(time->tm_year); 1968c2ecf20Sopenharmony_ci century = bcd2bin(century); 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci#ifdef CONFIG_MACH_DECSTATION 2008c2ecf20Sopenharmony_ci time->tm_year += real_year - 72; 2018c2ecf20Sopenharmony_ci#endif 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci if (century > 19) 2048c2ecf20Sopenharmony_ci time->tm_year += (century - 19) * 100; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci /* 2078c2ecf20Sopenharmony_ci * Account for differences between how the RTC uses the values 2088c2ecf20Sopenharmony_ci * and how they are defined in a struct rtc_time; 2098c2ecf20Sopenharmony_ci */ 2108c2ecf20Sopenharmony_ci if (time->tm_year <= 69) 2118c2ecf20Sopenharmony_ci time->tm_year += 100; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci time->tm_mon--; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci return 0; 2168c2ecf20Sopenharmony_ci} 2178c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(mc146818_get_time); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci/* AMD systems don't allow access to AltCentury with DV1 */ 2208c2ecf20Sopenharmony_cistatic bool apply_amd_register_a_behavior(void) 2218c2ecf20Sopenharmony_ci{ 2228c2ecf20Sopenharmony_ci#ifdef CONFIG_X86 2238c2ecf20Sopenharmony_ci if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD || 2248c2ecf20Sopenharmony_ci boot_cpu_data.x86_vendor == X86_VENDOR_HYGON) 2258c2ecf20Sopenharmony_ci return true; 2268c2ecf20Sopenharmony_ci#endif 2278c2ecf20Sopenharmony_ci return false; 2288c2ecf20Sopenharmony_ci} 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci/* Set the current date and time in the real time clock. */ 2318c2ecf20Sopenharmony_ciint mc146818_set_time(struct rtc_time *time) 2328c2ecf20Sopenharmony_ci{ 2338c2ecf20Sopenharmony_ci unsigned long flags; 2348c2ecf20Sopenharmony_ci unsigned char mon, day, hrs, min, sec; 2358c2ecf20Sopenharmony_ci unsigned char save_control, save_freq_select; 2368c2ecf20Sopenharmony_ci unsigned int yrs; 2378c2ecf20Sopenharmony_ci#ifdef CONFIG_MACH_DECSTATION 2388c2ecf20Sopenharmony_ci unsigned int real_yrs, leap_yr; 2398c2ecf20Sopenharmony_ci#endif 2408c2ecf20Sopenharmony_ci unsigned char century = 0; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci yrs = time->tm_year; 2438c2ecf20Sopenharmony_ci mon = time->tm_mon + 1; /* tm_mon starts at zero */ 2448c2ecf20Sopenharmony_ci day = time->tm_mday; 2458c2ecf20Sopenharmony_ci hrs = time->tm_hour; 2468c2ecf20Sopenharmony_ci min = time->tm_min; 2478c2ecf20Sopenharmony_ci sec = time->tm_sec; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci if (yrs > 255) /* They are unsigned */ 2508c2ecf20Sopenharmony_ci return -EINVAL; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci#ifdef CONFIG_MACH_DECSTATION 2538c2ecf20Sopenharmony_ci real_yrs = yrs; 2548c2ecf20Sopenharmony_ci leap_yr = ((!((yrs + 1900) % 4) && ((yrs + 1900) % 100)) || 2558c2ecf20Sopenharmony_ci !((yrs + 1900) % 400)); 2568c2ecf20Sopenharmony_ci yrs = 72; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci /* 2598c2ecf20Sopenharmony_ci * We want to keep the year set to 73 until March 2608c2ecf20Sopenharmony_ci * for non-leap years, so that Feb, 29th is handled 2618c2ecf20Sopenharmony_ci * correctly. 2628c2ecf20Sopenharmony_ci */ 2638c2ecf20Sopenharmony_ci if (!leap_yr && mon < 3) { 2648c2ecf20Sopenharmony_ci real_yrs--; 2658c2ecf20Sopenharmony_ci yrs = 73; 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci#endif 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI 2708c2ecf20Sopenharmony_ci if (acpi_gbl_FADT.header.revision >= FADT2_REVISION_ID && 2718c2ecf20Sopenharmony_ci acpi_gbl_FADT.century) { 2728c2ecf20Sopenharmony_ci century = (yrs + 1900) / 100; 2738c2ecf20Sopenharmony_ci yrs %= 100; 2748c2ecf20Sopenharmony_ci } 2758c2ecf20Sopenharmony_ci#endif 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci /* These limits and adjustments are independent of 2788c2ecf20Sopenharmony_ci * whether the chip is in binary mode or not. 2798c2ecf20Sopenharmony_ci */ 2808c2ecf20Sopenharmony_ci if (yrs > 169) 2818c2ecf20Sopenharmony_ci return -EINVAL; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci if (yrs >= 100) 2848c2ecf20Sopenharmony_ci yrs -= 100; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci spin_lock_irqsave(&rtc_lock, flags); 2878c2ecf20Sopenharmony_ci save_control = CMOS_READ(RTC_CONTROL); 2888c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 2898c2ecf20Sopenharmony_ci if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) { 2908c2ecf20Sopenharmony_ci sec = bin2bcd(sec); 2918c2ecf20Sopenharmony_ci min = bin2bcd(min); 2928c2ecf20Sopenharmony_ci hrs = bin2bcd(hrs); 2938c2ecf20Sopenharmony_ci day = bin2bcd(day); 2948c2ecf20Sopenharmony_ci mon = bin2bcd(mon); 2958c2ecf20Sopenharmony_ci yrs = bin2bcd(yrs); 2968c2ecf20Sopenharmony_ci century = bin2bcd(century); 2978c2ecf20Sopenharmony_ci } 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci spin_lock_irqsave(&rtc_lock, flags); 3008c2ecf20Sopenharmony_ci save_control = CMOS_READ(RTC_CONTROL); 3018c2ecf20Sopenharmony_ci CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL); 3028c2ecf20Sopenharmony_ci save_freq_select = CMOS_READ(RTC_FREQ_SELECT); 3038c2ecf20Sopenharmony_ci if (apply_amd_register_a_behavior()) 3048c2ecf20Sopenharmony_ci CMOS_WRITE((save_freq_select & ~RTC_AMD_BANK_SELECT), RTC_FREQ_SELECT); 3058c2ecf20Sopenharmony_ci else 3068c2ecf20Sopenharmony_ci CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT); 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci#ifdef CONFIG_MACH_DECSTATION 3098c2ecf20Sopenharmony_ci CMOS_WRITE(real_yrs, RTC_DEC_YEAR); 3108c2ecf20Sopenharmony_ci#endif 3118c2ecf20Sopenharmony_ci CMOS_WRITE(yrs, RTC_YEAR); 3128c2ecf20Sopenharmony_ci CMOS_WRITE(mon, RTC_MONTH); 3138c2ecf20Sopenharmony_ci CMOS_WRITE(day, RTC_DAY_OF_MONTH); 3148c2ecf20Sopenharmony_ci CMOS_WRITE(hrs, RTC_HOURS); 3158c2ecf20Sopenharmony_ci CMOS_WRITE(min, RTC_MINUTES); 3168c2ecf20Sopenharmony_ci CMOS_WRITE(sec, RTC_SECONDS); 3178c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI 3188c2ecf20Sopenharmony_ci if (acpi_gbl_FADT.header.revision >= FADT2_REVISION_ID && 3198c2ecf20Sopenharmony_ci acpi_gbl_FADT.century) 3208c2ecf20Sopenharmony_ci CMOS_WRITE(century, acpi_gbl_FADT.century); 3218c2ecf20Sopenharmony_ci#endif 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci CMOS_WRITE(save_control, RTC_CONTROL); 3248c2ecf20Sopenharmony_ci CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT); 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc_lock, flags); 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci return 0; 3298c2ecf20Sopenharmony_ci} 3308c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(mc146818_set_time); 331