1
1
#!/usr/bin/env python3
2
2
# -*- coding: utf-8 -*-
3
3
#
4
- # Copyright (c) 2020, Niklas Hauser
4
+ # Copyright (c) 2020, 2024, Niklas Hauser
5
5
#
6
6
# This file is part of the modm project.
7
7
#
13
13
from pathlib import Path
14
14
from collections import defaultdict
15
15
tusb_config = {}
16
+ default_port = 0
16
17
17
18
# -----------------------------------------------------------------------------
18
19
def init (module ):
@@ -21,14 +22,16 @@ def init(module):
21
22
22
23
def prepare (module , options ):
23
24
device = options [":target" ]
25
+ has_otg_hs = device .has_driver ("usb_otg_hs" )
26
+ has_otg_fs = device .has_driver ("usb_otg_fs" )
24
27
if not (device .has_driver ("usb" ) or
25
- device . has_driver ( "usb_otg_fs" ) or
26
- device . has_driver ( "usb_otg_hs" ) or
28
+ has_otg_fs or
29
+ has_otg_hs or
27
30
device .has_driver ("udp" ) or
28
31
device .has_driver ("uhp" )):
29
32
return False
30
33
31
- configs = { "device.cdc" , "device.msc " , "device.vendor " , "device.midi " , "device.dfu" }
34
+ configs = [ "device.cdc" , "device.dfu_rt " , "device.midi " , "device.msc " , "device.vendor" ]
32
35
# TODO: Allow all device classes
33
36
# configs = {"device.{}".format(p.parent.name)
34
37
# for p in Path(localpath("tinyusb/src/class/")).glob("*/*_device.h")}
@@ -39,30 +42,42 @@ def prepare(module, options):
39
42
module .add_list_option (
40
43
EnumerationOption (name = "config" ,
41
44
description = "Endpoint Configuration" ,
42
- enumeration = sorted ( configs ) ,
45
+ enumeration = configs ,
43
46
dependencies = lambda vs :
44
- [":tinyusb:{}" .format (v .replace ("." , ":" )) for v in vs ]))
47
+ [":tinyusb:" + v .replace ("." , ":" ) for v in vs ]))
48
+
45
49
46
50
# Most devices only have FS=usb, some STM32 have FS/HS=usb_otg_fs/usb_otg_hs
47
51
# and some STM32 only have HS=usb_otg_hs, so we only define this option if
48
52
# there is a choice and default this to FS by default.
49
- if device . has_driver ( "usb_otg_hs" ) and device . has_driver ( "usb_otg_fs" ) :
53
+ if has_otg_hs :
50
54
module .add_option (
51
- EnumerationOption (name = "speed" ,
52
- description = "USB Port Speed" ,
53
- enumeration = {"full" : "fs" , "high" : "hs" },
54
- default = "full" ,
55
- dependencies = lambda s : ":platform:usb:{}s" .format (s [0 ])))
56
-
57
- module .add_submodule (TinyUsbDeviceModule ())
58
- module .add_submodule (TinyUsbHostModule ())
55
+ EnumerationOption (name = "max-speed" , description = "Maximum HS port speed" ,
56
+ enumeration = ["full" , "high" ], default = "high" ))
57
+ # DEPRECATED: 2025q1
58
+ module .add_alias (
59
+ Alias (name = "speed" , description = "Renamed for more clarity" ,
60
+ destination = ":tinyusb:max-speed" ))
61
+ # Detect the number of ports and the default port
62
+ ports = 2 if (has_otg_hs and has_otg_fs ) else 1
63
+ if has_otg_hs and not has_otg_fs :
64
+ global default_port
65
+ default_port = 1
66
+ # Generate the host and device modules
67
+ module .add_submodule (TinyUsbDeviceModule (ports ))
68
+ module .add_submodule (TinyUsbHostModule (ports ))
59
69
module .depends (":cmsis:device" , ":architecture:atomic" , ":architecture:interrupt" , ":platform:usb" )
60
70
return True
61
71
62
72
63
73
def validate (env ):
64
- if env .has_module (":tinyusb:device" ) and env .has_module (":tinyusb:host" ):
65
- raise ValidateException ("TinyUSB won't let you use both Device *and* Host at the same time!" )
74
+ has_device = env .has_module (":tinyusb:device" )
75
+ has_host = env .has_module (":tinyusb:host" )
76
+ if has_device and has_host :
77
+ device_port = env .get (":tinyusb:device:port" )
78
+ host_port = env .get (":tinyusb:host:port" )
79
+ if device_port == host_port and host_port is not None :
80
+ raise ValidateException ("You cannot place Device and Host on the same USB port!" )
66
81
67
82
68
83
def build (env ):
@@ -90,7 +105,7 @@ def build(env):
90
105
91
106
target = env [":target" ].identifier
92
107
if target .platform == "stm32" :
93
- tusb_config ["CFG_TUSB_MCU" ] = "OPT_MCU_STM32{}" . format ( target .family .upper ())
108
+ tusb_config ["CFG_TUSB_MCU" ] = f "OPT_MCU_STM32{ target .family .upper ()} "
94
109
# TODO: use modm-devices driver type for this
95
110
fs_dev = (target .family in ["f0" , "f3" , "l0" , "g4" ] or
96
111
(target .family == "f1" and target .name <= "03" ))
@@ -106,7 +121,7 @@ def build(env):
106
121
env .copy ("tinyusb/src/portable/microchip/samg/" , "portable/microchip/samg/" )
107
122
else :
108
123
series = "e5x" if target .series .startswith ("e5" ) else target .series
109
- tusb_config ["CFG_TUSB_MCU" ] = "OPT_MCU_SAM{}" . format ( series .upper ())
124
+ tusb_config ["CFG_TUSB_MCU" ] = f "OPT_MCU_SAM{ series .upper ()} "
110
125
env .copy ("tinyusb/src/portable/microchip/samd/" , "portable/microchip/samd/" )
111
126
112
127
elif target .platform == "rp" :
@@ -130,21 +145,16 @@ def build(env):
130
145
tusb_config ["CFG_TUSB_DEBUG_PRINTF" ] = "tinyusb_debug_printf"
131
146
# env.collect(":build:cppdefines.debug", "CFG_TUSB_DEBUG=2")
132
147
133
- has_device = env .has_module (":tinyusb:device" )
134
- has_host = env .has_module (":tinyusb:host" )
135
- # On STM32 with both speeds, the option decides, otherwise we must default
136
- # to HS for the few STM32 with *only* usb_otg_hs, all other devices are FS.
137
- speed = env .get ("speed" , "hs" if env [":target" ].has_driver ("usb_otg_hs" ) else "fs" )
138
- port = 0 if speed == "fs" else 1
139
-
140
- tusb_config ["CFG_TUSB_RHPORT0_MODE" ] = "OPT_MODE_NONE"
141
- tusb_config ["CFG_TUSB_RHPORT1_MODE" ] = "OPT_MODE_NONE"
142
- mode = None
143
- if has_device : mode = "OPT_MODE_DEVICE" ;
144
- if has_host : mode = "OPT_MODE_HOST" ;
145
- if mode is not None :
146
- if "hs" in speed : mode = "({} | OPT_MODE_HIGH_SPEED)" .format (mode );
147
- tusb_config ["CFG_TUSB_RHPORT{}_MODE" .format (port )] = mode
148
+ ports = {}
149
+ global default_port
150
+ for mode in ["device" , "host" ]:
151
+ if env .has_module (f":tinyusb:{ mode } " ):
152
+ port = env .get (f":tinyusb:{ mode } :port" , default_port )
153
+ ports [port ] = mode [0 ]
154
+ mode = f"OPT_MODE_{ mode .upper ()} "
155
+ if port == 1 and (speed := env .get ("max-speed" )) is not None :
156
+ mode = f"({ mode } | OPT_MODE_{ speed .upper ()} _SPEED)"
157
+ tusb_config [f"CFG_TUSB_RHPORT{ port } _MODE" ] = mode
148
158
149
159
itf_config = env ["config" ]
150
160
# Enumerate the configurations
@@ -160,7 +170,7 @@ def build(env):
160
170
endpoints = {}
161
171
endpoint_counter = 0
162
172
for devclass , devitfs in config_enum .items ():
163
- prefix = "{}" . format ( devclass .upper () )
173
+ prefix = devclass .upper ()
164
174
for itf in devitfs :
165
175
endpoint_counter += 1
166
176
itf_prefix = prefix + str (itf )
@@ -169,10 +179,10 @@ def build(env):
169
179
itf_enum .extend ([itf_prefix , itf_prefix + "_DATA" ])
170
180
endpoints [itf_prefix + "_NOTIF" ] = hex (0x80 | endpoint_counter )
171
181
endpoint_counter += 1
172
- elif devclass in ["msc" , "midi" , "vendor" , "dfu " ]:
182
+ elif devclass in ["msc" , "midi" , "vendor" , "dfu_rt " ]:
173
183
itf_enum .append (itf_prefix )
174
184
else :
175
- raise ValidateException ("Unknown ITF device class '{}'!" . format ( devclass ) )
185
+ raise ValidateException (f "Unknown ITF device class '{ devclass } '!" )
176
186
177
187
endpoints [itf_prefix + "_OUT" ] = hex (endpoint_counter )
178
188
# SAMG doesn't support using the same EP number for IN and OUT
@@ -181,22 +191,26 @@ def build(env):
181
191
endpoints [itf_prefix + "_IN" ] = hex (0x80 | endpoint_counter )
182
192
183
193
if target .platform == "stm32" :
184
- irq_data = env .query (":platform:usb:irqs" )
185
- irqs = irq_data ["port_irqs" ][speed ]
194
+ irqs = []
195
+ for port , uirqs in env .query (":platform:usb:irqs" )["port_irqs" ].items ():
196
+ port = {"fs" : 0 , "hs" : 1 }[port ]
197
+ if port in ports :
198
+ irqs .extend ([(irq , port , ports [port ]) for irq in uirqs ])
186
199
elif target .platform == "sam" and target .family in ["g5x" ]:
187
- irqs = ["UDP" ]
200
+ irqs = [( "UDP" , 0 , "d" ) ]
188
201
elif target .platform == "sam" and target .family in ["d5x/e5x" ]:
189
- irqs = ["USB_OTHER" , "USB_SOF_HSOF" , "USB_TRCPT0" , "USB_TRCPT1" ]
202
+ irqs = [("USB_OTHER" , 0 , "d" ), ("USB_SOF_HSOF" , 0 , "d" ),
203
+ ("USB_TRCPT0" , 0 , "d" ), ("USB_TRCPT1" , 0 , "d" )]
190
204
elif target .platform == "rp" and target .family in ["20" ]:
191
- irqs = ["USBCTRL_IRQ" ]
205
+ irqs = [( "USBCTRL_IRQ" , 0 , "d" ) ]
192
206
else :
193
- irqs = ["USB" ]
207
+ irqs = [( "USB" , 0 , "d" ) ]
194
208
195
209
env .substitutions = {
196
210
"target" : target ,
197
211
"config" : tusb_config ,
198
212
"irqs" : irqs ,
199
- "port " : port ,
213
+ "ports " : ports ,
200
214
"with_debug" : env .has_module (":debug" ),
201
215
"with_cdc" : env .has_module (":tinyusb:device:cdc" ),
202
216
"itfs" : itfs ,
@@ -207,9 +221,24 @@ def build(env):
207
221
if len (itf_config ): env .template ("tusb_descriptors.c.in" );
208
222
env .template ("tusb_port.cpp.in" )
209
223
224
+ # Add support files shared between device and host classes
225
+ classes = env .generated_local_files (filterfunc = lambda path : "_device.c" in path or "_host.c" in path )
226
+ classes = {(Path (c ).parent .name , Path (c ).name .rsplit ("_" , 1 )[0 ]) for c in classes }
227
+ # Add support files outside of naming scheme
228
+ if any (p == "net" for p , _ in classes ): classes .update ({("net" , "ncm" ), ("net" , "net_device" )})
229
+ if ("dfu" , "dfu_rt" ) in classes : classes .add (("dfu" , "dfu" ))
230
+ # Filter out classes without support files
231
+ classes = ((p , n ) for p , n in classes if n not in ["bth" , "dfu_rt" , "vendor" , "ecm_rndis" , "ecm" ])
232
+ # Now copy the shared support file
233
+ for parent , name in classes :
234
+ env .copy (f"tinyusb/src/class/{ parent } /{ name } .h" , f"class/{ parent } /{ name } .h" )
235
+
210
236
211
237
# -----------------------------------------------------------------------------
212
238
class TinyUsbDeviceModule (Module ):
239
+ def __init__ (self , ports ):
240
+ self .ports = ports
241
+
213
242
def init (self , module ):
214
243
module .name = ":tinyusb:device"
215
244
module .description = """
@@ -223,20 +252,31 @@ Configuration options:
223
252
"""
224
253
225
254
def prepare (self , module , options ):
226
- paths = {p .parent for p in Path (localpath ("tinyusb/src/class/" )).glob ("*/*_device.h" )}
227
- for path in paths :
228
- module .add_submodule (TinyUsbClassModule (path , "device" ))
255
+ classes = {(p .parent .name , p .name .replace ("_device.c" , "" ))
256
+ for p in Path (localpath ("tinyusb/src/class/" )).glob ("*/*_device.c" )}
257
+ for parent , name in classes :
258
+ module .add_submodule (TinyUsbClassModule (parent , name , "device" ))
259
+ if self .ports == 2 :
260
+ module .add_option (
261
+ EnumerationOption (name = "port" , description = "USB Port selection" ,
262
+ enumeration = {"fs" : 0 , "hs" : 1 }, default = "fs" ,
263
+ dependencies = lambda s : f":platform:usb:{ s [0 ]} s" ))
229
264
return True
230
265
266
+ # def validate(self, env):
267
+ # if env.has_module(":tinyusb:device:ncm") and env.has_module(":tinyusb:device:ecm_rndis"):
268
+ # raise ValidateException("TinyUSB cannot enable both ECM_RNDIS and NCM network drivers!")
269
+
231
270
def build (self , env ):
232
271
env .outbasepath = "modm/ext/tinyusb"
233
272
env .copy ("tinyusb/src/device/" , "device/" )
234
273
235
274
236
275
# -----------------------------------------------------------------------------
237
276
class TinyUsbHostModule (Module ):
238
- def __init__ (self ):
277
+ def __init__ (self , ports ):
239
278
self .config = {}
279
+ self .ports = ports
240
280
241
281
def init (self , module ):
242
282
module .name = ":tinyusb:host"
@@ -257,9 +297,15 @@ Configuration options:
257
297
options [":target" ].has_driver ("uhp:samg*" )):
258
298
return False
259
299
260
- paths = {p .parent for p in Path (localpath ("tinyusb/src/class/" )).glob ("*/*_host.h" )}
261
- for path in paths :
262
- module .add_submodule (TinyUsbClassModule (path , "host" ))
300
+ classes = {(p .parent .name , p .name .replace ("_host.c" , "" ))
301
+ for p in Path (localpath ("tinyusb/src/class/" )).glob ("*/*_host.c" )}
302
+ for parent , name in classes :
303
+ module .add_submodule (TinyUsbClassModule (parent , name , "host" ))
304
+ if self .ports == 2 :
305
+ module .add_option (
306
+ EnumerationOption (name = "port" , description = "USB Port selection" ,
307
+ enumeration = {"fs" : 0 , "hs" : 1 }, default = "hs" ,
308
+ dependencies = lambda s : f":platform:usb:{ s [0 ]} s" ))
263
309
return True
264
310
265
311
def build (self , env ):
@@ -270,6 +316,9 @@ Configuration options:
270
316
# -----------------------------------------------------------------------------
271
317
# Only contains those configurations that this modules can generate a descriptor for
272
318
tu_config_descr = {"device" : {
319
+ "bth" : """
320
+ - `CFG_TUD_BTH_ISO_ALT_COUNT` = [undef] modm default: 1
321
+ """ ,
273
322
"cdc" : """
274
323
- `CFG_TUD_CDC_EP_BUFSIZE` = 64/512 (fs/hs)
275
324
- `CFG_TUD_CDC_RX_BUFSIZE` = [undef] modm default: 512
@@ -291,13 +340,14 @@ tu_config_descr = {"device": {
291
340
}
292
341
}
293
342
class TinyUsbClassModule (Module ):
294
- def __init__ (self , path , mode ):
343
+ def __init__ (self , parent , name , mode ):
344
+ self .parent = parent
345
+ self .name = name
295
346
self .mode = mode
296
- self .name = str (path .name )
297
347
298
348
def init (self , module ):
299
- module .name = ":tinyusb:{}:{}" . format ( self .mode , self . name )
300
- descr = "# {} class {}" . format ( self .mode .capitalize (), self .name .upper ())
349
+ module .name = f ":tinyusb:{ self . mode } :{ self .name } "
350
+ descr = f "# { self .mode .capitalize ()} class { self .name .upper ()} "
301
351
conf = tu_config_descr .get (self .mode , {}).get (self .name , "" )
302
352
if conf : descr += "\n \n Configuration options:\n " + conf
303
353
else : descr += "\n \n Please consult the TinyUSB docs for configuration options.\n "
@@ -314,19 +364,25 @@ class TinyUsbClassModule(Module):
314
364
315
365
def build (self , env ):
316
366
env .outbasepath = "modm/ext/tinyusb"
317
- env .copy ("tinyusb/src/class/{}" .format (self .name ), "class/{}" .format (self .name ),
318
- ignore = env .ignore_files ("*_host.*" if "device" in self .mode else "*_device.*" ))
367
+ for suffix in ["h" , "c" ]:
368
+ # both classes share the `net_device.h` file
369
+ if self .name in ["ncm" , "ecm_rndis" ] and suffix == "h" :
370
+ continue
371
+ file = f"class/{ self .parent } /{ self .name } _{ self .mode } .{ suffix } "
372
+ env .copy (f"tinyusb/src/{ file } " , file )
319
373
320
374
global tusb_config
321
- cfg_name = "CFG_TU{}_{}" . format ( self .mode [0 ].upper (), self .name .upper ())
322
- if "DFU " in cfg_name : cfg_name += " _RUNTIME";
323
- tusb_config [cfg_name ] = env [":tinyusb:config" ].count ("{}.{}" . format ( self .mode , self . name ) )
324
- speed = env .get (":tinyusb:speed" , 0 )
375
+ cfg_name = f "CFG_TU{ self .mode [0 ].upper ()} _ { self .name .upper ()} "
376
+ if "DFU_RT " in cfg_name : cfg_name = cfg_name . replace ( "_RT" , " _RUNTIME")
377
+ tusb_config [cfg_name ] = env [":tinyusb:config" ].count (f" { self . mode } . { self .name } " )
378
+ is_hs = env .get (":tinyusb:max- speed" , "full" ) == "high"
325
379
326
380
if self .mode == "device" :
327
381
# These are the defaults that don't crash TinyUSB.
328
382
if self .name in ["cdc" , "midi" , "vendor" ]:
329
- tusb_config [cfg_name + "_RX_BUFSIZE" ] = 512 if speed else 64
330
- tusb_config [cfg_name + "_TX_BUFSIZE" ] = 512 if speed else 64
383
+ tusb_config [cfg_name + "_RX_BUFSIZE" ] = 512 if is_hs else 64
384
+ tusb_config [cfg_name + "_TX_BUFSIZE" ] = 512 if is_hs else 64
331
385
if self .name in ["msc" ]:
332
386
tusb_config [cfg_name + "_EP_BUFSIZE" ] = 512
387
+ if self .name in ["bth" ]:
388
+ tusb_config [cfg_name + "_ISO_ALT_COUNT" ] = 1
0 commit comments