1 /* 2 * Ricoh RS5C372, R222x I2C RTC 3 * 4 * Copyright (c) 2025 Bernhard Beschow <shentey@gmail.com> 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) 237