xref: /qemu/hw/net/dp8393x.c (revision 331d2ac9ea307c990dc86e6493e8f0c48d14bb33)
1a65f56eeSaurel32 /*
2a65f56eeSaurel32  * QEMU NS SONIC DP8393x netcard
3a65f56eeSaurel32  *
4a65f56eeSaurel32  * Copyright (c) 2008-2009 Herve Poussineau
5a65f56eeSaurel32  *
6a65f56eeSaurel32  * This program is free software; you can redistribute it and/or
7a65f56eeSaurel32  * modify it under the terms of the GNU General Public License as
8a65f56eeSaurel32  * published by the Free Software Foundation; either version 2 of
9a65f56eeSaurel32  * the License, or (at your option) any later version.
10a65f56eeSaurel32  *
11a65f56eeSaurel32  * This program is distributed in the hope that it will be useful,
12a65f56eeSaurel32  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13a65f56eeSaurel32  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14a65f56eeSaurel32  * GNU General Public License for more details.
15a65f56eeSaurel32  *
16a65f56eeSaurel32  * You should have received a copy of the GNU General Public License along
178167ee88SBlue Swirl  * with this program; if not, see <http://www.gnu.org/licenses/>.
18a65f56eeSaurel32  */
19a65f56eeSaurel32 
20e8d40465SPeter Maydell #include "qemu/osdep.h"
2164552b6bSMarkus Armbruster #include "hw/irq.h"
22a27bd6c7SMarkus Armbruster #include "hw/qdev-properties.h"
23104655a5SHervé Poussineau #include "hw/sysbus.h"
24d6454270SMarkus Armbruster #include "migration/vmstate.h"
251422e32dSPaolo Bonzini #include "net/net.h"
26da34e65cSMarkus Armbruster #include "qapi/error.h"
270b8fa32fSMarkus Armbruster #include "qemu/module.h"
28104655a5SHervé Poussineau #include "qemu/timer.h"
29f2f62c4dSHervé Poussineau #include <zlib.h>
30db1015e9SEduardo Habkost #include "qom/object.h"
31a65f56eeSaurel32 
32a65f56eeSaurel32 //#define DEBUG_SONIC
33a65f56eeSaurel32 
3489ae0ff9SHervé Poussineau #define SONIC_PROM_SIZE 0x1000
35a65f56eeSaurel32 
36a65f56eeSaurel32 #ifdef DEBUG_SONIC
37001faf32SBlue Swirl #define DPRINTF(fmt, ...) \
38001faf32SBlue Swirl do { printf("sonic: " fmt , ##  __VA_ARGS__); } while (0)
39a65f56eeSaurel32 static const char* reg_names[] = {
40a65f56eeSaurel32     "CR", "DCR", "RCR", "TCR", "IMR", "ISR", "UTDA", "CTDA",
41a65f56eeSaurel32     "TPS", "TFC", "TSA0", "TSA1", "TFS", "URDA", "CRDA", "CRBA0",
42a65f56eeSaurel32     "CRBA1", "RBWC0", "RBWC1", "EOBC", "URRA", "RSA", "REA", "RRP",
43a65f56eeSaurel32     "RWP", "TRBA0", "TRBA1", "0x1b", "0x1c", "0x1d", "0x1e", "LLFA",
44a65f56eeSaurel32     "TTDA", "CEP", "CAP2", "CAP1", "CAP0", "CE", "CDP", "CDC",
45a65f56eeSaurel32     "SR", "WT0", "WT1", "RSC", "CRCT", "FAET", "MPT", "MDT",
46a65f56eeSaurel32     "0x30", "0x31", "0x32", "0x33", "0x34", "0x35", "0x36", "0x37",
47a65f56eeSaurel32     "0x38", "0x39", "0x3a", "0x3b", "0x3c", "0x3d", "0x3e", "DCR2" };
48a65f56eeSaurel32 #else
49001faf32SBlue Swirl #define DPRINTF(fmt, ...) do {} while (0)
50a65f56eeSaurel32 #endif
51a65f56eeSaurel32 
52001faf32SBlue Swirl #define SONIC_ERROR(fmt, ...) \
53001faf32SBlue Swirl do { printf("sonic ERROR: %s: " fmt, __func__ , ## __VA_ARGS__); } while (0)
54a65f56eeSaurel32 
55a65f56eeSaurel32 #define SONIC_CR     0x00
56a65f56eeSaurel32 #define SONIC_DCR    0x01
57a65f56eeSaurel32 #define SONIC_RCR    0x02
58a65f56eeSaurel32 #define SONIC_TCR    0x03
59a65f56eeSaurel32 #define SONIC_IMR    0x04
60a65f56eeSaurel32 #define SONIC_ISR    0x05
61a65f56eeSaurel32 #define SONIC_UTDA   0x06
62a65f56eeSaurel32 #define SONIC_CTDA   0x07
63a65f56eeSaurel32 #define SONIC_TPS    0x08
64a65f56eeSaurel32 #define SONIC_TFC    0x09
65a65f56eeSaurel32 #define SONIC_TSA0   0x0a
66a65f56eeSaurel32 #define SONIC_TSA1   0x0b
67a65f56eeSaurel32 #define SONIC_TFS    0x0c
68a65f56eeSaurel32 #define SONIC_URDA   0x0d
69a65f56eeSaurel32 #define SONIC_CRDA   0x0e
70a65f56eeSaurel32 #define SONIC_CRBA0  0x0f
71a65f56eeSaurel32 #define SONIC_CRBA1  0x10
72a65f56eeSaurel32 #define SONIC_RBWC0  0x11
73a65f56eeSaurel32 #define SONIC_RBWC1  0x12
74a65f56eeSaurel32 #define SONIC_EOBC   0x13
75a65f56eeSaurel32 #define SONIC_URRA   0x14
76a65f56eeSaurel32 #define SONIC_RSA    0x15
77a65f56eeSaurel32 #define SONIC_REA    0x16
78a65f56eeSaurel32 #define SONIC_RRP    0x17
79a65f56eeSaurel32 #define SONIC_RWP    0x18
80a65f56eeSaurel32 #define SONIC_TRBA0  0x19
81a65f56eeSaurel32 #define SONIC_TRBA1  0x1a
82a65f56eeSaurel32 #define SONIC_LLFA   0x1f
83a65f56eeSaurel32 #define SONIC_TTDA   0x20
84a65f56eeSaurel32 #define SONIC_CEP    0x21
85a65f56eeSaurel32 #define SONIC_CAP2   0x22
86a65f56eeSaurel32 #define SONIC_CAP1   0x23
87a65f56eeSaurel32 #define SONIC_CAP0   0x24
88a65f56eeSaurel32 #define SONIC_CE     0x25
89a65f56eeSaurel32 #define SONIC_CDP    0x26
90a65f56eeSaurel32 #define SONIC_CDC    0x27
91a65f56eeSaurel32 #define SONIC_SR     0x28
92a65f56eeSaurel32 #define SONIC_WT0    0x29
93a65f56eeSaurel32 #define SONIC_WT1    0x2a
94a65f56eeSaurel32 #define SONIC_RSC    0x2b
95a65f56eeSaurel32 #define SONIC_CRCT   0x2c
96a65f56eeSaurel32 #define SONIC_FAET   0x2d
97a65f56eeSaurel32 #define SONIC_MPT    0x2e
98a65f56eeSaurel32 #define SONIC_MDT    0x2f
99a65f56eeSaurel32 #define SONIC_DCR2   0x3f
100a65f56eeSaurel32 
101a65f56eeSaurel32 #define SONIC_CR_HTX     0x0001
102a65f56eeSaurel32 #define SONIC_CR_TXP     0x0002
103a65f56eeSaurel32 #define SONIC_CR_RXDIS   0x0004
104a65f56eeSaurel32 #define SONIC_CR_RXEN    0x0008
105a65f56eeSaurel32 #define SONIC_CR_STP     0x0010
106a65f56eeSaurel32 #define SONIC_CR_ST      0x0020
107a65f56eeSaurel32 #define SONIC_CR_RST     0x0080
108a65f56eeSaurel32 #define SONIC_CR_RRRA    0x0100
109a65f56eeSaurel32 #define SONIC_CR_LCAM    0x0200
110a65f56eeSaurel32 #define SONIC_CR_MASK    0x03bf
111a65f56eeSaurel32 
112a65f56eeSaurel32 #define SONIC_DCR_DW     0x0020
113a65f56eeSaurel32 #define SONIC_DCR_LBR    0x2000
114a65f56eeSaurel32 #define SONIC_DCR_EXBUS  0x8000
115a65f56eeSaurel32 
116a65f56eeSaurel32 #define SONIC_RCR_PRX    0x0001
117a65f56eeSaurel32 #define SONIC_RCR_LBK    0x0002
118a65f56eeSaurel32 #define SONIC_RCR_FAER   0x0004
119a65f56eeSaurel32 #define SONIC_RCR_CRCR   0x0008
120a65f56eeSaurel32 #define SONIC_RCR_CRS    0x0020
121a65f56eeSaurel32 #define SONIC_RCR_LPKT   0x0040
122a65f56eeSaurel32 #define SONIC_RCR_BC     0x0080
123a65f56eeSaurel32 #define SONIC_RCR_MC     0x0100
124a65f56eeSaurel32 #define SONIC_RCR_LB0    0x0200
125a65f56eeSaurel32 #define SONIC_RCR_LB1    0x0400
126a65f56eeSaurel32 #define SONIC_RCR_AMC    0x0800
127a65f56eeSaurel32 #define SONIC_RCR_PRO    0x1000
128a65f56eeSaurel32 #define SONIC_RCR_BRD    0x2000
129a65f56eeSaurel32 #define SONIC_RCR_RNT    0x4000
130a65f56eeSaurel32 
131a65f56eeSaurel32 #define SONIC_TCR_PTX    0x0001
132a65f56eeSaurel32 #define SONIC_TCR_BCM    0x0002
133a65f56eeSaurel32 #define SONIC_TCR_FU     0x0004
134a65f56eeSaurel32 #define SONIC_TCR_EXC    0x0040
135a65f56eeSaurel32 #define SONIC_TCR_CRSL   0x0080
136a65f56eeSaurel32 #define SONIC_TCR_NCRS   0x0100
137a65f56eeSaurel32 #define SONIC_TCR_EXD    0x0400
138a65f56eeSaurel32 #define SONIC_TCR_CRCI   0x2000
139a65f56eeSaurel32 #define SONIC_TCR_PINT   0x8000
140a65f56eeSaurel32 
141ada74315SFinn Thain #define SONIC_ISR_RBAE   0x0010
142a65f56eeSaurel32 #define SONIC_ISR_RBE    0x0020
143a65f56eeSaurel32 #define SONIC_ISR_RDE    0x0040
144a65f56eeSaurel32 #define SONIC_ISR_TC     0x0080
145a65f56eeSaurel32 #define SONIC_ISR_TXDN   0x0200
146a65f56eeSaurel32 #define SONIC_ISR_PKTRX  0x0400
147a65f56eeSaurel32 #define SONIC_ISR_PINT   0x0800
148a65f56eeSaurel32 #define SONIC_ISR_LCD    0x1000
149a65f56eeSaurel32 
15088f632fbSFinn Thain #define SONIC_DESC_EOL   0x0001
15188f632fbSFinn Thain #define SONIC_DESC_ADDR  0xFFFE
15288f632fbSFinn Thain 
153104655a5SHervé Poussineau #define TYPE_DP8393X "dp8393x"
1548063396bSEduardo Habkost OBJECT_DECLARE_SIMPLE_TYPE(dp8393xState, DP8393X)
155104655a5SHervé Poussineau 
156db1015e9SEduardo Habkost struct dp8393xState {
157104655a5SHervé Poussineau     SysBusDevice parent_obj;
158104655a5SHervé Poussineau 
159a65f56eeSaurel32     /* Hardware */
160104655a5SHervé Poussineau     uint8_t it_shift;
161be920841SLaurent Vivier     bool big_endian;
162c2279bd0SFinn Thain     bool last_rba_is_full;
163a65f56eeSaurel32     qemu_irq irq;
164a65f56eeSaurel32 #ifdef DEBUG_SONIC
165a65f56eeSaurel32     int irq_level;
166a65f56eeSaurel32 #endif
167a65f56eeSaurel32     QEMUTimer *watchdog;
168a65f56eeSaurel32     int64_t wt_last_update;
16905f41fe3SMark McLoughlin     NICConf conf;
17005f41fe3SMark McLoughlin     NICState *nic;
171024e5bb6SAvi Kivity     MemoryRegion mmio;
17289ae0ff9SHervé Poussineau     MemoryRegion prom;
173a65f56eeSaurel32 
174a65f56eeSaurel32     /* Registers */
175a65f56eeSaurel32     uint8_t cam[16][6];
176a65f56eeSaurel32     uint16_t regs[0x40];
177a65f56eeSaurel32 
178a65f56eeSaurel32     /* Temporaries */
179a65f56eeSaurel32     uint8_t tx_buffer[0x10000];
180af9f0be3SLaurent Vivier     uint16_t data[12];
181a65f56eeSaurel32     int loopback_packet;
182a65f56eeSaurel32 
183a65f56eeSaurel32     /* Memory access */
1843110ce81SMarc-André Lureau     MemoryRegion *dma_mr;
185dd820513SHervé Poussineau     AddressSpace as;
186db1015e9SEduardo Habkost };
187a65f56eeSaurel32 
188581f7b12SPeter Maydell /* Accessor functions for values which are formed by
189581f7b12SPeter Maydell  * concatenating two 16 bit device registers. By putting these
190581f7b12SPeter Maydell  * in their own functions with a uint32_t return type we avoid the
191581f7b12SPeter Maydell  * pitfall of implicit sign extension where ((x << 16) | y) is a
192581f7b12SPeter Maydell  * signed 32 bit integer that might get sign-extended to a 64 bit integer.
193581f7b12SPeter Maydell  */
194581f7b12SPeter Maydell static uint32_t dp8393x_cdp(dp8393xState *s)
195581f7b12SPeter Maydell {
196581f7b12SPeter Maydell     return (s->regs[SONIC_URRA] << 16) | s->regs[SONIC_CDP];
197581f7b12SPeter Maydell }
198581f7b12SPeter Maydell 
199581f7b12SPeter Maydell static uint32_t dp8393x_crba(dp8393xState *s)
200581f7b12SPeter Maydell {
201581f7b12SPeter Maydell     return (s->regs[SONIC_CRBA1] << 16) | s->regs[SONIC_CRBA0];
202581f7b12SPeter Maydell }
203581f7b12SPeter Maydell 
204581f7b12SPeter Maydell static uint32_t dp8393x_crda(dp8393xState *s)
205581f7b12SPeter Maydell {
20688f632fbSFinn Thain     return (s->regs[SONIC_URDA] << 16) |
20788f632fbSFinn Thain            (s->regs[SONIC_CRDA] & SONIC_DESC_ADDR);
208581f7b12SPeter Maydell }
209581f7b12SPeter Maydell 
210581f7b12SPeter Maydell static uint32_t dp8393x_rbwc(dp8393xState *s)
211581f7b12SPeter Maydell {
212581f7b12SPeter Maydell     return (s->regs[SONIC_RBWC1] << 16) | s->regs[SONIC_RBWC0];
213581f7b12SPeter Maydell }
214581f7b12SPeter Maydell 
215581f7b12SPeter Maydell static uint32_t dp8393x_rrp(dp8393xState *s)
216581f7b12SPeter Maydell {
217581f7b12SPeter Maydell     return (s->regs[SONIC_URRA] << 16) | s->regs[SONIC_RRP];
218581f7b12SPeter Maydell }
219581f7b12SPeter Maydell 
220581f7b12SPeter Maydell static uint32_t dp8393x_tsa(dp8393xState *s)
221581f7b12SPeter Maydell {
222581f7b12SPeter Maydell     return (s->regs[SONIC_TSA1] << 16) | s->regs[SONIC_TSA0];
223581f7b12SPeter Maydell }
224581f7b12SPeter Maydell 
225581f7b12SPeter Maydell static uint32_t dp8393x_ttda(dp8393xState *s)
226581f7b12SPeter Maydell {
22788f632fbSFinn Thain     return (s->regs[SONIC_UTDA] << 16) |
22888f632fbSFinn Thain            (s->regs[SONIC_TTDA] & SONIC_DESC_ADDR);
229581f7b12SPeter Maydell }
230581f7b12SPeter Maydell 
231581f7b12SPeter Maydell static uint32_t dp8393x_wt(dp8393xState *s)
232581f7b12SPeter Maydell {
233581f7b12SPeter Maydell     return s->regs[SONIC_WT1] << 16 | s->regs[SONIC_WT0];
234581f7b12SPeter Maydell }
235581f7b12SPeter Maydell 
236af9f0be3SLaurent Vivier static uint16_t dp8393x_get(dp8393xState *s, int width, int offset)
237be920841SLaurent Vivier {
238be920841SLaurent Vivier     uint16_t val;
239be920841SLaurent Vivier 
240be920841SLaurent Vivier     if (s->big_endian) {
241af9f0be3SLaurent Vivier         val = be16_to_cpu(s->data[offset * width + width - 1]);
242be920841SLaurent Vivier     } else {
243af9f0be3SLaurent Vivier         val = le16_to_cpu(s->data[offset * width]);
244be920841SLaurent Vivier     }
245be920841SLaurent Vivier     return val;
246be920841SLaurent Vivier }
247be920841SLaurent Vivier 
248af9f0be3SLaurent Vivier static void dp8393x_put(dp8393xState *s, int width, int offset,
249be920841SLaurent Vivier                         uint16_t val)
250be920841SLaurent Vivier {
251be920841SLaurent Vivier     if (s->big_endian) {
2523fe9a838SFinn Thain         if (width == 2) {
2533fe9a838SFinn Thain             s->data[offset * 2] = 0;
2543fe9a838SFinn Thain             s->data[offset * 2 + 1] = cpu_to_be16(val);
255be920841SLaurent Vivier         } else {
2563fe9a838SFinn Thain             s->data[offset] = cpu_to_be16(val);
2573fe9a838SFinn Thain         }
2583fe9a838SFinn Thain     } else {
2593fe9a838SFinn Thain         if (width == 2) {
2603fe9a838SFinn Thain             s->data[offset * 2] = cpu_to_le16(val);
2613fe9a838SFinn Thain             s->data[offset * 2 + 1] = 0;
2623fe9a838SFinn Thain         } else {
2633fe9a838SFinn Thain             s->data[offset] = cpu_to_le16(val);
2643fe9a838SFinn Thain         }
265be920841SLaurent Vivier     }
266be920841SLaurent Vivier }
267be920841SLaurent Vivier 
268a65f56eeSaurel32 static void dp8393x_update_irq(dp8393xState *s)
269a65f56eeSaurel32 {
270a65f56eeSaurel32     int level = (s->regs[SONIC_IMR] & s->regs[SONIC_ISR]) ? 1 : 0;
271a65f56eeSaurel32 
272a65f56eeSaurel32 #ifdef DEBUG_SONIC
273a65f56eeSaurel32     if (level != s->irq_level) {
274a65f56eeSaurel32         s->irq_level = level;
275a65f56eeSaurel32         if (level) {
276a65f56eeSaurel32             DPRINTF("raise irq, isr is 0x%04x\n", s->regs[SONIC_ISR]);
277a65f56eeSaurel32         } else {
278a65f56eeSaurel32             DPRINTF("lower irq\n");
279a65f56eeSaurel32         }
280a65f56eeSaurel32     }
281a65f56eeSaurel32 #endif
282a65f56eeSaurel32 
283a65f56eeSaurel32     qemu_set_irq(s->irq, level);
284a65f56eeSaurel32 }
285a65f56eeSaurel32 
2863df5de64SHervé Poussineau static void dp8393x_do_load_cam(dp8393xState *s)
287a65f56eeSaurel32 {
288a65f56eeSaurel32     int width, size;
289a65f56eeSaurel32     uint16_t index = 0;
290a65f56eeSaurel32 
291a65f56eeSaurel32     width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
292a65f56eeSaurel32     size = sizeof(uint16_t) * 4 * width;
293a65f56eeSaurel32 
294a65f56eeSaurel32     while (s->regs[SONIC_CDC] & 0x1f) {
295a65f56eeSaurel32         /* Fill current entry */
29619f70347SPeter Maydell         address_space_read(&s->as, dp8393x_cdp(s),
29719f70347SPeter Maydell                            MEMTXATTRS_UNSPECIFIED, s->data, size);
298af9f0be3SLaurent Vivier         s->cam[index][0] = dp8393x_get(s, width, 1) & 0xff;
299af9f0be3SLaurent Vivier         s->cam[index][1] = dp8393x_get(s, width, 1) >> 8;
300af9f0be3SLaurent Vivier         s->cam[index][2] = dp8393x_get(s, width, 2) & 0xff;
301af9f0be3SLaurent Vivier         s->cam[index][3] = dp8393x_get(s, width, 2) >> 8;
302af9f0be3SLaurent Vivier         s->cam[index][4] = dp8393x_get(s, width, 3) & 0xff;
303af9f0be3SLaurent Vivier         s->cam[index][5] = dp8393x_get(s, width, 3) >> 8;
304a65f56eeSaurel32         DPRINTF("load cam[%d] with %02x%02x%02x%02x%02x%02x\n", index,
305a65f56eeSaurel32             s->cam[index][0], s->cam[index][1], s->cam[index][2],
306a65f56eeSaurel32             s->cam[index][3], s->cam[index][4], s->cam[index][5]);
307a65f56eeSaurel32         /* Move to next entry */
308a65f56eeSaurel32         s->regs[SONIC_CDC]--;
309a65f56eeSaurel32         s->regs[SONIC_CDP] += size;
310a65f56eeSaurel32         index++;
311a65f56eeSaurel32     }
312a65f56eeSaurel32 
313a65f56eeSaurel32     /* Read CAM enable */
31419f70347SPeter Maydell     address_space_read(&s->as, dp8393x_cdp(s),
31519f70347SPeter Maydell                        MEMTXATTRS_UNSPECIFIED, s->data, size);
316af9f0be3SLaurent Vivier     s->regs[SONIC_CE] = dp8393x_get(s, width, 0);
317a65f56eeSaurel32     DPRINTF("load cam done. cam enable mask 0x%04x\n", s->regs[SONIC_CE]);
318a65f56eeSaurel32 
319a65f56eeSaurel32     /* Done */
320a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_LCAM;
321a65f56eeSaurel32     s->regs[SONIC_ISR] |= SONIC_ISR_LCD;
322a65f56eeSaurel32     dp8393x_update_irq(s);
323a65f56eeSaurel32 }
324a65f56eeSaurel32 
3253df5de64SHervé Poussineau static void dp8393x_do_read_rra(dp8393xState *s)
326a65f56eeSaurel32 {
327a65f56eeSaurel32     int width, size;
328a65f56eeSaurel32 
329a65f56eeSaurel32     /* Read memory */
330a65f56eeSaurel32     width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
331a65f56eeSaurel32     size = sizeof(uint16_t) * 4 * width;
33219f70347SPeter Maydell     address_space_read(&s->as, dp8393x_rrp(s),
33319f70347SPeter Maydell                        MEMTXATTRS_UNSPECIFIED, s->data, size);
334a65f56eeSaurel32 
335a65f56eeSaurel32     /* Update SONIC registers */
336af9f0be3SLaurent Vivier     s->regs[SONIC_CRBA0] = dp8393x_get(s, width, 0);
337af9f0be3SLaurent Vivier     s->regs[SONIC_CRBA1] = dp8393x_get(s, width, 1);
338af9f0be3SLaurent Vivier     s->regs[SONIC_RBWC0] = dp8393x_get(s, width, 2);
339af9f0be3SLaurent Vivier     s->regs[SONIC_RBWC1] = dp8393x_get(s, width, 3);
340a65f56eeSaurel32     DPRINTF("CRBA0/1: 0x%04x/0x%04x, RBWC0/1: 0x%04x/0x%04x\n",
341a65f56eeSaurel32         s->regs[SONIC_CRBA0], s->regs[SONIC_CRBA1],
342a65f56eeSaurel32         s->regs[SONIC_RBWC0], s->regs[SONIC_RBWC1]);
343a65f56eeSaurel32 
344a65f56eeSaurel32     /* Go to next entry */
345a65f56eeSaurel32     s->regs[SONIC_RRP] += size;
346a65f56eeSaurel32 
347a65f56eeSaurel32     /* Handle wrap */
348a65f56eeSaurel32     if (s->regs[SONIC_RRP] == s->regs[SONIC_REA]) {
349a65f56eeSaurel32         s->regs[SONIC_RRP] = s->regs[SONIC_RSA];
350a65f56eeSaurel32     }
351a65f56eeSaurel32 
352c2279bd0SFinn Thain     /* Warn the host if CRBA now has the last available resource */
353a65f56eeSaurel32     if (s->regs[SONIC_RRP] == s->regs[SONIC_RWP])
354a65f56eeSaurel32     {
355a65f56eeSaurel32         s->regs[SONIC_ISR] |= SONIC_ISR_RBE;
356a65f56eeSaurel32         dp8393x_update_irq(s);
357a65f56eeSaurel32     }
358c2279bd0SFinn Thain 
359c2279bd0SFinn Thain     /* Allow packet reception */
360c2279bd0SFinn Thain     s->last_rba_is_full = false;
361a65f56eeSaurel32 }
362a65f56eeSaurel32 
3633df5de64SHervé Poussineau static void dp8393x_do_software_reset(dp8393xState *s)
364a65f56eeSaurel32 {
365bc72ad67SAlex Bligh     timer_del(s->watchdog);
366a65f56eeSaurel32 
367a65f56eeSaurel32     s->regs[SONIC_CR] &= ~(SONIC_CR_LCAM | SONIC_CR_RRRA | SONIC_CR_TXP | SONIC_CR_HTX);
368a65f56eeSaurel32     s->regs[SONIC_CR] |= SONIC_CR_RST | SONIC_CR_RXDIS;
369a65f56eeSaurel32 }
370a65f56eeSaurel32 
3713df5de64SHervé Poussineau static void dp8393x_set_next_tick(dp8393xState *s)
372a65f56eeSaurel32 {
373a65f56eeSaurel32     uint32_t ticks;
374a65f56eeSaurel32     int64_t delay;
375a65f56eeSaurel32 
376a65f56eeSaurel32     if (s->regs[SONIC_CR] & SONIC_CR_STP) {
377bc72ad67SAlex Bligh         timer_del(s->watchdog);
378a65f56eeSaurel32         return;
379a65f56eeSaurel32     }
380a65f56eeSaurel32 
381581f7b12SPeter Maydell     ticks = dp8393x_wt(s);
382bc72ad67SAlex Bligh     s->wt_last_update = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
38373bcb24dSRutuja Shah     delay = NANOSECONDS_PER_SECOND * ticks / 5000000;
384bc72ad67SAlex Bligh     timer_mod(s->watchdog, s->wt_last_update + delay);
385a65f56eeSaurel32 }
386a65f56eeSaurel32 
3873df5de64SHervé Poussineau static void dp8393x_update_wt_regs(dp8393xState *s)
388a65f56eeSaurel32 {
389a65f56eeSaurel32     int64_t elapsed;
390a65f56eeSaurel32     uint32_t val;
391a65f56eeSaurel32 
392a65f56eeSaurel32     if (s->regs[SONIC_CR] & SONIC_CR_STP) {
393bc72ad67SAlex Bligh         timer_del(s->watchdog);
394a65f56eeSaurel32         return;
395a65f56eeSaurel32     }
396a65f56eeSaurel32 
397bc72ad67SAlex Bligh     elapsed = s->wt_last_update - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
398581f7b12SPeter Maydell     val = dp8393x_wt(s);
399a65f56eeSaurel32     val -= elapsed / 5000000;
400a65f56eeSaurel32     s->regs[SONIC_WT1] = (val >> 16) & 0xffff;
401a65f56eeSaurel32     s->regs[SONIC_WT0] = (val >> 0)  & 0xffff;
4023df5de64SHervé Poussineau     dp8393x_set_next_tick(s);
403a65f56eeSaurel32 
404a65f56eeSaurel32 }
405a65f56eeSaurel32 
4063df5de64SHervé Poussineau static void dp8393x_do_start_timer(dp8393xState *s)
407a65f56eeSaurel32 {
408a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_STP;
4093df5de64SHervé Poussineau     dp8393x_set_next_tick(s);
410a65f56eeSaurel32 }
411a65f56eeSaurel32 
4123df5de64SHervé Poussineau static void dp8393x_do_stop_timer(dp8393xState *s)
413a65f56eeSaurel32 {
414a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_ST;
4153df5de64SHervé Poussineau     dp8393x_update_wt_regs(s);
416a65f56eeSaurel32 }
417a65f56eeSaurel32 
418b8c4b67eSPhilippe Mathieu-Daudé static bool dp8393x_can_receive(NetClientState *nc);
4194594f93aSFam Zheng 
4203df5de64SHervé Poussineau static void dp8393x_do_receiver_enable(dp8393xState *s)
421a65f56eeSaurel32 {
422a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_RXDIS;
4234594f93aSFam Zheng     if (dp8393x_can_receive(s->nic->ncs)) {
4244594f93aSFam Zheng         qemu_flush_queued_packets(qemu_get_queue(s->nic));
4254594f93aSFam Zheng     }
426a65f56eeSaurel32 }
427a65f56eeSaurel32 
4283df5de64SHervé Poussineau static void dp8393x_do_receiver_disable(dp8393xState *s)
429a65f56eeSaurel32 {
430a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_RXEN;
431a65f56eeSaurel32 }
432a65f56eeSaurel32 
4333df5de64SHervé Poussineau static void dp8393x_do_transmit_packets(dp8393xState *s)
434a65f56eeSaurel32 {
435b356f76dSJason Wang     NetClientState *nc = qemu_get_queue(s->nic);
436a65f56eeSaurel32     int width, size;
437a65f56eeSaurel32     int tx_len, len;
438a65f56eeSaurel32     uint16_t i;
439a65f56eeSaurel32 
440a65f56eeSaurel32     width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
441a65f56eeSaurel32 
442a65f56eeSaurel32     while (1) {
443a65f56eeSaurel32         /* Read memory */
444a65f56eeSaurel32         size = sizeof(uint16_t) * 6 * width;
445a65f56eeSaurel32         s->regs[SONIC_TTDA] = s->regs[SONIC_CTDA];
446581f7b12SPeter Maydell         DPRINTF("Transmit packet at %08x\n", dp8393x_ttda(s));
44719f70347SPeter Maydell         address_space_read(&s->as, dp8393x_ttda(s) + sizeof(uint16_t) * width,
44819f70347SPeter Maydell                            MEMTXATTRS_UNSPECIFIED, s->data, size);
449a65f56eeSaurel32         tx_len = 0;
450a65f56eeSaurel32 
451a65f56eeSaurel32         /* Update registers */
452af9f0be3SLaurent Vivier         s->regs[SONIC_TCR] = dp8393x_get(s, width, 0) & 0xf000;
453af9f0be3SLaurent Vivier         s->regs[SONIC_TPS] = dp8393x_get(s, width, 1);
454af9f0be3SLaurent Vivier         s->regs[SONIC_TFC] = dp8393x_get(s, width, 2);
455af9f0be3SLaurent Vivier         s->regs[SONIC_TSA0] = dp8393x_get(s, width, 3);
456af9f0be3SLaurent Vivier         s->regs[SONIC_TSA1] = dp8393x_get(s, width, 4);
457af9f0be3SLaurent Vivier         s->regs[SONIC_TFS] = dp8393x_get(s, width, 5);
458a65f56eeSaurel32 
459a65f56eeSaurel32         /* Handle programmable interrupt */
460a65f56eeSaurel32         if (s->regs[SONIC_TCR] & SONIC_TCR_PINT) {
461a65f56eeSaurel32             s->regs[SONIC_ISR] |= SONIC_ISR_PINT;
462a65f56eeSaurel32         } else {
463a65f56eeSaurel32             s->regs[SONIC_ISR] &= ~SONIC_ISR_PINT;
464a65f56eeSaurel32         }
465a65f56eeSaurel32 
466a65f56eeSaurel32         for (i = 0; i < s->regs[SONIC_TFC]; ) {
467a65f56eeSaurel32             /* Append fragment */
468a65f56eeSaurel32             len = s->regs[SONIC_TFS];
469a65f56eeSaurel32             if (tx_len + len > sizeof(s->tx_buffer)) {
470a65f56eeSaurel32                 len = sizeof(s->tx_buffer) - tx_len;
471a65f56eeSaurel32             }
47219f70347SPeter Maydell             address_space_read(&s->as, dp8393x_tsa(s), MEMTXATTRS_UNSPECIFIED,
47319f70347SPeter Maydell                                &s->tx_buffer[tx_len], len);
474a65f56eeSaurel32             tx_len += len;
475a65f56eeSaurel32 
476a65f56eeSaurel32             i++;
477a65f56eeSaurel32             if (i != s->regs[SONIC_TFC]) {
478a65f56eeSaurel32                 /* Read next fragment details */
479a65f56eeSaurel32                 size = sizeof(uint16_t) * 3 * width;
48019f70347SPeter Maydell                 address_space_read(&s->as,
48119f70347SPeter Maydell                                    dp8393x_ttda(s)
48219f70347SPeter Maydell                                    + sizeof(uint16_t) * width * (4 + 3 * i),
48319f70347SPeter Maydell                                    MEMTXATTRS_UNSPECIFIED, s->data,
48419f70347SPeter Maydell                                    size);
485af9f0be3SLaurent Vivier                 s->regs[SONIC_TSA0] = dp8393x_get(s, width, 0);
486af9f0be3SLaurent Vivier                 s->regs[SONIC_TSA1] = dp8393x_get(s, width, 1);
487af9f0be3SLaurent Vivier                 s->regs[SONIC_TFS] = dp8393x_get(s, width, 2);
488a65f56eeSaurel32             }
489a65f56eeSaurel32         }
490a65f56eeSaurel32 
491a65f56eeSaurel32         /* Handle Ethernet checksum */
492a65f56eeSaurel32         if (!(s->regs[SONIC_TCR] & SONIC_TCR_CRCI)) {
493a65f56eeSaurel32             /* Don't append FCS there, to look like slirp packets
494a65f56eeSaurel32              * which don't have one */
495a65f56eeSaurel32         } else {
496a65f56eeSaurel32             /* Remove existing FCS */
497a65f56eeSaurel32             tx_len -= 4;
498915976bdSMauro Matteo Cascella             if (tx_len < 0) {
499915976bdSMauro Matteo Cascella                 SONIC_ERROR("tx_len is %d\n", tx_len);
500915976bdSMauro Matteo Cascella                 break;
501915976bdSMauro Matteo Cascella             }
502a65f56eeSaurel32         }
503a65f56eeSaurel32 
504a65f56eeSaurel32         if (s->regs[SONIC_RCR] & (SONIC_RCR_LB1 | SONIC_RCR_LB0)) {
505a65f56eeSaurel32             /* Loopback */
506a65f56eeSaurel32             s->regs[SONIC_TCR] |= SONIC_TCR_CRSL;
507b356f76dSJason Wang             if (nc->info->can_receive(nc)) {
508a65f56eeSaurel32                 s->loopback_packet = 1;
509*331d2ac9SJason Wang                 qemu_receive_packet(nc, s->tx_buffer, tx_len);
510a65f56eeSaurel32             }
511a65f56eeSaurel32         } else {
512a65f56eeSaurel32             /* Transmit packet */
513b356f76dSJason Wang             qemu_send_packet(nc, s->tx_buffer, tx_len);
514a65f56eeSaurel32         }
515a65f56eeSaurel32         s->regs[SONIC_TCR] |= SONIC_TCR_PTX;
516a65f56eeSaurel32 
517a65f56eeSaurel32         /* Write status */
518af9f0be3SLaurent Vivier         dp8393x_put(s, width, 0,
519be920841SLaurent Vivier                     s->regs[SONIC_TCR] & 0x0fff); /* status */
520a65f56eeSaurel32         size = sizeof(uint16_t) * width;
52119f70347SPeter Maydell         address_space_write(&s->as, dp8393x_ttda(s),
52219f70347SPeter Maydell                             MEMTXATTRS_UNSPECIFIED, s->data, size);
523a65f56eeSaurel32 
524a65f56eeSaurel32         if (!(s->regs[SONIC_CR] & SONIC_CR_HTX)) {
525a65f56eeSaurel32             /* Read footer of packet */
526a65f56eeSaurel32             size = sizeof(uint16_t) * width;
52719f70347SPeter Maydell             address_space_read(&s->as,
52819f70347SPeter Maydell                                dp8393x_ttda(s)
52919f70347SPeter Maydell                                + sizeof(uint16_t) * width
53019f70347SPeter Maydell                                  * (4 + 3 * s->regs[SONIC_TFC]),
53119f70347SPeter Maydell                                MEMTXATTRS_UNSPECIFIED, s->data,
53219f70347SPeter Maydell                                size);
533a0cf4297SFinn Thain             s->regs[SONIC_CTDA] = dp8393x_get(s, width, 0);
534a0cf4297SFinn Thain             if (s->regs[SONIC_CTDA] & SONIC_DESC_EOL) {
535a65f56eeSaurel32                 /* EOL detected */
536a65f56eeSaurel32                 break;
537a65f56eeSaurel32             }
538a65f56eeSaurel32         }
539a65f56eeSaurel32     }
540a65f56eeSaurel32 
541a65f56eeSaurel32     /* Done */
542a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_TXP;
543a65f56eeSaurel32     s->regs[SONIC_ISR] |= SONIC_ISR_TXDN;
544a65f56eeSaurel32     dp8393x_update_irq(s);
545a65f56eeSaurel32 }
546a65f56eeSaurel32 
5473df5de64SHervé Poussineau static void dp8393x_do_halt_transmission(dp8393xState *s)
548a65f56eeSaurel32 {
549a65f56eeSaurel32     /* Nothing to do */
550a65f56eeSaurel32 }
551a65f56eeSaurel32 
5523df5de64SHervé Poussineau static void dp8393x_do_command(dp8393xState *s, uint16_t command)
553a65f56eeSaurel32 {
554a65f56eeSaurel32     if ((s->regs[SONIC_CR] & SONIC_CR_RST) && !(command & SONIC_CR_RST)) {
555a65f56eeSaurel32         s->regs[SONIC_CR] &= ~SONIC_CR_RST;
556a65f56eeSaurel32         return;
557a65f56eeSaurel32     }
558a65f56eeSaurel32 
559a65f56eeSaurel32     s->regs[SONIC_CR] |= (command & SONIC_CR_MASK);
560a65f56eeSaurel32 
561a65f56eeSaurel32     if (command & SONIC_CR_HTX)
5623df5de64SHervé Poussineau         dp8393x_do_halt_transmission(s);
563a65f56eeSaurel32     if (command & SONIC_CR_TXP)
5643df5de64SHervé Poussineau         dp8393x_do_transmit_packets(s);
565a65f56eeSaurel32     if (command & SONIC_CR_RXDIS)
5663df5de64SHervé Poussineau         dp8393x_do_receiver_disable(s);
567a65f56eeSaurel32     if (command & SONIC_CR_RXEN)
5683df5de64SHervé Poussineau         dp8393x_do_receiver_enable(s);
569a65f56eeSaurel32     if (command & SONIC_CR_STP)
5703df5de64SHervé Poussineau         dp8393x_do_stop_timer(s);
571a65f56eeSaurel32     if (command & SONIC_CR_ST)
5723df5de64SHervé Poussineau         dp8393x_do_start_timer(s);
573a65f56eeSaurel32     if (command & SONIC_CR_RST)
5743df5de64SHervé Poussineau         dp8393x_do_software_reset(s);
575a3cce282SFinn Thain     if (command & SONIC_CR_RRRA) {
5763df5de64SHervé Poussineau         dp8393x_do_read_rra(s);
577a3cce282SFinn Thain         s->regs[SONIC_CR] &= ~SONIC_CR_RRRA;
578a3cce282SFinn Thain     }
579a65f56eeSaurel32     if (command & SONIC_CR_LCAM)
5803df5de64SHervé Poussineau         dp8393x_do_load_cam(s);
581a65f56eeSaurel32 }
582a65f56eeSaurel32 
58384689cbbSHervé Poussineau static uint64_t dp8393x_read(void *opaque, hwaddr addr, unsigned int size)
584a65f56eeSaurel32 {
58584689cbbSHervé Poussineau     dp8393xState *s = opaque;
58684689cbbSHervé Poussineau     int reg = addr >> s->it_shift;
587a65f56eeSaurel32     uint16_t val = 0;
588a65f56eeSaurel32 
589a65f56eeSaurel32     switch (reg) {
590a65f56eeSaurel32         /* Update data before reading it */
591a65f56eeSaurel32         case SONIC_WT0:
592a65f56eeSaurel32         case SONIC_WT1:
5933df5de64SHervé Poussineau             dp8393x_update_wt_regs(s);
594a65f56eeSaurel32             val = s->regs[reg];
595a65f56eeSaurel32             break;
596a65f56eeSaurel32         /* Accept read to some registers only when in reset mode */
597a65f56eeSaurel32         case SONIC_CAP2:
598a65f56eeSaurel32         case SONIC_CAP1:
599a65f56eeSaurel32         case SONIC_CAP0:
600a65f56eeSaurel32             if (s->regs[SONIC_CR] & SONIC_CR_RST) {
601a65f56eeSaurel32                 val = s->cam[s->regs[SONIC_CEP] & 0xf][2* (SONIC_CAP0 - reg) + 1] << 8;
602a65f56eeSaurel32                 val |= s->cam[s->regs[SONIC_CEP] & 0xf][2* (SONIC_CAP0 - reg)];
603a65f56eeSaurel32             }
604a65f56eeSaurel32             break;
605a65f56eeSaurel32         /* All other registers have no special contrainst */
606a65f56eeSaurel32         default:
607a65f56eeSaurel32             val = s->regs[reg];
608a65f56eeSaurel32     }
609a65f56eeSaurel32 
610a65f56eeSaurel32     DPRINTF("read 0x%04x from reg %s\n", val, reg_names[reg]);
611a65f56eeSaurel32 
6123fe9a838SFinn Thain     return s->big_endian ? val << 16 : val;
613a65f56eeSaurel32 }
614a65f56eeSaurel32 
61584689cbbSHervé Poussineau static void dp8393x_write(void *opaque, hwaddr addr, uint64_t data,
61684689cbbSHervé Poussineau                           unsigned int size)
617a65f56eeSaurel32 {
61884689cbbSHervé Poussineau     dp8393xState *s = opaque;
61984689cbbSHervé Poussineau     int reg = addr >> s->it_shift;
6203fe9a838SFinn Thain     uint32_t val = s->big_endian ? data >> 16 : data;
62184689cbbSHervé Poussineau 
6223fe9a838SFinn Thain     DPRINTF("write 0x%04x to reg %s\n", (uint16_t)val, reg_names[reg]);
623a65f56eeSaurel32 
624a65f56eeSaurel32     switch (reg) {
625a65f56eeSaurel32         /* Command register */
626a65f56eeSaurel32         case SONIC_CR:
6273fe9a838SFinn Thain             dp8393x_do_command(s, val);
628a65f56eeSaurel32             break;
629a65f56eeSaurel32         /* Prevent write to read-only registers */
630a65f56eeSaurel32         case SONIC_CAP2:
631a65f56eeSaurel32         case SONIC_CAP1:
632a65f56eeSaurel32         case SONIC_CAP0:
633a65f56eeSaurel32         case SONIC_SR:
634a65f56eeSaurel32         case SONIC_MDT:
635a65f56eeSaurel32             DPRINTF("writing to reg %d invalid\n", reg);
636a65f56eeSaurel32             break;
637a65f56eeSaurel32         /* Accept write to some registers only when in reset mode */
638a65f56eeSaurel32         case SONIC_DCR:
639a65f56eeSaurel32             if (s->regs[SONIC_CR] & SONIC_CR_RST) {
6403fe9a838SFinn Thain                 s->regs[reg] = val & 0xbfff;
641a65f56eeSaurel32             } else {
642a65f56eeSaurel32                 DPRINTF("writing to DCR invalid\n");
643a65f56eeSaurel32             }
644a65f56eeSaurel32             break;
645a65f56eeSaurel32         case SONIC_DCR2:
646a65f56eeSaurel32             if (s->regs[SONIC_CR] & SONIC_CR_RST) {
6473fe9a838SFinn Thain                 s->regs[reg] = val & 0xf017;
648a65f56eeSaurel32             } else {
649a65f56eeSaurel32                 DPRINTF("writing to DCR2 invalid\n");
650a65f56eeSaurel32             }
651a65f56eeSaurel32             break;
652a65f56eeSaurel32         /* 12 lower bytes are Read Only */
653a65f56eeSaurel32         case SONIC_TCR:
6543fe9a838SFinn Thain             s->regs[reg] = val & 0xf000;
655a65f56eeSaurel32             break;
656a65f56eeSaurel32         /* 9 lower bytes are Read Only */
657a65f56eeSaurel32         case SONIC_RCR:
6583fe9a838SFinn Thain             s->regs[reg] = val & 0xffe0;
659a65f56eeSaurel32             break;
660a65f56eeSaurel32         /* Ignore most significant bit */
661a65f56eeSaurel32         case SONIC_IMR:
6623fe9a838SFinn Thain             s->regs[reg] = val & 0x7fff;
663a65f56eeSaurel32             dp8393x_update_irq(s);
664a65f56eeSaurel32             break;
665a65f56eeSaurel32         /* Clear bits by writing 1 to them */
666a65f56eeSaurel32         case SONIC_ISR:
6673fe9a838SFinn Thain             val &= s->regs[reg];
6683fe9a838SFinn Thain             s->regs[reg] &= ~val;
6693fe9a838SFinn Thain             if (val & SONIC_ISR_RBE) {
6703df5de64SHervé Poussineau                 dp8393x_do_read_rra(s);
671a65f56eeSaurel32             }
672a65f56eeSaurel32             dp8393x_update_irq(s);
673a65f56eeSaurel32             break;
674ea227027SFinn Thain         /* The guest is required to store aligned pointers here */
675a65f56eeSaurel32         case SONIC_RSA:
676a65f56eeSaurel32         case SONIC_REA:
677a65f56eeSaurel32         case SONIC_RRP:
678a65f56eeSaurel32         case SONIC_RWP:
679ea227027SFinn Thain             if (s->regs[SONIC_DCR] & SONIC_DCR_DW) {
680ea227027SFinn Thain                 s->regs[reg] = val & 0xfffc;
681ea227027SFinn Thain             } else {
6823fe9a838SFinn Thain                 s->regs[reg] = val & 0xfffe;
683ea227027SFinn Thain             }
684a65f56eeSaurel32             break;
685a65f56eeSaurel32         /* Invert written value for some registers */
686a65f56eeSaurel32         case SONIC_CRCT:
687a65f56eeSaurel32         case SONIC_FAET:
688a65f56eeSaurel32         case SONIC_MPT:
6893fe9a838SFinn Thain             s->regs[reg] = val ^ 0xffff;
690a65f56eeSaurel32             break;
691a65f56eeSaurel32         /* All other registers have no special contrainst */
692a65f56eeSaurel32         default:
6933fe9a838SFinn Thain             s->regs[reg] = val;
694a65f56eeSaurel32     }
695a65f56eeSaurel32 
696a65f56eeSaurel32     if (reg == SONIC_WT0 || reg == SONIC_WT1) {
6973df5de64SHervé Poussineau         dp8393x_set_next_tick(s);
698a65f56eeSaurel32     }
699a65f56eeSaurel32 }
700a65f56eeSaurel32 
70184689cbbSHervé Poussineau static const MemoryRegionOps dp8393x_ops = {
70284689cbbSHervé Poussineau     .read = dp8393x_read,
70384689cbbSHervé Poussineau     .write = dp8393x_write,
7043fe9a838SFinn Thain     .impl.min_access_size = 4,
7053fe9a838SFinn Thain     .impl.max_access_size = 4,
70684689cbbSHervé Poussineau     .endianness = DEVICE_NATIVE_ENDIAN,
70784689cbbSHervé Poussineau };
70884689cbbSHervé Poussineau 
709a65f56eeSaurel32 static void dp8393x_watchdog(void *opaque)
710a65f56eeSaurel32 {
711a65f56eeSaurel32     dp8393xState *s = opaque;
712a65f56eeSaurel32 
713a65f56eeSaurel32     if (s->regs[SONIC_CR] & SONIC_CR_STP) {
714a65f56eeSaurel32         return;
715a65f56eeSaurel32     }
716a65f56eeSaurel32 
717a65f56eeSaurel32     s->regs[SONIC_WT1] = 0xffff;
718a65f56eeSaurel32     s->regs[SONIC_WT0] = 0xffff;
7193df5de64SHervé Poussineau     dp8393x_set_next_tick(s);
720a65f56eeSaurel32 
721a65f56eeSaurel32     /* Signal underflow */
722a65f56eeSaurel32     s->regs[SONIC_ISR] |= SONIC_ISR_TC;
723a65f56eeSaurel32     dp8393x_update_irq(s);
724a65f56eeSaurel32 }
725a65f56eeSaurel32 
726b8c4b67eSPhilippe Mathieu-Daudé static bool dp8393x_can_receive(NetClientState *nc)
727a65f56eeSaurel32 {
728cc1f0f45SJason Wang     dp8393xState *s = qemu_get_nic_opaque(nc);
729a65f56eeSaurel32 
730b8c4b67eSPhilippe Mathieu-Daudé     return !!(s->regs[SONIC_CR] & SONIC_CR_RXEN);
731a65f56eeSaurel32 }
732a65f56eeSaurel32 
7333df5de64SHervé Poussineau static int dp8393x_receive_filter(dp8393xState *s, const uint8_t * buf,
7343df5de64SHervé Poussineau                                   int size)
735a65f56eeSaurel32 {
736a65f56eeSaurel32     static const uint8_t bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
737a65f56eeSaurel32     int i;
738a65f56eeSaurel32 
739a65f56eeSaurel32     /* Check promiscuous mode */
740a65f56eeSaurel32     if ((s->regs[SONIC_RCR] & SONIC_RCR_PRO) && (buf[0] & 1) == 0) {
741a65f56eeSaurel32         return 0;
742a65f56eeSaurel32     }
743a65f56eeSaurel32 
744a65f56eeSaurel32     /* Check multicast packets */
745a65f56eeSaurel32     if ((s->regs[SONIC_RCR] & SONIC_RCR_AMC) && (buf[0] & 1) == 1) {
746a65f56eeSaurel32         return SONIC_RCR_MC;
747a65f56eeSaurel32     }
748a65f56eeSaurel32 
749a65f56eeSaurel32     /* Check broadcast */
750a65f56eeSaurel32     if ((s->regs[SONIC_RCR] & SONIC_RCR_BRD) && !memcmp(buf, bcast, sizeof(bcast))) {
751a65f56eeSaurel32         return SONIC_RCR_BC;
752a65f56eeSaurel32     }
753a65f56eeSaurel32 
754a65f56eeSaurel32     /* Check CAM */
755a65f56eeSaurel32     for (i = 0; i < 16; i++) {
756a65f56eeSaurel32         if (s->regs[SONIC_CE] & (1 << i)) {
757a65f56eeSaurel32              /* Entry enabled */
758a65f56eeSaurel32              if (!memcmp(buf, s->cam[i], sizeof(s->cam[i]))) {
759a65f56eeSaurel32                  return 0;
760a65f56eeSaurel32              }
761a65f56eeSaurel32         }
762a65f56eeSaurel32     }
763a65f56eeSaurel32 
764a65f56eeSaurel32     return -1;
765a65f56eeSaurel32 }
766a65f56eeSaurel32 
7673df5de64SHervé Poussineau static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
7689e3cd456SFinn Thain                                size_t pkt_size)
769a65f56eeSaurel32 {
770cc1f0f45SJason Wang     dp8393xState *s = qemu_get_nic_opaque(nc);
771a65f56eeSaurel32     int packet_type;
772a65f56eeSaurel32     uint32_t available, address;
773350e7d9aSFinn Thain     int width, rx_len, padded_len;
774a65f56eeSaurel32     uint32_t checksum;
7759e3cd456SFinn Thain     int size;
776a65f56eeSaurel32 
777a65f56eeSaurel32     s->regs[SONIC_RCR] &= ~(SONIC_RCR_PRX | SONIC_RCR_LBK | SONIC_RCR_FAER |
778a65f56eeSaurel32         SONIC_RCR_CRCR | SONIC_RCR_LPKT | SONIC_RCR_BC | SONIC_RCR_MC);
779a65f56eeSaurel32 
780c2279bd0SFinn Thain     if (s->last_rba_is_full) {
781c2279bd0SFinn Thain         return pkt_size;
782c2279bd0SFinn Thain     }
783c2279bd0SFinn Thain 
784350e7d9aSFinn Thain     rx_len = pkt_size + sizeof(checksum);
785350e7d9aSFinn Thain     if (s->regs[SONIC_DCR] & SONIC_DCR_DW) {
786350e7d9aSFinn Thain         width = 2;
787350e7d9aSFinn Thain         padded_len = ((rx_len - 1) | 3) + 1;
788350e7d9aSFinn Thain     } else {
789350e7d9aSFinn Thain         width = 1;
790350e7d9aSFinn Thain         padded_len = ((rx_len - 1) | 1) + 1;
791350e7d9aSFinn Thain     }
792350e7d9aSFinn Thain 
793350e7d9aSFinn Thain     if (padded_len > dp8393x_rbwc(s) * 2) {
794ada74315SFinn Thain         DPRINTF("oversize packet, pkt_size is %d\n", pkt_size);
795ada74315SFinn Thain         s->regs[SONIC_ISR] |= SONIC_ISR_RBAE;
796ada74315SFinn Thain         dp8393x_update_irq(s);
797c2279bd0SFinn Thain         s->regs[SONIC_RCR] |= SONIC_RCR_LPKT;
798c2279bd0SFinn Thain         goto done;
799ada74315SFinn Thain     }
800ada74315SFinn Thain 
8019e3cd456SFinn Thain     packet_type = dp8393x_receive_filter(s, buf, pkt_size);
802a65f56eeSaurel32     if (packet_type < 0) {
803a65f56eeSaurel32         DPRINTF("packet not for netcard\n");
8044f1c942bSMark McLoughlin         return -1;
805a65f56eeSaurel32     }
806a65f56eeSaurel32 
807a65f56eeSaurel32     /* Check for EOL */
80888f632fbSFinn Thain     if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
809a65f56eeSaurel32         /* Are we still in resource exhaustion? */
810a65f56eeSaurel32         size = sizeof(uint16_t) * 1 * width;
811581f7b12SPeter Maydell         address = dp8393x_crda(s) + sizeof(uint16_t) * 5 * width;
81219f70347SPeter Maydell         address_space_read(&s->as, address, MEMTXATTRS_UNSPECIFIED,
81319f70347SPeter Maydell                            s->data, size);
8145b0c98fcSFinn Thain         s->regs[SONIC_LLFA] = dp8393x_get(s, width, 0);
8155b0c98fcSFinn Thain         if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
816a65f56eeSaurel32             /* Still EOL ; stop reception */
8174f1c942bSMark McLoughlin             return -1;
818a65f56eeSaurel32         }
8195b0c98fcSFinn Thain         /* Link has been updated by host */
820d9fae131SFinn Thain 
821d9fae131SFinn Thain         /* Clear in_use */
822d9fae131SFinn Thain         size = sizeof(uint16_t) * width;
823d9fae131SFinn Thain         address = dp8393x_crda(s) + sizeof(uint16_t) * 6 * width;
824d9fae131SFinn Thain         dp8393x_put(s, width, 0, 0);
825d9fae131SFinn Thain         address_space_rw(&s->as, address, MEMTXATTRS_UNSPECIFIED,
826d9fae131SFinn Thain                          (uint8_t *)s->data, size, 1);
827d9fae131SFinn Thain 
828d9fae131SFinn Thain         /* Move to next descriptor */
8295b0c98fcSFinn Thain         s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
830d9fae131SFinn Thain         s->regs[SONIC_ISR] |= SONIC_ISR_PKTRX;
831a65f56eeSaurel32     }
832a65f56eeSaurel32 
833a65f56eeSaurel32     /* Save current position */
834a65f56eeSaurel32     s->regs[SONIC_TRBA1] = s->regs[SONIC_CRBA1];
835a65f56eeSaurel32     s->regs[SONIC_TRBA0] = s->regs[SONIC_CRBA0];
836a65f56eeSaurel32 
837a65f56eeSaurel32     /* Calculate the ethernet checksum */
838350e7d9aSFinn Thain     checksum = cpu_to_le32(crc32(0, buf, pkt_size));
839a65f56eeSaurel32 
840a65f56eeSaurel32     /* Put packet into RBA */
841581f7b12SPeter Maydell     DPRINTF("Receive packet at %08x\n", dp8393x_crba(s));
842581f7b12SPeter Maydell     address = dp8393x_crba(s);
84319f70347SPeter Maydell     address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
844350e7d9aSFinn Thain                         buf, pkt_size);
845350e7d9aSFinn Thain     address += pkt_size;
846350e7d9aSFinn Thain 
847350e7d9aSFinn Thain     /* Put frame checksum into RBA */
84819f70347SPeter Maydell     address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
849350e7d9aSFinn Thain                         &checksum, sizeof(checksum));
850350e7d9aSFinn Thain     address += sizeof(checksum);
851350e7d9aSFinn Thain 
852350e7d9aSFinn Thain     /* Pad short packets to keep pointers aligned */
853350e7d9aSFinn Thain     if (rx_len < padded_len) {
854350e7d9aSFinn Thain         size = padded_len - rx_len;
855350e7d9aSFinn Thain         address_space_rw(&s->as, address, MEMTXATTRS_UNSPECIFIED,
856350e7d9aSFinn Thain             (uint8_t *)"\xFF\xFF\xFF", size, 1);
857350e7d9aSFinn Thain         address += size;
858350e7d9aSFinn Thain     }
859350e7d9aSFinn Thain 
860a65f56eeSaurel32     s->regs[SONIC_CRBA1] = address >> 16;
861a65f56eeSaurel32     s->regs[SONIC_CRBA0] = address & 0xffff;
862581f7b12SPeter Maydell     available = dp8393x_rbwc(s);
863350e7d9aSFinn Thain     available -= padded_len >> 1;
864a65f56eeSaurel32     s->regs[SONIC_RBWC1] = available >> 16;
865a65f56eeSaurel32     s->regs[SONIC_RBWC0] = available & 0xffff;
866a65f56eeSaurel32 
867a65f56eeSaurel32     /* Update status */
868581f7b12SPeter Maydell     if (dp8393x_rbwc(s) < s->regs[SONIC_EOBC]) {
869a65f56eeSaurel32         s->regs[SONIC_RCR] |= SONIC_RCR_LPKT;
870a65f56eeSaurel32     }
871a65f56eeSaurel32     s->regs[SONIC_RCR] |= packet_type;
872a65f56eeSaurel32     s->regs[SONIC_RCR] |= SONIC_RCR_PRX;
873a65f56eeSaurel32     if (s->loopback_packet) {
874a65f56eeSaurel32         s->regs[SONIC_RCR] |= SONIC_RCR_LBK;
875a65f56eeSaurel32         s->loopback_packet = 0;
876a65f56eeSaurel32     }
877a65f56eeSaurel32 
878a65f56eeSaurel32     /* Write status to memory */
879581f7b12SPeter Maydell     DPRINTF("Write status at %08x\n", dp8393x_crda(s));
880af9f0be3SLaurent Vivier     dp8393x_put(s, width, 0, s->regs[SONIC_RCR]); /* status */
881af9f0be3SLaurent Vivier     dp8393x_put(s, width, 1, rx_len); /* byte count */
882af9f0be3SLaurent Vivier     dp8393x_put(s, width, 2, s->regs[SONIC_TRBA0]); /* pkt_ptr0 */
883af9f0be3SLaurent Vivier     dp8393x_put(s, width, 3, s->regs[SONIC_TRBA1]); /* pkt_ptr1 */
884af9f0be3SLaurent Vivier     dp8393x_put(s, width, 4, s->regs[SONIC_RSC]); /* seq_no */
885a65f56eeSaurel32     size = sizeof(uint16_t) * 5 * width;
88619f70347SPeter Maydell     address_space_write(&s->as, dp8393x_crda(s),
88719f70347SPeter Maydell                         MEMTXATTRS_UNSPECIFIED,
88819f70347SPeter Maydell                         s->data, size);
889a65f56eeSaurel32 
8905b0c98fcSFinn Thain     /* Check link field */
891a65f56eeSaurel32     size = sizeof(uint16_t) * width;
89219f70347SPeter Maydell     address_space_read(&s->as,
89319f70347SPeter Maydell                        dp8393x_crda(s) + sizeof(uint16_t) * 5 * width,
89419f70347SPeter Maydell                        MEMTXATTRS_UNSPECIFIED, s->data, size);
895af9f0be3SLaurent Vivier     s->regs[SONIC_LLFA] = dp8393x_get(s, width, 0);
89688f632fbSFinn Thain     if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
897a65f56eeSaurel32         /* EOL detected */
898a65f56eeSaurel32         s->regs[SONIC_ISR] |= SONIC_ISR_RDE;
899a65f56eeSaurel32     } else {
90046ffee9aSFinn Thain         /* Clear in_use */
90146ffee9aSFinn Thain         size = sizeof(uint16_t) * width;
90246ffee9aSFinn Thain         address = dp8393x_crda(s) + sizeof(uint16_t) * 6 * width;
90346ffee9aSFinn Thain         dp8393x_put(s, width, 0, 0);
90446ffee9aSFinn Thain         address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
90546ffee9aSFinn Thain                             s->data, size);
9065b0c98fcSFinn Thain 
9075b0c98fcSFinn Thain         /* Move to next descriptor */
908a65f56eeSaurel32         s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
909a65f56eeSaurel32         s->regs[SONIC_ISR] |= SONIC_ISR_PKTRX;
91080b60673SFinn Thain     }
91180b60673SFinn Thain 
912c2279bd0SFinn Thain     dp8393x_update_irq(s);
913c2279bd0SFinn Thain 
91480b60673SFinn Thain     s->regs[SONIC_RSC] = (s->regs[SONIC_RSC] & 0xff00) |
91580b60673SFinn Thain                          ((s->regs[SONIC_RSC] + 1) & 0x00ff);
916a65f56eeSaurel32 
917c2279bd0SFinn Thain done:
918c2279bd0SFinn Thain 
919a65f56eeSaurel32     if (s->regs[SONIC_RCR] & SONIC_RCR_LPKT) {
920c2279bd0SFinn Thain         if (s->regs[SONIC_RRP] == s->regs[SONIC_RWP]) {
921c2279bd0SFinn Thain             /* Stop packet reception */
922c2279bd0SFinn Thain             s->last_rba_is_full = true;
923c2279bd0SFinn Thain         } else {
924c2279bd0SFinn Thain             /* Read next resource */
9253df5de64SHervé Poussineau             dp8393x_do_read_rra(s);
926a65f56eeSaurel32         }
927c2279bd0SFinn Thain     }
9284f1c942bSMark McLoughlin 
9299e3cd456SFinn Thain     return pkt_size;
930a65f56eeSaurel32 }
931a65f56eeSaurel32 
932104655a5SHervé Poussineau static void dp8393x_reset(DeviceState *dev)
933a65f56eeSaurel32 {
934104655a5SHervé Poussineau     dp8393xState *s = DP8393X(dev);
935bc72ad67SAlex Bligh     timer_del(s->watchdog);
936a65f56eeSaurel32 
937bd8f1ebcSHervé Poussineau     memset(s->regs, 0, sizeof(s->regs));
938083e21bbSFinn Thain     s->regs[SONIC_SR] = 0x0004; /* only revision recognized by Linux/mips */
939a65f56eeSaurel32     s->regs[SONIC_CR] = SONIC_CR_RST | SONIC_CR_STP | SONIC_CR_RXDIS;
940a65f56eeSaurel32     s->regs[SONIC_DCR] &= ~(SONIC_DCR_EXBUS | SONIC_DCR_LBR);
941a65f56eeSaurel32     s->regs[SONIC_RCR] &= ~(SONIC_RCR_LB0 | SONIC_RCR_LB1 | SONIC_RCR_BRD | SONIC_RCR_RNT);
942a65f56eeSaurel32     s->regs[SONIC_TCR] |= SONIC_TCR_NCRS | SONIC_TCR_PTX;
943a65f56eeSaurel32     s->regs[SONIC_TCR] &= ~SONIC_TCR_BCM;
944a65f56eeSaurel32     s->regs[SONIC_IMR] = 0;
945a65f56eeSaurel32     s->regs[SONIC_ISR] = 0;
946a65f56eeSaurel32     s->regs[SONIC_DCR2] = 0;
947a65f56eeSaurel32     s->regs[SONIC_EOBC] = 0x02F8;
948a65f56eeSaurel32     s->regs[SONIC_RSC] = 0;
949a65f56eeSaurel32     s->regs[SONIC_CE] = 0;
950a65f56eeSaurel32     s->regs[SONIC_RSC] = 0;
951a65f56eeSaurel32 
952a65f56eeSaurel32     /* Network cable is connected */
953a65f56eeSaurel32     s->regs[SONIC_RCR] |= SONIC_RCR_CRS;
954a65f56eeSaurel32 
955a65f56eeSaurel32     dp8393x_update_irq(s);
956a65f56eeSaurel32 }
957a65f56eeSaurel32 
95805f41fe3SMark McLoughlin static NetClientInfo net_dp83932_info = {
959f394b2e2SEric Blake     .type = NET_CLIENT_DRIVER_NIC,
96005f41fe3SMark McLoughlin     .size = sizeof(NICState),
9613df5de64SHervé Poussineau     .can_receive = dp8393x_can_receive,
9623df5de64SHervé Poussineau     .receive = dp8393x_receive,
96305f41fe3SMark McLoughlin };
96405f41fe3SMark McLoughlin 
965104655a5SHervé Poussineau static void dp8393x_instance_init(Object *obj)
966a65f56eeSaurel32 {
967104655a5SHervé Poussineau     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
968104655a5SHervé Poussineau     dp8393xState *s = DP8393X(obj);
969a65f56eeSaurel32 
970104655a5SHervé Poussineau     sysbus_init_mmio(sbd, &s->mmio);
97189ae0ff9SHervé Poussineau     sysbus_init_mmio(sbd, &s->prom);
972104655a5SHervé Poussineau     sysbus_init_irq(sbd, &s->irq);
973104655a5SHervé Poussineau }
974a65f56eeSaurel32 
975104655a5SHervé Poussineau static void dp8393x_realize(DeviceState *dev, Error **errp)
976104655a5SHervé Poussineau {
977104655a5SHervé Poussineau     dp8393xState *s = DP8393X(dev);
97889ae0ff9SHervé Poussineau     int i, checksum;
97989ae0ff9SHervé Poussineau     uint8_t *prom;
98052579c68SHervé Poussineau     Error *local_err = NULL;
981a65f56eeSaurel32 
982104655a5SHervé Poussineau     address_space_init(&s->as, s->dma_mr, "dp8393x");
983104655a5SHervé Poussineau     memory_region_init_io(&s->mmio, OBJECT(dev), &dp8393x_ops, s,
984104655a5SHervé Poussineau                           "dp8393x-regs", 0x40 << s->it_shift);
985104655a5SHervé Poussineau 
986104655a5SHervé Poussineau     s->nic = qemu_new_nic(&net_dp83932_info, &s->conf,
987104655a5SHervé Poussineau                           object_get_typename(OBJECT(dev)), dev->id, s);
988104655a5SHervé Poussineau     qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
989104655a5SHervé Poussineau 
990bc72ad67SAlex Bligh     s->watchdog = timer_new_ns(QEMU_CLOCK_VIRTUAL, dp8393x_watchdog, s);
99189ae0ff9SHervé Poussineau 
992fcd3b085SPhilippe Mathieu-Daudé     memory_region_init_rom(&s->prom, OBJECT(dev), "dp8393x-prom",
993fcd3b085SPhilippe Mathieu-Daudé                            SONIC_PROM_SIZE, &local_err);
99452579c68SHervé Poussineau     if (local_err) {
99552579c68SHervé Poussineau         error_propagate(errp, local_err);
99652579c68SHervé Poussineau         return;
99752579c68SHervé Poussineau     }
99889ae0ff9SHervé Poussineau     prom = memory_region_get_ram_ptr(&s->prom);
99989ae0ff9SHervé Poussineau     checksum = 0;
100089ae0ff9SHervé Poussineau     for (i = 0; i < 6; i++) {
100189ae0ff9SHervé Poussineau         prom[i] = s->conf.macaddr.a[i];
100289ae0ff9SHervé Poussineau         checksum += prom[i];
100389ae0ff9SHervé Poussineau         if (checksum > 0xff) {
100489ae0ff9SHervé Poussineau             checksum = (checksum + 1) & 0xff;
100589ae0ff9SHervé Poussineau         }
100689ae0ff9SHervé Poussineau     }
100789ae0ff9SHervé Poussineau     prom[7] = 0xff - checksum;
1008a65f56eeSaurel32 }
1009104655a5SHervé Poussineau 
10101670735dSHervé Poussineau static const VMStateDescription vmstate_dp8393x = {
10111670735dSHervé Poussineau     .name = "dp8393x",
10121670735dSHervé Poussineau     .version_id = 0,
10131670735dSHervé Poussineau     .minimum_version_id = 0,
10141670735dSHervé Poussineau     .fields = (VMStateField []) {
10151670735dSHervé Poussineau         VMSTATE_BUFFER_UNSAFE(cam, dp8393xState, 0, 16 * 6),
10161670735dSHervé Poussineau         VMSTATE_UINT16_ARRAY(regs, dp8393xState, 0x40),
10171670735dSHervé Poussineau         VMSTATE_END_OF_LIST()
10181670735dSHervé Poussineau     }
10191670735dSHervé Poussineau };
10201670735dSHervé Poussineau 
1021104655a5SHervé Poussineau static Property dp8393x_properties[] = {
1022104655a5SHervé Poussineau     DEFINE_NIC_PROPERTIES(dp8393xState, conf),
10233110ce81SMarc-André Lureau     DEFINE_PROP_LINK("dma_mr", dp8393xState, dma_mr,
10243110ce81SMarc-André Lureau                      TYPE_MEMORY_REGION, MemoryRegion *),
1025104655a5SHervé Poussineau     DEFINE_PROP_UINT8("it_shift", dp8393xState, it_shift, 0),
1026be920841SLaurent Vivier     DEFINE_PROP_BOOL("big_endian", dp8393xState, big_endian, false),
1027104655a5SHervé Poussineau     DEFINE_PROP_END_OF_LIST(),
1028104655a5SHervé Poussineau };
1029104655a5SHervé Poussineau 
1030104655a5SHervé Poussineau static void dp8393x_class_init(ObjectClass *klass, void *data)
1031104655a5SHervé Poussineau {
1032104655a5SHervé Poussineau     DeviceClass *dc = DEVICE_CLASS(klass);
1033104655a5SHervé Poussineau 
1034104655a5SHervé Poussineau     set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
1035104655a5SHervé Poussineau     dc->realize = dp8393x_realize;
1036104655a5SHervé Poussineau     dc->reset = dp8393x_reset;
10371670735dSHervé Poussineau     dc->vmsd = &vmstate_dp8393x;
10384f67d30bSMarc-André Lureau     device_class_set_props(dc, dp8393x_properties);
1039104655a5SHervé Poussineau }
1040104655a5SHervé Poussineau 
1041104655a5SHervé Poussineau static const TypeInfo dp8393x_info = {
1042104655a5SHervé Poussineau     .name          = TYPE_DP8393X,
1043104655a5SHervé Poussineau     .parent        = TYPE_SYS_BUS_DEVICE,
1044104655a5SHervé Poussineau     .instance_size = sizeof(dp8393xState),
1045104655a5SHervé Poussineau     .instance_init = dp8393x_instance_init,
1046104655a5SHervé Poussineau     .class_init    = dp8393x_class_init,
1047104655a5SHervé Poussineau };
1048104655a5SHervé Poussineau 
1049104655a5SHervé Poussineau static void dp8393x_register_types(void)
1050104655a5SHervé Poussineau {
1051104655a5SHervé Poussineau     type_register_static(&dp8393x_info);
1052104655a5SHervé Poussineau }
1053104655a5SHervé Poussineau 
1054104655a5SHervé Poussineau type_init(dp8393x_register_types)
1055