1 /*
2 * Broadcom Serial Controller (BSC)
3 *
4 * Copyright (c) 2024 Rayhan Faizel <rayhan.faizel@gmail.com>
5 *
6 * SPDX-License-Identifier: MIT
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26
27 #include "qemu/osdep.h"
28 #include "qemu/log.h"
29 #include "hw/i2c/bcm2835_i2c.h"
30 #include "hw/irq.h"
31 #include "migration/vmstate.h"
32
bcm2835_i2c_update_interrupt(BCM2835I2CState * s)33 static void bcm2835_i2c_update_interrupt(BCM2835I2CState *s)
34 {
35 int do_interrupt = 0;
36 /* Interrupt on RXR (Needs reading) */
37 if (s->c & BCM2835_I2C_C_INTR && s->s & BCM2835_I2C_S_RXR) {
38 do_interrupt = 1;
39 }
40
41 /* Interrupt on TXW (Needs writing) */
42 if (s->c & BCM2835_I2C_C_INTT && s->s & BCM2835_I2C_S_TXW) {
43 do_interrupt = 1;
44 }
45
46 /* Interrupt on DONE (Transfer complete) */
47 if (s->c & BCM2835_I2C_C_INTD && s->s & BCM2835_I2C_S_DONE) {
48 do_interrupt = 1;
49 }
50 qemu_set_irq(s->irq, do_interrupt);
51 }
52
bcm2835_i2c_begin_transfer(BCM2835I2CState * s)53 static void bcm2835_i2c_begin_transfer(BCM2835I2CState *s)
54 {
55 int direction = s->c & BCM2835_I2C_C_READ;
56 if (i2c_start_transfer(s->bus, s->a, direction)) {
57 s->s |= BCM2835_I2C_S_ERR;
58 }
59 s->s |= BCM2835_I2C_S_TA;
60
61 if (direction) {
62 s->s |= BCM2835_I2C_S_RXR | BCM2835_I2C_S_RXD;
63 } else {
64 s->s |= BCM2835_I2C_S_TXW;
65 }
66 }
67
bcm2835_i2c_finish_transfer(BCM2835I2CState * s)68 static void bcm2835_i2c_finish_transfer(BCM2835I2CState *s)
69 {
70 /*
71 * STOP is sent when DLEN counts down to zero.
72 *
73 * https://github.com/torvalds/linux/blob/v6.7/drivers/i2c/busses/i2c-bcm2835.c#L223-L261
74 * It is possible to initiate repeated starts on real hardware.
75 * However, this requires sending another ST request before the bytes in
76 * TX FIFO are shifted out.
77 *
78 * This is not emulated currently.
79 */
80 i2c_end_transfer(s->bus);
81 s->s |= BCM2835_I2C_S_DONE;
82
83 /* Ensure RXD is cleared, otherwise the driver registers an error */
84 s->s &= ~(BCM2835_I2C_S_TA | BCM2835_I2C_S_RXR |
85 BCM2835_I2C_S_TXW | BCM2835_I2C_S_RXD);
86 }
87
bcm2835_i2c_read(void * opaque,hwaddr addr,unsigned size)88 static uint64_t bcm2835_i2c_read(void *opaque, hwaddr addr, unsigned size)
89 {
90 BCM2835I2CState *s = opaque;
91 uint32_t readval = 0;
92
93 switch (addr) {
94 case BCM2835_I2C_C:
95 readval = s->c;
96 break;
97 case BCM2835_I2C_S:
98 readval = s->s;
99 break;
100 case BCM2835_I2C_DLEN:
101 readval = s->dlen;
102 break;
103 case BCM2835_I2C_A:
104 readval = s->a;
105 break;
106 case BCM2835_I2C_FIFO:
107 /* We receive I2C messages directly instead of using FIFOs */
108 if (s->s & BCM2835_I2C_S_TA) {
109 readval = i2c_recv(s->bus);
110 s->dlen -= 1;
111
112 if (s->dlen == 0) {
113 bcm2835_i2c_finish_transfer(s);
114 }
115 }
116 bcm2835_i2c_update_interrupt(s);
117 break;
118 case BCM2835_I2C_DIV:
119 readval = s->div;
120 break;
121 case BCM2835_I2C_DEL:
122 readval = s->del;
123 break;
124 case BCM2835_I2C_CLKT:
125 readval = s->clkt;
126 break;
127 default:
128 qemu_log_mask(LOG_GUEST_ERROR,
129 "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr);
130 }
131
132 return readval;
133 }
134
bcm2835_i2c_write(void * opaque,hwaddr addr,uint64_t value,unsigned int size)135 static void bcm2835_i2c_write(void *opaque, hwaddr addr,
136 uint64_t value, unsigned int size)
137 {
138 BCM2835I2CState *s = opaque;
139 uint32_t writeval = value;
140
141 switch (addr) {
142 case BCM2835_I2C_C:
143 /* ST is a one-shot operation; it must read back as 0 */
144 s->c = writeval & ~BCM2835_I2C_C_ST;
145
146 /* Start transfer */
147 if (writeval & (BCM2835_I2C_C_ST | BCM2835_I2C_C_I2CEN)) {
148 bcm2835_i2c_begin_transfer(s);
149 /*
150 * Handle special case where transfer starts with zero data length.
151 * Required for zero length i2c quick messages to work.
152 */
153 if (s->dlen == 0) {
154 bcm2835_i2c_finish_transfer(s);
155 }
156 }
157
158 bcm2835_i2c_update_interrupt(s);
159 break;
160 case BCM2835_I2C_S:
161 if (writeval & BCM2835_I2C_S_DONE && s->s & BCM2835_I2C_S_DONE) {
162 /* When DONE is cleared, DLEN should read last written value. */
163 s->dlen = s->last_dlen;
164 }
165
166 /* Clear DONE, CLKT and ERR by writing 1 */
167 s->s &= ~(writeval & (BCM2835_I2C_S_DONE |
168 BCM2835_I2C_S_ERR | BCM2835_I2C_S_CLKT));
169 break;
170 case BCM2835_I2C_DLEN:
171 s->dlen = writeval;
172 s->last_dlen = writeval;
173 break;
174 case BCM2835_I2C_A:
175 s->a = writeval;
176 break;
177 case BCM2835_I2C_FIFO:
178 /* We send I2C messages directly instead of using FIFOs */
179 if (s->s & BCM2835_I2C_S_TA) {
180 if (s->s & BCM2835_I2C_S_TXD) {
181 if (!i2c_send(s->bus, writeval & 0xff)) {
182 s->dlen -= 1;
183 } else {
184 s->s |= BCM2835_I2C_S_ERR;
185 }
186 }
187
188 if (s->dlen == 0) {
189 bcm2835_i2c_finish_transfer(s);
190 }
191 }
192 bcm2835_i2c_update_interrupt(s);
193 break;
194 case BCM2835_I2C_DIV:
195 s->div = writeval;
196 break;
197 case BCM2835_I2C_DEL:
198 s->del = writeval;
199 break;
200 case BCM2835_I2C_CLKT:
201 s->clkt = writeval;
202 break;
203 default:
204 qemu_log_mask(LOG_GUEST_ERROR,
205 "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, addr);
206 }
207 }
208
209 static const MemoryRegionOps bcm2835_i2c_ops = {
210 .read = bcm2835_i2c_read,
211 .write = bcm2835_i2c_write,
212 .endianness = DEVICE_NATIVE_ENDIAN,
213 .valid = {
214 .min_access_size = 4,
215 .max_access_size = 4,
216 },
217 };
218
bcm2835_i2c_realize(DeviceState * dev,Error ** errp)219 static void bcm2835_i2c_realize(DeviceState *dev, Error **errp)
220 {
221 BCM2835I2CState *s = BCM2835_I2C(dev);
222 s->bus = i2c_init_bus(dev, NULL);
223
224 memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_i2c_ops, s,
225 TYPE_BCM2835_I2C, 0x24);
226 sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
227 sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
228 }
229
bcm2835_i2c_reset(DeviceState * dev)230 static void bcm2835_i2c_reset(DeviceState *dev)
231 {
232 BCM2835I2CState *s = BCM2835_I2C(dev);
233
234 /* Reset values according to BCM2835 Peripheral Documentation */
235 s->c = 0x0;
236 s->s = BCM2835_I2C_S_TXD | BCM2835_I2C_S_TXE;
237 s->dlen = 0x0;
238 s->a = 0x0;
239 s->div = 0x5dc;
240 s->del = 0x00300030;
241 s->clkt = 0x40;
242 }
243
244 static const VMStateDescription vmstate_bcm2835_i2c = {
245 .name = TYPE_BCM2835_I2C,
246 .version_id = 1,
247 .minimum_version_id = 1,
248 .fields = (const VMStateField[]) {
249 VMSTATE_UINT32(c, BCM2835I2CState),
250 VMSTATE_UINT32(s, BCM2835I2CState),
251 VMSTATE_UINT32(dlen, BCM2835I2CState),
252 VMSTATE_UINT32(a, BCM2835I2CState),
253 VMSTATE_UINT32(div, BCM2835I2CState),
254 VMSTATE_UINT32(del, BCM2835I2CState),
255 VMSTATE_UINT32(clkt, BCM2835I2CState),
256 VMSTATE_UINT32(last_dlen, BCM2835I2CState),
257 VMSTATE_END_OF_LIST()
258 }
259 };
260
bcm2835_i2c_class_init(ObjectClass * klass,void * data)261 static void bcm2835_i2c_class_init(ObjectClass *klass, void *data)
262 {
263 DeviceClass *dc = DEVICE_CLASS(klass);
264
265 dc->reset = bcm2835_i2c_reset;
266 dc->realize = bcm2835_i2c_realize;
267 dc->vmsd = &vmstate_bcm2835_i2c;
268 }
269
270 static const TypeInfo bcm2835_i2c_info = {
271 .name = TYPE_BCM2835_I2C,
272 .parent = TYPE_SYS_BUS_DEVICE,
273 .instance_size = sizeof(BCM2835I2CState),
274 .class_init = bcm2835_i2c_class_init,
275 };
276
bcm2835_i2c_register_types(void)277 static void bcm2835_i2c_register_types(void)
278 {
279 type_register_static(&bcm2835_i2c_info);
280 }
281
282 type_init(bcm2835_i2c_register_types)
283