aboutsummaryrefslogtreecommitdiff
path: root/Marlin/src/sd/usb_flashdrive/Sd2Card_FlashDrive.cpp
blob: c6e3c73f52874fb2c1b2b923f8ca71cb383625d8 (plain) (blame)
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
/**
 * Marlin 3D Printer Firmware
 * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
 *
 * Based on Sprinter and grbl.
 * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
 *
 * 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "../../inc/MarlinConfigPre.h"

/**
 * Adjust USB_DEBUG to select debugging verbosity.
 *    0 - no debug messages
 *    1 - basic insertion/removal messages
 *    2 - show USB state transitions
 *    3 - perform block range checking
 *    4 - print each block access
 */
#define USB_DEBUG         1
#define USB_STARTUP_DELAY 0

// uncomment to get 'printf' console debugging. NOT FOR UNO!
//#define HOST_DEBUG(...)     {char s[255]; sprintf(s,__VA_ARGS__); SERIAL_ECHOLNPAIR("UHS:",s);}
//#define BS_HOST_DEBUG(...)  {char s[255]; sprintf(s,__VA_ARGS__); SERIAL_ECHOLNPAIR("UHS:",s);}
//#define MAX_HOST_DEBUG(...) {char s[255]; sprintf(s,__VA_ARGS__); SERIAL_ECHOLNPAIR("UHS:",s);}

#if ENABLED(USB_FLASH_DRIVE_SUPPORT)

#include "../../MarlinCore.h"
#include "../../core/serial.h"
#include "../../module/temperature.h"

#if DISABLED(USE_OTG_USB_HOST) && !PINS_EXIST(USB_CS, USB_INTR)
  #error "USB_FLASH_DRIVE_SUPPORT requires USB_CS_PIN and USB_INTR_PIN to be defined."
#endif

#if ENABLED(USE_UHS3_USB)
  #define NO_AUTO_SPEED
  #define UHS_MAX3421E_SPD 8000000 >> SD_SPI_SPEED
  #define UHS_DEVICE_WINDOWS_USB_SPEC_VIOLATION_DESCRIPTOR_DEVICE 1
  #define UHS_HOST_MAX_INTERFACE_DRIVERS 2
  #define MASS_MAX_SUPPORTED_LUN 1
  #define USB_HOST_SERIAL MYSERIAL0

  // Workaround for certain issues with UHS3
  #define SKIP_PAGE3F // Required for IOGEAR media adapter
  #define USB_NO_TEST_UNIT_READY // Required for removable media adapter
  #define USB_HOST_MANUAL_POLL // Optimization to shut off IRQ automatically

  // Workarounds for keeping Marlin's watchdog timer from barking...
  void marlin_yield() {
    thermalManager.manage_heater();
  }
  #define SYSTEM_OR_SPECIAL_YIELD(...) marlin_yield();
  #define delay(x) safe_delay(x)

  #define LOAD_USB_HOST_SYSTEM
  #define LOAD_USB_HOST_SHIELD
  #define LOAD_UHS_BULK_STORAGE

  #define MARLIN_UHS_WRITE_SS(v) WRITE(USB_CS_PIN, v)
  #define MARLIN_UHS_READ_IRQ()  READ(USB_INTR_PIN)

  #include "lib-uhs3/UHS_host/UHS_host.h"

  MAX3421E_HOST usb(USB_CS_PIN, USB_INTR_PIN);
  UHS_Bulk_Storage bulk(&usb);

  #define UHS_START  (usb.Init() == 0)
  #define UHS_STATE(state) UHS_USB_HOST_STATE_##state
#elif ENABLED(USE_OTG_USB_HOST)

  #if HAS_SD_HOST_DRIVE
    #include HAL_PATH(../../HAL, msc_sd.h)
  #endif

  #include HAL_PATH(../../HAL, usb_host.h)

  #define UHS_START usb.start()
  #define rREVISION 0
  #define UHS_STATE(state) USB_STATE_##state
#else
  #include "lib-uhs2/Usb.h"
  #include "lib-uhs2/masstorage.h"

  USB usb;
  BulkOnly bulk(&usb);

  #define UHS_START usb.start()
  #define UHS_STATE(state) USB_STATE_##state
#endif

#include "Sd2Card_FlashDrive.h"

#include "../../lcd/marlinui.h"

static enum {
  UNINITIALIZED,
  DO_STARTUP,
  WAIT_FOR_DEVICE,
  WAIT_FOR_LUN,
  MEDIA_READY,
  MEDIA_ERROR
} state;

#if USB_DEBUG >= 3
  uint32_t lun0_capacity;
#endif

bool Sd2Card::usbStartup() {
  if (state <= DO_STARTUP) {
    SERIAL_ECHOPGM("Starting USB host...");
    if (!UHS_START) {
      SERIAL_ECHOLNPGM(" failed.");
      LCD_MESSAGEPGM(MSG_MEDIA_USB_FAILED);
      return false;
    }

    // SPI quick test - check revision register
    switch (usb.regRd(rREVISION)) {
      case 0x01: SERIAL_ECHOLNPGM("rev.01 started"); break;
      case 0x12: SERIAL_ECHOLNPGM("rev.02 started"); break;
      case 0x13: SERIAL_ECHOLNPGM("rev.03 started"); break;
      default:   SERIAL_ECHOLNPGM("started. rev unknown."); break;
    }
    state = WAIT_FOR_DEVICE;
  }
  return true;
}

// The USB library needs to be called periodically to detect USB thumbdrive
// insertion and removals. Call this idle() function periodically to allow
// the USB library to monitor for such events. This function also takes care
// of initializing the USB library for the first time.

void Sd2Card::idle() {
  usb.Task();

  const uint8_t task_state = usb.getUsbTaskState();

  #if USB_DEBUG >= 2
    if (state > DO_STARTUP) {
      static uint8_t laststate = 232;
      if (task_state != laststate) {
        laststate = task_state;
        #define UHS_USB_DEBUG(x) case UHS_STATE(x): SERIAL_ECHOLNPGM(#x); break
        switch (task_state) {
          UHS_USB_DEBUG(IDLE);
          UHS_USB_DEBUG(RESET_DEVICE);
          UHS_USB_DEBUG(RESET_NOT_COMPLETE);
          UHS_USB_DEBUG(DEBOUNCE);
          UHS_USB_DEBUG(DEBOUNCE_NOT_COMPLETE);
          UHS_USB_DEBUG(WAIT_SOF);
          UHS_USB_DEBUG(ERROR);
          UHS_USB_DEBUG(CONFIGURING);
          UHS_USB_DEBUG(CONFIGURING_DONE);
          UHS_USB_DEBUG(RUNNING);
          default:
            SERIAL_ECHOLNPAIR("UHS_USB_HOST_STATE: ", task_state);
            break;
        }
      }
    }
  #endif

  static millis_t next_state_ms = millis();

  #define GOTO_STATE_AFTER_DELAY(STATE, DELAY) do{ state = STATE; next_state_ms  = millis() + DELAY; }while(0)

  if (ELAPSED(millis(), next_state_ms)) {
    GOTO_STATE_AFTER_DELAY(state, 250); // Default delay

    switch (state) {

      case UNINITIALIZED:
        #ifndef MANUAL_USB_STARTUP
          GOTO_STATE_AFTER_DELAY( DO_STARTUP, USB_STARTUP_DELAY );
        #endif
        break;

      case DO_STARTUP: usbStartup(); break;

      case WAIT_FOR_DEVICE:
        if (task_state == UHS_STATE(RUNNING)) {
          #if USB_DEBUG >= 1
            SERIAL_ECHOLNPGM("USB device inserted");
          #endif
          GOTO_STATE_AFTER_DELAY( WAIT_FOR_LUN, 250 );
        }
        break;

      case WAIT_FOR_LUN:
        /* USB device is inserted, but if it is an SD card,
         * adapter it may not have an SD card in it yet. */
        if (bulk.LUNIsGood(0)) {
          #if USB_DEBUG >= 1
            SERIAL_ECHOLNPGM("LUN is good");
          #endif
          GOTO_STATE_AFTER_DELAY( MEDIA_READY, 100 );
        }
        else {
          #ifdef USB_HOST_MANUAL_POLL
            // Make sure we catch disconnect events
            usb.busprobe();
            usb.VBUS_changed();
          #endif
          #if USB_DEBUG >= 1
            SERIAL_ECHOLNPGM("Waiting for media");
          #endif
          LCD_MESSAGEPGM(MSG_MEDIA_WAITING);
          GOTO_STATE_AFTER_DELAY(state, 2000);
        }
        break;

      case MEDIA_READY: break;
      case MEDIA_ERROR: break;
    }

    if (state > WAIT_FOR_DEVICE && task_state != UHS_STATE(RUNNING)) {
      // Handle device removal events
      #if USB_DEBUG >= 1
        SERIAL_ECHOLNPGM("USB device removed");
      #endif
      if (state != MEDIA_READY)
        LCD_MESSAGEPGM(MSG_MEDIA_USB_REMOVED);
      GOTO_STATE_AFTER_DELAY(WAIT_FOR_DEVICE, 0);
    }

    else if (state > WAIT_FOR_LUN && !bulk.LUNIsGood(0)) {
      // Handle media removal events
      #if USB_DEBUG >= 1
        SERIAL_ECHOLNPGM("Media removed");
      #endif
      LCD_MESSAGEPGM(MSG_MEDIA_REMOVED);
      GOTO_STATE_AFTER_DELAY(WAIT_FOR_DEVICE, 0);
    }

    else if (task_state == UHS_STATE(ERROR)) {
      LCD_MESSAGEPGM(MSG_MEDIA_READ_ERROR);
      GOTO_STATE_AFTER_DELAY(MEDIA_ERROR, 0);
    }
  }
}

// Marlin calls this function to check whether an USB drive is inserted.
// This is equivalent to polling the SD_DETECT when using SD cards.
bool Sd2Card::isInserted() {
  return state == MEDIA_READY;
}

bool Sd2Card::isReady() {
  return state > DO_STARTUP;
}

// Marlin calls this to initialize an SD card once it is inserted.
bool Sd2Card::init(const uint8_t, const pin_t) {
  if (!isInserted()) return false;

  #if USB_DEBUG >= 1
  const uint32_t sectorSize = bulk.GetSectorSize(0);
  if (sectorSize != 512) {
    SERIAL_ECHOLNPAIR("Expecting sector size of 512. Got: ", sectorSize);
    return false;
  }
  #endif

  #if USB_DEBUG >= 3
    lun0_capacity = bulk.GetCapacity(0);
    SERIAL_ECHOLNPAIR("LUN Capacity (in blocks): ", lun0_capacity);
  #endif
  return true;
}

// Returns the capacity of the card in blocks.
uint32_t Sd2Card::cardSize() {
  if (!isInserted()) return false;
  #if USB_DEBUG < 3
    const uint32_t
  #endif
      lun0_capacity = bulk.GetCapacity(0);
  return lun0_capacity;
}

bool Sd2Card::readBlock(uint32_t block, uint8_t* dst) {
  if (!isInserted()) return false;
  #if USB_DEBUG >= 3
    if (block >= lun0_capacity) {
      SERIAL_ECHOLNPAIR("Attempt to read past end of LUN: ", block);
      return false;
    }
    #if USB_DEBUG >= 4
      SERIAL_ECHOLNPAIR("Read block ", block);
    #endif
  #endif
  return bulk.Read(0, block, 512, 1, dst) == 0;
}

bool Sd2Card::writeBlock(uint32_t block, const uint8_t* src) {
  if (!isInserted()) return false;
  #if USB_DEBUG >= 3
    if (block >= lun0_capacity) {
      SERIAL_ECHOLNPAIR("Attempt to write past end of LUN: ", block);
      return false;
    }
    #if USB_DEBUG >= 4
      SERIAL_ECHOLNPAIR("Write block ", block);
    #endif
  #endif
  return bulk.Write(0, block, 512, 1, src) == 0;
}

#endif // USB_FLASH_DRIVE_SUPPORT