Commit | Line | Data |
---|---|---|
0d2f2a3d NC |
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 | /** | |
25 | * struct ec_rtc_read - Format of RTC returned by EC. | |
26 | * @second: Second value (0..59) | |
27 | * @minute: Minute value (0..59) | |
28 | * @hour: Hour value (0..23) | |
29 | * @day: Day value (1..31) | |
30 | * @month: Month value (1..12) | |
31 | * @year: Year value (full year % 100) | |
32 | * @century: Century value (full year / 100) | |
33 | * | |
34 | * All values are presented in binary (not BCD). | |
35 | */ | |
36 | struct ec_rtc_read { | |
37 | u8 second; | |
38 | u8 minute; | |
39 | u8 hour; | |
40 | u8 day; | |
41 | u8 month; | |
42 | u8 year; | |
43 | u8 century; | |
44 | } __packed; | |
45 | ||
46 | /** | |
47 | * struct ec_rtc_write - Format of RTC sent to the EC. | |
48 | * @param: EC_CMOS_TOD_WRITE | |
49 | * @century: Century value (full year / 100) | |
50 | * @year: Year value (full year % 100) | |
51 | * @month: Month value (1..12) | |
52 | * @day: Day value (1..31) | |
53 | * @hour: Hour value (0..23) | |
54 | * @minute: Minute value (0..59) | |
55 | * @second: Second value (0..59) | |
56 | * @weekday: Day of the week (0=Saturday) | |
57 | * | |
58 | * All values are presented in BCD. | |
59 | */ | |
60 | struct ec_rtc_write { | |
61 | u8 param; | |
62 | u8 century; | |
63 | u8 year; | |
64 | u8 month; | |
65 | u8 day; | |
66 | u8 hour; | |
67 | u8 minute; | |
68 | u8 second; | |
69 | u8 weekday; | |
70 | } __packed; | |
71 | ||
72 | static int wilco_ec_rtc_read(struct device *dev, struct rtc_time *tm) | |
73 | { | |
74 | struct wilco_ec_device *ec = dev_get_drvdata(dev->parent); | |
75 | u8 param = EC_CMOS_TOD_READ; | |
76 | struct ec_rtc_read rtc; | |
77 | struct wilco_ec_message msg = { | |
78 | .type = WILCO_EC_MSG_LEGACY, | |
79 | .flags = WILCO_EC_FLAG_RAW_RESPONSE, | |
80 | .command = EC_COMMAND_CMOS, | |
81 | .request_data = ¶m, | |
82 | .request_size = sizeof(param), | |
83 | .response_data = &rtc, | |
84 | .response_size = sizeof(rtc), | |
85 | }; | |
86 | int ret; | |
87 | ||
88 | ret = wilco_ec_mailbox(ec, &msg); | |
89 | if (ret < 0) | |
90 | return ret; | |
91 | ||
92 | tm->tm_sec = rtc.second; | |
93 | tm->tm_min = rtc.minute; | |
94 | tm->tm_hour = rtc.hour; | |
95 | tm->tm_mday = rtc.day; | |
96 | tm->tm_mon = rtc.month - 1; | |
97 | tm->tm_year = rtc.year + (rtc.century * 100) - 1900; | |
98 | tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year); | |
99 | ||
100 | /* Don't compute day of week, we don't need it. */ | |
101 | tm->tm_wday = -1; | |
102 | ||
103 | return 0; | |
104 | } | |
105 | ||
106 | static int wilco_ec_rtc_write(struct device *dev, struct rtc_time *tm) | |
107 | { | |
108 | struct wilco_ec_device *ec = dev_get_drvdata(dev->parent); | |
109 | struct ec_rtc_write rtc; | |
110 | struct wilco_ec_message msg = { | |
111 | .type = WILCO_EC_MSG_LEGACY, | |
112 | .flags = WILCO_EC_FLAG_RAW_RESPONSE, | |
113 | .command = EC_COMMAND_CMOS, | |
114 | .request_data = &rtc, | |
115 | .request_size = sizeof(rtc), | |
116 | }; | |
117 | int year = tm->tm_year + 1900; | |
118 | /* | |
119 | * Convert from 0=Sunday to 0=Saturday for the EC | |
120 | * We DO need to set weekday because the EC controls battery charging | |
121 | * schedules that depend on the day of the week. | |
122 | */ | |
123 | int wday = tm->tm_wday == 6 ? 0 : tm->tm_wday + 1; | |
124 | int ret; | |
125 | ||
126 | rtc.param = EC_CMOS_TOD_WRITE; | |
127 | rtc.century = bin2bcd(year / 100); | |
128 | rtc.year = bin2bcd(year % 100); | |
129 | rtc.month = bin2bcd(tm->tm_mon + 1); | |
130 | rtc.day = bin2bcd(tm->tm_mday); | |
131 | rtc.hour = bin2bcd(tm->tm_hour); | |
132 | rtc.minute = bin2bcd(tm->tm_min); | |
133 | rtc.second = bin2bcd(tm->tm_sec); | |
134 | rtc.weekday = bin2bcd(wday); | |
135 | ||
136 | ret = wilco_ec_mailbox(ec, &msg); | |
137 | if (ret < 0) | |
138 | return ret; | |
139 | ||
140 | return 0; | |
141 | } | |
142 | ||
143 | static const struct rtc_class_ops wilco_ec_rtc_ops = { | |
144 | .read_time = wilco_ec_rtc_read, | |
145 | .set_time = wilco_ec_rtc_write, | |
146 | }; | |
147 | ||
148 | static int wilco_ec_rtc_probe(struct platform_device *pdev) | |
149 | { | |
150 | struct rtc_device *rtc; | |
151 | ||
152 | rtc = devm_rtc_allocate_device(&pdev->dev); | |
153 | if (IS_ERR(rtc)) | |
154 | return PTR_ERR(rtc); | |
155 | ||
156 | rtc->ops = &wilco_ec_rtc_ops; | |
157 | /* EC only supports this century */ | |
158 | rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; | |
159 | rtc->range_max = RTC_TIMESTAMP_END_2099; | |
160 | rtc->owner = THIS_MODULE; | |
161 | ||
162 | return rtc_register_device(rtc); | |
163 | } | |
164 | ||
165 | static struct platform_driver wilco_ec_rtc_driver = { | |
166 | .driver = { | |
167 | .name = "rtc-wilco-ec", | |
168 | }, | |
169 | .probe = wilco_ec_rtc_probe, | |
170 | }; | |
171 | ||
172 | module_platform_driver(wilco_ec_rtc_driver); | |
173 | ||
174 | MODULE_ALIAS("platform:rtc-wilco-ec"); | |
175 | MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); | |
176 | MODULE_LICENSE("GPL v2"); | |
177 | MODULE_DESCRIPTION("Wilco EC RTC driver"); |