18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * IBM OPAL RTC driver 48c2ecf20Sopenharmony_ci * Copyright (C) 2014 IBM 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#define DRVNAME "rtc-opal" 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/err.h> 138c2ecf20Sopenharmony_ci#include <linux/rtc.h> 148c2ecf20Sopenharmony_ci#include <linux/delay.h> 158c2ecf20Sopenharmony_ci#include <linux/bcd.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/of.h> 188c2ecf20Sopenharmony_ci#include <asm/opal.h> 198c2ecf20Sopenharmony_ci#include <asm/firmware.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic void opal_to_tm(u32 y_m_d, u64 h_m_s_ms, struct rtc_time *tm) 228c2ecf20Sopenharmony_ci{ 238c2ecf20Sopenharmony_ci tm->tm_year = ((bcd2bin(y_m_d >> 24) * 100) + 248c2ecf20Sopenharmony_ci bcd2bin((y_m_d >> 16) & 0xff)) - 1900; 258c2ecf20Sopenharmony_ci tm->tm_mon = bcd2bin((y_m_d >> 8) & 0xff) - 1; 268c2ecf20Sopenharmony_ci tm->tm_mday = bcd2bin(y_m_d & 0xff); 278c2ecf20Sopenharmony_ci tm->tm_hour = bcd2bin((h_m_s_ms >> 56) & 0xff); 288c2ecf20Sopenharmony_ci tm->tm_min = bcd2bin((h_m_s_ms >> 48) & 0xff); 298c2ecf20Sopenharmony_ci tm->tm_sec = bcd2bin((h_m_s_ms >> 40) & 0xff); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci tm->tm_wday = -1; 328c2ecf20Sopenharmony_ci} 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic void tm_to_opal(struct rtc_time *tm, u32 *y_m_d, u64 *h_m_s_ms) 358c2ecf20Sopenharmony_ci{ 368c2ecf20Sopenharmony_ci *y_m_d |= ((u32)bin2bcd((tm->tm_year + 1900) / 100)) << 24; 378c2ecf20Sopenharmony_ci *y_m_d |= ((u32)bin2bcd((tm->tm_year + 1900) % 100)) << 16; 388c2ecf20Sopenharmony_ci *y_m_d |= ((u32)bin2bcd((tm->tm_mon + 1))) << 8; 398c2ecf20Sopenharmony_ci *y_m_d |= ((u32)bin2bcd(tm->tm_mday)); 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci *h_m_s_ms |= ((u64)bin2bcd(tm->tm_hour)) << 56; 428c2ecf20Sopenharmony_ci *h_m_s_ms |= ((u64)bin2bcd(tm->tm_min)) << 48; 438c2ecf20Sopenharmony_ci *h_m_s_ms |= ((u64)bin2bcd(tm->tm_sec)) << 40; 448c2ecf20Sopenharmony_ci} 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic int opal_get_rtc_time(struct device *dev, struct rtc_time *tm) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci s64 rc = OPAL_BUSY; 498c2ecf20Sopenharmony_ci int retries = 10; 508c2ecf20Sopenharmony_ci u32 y_m_d; 518c2ecf20Sopenharmony_ci u64 h_m_s_ms; 528c2ecf20Sopenharmony_ci __be32 __y_m_d; 538c2ecf20Sopenharmony_ci __be64 __h_m_s_ms; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { 568c2ecf20Sopenharmony_ci rc = opal_rtc_read(&__y_m_d, &__h_m_s_ms); 578c2ecf20Sopenharmony_ci if (rc == OPAL_BUSY_EVENT) { 588c2ecf20Sopenharmony_ci msleep(OPAL_BUSY_DELAY_MS); 598c2ecf20Sopenharmony_ci opal_poll_events(NULL); 608c2ecf20Sopenharmony_ci } else if (rc == OPAL_BUSY) { 618c2ecf20Sopenharmony_ci msleep(OPAL_BUSY_DELAY_MS); 628c2ecf20Sopenharmony_ci } else if (rc == OPAL_HARDWARE || rc == OPAL_INTERNAL_ERROR) { 638c2ecf20Sopenharmony_ci if (retries--) { 648c2ecf20Sopenharmony_ci msleep(10); /* Wait 10ms before retry */ 658c2ecf20Sopenharmony_ci rc = OPAL_BUSY; /* go around again */ 668c2ecf20Sopenharmony_ci } 678c2ecf20Sopenharmony_ci } 688c2ecf20Sopenharmony_ci } 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci if (rc != OPAL_SUCCESS) 718c2ecf20Sopenharmony_ci return -EIO; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci y_m_d = be32_to_cpu(__y_m_d); 748c2ecf20Sopenharmony_ci h_m_s_ms = be64_to_cpu(__h_m_s_ms); 758c2ecf20Sopenharmony_ci opal_to_tm(y_m_d, h_m_s_ms, tm); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci return 0; 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic int opal_set_rtc_time(struct device *dev, struct rtc_time *tm) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci s64 rc = OPAL_BUSY; 838c2ecf20Sopenharmony_ci int retries = 10; 848c2ecf20Sopenharmony_ci u32 y_m_d = 0; 858c2ecf20Sopenharmony_ci u64 h_m_s_ms = 0; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci tm_to_opal(tm, &y_m_d, &h_m_s_ms); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { 908c2ecf20Sopenharmony_ci rc = opal_rtc_write(y_m_d, h_m_s_ms); 918c2ecf20Sopenharmony_ci if (rc == OPAL_BUSY_EVENT) { 928c2ecf20Sopenharmony_ci msleep(OPAL_BUSY_DELAY_MS); 938c2ecf20Sopenharmony_ci opal_poll_events(NULL); 948c2ecf20Sopenharmony_ci } else if (rc == OPAL_BUSY) { 958c2ecf20Sopenharmony_ci msleep(OPAL_BUSY_DELAY_MS); 968c2ecf20Sopenharmony_ci } else if (rc == OPAL_HARDWARE || rc == OPAL_INTERNAL_ERROR) { 978c2ecf20Sopenharmony_ci if (retries--) { 988c2ecf20Sopenharmony_ci msleep(10); /* Wait 10ms before retry */ 998c2ecf20Sopenharmony_ci rc = OPAL_BUSY; /* go around again */ 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci } 1028c2ecf20Sopenharmony_ci } 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci return rc == OPAL_SUCCESS ? 0 : -EIO; 1058c2ecf20Sopenharmony_ci} 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci/* 1088c2ecf20Sopenharmony_ci * TPO Timed Power-On 1098c2ecf20Sopenharmony_ci * 1108c2ecf20Sopenharmony_ci * TPO get/set OPAL calls care about the hour and min and to make it consistent 1118c2ecf20Sopenharmony_ci * with the rtc utility time conversion functions, we use the 'u64' to store 1128c2ecf20Sopenharmony_ci * its value and perform bit shift by 32 before use.. 1138c2ecf20Sopenharmony_ci */ 1148c2ecf20Sopenharmony_cistatic int opal_get_tpo_time(struct device *dev, struct rtc_wkalrm *alarm) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci __be32 __y_m_d, __h_m; 1178c2ecf20Sopenharmony_ci struct opal_msg msg; 1188c2ecf20Sopenharmony_ci int rc, token; 1198c2ecf20Sopenharmony_ci u64 h_m_s_ms; 1208c2ecf20Sopenharmony_ci u32 y_m_d; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci token = opal_async_get_token_interruptible(); 1238c2ecf20Sopenharmony_ci if (token < 0) { 1248c2ecf20Sopenharmony_ci if (token != -ERESTARTSYS) 1258c2ecf20Sopenharmony_ci pr_err("Failed to get the async token\n"); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci return token; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci rc = opal_tpo_read(token, &__y_m_d, &__h_m); 1318c2ecf20Sopenharmony_ci if (rc != OPAL_ASYNC_COMPLETION) { 1328c2ecf20Sopenharmony_ci rc = -EIO; 1338c2ecf20Sopenharmony_ci goto exit; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci rc = opal_async_wait_response(token, &msg); 1378c2ecf20Sopenharmony_ci if (rc) { 1388c2ecf20Sopenharmony_ci rc = -EIO; 1398c2ecf20Sopenharmony_ci goto exit; 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci rc = opal_get_async_rc(msg); 1438c2ecf20Sopenharmony_ci if (rc != OPAL_SUCCESS) { 1448c2ecf20Sopenharmony_ci rc = -EIO; 1458c2ecf20Sopenharmony_ci goto exit; 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci y_m_d = be32_to_cpu(__y_m_d); 1498c2ecf20Sopenharmony_ci h_m_s_ms = ((u64)be32_to_cpu(__h_m) << 32); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* check if no alarm is set */ 1528c2ecf20Sopenharmony_ci if (y_m_d == 0 && h_m_s_ms == 0) { 1538c2ecf20Sopenharmony_ci pr_debug("No alarm is set\n"); 1548c2ecf20Sopenharmony_ci rc = -ENOENT; 1558c2ecf20Sopenharmony_ci goto exit; 1568c2ecf20Sopenharmony_ci } else { 1578c2ecf20Sopenharmony_ci pr_debug("Alarm set to %x %llx\n", y_m_d, h_m_s_ms); 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci opal_to_tm(y_m_d, h_m_s_ms, &alarm->time); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ciexit: 1638c2ecf20Sopenharmony_ci opal_async_release_token(token); 1648c2ecf20Sopenharmony_ci return rc; 1658c2ecf20Sopenharmony_ci} 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci/* Set Timed Power-On */ 1688c2ecf20Sopenharmony_cistatic int opal_set_tpo_time(struct device *dev, struct rtc_wkalrm *alarm) 1698c2ecf20Sopenharmony_ci{ 1708c2ecf20Sopenharmony_ci u64 h_m_s_ms = 0; 1718c2ecf20Sopenharmony_ci struct opal_msg msg; 1728c2ecf20Sopenharmony_ci u32 y_m_d = 0; 1738c2ecf20Sopenharmony_ci int token, rc; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci /* if alarm is enabled */ 1768c2ecf20Sopenharmony_ci if (alarm->enabled) { 1778c2ecf20Sopenharmony_ci tm_to_opal(&alarm->time, &y_m_d, &h_m_s_ms); 1788c2ecf20Sopenharmony_ci pr_debug("Alarm set to %x %llx\n", y_m_d, h_m_s_ms); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci } else { 1818c2ecf20Sopenharmony_ci pr_debug("Alarm getting disabled\n"); 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci token = opal_async_get_token_interruptible(); 1858c2ecf20Sopenharmony_ci if (token < 0) { 1868c2ecf20Sopenharmony_ci if (token != -ERESTARTSYS) 1878c2ecf20Sopenharmony_ci pr_err("Failed to get the async token\n"); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci return token; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci /* TPO, we care about hour and minute */ 1938c2ecf20Sopenharmony_ci rc = opal_tpo_write(token, y_m_d, 1948c2ecf20Sopenharmony_ci (u32)((h_m_s_ms >> 32) & 0xffff0000)); 1958c2ecf20Sopenharmony_ci if (rc != OPAL_ASYNC_COMPLETION) { 1968c2ecf20Sopenharmony_ci rc = -EIO; 1978c2ecf20Sopenharmony_ci goto exit; 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci rc = opal_async_wait_response(token, &msg); 2018c2ecf20Sopenharmony_ci if (rc) { 2028c2ecf20Sopenharmony_ci rc = -EIO; 2038c2ecf20Sopenharmony_ci goto exit; 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci rc = opal_get_async_rc(msg); 2078c2ecf20Sopenharmony_ci if (rc != OPAL_SUCCESS) 2088c2ecf20Sopenharmony_ci rc = -EIO; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ciexit: 2118c2ecf20Sopenharmony_ci opal_async_release_token(token); 2128c2ecf20Sopenharmony_ci return rc; 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic int opal_tpo_alarm_irq_enable(struct device *dev, unsigned int enabled) 2168c2ecf20Sopenharmony_ci{ 2178c2ecf20Sopenharmony_ci struct rtc_wkalrm alarm = { .enabled = 0 }; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci /* 2208c2ecf20Sopenharmony_ci * TPO is automatically enabled when opal_set_tpo_time() is called with 2218c2ecf20Sopenharmony_ci * non-zero rtc-time. We only handle disable case which needs to be 2228c2ecf20Sopenharmony_ci * explicitly told to opal. 2238c2ecf20Sopenharmony_ci */ 2248c2ecf20Sopenharmony_ci return enabled ? 0 : opal_set_tpo_time(dev, &alarm); 2258c2ecf20Sopenharmony_ci} 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cistatic struct rtc_class_ops opal_rtc_ops = { 2288c2ecf20Sopenharmony_ci .read_time = opal_get_rtc_time, 2298c2ecf20Sopenharmony_ci .set_time = opal_set_rtc_time, 2308c2ecf20Sopenharmony_ci}; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic int opal_rtc_probe(struct platform_device *pdev) 2338c2ecf20Sopenharmony_ci{ 2348c2ecf20Sopenharmony_ci struct rtc_device *rtc; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci if (pdev->dev.of_node && 2378c2ecf20Sopenharmony_ci (of_property_read_bool(pdev->dev.of_node, "wakeup-source") || 2388c2ecf20Sopenharmony_ci of_property_read_bool(pdev->dev.of_node, "has-tpo")/* legacy */)) { 2398c2ecf20Sopenharmony_ci device_set_wakeup_capable(&pdev->dev, true); 2408c2ecf20Sopenharmony_ci opal_rtc_ops.read_alarm = opal_get_tpo_time; 2418c2ecf20Sopenharmony_ci opal_rtc_ops.set_alarm = opal_set_tpo_time; 2428c2ecf20Sopenharmony_ci opal_rtc_ops.alarm_irq_enable = opal_tpo_alarm_irq_enable; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci rtc = devm_rtc_device_register(&pdev->dev, DRVNAME, &opal_rtc_ops, 2468c2ecf20Sopenharmony_ci THIS_MODULE); 2478c2ecf20Sopenharmony_ci if (IS_ERR(rtc)) 2488c2ecf20Sopenharmony_ci return PTR_ERR(rtc); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci rtc->uie_unsupported = 1; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci return 0; 2538c2ecf20Sopenharmony_ci} 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_cistatic const struct of_device_id opal_rtc_match[] = { 2568c2ecf20Sopenharmony_ci { 2578c2ecf20Sopenharmony_ci .compatible = "ibm,opal-rtc", 2588c2ecf20Sopenharmony_ci }, 2598c2ecf20Sopenharmony_ci { } 2608c2ecf20Sopenharmony_ci}; 2618c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, opal_rtc_match); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cistatic const struct platform_device_id opal_rtc_driver_ids[] = { 2648c2ecf20Sopenharmony_ci { 2658c2ecf20Sopenharmony_ci .name = "opal-rtc", 2668c2ecf20Sopenharmony_ci }, 2678c2ecf20Sopenharmony_ci { } 2688c2ecf20Sopenharmony_ci}; 2698c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(platform, opal_rtc_driver_ids); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_cistatic struct platform_driver opal_rtc_driver = { 2728c2ecf20Sopenharmony_ci .probe = opal_rtc_probe, 2738c2ecf20Sopenharmony_ci .id_table = opal_rtc_driver_ids, 2748c2ecf20Sopenharmony_ci .driver = { 2758c2ecf20Sopenharmony_ci .name = DRVNAME, 2768c2ecf20Sopenharmony_ci .of_match_table = opal_rtc_match, 2778c2ecf20Sopenharmony_ci }, 2788c2ecf20Sopenharmony_ci}; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_cistatic int __init opal_rtc_init(void) 2818c2ecf20Sopenharmony_ci{ 2828c2ecf20Sopenharmony_ci if (!firmware_has_feature(FW_FEATURE_OPAL)) 2838c2ecf20Sopenharmony_ci return -ENODEV; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci return platform_driver_register(&opal_rtc_driver); 2868c2ecf20Sopenharmony_ci} 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_cistatic void __exit opal_rtc_exit(void) 2898c2ecf20Sopenharmony_ci{ 2908c2ecf20Sopenharmony_ci platform_driver_unregister(&opal_rtc_driver); 2918c2ecf20Sopenharmony_ci} 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ciMODULE_AUTHOR("Neelesh Gupta <neelegup@linux.vnet.ibm.com>"); 2948c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("IBM OPAL RTC driver"); 2958c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_cimodule_init(opal_rtc_init); 2988c2ecf20Sopenharmony_cimodule_exit(opal_rtc_exit); 299