summaryrefslogtreecommitdiff
path: root/src/portable/synopsys/dwc2/dwc2_common.c
blob: 0e38218e395b5d5b0aaa50f733fd5f5678e53cbc (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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
 * SPDX-FileCopyrightText: Copyright (c) 2024 Ha Thach (tinyusb.org)
 * SPDX-License-Identifier: MIT
 *
 * This file is part of the TinyUSB stack.
 */

#include "tusb_option.h"

#define DWC2_COMMON_DEBUG   2

#if defined(TUP_USBIP_DWC2) && (CFG_TUH_ENABLED || CFG_TUD_ENABLED)
#include "dwc2_common.h"

//--------------------------------------------------------------------
//
//--------------------------------------------------------------------
static void reset_core(dwc2_regs_t* dwc2) {
  // The software must check that bit 31 in this register is set to 1 (AHB Master is Idle) before starting any operation
  while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) {
  }

  const uint32_t gsnpsid = dwc2->gsnpsid; // preload gsnpsid which is not readable while resetting
  dwc2->grstctl |= GRSTCTL_CSRST; // reset core

  if ((gsnpsid & DWC2_CORE_REV_MASK) < (DWC2_CORE_REV_4_20a & DWC2_CORE_REV_MASK)) {
    // prior v4.20a: CSRST is self-clearing, and the core clears this bit after all the necessary logic is reset in
    // the core, which can take several clocks, depending on the current state of the core. Once this bit has been
    // cleared, the software must wait at least 3 PHY clocks before accessing the PHY domain (synchronization delay).
    while (dwc2->grstctl & GRSTCTL_CSRST) {}
  } else {
    // From v4.20a: CSRST bit is write only. The application must clear this bit after checking the bit 29 of this
    // register i.e Core Soft Reset Done CSRT_DONE (w1c)
    while (!(dwc2->grstctl & GRSTCTL_CSRST_DONE)) {}
    dwc2->grstctl = (dwc2->grstctl & ~GRSTCTL_CSRST) | GRSTCTL_CSRST_DONE;
  }

  while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) {} // wait for AHB master IDLE
}

// Dedicated FS PHY is internal with a clock 48Mhz.
static void phy_fs_init(dwc2_regs_t* dwc2) {
  TU_LOG(DWC2_COMMON_DEBUG, "Fullspeed PHY init\r\n");

  uint32_t gusbcfg = dwc2->gusbcfg;

  // Select FS PHY
  gusbcfg |= GUSBCFG_PHYSEL;
  dwc2->gusbcfg = gusbcfg;

  // MCU specific PHY init before reset
  dwc2_phy_init(dwc2, GHWCFG2_HSPHY_NOT_SUPPORTED);

  // Reset core after selecting PHY
  reset_core(dwc2);

  // USB turnaround time is critical for certification where long cables and 5-Hubs are used.
  // So if you need the AHB to run at less than 30 MHz, and if USB turnaround time is not critical,
  // these bits can be programmed to a larger value. Default is 5
  gusbcfg &= ~GUSBCFG_TRDT_Msk;
  gusbcfg |= 5u << GUSBCFG_TRDT_Pos;
  dwc2->gusbcfg = gusbcfg;

  // MCU specific PHY update post reset
  dwc2_phy_update(dwc2, GHWCFG2_HSPHY_NOT_SUPPORTED);
}

/* dwc2 has 2 highspeed PHYs options
 * - UTMI+ is internal highspeed PHY, can be clocked at 30 Mhz (8-bit) or 60 Mhz (16-bit).
 * - ULPI is external highspeed PHY, clocked at 60Mhz with 8-bit interface.
 *
 * In addition, UTMI+/ULPI can be shared to run at fullspeed mode with 48Mhz
 */
static void phy_hs_init(dwc2_regs_t* dwc2) {
  uint32_t gusbcfg = dwc2->gusbcfg;
  const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2};
  const dwc2_ghwcfg4_t ghwcfg4 = {.value = dwc2->ghwcfg4};

  uint8_t phy_width;
  if (CFG_TUSB_MCU != OPT_MCU_AT32F402_405 && // at32f402_405 does not support 16-bit
      ghwcfg4.phy_data_width) {
    phy_width = 16; // 16-bit PHY interface if supported
  } else {
    phy_width = 8; // 8-bit PHY interface
  }

  // De-select FS PHY
  gusbcfg &= ~GUSBCFG_PHYSEL;

  if (ghwcfg2.hs_phy_type == GHWCFG2_HSPHY_ULPI) {
    TU_LOG(DWC2_COMMON_DEBUG, "Highspeed ULPI PHY init\r\n");

    // Select ULPI PHY (external)
    gusbcfg |= GUSBCFG_ULPI_UTMI_SEL;

    // ULPI is always 8-bit interface
    gusbcfg &= ~GUSBCFG_PHYIF16;

    // ULPI select single data rate
    gusbcfg &= ~GUSBCFG_DDRSEL;

    // default internal VBUS Indicator and Drive
    gusbcfg &= ~(GUSBCFG_ULPIEVBUSD | GUSBCFG_ULPIEVBUSI);

    // Disable FS/LS ULPI
    gusbcfg &= ~(GUSBCFG_ULPIFSLS | GUSBCFG_ULPICSM);
  } else {
    TU_LOG(DWC2_COMMON_DEBUG, "Highspeed UTMI+ PHY init\r\n");

    // Select UTMI+ PHY (internal)
    gusbcfg &= ~GUSBCFG_ULPI_UTMI_SEL;

    // Set 16-bit interface if supported
    if (phy_width == 16) {
      gusbcfg |= GUSBCFG_PHYIF16;
    } else {
      gusbcfg &= ~GUSBCFG_PHYIF16;
    }
  }

  // Apply config
  dwc2->gusbcfg = gusbcfg;

  // mcu specific phy init
  dwc2_phy_init(dwc2, ghwcfg2.hs_phy_type);

  // Reset core after selecting PHY
  reset_core(dwc2);

  // Set turn-around, must after core reset otherwise it will be clear
  // - 9 if using 8-bit PHY interface
  // - 5 if using 16-bit PHY interface
  gusbcfg &= ~GUSBCFG_TRDT_Msk;
  gusbcfg |= (phy_width == 16 ? 5u : 9u) << GUSBCFG_TRDT_Pos;
  dwc2->gusbcfg = gusbcfg;

  // MCU specific PHY update post reset
  dwc2_phy_update(dwc2, ghwcfg2.hs_phy_type);
}

static bool check_dwc2(dwc2_regs_t* dwc2) {
#if CFG_TUSB_DEBUG >= DWC2_COMMON_DEBUG
  // print guid, gsnpsid, ghwcfg1, ghwcfg2, ghwcfg3, ghwcfg4
  // Run 'python dwc2_info.py' and check dwc2_info.md for bit-field value and comparison with other ports
  volatile uint32_t const* p = (volatile uint32_t const*) &dwc2->guid;
  TU_LOG1("guid, gsnpsid, ghwcfg1, ghwcfg2, ghwcfg3, ghwcfg4\r\n");
  for (size_t i = 0; i < 5; i++) {
    TU_LOG1("0x%08" PRIX32 ", ", p[i]);
  }
  TU_LOG1("0x%08" PRIX32 "\r\n", p[5]);
#endif

  // For some reason: GD32VF103 gsnpsid and all hwcfg register are always zero (skip it)
  (void)dwc2;
#if !TU_CHECK_MCU(OPT_MCU_GD32VF103)
  enum { GSNPSID_ID_MASK = TU_GENMASK(31, 16) };
  const uint32_t gsnpsid = dwc2->gsnpsid & GSNPSID_ID_MASK;
  TU_ASSERT(gsnpsid == DWC2_OTG_ID || gsnpsid == DWC2_FS_IOT_ID || gsnpsid == DWC2_HS_IOT_ID);
#endif

  return true;
}

//--------------------------------------------------------------------
//
//--------------------------------------------------------------------
bool dwc2_core_is_highspeed_phy(dwc2_regs_t* dwc2, bool prefer_hs_phy) {
  const dwc2_ghwcfg2_t ghwcfg2    = {.value = dwc2->ghwcfg2};
  const bool           has_hs_phy = (ghwcfg2.hs_phy_type != GHWCFG2_HSPHY_NOT_SUPPORTED);

  if (prefer_hs_phy) {
    return has_hs_phy;
  } else {
    const bool has_fs_phy = (ghwcfg2.fs_phy_type != GHWCFG2_FSPHY_NOT_SUPPORTED);
    // false if has fs phy, otherwise true since hs phy is the only available phy
    return !has_fs_phy && has_hs_phy;
  }
}

bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma) {
  dwc2_regs_t* dwc2 = DWC2_REG(rhport);

  // Check Synopsys ID register, failed if controller clock/power is not enabled
  TU_ASSERT(check_dwc2(dwc2));

  // disable global interrupt
  dwc2->gahbcfg &= ~GAHBCFG_GINT;

  if (is_hs_phy) {
    phy_hs_init(dwc2);
  } else {
    phy_fs_init(dwc2);
  }

  /* Set HS/FS Timeout Calibration to 7 (max available value).
   * The number of PHY clocks that the application programs in
   * this field is added to the high/full speed interpacket timeout
   * duration in the core to account for any additional delays
   * introduced by the PHY. This can be required, because the delay
   * introduced by the PHY in generating the linestate condition
   * can vary from one PHY to another. */
  dwc2->gusbcfg |= (7ul << GUSBCFG_TOCAL_Pos);

  // Enable PHY clock TODO stop/gate clock when suspended mode
  dwc2->pcgcctl &= ~(PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK | PCGCCTL_PWRCLMP | PCGCCTL_RSTPDWNMODULE);

  dfifo_flush_tx(dwc2, 0x10); // all tx fifo
  dfifo_flush_rx(dwc2);

  // Clear pending and disable all interrupts
  dwc2->gintsts = 0xFFFFFFFFU;
  dwc2->gotgint = 0xFFFFFFFFU;
  dwc2->gintmsk = 0;

  TU_LOG(DWC2_COMMON_DEBUG, "DMA = %u\r\n", is_dma);

  if (is_dma) {
    // DMA seems to be only settable after a core reset, and not possible to switch on-the-fly
    dwc2->gahbcfg |= GAHBCFG_DMAEN | GAHBCFG_HBSTLEN_2;
  } else {
    dwc2->gintmsk |= GINTSTS_RXFLVL;
  }

  return true;
}

void dwc2_core_deinit(uint8_t rhport) {
  dwc2_regs_t* dwc2 = DWC2_REG(rhport);

  // Disable global interrupt
  dwc2->gahbcfg &= ~GAHBCFG_GINT;

  // Reset core: this also flushes FIFOs and clears all interrupt registers
  reset_core(dwc2);

  // Stop PHY clock and gate HCLK for power saving (per databook chapter 14)
  dwc2->pcgcctl |= PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK;

  // MCU-specific PHY deinit (disable PHY power)
  const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2};
  const uint8_t hs_phy_type = (dwc2->gusbcfg & GUSBCFG_PHYSEL) ? GHWCFG2_HSPHY_NOT_SUPPORTED : ghwcfg2.hs_phy_type;
  dwc2_phy_deinit(dwc2, hs_phy_type);
}

// void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr) {
//   (void) in_isr;
//   dwc2_regs_t * const dwc2 = DWC2_REG(rhport);
//   const uint32_t int_mask = dwc2->gintmsk;
//   const uint32_t int_status = dwc2->gintsts & int_mask;
//
//   // Device disconnect
//   if (int_status & GINTSTS_DISCINT) {
//     dwc2->gintsts = GINTSTS_DISCINT;
//   }
//
// }

#endif