From 136827e6dad4b9e23d211acbbcef61e2ffc827e5 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Wed, 14 Sep 2022 09:03:56 +0200 Subject: [PATCH] pkg: add tinyUSB device/host stack as package --- Makefile.features | 3 + drivers/Makefile.dep | 4 + drivers/periph_common/Kconfig | 12 + pkg/Kconfig | 1 + pkg/tinyusb/Kconfig | 138 ++++++++ pkg/tinyusb/Makefile | 69 ++++ pkg/tinyusb/Makefile.dep | 61 ++++ pkg/tinyusb/Makefile.include | 11 + pkg/tinyusb/contrib/Makefile | 3 + pkg/tinyusb/contrib/include/tinyusb.h | 62 ++++ pkg/tinyusb/contrib/include/tinyusb_config.h | 306 ++++++++++++++++++ pkg/tinyusb/contrib/include/tusb_os_custom.h | 255 +++++++++++++++ pkg/tinyusb/contrib/tinyusb.c | 81 +++++ pkg/tinyusb/doc.txt | 79 +++++ pkg/tinyusb/hw/Makefile | 5 + pkg/tinyusb/hw/include/tinyusb_hw.h | 41 +++ ...1-src-portable-espressif-fix-include.patch | Bin 0 -> 801 bytes ...opsys-define-SystemCoreClock-variabl.patch | Bin 0 -> 1513 bytes 18 files changed, 1131 insertions(+) create mode 100644 pkg/tinyusb/Kconfig create mode 100644 pkg/tinyusb/Makefile create mode 100644 pkg/tinyusb/Makefile.dep create mode 100644 pkg/tinyusb/Makefile.include create mode 100644 pkg/tinyusb/contrib/Makefile create mode 100644 pkg/tinyusb/contrib/include/tinyusb.h create mode 100644 pkg/tinyusb/contrib/include/tinyusb_config.h create mode 100644 pkg/tinyusb/contrib/include/tusb_os_custom.h create mode 100644 pkg/tinyusb/contrib/tinyusb.c create mode 100644 pkg/tinyusb/doc.txt create mode 100644 pkg/tinyusb/hw/Makefile create mode 100644 pkg/tinyusb/hw/include/tinyusb_hw.h create mode 100644 pkg/tinyusb/patches/0001-src-portable-espressif-fix-include.patch create mode 100644 pkg/tinyusb/patches/0002-src-portable-synopsys-define-SystemCoreClock-variabl.patch diff --git a/Makefile.features b/Makefile.features index 62499d2a2c..086d677bd8 100644 --- a/Makefile.features +++ b/Makefile.features @@ -27,3 +27,6 @@ FEATURES_CONFLICT_MSG += "Only one standard C library can be used" FEATURES_CONFLICT += periph_gpio_irq:periph_gpio_ll_irq FEATURES_CONFLICT_MSG += "Only one GPIO IRQ implementation can be used" + +FEATURES_CONFLICT += periph_usbdev:tinyusb +FEATURES_CONFLICT_MSG += "Package tinyUSB is not yet compatible with periph/usdev" diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 09f99693d3..239c53f5d3 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -137,6 +137,10 @@ ifneq (,$(filter periph_ptp_timer periph_ptp_speed_adjustment,$(FEATURES_USED))) FEATURES_REQUIRED += periph_ptp endif +ifneq (,$(filter periph_usbdev,$(USEMODULE))) + USEMODULE += periph_usbdev_clk +endif + ifneq (,$(filter pn532_i2c,$(USEMODULE))) FEATURES_REQUIRED += periph_i2c USEMODULE += pn532 diff --git a/drivers/periph_common/Kconfig b/drivers/periph_common/Kconfig index 4add675808..f0b90862fc 100644 --- a/drivers/periph_common/Kconfig +++ b/drivers/periph_common/Kconfig @@ -161,12 +161,24 @@ config MODULE_PERIPH_USBDEV bool "USBDEV peripheral driver" depends on HAS_PERIPH_USBDEV select MODULE_PERIPH_COMMON + select MODULE_PERIPH_USBDEV_CLK config MODULE_PERIPH_INIT_USBDEV bool "Auto initialize USBDEV peripheral" default y if MODULE_PERIPH_INIT depends on MODULE_PERIPH_USBDEV +config MODULE_PERIPH_USBDEV_CLK + bool + depends on HAS_PERIPH_USBDEV + help + Enable the USB device specific clock settings, if there are any + +config MODULE_PERIPH_INIT_USBDEV_CLK + bool + default y if MODULE_PERIPH_INIT && MODULE_PERIPH_USBDEV_CLK + depends on HAS_PERIPH_USBDEV + endif # TEST_KCONFIG config HAVE_SHARED_PERIPH_RTT_PERIPH_RTC diff --git a/pkg/Kconfig b/pkg/Kconfig index d983bf8782..4f0269d1a5 100644 --- a/pkg/Kconfig +++ b/pkg/Kconfig @@ -67,6 +67,7 @@ rsource "tiny-asn1/Kconfig" rsource "tinycbor/Kconfig" rsource "tinycrypt/Kconfig" rsource "tinydtls/Kconfig" +rsource "tinyusb/Kconfig" rsource "tinyvcdiff/Kconfig" rsource "tlsf/Kconfig" rsource "tweetnacl/Kconfig" diff --git a/pkg/tinyusb/Kconfig b/pkg/tinyusb/Kconfig new file mode 100644 index 0000000000..a7ab07e9e9 --- /dev/null +++ b/pkg/tinyusb/Kconfig @@ -0,0 +1,138 @@ +# Copyright (c) 2021 HAW Hamburg +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +config HAS_TINYUSB_DEVICE + bool + help + Indicates that the hardware supports tinyUSB device stack + +config HAS_TINYUSB_HOST + bool + help + Indicates that the hardware supports tinyUSB host stack + +menuconfig PACKAGE_TINYUSB + bool "TinyUSB stack package" + depends on HAS_ARCH_32BIT + depends on HAS_TINYUSB_DEVICE || HAS_TINYUSB_HOST + select MODULE_PERIPH_USBDEV_CLK + select MODULE_SEMA + select MODULE_TINYUSB_COMMON + select MODULE_TINYUSB_CONTRIB + select MODULE_TINYUSB_HW + select MODULE_TINYUSB_PORTABLE_ESPRESSIF if CPU_FAM_ESP32S2 || CPU_FAM_ESP32S3 + select MODULE_TINYUSB_PORTABLE_SYNOPSYS_DWC2 if CPU_STM32 && CPU_FAM_F2 + select MODULE_TINYUSB_PORTABLE_SYNOPSYS_DWC2 if CPU_STM32 && CPU_FAM_F4 + select MODULE_TINYUSB_PORTABLE_SYNOPSYS_DWC2 if CPU_STM32 && CPU_FAM_F7 + select MODULE_TINYUSB_PORTABLE_SYNOPSYS_DWC2 if CPU_STM32 && CPU_FAM_H7 + select MODULE_TINYUSB_PORTABLE_SYNOPSYS_DWC2 if CPU_STM32 && CPU_FAM_L4 + select MODULE_TINYUSB_PORTABLE_STM32_FSEDV if CPU_STM32 && CPU_FAM_F0 + select MODULE_TINYUSB_PORTABLE_STM32_FSEDV if CPU_STM32 && CPU_FAM_F1 + select MODULE_TINYUSB_PORTABLE_STM32_FSEDV if CPU_STM32 && CPU_FAM_G4 + select MODULE_TINYUSB_PORTABLE_STM32_FSEDV if CPU_STM32 && CPU_FAM_L0 + select MODULE_TINYUSB_PORTABLE_STM32_FSEDV if CPU_STM32 && CPU_FAM_WB + select MODULE_ZTIMER_MSEC + help + tinyUSB is an open-source cross-platform USB Host/Device stack for + embedded systems. + +if PACKAGE_TINYUSB + +config MODULE_TINYUSB_COMMON + bool + help + Common tinyUSB files + +config MODULE_TINYUSB_CONTRIB + bool + help + RIOT support for tinyUSB + +config MODULE_TINYUSB_HW + bool + help + tinyUSB hardware driver implementation + +config MODULE_TINYUSB_DEVICE + bool "Device Stack" + depends on HAS_TINYUSB_DEVICE + help + Select to enable tinyUSB device stack + +config MODULE_TINYUSB_HOST + bool "Host Stack" + depends on HAS_TINYUSB_HOST + help + Select to enable tinyUSB host stack + +config MODULE_TINYUSB_PORTABLE_ESPRESSIF + bool + help + tinyUSB driver for ESP32Sx is used + +config MODULE_TINYUSB_PORTABLE_SYNOPSYS_DWC2 + bool + help + tinyUSB Sysnopsys DCW2 driver is used + +config MODULE_TINYUSB_PORTABLE_STM32_FSDEV + bool + help + tinyUSB STM32 FS device driver is used + +menu "Device Classes" + config MODULE_TINYUSB_CLASS_AUDIO + bool "Audio Class 2.0 (UAC2)" + depends on MODULE_TINYUSB_DEVICE + + config MODULE_TINYUSB_CLASS_BTH + bool "Bluetooth Host Controller Interface (BTH HCI)" + depends on MODULE_TINYUSB_DEVICE + + config MODULE_TINYUSB_CLASS_CDC + bool "Communication Device Class (CDC)" + + config MODULE_TINYUSB_CLASS_DFU + bool "Device Firmware Update (DFU) Runtime" + depends on MODULE_TINYUSB_DEVICE + + config MODULE_TINYUSB_CLASS_DFU_RUNTIME + bool "Device Firmware Update (DFU)" + depends on MODULE_TINYUSB_DEVICE + + config MODULE_TINYUSB_CLASS_HID + bool "Human Interface Device (HID)" + + config MODULE_TINYUSB_CLASS_MSC + bool "Mass Storage Class (MSC)" + + config MODULE_TINYUSB_CLASS_MIDI + bool "Musical Instrument Digital Interface (MIDI)" + depends on MODULE_TINYUSB_DEVICE + + config MODULE_TINYUSB_CLASS_NET_ECM_RNDIS + bool "Network with RNDIS, Ethernet Control Model (ECM)" + depends on MODULE_TINYUSB_DEVICE + + config MODULE_TINYUSB_CLASS_NET_NCM + bool "Network with Network Control Model (NCM)" + depends on MODULE_TINYUSB_DEVICE + + config MODULE_TINYUSB_CLASS_USBTMC + bool "Test and Measurement Class (USBTMC)" + depends on MODULE_TINYUSB_DEVICE + + config MODULE_TINYUSB_CLASS_VENDOR + bool "Vendor-specific class support with generic IN & OUT endpoints" + + config MODULE_TINYUSB_CLASS_VIDEO + bool "Video class 1.5 (UVC)" + depends on MODULE_TINYUSB_DEVICE + +endmenu + +endif # PACKAGE_TINYUSB diff --git a/pkg/tinyusb/Makefile b/pkg/tinyusb/Makefile new file mode 100644 index 0000000000..af9b4ef35d --- /dev/null +++ b/pkg/tinyusb/Makefile @@ -0,0 +1,69 @@ +PKG_NAME=tinyusb +PKG_URL=https://github.com/hathach/tinyusb +# TinyUSB 0.14.0 +PKG_VERSION=9e91b02ec7fb3502747b5c413a4824654fa7fc7e + +PKG_LICENSE=MIT + +include $(RIOTBASE)/pkg/pkg.mk + +PSRC = $(PKG_SOURCE_DIR)/src + +.PHONY: all + +all: $(filter tinyusb_%,$(USEMODULE)) + $(QQ)"$(MAKE)" -C $(PSRC) -f $(RIOTBASE)/Makefile.base MODULE=tinyusb + +tinyusb_contrib: + $(QQ)"$(MAKE)" -C $(RIOTPKG)/$(PKG_NAME)/contrib + +tinyusb_hw: + $(QQ)"$(MAKE)" -C $(RIOTPKG)/$(PKG_NAME)/hw + +tinyusb_class_audio: + $(QQ)"$(MAKE)" -C $(PSRC)/class/audio -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_bth: + $(QQ)"$(MAKE)" -C $(PSRC)/class/bth -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_cdc: + $(QQ)"$(MAKE)" -C $(PSRC)/class/cdc -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_dfu: + $(QQ)"$(MAKE)" -C $(PSRC)/class/dfu -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_dfu_runtime: + $(QQ)"$(MAKE)" -C $(PSRC)/class/dfu -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_hid: + $(QQ)"$(MAKE)" -C $(PSRC)/class/hid -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_midi: + $(QQ)"$(MAKE)" -C $(PSRC)/class/midi -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_msc: + $(QQ)"$(MAKE)" -C $(PSRC)/class/msc -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_net_ecm_rndis: + $(QQ)"$(MAKE)" -C $(PSRC)/class/net -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_net_ncm: + $(QQ)"$(MAKE)" -C $(PSRC)/class/net -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_usbtmc: + $(QQ)"$(MAKE)" -C $(PSRC)/class/usbtmc -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_vendor: + $(QQ)"$(MAKE)" -C $(PSRC)/class/vendor -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_class_video: + $(QQ)"$(MAKE)" -C $(PSRC)/class/video -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_common: + $(QQ)"$(MAKE)" -C $(PSRC)/common -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_device: + $(QQ)"$(MAKE)" -C $(PSRC)/device -f $(RIOTBASE)/Makefile.base MODULE=$@ + +tinyusb_host: + $(QQ)"$(MAKE)" -C $(PSRC)/host -f $(RIOTBASE)/Makefile.base MODULE=$@ diff --git a/pkg/tinyusb/Makefile.dep b/pkg/tinyusb/Makefile.dep new file mode 100644 index 0000000000..564c3782db --- /dev/null +++ b/pkg/tinyusb/Makefile.dep @@ -0,0 +1,61 @@ +# Package tinyUSB has its own USB device driver. Therefore, it cannot be used +# together with periph/usbdev for now. +ifneq (,$(filter periph_usbdev,$(USEMODULE))) + $(error "Package tinyUSB is not yet compatible with periph/usdev") +endif + +# tinyUSB muteces use priority inheritance +# USEMODULE += core_mutex_priority_inheritance + +# tinyUSB modules always needed +USEMODULE += tinyusb_common +USEMODULE += tinyusb_contrib +USEMODULE += tinyusb_hw + +ifeq (,$(filter tinyusb_class_%,$(USEMODULE))) + $(error At least one tinyusb_class_* module has to be enabled) +endif + +# tinyUSB device stack has to be supported if tinyusb_device is used +ifneq (,$(filter tinyusb_device,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_device +endif + +# tinyUSB host stack has to be supported if tinyusb_host is used +ifneq (,$(filter tinyusb_host,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_host +endif + +# Following device classes work only with tinyUSB device stack +ifneq (,$(filter tinyusb_class_audio,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_device +endif +ifneq (,$(filter tinyusb_class_bth,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_device +endif +ifneq (,$(filter tinyusb_class_dfu,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_device +endif +ifneq (,$(filter tinyusb_class_dfu_runtime,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_device +endif +ifneq (,$(filter tinyusb_class_midi,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_device +endif +ifneq (,$(filter tinyusb_class_net_ecm_rndis,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_device +endif +ifneq (,$(filter tinyusb_class_net_ncm,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_device +endif +ifneq (,$(filter tinyusb_class_usbtmc,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_device +endif +ifneq (,$(filter tinyusb_class_video,$(USEMODULE))) + FEATURES_REQUIRED += tinyusb_device +endif + +# other module dependencies +USEMODULE += periph_usbdev_clk +USEMODULE += sema +USEMODULE += ztimer_msec diff --git a/pkg/tinyusb/Makefile.include b/pkg/tinyusb/Makefile.include new file mode 100644 index 0000000000..0055efc30f --- /dev/null +++ b/pkg/tinyusb/Makefile.include @@ -0,0 +1,11 @@ +INCLUDES += -I$(RIOTBASE)/pkg/tinyusb/contrib +INCLUDES += -I$(RIOTBASE)/pkg/tinyusb/contrib/include +INCLUDES += -I$(RIOTBASE)/pkg/tinyusb/hw/include +INCLUDES += -I$(PKGDIRBASE)/tinyusb/src + +CFLAGS += -DCFG_TUSB_OS=OPT_OS_CUSTOM +CFLAGS += -Wno-format-nonliteral + +ifneq (,$(filter tinyusb_class_net_ecm_rndis,$(USEMODULE))) + INCLUDES += -I$(PKGDIRBASE)/tinyusb/lib/networking +endif diff --git a/pkg/tinyusb/contrib/Makefile b/pkg/tinyusb/contrib/Makefile new file mode 100644 index 0000000000..15d741bf25 --- /dev/null +++ b/pkg/tinyusb/contrib/Makefile @@ -0,0 +1,3 @@ +MODULE = tinyusb_contrib + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/tinyusb/contrib/include/tinyusb.h b/pkg/tinyusb/contrib/include/tinyusb.h new file mode 100644 index 0000000000..8667429a9b --- /dev/null +++ b/pkg/tinyusb/contrib/include/tinyusb.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup pkg_tinyusb + * @{ + * + * @file + * @brief TinyUSB API + * + * @author Gunar Schorcht + */ + +#ifndef TINYUSB_H +#define TINYUSB_H + +#include "periph_conf.h" + +#ifndef TINYUSB_THREAD_STACKSIZE_MAIN +/** Stack size used for the tinyUSB thread */ +#define TINYUSB_THREAD_STACKSIZE (THREAD_STACKSIZE_DEFAULT) +#endif + +#ifndef TINYUSB_PRIORITY +/** Priority used for the tinyUSB thread */ +#define TINYUSB_PRIORITY (2) +#endif + +#ifndef TINYUSB_TUD_RHPORT +/** tinyUSB RHPort number used for device, default value is 0 */ +#define TINYUSB_TUD_RHPORT 0 +#endif + +#ifndef TINYUSB_TUH_RHPORT +/** tinyUSB RHPort number used for host, defaults value is 0 */ +#define TINYUSB_TUH_RHPORT 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the tinyUSB stack including used peripherals and start the tinyUSB thread + * + * @return 0 on success + * @return -ENODEV if peripherals couldn't be initialized + * @return -EINVAL or -EOVERFLOW if the thread couldn't be created + */ +int tinyusb_setup(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TINYUSB_H */ +/** @} */ diff --git a/pkg/tinyusb/contrib/include/tinyusb_config.h b/pkg/tinyusb/contrib/include/tinyusb_config.h new file mode 100644 index 0000000000..55ec032e98 --- /dev/null +++ b/pkg/tinyusb/contrib/include/tinyusb_config.h @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2019 Ha Thach (tinyusb.org) + * 2022 Gunar Schorcht + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @ingroup pkg_tinyusb + * @{ + * + * @file + * @brief TinyUSB default configurations + * + * @author Gunar Schorcht + */ + +#ifndef TINYUSB_CONFIG_H +#define TINYUSB_CONFIG_H + +#include "tusb_config.h" /* defined by the application */ +#include "tinyusb.h" + +#if !DOXYGEN + +/** + * @name Common tinyUSB configurations + * @{ + */ +/** Sanity check, the MCU must be defined */ +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +/** RIOT provides the custom OS API */ +#define CFG_TUSB_OS OPT_OS_CUSTOM + +/** Debug log level */ +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +/** + * @brief DMA memory section and alignment + * + * USB DMA on some MCUs can only access a specific SRAM region with restriction + * on alignment. + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +/** @} */ + +/** + * @name Common device configurations + * @{ + */ +#define CFG_TUD_ENABLED MODULE_TINYUSB_DEVICE + +#ifndef CFG_TUD_MAX_SPEED +#define CFG_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +#ifndef CFG_TUD_AUDIO +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_AUDIO) +#define CFG_TUD_AUDIO 1 +#else +#define CFG_TUD_AUDIO 0 +#endif +#endif + +#ifndef CFG_TUD_BTH +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_BTH) +#define CFG_TUD_BTH 1 +#else +#define CFG_TUD_BTH 0 +#endif +#endif /* CFG_TUD_BTH */ + +#ifndef CFG_TUD_CDC +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_CDC) +#define CFG_TUD_CDC 1 +#else +#define CFG_TUD_CDC 0 +#endif +#endif /* CFG_TUD_CDC */ + +#ifndef CFG_TUD_DFU +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_DFU) +#define CFG_TUD_DFU 1 +#else +#define CFG_TUD_DFU 0 +#endif +#endif /* CFG_TUD_DFU */ + +#ifndef CFG_TUD_DFU_RUNTIME +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_DFU_RUNTIME) +#define CFG_TUD_DFU_RUNTIME 1 +#else +#define CFG_TUD_DFU_RUNTIME 0 +#endif +#endif /* CFG_TUD_DFU */ + +#ifndef CFG_TUD_HID +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_HID) +#define CFG_TUD_HID 1 +#else +#define CFG_TUD_HID 0 +#endif +#endif /* CFG_TUD_HID */ + +#ifndef CFG_TUD_MIDI +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_MIDI) +#define CFG_TUD_MIDI 1 +#else +#define CFG_TUD_MIDI 0 +#endif +#endif /* CFG_TUD_MIDI */ + +#ifndef CFG_TUD_MSC +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_MSC) +#define CFG_TUD_MSC 1 +#else +#define CFG_TUD_MSC 0 +#endif +#endif /* CFG_TUD_MSC */ + +#ifndef CFG_TUD_ECM_RNDIS +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_NET_ECM_RNDIS) +#define CFG_TUD_ECM_RNDIS 1 +#else +#define CFG_TUD_ECM_RNDIS 0 +#endif +#endif /* CFG_TUD_ECM_RNDIS */ + +#ifndef CFG_TUD_NCM +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_NET_NCM) +#define CFG_TUD_NCM 1 +#else +#define CFG_TUD_NCM 0 +#endif +#endif /* CFG_TUD_NCM */ + +#ifndef CFG_TUD_USBMTC +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_USBMTC) +#define CFG_TUD_USBMTC 1 +#else +#define CFG_TUD_USBMTC 0 +#endif +#endif /* CFG_TUD_USBMTC */ + +#ifndef CFG_TUD_VENDOR +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_VENDOR) +#define CFG_TUD_VENDOR 1 +#else +#define CFG_TUD_VENDOR 0 +#endif +#endif /* CFG_TUD_VENDOR */ + +#ifndef CFG_TUD_VIDEO +#if defined(MODULE_TINYUSB_DEVICE) && defined(MODULE_TINYUSB_CLASS_VIDEO) +#define CFG_TUD_VIDEO 1 +#else +#define CFG_TUD_VIDEO 0 +#endif +#endif /* CFG_TUD_VIDEO */ + +/** @} */ + +/** + * @name Common host configurations + * @{ + */ +#define CFG_TUH_ENABLED MODULE_TINYUSB_HOST + +#ifndef CFG_TUH_MAX_SPEED +#define CFG_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +#ifndef CFG_TUH_ENUMERATION_BUFSIZE +#define CFG_TUH_ENUMERATION_BUFSIZE 256 +#endif + +/** Hub typically has 4 ports */ +#ifndef CFG_TUH_DEVICE_MAX +#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) +#endif + +#ifndef CFG_TUH_CDC +#if defined(MODULE_TINYUSB_HOST) && defined(MODULE_TINYUSB_CLASS_CDC) +#define CFG_TUH_CDC 1 +#else +#define CFG_TUH_CDC 0 +#endif +#endif /* CFG_TUH_CDC */ + +#ifndef CFG_TUH_HID +#if defined(MODULE_TINYUSB_HOST) && defined(MODULE_TINYUSB_CLASS_HID) +#define CFG_TUH_HID 1 +#else +#define CFG_TUH_HID 0 +#endif +#endif /* CFG_TUH_HID */ + +#ifndef CFG_TUH_MSC +#if defined(MODULE_TINYUSB_HOST) && defined(MODULE_TINYUSB_CLASS_MSC) +#define CFG_TUH_MSC 1 +#else +#define CFG_TUH_MSC 0 +#endif +#endif /* CFG_TUH_MSC */ + +#ifndef CFG_TUD_VENDOR +#if defined(MODULE_TINYUSB_HOST) && defined(MODULE_TINYUSB_CLASS_VENDOR) +#define CFG_TUH_VENDOR 1 +#else +#define CFG_TUH_VENDOR 0 +#endif +#endif /* CFG_TUD_VENDOR */ + +/** @} */ + +/** + * @name Typical required CDC device class configurations + * @{ + */ +/** CDC RX FIFO size */ +#ifndef CFG_TUD_CDC_RX_BUFSIZE +#define CFG_TUD_CDC_RX_BUFSIZE CFG_TUD_CDC_EP_BUFSIZE +#endif + +/** CDC RX FIFO size */ +#ifndef CFG_TUD_CDC_TX_BUFSIZE +#define CFG_TUD_CDC_TX_BUFSIZE CFG_TUD_CDC_EP_BUFSIZE +#endif + +/** @} */ + +/** + * @name Typical required DFU device class configurations + * @{ + */ +#ifndef CFG_TUD_DFU_XFER_BUFSIZE +#define CFG_TUD_DFU_XFER_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#endif +/** @} */ + +/** + * @name Typical required MIDI device class configurations + * @{ + */ +#ifndef CFG_TUD_MIDI_RX_BUFSIZE +#define CFG_TUD_MIDI_RX_BUFSIZE CFG_TUD_MIDI_EP_BUFSIZE +#endif + +#ifndef CFG_TUD_MIDI_TX_BUFSIZE +#define CFG_TUD_MIDI_TX_BUFSIZE CFG_TUD_MIDI_EP_BUFSIZE +#endif +/** @} */ + +/** + * @name Typical required MSC device class configurations + * @{ + */ +#ifndef CFG_TUD_MSC_EP_BUFSIZE +#define CFG_TUD_MSC_EP_BUFSIZE 512 +#endif +/** @} */ +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* !DOXYGEN */ +#endif /* TINYUSB_CONFIG_H */ +/** @} */ diff --git a/pkg/tinyusb/contrib/include/tusb_os_custom.h b/pkg/tinyusb/contrib/include/tusb_os_custom.h new file mode 100644 index 0000000000..930170aade --- /dev/null +++ b/pkg/tinyusb/contrib/include/tusb_os_custom.h @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup pkg_tinyusb + * @{ + * + * @file + * @brief TinyUSB OS Abstraction Layer for RIOT + * + * @author Gunar Schorcht + */ + +#ifndef TUSB_OS_CUSTOM_H +#define TUSB_OS_CUSTOM_H + +#include "mutex.h" +#include "sema.h" +#include "ztimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if !DOXYGEN + +/* set to 1 to use cancelable mutex in osal_mutex and osal_queue */ +#ifndef TINYUSB_OSAL_MUTEX_CANCELABLE +#define TINYUSB_OSAL_MUTEX_CANCELABLE 0 +#endif + +/** + * Task API + */ + +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) +{ + ztimer_sleep(ZTIMER_MSEC, msec); +} + +/** + * Semaphore API + * + * This API is only used by RNDIS-CDC. + */ + +typedef sema_t osal_semaphore_def_t; /* semaphore */ +typedef sema_t *osal_semaphore_t; /* semaphore handle */ + +TU_ATTR_ALWAYS_INLINE +static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) +{ + sema_create(semdef, 0); + return (osal_semaphore_t)semdef; +} + +TU_ATTR_ALWAYS_INLINE +static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) +{ + (void)in_isr; /* hasn't to be considered since RIOT doesn't block in sema_post */ + return sema_post(sem_hdl) == 0; +} + +TU_ATTR_ALWAYS_INLINE +static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) +{ + assert(0); + return sema_wait_timed_ztimer(sem_hdl, ZTIMER_MSEC, msec) == 0; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) +{ + (void)sem_hdl; + /* TODO, function seems to be removed anyway */ +} + +/** Mutex API (priority inheritance) */ +typedef mutex_t osal_mutex_def_t; /* RIOT mutex */ +typedef mutex_t *osal_mutex_t; /* RIOT mutex handle */ + +TU_ATTR_ALWAYS_INLINE +static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) +{ + assert(mdef != NULL); + mutex_init((mutex_t *)mdef); + return (osal_mutex_t)mdef; +} + +#if TINYUSB_OSAL_MUTEX_CANCELABLE +static void _osal_mutex_lock_timeout(void *arg) +{ + mutex_cancel(arg); +} +#endif + +TU_ATTR_ALWAYS_INLINE +static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) +{ + assert(mutex_hdl); +#if TINYUSB_OSAL_MUTEX_CANCELABLE + mutex_cancel_t _mc = mutex_cancel_init(mutex_hdl); + + ztimer_t _timer = { .callback = _osal_mutex_lock_timeout, .arg = &_mc }; + ztimer_set(ZTIMER_MSEC, &_timer, msec); + + return mutex_lock_cancelable(&_mc) == 0; +#else + assert(msec == OSAL_TIMEOUT_WAIT_FOREVER); + mutex_lock(mutex_hdl); + return true; +#endif +} + +TU_ATTR_ALWAYS_INLINE +static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) +{ + assert(mutex_hdl); + mutex_unlock(mutex_hdl); + return true; +} + +/** + * Queue API + */ + +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ + /* _int_set is not used in RTOS */ \ + static _type _name##_##q_items[_depth]; /* queue item data storage */ \ + osal_queue_def_t _name = { \ + .buffer = _name##_##q_items, \ + .size = sizeof(_type), \ + .depth = _depth, \ + }; + +typedef struct { + list_node_t node; + void *data; +} osal_queue_entry; + +typedef struct { + void *buffer; /* buffer used for queue item data */ + uint16_t size; /* queue item size */ + uint16_t depth; /* maximum number of queue items */ + uint16_t front; + uint16_t tail; + sema_t smphr; /* semaphore value represents the queue fill level */ +} osal_queue_def_t; + +typedef osal_queue_def_t *osal_queue_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) +{ + assert(qdef != NULL); + + qdef->front = 0; + qdef->tail = 0; + + sema_create(&qdef->smphr, 0); + + return (osal_queue_t)qdef; +} + +TU_ATTR_ALWAYS_INLINE +static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr) +{ + assert(qhdl != NULL); + assert(data != NULL); + + if (sema_get_value(&qhdl->smphr) == qhdl->depth) { + /* queue is full */ + if (in_isr) { + /* return in case of ISR */ + return false; + } + /* We do not block the sending thread when `osal_queue_send` is called + * from a thread context and the queue is full. The call of function + * `osal_queue_send` is usually interrupt-driven and must not block + * anyway. There is only one exception in `src/class/msc/msc_device.c` + * where it is called in thread context. However, since the call of + * `osal_queue_send` would then be made in the same thread context as + * the call of the unlocking function `osal_queue_receive`, using a + * mutex to block the sending thread would not work here anyway. + * Therefore, we return with false in all cases when `NDEBUG` is + * defined, as most other implementations of `osal_queue_send` do, + * or point out the problem with assertion. */ +#ifdef NDEBUG + return false; +#else + assert(0); +#endif + } + + /* copy the data to the queue item data */ + memcpy(qhdl->buffer + (qhdl->tail * (qhdl->size)), data, qhdl->size); + /* update write pointer */ + qhdl->tail = (qhdl->tail + 1) % qhdl->depth; + + /* unlock a possibly waiting receiving thread */ + sema_post(&qhdl->smphr); + + return true; +} + +#if TINYUSB_OSAL_MUTEX_CANCELABLE +static void _osal_queue_lock_timeout(void *arg) +{ + mutex_cancel(arg); +} +#endif + +TU_ATTR_ALWAYS_INLINE +static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) +{ + assert(qhdl != NULL); + assert(data != NULL); + + (void)msec; + /* In RIOT we use only `tusd_task` and `tush_task` functions which call + * `osal_queue_receive` with `OSAL_TIMEOUT_WAIT_FOREVER` (`UINT32_MAX`). + * Therefore we do not use `msec` and just call `sema_wait` without timeout + * handling here. */ + if (sema_wait(&qhdl->smphr) != 0) { + /* timeout error or any other semaphore error on receiving */ + return false; + } + + /* at least one item should be in the queue now */ + assert(qhdl->front != qhdl->tail); + + /* copy data from queue item data */ + memcpy(data, qhdl->buffer + (qhdl->front * (qhdl->size)), qhdl->size); + /* update read pointer */ + qhdl->front = (qhdl->front + 1) % qhdl->depth; + + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) +{ + assert(qhdl != NULL); + return sema_get_value(&qhdl->smphr) == 0; +} + +#ifdef __cplusplus +} +#endif + +#endif /* !DOXYGEN */ +#endif /* TUSB_OS_CUSTOM_H */ +/** @} */ diff --git a/pkg/tinyusb/contrib/tinyusb.c b/pkg/tinyusb/contrib/tinyusb.c new file mode 100644 index 0000000000..d7d481e5da --- /dev/null +++ b/pkg/tinyusb/contrib/tinyusb.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include "kernel_defines.h" +#include "thread.h" + +#include "tusb.h" +#include "device/usbd.h" +#include "host/usbh.h" + +#include "tinyusb.h" +#include "tinyusb_hw.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +static void *_tinyusb_thread_impl(void *arg) +{ + (void)arg; + + if (IS_USED(MODULE_TINYUSB_DEVICE)) { + if (!tud_init(TINYUSB_TUD_RHPORT)) { + DEBUG("tinyUSB device stack couldn't be initialized\n"); + assert(0); + } + DEBUG("tinyUSB device stack initialized\n"); + } + + if (IS_USED(MODULE_TINYUSB_HOST)) { + if (!tuh_init(TINYUSB_TUH_RHPORT)) { + DEBUG("tinyUSB host stack couldn't be initialized\n"); + assert(0); + } + DEBUG("tinyUSB host stack initialized\n"); + } + + while (1) { + if (IS_USED(MODULE_TINYUSB_DEVICE)) { + /* call tinyUSB device task blocking */ + tud_task(); + DEBUG("tinyUSB device task executed\n"); + } + if (IS_USED(MODULE_TINYUSB_HOST)) { + /* call tinyUSB device task blocking */ + tuh_task(); + DEBUG("tinyUSB host task executed\n"); + } + } + + return NULL; +} + +static char _tinyusb_thread_stack[TINYUSB_THREAD_STACKSIZE]; + +int tinyusb_setup(void) +{ + int res; + + if ((res = tinyusb_hw_init()) != 0) { + DEBUG("tinyUSB peripherals couldn't be initialized\n"); + return res; + } + DEBUG("tinyUSB peripherals initialized\n"); + + if ((res = thread_create(_tinyusb_thread_stack, + sizeof(_tinyusb_thread_stack), + TINYUSB_PRIORITY, + THREAD_CREATE_WOUT_YIELD | THREAD_CREATE_STACKTEST, + _tinyusb_thread_impl, NULL, "tinyusb")) < 0) { + DEBUG("tinyUSB thread couldn't be created, reason %d\n", res); + return res; + } + DEBUG("tinyUSB thread created\n"); + + return 0; +} diff --git a/pkg/tinyusb/doc.txt b/pkg/tinyusb/doc.txt new file mode 100644 index 0000000000..ce1066fb20 --- /dev/null +++ b/pkg/tinyusb/doc.txt @@ -0,0 +1,79 @@ +/** + * @defgroup pkg_tinyusb TinyUSB package + * @ingroup pkg + * @brief Provides the tinyUSB stack as package + * @author Gunar Schorcht + * @see https://github.com/hathach/tinyusb + * + * # TinyUSB + * + * tinyUSB is an open-source cross-platform USB Host/Device stack for + * embedded systems. + * + * # Usage + * + * Add the following entries to your application makefile: + * - Enable tinyUSB package + * ```makefile + * USEPKG += tinyusb + * ``` + * - Select whether to use the tinyUSB device stack or the tinyUSB host stack or both: + * ```makefile + * USEMODULE += tinyusb_device + * ``` + * - Select the device/host classes to be used, for example: + * ```makefile + * USEMODULE += tinyusb_class_cdc tinyusb_class_msc + * ``` + * + * Add `tinyusb_setup()` to your main function to initialize the tinyUSB stack + * including used peripherals and to start the tinyUSB thread. + * ```c + * int main(void) + * { + * ... + * // initialize the tinyUSB stack including used peripherals and + * // start the tinyUSB thread + * tinyusb_setup(); + * + * while (1) { + * ... + * } + * + * return 0; + * } + * ``` + * + * Create a file `tusb_config.h` in your application directory which includes + * at least the file `tinyusb_config.h` from the tinyUSB package. This file is + * required by the tinyUSB stack and can be used to override the default + * configuration defined in `tinyusb_config.h`. + * ```c + * #define CFG_TUD_CDC 2 + * #define CFG_TUD_CDC_EP_BUFSIZE 128 + * + * #include "tinyusb_config.h" + * ``` + * + * Add the application path to the include paths at the end of your + * application's Makefile. This is necessary so that the tinyUSB stack + * uses the file `tusb_config.h` from your application directory and thus the + * file `tinyusb_config.h` from the tinyUSB package. + * ```makefile + * USEPKG += tinyusb + * USEMODULE += tinyusb_class_cdc + * USEMODULE += tinyusb_class_msc + * USEMODULE += tinyusb_device + * + * include $(RIOTBASE)/Makefile.include + * + * INCLUDES += -I$(APPDIR) + * ``` + * + * Implement required device descriptors and descriptor callbacks as well as + * the callbacks of the enabled classes. + * + * Please refer `$RIOTBASE/tests/pkg_tinyusb_cdc_msc` and the + * [tinyUSB documentation](https://docs.tinyusb.org/en/latest/reference/getting_started.html) + * for details. + */ diff --git a/pkg/tinyusb/hw/Makefile b/pkg/tinyusb/hw/Makefile new file mode 100644 index 0000000000..b18b9ec0e5 --- /dev/null +++ b/pkg/tinyusb/hw/Makefile @@ -0,0 +1,5 @@ +MODULE = tinyusb_hw + +SRC = hw_$(CPU).c + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/tinyusb/hw/include/tinyusb_hw.h b/pkg/tinyusb/hw/include/tinyusb_hw.h new file mode 100644 index 0000000000..40c4654afd --- /dev/null +++ b/pkg/tinyusb/hw/include/tinyusb_hw.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup pkg_tinyusb + * @{ + * + * @file + * @brief TinyUSB hardware driver API + * + * @author Gunar Schorcht + */ + +#ifndef TINYUSB_HW_H +#define TINYUSB_HW_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the peripherals for tinyUSB. + * + * This functions is called by #tinyusb_setup to initialize the peripherals. + * + * @return 0 on success + * @return -ENODEV if peripherals couldn't be initialized + */ +int tinyusb_hw_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TINYUSB_HW_H */ +/** @} */ diff --git a/pkg/tinyusb/patches/0001-src-portable-espressif-fix-include.patch b/pkg/tinyusb/patches/0001-src-portable-espressif-fix-include.patch new file mode 100644 index 0000000000000000000000000000000000000000..33891d23a0734f7aef197461fbb9e2b3e32ac6e7 GIT binary patch literal 801 zcmb7BU2EGg6n)pPIJ8g_9NYSEY{#r4Z9ck(bq#a+G=@=RDbeukU^|e9{P@~!24l30 zfVkYFdvtW9v#hJ%U{s8QDBLhE14VTlhe~Xe3GzrplksE{n@}1Zg?Ow=qha42PsRPCx-?OJ@=_ITQyu~+K$Gnva)C%w=J<5w+CIR_UBjgoNyhjw}- zJ#2ny)h76{x?9h_fcfkvnp*k$s&3_`(7tZ=wQd?~5*T}gEmd*IH6gC+k`I{u8Nr*Q zpQ}7O_nyKD3=Zjvv4w`($#SdnQHO2W=(@F4IdF!AhaBQ-W)2Fc=JG%ZF_J zoZe@TvuE7i;%T+++ia1p1W)-8rxWlWmKY9kbAvajdA9g=cmH)U9}*DwJ{Et{CC>FZ zT3nf0cX?EezwDat>!;M)UG<*cqW^1tY<1blOz!PJj4t%dmTjHMqNvn6!=l>uyWRg1 K?y<;YguDP;w)c?$ literal 0 HcmV?d00001 diff --git a/pkg/tinyusb/patches/0002-src-portable-synopsys-define-SystemCoreClock-variabl.patch b/pkg/tinyusb/patches/0002-src-portable-synopsys-define-SystemCoreClock-variabl.patch new file mode 100644 index 0000000000000000000000000000000000000000..a197b606e1d7a0733c82784fdeeb5aa1cb469bd9 GIT binary patch literal 1513 zcmbtU?{C{S5dF@-;*fq=0@<=8|902GuA4N*;wFe}bVE_dGq<6Y6CpZyS=2_!FIwi>UMf!m=D+8o)q0qKkml^zLvvv-iLXm zA;m4keTbq_{e>ioVs=F^Blx(}+(0Ud%80_j&kz23Y@e<}jqVrrfjb<*l~D~C)>uiD zBoKE--QI{`gNX20x?BH&!j0g|-DEMnhNP8z1#3iWTN%gKCAREAS6h3qBaoOYjgTI! z!_BlZI4vvj4Zd@ud?jNaK2Hi1N9RnHz-@j#uAi<6)~ainRT0XlJEEX@Xw+l{$6n{!J;D&)R6DAaLPOQZ>r0! zBu{(`K@CubkSZ+@?q|0P!2PyVLOG<)xEJM49@$%jT$N}WwUlSE9z>%eb>;_8&?Nnt zt6zIUqB4dIE)O9rs2Ir}Y=Cke;JMc8l8+)es#?Lb7+2NQezLHd-rP=q&8D~am-TA( zv^QkUW|O`Az*-)%J{)nw0{(&y1VKPM;#w4lg6jvA4QP|O7L9k!o(C5VDv(&BzdmnX zFeX)=L$mon#Chw*e7w+Z{n|>Vw8TBc@t}{9>|q#2q8;_yNg^SRqF$%tb-uncBa!g> z?&C2u2T7v`0UZZ09y0)VgT~M^JX>nJ+xlrh3G?aFo-z7MlOGIN@P_i^lLBX={FaGI z=V5Wi0tT%X+%7)Cbqc(E!3~=EuE=o2u$W6JGu4cE4=d($n*dGAunn-PDatkpO=Qq0|l?PP{R5EIo~LnzXG{L z!vrjj(Pvq|t qn9`+x)qSs&Jo+Vt%!opU+P~_~eX{|vF@;aY1*H>M62|>7X6zqxkK$4Q literal 0 HcmV?d00001