1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * RTC interface for Wilco Embedded Controller with R/W abilities 4 * 5 * Copyright 2018 Google LLC 6 * 7 * The corresponding platform device is typically registered in 8 * drivers/platform/chrome/wilco_ec/core.c 9 */ 10 11 #include <linux/bcd.h> 12 #include <linux/err.h> 13 #include <linux/kernel.h> 14 #include <linux/module.h> 15 #include <linux/platform_device.h> 16 #include <linux/platform_data/wilco-ec.h> 17 #include <linux/rtc.h> 18 #include <linux/timekeeping.h> 19 20 #define EC_COMMAND_CMOS 0x7c 21 #define EC_CMOS_TOD_WRITE 0x02 22 #define EC_CMOS_TOD_READ 0x08 23 24 /* Message sent to the EC to request the current time. */ 25 struct ec_rtc_read_request { 26 u8 command; 27 u8 reserved; 28 u8 param; 29 } __packed; 30 static struct ec_rtc_read_request read_rq = { 31 .command = EC_COMMAND_CMOS, 32 .param = EC_CMOS_TOD_READ, 33 }; 34 35 /** 36 * struct ec_rtc_read_response - Format of RTC returned by EC. 37 * @reserved: Unused byte 38 * @second: Second value (0..59) 39 * @minute: Minute value (0..59) 40 * @hour: Hour value (0..23) 41 * @day: Day value (1..31) 42 * @month: Month value (1..12) 43 * @year: Year value (full year % 100) 44 * @century: Century value (full year / 100) 45 * 46 * All values are presented in binary (not BCD). 47 */ 48 struct ec_rtc_read_response { 49 u8 reserved; 50 u8 second; 51 u8 minute; 52 u8 hour; 53 u8 day; 54 u8 month; 55 u8 year; 56 u8 century; 57 } __packed; 58 59 /** 60 * struct ec_rtc_write_request - Format of RTC sent to the EC. 61 * @command: Always EC_COMMAND_CMOS 62 * @reserved: Unused byte 63 * @param: Always EC_CMOS_TOD_WRITE 64 * @century: Century value (full year / 100) 65 * @year: Year value (full year % 100) 66 * @month: Month value (1..12) 67 * @day: Day value (1..31) 68 * @hour: Hour value (0..23) 69 * @minute: Minute value (0..59) 70 * @second: Second value (0..59) 71 * @weekday: Day of the week (0=Saturday) 72 * 73 * All values are presented in BCD. 74 */ 75 struct ec_rtc_write_request { 76 u8 command; 77 u8 reserved; 78 u8 param; 79 u8 century; 80 u8 year; 81 u8 month; 82 u8 day; 83 u8 hour; 84 u8 minute; 85 u8 second; 86 u8 weekday; 87 } __packed; 88 89 static int wilco_ec_rtc_read(struct device *dev, struct rtc_time *tm) 90 { 91 struct wilco_ec_device *ec = dev_get_drvdata(dev->parent); 92 struct ec_rtc_read_response rtc; 93 struct wilco_ec_message msg; 94 int ret; 95 96 memset(&msg, 0, sizeof(msg)); 97 msg.type = WILCO_EC_MSG_LEGACY; 98 msg.request_data = &read_rq; 99 msg.request_size = sizeof(read_rq); 100 msg.response_data = &rtc; 101 msg.response_size = sizeof(rtc); 102 103 ret = wilco_ec_mailbox(ec, &msg); 104 if (ret < 0) 105 return ret; 106 107 tm->tm_sec = rtc.second; 108 tm->tm_min = rtc.minute; 109 tm->tm_hour = rtc.hour; 110 tm->tm_mday = rtc.day; 111 tm->tm_mon = rtc.month - 1; 112 tm->tm_year = rtc.year + (rtc.century * 100) - 1900; 113 /* Ignore other tm fields, man rtc says userspace shouldn't use them. */ 114 115 if (rtc_valid_tm(tm)) { 116 dev_err(dev, "Time from RTC is invalid: %ptRr\n", tm); 117 return -EIO; 118 } 119 120 return 0; 121 } 122 123 static int wilco_ec_rtc_write(struct device *dev, struct rtc_time *tm) 124 { 125 struct wilco_ec_device *ec = dev_get_drvdata(dev->parent); 126 struct ec_rtc_write_request rtc; 127 struct wilco_ec_message msg; 128 int year = tm->tm_year + 1900; 129 /* 130 * Convert from 0=Sunday to 0=Saturday for the EC 131 * We DO need to set weekday because the EC controls battery charging 132 * schedules that depend on the day of the week. 133 */ 134 int wday = tm->tm_wday == 6 ? 0 : tm->tm_wday + 1; 135 int ret; 136 137 rtc.command = EC_COMMAND_CMOS; 138 rtc.param = EC_CMOS_TOD_WRITE; 139 rtc.century = bin2bcd(year / 100); 140 rtc.year = bin2bcd(year % 100); 141 rtc.month = bin2bcd(tm->tm_mon + 1); 142 rtc.day = bin2bcd(tm->tm_mday); 143 rtc.hour = bin2bcd(tm->tm_hour); 144 rtc.minute = bin2bcd(tm->tm_min); 145 rtc.second = bin2bcd(tm->tm_sec); 146 rtc.weekday = bin2bcd(wday); 147 148 memset(&msg, 0, sizeof(msg)); 149 msg.type = WILCO_EC_MSG_LEGACY; 150 msg.request_data = &rtc; 151 msg.request_size = sizeof(rtc); 152 153 ret = wilco_ec_mailbox(ec, &msg); 154 if (ret < 0) 155 return ret; 156 157 return 0; 158 } 159 160 static const struct rtc_class_ops wilco_ec_rtc_ops = { 161 .read_time = wilco_ec_rtc_read, 162 .set_time = wilco_ec_rtc_write, 163 }; 164 165 static int wilco_ec_rtc_probe(struct platform_device *pdev) 166 { 167 struct rtc_device *rtc; 168 169 rtc = devm_rtc_allocate_device(&pdev->dev); 170 if (IS_ERR(rtc)) 171 return PTR_ERR(rtc); 172 173 rtc->ops = &wilco_ec_rtc_ops; 174 /* EC only supports this century */ 175 rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; 176 rtc->range_max = RTC_TIMESTAMP_END_2099; 177 rtc->owner = THIS_MODULE; 178 179 return rtc_register_device(rtc); 180 } 181 182 static struct platform_driver wilco_ec_rtc_driver = { 183 .driver = { 184 .name = "rtc-wilco-ec", 185 }, 186 .probe = wilco_ec_rtc_probe, 187 }; 188 189 module_platform_driver(wilco_ec_rtc_driver); 190 191 MODULE_ALIAS("platform:rtc-wilco-ec"); 192 MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); 193 MODULE_LICENSE("GPL v2"); 194 MODULE_DESCRIPTION("Wilco EC RTC driver"); 195