xref: /qemu/hw/net/dp8393x.c (revision c0af04a43667e2e50ed347ca9f707b597c874496)
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"
31*c0af04a4SMark Cave-Ayland #include "trace.h"
32a65f56eeSaurel32 
3389ae0ff9SHervé Poussineau #define SONIC_PROM_SIZE 0x1000
34a65f56eeSaurel32 
35a65f56eeSaurel32 static const char *reg_names[] = {
36a65f56eeSaurel32     "CR", "DCR", "RCR", "TCR", "IMR", "ISR", "UTDA", "CTDA",
37a65f56eeSaurel32     "TPS", "TFC", "TSA0", "TSA1", "TFS", "URDA", "CRDA", "CRBA0",
38a65f56eeSaurel32     "CRBA1", "RBWC0", "RBWC1", "EOBC", "URRA", "RSA", "REA", "RRP",
39a65f56eeSaurel32     "RWP", "TRBA0", "TRBA1", "0x1b", "0x1c", "0x1d", "0x1e", "LLFA",
40a65f56eeSaurel32     "TTDA", "CEP", "CAP2", "CAP1", "CAP0", "CE", "CDP", "CDC",
41a65f56eeSaurel32     "SR", "WT0", "WT1", "RSC", "CRCT", "FAET", "MPT", "MDT",
42a65f56eeSaurel32     "0x30", "0x31", "0x32", "0x33", "0x34", "0x35", "0x36", "0x37",
43a65f56eeSaurel32     "0x38", "0x39", "0x3a", "0x3b", "0x3c", "0x3d", "0x3e", "DCR2" };
44a65f56eeSaurel32 
45a65f56eeSaurel32 #define SONIC_CR     0x00
46a65f56eeSaurel32 #define SONIC_DCR    0x01
47a65f56eeSaurel32 #define SONIC_RCR    0x02
48a65f56eeSaurel32 #define SONIC_TCR    0x03
49a65f56eeSaurel32 #define SONIC_IMR    0x04
50a65f56eeSaurel32 #define SONIC_ISR    0x05
51a65f56eeSaurel32 #define SONIC_UTDA   0x06
52a65f56eeSaurel32 #define SONIC_CTDA   0x07
53a65f56eeSaurel32 #define SONIC_TPS    0x08
54a65f56eeSaurel32 #define SONIC_TFC    0x09
55a65f56eeSaurel32 #define SONIC_TSA0   0x0a
56a65f56eeSaurel32 #define SONIC_TSA1   0x0b
57a65f56eeSaurel32 #define SONIC_TFS    0x0c
58a65f56eeSaurel32 #define SONIC_URDA   0x0d
59a65f56eeSaurel32 #define SONIC_CRDA   0x0e
60a65f56eeSaurel32 #define SONIC_CRBA0  0x0f
61a65f56eeSaurel32 #define SONIC_CRBA1  0x10
62a65f56eeSaurel32 #define SONIC_RBWC0  0x11
63a65f56eeSaurel32 #define SONIC_RBWC1  0x12
64a65f56eeSaurel32 #define SONIC_EOBC   0x13
65a65f56eeSaurel32 #define SONIC_URRA   0x14
66a65f56eeSaurel32 #define SONIC_RSA    0x15
67a65f56eeSaurel32 #define SONIC_REA    0x16
68a65f56eeSaurel32 #define SONIC_RRP    0x17
69a65f56eeSaurel32 #define SONIC_RWP    0x18
70a65f56eeSaurel32 #define SONIC_TRBA0  0x19
71a65f56eeSaurel32 #define SONIC_TRBA1  0x1a
72a65f56eeSaurel32 #define SONIC_LLFA   0x1f
73a65f56eeSaurel32 #define SONIC_TTDA   0x20
74a65f56eeSaurel32 #define SONIC_CEP    0x21
75a65f56eeSaurel32 #define SONIC_CAP2   0x22
76a65f56eeSaurel32 #define SONIC_CAP1   0x23
77a65f56eeSaurel32 #define SONIC_CAP0   0x24
78a65f56eeSaurel32 #define SONIC_CE     0x25
79a65f56eeSaurel32 #define SONIC_CDP    0x26
80a65f56eeSaurel32 #define SONIC_CDC    0x27
81a65f56eeSaurel32 #define SONIC_SR     0x28
82a65f56eeSaurel32 #define SONIC_WT0    0x29
83a65f56eeSaurel32 #define SONIC_WT1    0x2a
84a65f56eeSaurel32 #define SONIC_RSC    0x2b
85a65f56eeSaurel32 #define SONIC_CRCT   0x2c
86a65f56eeSaurel32 #define SONIC_FAET   0x2d
87a65f56eeSaurel32 #define SONIC_MPT    0x2e
88a65f56eeSaurel32 #define SONIC_MDT    0x2f
89a65f56eeSaurel32 #define SONIC_DCR2   0x3f
90a65f56eeSaurel32 
91a65f56eeSaurel32 #define SONIC_CR_HTX     0x0001
92a65f56eeSaurel32 #define SONIC_CR_TXP     0x0002
93a65f56eeSaurel32 #define SONIC_CR_RXDIS   0x0004
94a65f56eeSaurel32 #define SONIC_CR_RXEN    0x0008
95a65f56eeSaurel32 #define SONIC_CR_STP     0x0010
96a65f56eeSaurel32 #define SONIC_CR_ST      0x0020
97a65f56eeSaurel32 #define SONIC_CR_RST     0x0080
98a65f56eeSaurel32 #define SONIC_CR_RRRA    0x0100
99a65f56eeSaurel32 #define SONIC_CR_LCAM    0x0200
100a65f56eeSaurel32 #define SONIC_CR_MASK    0x03bf
101a65f56eeSaurel32 
102a65f56eeSaurel32 #define SONIC_DCR_DW     0x0020
103a65f56eeSaurel32 #define SONIC_DCR_LBR    0x2000
104a65f56eeSaurel32 #define SONIC_DCR_EXBUS  0x8000
105a65f56eeSaurel32 
106a65f56eeSaurel32 #define SONIC_RCR_PRX    0x0001
107a65f56eeSaurel32 #define SONIC_RCR_LBK    0x0002
108a65f56eeSaurel32 #define SONIC_RCR_FAER   0x0004
109a65f56eeSaurel32 #define SONIC_RCR_CRCR   0x0008
110a65f56eeSaurel32 #define SONIC_RCR_CRS    0x0020
111a65f56eeSaurel32 #define SONIC_RCR_LPKT   0x0040
112a65f56eeSaurel32 #define SONIC_RCR_BC     0x0080
113a65f56eeSaurel32 #define SONIC_RCR_MC     0x0100
114a65f56eeSaurel32 #define SONIC_RCR_LB0    0x0200
115a65f56eeSaurel32 #define SONIC_RCR_LB1    0x0400
116a65f56eeSaurel32 #define SONIC_RCR_AMC    0x0800
117a65f56eeSaurel32 #define SONIC_RCR_PRO    0x1000
118a65f56eeSaurel32 #define SONIC_RCR_BRD    0x2000
119a65f56eeSaurel32 #define SONIC_RCR_RNT    0x4000
120a65f56eeSaurel32 
121a65f56eeSaurel32 #define SONIC_TCR_PTX    0x0001
122a65f56eeSaurel32 #define SONIC_TCR_BCM    0x0002
123a65f56eeSaurel32 #define SONIC_TCR_FU     0x0004
124a65f56eeSaurel32 #define SONIC_TCR_EXC    0x0040
125a65f56eeSaurel32 #define SONIC_TCR_CRSL   0x0080
126a65f56eeSaurel32 #define SONIC_TCR_NCRS   0x0100
127a65f56eeSaurel32 #define SONIC_TCR_EXD    0x0400
128a65f56eeSaurel32 #define SONIC_TCR_CRCI   0x2000
129a65f56eeSaurel32 #define SONIC_TCR_PINT   0x8000
130a65f56eeSaurel32 
131ada74315SFinn Thain #define SONIC_ISR_RBAE   0x0010
132a65f56eeSaurel32 #define SONIC_ISR_RBE    0x0020
133a65f56eeSaurel32 #define SONIC_ISR_RDE    0x0040
134a65f56eeSaurel32 #define SONIC_ISR_TC     0x0080
135a65f56eeSaurel32 #define SONIC_ISR_TXDN   0x0200
136a65f56eeSaurel32 #define SONIC_ISR_PKTRX  0x0400
137a65f56eeSaurel32 #define SONIC_ISR_PINT   0x0800
138a65f56eeSaurel32 #define SONIC_ISR_LCD    0x1000
139a65f56eeSaurel32 
14088f632fbSFinn Thain #define SONIC_DESC_EOL   0x0001
14188f632fbSFinn Thain #define SONIC_DESC_ADDR  0xFFFE
14288f632fbSFinn Thain 
143104655a5SHervé Poussineau #define TYPE_DP8393X "dp8393x"
1448063396bSEduardo Habkost OBJECT_DECLARE_SIMPLE_TYPE(dp8393xState, DP8393X)
145104655a5SHervé Poussineau 
146db1015e9SEduardo Habkost struct dp8393xState {
147104655a5SHervé Poussineau     SysBusDevice parent_obj;
148104655a5SHervé Poussineau 
149a65f56eeSaurel32     /* Hardware */
150104655a5SHervé Poussineau     uint8_t it_shift;
151be920841SLaurent Vivier     bool big_endian;
152c2279bd0SFinn Thain     bool last_rba_is_full;
153a65f56eeSaurel32     qemu_irq irq;
154a65f56eeSaurel32     int irq_level;
155a65f56eeSaurel32     QEMUTimer *watchdog;
156a65f56eeSaurel32     int64_t wt_last_update;
15705f41fe3SMark McLoughlin     NICConf conf;
15805f41fe3SMark McLoughlin     NICState *nic;
159024e5bb6SAvi Kivity     MemoryRegion mmio;
16089ae0ff9SHervé Poussineau     MemoryRegion prom;
161a65f56eeSaurel32 
162a65f56eeSaurel32     /* Registers */
163a65f56eeSaurel32     uint8_t cam[16][6];
164a65f56eeSaurel32     uint16_t regs[0x40];
165a65f56eeSaurel32 
166a65f56eeSaurel32     /* Temporaries */
167a65f56eeSaurel32     uint8_t tx_buffer[0x10000];
168af9f0be3SLaurent Vivier     uint16_t data[12];
169a65f56eeSaurel32     int loopback_packet;
170a65f56eeSaurel32 
171a65f56eeSaurel32     /* Memory access */
1723110ce81SMarc-André Lureau     MemoryRegion *dma_mr;
173dd820513SHervé Poussineau     AddressSpace as;
174db1015e9SEduardo Habkost };
175a65f56eeSaurel32 
1761ca82a8dSMark Cave-Ayland /*
1771ca82a8dSMark Cave-Ayland  * Accessor functions for values which are formed by
178581f7b12SPeter Maydell  * concatenating two 16 bit device registers. By putting these
179581f7b12SPeter Maydell  * in their own functions with a uint32_t return type we avoid the
180581f7b12SPeter Maydell  * pitfall of implicit sign extension where ((x << 16) | y) is a
181581f7b12SPeter Maydell  * signed 32 bit integer that might get sign-extended to a 64 bit integer.
182581f7b12SPeter Maydell  */
183581f7b12SPeter Maydell static uint32_t dp8393x_cdp(dp8393xState *s)
184581f7b12SPeter Maydell {
185581f7b12SPeter Maydell     return (s->regs[SONIC_URRA] << 16) | s->regs[SONIC_CDP];
186581f7b12SPeter Maydell }
187581f7b12SPeter Maydell 
188581f7b12SPeter Maydell static uint32_t dp8393x_crba(dp8393xState *s)
189581f7b12SPeter Maydell {
190581f7b12SPeter Maydell     return (s->regs[SONIC_CRBA1] << 16) | s->regs[SONIC_CRBA0];
191581f7b12SPeter Maydell }
192581f7b12SPeter Maydell 
193581f7b12SPeter Maydell static uint32_t dp8393x_crda(dp8393xState *s)
194581f7b12SPeter Maydell {
19588f632fbSFinn Thain     return (s->regs[SONIC_URDA] << 16) |
19688f632fbSFinn Thain            (s->regs[SONIC_CRDA] & SONIC_DESC_ADDR);
197581f7b12SPeter Maydell }
198581f7b12SPeter Maydell 
199581f7b12SPeter Maydell static uint32_t dp8393x_rbwc(dp8393xState *s)
200581f7b12SPeter Maydell {
201581f7b12SPeter Maydell     return (s->regs[SONIC_RBWC1] << 16) | s->regs[SONIC_RBWC0];
202581f7b12SPeter Maydell }
203581f7b12SPeter Maydell 
204581f7b12SPeter Maydell static uint32_t dp8393x_rrp(dp8393xState *s)
205581f7b12SPeter Maydell {
206581f7b12SPeter Maydell     return (s->regs[SONIC_URRA] << 16) | s->regs[SONIC_RRP];
207581f7b12SPeter Maydell }
208581f7b12SPeter Maydell 
209581f7b12SPeter Maydell static uint32_t dp8393x_tsa(dp8393xState *s)
210581f7b12SPeter Maydell {
211581f7b12SPeter Maydell     return (s->regs[SONIC_TSA1] << 16) | s->regs[SONIC_TSA0];
212581f7b12SPeter Maydell }
213581f7b12SPeter Maydell 
214581f7b12SPeter Maydell static uint32_t dp8393x_ttda(dp8393xState *s)
215581f7b12SPeter Maydell {
21688f632fbSFinn Thain     return (s->regs[SONIC_UTDA] << 16) |
21788f632fbSFinn Thain            (s->regs[SONIC_TTDA] & SONIC_DESC_ADDR);
218581f7b12SPeter Maydell }
219581f7b12SPeter Maydell 
220581f7b12SPeter Maydell static uint32_t dp8393x_wt(dp8393xState *s)
221581f7b12SPeter Maydell {
222581f7b12SPeter Maydell     return s->regs[SONIC_WT1] << 16 | s->regs[SONIC_WT0];
223581f7b12SPeter Maydell }
224581f7b12SPeter Maydell 
225af9f0be3SLaurent Vivier static uint16_t dp8393x_get(dp8393xState *s, int width, int offset)
226be920841SLaurent Vivier {
227be920841SLaurent Vivier     uint16_t val;
228be920841SLaurent Vivier 
229be920841SLaurent Vivier     if (s->big_endian) {
230af9f0be3SLaurent Vivier         val = be16_to_cpu(s->data[offset * width + width - 1]);
231be920841SLaurent Vivier     } else {
232af9f0be3SLaurent Vivier         val = le16_to_cpu(s->data[offset * width]);
233be920841SLaurent Vivier     }
234be920841SLaurent Vivier     return val;
235be920841SLaurent Vivier }
236be920841SLaurent Vivier 
237af9f0be3SLaurent Vivier static void dp8393x_put(dp8393xState *s, int width, int offset,
238be920841SLaurent Vivier                         uint16_t val)
239be920841SLaurent Vivier {
240be920841SLaurent Vivier     if (s->big_endian) {
2413fe9a838SFinn Thain         if (width == 2) {
2423fe9a838SFinn Thain             s->data[offset * 2] = 0;
2433fe9a838SFinn Thain             s->data[offset * 2 + 1] = cpu_to_be16(val);
244be920841SLaurent Vivier         } else {
2453fe9a838SFinn Thain             s->data[offset] = cpu_to_be16(val);
2463fe9a838SFinn Thain         }
2473fe9a838SFinn Thain     } else {
2483fe9a838SFinn Thain         if (width == 2) {
2493fe9a838SFinn Thain             s->data[offset * 2] = cpu_to_le16(val);
2503fe9a838SFinn Thain             s->data[offset * 2 + 1] = 0;
2513fe9a838SFinn Thain         } else {
2523fe9a838SFinn Thain             s->data[offset] = cpu_to_le16(val);
2533fe9a838SFinn Thain         }
254be920841SLaurent Vivier     }
255be920841SLaurent Vivier }
256be920841SLaurent Vivier 
257a65f56eeSaurel32 static void dp8393x_update_irq(dp8393xState *s)
258a65f56eeSaurel32 {
259a65f56eeSaurel32     int level = (s->regs[SONIC_IMR] & s->regs[SONIC_ISR]) ? 1 : 0;
260a65f56eeSaurel32 
261a65f56eeSaurel32     if (level != s->irq_level) {
262a65f56eeSaurel32         s->irq_level = level;
263a65f56eeSaurel32         if (level) {
264*c0af04a4SMark Cave-Ayland             trace_dp8393x_raise_irq(s->regs[SONIC_ISR]);
265a65f56eeSaurel32         } else {
266*c0af04a4SMark Cave-Ayland             trace_dp8393x_lower_irq();
267a65f56eeSaurel32         }
268a65f56eeSaurel32     }
269a65f56eeSaurel32 
270a65f56eeSaurel32     qemu_set_irq(s->irq, level);
271a65f56eeSaurel32 }
272a65f56eeSaurel32 
2733df5de64SHervé Poussineau static void dp8393x_do_load_cam(dp8393xState *s)
274a65f56eeSaurel32 {
275a65f56eeSaurel32     int width, size;
276a65f56eeSaurel32     uint16_t index = 0;
277a65f56eeSaurel32 
278a65f56eeSaurel32     width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
279a65f56eeSaurel32     size = sizeof(uint16_t) * 4 * width;
280a65f56eeSaurel32 
281a65f56eeSaurel32     while (s->regs[SONIC_CDC] & 0x1f) {
282a65f56eeSaurel32         /* Fill current entry */
28319f70347SPeter Maydell         address_space_read(&s->as, dp8393x_cdp(s),
28419f70347SPeter Maydell                            MEMTXATTRS_UNSPECIFIED, s->data, size);
285af9f0be3SLaurent Vivier         s->cam[index][0] = dp8393x_get(s, width, 1) & 0xff;
286af9f0be3SLaurent Vivier         s->cam[index][1] = dp8393x_get(s, width, 1) >> 8;
287af9f0be3SLaurent Vivier         s->cam[index][2] = dp8393x_get(s, width, 2) & 0xff;
288af9f0be3SLaurent Vivier         s->cam[index][3] = dp8393x_get(s, width, 2) >> 8;
289af9f0be3SLaurent Vivier         s->cam[index][4] = dp8393x_get(s, width, 3) & 0xff;
290af9f0be3SLaurent Vivier         s->cam[index][5] = dp8393x_get(s, width, 3) >> 8;
291*c0af04a4SMark Cave-Ayland         trace_dp8393x_load_cam(index, s->cam[index][0], s->cam[index][1],
292*c0af04a4SMark Cave-Ayland                                s->cam[index][2], s->cam[index][3],
293*c0af04a4SMark Cave-Ayland                                s->cam[index][4], s->cam[index][5]);
294a65f56eeSaurel32         /* Move to next entry */
295a65f56eeSaurel32         s->regs[SONIC_CDC]--;
296a65f56eeSaurel32         s->regs[SONIC_CDP] += size;
297a65f56eeSaurel32         index++;
298a65f56eeSaurel32     }
299a65f56eeSaurel32 
300a65f56eeSaurel32     /* Read CAM enable */
30119f70347SPeter Maydell     address_space_read(&s->as, dp8393x_cdp(s),
30219f70347SPeter Maydell                        MEMTXATTRS_UNSPECIFIED, s->data, size);
303af9f0be3SLaurent Vivier     s->regs[SONIC_CE] = dp8393x_get(s, width, 0);
304*c0af04a4SMark Cave-Ayland     trace_dp8393x_load_cam_done(s->regs[SONIC_CE]);
305a65f56eeSaurel32 
306a65f56eeSaurel32     /* Done */
307a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_LCAM;
308a65f56eeSaurel32     s->regs[SONIC_ISR] |= SONIC_ISR_LCD;
309a65f56eeSaurel32     dp8393x_update_irq(s);
310a65f56eeSaurel32 }
311a65f56eeSaurel32 
3123df5de64SHervé Poussineau static void dp8393x_do_read_rra(dp8393xState *s)
313a65f56eeSaurel32 {
314a65f56eeSaurel32     int width, size;
315a65f56eeSaurel32 
316a65f56eeSaurel32     /* Read memory */
317a65f56eeSaurel32     width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
318a65f56eeSaurel32     size = sizeof(uint16_t) * 4 * width;
31919f70347SPeter Maydell     address_space_read(&s->as, dp8393x_rrp(s),
32019f70347SPeter Maydell                        MEMTXATTRS_UNSPECIFIED, s->data, size);
321a65f56eeSaurel32 
322a65f56eeSaurel32     /* Update SONIC registers */
323af9f0be3SLaurent Vivier     s->regs[SONIC_CRBA0] = dp8393x_get(s, width, 0);
324af9f0be3SLaurent Vivier     s->regs[SONIC_CRBA1] = dp8393x_get(s, width, 1);
325af9f0be3SLaurent Vivier     s->regs[SONIC_RBWC0] = dp8393x_get(s, width, 2);
326af9f0be3SLaurent Vivier     s->regs[SONIC_RBWC1] = dp8393x_get(s, width, 3);
327*c0af04a4SMark Cave-Ayland     trace_dp8393x_read_rra_regs(s->regs[SONIC_CRBA0], s->regs[SONIC_CRBA1],
328a65f56eeSaurel32                                 s->regs[SONIC_RBWC0], s->regs[SONIC_RBWC1]);
329a65f56eeSaurel32 
330a65f56eeSaurel32     /* Go to next entry */
331a65f56eeSaurel32     s->regs[SONIC_RRP] += size;
332a65f56eeSaurel32 
333a65f56eeSaurel32     /* Handle wrap */
334a65f56eeSaurel32     if (s->regs[SONIC_RRP] == s->regs[SONIC_REA]) {
335a65f56eeSaurel32         s->regs[SONIC_RRP] = s->regs[SONIC_RSA];
336a65f56eeSaurel32     }
337a65f56eeSaurel32 
338c2279bd0SFinn Thain     /* Warn the host if CRBA now has the last available resource */
3391ca82a8dSMark Cave-Ayland     if (s->regs[SONIC_RRP] == s->regs[SONIC_RWP]) {
340a65f56eeSaurel32         s->regs[SONIC_ISR] |= SONIC_ISR_RBE;
341a65f56eeSaurel32         dp8393x_update_irq(s);
342a65f56eeSaurel32     }
343c2279bd0SFinn Thain 
344c2279bd0SFinn Thain     /* Allow packet reception */
345c2279bd0SFinn Thain     s->last_rba_is_full = false;
346a65f56eeSaurel32 }
347a65f56eeSaurel32 
3483df5de64SHervé Poussineau static void dp8393x_do_software_reset(dp8393xState *s)
349a65f56eeSaurel32 {
350bc72ad67SAlex Bligh     timer_del(s->watchdog);
351a65f56eeSaurel32 
3521ca82a8dSMark Cave-Ayland     s->regs[SONIC_CR] &= ~(SONIC_CR_LCAM | SONIC_CR_RRRA | SONIC_CR_TXP |
3531ca82a8dSMark Cave-Ayland                            SONIC_CR_HTX);
354a65f56eeSaurel32     s->regs[SONIC_CR] |= SONIC_CR_RST | SONIC_CR_RXDIS;
355a65f56eeSaurel32 }
356a65f56eeSaurel32 
3573df5de64SHervé Poussineau static void dp8393x_set_next_tick(dp8393xState *s)
358a65f56eeSaurel32 {
359a65f56eeSaurel32     uint32_t ticks;
360a65f56eeSaurel32     int64_t delay;
361a65f56eeSaurel32 
362a65f56eeSaurel32     if (s->regs[SONIC_CR] & SONIC_CR_STP) {
363bc72ad67SAlex Bligh         timer_del(s->watchdog);
364a65f56eeSaurel32         return;
365a65f56eeSaurel32     }
366a65f56eeSaurel32 
367581f7b12SPeter Maydell     ticks = dp8393x_wt(s);
368bc72ad67SAlex Bligh     s->wt_last_update = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
36973bcb24dSRutuja Shah     delay = NANOSECONDS_PER_SECOND * ticks / 5000000;
370bc72ad67SAlex Bligh     timer_mod(s->watchdog, s->wt_last_update + delay);
371a65f56eeSaurel32 }
372a65f56eeSaurel32 
3733df5de64SHervé Poussineau static void dp8393x_update_wt_regs(dp8393xState *s)
374a65f56eeSaurel32 {
375a65f56eeSaurel32     int64_t elapsed;
376a65f56eeSaurel32     uint32_t val;
377a65f56eeSaurel32 
378a65f56eeSaurel32     if (s->regs[SONIC_CR] & SONIC_CR_STP) {
379bc72ad67SAlex Bligh         timer_del(s->watchdog);
380a65f56eeSaurel32         return;
381a65f56eeSaurel32     }
382a65f56eeSaurel32 
383bc72ad67SAlex Bligh     elapsed = s->wt_last_update - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
384581f7b12SPeter Maydell     val = dp8393x_wt(s);
385a65f56eeSaurel32     val -= elapsed / 5000000;
386a65f56eeSaurel32     s->regs[SONIC_WT1] = (val >> 16) & 0xffff;
387a65f56eeSaurel32     s->regs[SONIC_WT0] = (val >> 0)  & 0xffff;
3883df5de64SHervé Poussineau     dp8393x_set_next_tick(s);
389a65f56eeSaurel32 
390a65f56eeSaurel32 }
391a65f56eeSaurel32 
3923df5de64SHervé Poussineau static void dp8393x_do_start_timer(dp8393xState *s)
393a65f56eeSaurel32 {
394a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_STP;
3953df5de64SHervé Poussineau     dp8393x_set_next_tick(s);
396a65f56eeSaurel32 }
397a65f56eeSaurel32 
3983df5de64SHervé Poussineau static void dp8393x_do_stop_timer(dp8393xState *s)
399a65f56eeSaurel32 {
400a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_ST;
4013df5de64SHervé Poussineau     dp8393x_update_wt_regs(s);
402a65f56eeSaurel32 }
403a65f56eeSaurel32 
404b8c4b67eSPhilippe Mathieu-Daudé static bool dp8393x_can_receive(NetClientState *nc);
4054594f93aSFam Zheng 
4063df5de64SHervé Poussineau static void dp8393x_do_receiver_enable(dp8393xState *s)
407a65f56eeSaurel32 {
408a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_RXDIS;
4094594f93aSFam Zheng     if (dp8393x_can_receive(s->nic->ncs)) {
4104594f93aSFam Zheng         qemu_flush_queued_packets(qemu_get_queue(s->nic));
4114594f93aSFam Zheng     }
412a65f56eeSaurel32 }
413a65f56eeSaurel32 
4143df5de64SHervé Poussineau static void dp8393x_do_receiver_disable(dp8393xState *s)
415a65f56eeSaurel32 {
416a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_RXEN;
417a65f56eeSaurel32 }
418a65f56eeSaurel32 
4193df5de64SHervé Poussineau static void dp8393x_do_transmit_packets(dp8393xState *s)
420a65f56eeSaurel32 {
421b356f76dSJason Wang     NetClientState *nc = qemu_get_queue(s->nic);
422a65f56eeSaurel32     int width, size;
423a65f56eeSaurel32     int tx_len, len;
424a65f56eeSaurel32     uint16_t i;
425a65f56eeSaurel32 
426a65f56eeSaurel32     width = (s->regs[SONIC_DCR] & SONIC_DCR_DW) ? 2 : 1;
427a65f56eeSaurel32 
428a65f56eeSaurel32     while (1) {
429a65f56eeSaurel32         /* Read memory */
430a65f56eeSaurel32         size = sizeof(uint16_t) * 6 * width;
431a65f56eeSaurel32         s->regs[SONIC_TTDA] = s->regs[SONIC_CTDA];
432*c0af04a4SMark Cave-Ayland         trace_dp8393x_transmit_packet(dp8393x_ttda(s));
43319f70347SPeter Maydell         address_space_read(&s->as, dp8393x_ttda(s) + sizeof(uint16_t) * width,
43419f70347SPeter Maydell                            MEMTXATTRS_UNSPECIFIED, s->data, size);
435a65f56eeSaurel32         tx_len = 0;
436a65f56eeSaurel32 
437a65f56eeSaurel32         /* Update registers */
438af9f0be3SLaurent Vivier         s->regs[SONIC_TCR] = dp8393x_get(s, width, 0) & 0xf000;
439af9f0be3SLaurent Vivier         s->regs[SONIC_TPS] = dp8393x_get(s, width, 1);
440af9f0be3SLaurent Vivier         s->regs[SONIC_TFC] = dp8393x_get(s, width, 2);
441af9f0be3SLaurent Vivier         s->regs[SONIC_TSA0] = dp8393x_get(s, width, 3);
442af9f0be3SLaurent Vivier         s->regs[SONIC_TSA1] = dp8393x_get(s, width, 4);
443af9f0be3SLaurent Vivier         s->regs[SONIC_TFS] = dp8393x_get(s, width, 5);
444a65f56eeSaurel32 
445a65f56eeSaurel32         /* Handle programmable interrupt */
446a65f56eeSaurel32         if (s->regs[SONIC_TCR] & SONIC_TCR_PINT) {
447a65f56eeSaurel32             s->regs[SONIC_ISR] |= SONIC_ISR_PINT;
448a65f56eeSaurel32         } else {
449a65f56eeSaurel32             s->regs[SONIC_ISR] &= ~SONIC_ISR_PINT;
450a65f56eeSaurel32         }
451a65f56eeSaurel32 
452a65f56eeSaurel32         for (i = 0; i < s->regs[SONIC_TFC]; ) {
453a65f56eeSaurel32             /* Append fragment */
454a65f56eeSaurel32             len = s->regs[SONIC_TFS];
455a65f56eeSaurel32             if (tx_len + len > sizeof(s->tx_buffer)) {
456a65f56eeSaurel32                 len = sizeof(s->tx_buffer) - tx_len;
457a65f56eeSaurel32             }
45819f70347SPeter Maydell             address_space_read(&s->as, dp8393x_tsa(s), MEMTXATTRS_UNSPECIFIED,
45919f70347SPeter Maydell                                &s->tx_buffer[tx_len], len);
460a65f56eeSaurel32             tx_len += len;
461a65f56eeSaurel32 
462a65f56eeSaurel32             i++;
463a65f56eeSaurel32             if (i != s->regs[SONIC_TFC]) {
464a65f56eeSaurel32                 /* Read next fragment details */
465a65f56eeSaurel32                 size = sizeof(uint16_t) * 3 * width;
46619f70347SPeter Maydell                 address_space_read(&s->as,
46719f70347SPeter Maydell                                    dp8393x_ttda(s)
46819f70347SPeter Maydell                                    + sizeof(uint16_t) * width * (4 + 3 * i),
46919f70347SPeter Maydell                                    MEMTXATTRS_UNSPECIFIED, s->data,
47019f70347SPeter Maydell                                    size);
471af9f0be3SLaurent Vivier                 s->regs[SONIC_TSA0] = dp8393x_get(s, width, 0);
472af9f0be3SLaurent Vivier                 s->regs[SONIC_TSA1] = dp8393x_get(s, width, 1);
473af9f0be3SLaurent Vivier                 s->regs[SONIC_TFS] = dp8393x_get(s, width, 2);
474a65f56eeSaurel32             }
475a65f56eeSaurel32         }
476a65f56eeSaurel32 
477a65f56eeSaurel32         /* Handle Ethernet checksum */
478a65f56eeSaurel32         if (!(s->regs[SONIC_TCR] & SONIC_TCR_CRCI)) {
4791ca82a8dSMark Cave-Ayland             /*
4801ca82a8dSMark Cave-Ayland              * Don't append FCS there, to look like slirp packets
4811ca82a8dSMark Cave-Ayland              * which don't have one
4821ca82a8dSMark Cave-Ayland              */
483a65f56eeSaurel32         } else {
484a65f56eeSaurel32             /* Remove existing FCS */
485a65f56eeSaurel32             tx_len -= 4;
486915976bdSMauro Matteo Cascella             if (tx_len < 0) {
487*c0af04a4SMark Cave-Ayland                 trace_dp8393x_transmit_txlen_error(tx_len);
488915976bdSMauro Matteo Cascella                 break;
489915976bdSMauro Matteo Cascella             }
490a65f56eeSaurel32         }
491a65f56eeSaurel32 
492a65f56eeSaurel32         if (s->regs[SONIC_RCR] & (SONIC_RCR_LB1 | SONIC_RCR_LB0)) {
493a65f56eeSaurel32             /* Loopback */
494a65f56eeSaurel32             s->regs[SONIC_TCR] |= SONIC_TCR_CRSL;
495b356f76dSJason Wang             if (nc->info->can_receive(nc)) {
496a65f56eeSaurel32                 s->loopback_packet = 1;
497331d2ac9SJason Wang                 qemu_receive_packet(nc, s->tx_buffer, tx_len);
498a65f56eeSaurel32             }
499a65f56eeSaurel32         } else {
500a65f56eeSaurel32             /* Transmit packet */
501b356f76dSJason Wang             qemu_send_packet(nc, s->tx_buffer, tx_len);
502a65f56eeSaurel32         }
503a65f56eeSaurel32         s->regs[SONIC_TCR] |= SONIC_TCR_PTX;
504a65f56eeSaurel32 
505a65f56eeSaurel32         /* Write status */
506af9f0be3SLaurent Vivier         dp8393x_put(s, width, 0,
507be920841SLaurent Vivier                     s->regs[SONIC_TCR] & 0x0fff); /* status */
508a65f56eeSaurel32         size = sizeof(uint16_t) * width;
50919f70347SPeter Maydell         address_space_write(&s->as, dp8393x_ttda(s),
51019f70347SPeter Maydell                             MEMTXATTRS_UNSPECIFIED, s->data, size);
511a65f56eeSaurel32 
512a65f56eeSaurel32         if (!(s->regs[SONIC_CR] & SONIC_CR_HTX)) {
513a65f56eeSaurel32             /* Read footer of packet */
514a65f56eeSaurel32             size = sizeof(uint16_t) * width;
51519f70347SPeter Maydell             address_space_read(&s->as,
51619f70347SPeter Maydell                                dp8393x_ttda(s)
51719f70347SPeter Maydell                                + sizeof(uint16_t) * width
51819f70347SPeter Maydell                                  * (4 + 3 * s->regs[SONIC_TFC]),
51919f70347SPeter Maydell                                MEMTXATTRS_UNSPECIFIED, s->data,
52019f70347SPeter Maydell                                size);
521a0cf4297SFinn Thain             s->regs[SONIC_CTDA] = dp8393x_get(s, width, 0);
522a0cf4297SFinn Thain             if (s->regs[SONIC_CTDA] & SONIC_DESC_EOL) {
523a65f56eeSaurel32                 /* EOL detected */
524a65f56eeSaurel32                 break;
525a65f56eeSaurel32             }
526a65f56eeSaurel32         }
527a65f56eeSaurel32     }
528a65f56eeSaurel32 
529a65f56eeSaurel32     /* Done */
530a65f56eeSaurel32     s->regs[SONIC_CR] &= ~SONIC_CR_TXP;
531a65f56eeSaurel32     s->regs[SONIC_ISR] |= SONIC_ISR_TXDN;
532a65f56eeSaurel32     dp8393x_update_irq(s);
533a65f56eeSaurel32 }
534a65f56eeSaurel32 
5353df5de64SHervé Poussineau static void dp8393x_do_halt_transmission(dp8393xState *s)
536a65f56eeSaurel32 {
537a65f56eeSaurel32     /* Nothing to do */
538a65f56eeSaurel32 }
539a65f56eeSaurel32 
5403df5de64SHervé Poussineau static void dp8393x_do_command(dp8393xState *s, uint16_t command)
541a65f56eeSaurel32 {
542a65f56eeSaurel32     if ((s->regs[SONIC_CR] & SONIC_CR_RST) && !(command & SONIC_CR_RST)) {
543a65f56eeSaurel32         s->regs[SONIC_CR] &= ~SONIC_CR_RST;
544a65f56eeSaurel32         return;
545a65f56eeSaurel32     }
546a65f56eeSaurel32 
547a65f56eeSaurel32     s->regs[SONIC_CR] |= (command & SONIC_CR_MASK);
548a65f56eeSaurel32 
5491ca82a8dSMark Cave-Ayland     if (command & SONIC_CR_HTX) {
5503df5de64SHervé Poussineau         dp8393x_do_halt_transmission(s);
5511ca82a8dSMark Cave-Ayland     }
5521ca82a8dSMark Cave-Ayland     if (command & SONIC_CR_TXP) {
5533df5de64SHervé Poussineau         dp8393x_do_transmit_packets(s);
5541ca82a8dSMark Cave-Ayland     }
5551ca82a8dSMark Cave-Ayland     if (command & SONIC_CR_RXDIS) {
5563df5de64SHervé Poussineau         dp8393x_do_receiver_disable(s);
5571ca82a8dSMark Cave-Ayland     }
5581ca82a8dSMark Cave-Ayland     if (command & SONIC_CR_RXEN) {
5593df5de64SHervé Poussineau         dp8393x_do_receiver_enable(s);
5601ca82a8dSMark Cave-Ayland     }
5611ca82a8dSMark Cave-Ayland     if (command & SONIC_CR_STP) {
5623df5de64SHervé Poussineau         dp8393x_do_stop_timer(s);
5631ca82a8dSMark Cave-Ayland     }
5641ca82a8dSMark Cave-Ayland     if (command & SONIC_CR_ST) {
5653df5de64SHervé Poussineau         dp8393x_do_start_timer(s);
5661ca82a8dSMark Cave-Ayland     }
5671ca82a8dSMark Cave-Ayland     if (command & SONIC_CR_RST) {
5683df5de64SHervé Poussineau         dp8393x_do_software_reset(s);
5691ca82a8dSMark Cave-Ayland     }
570a3cce282SFinn Thain     if (command & SONIC_CR_RRRA) {
5713df5de64SHervé Poussineau         dp8393x_do_read_rra(s);
572a3cce282SFinn Thain         s->regs[SONIC_CR] &= ~SONIC_CR_RRRA;
573a3cce282SFinn Thain     }
5741ca82a8dSMark Cave-Ayland     if (command & SONIC_CR_LCAM) {
5753df5de64SHervé Poussineau         dp8393x_do_load_cam(s);
576a65f56eeSaurel32     }
5771ca82a8dSMark Cave-Ayland }
578a65f56eeSaurel32 
57984689cbbSHervé Poussineau static uint64_t dp8393x_read(void *opaque, hwaddr addr, unsigned int size)
580a65f56eeSaurel32 {
58184689cbbSHervé Poussineau     dp8393xState *s = opaque;
58284689cbbSHervé Poussineau     int reg = addr >> s->it_shift;
583a65f56eeSaurel32     uint16_t val = 0;
584a65f56eeSaurel32 
585a65f56eeSaurel32     switch (reg) {
586a65f56eeSaurel32     /* Update data before reading it */
587a65f56eeSaurel32     case SONIC_WT0:
588a65f56eeSaurel32     case SONIC_WT1:
5893df5de64SHervé Poussineau         dp8393x_update_wt_regs(s);
590a65f56eeSaurel32         val = s->regs[reg];
591a65f56eeSaurel32         break;
592a65f56eeSaurel32     /* Accept read to some registers only when in reset mode */
593a65f56eeSaurel32     case SONIC_CAP2:
594a65f56eeSaurel32     case SONIC_CAP1:
595a65f56eeSaurel32     case SONIC_CAP0:
596a65f56eeSaurel32         if (s->regs[SONIC_CR] & SONIC_CR_RST) {
597a65f56eeSaurel32             val = s->cam[s->regs[SONIC_CEP] & 0xf][2 * (SONIC_CAP0 - reg) + 1] << 8;
598a65f56eeSaurel32             val |= s->cam[s->regs[SONIC_CEP] & 0xf][2 * (SONIC_CAP0 - reg)];
599a65f56eeSaurel32         }
600a65f56eeSaurel32         break;
6011ca82a8dSMark Cave-Ayland     /* All other registers have no special contraints */
602a65f56eeSaurel32     default:
603a65f56eeSaurel32         val = s->regs[reg];
604a65f56eeSaurel32     }
605a65f56eeSaurel32 
606*c0af04a4SMark Cave-Ayland     trace_dp8393x_read(reg, reg_names[reg], val, size);
607a65f56eeSaurel32 
6083fe9a838SFinn Thain     return s->big_endian ? val << 16 : val;
609a65f56eeSaurel32 }
610a65f56eeSaurel32 
61184689cbbSHervé Poussineau static void dp8393x_write(void *opaque, hwaddr addr, uint64_t data,
61284689cbbSHervé Poussineau                           unsigned int size)
613a65f56eeSaurel32 {
61484689cbbSHervé Poussineau     dp8393xState *s = opaque;
61584689cbbSHervé Poussineau     int reg = addr >> s->it_shift;
6163fe9a838SFinn Thain     uint32_t val = s->big_endian ? data >> 16 : data;
61784689cbbSHervé Poussineau 
618*c0af04a4SMark Cave-Ayland     trace_dp8393x_write(reg, reg_names[reg], val, size);
619a65f56eeSaurel32 
620a65f56eeSaurel32     switch (reg) {
621a65f56eeSaurel32     /* Command register */
622a65f56eeSaurel32     case SONIC_CR:
6233fe9a838SFinn Thain         dp8393x_do_command(s, val);
624a65f56eeSaurel32         break;
625a65f56eeSaurel32     /* Prevent write to read-only registers */
626a65f56eeSaurel32     case SONIC_CAP2:
627a65f56eeSaurel32     case SONIC_CAP1:
628a65f56eeSaurel32     case SONIC_CAP0:
629a65f56eeSaurel32     case SONIC_SR:
630a65f56eeSaurel32     case SONIC_MDT:
631*c0af04a4SMark Cave-Ayland         trace_dp8393x_write_invalid(reg);
632a65f56eeSaurel32         break;
633a65f56eeSaurel32     /* Accept write to some registers only when in reset mode */
634a65f56eeSaurel32     case SONIC_DCR:
635a65f56eeSaurel32         if (s->regs[SONIC_CR] & SONIC_CR_RST) {
6363fe9a838SFinn Thain             s->regs[reg] = val & 0xbfff;
637a65f56eeSaurel32         } else {
638*c0af04a4SMark Cave-Ayland             trace_dp8393x_write_invalid_dcr("DCR");
639a65f56eeSaurel32         }
640a65f56eeSaurel32         break;
641a65f56eeSaurel32     case SONIC_DCR2:
642a65f56eeSaurel32         if (s->regs[SONIC_CR] & SONIC_CR_RST) {
6433fe9a838SFinn Thain             s->regs[reg] = val & 0xf017;
644a65f56eeSaurel32         } else {
645*c0af04a4SMark Cave-Ayland             trace_dp8393x_write_invalid_dcr("DCR2");
646a65f56eeSaurel32         }
647a65f56eeSaurel32         break;
648a65f56eeSaurel32     /* 12 lower bytes are Read Only */
649a65f56eeSaurel32     case SONIC_TCR:
6503fe9a838SFinn Thain         s->regs[reg] = val & 0xf000;
651a65f56eeSaurel32         break;
652a65f56eeSaurel32     /* 9 lower bytes are Read Only */
653a65f56eeSaurel32     case SONIC_RCR:
6543fe9a838SFinn Thain         s->regs[reg] = val & 0xffe0;
655a65f56eeSaurel32         break;
656a65f56eeSaurel32     /* Ignore most significant bit */
657a65f56eeSaurel32     case SONIC_IMR:
6583fe9a838SFinn Thain         s->regs[reg] = val & 0x7fff;
659a65f56eeSaurel32         dp8393x_update_irq(s);
660a65f56eeSaurel32         break;
661a65f56eeSaurel32     /* Clear bits by writing 1 to them */
662a65f56eeSaurel32     case SONIC_ISR:
6633fe9a838SFinn Thain         val &= s->regs[reg];
6643fe9a838SFinn Thain         s->regs[reg] &= ~val;
6653fe9a838SFinn Thain         if (val & SONIC_ISR_RBE) {
6663df5de64SHervé Poussineau             dp8393x_do_read_rra(s);
667a65f56eeSaurel32         }
668a65f56eeSaurel32         dp8393x_update_irq(s);
669a65f56eeSaurel32         break;
670ea227027SFinn Thain     /* The guest is required to store aligned pointers here */
671a65f56eeSaurel32     case SONIC_RSA:
672a65f56eeSaurel32     case SONIC_REA:
673a65f56eeSaurel32     case SONIC_RRP:
674a65f56eeSaurel32     case SONIC_RWP:
675ea227027SFinn Thain         if (s->regs[SONIC_DCR] & SONIC_DCR_DW) {
676ea227027SFinn Thain             s->regs[reg] = val & 0xfffc;
677ea227027SFinn Thain         } else {
6783fe9a838SFinn Thain             s->regs[reg] = val & 0xfffe;
679ea227027SFinn Thain         }
680a65f56eeSaurel32         break;
681a65f56eeSaurel32     /* Invert written value for some registers */
682a65f56eeSaurel32     case SONIC_CRCT:
683a65f56eeSaurel32     case SONIC_FAET:
684a65f56eeSaurel32     case SONIC_MPT:
6853fe9a838SFinn Thain         s->regs[reg] = val ^ 0xffff;
686a65f56eeSaurel32         break;
687a65f56eeSaurel32     /* All other registers have no special contrainst */
688a65f56eeSaurel32     default:
6893fe9a838SFinn Thain         s->regs[reg] = val;
690a65f56eeSaurel32     }
691a65f56eeSaurel32 
692a65f56eeSaurel32     if (reg == SONIC_WT0 || reg == SONIC_WT1) {
6933df5de64SHervé Poussineau         dp8393x_set_next_tick(s);
694a65f56eeSaurel32     }
695a65f56eeSaurel32 }
696a65f56eeSaurel32 
69784689cbbSHervé Poussineau static const MemoryRegionOps dp8393x_ops = {
69884689cbbSHervé Poussineau     .read = dp8393x_read,
69984689cbbSHervé Poussineau     .write = dp8393x_write,
7003fe9a838SFinn Thain     .impl.min_access_size = 4,
7013fe9a838SFinn Thain     .impl.max_access_size = 4,
70284689cbbSHervé Poussineau     .endianness = DEVICE_NATIVE_ENDIAN,
70384689cbbSHervé Poussineau };
70484689cbbSHervé Poussineau 
705a65f56eeSaurel32 static void dp8393x_watchdog(void *opaque)
706a65f56eeSaurel32 {
707a65f56eeSaurel32     dp8393xState *s = opaque;
708a65f56eeSaurel32 
709a65f56eeSaurel32     if (s->regs[SONIC_CR] & SONIC_CR_STP) {
710a65f56eeSaurel32         return;
711a65f56eeSaurel32     }
712a65f56eeSaurel32 
713a65f56eeSaurel32     s->regs[SONIC_WT1] = 0xffff;
714a65f56eeSaurel32     s->regs[SONIC_WT0] = 0xffff;
7153df5de64SHervé Poussineau     dp8393x_set_next_tick(s);
716a65f56eeSaurel32 
717a65f56eeSaurel32     /* Signal underflow */
718a65f56eeSaurel32     s->regs[SONIC_ISR] |= SONIC_ISR_TC;
719a65f56eeSaurel32     dp8393x_update_irq(s);
720a65f56eeSaurel32 }
721a65f56eeSaurel32 
722b8c4b67eSPhilippe Mathieu-Daudé static bool dp8393x_can_receive(NetClientState *nc)
723a65f56eeSaurel32 {
724cc1f0f45SJason Wang     dp8393xState *s = qemu_get_nic_opaque(nc);
725a65f56eeSaurel32 
726b8c4b67eSPhilippe Mathieu-Daudé     return !!(s->regs[SONIC_CR] & SONIC_CR_RXEN);
727a65f56eeSaurel32 }
728a65f56eeSaurel32 
7293df5de64SHervé Poussineau static int dp8393x_receive_filter(dp8393xState *s, const uint8_t * buf,
7303df5de64SHervé Poussineau                                   int size)
731a65f56eeSaurel32 {
732a65f56eeSaurel32     static const uint8_t bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
733a65f56eeSaurel32     int i;
734a65f56eeSaurel32 
735a65f56eeSaurel32     /* Check promiscuous mode */
736a65f56eeSaurel32     if ((s->regs[SONIC_RCR] & SONIC_RCR_PRO) && (buf[0] & 1) == 0) {
737a65f56eeSaurel32         return 0;
738a65f56eeSaurel32     }
739a65f56eeSaurel32 
740a65f56eeSaurel32     /* Check multicast packets */
741a65f56eeSaurel32     if ((s->regs[SONIC_RCR] & SONIC_RCR_AMC) && (buf[0] & 1) == 1) {
742a65f56eeSaurel32         return SONIC_RCR_MC;
743a65f56eeSaurel32     }
744a65f56eeSaurel32 
745a65f56eeSaurel32     /* Check broadcast */
7461ca82a8dSMark Cave-Ayland     if ((s->regs[SONIC_RCR] & SONIC_RCR_BRD) &&
7471ca82a8dSMark Cave-Ayland          !memcmp(buf, bcast, sizeof(bcast))) {
748a65f56eeSaurel32         return SONIC_RCR_BC;
749a65f56eeSaurel32     }
750a65f56eeSaurel32 
751a65f56eeSaurel32     /* Check CAM */
752a65f56eeSaurel32     for (i = 0; i < 16; i++) {
753a65f56eeSaurel32         if (s->regs[SONIC_CE] & (1 << i)) {
754a65f56eeSaurel32             /* Entry enabled */
755a65f56eeSaurel32             if (!memcmp(buf, s->cam[i], sizeof(s->cam[i]))) {
756a65f56eeSaurel32                 return 0;
757a65f56eeSaurel32             }
758a65f56eeSaurel32         }
759a65f56eeSaurel32     }
760a65f56eeSaurel32 
761a65f56eeSaurel32     return -1;
762a65f56eeSaurel32 }
763a65f56eeSaurel32 
7643df5de64SHervé Poussineau static ssize_t dp8393x_receive(NetClientState *nc, const uint8_t * buf,
7659e3cd456SFinn Thain                                size_t pkt_size)
766a65f56eeSaurel32 {
767cc1f0f45SJason Wang     dp8393xState *s = qemu_get_nic_opaque(nc);
768a65f56eeSaurel32     int packet_type;
769a65f56eeSaurel32     uint32_t available, address;
770350e7d9aSFinn Thain     int width, rx_len, padded_len;
771a65f56eeSaurel32     uint32_t checksum;
7729e3cd456SFinn Thain     int size;
773a65f56eeSaurel32 
774a65f56eeSaurel32     s->regs[SONIC_RCR] &= ~(SONIC_RCR_PRX | SONIC_RCR_LBK | SONIC_RCR_FAER |
775a65f56eeSaurel32         SONIC_RCR_CRCR | SONIC_RCR_LPKT | SONIC_RCR_BC | SONIC_RCR_MC);
776a65f56eeSaurel32 
777c2279bd0SFinn Thain     if (s->last_rba_is_full) {
778c2279bd0SFinn Thain         return pkt_size;
779c2279bd0SFinn Thain     }
780c2279bd0SFinn Thain 
781350e7d9aSFinn Thain     rx_len = pkt_size + sizeof(checksum);
782350e7d9aSFinn Thain     if (s->regs[SONIC_DCR] & SONIC_DCR_DW) {
783350e7d9aSFinn Thain         width = 2;
784350e7d9aSFinn Thain         padded_len = ((rx_len - 1) | 3) + 1;
785350e7d9aSFinn Thain     } else {
786350e7d9aSFinn Thain         width = 1;
787350e7d9aSFinn Thain         padded_len = ((rx_len - 1) | 1) + 1;
788350e7d9aSFinn Thain     }
789350e7d9aSFinn Thain 
790350e7d9aSFinn Thain     if (padded_len > dp8393x_rbwc(s) * 2) {
791*c0af04a4SMark Cave-Ayland         trace_dp8393x_receive_oversize(pkt_size);
792ada74315SFinn Thain         s->regs[SONIC_ISR] |= SONIC_ISR_RBAE;
793ada74315SFinn Thain         dp8393x_update_irq(s);
794c2279bd0SFinn Thain         s->regs[SONIC_RCR] |= SONIC_RCR_LPKT;
795c2279bd0SFinn Thain         goto done;
796ada74315SFinn Thain     }
797ada74315SFinn Thain 
7989e3cd456SFinn Thain     packet_type = dp8393x_receive_filter(s, buf, pkt_size);
799a65f56eeSaurel32     if (packet_type < 0) {
800*c0af04a4SMark Cave-Ayland         trace_dp8393x_receive_not_netcard();
8014f1c942bSMark McLoughlin         return -1;
802a65f56eeSaurel32     }
803a65f56eeSaurel32 
804a65f56eeSaurel32     /* Check for EOL */
80588f632fbSFinn Thain     if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
806a65f56eeSaurel32         /* Are we still in resource exhaustion? */
807a65f56eeSaurel32         size = sizeof(uint16_t) * 1 * width;
808581f7b12SPeter Maydell         address = dp8393x_crda(s) + sizeof(uint16_t) * 5 * width;
80919f70347SPeter Maydell         address_space_read(&s->as, address, MEMTXATTRS_UNSPECIFIED,
81019f70347SPeter Maydell                            s->data, size);
8115b0c98fcSFinn Thain         s->regs[SONIC_LLFA] = dp8393x_get(s, width, 0);
8125b0c98fcSFinn Thain         if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
813a65f56eeSaurel32             /* Still EOL ; stop reception */
8144f1c942bSMark McLoughlin             return -1;
815a65f56eeSaurel32         }
8165b0c98fcSFinn Thain         /* Link has been updated by host */
817d9fae131SFinn Thain 
818d9fae131SFinn Thain         /* Clear in_use */
819d9fae131SFinn Thain         size = sizeof(uint16_t) * width;
820d9fae131SFinn Thain         address = dp8393x_crda(s) + sizeof(uint16_t) * 6 * width;
821d9fae131SFinn Thain         dp8393x_put(s, width, 0, 0);
822d9fae131SFinn Thain         address_space_rw(&s->as, address, MEMTXATTRS_UNSPECIFIED,
823d9fae131SFinn Thain                          (uint8_t *)s->data, size, 1);
824d9fae131SFinn Thain 
825d9fae131SFinn Thain         /* Move to next descriptor */
8265b0c98fcSFinn Thain         s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
827d9fae131SFinn Thain         s->regs[SONIC_ISR] |= SONIC_ISR_PKTRX;
828a65f56eeSaurel32     }
829a65f56eeSaurel32 
830a65f56eeSaurel32     /* Save current position */
831a65f56eeSaurel32     s->regs[SONIC_TRBA1] = s->regs[SONIC_CRBA1];
832a65f56eeSaurel32     s->regs[SONIC_TRBA0] = s->regs[SONIC_CRBA0];
833a65f56eeSaurel32 
834a65f56eeSaurel32     /* Calculate the ethernet checksum */
835350e7d9aSFinn Thain     checksum = cpu_to_le32(crc32(0, buf, pkt_size));
836a65f56eeSaurel32 
837a65f56eeSaurel32     /* Put packet into RBA */
838*c0af04a4SMark Cave-Ayland     trace_dp8393x_receive_packet(dp8393x_crba(s));
839581f7b12SPeter Maydell     address = dp8393x_crba(s);
84019f70347SPeter Maydell     address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
841350e7d9aSFinn Thain                         buf, pkt_size);
842350e7d9aSFinn Thain     address += pkt_size;
843350e7d9aSFinn Thain 
844350e7d9aSFinn Thain     /* Put frame checksum into RBA */
84519f70347SPeter Maydell     address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
846350e7d9aSFinn Thain                         &checksum, sizeof(checksum));
847350e7d9aSFinn Thain     address += sizeof(checksum);
848350e7d9aSFinn Thain 
849350e7d9aSFinn Thain     /* Pad short packets to keep pointers aligned */
850350e7d9aSFinn Thain     if (rx_len < padded_len) {
851350e7d9aSFinn Thain         size = padded_len - rx_len;
852350e7d9aSFinn Thain         address_space_rw(&s->as, address, MEMTXATTRS_UNSPECIFIED,
853350e7d9aSFinn Thain             (uint8_t *)"\xFF\xFF\xFF", size, 1);
854350e7d9aSFinn Thain         address += size;
855350e7d9aSFinn Thain     }
856350e7d9aSFinn Thain 
857a65f56eeSaurel32     s->regs[SONIC_CRBA1] = address >> 16;
858a65f56eeSaurel32     s->regs[SONIC_CRBA0] = address & 0xffff;
859581f7b12SPeter Maydell     available = dp8393x_rbwc(s);
860350e7d9aSFinn Thain     available -= padded_len >> 1;
861a65f56eeSaurel32     s->regs[SONIC_RBWC1] = available >> 16;
862a65f56eeSaurel32     s->regs[SONIC_RBWC0] = available & 0xffff;
863a65f56eeSaurel32 
864a65f56eeSaurel32     /* Update status */
865581f7b12SPeter Maydell     if (dp8393x_rbwc(s) < s->regs[SONIC_EOBC]) {
866a65f56eeSaurel32         s->regs[SONIC_RCR] |= SONIC_RCR_LPKT;
867a65f56eeSaurel32     }
868a65f56eeSaurel32     s->regs[SONIC_RCR] |= packet_type;
869a65f56eeSaurel32     s->regs[SONIC_RCR] |= SONIC_RCR_PRX;
870a65f56eeSaurel32     if (s->loopback_packet) {
871a65f56eeSaurel32         s->regs[SONIC_RCR] |= SONIC_RCR_LBK;
872a65f56eeSaurel32         s->loopback_packet = 0;
873a65f56eeSaurel32     }
874a65f56eeSaurel32 
875a65f56eeSaurel32     /* Write status to memory */
876*c0af04a4SMark Cave-Ayland     trace_dp8393x_receive_write_status(dp8393x_crda(s));
877af9f0be3SLaurent Vivier     dp8393x_put(s, width, 0, s->regs[SONIC_RCR]); /* status */
878af9f0be3SLaurent Vivier     dp8393x_put(s, width, 1, rx_len); /* byte count */
879af9f0be3SLaurent Vivier     dp8393x_put(s, width, 2, s->regs[SONIC_TRBA0]); /* pkt_ptr0 */
880af9f0be3SLaurent Vivier     dp8393x_put(s, width, 3, s->regs[SONIC_TRBA1]); /* pkt_ptr1 */
881af9f0be3SLaurent Vivier     dp8393x_put(s, width, 4, s->regs[SONIC_RSC]); /* seq_no */
882a65f56eeSaurel32     size = sizeof(uint16_t) * 5 * width;
88319f70347SPeter Maydell     address_space_write(&s->as, dp8393x_crda(s),
88419f70347SPeter Maydell                         MEMTXATTRS_UNSPECIFIED,
88519f70347SPeter Maydell                         s->data, size);
886a65f56eeSaurel32 
8875b0c98fcSFinn Thain     /* Check link field */
888a65f56eeSaurel32     size = sizeof(uint16_t) * width;
88919f70347SPeter Maydell     address_space_read(&s->as,
89019f70347SPeter Maydell                        dp8393x_crda(s) + sizeof(uint16_t) * 5 * width,
89119f70347SPeter Maydell                        MEMTXATTRS_UNSPECIFIED, s->data, size);
892af9f0be3SLaurent Vivier     s->regs[SONIC_LLFA] = dp8393x_get(s, width, 0);
89388f632fbSFinn Thain     if (s->regs[SONIC_LLFA] & SONIC_DESC_EOL) {
894a65f56eeSaurel32         /* EOL detected */
895a65f56eeSaurel32         s->regs[SONIC_ISR] |= SONIC_ISR_RDE;
896a65f56eeSaurel32     } else {
89746ffee9aSFinn Thain         /* Clear in_use */
89846ffee9aSFinn Thain         size = sizeof(uint16_t) * width;
89946ffee9aSFinn Thain         address = dp8393x_crda(s) + sizeof(uint16_t) * 6 * width;
90046ffee9aSFinn Thain         dp8393x_put(s, width, 0, 0);
90146ffee9aSFinn Thain         address_space_write(&s->as, address, MEMTXATTRS_UNSPECIFIED,
90246ffee9aSFinn Thain                             s->data, size);
9035b0c98fcSFinn Thain 
9045b0c98fcSFinn Thain         /* Move to next descriptor */
905a65f56eeSaurel32         s->regs[SONIC_CRDA] = s->regs[SONIC_LLFA];
906a65f56eeSaurel32         s->regs[SONIC_ISR] |= SONIC_ISR_PKTRX;
90780b60673SFinn Thain     }
90880b60673SFinn Thain 
909c2279bd0SFinn Thain     dp8393x_update_irq(s);
910c2279bd0SFinn Thain 
91180b60673SFinn Thain     s->regs[SONIC_RSC] = (s->regs[SONIC_RSC] & 0xff00) |
91280b60673SFinn Thain                          ((s->regs[SONIC_RSC] + 1) & 0x00ff);
913a65f56eeSaurel32 
914c2279bd0SFinn Thain done:
915c2279bd0SFinn Thain 
916a65f56eeSaurel32     if (s->regs[SONIC_RCR] & SONIC_RCR_LPKT) {
917c2279bd0SFinn Thain         if (s->regs[SONIC_RRP] == s->regs[SONIC_RWP]) {
918c2279bd0SFinn Thain             /* Stop packet reception */
919c2279bd0SFinn Thain             s->last_rba_is_full = true;
920c2279bd0SFinn Thain         } else {
921c2279bd0SFinn Thain             /* Read next resource */
9223df5de64SHervé Poussineau             dp8393x_do_read_rra(s);
923a65f56eeSaurel32         }
924c2279bd0SFinn Thain     }
9254f1c942bSMark McLoughlin 
9269e3cd456SFinn Thain     return pkt_size;
927a65f56eeSaurel32 }
928a65f56eeSaurel32 
929104655a5SHervé Poussineau static void dp8393x_reset(DeviceState *dev)
930a65f56eeSaurel32 {
931104655a5SHervé Poussineau     dp8393xState *s = DP8393X(dev);
932bc72ad67SAlex Bligh     timer_del(s->watchdog);
933a65f56eeSaurel32 
934bd8f1ebcSHervé Poussineau     memset(s->regs, 0, sizeof(s->regs));
935083e21bbSFinn Thain     s->regs[SONIC_SR] = 0x0004; /* only revision recognized by Linux/mips */
936a65f56eeSaurel32     s->regs[SONIC_CR] = SONIC_CR_RST | SONIC_CR_STP | SONIC_CR_RXDIS;
937a65f56eeSaurel32     s->regs[SONIC_DCR] &= ~(SONIC_DCR_EXBUS | SONIC_DCR_LBR);
9381ca82a8dSMark Cave-Ayland     s->regs[SONIC_RCR] &= ~(SONIC_RCR_LB0 | SONIC_RCR_LB1 | SONIC_RCR_BRD |
9391ca82a8dSMark Cave-Ayland                             SONIC_RCR_RNT);
940a65f56eeSaurel32     s->regs[SONIC_TCR] |= SONIC_TCR_NCRS | SONIC_TCR_PTX;
941a65f56eeSaurel32     s->regs[SONIC_TCR] &= ~SONIC_TCR_BCM;
942a65f56eeSaurel32     s->regs[SONIC_IMR] = 0;
943a65f56eeSaurel32     s->regs[SONIC_ISR] = 0;
944a65f56eeSaurel32     s->regs[SONIC_DCR2] = 0;
945a65f56eeSaurel32     s->regs[SONIC_EOBC] = 0x02F8;
946a65f56eeSaurel32     s->regs[SONIC_RSC] = 0;
947a65f56eeSaurel32     s->regs[SONIC_CE] = 0;
948a65f56eeSaurel32     s->regs[SONIC_RSC] = 0;
949a65f56eeSaurel32 
950a65f56eeSaurel32     /* Network cable is connected */
951a65f56eeSaurel32     s->regs[SONIC_RCR] |= SONIC_RCR_CRS;
952a65f56eeSaurel32 
953a65f56eeSaurel32     dp8393x_update_irq(s);
954a65f56eeSaurel32 }
955a65f56eeSaurel32 
95605f41fe3SMark McLoughlin static NetClientInfo net_dp83932_info = {
957f394b2e2SEric Blake     .type = NET_CLIENT_DRIVER_NIC,
95805f41fe3SMark McLoughlin     .size = sizeof(NICState),
9593df5de64SHervé Poussineau     .can_receive = dp8393x_can_receive,
9603df5de64SHervé Poussineau     .receive = dp8393x_receive,
96105f41fe3SMark McLoughlin };
96205f41fe3SMark McLoughlin 
963104655a5SHervé Poussineau static void dp8393x_instance_init(Object *obj)
964a65f56eeSaurel32 {
965104655a5SHervé Poussineau     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
966104655a5SHervé Poussineau     dp8393xState *s = DP8393X(obj);
967a65f56eeSaurel32 
968104655a5SHervé Poussineau     sysbus_init_mmio(sbd, &s->mmio);
96989ae0ff9SHervé Poussineau     sysbus_init_mmio(sbd, &s->prom);
970104655a5SHervé Poussineau     sysbus_init_irq(sbd, &s->irq);
971104655a5SHervé Poussineau }
972a65f56eeSaurel32 
973104655a5SHervé Poussineau static void dp8393x_realize(DeviceState *dev, Error **errp)
974104655a5SHervé Poussineau {
975104655a5SHervé Poussineau     dp8393xState *s = DP8393X(dev);
97689ae0ff9SHervé Poussineau     int i, checksum;
97789ae0ff9SHervé Poussineau     uint8_t *prom;
97852579c68SHervé Poussineau     Error *local_err = NULL;
979a65f56eeSaurel32 
980104655a5SHervé Poussineau     address_space_init(&s->as, s->dma_mr, "dp8393x");
981104655a5SHervé Poussineau     memory_region_init_io(&s->mmio, OBJECT(dev), &dp8393x_ops, s,
982104655a5SHervé Poussineau                           "dp8393x-regs", 0x40 << s->it_shift);
983104655a5SHervé Poussineau 
984104655a5SHervé Poussineau     s->nic = qemu_new_nic(&net_dp83932_info, &s->conf,
985104655a5SHervé Poussineau                           object_get_typename(OBJECT(dev)), dev->id, s);
986104655a5SHervé Poussineau     qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
987104655a5SHervé Poussineau 
988bc72ad67SAlex Bligh     s->watchdog = timer_new_ns(QEMU_CLOCK_VIRTUAL, dp8393x_watchdog, s);
98989ae0ff9SHervé Poussineau 
990fcd3b085SPhilippe Mathieu-Daudé     memory_region_init_rom(&s->prom, OBJECT(dev), "dp8393x-prom",
991fcd3b085SPhilippe Mathieu-Daudé                            SONIC_PROM_SIZE, &local_err);
99252579c68SHervé Poussineau     if (local_err) {
99352579c68SHervé Poussineau         error_propagate(errp, local_err);
99452579c68SHervé Poussineau         return;
99552579c68SHervé Poussineau     }
99689ae0ff9SHervé Poussineau     prom = memory_region_get_ram_ptr(&s->prom);
99789ae0ff9SHervé Poussineau     checksum = 0;
99889ae0ff9SHervé Poussineau     for (i = 0; i < 6; i++) {
99989ae0ff9SHervé Poussineau         prom[i] = s->conf.macaddr.a[i];
100089ae0ff9SHervé Poussineau         checksum += prom[i];
100189ae0ff9SHervé Poussineau         if (checksum > 0xff) {
100289ae0ff9SHervé Poussineau             checksum = (checksum + 1) & 0xff;
100389ae0ff9SHervé Poussineau         }
100489ae0ff9SHervé Poussineau     }
100589ae0ff9SHervé Poussineau     prom[7] = 0xff - checksum;
1006a65f56eeSaurel32 }
1007104655a5SHervé Poussineau 
10081670735dSHervé Poussineau static const VMStateDescription vmstate_dp8393x = {
10091670735dSHervé Poussineau     .name = "dp8393x",
10101670735dSHervé Poussineau     .version_id = 0,
10111670735dSHervé Poussineau     .minimum_version_id = 0,
10121670735dSHervé Poussineau     .fields = (VMStateField []) {
10131670735dSHervé Poussineau         VMSTATE_BUFFER_UNSAFE(cam, dp8393xState, 0, 16 * 6),
10141670735dSHervé Poussineau         VMSTATE_UINT16_ARRAY(regs, dp8393xState, 0x40),
10151670735dSHervé Poussineau         VMSTATE_END_OF_LIST()
10161670735dSHervé Poussineau     }
10171670735dSHervé Poussineau };
10181670735dSHervé Poussineau 
1019104655a5SHervé Poussineau static Property dp8393x_properties[] = {
1020104655a5SHervé Poussineau     DEFINE_NIC_PROPERTIES(dp8393xState, conf),
10213110ce81SMarc-André Lureau     DEFINE_PROP_LINK("dma_mr", dp8393xState, dma_mr,
10223110ce81SMarc-André Lureau                      TYPE_MEMORY_REGION, MemoryRegion *),
1023104655a5SHervé Poussineau     DEFINE_PROP_UINT8("it_shift", dp8393xState, it_shift, 0),
1024be920841SLaurent Vivier     DEFINE_PROP_BOOL("big_endian", dp8393xState, big_endian, false),
1025104655a5SHervé Poussineau     DEFINE_PROP_END_OF_LIST(),
1026104655a5SHervé Poussineau };
1027104655a5SHervé Poussineau 
1028104655a5SHervé Poussineau static void dp8393x_class_init(ObjectClass *klass, void *data)
1029104655a5SHervé Poussineau {
1030104655a5SHervé Poussineau     DeviceClass *dc = DEVICE_CLASS(klass);
1031104655a5SHervé Poussineau 
1032104655a5SHervé Poussineau     set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
1033104655a5SHervé Poussineau     dc->realize = dp8393x_realize;
1034104655a5SHervé Poussineau     dc->reset = dp8393x_reset;
10351670735dSHervé Poussineau     dc->vmsd = &vmstate_dp8393x;
10364f67d30bSMarc-André Lureau     device_class_set_props(dc, dp8393x_properties);
1037104655a5SHervé Poussineau }
1038104655a5SHervé Poussineau 
1039104655a5SHervé Poussineau static const TypeInfo dp8393x_info = {
1040104655a5SHervé Poussineau     .name          = TYPE_DP8393X,
1041104655a5SHervé Poussineau     .parent        = TYPE_SYS_BUS_DEVICE,
1042104655a5SHervé Poussineau     .instance_size = sizeof(dp8393xState),
1043104655a5SHervé Poussineau     .instance_init = dp8393x_instance_init,
1044104655a5SHervé Poussineau     .class_init    = dp8393x_class_init,
1045104655a5SHervé Poussineau };
1046104655a5SHervé Poussineau 
1047104655a5SHervé Poussineau static void dp8393x_register_types(void)
1048104655a5SHervé Poussineau {
1049104655a5SHervé Poussineau     type_register_static(&dp8393x_info);
1050104655a5SHervé Poussineau }
1051104655a5SHervé Poussineau 
1052104655a5SHervé Poussineau type_init(dp8393x_register_types)
1053