162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * IBM OPAL RTC driver 462306a36Sopenharmony_ci * Copyright (C) 2014 IBM 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define DRVNAME "rtc-opal" 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/err.h> 1362306a36Sopenharmony_ci#include <linux/rtc.h> 1462306a36Sopenharmony_ci#include <linux/delay.h> 1562306a36Sopenharmony_ci#include <linux/bcd.h> 1662306a36Sopenharmony_ci#include <linux/platform_device.h> 1762306a36Sopenharmony_ci#include <linux/of.h> 1862306a36Sopenharmony_ci#include <asm/opal.h> 1962306a36Sopenharmony_ci#include <asm/firmware.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic void opal_to_tm(u32 y_m_d, u64 h_m_s_ms, struct rtc_time *tm) 2262306a36Sopenharmony_ci{ 2362306a36Sopenharmony_ci tm->tm_year = ((bcd2bin(y_m_d >> 24) * 100) + 2462306a36Sopenharmony_ci bcd2bin((y_m_d >> 16) & 0xff)) - 1900; 2562306a36Sopenharmony_ci tm->tm_mon = bcd2bin((y_m_d >> 8) & 0xff) - 1; 2662306a36Sopenharmony_ci tm->tm_mday = bcd2bin(y_m_d & 0xff); 2762306a36Sopenharmony_ci tm->tm_hour = bcd2bin((h_m_s_ms >> 56) & 0xff); 2862306a36Sopenharmony_ci tm->tm_min = bcd2bin((h_m_s_ms >> 48) & 0xff); 2962306a36Sopenharmony_ci tm->tm_sec = bcd2bin((h_m_s_ms >> 40) & 0xff); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci tm->tm_wday = -1; 3262306a36Sopenharmony_ci} 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic void tm_to_opal(struct rtc_time *tm, u32 *y_m_d, u64 *h_m_s_ms) 3562306a36Sopenharmony_ci{ 3662306a36Sopenharmony_ci *y_m_d |= ((u32)bin2bcd((tm->tm_year + 1900) / 100)) << 24; 3762306a36Sopenharmony_ci *y_m_d |= ((u32)bin2bcd((tm->tm_year + 1900) % 100)) << 16; 3862306a36Sopenharmony_ci *y_m_d |= ((u32)bin2bcd((tm->tm_mon + 1))) << 8; 3962306a36Sopenharmony_ci *y_m_d |= ((u32)bin2bcd(tm->tm_mday)); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci *h_m_s_ms |= ((u64)bin2bcd(tm->tm_hour)) << 56; 4262306a36Sopenharmony_ci *h_m_s_ms |= ((u64)bin2bcd(tm->tm_min)) << 48; 4362306a36Sopenharmony_ci *h_m_s_ms |= ((u64)bin2bcd(tm->tm_sec)) << 40; 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic int opal_get_rtc_time(struct device *dev, struct rtc_time *tm) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci s64 rc = OPAL_BUSY; 4962306a36Sopenharmony_ci int retries = 10; 5062306a36Sopenharmony_ci u32 y_m_d; 5162306a36Sopenharmony_ci u64 h_m_s_ms; 5262306a36Sopenharmony_ci __be32 __y_m_d; 5362306a36Sopenharmony_ci __be64 __h_m_s_ms; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { 5662306a36Sopenharmony_ci rc = opal_rtc_read(&__y_m_d, &__h_m_s_ms); 5762306a36Sopenharmony_ci if (rc == OPAL_BUSY_EVENT) { 5862306a36Sopenharmony_ci msleep(OPAL_BUSY_DELAY_MS); 5962306a36Sopenharmony_ci opal_poll_events(NULL); 6062306a36Sopenharmony_ci } else if (rc == OPAL_BUSY) { 6162306a36Sopenharmony_ci msleep(OPAL_BUSY_DELAY_MS); 6262306a36Sopenharmony_ci } else if (rc == OPAL_HARDWARE || rc == OPAL_INTERNAL_ERROR) { 6362306a36Sopenharmony_ci if (retries--) { 6462306a36Sopenharmony_ci msleep(10); /* Wait 10ms before retry */ 6562306a36Sopenharmony_ci rc = OPAL_BUSY; /* go around again */ 6662306a36Sopenharmony_ci } 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci if (rc != OPAL_SUCCESS) 7162306a36Sopenharmony_ci return -EIO; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci y_m_d = be32_to_cpu(__y_m_d); 7462306a36Sopenharmony_ci h_m_s_ms = be64_to_cpu(__h_m_s_ms); 7562306a36Sopenharmony_ci opal_to_tm(y_m_d, h_m_s_ms, tm); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci return 0; 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic int opal_set_rtc_time(struct device *dev, struct rtc_time *tm) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci s64 rc = OPAL_BUSY; 8362306a36Sopenharmony_ci int retries = 10; 8462306a36Sopenharmony_ci u32 y_m_d = 0; 8562306a36Sopenharmony_ci u64 h_m_s_ms = 0; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci tm_to_opal(tm, &y_m_d, &h_m_s_ms); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { 9062306a36Sopenharmony_ci rc = opal_rtc_write(y_m_d, h_m_s_ms); 9162306a36Sopenharmony_ci if (rc == OPAL_BUSY_EVENT) { 9262306a36Sopenharmony_ci msleep(OPAL_BUSY_DELAY_MS); 9362306a36Sopenharmony_ci opal_poll_events(NULL); 9462306a36Sopenharmony_ci } else if (rc == OPAL_BUSY) { 9562306a36Sopenharmony_ci msleep(OPAL_BUSY_DELAY_MS); 9662306a36Sopenharmony_ci } else if (rc == OPAL_HARDWARE || rc == OPAL_INTERNAL_ERROR) { 9762306a36Sopenharmony_ci if (retries--) { 9862306a36Sopenharmony_ci msleep(10); /* Wait 10ms before retry */ 9962306a36Sopenharmony_ci rc = OPAL_BUSY; /* go around again */ 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci return rc == OPAL_SUCCESS ? 0 : -EIO; 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci/* 10862306a36Sopenharmony_ci * TPO Timed Power-On 10962306a36Sopenharmony_ci * 11062306a36Sopenharmony_ci * TPO get/set OPAL calls care about the hour and min and to make it consistent 11162306a36Sopenharmony_ci * with the rtc utility time conversion functions, we use the 'u64' to store 11262306a36Sopenharmony_ci * its value and perform bit shift by 32 before use.. 11362306a36Sopenharmony_ci */ 11462306a36Sopenharmony_cistatic int opal_get_tpo_time(struct device *dev, struct rtc_wkalrm *alarm) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci __be32 __y_m_d, __h_m; 11762306a36Sopenharmony_ci struct opal_msg msg; 11862306a36Sopenharmony_ci int rc, token; 11962306a36Sopenharmony_ci u64 h_m_s_ms; 12062306a36Sopenharmony_ci u32 y_m_d; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci token = opal_async_get_token_interruptible(); 12362306a36Sopenharmony_ci if (token < 0) { 12462306a36Sopenharmony_ci if (token != -ERESTARTSYS) 12562306a36Sopenharmony_ci pr_err("Failed to get the async token\n"); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci return token; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci rc = opal_tpo_read(token, &__y_m_d, &__h_m); 13162306a36Sopenharmony_ci if (rc != OPAL_ASYNC_COMPLETION) { 13262306a36Sopenharmony_ci rc = -EIO; 13362306a36Sopenharmony_ci goto exit; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci rc = opal_async_wait_response(token, &msg); 13762306a36Sopenharmony_ci if (rc) { 13862306a36Sopenharmony_ci rc = -EIO; 13962306a36Sopenharmony_ci goto exit; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci rc = opal_get_async_rc(msg); 14362306a36Sopenharmony_ci if (rc != OPAL_SUCCESS) { 14462306a36Sopenharmony_ci rc = -EIO; 14562306a36Sopenharmony_ci goto exit; 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci y_m_d = be32_to_cpu(__y_m_d); 14962306a36Sopenharmony_ci h_m_s_ms = ((u64)be32_to_cpu(__h_m) << 32); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci /* check if no alarm is set */ 15262306a36Sopenharmony_ci if (y_m_d == 0 && h_m_s_ms == 0) { 15362306a36Sopenharmony_ci pr_debug("No alarm is set\n"); 15462306a36Sopenharmony_ci rc = -ENOENT; 15562306a36Sopenharmony_ci goto exit; 15662306a36Sopenharmony_ci } else { 15762306a36Sopenharmony_ci pr_debug("Alarm set to %x %llx\n", y_m_d, h_m_s_ms); 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci opal_to_tm(y_m_d, h_m_s_ms, &alarm->time); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ciexit: 16362306a36Sopenharmony_ci opal_async_release_token(token); 16462306a36Sopenharmony_ci return rc; 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci/* Set Timed Power-On */ 16862306a36Sopenharmony_cistatic int opal_set_tpo_time(struct device *dev, struct rtc_wkalrm *alarm) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci u64 h_m_s_ms = 0; 17162306a36Sopenharmony_ci struct opal_msg msg; 17262306a36Sopenharmony_ci u32 y_m_d = 0; 17362306a36Sopenharmony_ci int token, rc; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* if alarm is enabled */ 17662306a36Sopenharmony_ci if (alarm->enabled) { 17762306a36Sopenharmony_ci tm_to_opal(&alarm->time, &y_m_d, &h_m_s_ms); 17862306a36Sopenharmony_ci pr_debug("Alarm set to %x %llx\n", y_m_d, h_m_s_ms); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci } else { 18162306a36Sopenharmony_ci pr_debug("Alarm getting disabled\n"); 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci token = opal_async_get_token_interruptible(); 18562306a36Sopenharmony_ci if (token < 0) { 18662306a36Sopenharmony_ci if (token != -ERESTARTSYS) 18762306a36Sopenharmony_ci pr_err("Failed to get the async token\n"); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return token; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci /* TPO, we care about hour and minute */ 19362306a36Sopenharmony_ci rc = opal_tpo_write(token, y_m_d, 19462306a36Sopenharmony_ci (u32)((h_m_s_ms >> 32) & 0xffff0000)); 19562306a36Sopenharmony_ci if (rc != OPAL_ASYNC_COMPLETION) { 19662306a36Sopenharmony_ci rc = -EIO; 19762306a36Sopenharmony_ci goto exit; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci rc = opal_async_wait_response(token, &msg); 20162306a36Sopenharmony_ci if (rc) { 20262306a36Sopenharmony_ci rc = -EIO; 20362306a36Sopenharmony_ci goto exit; 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci rc = opal_get_async_rc(msg); 20762306a36Sopenharmony_ci if (rc != OPAL_SUCCESS) 20862306a36Sopenharmony_ci rc = -EIO; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ciexit: 21162306a36Sopenharmony_ci opal_async_release_token(token); 21262306a36Sopenharmony_ci return rc; 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic int opal_tpo_alarm_irq_enable(struct device *dev, unsigned int enabled) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct rtc_wkalrm alarm = { .enabled = 0 }; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* 22062306a36Sopenharmony_ci * TPO is automatically enabled when opal_set_tpo_time() is called with 22162306a36Sopenharmony_ci * non-zero rtc-time. We only handle disable case which needs to be 22262306a36Sopenharmony_ci * explicitly told to opal. 22362306a36Sopenharmony_ci */ 22462306a36Sopenharmony_ci return enabled ? 0 : opal_set_tpo_time(dev, &alarm); 22562306a36Sopenharmony_ci} 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_cistatic const struct rtc_class_ops opal_rtc_ops = { 22862306a36Sopenharmony_ci .read_time = opal_get_rtc_time, 22962306a36Sopenharmony_ci .set_time = opal_set_rtc_time, 23062306a36Sopenharmony_ci .read_alarm = opal_get_tpo_time, 23162306a36Sopenharmony_ci .set_alarm = opal_set_tpo_time, 23262306a36Sopenharmony_ci .alarm_irq_enable = opal_tpo_alarm_irq_enable, 23362306a36Sopenharmony_ci}; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_cistatic int opal_rtc_probe(struct platform_device *pdev) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci struct rtc_device *rtc; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci rtc = devm_rtc_allocate_device(&pdev->dev); 24062306a36Sopenharmony_ci if (IS_ERR(rtc)) 24162306a36Sopenharmony_ci return PTR_ERR(rtc); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci if (pdev->dev.of_node && 24462306a36Sopenharmony_ci (of_property_read_bool(pdev->dev.of_node, "wakeup-source") || 24562306a36Sopenharmony_ci of_property_read_bool(pdev->dev.of_node, "has-tpo")/* legacy */)) 24662306a36Sopenharmony_ci device_set_wakeup_capable(&pdev->dev, true); 24762306a36Sopenharmony_ci else 24862306a36Sopenharmony_ci clear_bit(RTC_FEATURE_ALARM, rtc->features); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci rtc->ops = &opal_rtc_ops; 25162306a36Sopenharmony_ci rtc->range_min = RTC_TIMESTAMP_BEGIN_0000; 25262306a36Sopenharmony_ci rtc->range_max = RTC_TIMESTAMP_END_9999; 25362306a36Sopenharmony_ci clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci return devm_rtc_register_device(rtc); 25662306a36Sopenharmony_ci} 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_cistatic const struct of_device_id opal_rtc_match[] = { 25962306a36Sopenharmony_ci { 26062306a36Sopenharmony_ci .compatible = "ibm,opal-rtc", 26162306a36Sopenharmony_ci }, 26262306a36Sopenharmony_ci { } 26362306a36Sopenharmony_ci}; 26462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, opal_rtc_match); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic const struct platform_device_id opal_rtc_driver_ids[] = { 26762306a36Sopenharmony_ci { 26862306a36Sopenharmony_ci .name = "opal-rtc", 26962306a36Sopenharmony_ci }, 27062306a36Sopenharmony_ci { } 27162306a36Sopenharmony_ci}; 27262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(platform, opal_rtc_driver_ids); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_cistatic struct platform_driver opal_rtc_driver = { 27562306a36Sopenharmony_ci .probe = opal_rtc_probe, 27662306a36Sopenharmony_ci .id_table = opal_rtc_driver_ids, 27762306a36Sopenharmony_ci .driver = { 27862306a36Sopenharmony_ci .name = DRVNAME, 27962306a36Sopenharmony_ci .of_match_table = opal_rtc_match, 28062306a36Sopenharmony_ci }, 28162306a36Sopenharmony_ci}; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cistatic int __init opal_rtc_init(void) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci if (!firmware_has_feature(FW_FEATURE_OPAL)) 28662306a36Sopenharmony_ci return -ENODEV; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci return platform_driver_register(&opal_rtc_driver); 28962306a36Sopenharmony_ci} 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_cistatic void __exit opal_rtc_exit(void) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci platform_driver_unregister(&opal_rtc_driver); 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ciMODULE_AUTHOR("Neelesh Gupta <neelegup@linux.vnet.ibm.com>"); 29762306a36Sopenharmony_ciMODULE_DESCRIPTION("IBM OPAL RTC driver"); 29862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cimodule_init(opal_rtc_init); 30162306a36Sopenharmony_cimodule_exit(opal_rtc_exit); 302