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