summaryrefslogtreecommitdiff
path: root/utils/hwstub/stub/jz4760b/usb_drv_jz4760b.c
blob: 92880c5303d3383d98a24d019e1d938eb77118b5 (plain)
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
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2014 by Marcin Bukat
                         Amaury Pouly
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "usb_drv.h"
#include "config.h"
#include "memory.h"
#include "target.h"
#include "jz4760b.h"

static void udc_reset(void)
{
    REG_USB_FADDR = 0;
    /* Reset EP0 */
    REG_USB_INDEX = 0;
    REG_USB_CSR0 = USB_CSR0_FLUSHFIFO | USB_CSR0_SVDOUTPKTRDY | USB_CSR0_SVDSETUPEND; /* clear setupend and rxpktrdy */
    REG_USB_POWER = USB_POWER_SOFTCONN | USB_POWER_HSENAB | USB_POWER_SUSPENDM;
}

void usb_drv_init(void)
{
    /* in case usb is running, soft disconnect */
    REG_USB_POWER &= ~USB_POWER_SOFTCONN;
    /* A delay seems necessary to avoid causing havoc. The USB spec says disconnect
     * detection time (T_DDIS) is around 2us but in practice many hubs might
     * require more. */
    target_mdelay(1);
    /* disable usb */
    REG_CPM_CLKGR0 |= CLKGR0_OTG;
    /* power up usb: assume EXCLK=12Mhz */
    REG_CPM_CPCCR &= ~CPCCR_ECS; /* use EXCLK as source (and not EXCLK/2) */
    REG_CPM_USBCDR = 0; /* use EXCLK as source, no divisor */
    REG_CPM_CPCCR |= CPCCR_CE; /* change source now */
    /* wait for stable clock */
    target_udelay(3);
    /* enable usb */
    REG_CPM_CLKGR0 &= ~CLKGR0_OTG;
    /* tweak various parameters */
    REG_CPM_USBVBFIL = 0x80;
    REG_CPM_USBRDT = 0x96;
    REG_CPM_USBRDT |= (1 << 25);
    REG_CPM_USBPCR &= ~0x3f;
    REG_CPM_USBPCR |= 0x35;
    REG_CPM_USBPCR &= ~USBPCR_USB_MODE;
    REG_CPM_USBPCR |= USBPCR_VBUSVLDEXT;
    /* reset otg phy */
    REG_CPM_USBPCR |= USBPCR_POR;
    target_udelay(30);
    REG_CPM_USBPCR &= ~USBPCR_POR;
    target_udelay(300);
    /* enable otg phy */
    REG_CPM_OPCR |= OPCR_OTGPHY_ENABLE;
    /* wait for stable phy */
    target_udelay(300);
    /* reset */
    udc_reset();
}

static void *read_fifo0(void *dst, unsigned size)
{
    unsigned char *p = dst;
    while(size-- > 0)
        *p++ = *(volatile uint8_t *)USB_FIFO_EP(0);
    return p;
}

static void *write_fifo0(void *src, unsigned size)
{
    unsigned char *p = src;
    while(size-- > 0)
        *(volatile uint8_t *)USB_FIFO_EP(0) = *p++;
    return p;
}

/* NOTE: this core is a bit weird, it handles the status stage automatically
 * as soon as DataEnd is written to CSR. The problem is that DataEnd needs
 * to be written as part of a read (INPKTRDY) or write (SVDOUTPKTRDY) request
 * but not on its own.
 * Thus the design is follows: after receiving the setup packet, we DO NOT
 * acknowledge it with SVDOUTPKTRDY. Instead it will be acknowledged
 * either as part of STALL or recv/send. If there is an OUT data stage, we use
 * a similar trick: we do not acknowledge the last packet and leave a pending
 * SVDOUTPKTRDY to be done as part of a final STALL or ZLP. */

int usb_drv_recv_setup(struct usb_ctrlrequest *req)
{
    while(1)
    {
        unsigned intr = REG_USB_INTRUSB;
        unsigned intrin = REG_USB_INTRIN;
        /* handle reset */
        if(intr & USB_INTR_RESET)
        {
            udc_reset();
            continue;
        }
        /* ignore anything but EP0 irq */
        if(!(intrin & 1))
            continue;
        /* select EP0 */
        REG_USB_INDEX = 0;
        /* load csr to examine the cause of the interrupt */
        unsigned csr0 = REG_USB_CSR0;
        /* wait setup: we expect to receive a packet */
        if(csr0 & USB_CSR0_OUTPKTRDY)
        {
            unsigned cnt = REG_USB_COUNT0;
            /* anything other than 8-byte is wrong */
            if(cnt == 8)
            {
                read_fifo0(req, 8);
                /* DO NOT acknowledge the packet, leave this to recv/send/stall */
                return 0;
            }
        }
    }
    return 0;
}

int usb_drv_port_speed(void)
{
    return (REG_USB_POWER & USB_POWER_HSMODE) ? 1 : 0;
}

void usb_drv_set_address(int address)
{
    REG_USB_FADDR = address;
}

int usb_drv_send(int endpoint, void *ptr, int length)
{
    (void) endpoint;
    /* select EP0 */
    REG_USB_INDEX = 0;
    /* clear packet ready for the PREVIOUS packet: warning, there is a trap here!
     * if we are clearing without sending anything (length=0) then we must
     * set DataEnd at the same time. Otherwise, we must set it by itself and then
     * send data */
    if(length > 0)
    {
        /* clear packet ready for the PREVIOUS packet (SETUP) */
        REG_USB_CSR0 |= USB_CSR0_SVDOUTPKTRDY;
        /* send data */
        do
        {
            unsigned csr = REG_USB_CSR0;
            /* write data  */
            int cnt = MIN(length, 64);
            ptr = write_fifo0(ptr, cnt);
            length -= cnt;
            csr |= USB_CSR0_INPKTRDY;
            /* last packet ? */
            if(length == 0)
                csr |= USB_CSR0_DATAEND;
            /* write csr */
            REG_USB_CSR0 = csr;
            /* wait for packet to be transmitted */
            while(REG_USB_CSR0 & USB_CSR0_INPKTRDY) {}
        }while(length > 0);
    }
    else
    {
        /* clear packet ready for the PREVIOUS packet (SETUP or DATA) and finish */
        REG_USB_CSR0 |= USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND;
    }
    /* wait until acknowledgement */
    while(REG_USB_CSR0 & USB_CSR0_DATAEND) {}
    return 0;
}

int usb_drv_recv(int endpoint, void* ptr, int length)
{
    (void) endpoint;
    int old_len = length;
    /* select EP0 */
    REG_USB_INDEX = 0;
    /* ZLP: ignore receive, the core does it automatically on DataEnd */
    if(length == 0)
        return 0;
    /* receive data
     * NOTE when we are called here, there is a pending SVDOUTPKTRDY to
     * be done (see note above usb_drv_recv_setup), and when we will finish,
     * we will also leave a pending SVDOUTPKTRDY to be done in stall or send */
    while(length > 0)
    {
        /* clear packet ready for the PREVIOUS packet */
        REG_USB_CSR0 |= USB_CSR0_SVDOUTPKTRDY;
        /* wait for data */
        while(!(REG_USB_CSR0 & USB_CSR0_OUTPKTRDY)) {}
        int cnt = REG_USB_COUNT0;
        /* clamp just in case */
        cnt = MIN(cnt, length);
        /* read fifo */
        ptr = read_fifo0(ptr, cnt);
        length -= cnt;
    }
    /* there is still a pending SVDOUTPKTRDY here */
    return old_len;
}

void usb_drv_stall(int endpoint, bool stall, bool in)
{
    (void) endpoint;
    (void) in;
    if(!stall)
        return; /* EP0 unstall automatically */
    /* select EP0 */
    REG_USB_INDEX = 0;
    /* set stall */
    REG_USB_CSR0 |= USB_CSR0_SVDOUTPKTRDY | USB_CSR0_SENDSTALL;
}

void usb_drv_exit(void)
{
    /* in case usb is running, soft disconnect */
    REG_USB_POWER &= ~USB_POWER_SOFTCONN;
    /* A delay seems necessary to avoid causing havoc. The USB spec says disconnect
     * detection time (T_DDIS) is around 2us but in practice many hubs might
     * require more. */
    target_mdelay(1);
    /* disable usb */
    REG_CPM_CLKGR0 |= CLKGR0_OTG;
}