| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 1 | /* | 
|  | 2 | * IBM OPAL RTC driver | 
|  | 3 | * Copyright (C) 2014 IBM | 
|  | 4 | * | 
|  | 5 | * This program is free software; you can redistribute it and/or modify | 
|  | 6 | * it under the terms of the GNU General Public License as published by | 
|  | 7 | * the Free Software Foundation; either version 2 of the License, or | 
|  | 8 | * (at your option) any later version. | 
|  | 9 | * | 
|  | 10 | * This program is distributed in the hope that it will be useful, | 
|  | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
|  | 13 | * GNU General Public License for more details. | 
|  | 14 | * | 
|  | 15 | * You should have received a copy of the GNU General Public License | 
|  | 16 | * along with this program. | 
|  | 17 | */ | 
|  | 18 |  | 
| Joe Perches | a737e83 | 2015-04-16 12:46:14 -0700 | [diff] [blame] | 19 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 
|  | 20 |  | 
| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 21 | #define DRVNAME		"rtc-opal" | 
| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 22 |  | 
|  | 23 | #include <linux/module.h> | 
|  | 24 | #include <linux/err.h> | 
|  | 25 | #include <linux/rtc.h> | 
|  | 26 | #include <linux/delay.h> | 
|  | 27 | #include <linux/bcd.h> | 
|  | 28 | #include <linux/platform_device.h> | 
|  | 29 | #include <linux/of.h> | 
|  | 30 | #include <asm/opal.h> | 
|  | 31 | #include <asm/firmware.h> | 
|  | 32 |  | 
|  | 33 | static void opal_to_tm(u32 y_m_d, u64 h_m_s_ms, struct rtc_time *tm) | 
|  | 34 | { | 
|  | 35 | tm->tm_year = ((bcd2bin(y_m_d >> 24) * 100) + | 
|  | 36 | bcd2bin((y_m_d >> 16) & 0xff)) - 1900; | 
|  | 37 | tm->tm_mon  = bcd2bin((y_m_d >> 8) & 0xff) - 1; | 
|  | 38 | tm->tm_mday = bcd2bin(y_m_d & 0xff); | 
|  | 39 | tm->tm_hour = bcd2bin((h_m_s_ms >> 56) & 0xff); | 
|  | 40 | tm->tm_min  = bcd2bin((h_m_s_ms >> 48) & 0xff); | 
|  | 41 | tm->tm_sec  = bcd2bin((h_m_s_ms >> 40) & 0xff); | 
|  | 42 |  | 
| Daniel Axtens | 00b912b | 2015-12-15 18:09:14 +1100 | [diff] [blame] | 43 | tm->tm_wday = -1; | 
| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 44 | } | 
|  | 45 |  | 
|  | 46 | static void tm_to_opal(struct rtc_time *tm, u32 *y_m_d, u64 *h_m_s_ms) | 
|  | 47 | { | 
|  | 48 | *y_m_d |= ((u32)bin2bcd((tm->tm_year + 1900) / 100)) << 24; | 
|  | 49 | *y_m_d |= ((u32)bin2bcd((tm->tm_year + 1900) % 100)) << 16; | 
|  | 50 | *y_m_d |= ((u32)bin2bcd((tm->tm_mon + 1))) << 8; | 
|  | 51 | *y_m_d |= ((u32)bin2bcd(tm->tm_mday)); | 
|  | 52 |  | 
|  | 53 | *h_m_s_ms |= ((u64)bin2bcd(tm->tm_hour)) << 56; | 
|  | 54 | *h_m_s_ms |= ((u64)bin2bcd(tm->tm_min)) << 48; | 
|  | 55 | *h_m_s_ms |= ((u64)bin2bcd(tm->tm_sec)) << 40; | 
|  | 56 | } | 
|  | 57 |  | 
|  | 58 | static int opal_get_rtc_time(struct device *dev, struct rtc_time *tm) | 
|  | 59 | { | 
|  | 60 | long rc = OPAL_BUSY; | 
|  | 61 | u32 y_m_d; | 
|  | 62 | u64 h_m_s_ms; | 
|  | 63 | __be32 __y_m_d; | 
|  | 64 | __be64 __h_m_s_ms; | 
|  | 65 |  | 
|  | 66 | while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { | 
|  | 67 | rc = opal_rtc_read(&__y_m_d, &__h_m_s_ms); | 
|  | 68 | if (rc == OPAL_BUSY_EVENT) | 
|  | 69 | opal_poll_events(NULL); | 
|  | 70 | else | 
|  | 71 | msleep(10); | 
|  | 72 | } | 
|  | 73 |  | 
|  | 74 | if (rc != OPAL_SUCCESS) | 
|  | 75 | return -EIO; | 
|  | 76 |  | 
|  | 77 | y_m_d = be32_to_cpu(__y_m_d); | 
|  | 78 | h_m_s_ms = be64_to_cpu(__h_m_s_ms); | 
|  | 79 | opal_to_tm(y_m_d, h_m_s_ms, tm); | 
|  | 80 |  | 
|  | 81 | return 0; | 
|  | 82 | } | 
|  | 83 |  | 
|  | 84 | static int opal_set_rtc_time(struct device *dev, struct rtc_time *tm) | 
|  | 85 | { | 
|  | 86 | long rc = OPAL_BUSY; | 
|  | 87 | u32 y_m_d = 0; | 
|  | 88 | u64 h_m_s_ms = 0; | 
|  | 89 |  | 
|  | 90 | tm_to_opal(tm, &y_m_d, &h_m_s_ms); | 
|  | 91 | while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { | 
|  | 92 | rc = opal_rtc_write(y_m_d, h_m_s_ms); | 
|  | 93 | if (rc == OPAL_BUSY_EVENT) | 
|  | 94 | opal_poll_events(NULL); | 
|  | 95 | else | 
|  | 96 | msleep(10); | 
|  | 97 | } | 
|  | 98 |  | 
|  | 99 | return rc == OPAL_SUCCESS ? 0 : -EIO; | 
|  | 100 | } | 
|  | 101 |  | 
|  | 102 | /* | 
|  | 103 | * TPO	Timed Power-On | 
|  | 104 | * | 
|  | 105 | * TPO get/set OPAL calls care about the hour and min and to make it consistent | 
|  | 106 | * with the rtc utility time conversion functions, we use the 'u64' to store | 
|  | 107 | * its value and perform bit shift by 32 before use.. | 
|  | 108 | */ | 
|  | 109 | static int opal_get_tpo_time(struct device *dev, struct rtc_wkalrm *alarm) | 
|  | 110 | { | 
|  | 111 | __be32 __y_m_d, __h_m; | 
|  | 112 | struct opal_msg msg; | 
|  | 113 | int rc, token; | 
|  | 114 | u64 h_m_s_ms; | 
|  | 115 | u32 y_m_d; | 
|  | 116 |  | 
|  | 117 | token = opal_async_get_token_interruptible(); | 
|  | 118 | if (token < 0) { | 
|  | 119 | if (token != -ERESTARTSYS) | 
|  | 120 | pr_err("Failed to get the async token\n"); | 
|  | 121 |  | 
|  | 122 | return token; | 
|  | 123 | } | 
|  | 124 |  | 
|  | 125 | rc = opal_tpo_read(token, &__y_m_d, &__h_m); | 
|  | 126 | if (rc != OPAL_ASYNC_COMPLETION) { | 
|  | 127 | rc = -EIO; | 
|  | 128 | goto exit; | 
|  | 129 | } | 
|  | 130 |  | 
|  | 131 | rc = opal_async_wait_response(token, &msg); | 
|  | 132 | if (rc) { | 
|  | 133 | rc = -EIO; | 
|  | 134 | goto exit; | 
|  | 135 | } | 
|  | 136 |  | 
|  | 137 | rc = be64_to_cpu(msg.params[1]); | 
|  | 138 | if (rc != OPAL_SUCCESS) { | 
|  | 139 | rc = -EIO; | 
|  | 140 | goto exit; | 
|  | 141 | } | 
|  | 142 |  | 
|  | 143 | y_m_d = be32_to_cpu(__y_m_d); | 
|  | 144 | h_m_s_ms = ((u64)be32_to_cpu(__h_m) << 32); | 
|  | 145 | opal_to_tm(y_m_d, h_m_s_ms, &alarm->time); | 
|  | 146 |  | 
|  | 147 | exit: | 
|  | 148 | opal_async_release_token(token); | 
|  | 149 | return rc; | 
|  | 150 | } | 
|  | 151 |  | 
|  | 152 | /* Set Timed Power-On */ | 
|  | 153 | static int opal_set_tpo_time(struct device *dev, struct rtc_wkalrm *alarm) | 
|  | 154 | { | 
| Andrzej Hajda | c353009 | 2015-09-21 15:33:56 +0200 | [diff] [blame] | 155 | u64 h_m_s_ms = 0; | 
| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 156 | struct opal_msg msg; | 
|  | 157 | u32 y_m_d = 0; | 
| Andrzej Hajda | c353009 | 2015-09-21 15:33:56 +0200 | [diff] [blame] | 158 | int token, rc; | 
| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 159 |  | 
|  | 160 | tm_to_opal(&alarm->time, &y_m_d, &h_m_s_ms); | 
|  | 161 |  | 
|  | 162 | token = opal_async_get_token_interruptible(); | 
|  | 163 | if (token < 0) { | 
|  | 164 | if (token != -ERESTARTSYS) | 
|  | 165 | pr_err("Failed to get the async token\n"); | 
|  | 166 |  | 
|  | 167 | return token; | 
|  | 168 | } | 
|  | 169 |  | 
|  | 170 | /* TPO, we care about hour and minute */ | 
|  | 171 | rc = opal_tpo_write(token, y_m_d, | 
|  | 172 | (u32)((h_m_s_ms >> 32) & 0xffff0000)); | 
|  | 173 | if (rc != OPAL_ASYNC_COMPLETION) { | 
|  | 174 | rc = -EIO; | 
|  | 175 | goto exit; | 
|  | 176 | } | 
|  | 177 |  | 
|  | 178 | rc = opal_async_wait_response(token, &msg); | 
|  | 179 | if (rc) { | 
|  | 180 | rc = -EIO; | 
|  | 181 | goto exit; | 
|  | 182 | } | 
|  | 183 |  | 
|  | 184 | rc = be64_to_cpu(msg.params[1]); | 
|  | 185 | if (rc != OPAL_SUCCESS) | 
|  | 186 | rc = -EIO; | 
|  | 187 |  | 
|  | 188 | exit: | 
|  | 189 | opal_async_release_token(token); | 
|  | 190 | return rc; | 
|  | 191 | } | 
|  | 192 |  | 
| Vaibhav Jain | f4a2eec | 2015-07-14 13:28:28 +0530 | [diff] [blame] | 193 | static struct rtc_class_ops opal_rtc_ops = { | 
| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 194 | .read_time	= opal_get_rtc_time, | 
|  | 195 | .set_time	= opal_set_rtc_time, | 
| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 196 | }; | 
|  | 197 |  | 
|  | 198 | static int opal_rtc_probe(struct platform_device *pdev) | 
|  | 199 | { | 
|  | 200 | struct rtc_device *rtc; | 
|  | 201 |  | 
| Sudeep Holla | 347e40f | 2015-10-21 11:10:00 +0100 | [diff] [blame] | 202 | if (pdev->dev.of_node && | 
|  | 203 | (of_property_read_bool(pdev->dev.of_node, "wakeup-source") || | 
|  | 204 | of_property_read_bool(pdev->dev.of_node, "has-tpo")/* legacy */)) { | 
| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 205 | device_set_wakeup_capable(&pdev->dev, true); | 
| Vaibhav Jain | f4a2eec | 2015-07-14 13:28:28 +0530 | [diff] [blame] | 206 | opal_rtc_ops.read_alarm	= opal_get_tpo_time; | 
|  | 207 | opal_rtc_ops.set_alarm = opal_set_tpo_time; | 
|  | 208 | } | 
| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 209 |  | 
|  | 210 | rtc = devm_rtc_device_register(&pdev->dev, DRVNAME, &opal_rtc_ops, | 
|  | 211 | THIS_MODULE); | 
|  | 212 | if (IS_ERR(rtc)) | 
|  | 213 | return PTR_ERR(rtc); | 
|  | 214 |  | 
|  | 215 | rtc->uie_unsupported = 1; | 
|  | 216 |  | 
|  | 217 | return 0; | 
|  | 218 | } | 
|  | 219 |  | 
|  | 220 | static const struct of_device_id opal_rtc_match[] = { | 
|  | 221 | { | 
|  | 222 | .compatible	= "ibm,opal-rtc", | 
|  | 223 | }, | 
|  | 224 | { } | 
|  | 225 | }; | 
|  | 226 | MODULE_DEVICE_TABLE(of, opal_rtc_match); | 
|  | 227 |  | 
|  | 228 | static const struct platform_device_id opal_rtc_driver_ids[] = { | 
|  | 229 | { | 
|  | 230 | .name		= "opal-rtc", | 
|  | 231 | }, | 
|  | 232 | { } | 
|  | 233 | }; | 
|  | 234 | MODULE_DEVICE_TABLE(platform, opal_rtc_driver_ids); | 
|  | 235 |  | 
|  | 236 | static struct platform_driver opal_rtc_driver = { | 
|  | 237 | .probe		= opal_rtc_probe, | 
|  | 238 | .id_table	= opal_rtc_driver_ids, | 
|  | 239 | .driver		= { | 
|  | 240 | .name		= DRVNAME, | 
| Neelesh Gupta | 16b1d26 | 2014-10-14 14:08:36 +0530 | [diff] [blame] | 241 | .of_match_table	= opal_rtc_match, | 
|  | 242 | }, | 
|  | 243 | }; | 
|  | 244 |  | 
|  | 245 | static int __init opal_rtc_init(void) | 
|  | 246 | { | 
|  | 247 | if (!firmware_has_feature(FW_FEATURE_OPAL)) | 
|  | 248 | return -ENODEV; | 
|  | 249 |  | 
|  | 250 | return platform_driver_register(&opal_rtc_driver); | 
|  | 251 | } | 
|  | 252 |  | 
|  | 253 | static void __exit opal_rtc_exit(void) | 
|  | 254 | { | 
|  | 255 | platform_driver_unregister(&opal_rtc_driver); | 
|  | 256 | } | 
|  | 257 |  | 
|  | 258 | MODULE_AUTHOR("Neelesh Gupta <neelegup@linux.vnet.ibm.com>"); | 
|  | 259 | MODULE_DESCRIPTION("IBM OPAL RTC driver"); | 
|  | 260 | MODULE_LICENSE("GPL"); | 
|  | 261 |  | 
|  | 262 | module_init(opal_rtc_init); | 
|  | 263 | module_exit(opal_rtc_exit); |