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