1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
#include "global.h"
#include "core/synopsysotg/synopsysotg.h"
#include "protocol/usb/usb.h"
#include "sys/time.h"

#ifndef SYNOPSYSOTG_AHB_BURST_LEN
#define SYNOPSYSOTG_AHB_BURST_LEN 5
#endif
#ifndef SYNOPSYSOTG_AHB_THRESHOLD
#define SYNOPSYSOTG_AHB_THRESHOLD 8
#endif
#ifndef SYNOPSYSOTG_TURNAROUND
#define SYNOPSYSOTG_TURNAROUND 3
#endif

static void synopsysotg_flush_in_endpoint(const struct usb_instance* instance, int ep)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;
    if (data->core->INEP_REGS[ep].DIEPCTL.b.epena)
    {
        // Urgh, someone was still babbling on our IN pipe, and now we have some
        // old crap in the FIFO. Disable the endpoint, to make sure nobody will
        // fetch any more old crap while we're trying to get rid of it.
        synopsysotg_target_disable_irq(instance);
        data->core->INEP_REGS[ep].DIEPCTL.b.snak = 1;
        while (!(data->core->INEP_REGS[ep].DIEPINT.b.inepnakeff));
        data->core->INEP_REGS[ep].DIEPCTL.b.epdis = 1;
        while (!(data->core->INEP_REGS[ep].DIEPINT.b.epdisabled));
        synopsysotg_target_enable_irq(instance);
        // Wait for any DMA activity to stop, to make sure nobody will touch the FIFO.
        while (!data->core->GREGS.GRSTCTL.b.ahbidle);
        // Flush it all the way down!
        USB_OTG_GRSTCTL_TypeDef grstctl = { .b = { .txfnum = ep, .txfflsh = 1 } };
        data->core->GREGS.GRSTCTL = grstctl;
        while (data->core->GREGS.GRSTCTL.b.txfflsh);
    }
    // Reset the transfer size register. Not strictly neccessary, but can't hurt.
    data->core->INEP_REGS[ep].DIEPTSIZ.d32 = 0;
}

static void synopsysotg_flush_ints(const struct usb_instance* instance)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;
    int i;
    for (i = 0; i < 16; i++)
    {
        data->core->OUTEP_REGS[i].DOEPINT = data->core->OUTEP_REGS[i].DOEPINT;
        data->core->INEP_REGS[i].DIEPINT = data->core->INEP_REGS[i].DIEPINT;
    }
    data->core->GREGS.GINTSTS = data->core->GREGS.GINTSTS;
}

void synopsysotg_start_rx(const struct usb_instance* instance, union usb_endpoint_number ep, void* buf, int size)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;
    struct synopsysotg_state* state = (struct synopsysotg_state*)instance->driver_state;

    // Find the appropriate set of endpoint registers
    volatile USB_OTG_OUTEPREGS* regs = &data->core->OUTEP_REGS[ep.number];

    // Calculate number of packets (if size == 0 an empty packet will be sent)
    int maxpacket = regs->DOEPCTL.b.mps;
    int packets = (size + maxpacket - 1) / maxpacket;
    if (!packets) packets = 1;

    // Set up data desination
    if (data->use_dma) regs->DOEPDMA = buf;
    else state->endpoints[ep.number].rxaddr = (uint32_t*)buf;
    USB_OTG_DEPXFRSIZ_TypeDef deptsiz = { .b = { .pktcnt = packets, .xfersize = size } };
    regs->DOEPTSIZ = deptsiz;

    // Enable the endpoint
    USB_OTG_DEPCTL_TypeDef depctl = regs->DOEPCTL;
    depctl.b.epena = 1;
    depctl.b.cnak = 1;
    regs->DOEPCTL = depctl;
}

void synopsysotg_start_tx(const struct usb_instance* instance, union usb_endpoint_number ep, const void* buf, int size)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;
    struct synopsysotg_state* state = (struct synopsysotg_state*)instance->driver_state;

    // Find the appropriate set of endpoint registers
    volatile USB_OTG_INEPREGS* regs = &data->core->INEP_REGS[ep.number];

    // Calculate number of packets (if size == 0 an empty packet will be sent)
    int maxpacket = regs->DIEPCTL.b.mps;
    int packets = (size + maxpacket - 1) / maxpacket;
    if (!packets) packets = 1;

    // Set up data desination
    if (data->use_dma) regs->DIEPDMA = buf;
    else state->endpoints[ep.number].txaddr = (uint32_t*)buf;
    USB_OTG_DEPXFRSIZ_TypeDef deptsiz = { .b = { .pktcnt = packets, .xfersize = size } };
    regs->DIEPTSIZ = deptsiz;

    // Enable the endpoint
    USB_OTG_DEPCTL_TypeDef depctl = regs->DIEPCTL;
    depctl.b.epena = 1;
    depctl.b.cnak = 1;
    regs->DIEPCTL = depctl;

    // Start pushing data into the FIFO (must be done after enabling the endpoint)
    if (!data->use_dma) data->core->DREGS.DIEPEMPMSK.ep.in |= (1 << ep.number);
}

void synopsysotg_set_stall(const struct usb_instance* instance, union usb_endpoint_number ep, int stall)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;
    if (ep.direction == USB_ENDPOINT_DIRECTION_IN)
        data->core->INEP_REGS[ep.number].DIEPCTL.b.stall = !!stall;
    else data->core->OUTEP_REGS[ep.number].DOEPCTL.b.stall = !!stall;
}

void synopsysotg_set_address(const struct usb_instance* instance, uint8_t address)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;
    data->core->DREGS.DCFG.b.devaddr = address;
}

void synopsysotg_unconfigure_ep(const struct usb_instance* instance, union usb_endpoint_number ep)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;
    USB_OTG_DEPCTL_TypeDef depctl = { .b = { .epdis = 1 } };
    if (ep.direction == USB_ENDPOINT_DIRECTION_IN)
    {
        synopsysotg_flush_in_endpoint(instance, ep.number);
        data->core->INEP_REGS[ep.number].DIEPCTL = depctl;
        // Mask interrupts for this endpoint
        data->core->DREGS.DAINTMSK.ep.in &= ~(1 << ep.number);
    }
    else
    {
        // We can't really do much about in-flight OUT requests except for ignoring them.
        data->core->OUTEP_REGS[ep.number].DOEPTSIZ.d32 = 0;
        data->core->OUTEP_REGS[ep.number].DOEPCTL = depctl;
        // Mask interrupts for this endpoint
        data->core->DREGS.DAINTMSK.ep.out &= ~(1 << ep.number);
    }
}

void synopsysotg_configure_ep(const struct usb_instance* instance, union usb_endpoint_number ep,
                                  enum usb_endpoint_type type, int maxpacket)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;

    // Reset the endpoint, just in case someone left it in a dirty state.
    synopsysotg_unconfigure_ep(instance, ep);

    // Write the new configuration and unmask interrupts for the endpoint.
    // Reset data toggle to DATA0, as required by the USB specification.
    USB_OTG_DEPCTL_TypeDef depctl = { .b = { .usbactep = 1, .eptype = type, .mps = maxpacket, .txfnum = ep.number, .setd0pid = 1 } };
    if (ep.direction == USB_ENDPOINT_DIRECTION_IN)
    {
        data->core->INEP_REGS[ep.number].DIEPCTL = depctl;
        data->core->DREGS.DAINTMSK.ep.in |= (1 << ep.number);
    }
    else
    {
        data->core->OUTEP_REGS[ep.number].DOEPCTL = depctl;
        data->core->DREGS.DAINTMSK.ep.out |= (1 << ep.number);
    }
}

void synopsysotg_ep0_start_rx(const struct usb_instance* instance, int non_setup)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;
    struct synopsysotg_state* state = (struct synopsysotg_state*)instance->driver_state;

    // If we don't expect a non-SETUP packet, we can stall the OUT pipe,
    // SETUP packets will ignore that.
    if (!non_setup) data->core->OUTEP_REGS[0].DOEPCTL.b.stall = 1;

    // Set up data desination
    if (data->use_dma) data->core->OUTEP_REGS[0].DOEPDMA = instance->buffer;
    else state->endpoints[0].rxaddr = (uint32_t*)instance->buffer;
    USB_OTG_DEP0XFRSIZ_TypeDef deptsiz = { .b = { .supcnt = 3, .pktcnt = !!non_setup, .xfersize = 64 } };
    data->core->OUTEP_REGS[0].DOEPTSIZ.d32 = deptsiz.d32;

    // Enable the endpoint
    USB_OTG_DEPCTL_TypeDef depctl = data->core->OUTEP_REGS[0].DOEPCTL;
    depctl.b.epena = 1;
    depctl.b.cnak = 1;
    data->core->OUTEP_REGS[0].DOEPCTL = depctl;
}

void synopsysotg_ep0_start_tx(const struct usb_instance* instance, const void* buf, int len)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;
    struct synopsysotg_state* state = (struct synopsysotg_state*)instance->driver_state;

    if (len)
    {
        // Set up data source
        if (data->use_dma) data->core->INEP_REGS[0].DIEPDMA = buf;
        else state->endpoints[0].txaddr = buf;
        USB_OTG_DEP0XFRSIZ_TypeDef deptsiz = { .b = { .pktcnt = (len + 63) >> 6, .xfersize = len } };
        data->core->INEP_REGS[0].DIEPTSIZ.d32 = deptsiz.d32;
    }
    else
    {
        // Set up the IN pipe for a zero-length packet
        USB_OTG_DEP0XFRSIZ_TypeDef deptsiz = { .b = { .pktcnt = 1 } };
        data->core->INEP_REGS[0].DIEPTSIZ.d32 = deptsiz.d32;
    }

    // Enable the endpoint
    USB_OTG_DEPCTL_TypeDef depctl = data->core->INEP_REGS[0].DIEPCTL;
    depctl.b.epena = 1;
    depctl.b.cnak = 1;
    data->core->INEP_REGS[0].DIEPCTL = depctl;

    // Start pushing data into the FIFO (must be done after enabling the endpoint)
    if (len && !data->use_dma) data->core->DREGS.DIEPEMPMSK.ep.in |= 1;
}

static void synopsysotg_ep0_init(const struct usb_instance* instance)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;

    // Make sure both EP0 pipes are active.
    // (The hardware should take care of that, but who knows...)
    USB_OTG_DEPCTL_TypeDef depctl = { .b = { .usbactep = 1 } };
    data->core->OUTEP_REGS[0].DOEPCTL = depctl;
    data->core->INEP_REGS[0].DIEPCTL = depctl;

    // Prime EP0 for the first setup packet.
    usb_ep0_expect_setup(instance);
}

void synopsysotg_irq(const struct usb_instance* instance)
{
    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;
    struct synopsysotg_state* state = (struct synopsysotg_state*)instance->driver_state;

    USB_OTG_GINTSTS_TypeDef gintsts = data->core->GREGS.GINTSTS;

    if (gintsts.b.usbreset)
    {
        data->core->DREGS.DCFG.b.devaddr = 0;
        synopsysotg_ep0_init(instance);
        usb_handle_bus_reset(instance, data->core->DREGS.DSTS.b.enumspd == 0);
    }

    if (gintsts.b.rxstsqlvl)
    {
        // Device to memory part of the "software DMA" implementation, used to receive data if use_dma == 0.
        // Handle one packet at a time, the IRQ will re-trigger if there's something left.
        USB_OTG_GRXFSTS_TypeDef rxsts = data->core->GREGS.GRXSTSP;
        int ep = rxsts.b.chnum;
        int words = (rxsts.b.bcnt + 3) >> 2;
        while (words--) *state->endpoints[ep].rxaddr++ = data->core->DFIFO[0][0];
    }

    if (gintsts.b.inepintr)
    {
        USB_OTG_DAINT_TypeDef daint = data->core->DREGS.DAINT;
        int ep;
        for (ep = 0; ep < 16; ep++)
            if (daint.ep.in & (1 << ep))
            {
                USB_OTG_DIEPINTn_TypeDef epints = data->core->INEP_REGS[ep].DIEPINT;
                if (epints.b.emptyintr)
                {
                    // Memory to device part of the "software DMA" implementation, used to transmit data if use_dma == 0.
                    USB_OTG_DEPXFRSIZ_TypeDef deptsiz = data->core->INEP_REGS[ep].DIEPTSIZ;
                    if (!deptsiz.b.pktcnt) data->core->DREGS.DIEPEMPMSK.ep.in &= ~(1 << ep);
                    else
                    {
                        // Push data into the TX FIFO until we don't have anything left or the FIFO would overflow.
                        int left = (deptsiz.b.xfersize + 3) >> 2;
                        while (left)
                        {
                            int words = data->core->INEP_REGS[ep].DTXFSTS.b.txfspcavail;
                            if (words > left) words = left;
                            if (!words) break;
                            left -= words;
                            while (words--) data->core->DFIFO[ep][0] = *state->endpoints[ep].txaddr++;
                        }
                    }
                }
                union usb_endpoint_number epnum = { .direction = USB_ENDPOINT_DIRECTION_IN, .number = ep };
                int bytesleft = data->core->INEP_REGS[ep].DIEPTSIZ.b.xfersize;
                if (epints.b.timeout) usb_handle_timeout(instance, epnum, bytesleft);
                if (epints.b.xfercompl) usb_handle_xfer_complete(instance, epnum, bytesleft);
                data->core->INEP_REGS[ep].DIEPINT = epints;
            }
    }

    if (gintsts.b.outepintr)
    {
        USB_OTG_DAINT_TypeDef daint = data->core->DREGS.DAINT;
        int ep;
        for (ep = 0; ep < 16; ep++)
            if (daint.ep.out & (1 << ep))
            {
                USB_OTG_DOEPINTn_TypeDef epints = data->core->OUTEP_REGS[ep].DOEPINT;
                union usb_endpoint_number epnum = { .direction = USB_ENDPOINT_DIRECTION_OUT, .number = ep };
                if (epints.b.setup)
                {
                    USB_OTG_DEP0XFRSIZ_TypeDef deptsiz = { .d32 = data->core->OUTEP_REGS[0].DOEPTSIZ.d32 };
                    int back2back = 3 - deptsiz.b.supcnt;
                    synopsysotg_flush_in_endpoint(instance, ep);
                    usb_handle_setup_received(instance, epnum, back2back);
                }
                else if (epints.b.xfercompl)
                {
                    int bytesleft = data->core->INEP_REGS[ep].DIEPTSIZ.b.xfersize;
                    usb_handle_xfer_complete(instance, epnum, bytesleft);
                }
                data->core->OUTEP_REGS[ep].DOEPINT = epints;
            }
    }

    data->core->GREGS.GINTSTS = gintsts;
}

void synopsysotg_init(const struct usb_instance* instance)
{
    int i;

    const struct synopsysotg_config* data = (const struct synopsysotg_config*)instance->driver_config;

    // Disable IRQ during setup
    synopsysotg_target_disable_irq(instance);

    // Enable OTG clocks
    synopsysotg_target_enable_clocks(instance);

    // Enable PHY clocks
    USB_OTG_PCGCCTL_TypeDef pcgcctl = { .b = {} };
    data->core->PCGCCTL = pcgcctl;

    // Configure PHY type (must be done before reset)
    USB_OTG_GCCFG_TypeDef gccfg = { .b = { .disablevbussensing = 1, .pwdn = 0 } };
    data->core->GREGS.GCCFG = gccfg;
    USB_OTG_GUSBCFG_TypeDef gusbcfg = { .b = { .force_dev = 1, .usbtrdtim = SYNOPSYSOTG_TURNAROUND } };
    if (data->use_ulpi) gusbcfg.b.ulpi_utmi_sel = 1;
    else gusbcfg.b.physel  = 1;
    data->core->GREGS.GUSBCFG = gusbcfg;

    // Reset the whole USB core
    USB_OTG_GRSTCTL_TypeDef grstctl = { .b = { .csftrst = 1 } };
    udelay(100);
    while (!data->core->GREGS.GRSTCTL.b.ahbidle);
    data->core->GREGS.GRSTCTL = grstctl;
    while (data->core->GREGS.GRSTCTL.b.csftrst);
    while (!data->core->GREGS.GRSTCTL.b.ahbidle);

    // Soft disconnect
    USB_OTG_DCTL_TypeDef dctl = { .b = { .sftdiscon = 1 } };
    data->core->DREGS.DCTL = dctl;

    // Configure the core
    USB_OTG_GAHBCFG_TypeDef gahbcfg = { .b = { .dmaenable = data->use_dma, .hburstlen = SYNOPSYSOTG_AHB_BURST_LEN, .glblintrmsk = 1 } };
    if (data->disable_double_buffering)
    {
        gahbcfg.b.nptxfemplvl_txfemplvl = 1;
        gahbcfg.b.ptxfemplvl = 1;
    }
    data->core->GREGS.GAHBCFG = gahbcfg;
    data->core->GREGS.GUSBCFG = gusbcfg;
    gccfg.b.pwdn = 1;
    data->core->GREGS.GCCFG = gccfg;
    USB_OTG_DCFG_TypeDef dcfg = { .b = { .nzstsouthshk = 1 } };
    data->core->DREGS.DCFG = dcfg;

    // Configure the FIFOs
    if (data->use_dma)
    {
        USB_OTG_DTHRCTL_TypeDef dthrctl = { .b = { .arb_park_en = 1, .rx_thr_en = 1, .iso_thr_en = 0, .non_iso_thr_en = 0,
                                                   .rx_thr_len = SYNOPSYSOTG_AHB_THRESHOLD } };
        data->core->DREGS.DTHRCTL = dthrctl;
    }
    int addr = data->fifosize;
    for (i = 0; i < 16; i++)
    {
        int size = data->txfifosize[i];
        addr -= size;
        if (size)
        {
            USB_OTG_TXFSIZ_TypeDef fsiz = { .b = { .startaddr = addr, .depth = size } };
            if (!i) data->core->GREGS.DIEPTXF0_HNPTXFSIZ = fsiz;
            else data->core->GREGS.DIEPTXF[i - 1] = fsiz;
        }
    }
    USB_OTG_RXFSIZ_TypeDef fsiz = { .b = { .depth = addr } };
    data->core->GREGS.GRXFSIZ = fsiz;

    // Set up interrupts
    USB_OTG_DOEPMSK_TypeDef doepmsk =  { .b = { .xfercompl = 1, .setup = 1 } };
    data->core->DREGS.DOEPMSK = doepmsk;
    USB_OTG_DIEPMSK_TypeDef diepmsk =  { .b = { .xfercompl = 1, .timeout = 1 } };
    data->core->DREGS.DIEPMSK = diepmsk;
    data->core->DREGS.DIEPEMPMSK.d32 = 0;
    USB_OTG_DAINT_TypeDef daintmsk = { .ep = { .in = 0b0000000000000001, .out = 0b0000000000000001 } };
    data->core->DREGS.DAINTMSK = daintmsk;
    USB_OTG_GINTMSK_TypeDef gintmsk =  { .b = { .usbreset = 1, .outepintr = 1, .inepintr = 1 } };
    if (!data->use_dma) gintmsk.b.rxstsqlvl = 1;
    data->core->GREGS.GINTMSK = gintmsk;
    synopsysotg_flush_ints(instance);
    synopsysotg_target_clear_irq(instance);
    synopsysotg_target_enable_irq(instance);

    // Soft reconnect
    dctl.b.sftdiscon = 0;
    data->core->DREGS.DCTL = dctl;
}

const struct usb_driver synopsysotg_driver =
{
    .init = synopsysotg_init,
    .ep0_start_rx = synopsysotg_ep0_start_rx,
    .ep0_start_tx = synopsysotg_ep0_start_tx,
    .start_rx = synopsysotg_start_rx,
    .start_tx = synopsysotg_start_tx,
    .set_stall = synopsysotg_set_stall,
    .set_address = synopsysotg_set_address,
    .configure_ep = synopsysotg_configure_ep,
    .unconfigure_ep = synopsysotg_unconfigure_ep,
};