1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * (C) Copyright 2001
4  * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
5  */
6 
7 /*
8  * RTC, Date & Time support: get and set date & time
9  */
10 #include <common.h>
11 #include <command.h>
12 #include <dm.h>
13 #include <rtc.h>
14 #include <i2c.h>
15 #include <asm/global_data.h>
16 
17 DECLARE_GLOBAL_DATA_PTR;
18 
19 static const char * const weekdays[] = {
20 	"Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur",
21 };
22 
23 #ifdef CONFIG_NEEDS_MANUAL_RELOC
24 #define RELOC(a)	((typeof(a))((unsigned long)(a) + gd->reloc_off))
25 #else
26 #define RELOC(a)	a
27 #endif
28 
29 int mk_date (const char *, struct rtc_time *);
30 
31 static struct rtc_time default_tm = { 0, 0, 0, 1, 1, 2000, 6, 0, 0 };
32 
do_date(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])33 static int do_date(struct cmd_tbl *cmdtp, int flag, int argc,
34 		   char *const argv[])
35 {
36 	struct rtc_time tm;
37 	int rcode = 0;
38 	int old_bus __maybe_unused;
39 
40 	/* switch to correct I2C bus */
41 #ifdef CONFIG_DM_RTC
42 	struct udevice *dev;
43 
44 	rcode = uclass_get_device_by_seq(UCLASS_RTC, 0, &dev);
45 	if (rcode) {
46 		rcode = uclass_get_device(UCLASS_RTC, 0, &dev);
47 		if (rcode) {
48 			printf("Cannot find RTC: err=%d\n", rcode);
49 			return CMD_RET_FAILURE;
50 		}
51 	}
52 #elif CONFIG_IS_ENABLED(SYS_I2C_LEGACY)
53 	old_bus = i2c_get_bus_num();
54 	i2c_set_bus_num(CONFIG_SYS_RTC_BUS_NUM);
55 #else
56 	old_bus = I2C_GET_BUS();
57 	I2C_SET_BUS(CONFIG_SYS_RTC_BUS_NUM);
58 #endif
59 
60 	switch (argc) {
61 	case 2:			/* set date & time */
62 		if (strcmp(argv[1],"reset") == 0) {
63 			puts ("Reset RTC...\n");
64 #ifdef CONFIG_DM_RTC
65 			rcode = dm_rtc_reset(dev);
66 			if (!rcode)
67 				rcode = dm_rtc_set(dev, &default_tm);
68 #else
69 			rtc_reset();
70 			rcode = rtc_set(&default_tm);
71 #endif
72 			if (rcode)
73 				puts("## Failed to set date after RTC reset\n");
74 		} else {
75 			/* initialize tm with current time */
76 #ifdef CONFIG_DM_RTC
77 			rcode = dm_rtc_get(dev, &tm);
78 #else
79 			rcode = rtc_get(&tm);
80 #endif
81 			if (!rcode) {
82 				/* insert new date & time */
83 				if (mk_date(argv[1], &tm) != 0) {
84 					puts ("## Bad date format\n");
85 					break;
86 				}
87 				/* and write to RTC */
88 #ifdef CONFIG_DM_RTC
89 				rcode = dm_rtc_set(dev, &tm);
90 #else
91 				rcode = rtc_set(&tm);
92 #endif
93 				if (rcode) {
94 					printf("## Set date failed: err=%d\n",
95 					       rcode);
96 				}
97 			} else {
98 				puts("## Get date failed\n");
99 			}
100 		}
101 		/* FALL TROUGH */
102 	case 1:			/* get date & time */
103 #ifdef CONFIG_DM_RTC
104 		rcode = dm_rtc_get(dev, &tm);
105 #else
106 		rcode = rtc_get(&tm);
107 #endif
108 		if (rcode) {
109 			puts("## Get date failed\n");
110 			break;
111 		}
112 
113 		printf ("Date: %4d-%02d-%02d (%sday)    Time: %2d:%02d:%02d\n",
114 			tm.tm_year, tm.tm_mon, tm.tm_mday,
115 			(tm.tm_wday<0 || tm.tm_wday>6) ?
116 				"unknown " : RELOC(weekdays[tm.tm_wday]),
117 			tm.tm_hour, tm.tm_min, tm.tm_sec);
118 
119 		break;
120 	default:
121 		rcode = CMD_RET_USAGE;
122 	}
123 
124 	/* switch back to original I2C bus */
125 #if CONFIG_IS_ENABLED(SYS_I2C_LEGACY)
126 	i2c_set_bus_num(old_bus);
127 #elif !defined(CONFIG_DM_RTC)
128 	I2C_SET_BUS(old_bus);
129 #endif
130 
131 	return rcode ? CMD_RET_FAILURE : 0;
132 }
133 
134 /*
135  * simple conversion of two-digit string with error checking
136  */
cnvrt2(const char * str,int * valp)137 static int cnvrt2 (const char *str, int *valp)
138 {
139 	int val;
140 
141 	if ((*str < '0') || (*str > '9'))
142 		return (-1);
143 
144 	val = *str - '0';
145 
146 	++str;
147 
148 	if ((*str < '0') || (*str > '9'))
149 		return (-1);
150 
151 	*valp = 10 * val + (*str - '0');
152 
153 	return (0);
154 }
155 
156 /*
157  * Convert date string: MMDDhhmm[[CC]YY][.ss]
158  *
159  * Some basic checking for valid values is done, but this will not catch
160  * all possible error conditions.
161  */
mk_date(const char * datestr,struct rtc_time * tmp)162 int mk_date (const char *datestr, struct rtc_time *tmp)
163 {
164 	int len, val;
165 	char *ptr;
166 
167 	ptr = strchr(datestr, '.');
168 	len = strlen(datestr);
169 
170 	/* Set seconds */
171 	if (ptr) {
172 		int sec;
173 
174 		ptr++;
175 		if ((len - (ptr - datestr)) != 2)
176 			return (-1);
177 
178 		len -= 3;
179 
180 		if (cnvrt2 (ptr, &sec))
181 			return (-1);
182 
183 		tmp->tm_sec = sec;
184 	} else {
185 		tmp->tm_sec = 0;
186 	}
187 
188 	if (len == 12) {		/* MMDDhhmmCCYY	*/
189 		int year, century;
190 
191 		if (cnvrt2 (datestr+ 8, &century) ||
192 		    cnvrt2 (datestr+10, &year) ) {
193 			return (-1);
194 		}
195 		tmp->tm_year = 100 * century + year;
196 	} else if (len == 10) {		/* MMDDhhmmYY	*/
197 		int year, century;
198 
199 		century = tmp->tm_year / 100;
200 		if (cnvrt2 (datestr+ 8, &year))
201 			return (-1);
202 		tmp->tm_year = 100 * century + year;
203 	}
204 
205 	switch (len) {
206 	case 8:			/* MMDDhhmm	*/
207 		/* fall thru */
208 	case 10:		/* MMDDhhmmYY	*/
209 		/* fall thru */
210 	case 12:		/* MMDDhhmmCCYY	*/
211 		if (cnvrt2 (datestr+0, &val) ||
212 		    val > 12) {
213 			break;
214 		}
215 		tmp->tm_mon  = val;
216 		if (cnvrt2 (datestr+2, &val) ||
217 		    val > ((tmp->tm_mon==2) ? 29 : 31)) {
218 			break;
219 		}
220 		tmp->tm_mday = val;
221 
222 		if (cnvrt2 (datestr+4, &val) ||
223 		    val > 23) {
224 			break;
225 		}
226 		tmp->tm_hour = val;
227 
228 		if (cnvrt2 (datestr+6, &val) ||
229 		    val > 59) {
230 			break;
231 		}
232 		tmp->tm_min  = val;
233 
234 		/* calculate day of week */
235 		rtc_calc_weekday(tmp);
236 
237 		return (0);
238 	default:
239 		break;
240 	}
241 
242 	return (-1);
243 }
244 
245 /***************************************************/
246 
247 U_BOOT_CMD(
248 	date,	2,	1,	do_date,
249 	"get/set/reset date & time",
250 	"[MMDDhhmm[[CC]YY][.ss]]\ndate reset\n"
251 	"  - without arguments: print date & time\n"
252 	"  - with numeric argument: set the system date & time\n"
253 	"  - with 'reset' argument: reset the RTC"
254 );
255