Skip to content

Commit 822405b

Browse files
shentokphilmd
authored andcommitted
hw/rtc: Add Ricoh RS5C372 RTC emulation
The implementation just allows Linux to determine date and time. Signed-off-by: Bernhard Beschow <[email protected]> Reviewed-by: Philippe Mathieu-Daudé <[email protected]> Acked-by: Fabiano Rosas <[email protected]> Message-ID: <[email protected]> Signed-off-by: Philippe Mathieu-Daudé <[email protected]>
1 parent d060b27 commit 822405b

File tree

7 files changed

+292
-0
lines changed

7 files changed

+292
-0
lines changed

MAINTAINERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,10 +827,12 @@ F: hw/arm/imx8mp-evk.c
827827
F: hw/arm/fsl-imx8mp.c
828828
F: hw/misc/imx8mp_*.c
829829
F: hw/pci-host/fsl_imx8m_phy.c
830+
F: hw/rtc/rs5c372.c
830831
F: include/hw/arm/fsl-imx8mp.h
831832
F: include/hw/misc/imx8mp_*.h
832833
F: include/hw/pci-host/fsl_imx8m_phy.h
833834
F: docs/system/arm/imx8mp-evk.rst
835+
F: tests/qtest/rs5c372-test.c
834836

835837
MPS2 / MPS3
836838
M: Peter Maydell <[email protected]>

hw/rtc/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,8 @@ config GOLDFISH_RTC
2626

2727
config LS7A_RTC
2828
bool
29+
30+
config RS5C372_RTC
31+
bool
32+
depends on I2C
33+
default y if I2C_DEVICES

hw/rtc/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ system_ss.add(when: 'CONFIG_GOLDFISH_RTC', if_true: files('goldfish_rtc.c'))
1313
system_ss.add(when: 'CONFIG_LS7A_RTC', if_true: files('ls7a_rtc.c'))
1414
system_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-rtc.c'))
1515
system_ss.add(when: 'CONFIG_MC146818RTC', if_true: files('mc146818rtc.c'))
16+
system_ss.add(when: 'CONFIG_RS5C372_RTC', if_true: files('rs5c372.c'))

hw/rtc/rs5c372.c

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
* Ricoh RS5C372, R222x I2C RTC
3+
*
4+
* Copyright (c) 2025 Bernhard Beschow <[email protected]>
5+
*
6+
* Based on hw/rtc/ds1338.c
7+
*
8+
* SPDX-License-Identifier: GPL-2.0-or-later
9+
*/
10+
11+
#include "qemu/osdep.h"
12+
#include "hw/i2c/i2c.h"
13+
#include "hw/qdev-properties.h"
14+
#include "hw/resettable.h"
15+
#include "migration/vmstate.h"
16+
#include "qemu/bcd.h"
17+
#include "qom/object.h"
18+
#include "system/rtc.h"
19+
#include "trace.h"
20+
21+
#define NVRAM_SIZE 0x10
22+
23+
/* Flags definitions */
24+
#define SECONDS_CH 0x80
25+
#define HOURS_PM 0x20
26+
#define CTRL2_24 0x20
27+
28+
#define TYPE_RS5C372 "rs5c372"
29+
OBJECT_DECLARE_SIMPLE_TYPE(RS5C372State, RS5C372)
30+
31+
struct RS5C372State {
32+
I2CSlave parent_obj;
33+
34+
int64_t offset;
35+
uint8_t wday_offset;
36+
uint8_t nvram[NVRAM_SIZE];
37+
uint8_t ptr;
38+
uint8_t tx_format;
39+
bool addr_byte;
40+
};
41+
42+
static void capture_current_time(RS5C372State *s)
43+
{
44+
/*
45+
* Capture the current time into the secondary registers which will be
46+
* actually read by the data transfer operation.
47+
*/
48+
struct tm now;
49+
qemu_get_timedate(&now, s->offset);
50+
s->nvram[0] = to_bcd(now.tm_sec);
51+
s->nvram[1] = to_bcd(now.tm_min);
52+
if (s->nvram[0xf] & CTRL2_24) {
53+
s->nvram[2] = to_bcd(now.tm_hour);
54+
} else {
55+
int tmp = now.tm_hour;
56+
if (tmp % 12 == 0) {
57+
tmp += 12;
58+
}
59+
if (tmp <= 12) {
60+
s->nvram[2] = to_bcd(tmp);
61+
} else {
62+
s->nvram[2] = HOURS_PM | to_bcd(tmp - 12);
63+
}
64+
}
65+
s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1;
66+
s->nvram[4] = to_bcd(now.tm_mday);
67+
s->nvram[5] = to_bcd(now.tm_mon + 1);
68+
s->nvram[6] = to_bcd(now.tm_year - 100);
69+
}
70+
71+
static void inc_regptr(RS5C372State *s)
72+
{
73+
s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1);
74+
}
75+
76+
static int rs5c372_event(I2CSlave *i2c, enum i2c_event event)
77+
{
78+
RS5C372State *s = RS5C372(i2c);
79+
80+
switch (event) {
81+
case I2C_START_RECV:
82+
/*
83+
* In h/w, capture happens on any START condition, not just a
84+
* START_RECV, but there is no need to actually capture on
85+
* START_SEND, because the guest can't get at that data
86+
* without going through a START_RECV which would overwrite it.
87+
*/
88+
capture_current_time(s);
89+
s->ptr = 0xf;
90+
break;
91+
case I2C_START_SEND:
92+
s->addr_byte = true;
93+
break;
94+
default:
95+
break;
96+
}
97+
98+
return 0;
99+
}
100+
101+
static uint8_t rs5c372_recv(I2CSlave *i2c)
102+
{
103+
RS5C372State *s = RS5C372(i2c);
104+
uint8_t res;
105+
106+
res = s->nvram[s->ptr];
107+
108+
trace_rs5c372_recv(s->ptr, res);
109+
110+
inc_regptr(s);
111+
return res;
112+
}
113+
114+
static int rs5c372_send(I2CSlave *i2c, uint8_t data)
115+
{
116+
RS5C372State *s = RS5C372(i2c);
117+
118+
if (s->addr_byte) {
119+
s->ptr = data >> 4;
120+
s->tx_format = data & 0xf;
121+
s->addr_byte = false;
122+
return 0;
123+
}
124+
125+
trace_rs5c372_send(s->ptr, data);
126+
127+
if (s->ptr < 7) {
128+
/* Time register. */
129+
struct tm now;
130+
qemu_get_timedate(&now, s->offset);
131+
switch (s->ptr) {
132+
case 0:
133+
now.tm_sec = from_bcd(data & 0x7f);
134+
break;
135+
case 1:
136+
now.tm_min = from_bcd(data & 0x7f);
137+
break;
138+
case 2:
139+
if (s->nvram[0xf] & CTRL2_24) {
140+
now.tm_hour = from_bcd(data & 0x3f);
141+
} else {
142+
int tmp = from_bcd(data & (HOURS_PM - 1));
143+
if (data & HOURS_PM) {
144+
tmp += 12;
145+
}
146+
if (tmp % 12 == 0) {
147+
tmp -= 12;
148+
}
149+
now.tm_hour = tmp;
150+
}
151+
break;
152+
case 3:
153+
{
154+
/*
155+
* The day field is supposed to contain a value in the range
156+
* 1-7. Otherwise behavior is undefined.
157+
*/
158+
int user_wday = (data & 7) - 1;
159+
s->wday_offset = (user_wday - now.tm_wday + 7) % 7;
160+
}
161+
break;
162+
case 4:
163+
now.tm_mday = from_bcd(data & 0x3f);
164+
break;
165+
case 5:
166+
now.tm_mon = from_bcd(data & 0x1f) - 1;
167+
break;
168+
case 6:
169+
now.tm_year = from_bcd(data) + 100;
170+
break;
171+
}
172+
s->offset = qemu_timedate_diff(&now);
173+
} else {
174+
s->nvram[s->ptr] = data;
175+
}
176+
inc_regptr(s);
177+
return 0;
178+
}
179+
180+
static void rs5c372_reset_hold(Object *obj, ResetType type)
181+
{
182+
RS5C372State *s = RS5C372(obj);
183+
184+
/* The clock is running and synchronized with the host */
185+
s->offset = 0;
186+
s->wday_offset = 0;
187+
memset(s->nvram, 0, NVRAM_SIZE);
188+
s->ptr = 0;
189+
s->addr_byte = false;
190+
}
191+
192+
static const VMStateDescription rs5c372_vmstate = {
193+
.name = "rs5c372",
194+
.version_id = 1,
195+
.minimum_version_id = 1,
196+
.fields = (const VMStateField[]) {
197+
VMSTATE_I2C_SLAVE(parent_obj, RS5C372State),
198+
VMSTATE_INT64(offset, RS5C372State),
199+
VMSTATE_UINT8_V(wday_offset, RS5C372State, 2),
200+
VMSTATE_UINT8_ARRAY(nvram, RS5C372State, NVRAM_SIZE),
201+
VMSTATE_UINT8(ptr, RS5C372State),
202+
VMSTATE_UINT8(tx_format, RS5C372State),
203+
VMSTATE_BOOL(addr_byte, RS5C372State),
204+
VMSTATE_END_OF_LIST()
205+
}
206+
};
207+
208+
static void rs5c372_init(Object *obj)
209+
{
210+
qdev_prop_set_uint8(DEVICE(obj), "address", 0x32);
211+
}
212+
213+
static void rs5c372_class_init(ObjectClass *klass, void *data)
214+
{
215+
DeviceClass *dc = DEVICE_CLASS(klass);
216+
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
217+
ResettableClass *rc = RESETTABLE_CLASS(klass);
218+
219+
k->event = rs5c372_event;
220+
k->recv = rs5c372_recv;
221+
k->send = rs5c372_send;
222+
dc->vmsd = &rs5c372_vmstate;
223+
rc->phases.hold = rs5c372_reset_hold;
224+
}
225+
226+
static const TypeInfo rs5c372_types[] = {
227+
{
228+
.name = TYPE_RS5C372,
229+
.parent = TYPE_I2C_SLAVE,
230+
.instance_size = sizeof(RS5C372State),
231+
.instance_init = rs5c372_init,
232+
.class_init = rs5c372_class_init,
233+
},
234+
};
235+
236+
DEFINE_TYPES(rs5c372_types)

hw/rtc/trace-events

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ m48txx_nvram_mem_write(uint32_t addr, uint32_t value) "mem write addr:0x%04x val
3535
# goldfish_rtc.c
3636
goldfish_rtc_read(uint64_t addr, uint64_t value) "addr 0x%02" PRIx64 " value 0x%08" PRIx64
3737
goldfish_rtc_write(uint64_t addr, uint64_t value) "addr 0x%02" PRIx64 " value 0x%08" PRIx64
38+
39+
# rs5c372.c
40+
rs5c372_recv(uint32_t addr, uint8_t value) "[0x%" PRIx32 "] -> 0x%02" PRIx8
41+
rs5c372_send(uint32_t addr, uint8_t value) "[0x%" PRIx32 "] <- 0x%02" PRIx8

tests/qtest/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ qos_test_ss.add(
298298
'pca9552-test.c',
299299
'pci-test.c',
300300
'pcnet-test.c',
301+
'rs5c372-test.c',
301302
'sdhci-test.c',
302303
'spapr-phb-test.c',
303304
'tmp105-test.c',

tests/qtest/rs5c372-test.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* QTest testcase for the RS5C372 RTC
3+
*
4+
* Copyright (c) 2025 Bernhard Beschow <[email protected]>
5+
*
6+
* Based on ds1338-test.c
7+
*
8+
* SPDX-License-Identifier: GPL-2.0-or-later
9+
*/
10+
11+
#include "qemu/osdep.h"
12+
#include "qemu/bcd.h"
13+
#include "libqos/i2c.h"
14+
15+
#define RS5C372_ADDR 0x32
16+
17+
static void rs5c372_read_date(void *obj, void *data, QGuestAllocator *alloc)
18+
{
19+
QI2CDevice *i2cdev = obj;
20+
21+
uint8_t resp[0x10];
22+
time_t now = time(NULL);
23+
struct tm *utc = gmtime(&now);
24+
25+
i2c_read_block(i2cdev, 0, resp, sizeof(resp));
26+
27+
/* check retrieved time against local time */
28+
g_assert_cmpuint(from_bcd(resp[5]), == , utc->tm_mday);
29+
g_assert_cmpuint(from_bcd(resp[6]), == , 1 + utc->tm_mon);
30+
g_assert_cmpuint(2000 + from_bcd(resp[7]), == , 1900 + utc->tm_year);
31+
}
32+
33+
static void rs5c372_register_nodes(void)
34+
{
35+
QOSGraphEdgeOptions opts = { };
36+
add_qi2c_address(&opts, &(QI2CAddress) { RS5C372_ADDR });
37+
38+
qos_node_create_driver("rs5c372", i2c_device_create);
39+
qos_node_consumes("rs5c372", "i2c-bus", &opts);
40+
qos_add_test("read_date", "rs5c372", rs5c372_read_date, NULL);
41+
}
42+
43+
libqos_init(rs5c372_register_nodes);

0 commit comments

Comments
 (0)