Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
CTCaer
GitHub Repository: CTCaer/hekate
Path: blob/master/bdk/rtc/max77620-rtc.c
3694 views
1
/*
2
* PMIC Real Time Clock driver for Nintendo Switch's MAX77620-RTC
3
*
4
* Copyright (c) 2018-2025 CTCaer
5
* Copyright (c) 2019 shchmue
6
*
7
* This program is free software; you can redistribute it and/or modify it
8
* under the terms and conditions of the GNU General Public License,
9
* version 2, as published by the Free Software Foundation.
10
*
11
* This program is distributed in the hope it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14
* more details.
15
*
16
* You should have received a copy of the GNU General Public License
17
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18
*/
19
20
#include <rtc/max77620-rtc.h>
21
#include <soc/i2c.h>
22
#include <soc/pmc.h>
23
#include <soc/timer.h>
24
#include <soc/t210.h>
25
26
bool auto_dst = false;
27
int epoch_offset = 0;
28
29
void max77620_rtc_prep_read()
30
{
31
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_UPDATE0_REG, MAX77620_RTC_READ_UPDATE);
32
}
33
34
void max77620_rtc_get_time(rtc_time_t *time)
35
{
36
u8 val = 0;
37
38
// Update RTC regs from RTC clock.
39
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_UPDATE0_REG, MAX77620_RTC_READ_UPDATE);
40
41
// Get control reg config.
42
val = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_CONTROL_REG);
43
// TODO: Make also sure it's binary format?
44
45
// Get time.
46
time->sec = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_SEC_REG) & 0x7F;
47
time->min = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_MIN_REG) & 0x7F;
48
u8 hour = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_HOUR_REG) & 0x1F;
49
time->hour = hour & 0x1F;
50
51
if (!(val & MAX77620_RTC_24H))
52
{
53
time->hour = hour & 0xF;
54
if (hour & MAX77620_RTC_HOUR_PM_MASK)
55
time->hour += 12;
56
}
57
58
// Get day of week. 1: Monday to 7: Sunday.
59
time->weekday = 0;
60
val = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_WEEKDAY_REG) & 0x7F;
61
for (int i = 0; i < 8; i++)
62
{
63
time->weekday++;
64
if (val & 1)
65
break;
66
val >>= 1;
67
}
68
69
// Get date.
70
time->day = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_DAY_REG) & 0x1F;
71
time->month = (i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_MONTH_REG) & 0xF) - 1;
72
time->month++; // Normally minus 1, but everything else expects 1 as January.
73
time->year = (i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_YEAR_REG) & 0x7F) + 2000;
74
}
75
76
void max77620_rtc_stop_alarm()
77
{
78
u8 val = 0;
79
80
// Update RTC regs from RTC clock.
81
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_UPDATE0_REG, MAX77620_RTC_READ_UPDATE);
82
msleep(16);
83
84
// Stop alarm for both ALARM1 and ALARM2. Horizon uses ALARM2.
85
for (int i = 0; i < (MAX77620_RTC_NR_TIME_REGS * 2); i++)
86
{
87
val = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM1_SEC_REG + i);
88
val &= ~MAX77620_RTC_ALARM_EN_MASK;
89
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM1_SEC_REG + i, val);
90
}
91
92
// Update RTC clock from RTC regs.
93
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_UPDATE0_REG, MAX77620_RTC_WRITE_UPDATE);
94
}
95
96
void max77620_rtc_epoch_to_date(u32 epoch, rtc_time_t *time)
97
{
98
u32 tmp, edays, year, month, day;
99
100
// Set time.
101
time->sec = epoch % 60;
102
epoch /= 60;
103
time->min = epoch % 60;
104
epoch /= 60;
105
time->hour = epoch % 24;
106
epoch /= 24;
107
108
// Calculate base date values.
109
tmp = (u32)(((u64)4 * epoch + 102032) / 146097 + 15);
110
tmp = (u32)((u64)epoch + 2442113 + tmp - (tmp >> 2));
111
112
year = (20 * tmp - 2442) / 7305;
113
edays = tmp - 365 * year - (year >> 2);
114
month = edays * 1000 / 30601;
115
day = edays - month * 30 - month * 601 / 1000;
116
117
// Month/Year offset.
118
if (month < 14)
119
{
120
year -= 4716;
121
month--;
122
}
123
else
124
{
125
year -= 4715;
126
month -= 13;
127
}
128
129
// Set date.
130
time->year = year;
131
time->month = month;
132
time->day = day;
133
134
// Set weekday.
135
time->weekday = (day + 4) % 7;
136
}
137
138
u32 max77620_rtc_date_to_epoch(const rtc_time_t *time)
139
{
140
u32 year, month, epoch;
141
142
//Year
143
year = time->year;
144
//Month of year
145
month = time->month;
146
147
// Month/Year offset.
148
if (month < 3)
149
{
150
month += 12;
151
year--;
152
}
153
154
epoch = (365 * year) + (year >> 2) - (year / 100) + (year / 400); // Years to days.
155
156
epoch += (30 * month) + (3 * (month + 1) / 5) + time->day; // Months to days.
157
epoch -= 719561; // Epoch time is 1/1/1970.
158
159
epoch *= 86400; // Days to seconds.
160
epoch += (3600 * time->hour) + (60 * time->min) + time->sec; // Add hours, minutes and seconds.
161
162
return epoch;
163
}
164
165
void max77620_rtc_get_time_adjusted(rtc_time_t *time)
166
{
167
max77620_rtc_get_time(time);
168
if (epoch_offset)
169
{
170
u32 epoch = (u32)((s64)max77620_rtc_date_to_epoch(time) + epoch_offset);
171
172
// Adjust for DST between 28 march and 28 october. Good enough to cover all years as week info is not valid.
173
u16 md = (time->month << 8) | time->day;
174
if (auto_dst && md >= 0x31C && md < 0xA1C)
175
epoch += 3600;
176
177
max77620_rtc_epoch_to_date(epoch, time);
178
}
179
}
180
181
void max77620_rtc_set_auto_dst(bool enable)
182
{
183
auto_dst = enable;
184
}
185
186
void max77620_rtc_set_epoch_offset(int offset)
187
{
188
epoch_offset = offset;
189
}
190
191
void max77620_rtc_set_reboot_reason(rtc_reboot_reason_t *rr)
192
{
193
max77620_rtc_stop_alarm();
194
195
// Set reboot reason.
196
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM1_YEAR_REG, rr->enc.val1);
197
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM2_YEAR_REG, rr->enc.val2);
198
199
// Set reboot reason magic.
200
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM1_WEEKDAY_REG, RTC_REBOOT_REASON_MAGIC);
201
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM2_WEEKDAY_REG, RTC_REBOOT_REASON_MAGIC);
202
203
// Update RTC clock from RTC regs.
204
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_UPDATE0_REG, MAX77620_RTC_WRITE_UPDATE);
205
msleep(16);
206
}
207
208
bool max77620_rtc_get_reboot_reason(rtc_reboot_reason_t *rr)
209
{
210
u8 magic[2];
211
212
// Get reboot reason magic.
213
magic[0] = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM1_WEEKDAY_REG);
214
magic[1] = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM2_WEEKDAY_REG);
215
216
// Magic must be correct and match on both registers.
217
if (magic[0] != RTC_REBOOT_REASON_MAGIC || magic[0] != magic[1])
218
return false;
219
220
// Reboot reason setter is expected to have updated the actual regs already.
221
rr->enc.val1 = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM1_YEAR_REG);
222
rr->enc.val2 = i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM2_YEAR_REG);
223
224
// Clear magic and update actual regs.
225
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM1_WEEKDAY_REG, 0);
226
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_ALARM2_WEEKDAY_REG, 0);
227
i2c_send_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_UPDATE0_REG, MAX77620_RTC_WRITE_UPDATE);
228
229
// Return reboot reason. False if [config] was selected.
230
return true;
231
}
232
233