summaryrefslogtreecommitdiffstats
path: root/firmware/target/mips/ingenic_x1000/usb-x1000.c
blob: 1a31d8db2e72fd1a7134d55fde90b331697313d0 (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
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2021 Aidan MacDonald
 *
 * 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 "system.h"
#include "usb.h"
#include "usb_core.h"
#include "usb_drv.h"
#include "usb-designware.h"
#include "irq-x1000.h"
#include "gpio-x1000.h"
#include "x1000/cpm.h"

/*
 * USB-Designware driver API
 */

const struct usb_dw_config usb_dw_config = {
    .phytype = DWC_PHYTYPE_UTMI_16,

    /* Available FIFO memory: 3576 words */
    .rx_fifosz   = 1024,
    .nptx_fifosz = 128,  /* 1 dedicated FIFO for EP0 */
    .ptx_fifosz  = 768,  /* 3 dedicated FIFOs */

#ifndef USB_DW_ARCH_SLAVE
    .ahb_burst_len = HBSTLEN_INCR16,
    /* Disable Rx FIFO thresholding. It appears to cause problems,
     * apparently a known issue -- Synopsys recommends disabling it
     * because it can cause issues during certain error conditions.
     */
    .ahb_threshold = 0,
#else
    .disable_double_buffering = false,
#endif
};

/* USB PHY init from Linux kernel code:
 * - arch/mips/xburst/soc-x1000/common/cpm_usb.c
 *   Copyright (C) 2005-2017 Ingenic Semiconductor
 */
void usb_dw_target_enable_clocks(void)
{
    /* Enable CPM clock */
    jz_writef(CPM_CLKGR, OTG(0));
#if X1000_EXCLK_FREQ == 24000000
    jz_writef(CPM_USBCDR, CLKSRC_V(EXCLK), CE(1), CLKDIV(0), PHY_GATE(0));
#else
# error "please add USB clock settings for 26 MHz EXCLK"
#endif
    while(jz_readf(CPM_USBCDR, BUSY));
    jz_writef(CPM_USBCDR, CE(0));

    /* PHY soft reset */
    jz_writef(CPM_SRBC, OTG_SR(1));
    udelay(10);
    jz_writef(CPM_SRBC, OTG_SR(0));

    /* Ungate PHY clock */
    jz_writef(CPM_OPCR, GATE_USBPHY_CLK(0));

    /* Exit suspend state */
    jz_writef(CPM_OPCR, SPENDN0(1));
    udelay(45);

    /* Program core configuration */
    jz_overwritef(CPM_USBVBFIL,
                  IDDIGFIL(0),
                  VBFIL(0));
    jz_overwritef(CPM_USBRDT,
                  HB_MASK(0),
                  VBFIL_LD_EN(1),
                  IDDIG_EN(0),
                  RDT(0x96));
    jz_overwritef(CPM_USBPCR,
                  OTG_DISABLE(1),
                  COMMONONN(1),
                  VBUSVLDEXT(1),
                  VBUSVLDEXTSEL(1),
                  SQRXTUNE(7),
                  TXPREEMPHTUNE(1),
                  TXHSXVTUNE(1),
                  TXVREFTUNE(7));
    jz_overwritef(CPM_USBPCR1,
                  BVLD_REG(1),
                  REFCLK_SEL_V(CLKCORE),
                  REFCLK_DIV_V(24MHZ), /* applies for 26 MHz EXCLK too */
                  WORD_IF_V(16BIT));

    /* Power on reset */
    jz_writef(CPM_USBPCR, POR(1));
    mdelay(1);
    jz_writef(CPM_USBPCR, POR(0));
    mdelay(1);
}

void usb_dw_target_disable_clocks(void)
{
    /* Suspend and power down PHY, then gate its clock */
    jz_writef(CPM_OPCR, SPENDN0(0));
    udelay(5);
    jz_writef(CPM_USBPCR, OTG_DISABLE(1), SIDDQ(1));
    jz_writef(CPM_OPCR, GATE_USBPHY_CLK(1));

    /* Disable CPM clock */
    jz_writef(CPM_USBCDR, CE(1), STOP(1), PHY_GATE(1));
    while(jz_readf(CPM_USBCDR, BUSY));
    jz_writef(CPM_USBCDR, CE(0));
    jz_writef(CPM_CLKGR, OTG(1));
}

void usb_dw_target_enable_irq(void)
{
    system_enable_irq(IRQ_OTG);
}

void usb_dw_target_disable_irq(void)
{
    system_disable_irq(IRQ_OTG);
}

void usb_dw_target_clear_irq(void)
{
}

/*
 * Rockbox API
 */

#ifdef USB_STATUS_BY_EVENT
static volatile int usb_status = USB_EXTRACTED;
static void usb_detect_interrupt(void);
#endif

static int __usb_detect(void)
{
    /* XXX: Do we need an active level define for this? */
    if(gpio_get_level(GPIO_USB_DETECT))
        return USB_INSERTED;
    else
        return USB_EXTRACTED;
}

void usb_enable(bool on)
{
    if(on)
        usb_core_init();
    else
        usb_core_exit();
}

void usb_init_device(void)
{
    /* Disable drvvbus pin -- it is only used when acting as a host,
     * which Rockbox does not support */
    gpio_set_function(GPIO_USB_DRVVBUS, GPIOF_OUTPUT(0));

    /* Power up the core clocks to allow writing
       to some registers needed to power it down */
    usb_dw_target_disable_irq();
    usb_dw_target_enable_clocks();
    usb_drv_exit();

#ifdef USB_STATUS_BY_EVENT
    /* Setup USB detect pin IRQ */
    usb_status = __usb_detect();
    system_set_irq_handler(GPIO_TO_IRQ(GPIO_USB_DETECT), usb_detect_interrupt);
    gpio_set_function(GPIO_USB_DETECT, GPIOF_IRQ_EDGE(1));
    gpio_flip_edge_irq(GPIO_USB_DETECT);
    gpio_enable_irq(GPIO_USB_DETECT);
#endif
}

#ifndef USB_STATUS_BY_EVENT
int usb_detect(void)
{
    return __usb_detect();
}
#else
int usb_detect(void)
{
    return usb_status;
}

static void usb_detect_interrupt(void)
{
    /* Update status and flip the IRQ trigger edge */
    usb_status = __usb_detect();
    gpio_flip_edge_irq(GPIO_USB_DETECT);

    /* Notify Rockbox of event */
    usb_status_event(usb_status);
}
#endif