From aca3191a5824b4afd466ba82fc6d9c7cf1c20919 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 18 Mar 2024 16:08:33 -0700 Subject: [PATCH] Add Max3421E support for usb host It is enabled on: * SAMD51 boards with 1MB flash (SKU ending in 20A) * Feather RP2040 DVI. Others have PIO usb host. * All ESP32 boards. * All nRF boards Fixes #8676 --- .gitignore | 4 + docs/shared_bindings_matrix.py | 2 +- lib/tinyusb | 2 +- .../atmel-samd/common-hal/max3421e/Max3421E.c | 80 ++++++++ .../atmel-samd/common-hal/max3421e/Max3421E.h | 31 +++ ports/atmel-samd/eic_handler.c | 7 + ports/atmel-samd/eic_handler.h | 6 +- ports/atmel-samd/mpconfigport.mk | 8 + .../mpconfigboard.mk | 2 +- ports/espressif/common-hal/busio/SPI.c | 33 ++- ports/espressif/common-hal/busio/SPI.h | 7 +- .../espressif/common-hal/max3421e/Max3421E.c | 117 +++++++++++ .../espressif/common-hal/max3421e/Max3421E.h | 29 +++ ports/espressif/common-hal/ps2io/Ps2.c | 6 + ports/nordic/common-hal/max3421e/Max3421E.c | 72 +++++++ ports/raspberrypi/Makefile | 2 +- .../mpconfigboard.mk | 4 + .../common-hal/max3421e/Max3421E.c | 71 +++++++ ports/raspberrypi/mpconfigport.mk | 1 + py/circuitpy_defns.mk | 11 +- py/circuitpy_mpconfig.mk | 8 +- shared-bindings/fourwire/__init__.c | 2 +- shared-bindings/max3421e/Max3421E.c | 103 ++++++++++ shared-bindings/max3421e/Max3421E.h | 50 +++++ shared-bindings/max3421e/__init__.c | 73 +++++++ shared-bindings/max3421e/__init__.h | 27 +++ shared-module/board/__init__.c | 2 - shared-module/max3421e/Max3421E.c | 192 ++++++++++++++++++ shared-module/max3421e/Max3421E.h | 56 +++++ shared-module/max3421e/__init__.c | 0 supervisor/shared/usb/host_keyboard.c | 3 + supervisor/shared/usb/tusb_config.h | 17 +- supervisor/shared/usb/usb.c | 2 +- supervisor/supervisor.mk | 16 +- 34 files changed, 1014 insertions(+), 32 deletions(-) create mode 100644 ports/atmel-samd/common-hal/max3421e/Max3421E.c create mode 100644 ports/atmel-samd/common-hal/max3421e/Max3421E.h create mode 100644 ports/espressif/common-hal/max3421e/Max3421E.c create mode 100644 ports/espressif/common-hal/max3421e/Max3421E.h create mode 100644 ports/nordic/common-hal/max3421e/Max3421E.c create mode 100644 ports/raspberrypi/common-hal/max3421e/Max3421E.c create mode 100644 shared-bindings/max3421e/Max3421E.c create mode 100644 shared-bindings/max3421e/Max3421E.h create mode 100644 shared-bindings/max3421e/__init__.c create mode 100644 shared-bindings/max3421e/__init__.h create mode 100644 shared-module/max3421e/Max3421E.c create mode 100644 shared-module/max3421e/Max3421E.h create mode 100644 shared-module/max3421e/__init__.c diff --git a/.gitignore b/.gitignore index 9cb1e9ea5e497..70f05586f1a3d 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,7 @@ TAGS # Uncrustify formatting *.uncrustify + +# clangd cache +############## +.cache diff --git a/docs/shared_bindings_matrix.py b/docs/shared_bindings_matrix.py index cd5b09b6bdf27..4a858a7be3747 100644 --- a/docs/shared_bindings_matrix.py +++ b/docs/shared_bindings_matrix.py @@ -83,7 +83,7 @@ "select": "MICROPY_PY_SELECT_SELECT", "sys": "CIRCUITPY_SYS", "terminalio": "CIRCUITPY_DISPLAYIO", - "usb": "CIRCUITPY_USB_HOST", + "usb": "CIRCUITPY_PYUSB", } MODULES_NOT_IN_BINDINGS = [ "binascii", "errno", "json", "re", "ulab" ] diff --git a/lib/tinyusb b/lib/tinyusb index 60764de56461e..5738757e2ca73 160000 --- a/lib/tinyusb +++ b/lib/tinyusb @@ -1 +1 @@ -Subproject commit 60764de56461edbf1e8bafe4e73fdc97ec3d2e6e +Subproject commit 5738757e2ca73ab003df5bfbf5ec7b60850ef88b diff --git a/ports/atmel-samd/common-hal/max3421e/Max3421E.c b/ports/atmel-samd/common-hal/max3421e/Max3421E.c new file mode 100644 index 0000000000000..e87d5de6cc3d7 --- /dev/null +++ b/ports/atmel-samd/common-hal/max3421e/Max3421E.c @@ -0,0 +1,80 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#include "shared-module/max3421e/Max3421E.h" + +#include "common-hal/max3421e/Max3421E.h" + +#include "lib/tinyusb/src/host/usbh.h" +#include "supervisor/usb.h" + +#include "eic_handler.h" +#include "py/runtime.h" +#include "samd/external_interrupts.h" + +#include "shared-bindings/microcontroller/Pin.h" + +void samd_max3421e_interrupt_handler(uint8_t channel) { + max3421e_interrupt_handler((max3421e_max3421e_obj_t *)get_eic_channel_data(channel)); +} + +// Setup irq on self->irq to call tuh_int_handler(rhport, true) on falling edge +void common_hal_max3421e_max3421e_init_irq(max3421e_max3421e_obj_t *self) { + const mcu_pin_obj_t *pin = self->irq.pin; + if (!pin->has_extint) { + raise_ValueError_invalid_pin(); + } + if (eic_get_enable()) { + if (!eic_channel_free(pin->extint_channel)) { + mp_raise_RuntimeError(MP_ERROR_TEXT("Internal resource(s) in use")); + } + } else { + turn_on_external_interrupt_controller(); + } + + set_eic_channel_data(pin->extint_channel, self); + set_eic_handler(pin->extint_channel, EIC_HANDLER_MAX3421E); + turn_on_eic_channel(pin->extint_channel, EIC_CONFIG_SENSE0_LOW_Val); +} + +void common_hal_max3421e_max3421e_deinit_irq(max3421e_max3421e_obj_t *self) { + const mcu_pin_obj_t *pin = self->irq.pin; + set_eic_handler(pin->extint_channel, EIC_HANDLER_NO_INTERRUPT); + turn_off_eic_channel(pin->extint_channel); + reset_pin_number(pin->extint_channel); +} + +// Enable or disable the irq interrupt. +void common_hal_max3421e_max3421e_irq_enabled(max3421e_max3421e_obj_t *self, bool enabled) { + const mcu_pin_obj_t *pin = self->irq.pin; + uint32_t mask = 1 << pin->extint_channel; + // Don't use the turn_(on|off) API because it may deinit the eic completely. + if (!enabled) { + EIC->INTENCLR.reg = mask << EIC_INTENSET_EXTINT_Pos; + } else { + EIC->INTENSET.reg = mask << EIC_INTENSET_EXTINT_Pos; + } +} diff --git a/ports/atmel-samd/common-hal/max3421e/Max3421E.h b/ports/atmel-samd/common-hal/max3421e/Max3421E.h new file mode 100644 index 0000000000000..d4f3e87dd3736 --- /dev/null +++ b/ports/atmel-samd/common-hal/max3421e/Max3421E.h @@ -0,0 +1,31 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#pragma once + +#include + +void samd_max3421e_interrupt_handler(uint8_t channel); diff --git a/ports/atmel-samd/eic_handler.c b/ports/atmel-samd/eic_handler.c index 48824031c0916..90a10d9f7f98d 100644 --- a/ports/atmel-samd/eic_handler.c +++ b/ports/atmel-samd/eic_handler.c @@ -24,6 +24,7 @@ * THE SOFTWARE. */ +#include "common-hal/max3421e/Max3421E.h" #include "common-hal/pulseio/PulseIn.h" #include "common-hal/ps2io/Ps2.h" #include "common-hal/rotaryio/IncrementalEncoder.h" @@ -73,6 +74,12 @@ void shared_eic_handler(uint8_t channel) { break; #endif + #if CIRCUITPY_MAX3421E + case EIC_HANDLER_MAX3421E: + samd_max3421e_interrupt_handler(channel); + break; + #endif + default: break; } diff --git a/ports/atmel-samd/eic_handler.h b/ports/atmel-samd/eic_handler.h index d73ef2a0f7132..2166e2f6de63d 100644 --- a/ports/atmel-samd/eic_handler.h +++ b/ports/atmel-samd/eic_handler.h @@ -23,8 +23,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_ATMEL_SAMD_EIC_HANDLER_H -#define MICROPY_INCLUDED_ATMEL_SAMD_EIC_HANDLER_H +#pragma once #define EIC_HANDLER_NO_INTERRUPT 0x0 #define EIC_HANDLER_PULSEIN 0x1 @@ -32,8 +31,7 @@ #define EIC_HANDLER_PS2 0x3 #define EIC_HANDLER_COUNTER 0x04 #define EIC_HANDLER_ALARM 0x05 +#define EIC_HANDLER_MAX3421E 0x06 void set_eic_handler(uint8_t channel, uint8_t eic_handler); void shared_eic_handler(uint8_t channel); - -#endif // MICROPY_INCLUDED_ATMEL_SAMD_EIC_HANDLER_H diff --git a/ports/atmel-samd/mpconfigport.mk b/ports/atmel-samd/mpconfigport.mk index 6585783546f83..c3449485cf40e 100644 --- a/ports/atmel-samd/mpconfigport.mk +++ b/ports/atmel-samd/mpconfigport.mk @@ -99,6 +99,13 @@ ifeq ($(CIRCUITPY_FULL_BUILD),0) CIRCUITPY_LTO_PARTITION ?= one endif +# 20A skus have 1MB flash and can fit more. +ifeq ($(patsubst %20A,,$(CHIP_VARIANT)),) +HAS_1MB_FLASH = 1 +else +HAS_1MB_FLASH = 0 +endif + # The ?='s allow overriding in mpconfigboard.mk. @@ -107,6 +114,7 @@ CIRCUITPY_ALARM ?= 1 CIRCUITPY_BITMAPFILTER ?= 0 CIRCUITPY_FLOPPYIO ?= $(CIRCUITPY_FULL_BUILD) CIRCUITPY_FRAMEBUFFERIO ?= $(CIRCUITPY_FULL_BUILD) +CIRCUITPY_MAX3421E ?= $(HAS_1MB_FLASH) CIRCUITPY_PS2IO ?= 1 CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_FRAMEBUFFERIO) CIRCUITPY_SAMD ?= 1 diff --git a/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.mk b/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.mk index 582ed4409c149..bf0dc3f320f50 100644 --- a/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.mk +++ b/ports/espressif/boards/adafruit_feather_esp32s3_nopsram/mpconfigboard.mk @@ -1,6 +1,6 @@ USB_VID = 0x239A USB_PID = 0x8114 -USB_PRODUCT = "Adafruit Feather ESP32S3 No PSRAM" +USB_PRODUCT = "Feather ESP32S3 No PSRAM" USB_MANUFACTURER = "Adafruit" IDF_TARGET = esp32s3 diff --git a/ports/espressif/common-hal/busio/SPI.c b/ports/espressif/common-hal/busio/SPI.c index 08fd0cd0126b0..190e8757ff88a 100644 --- a/ports/espressif/common-hal/busio/SPI.c +++ b/ports/espressif/common-hal/busio/SPI.c @@ -26,6 +26,7 @@ #include +#include "freertos/projdefs.h" #include "py/runtime.h" #include "shared-bindings/busio/SPI.h" #include "shared-bindings/microcontroller/Pin.h" @@ -37,6 +38,7 @@ static bool spi_never_reset[SOC_SPI_PERIPH_NUM]; static spi_device_handle_t spi_handle[SOC_SPI_PERIPH_NUM]; +static StaticSemaphore_t spi_mutex[SOC_SPI_PERIPH_NUM]; static bool spi_bus_is_free(spi_host_device_t host_id) { return spi_bus_get_attr(host_id) == NULL; @@ -121,6 +123,8 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, claim_pin(miso); } claim_pin(clock); + + self->mutex = xSemaphoreCreateMutexStatic(&spi_mutex[self->host_id]); } void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { @@ -143,14 +147,22 @@ void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { return; } + // Wait for any other users of this to finish. + while (!common_hal_busio_spi_try_lock(self)) { + } + + // Mark us as deinit early in case we are used in an interrupt. + common_hal_reset_pin(self->clock); + self->clock = NULL; + spi_never_reset[self->host_id] = false; spi_bus_remove_device(spi_handle[self->host_id]); spi_bus_free(self->host_id); + vSemaphoreDelete(self->mutex); + common_hal_reset_pin(self->MOSI); common_hal_reset_pin(self->MISO); - common_hal_reset_pin(self->clock); - self->clock = NULL; } bool common_hal_busio_spi_configure(busio_spi_obj_t *self, @@ -167,20 +179,18 @@ bool common_hal_busio_spi_configure(busio_spi_obj_t *self, } bool common_hal_busio_spi_try_lock(busio_spi_obj_t *self) { - bool grabbed_lock = false; - if (!self->has_lock) { - grabbed_lock = true; - self->has_lock = true; + if (common_hal_busio_spi_deinited(self)) { + return false; } - return grabbed_lock; + return xSemaphoreTake(self->mutex, 1) == pdTRUE; } bool common_hal_busio_spi_has_lock(busio_spi_obj_t *self) { - return self->has_lock; + return xSemaphoreGetMutexHolder(self->mutex) == xTaskGetCurrentTaskHandle(); } void common_hal_busio_spi_unlock(busio_spi_obj_t *self) { - self->has_lock = false; + xSemaphoreGive(self->mutex); } bool common_hal_busio_spi_write(busio_spi_obj_t *self, @@ -229,7 +239,10 @@ bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, transactions[0].flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA; transactions[0].length = bits_to_send; - spi_device_transmit(spi_handle[self->host_id], &transactions[0]); + esp_err_t result = spi_device_transmit(spi_handle[self->host_id], &transactions[0]); + if (result != ESP_OK) { + return false; + } if (data_in != NULL) { memcpy(data_in, &transactions[0].rx_data, len); diff --git a/ports/espressif/common-hal/busio/SPI.h b/ports/espressif/common-hal/busio/SPI.h index 20a744a738ad9..9548d0c992ccf 100644 --- a/ports/espressif/common-hal/busio/SPI.h +++ b/ports/espressif/common-hal/busio/SPI.h @@ -24,8 +24,7 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_BUSIO_SPI_H -#define MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_BUSIO_SPI_H +#pragma once #include "driver/spi_master.h" #include "shared-bindings/microcontroller/Pin.h" @@ -44,9 +43,7 @@ typedef struct { uint8_t polarity; uint32_t baudrate; - bool has_lock; + SemaphoreHandle_t mutex; } busio_spi_obj_t; void spi_reset(void); - -#endif // MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_BUSIO_SPI_H diff --git a/ports/espressif/common-hal/max3421e/Max3421E.c b/ports/espressif/common-hal/max3421e/Max3421E.c new file mode 100644 index 0000000000000..f96eed7d56f13 --- /dev/null +++ b/ports/espressif/common-hal/max3421e/Max3421E.c @@ -0,0 +1,117 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#include "shared-module/max3421e/Max3421E.h" + +#include "bindings/espidf/__init__.h" + +#include "esp_heap_caps.h" +#include "hal/gpio_types.h" +#include "lib/tinyusb/src/host/usbh.h" +#include "py/runtime.h" +#include "shared-bindings/busio/SPI.h" +#include "supervisor/usb.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include "components/driver/gpio/include/driver/gpio.h" + +#ifdef CFG_TUSB_DEBUG + #define USBH_STACK_SIZE (3 * configMINIMAL_STACK_SIZE) +#else + #define USBH_STACK_SIZE (3 * configMINIMAL_STACK_SIZE / 2) +#endif + +// Setup a separate FreeRTOS task for host because the device task blocks while +// waiting for more device tasks. The stack is allocated on first use when the +// task is started. +TaskHandle_t usb_host_task_handle; + +STATIC void usb_host_task(void *param) { + (void)param; + // RTOS forever loop + while (1) { + tuh_task(); + vTaskDelay(1); + } +} + +static void _interrupt_wrapper(void *arg) { + max3421e_interrupt_handler((max3421e_max3421e_obj_t *)arg); +} + +// Setup irq on self->irq to call tuh_int_handler(rhport, true) on falling edge +void common_hal_max3421e_max3421e_init_irq(max3421e_max3421e_obj_t *self) { + + // Pin the USB task to the same core as CircuitPython. This way we leave + // the other core for networking. + BaseType_t base_type = xTaskCreatePinnedToCore(usb_host_task, + "usbh", + USBH_STACK_SIZE, + NULL, + 5, + &usb_host_task_handle, + xPortGetCoreID()); + + if (base_type != pdPASS) { + if (base_type == errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY) { + mp_raise_espidf_MemoryError(); + } else { + mp_raise_RuntimeError_varg(MP_ERROR_TEXT("Unknown error code %d"), base_type); + } + } + + const mcu_pin_obj_t *pin = self->irq.pin; + esp_err_t result = gpio_install_isr_service(ESP_INTR_FLAG_SHARED); + if (result != ESP_OK) { + vTaskDelete(usb_host_task_handle); + CHECK_ESP_RESULT(result); + } + result = gpio_isr_handler_add(pin->number, _interrupt_wrapper, self); + if (result != ESP_OK) { + vTaskDelete(usb_host_task_handle); + CHECK_ESP_RESULT(result); + } + gpio_set_intr_type(pin->number, GPIO_INTR_LOW_LEVEL); + gpio_intr_enable(pin->number); +} + +void common_hal_max3421e_max3421e_deinit_irq(max3421e_max3421e_obj_t *self) { + const mcu_pin_obj_t *pin = self->irq.pin; + gpio_isr_handler_remove(pin->number); + gpio_uninstall_isr_service(); + + vTaskDelete(usb_host_task_handle); +} + +// Enable or disable the irq interrupt. +void common_hal_max3421e_max3421e_irq_enabled(max3421e_max3421e_obj_t *self, bool enabled) { + const mcu_pin_obj_t *pin = self->irq.pin; + if (!enabled) { + gpio_intr_disable(pin->number); + } else { + gpio_intr_enable(pin->number); + } +} diff --git a/ports/espressif/common-hal/max3421e/Max3421E.h b/ports/espressif/common-hal/max3421e/Max3421E.h new file mode 100644 index 0000000000000..e52561a6ceffa --- /dev/null +++ b/ports/espressif/common-hal/max3421e/Max3421E.h @@ -0,0 +1,29 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#pragma once + +void max3421e_interrupt_handler(uint8_t channel); diff --git a/ports/espressif/common-hal/ps2io/Ps2.c b/ports/espressif/common-hal/ps2io/Ps2.c index 2b6dc529674c1..e065012a0cafa 100644 --- a/ports/espressif/common-hal/ps2io/Ps2.c +++ b/ports/espressif/common-hal/ps2io/Ps2.c @@ -49,7 +49,12 @@ #define ERROR_TX_RTS 0x1000 #define ERROR_TX_NORESP 0x2000 +static bool ps2_used = false; + void ps2_reset(void) { + if (!ps2_used) { + return; + } gpio_uninstall_isr_service(); } @@ -138,6 +143,7 @@ static void IRAM_ATTR ps2_interrupt_handler(void *self_in) { static void enable_interrupt(ps2io_ps2_obj_t *self) { // turn on falling edge interrupt + ps2_used = true; gpio_install_isr_service(ESP_INTR_FLAG_IRAM); gpio_set_intr_type(self->clk_pin, GPIO_INTR_NEGEDGE); gpio_isr_handler_add(self->clk_pin, ps2_interrupt_handler, (void *)self); diff --git a/ports/nordic/common-hal/max3421e/Max3421E.c b/ports/nordic/common-hal/max3421e/Max3421E.c new file mode 100644 index 0000000000000..adc18b5bd4876 --- /dev/null +++ b/ports/nordic/common-hal/max3421e/Max3421E.c @@ -0,0 +1,72 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#include "shared-module/max3421e/Max3421E.h" + +#include "lib/tinyusb/src/host/usbh.h" +#include "supervisor/usb.h" + +#include "nrfx_gpiote.h" + +static max3421e_max3421e_obj_t *_active; + +static void max3421_int_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { + if (!(action == NRF_GPIOTE_POLARITY_HITOLO)) { + return; + } + max3421e_interrupt_handler(_active); +} + +// Setup irq on self->irq to call tuh_int_handler(rhport, true) on falling edge +void common_hal_max3421e_max3421e_init_irq(max3421e_max3421e_obj_t *self) { + nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_HITOLO(true); + in_config.pull = NRF_GPIO_PIN_PULLUP; + + size_t pin_number = self->irq.pin->number; + + nrfx_gpiote_in_init(pin_number, &in_config, max3421_int_handler); + nrfx_gpiote_in_event_enable(pin_number, true); + + _active = self; +} + +void common_hal_max3421e_max3421e_deinit_irq(max3421e_max3421e_obj_t *self) { + size_t pin_number = self->irq.pin->number; + // Bulk reset may have already reset us but this will raise an assert in + // debug mode if so. + nrfx_gpiote_in_event_disable(pin_number); + nrfx_gpiote_in_uninit(pin_number); + _active = NULL; +} + +// Enable or disable the irq interrupt. +void common_hal_max3421e_max3421e_irq_enabled(max3421e_max3421e_obj_t *self, bool enabled) { + if (enabled) { + NVIC_EnableIRQ(GPIOTE_IRQn); + } else { + NVIC_DisableIRQ(GPIOTE_IRQn); + } +} diff --git a/ports/raspberrypi/Makefile b/ports/raspberrypi/Makefile index 12a183e71af7f..54e6cf35f5755 100644 --- a/ports/raspberrypi/Makefile +++ b/ports/raspberrypi/Makefile @@ -178,7 +178,7 @@ CFLAGS += $(INC) -Wall -Werror -std=gnu11 -fshort-enums $(BASE_CFLAGS) $(CFLAGS_ CFLAGS += \ -march=armv6-m \ -mthumb \ - -mabi=aapcs-linux \ + -mabi=aapcs \ -mcpu=cortex-m0plus \ -msoft-float \ -mfloat-abi=soft diff --git a/ports/raspberrypi/boards/adafruit_feather_rp2040_dvi/mpconfigboard.mk b/ports/raspberrypi/boards/adafruit_feather_rp2040_dvi/mpconfigboard.mk index ca8a6fa396cd4..901b2c973d009 100644 --- a/ports/raspberrypi/boards/adafruit_feather_rp2040_dvi/mpconfigboard.mk +++ b/ports/raspberrypi/boards/adafruit_feather_rp2040_dvi/mpconfigboard.mk @@ -8,4 +8,8 @@ CHIP_FAMILY = rp2 EXTERNAL_FLASH_DEVICES = "GD25Q64C,W25Q64JVxQ" +CIRCUITPY_MAX3421E = 1 CIRCUITPY_PICODVI = 1 +# Disable native USB host because it won't work alongside DVI anyway. (They both +# use the second core.) +CIRCUITPY_USB_HOST = 0 diff --git a/ports/raspberrypi/common-hal/max3421e/Max3421E.c b/ports/raspberrypi/common-hal/max3421e/Max3421E.c new file mode 100644 index 0000000000000..7233d149321db --- /dev/null +++ b/ports/raspberrypi/common-hal/max3421e/Max3421E.c @@ -0,0 +1,71 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#include "shared-module/max3421e/Max3421E.h" + +#include "lib/tinyusb/src/host/usbh.h" +#include "shared-bindings/busio/SPI.h" +#include "supervisor/usb.h" + +#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h" + +static max3421e_max3421e_obj_t *active_max = NULL; + +static void _interrupt_wrapper(void) { + if (active_max == NULL) { + return; + } + size_t pin_number = active_max->irq.pin->number; + if ((gpio_get_irq_event_mask(pin_number) & GPIO_IRQ_LEVEL_LOW) == 0) { + return; + } + gpio_acknowledge_irq(pin_number, GPIO_IRQ_LEVEL_LOW); + max3421e_interrupt_handler(active_max); +} + +// Setup irq on self->irq to call tuh_int_handler(rhport, true) on falling edge +void common_hal_max3421e_max3421e_init_irq(max3421e_max3421e_obj_t *self) { + size_t pin_number = self->irq.pin->number; + active_max = self; + gpio_add_raw_irq_handler(pin_number, _interrupt_wrapper); + irq_set_enabled(IO_IRQ_BANK0, true); + gpio_set_irq_enabled(pin_number, GPIO_IRQ_LEVEL_LOW, true); +} + +void common_hal_max3421e_max3421e_deinit_irq(max3421e_max3421e_obj_t *self) { + size_t pin_number = self->irq.pin->number; + gpio_set_irq_enabled(pin_number, GPIO_IRQ_LEVEL_LOW, false); + irq_set_enabled(IO_IRQ_BANK0, false); + gpio_acknowledge_irq(pin_number, GPIO_IRQ_LEVEL_LOW); + gpio_remove_raw_irq_handler(pin_number, _interrupt_wrapper); + active_max = NULL; +} + +// Enable or disable the irq interrupt. +void common_hal_max3421e_max3421e_irq_enabled(max3421e_max3421e_obj_t *self, bool enabled) { + size_t pin_number = self->irq.pin->number; + gpio_set_irq_enabled(pin_number, GPIO_IRQ_LEVEL_LOW, enabled); +} diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index e44d75bf05fe7..ab72ddc4f05bc 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -16,6 +16,7 @@ CIRCUITPY_BITOPS ?= 1 CIRCUITPY_HASHLIB ?= 1 CIRCUITPY_HASHLIB_MBEDTLS ?= 1 CIRCUITPY_IMAGECAPTURE ?= 1 +CIRCUITPY_MAX3421E ?= 0 CIRCUITPY_MEMORYMAP ?= 1 CIRCUITPY_PWMIO ?= 1 CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_DISPLAYIO) diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 58b9c169cf0ca..36af49a1a7945 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -261,6 +261,9 @@ endif ifeq ($(CIRCUITPY_MATH),1) SRC_PATTERNS += math/% endif +ifeq ($(CIRCUITPY_MAX3421E),1) +SRC_PATTERNS += max3421e/% +endif ifeq ($(CIRCUITPY_MEMORYMAP),1) SRC_PATTERNS += memorymap/% endif @@ -390,6 +393,9 @@ endif ifeq ($(CIRCUITPY_UHEAP),1) SRC_PATTERNS += uheap/% endif +ifeq ($(CIRCUITPY_PYUSB),1) +SRC_PATTERNS += usb/% +endif ifeq ($(CIRCUITPY_USB_CDC),1) SRC_PATTERNS += usb_cdc/% endif @@ -400,7 +406,7 @@ ifeq ($(CIRCUITPY_USB_VIDEO),1) SRC_PATTERNS += usb_video/% endif ifeq ($(CIRCUITPY_USB_HOST),1) -SRC_PATTERNS += usb_host/% usb/% +SRC_PATTERNS += usb_host/% endif ifeq ($(CIRCUITPY_USB_MIDI),1) SRC_PATTERNS += usb_midi/% @@ -489,6 +495,7 @@ SRC_COMMON_HAL_ALL = \ gnss/SatelliteSystem.c \ i2ctarget/I2CTarget.c \ i2ctarget/__init__.c \ + max3421e/Max3421E.c \ memorymap/__init__.c \ memorymap/AddressRange.c \ microcontroller/__init__.c \ @@ -664,6 +671,8 @@ SRC_SHARED_MODULE_ALL = \ keypad/KeyMatrix.c \ keypad/ShiftRegisterKeys.c \ keypad/Keys.c \ + max3421e/__init__.c \ + max3421e/Max3421E.c \ memorymonitor/__init__.c \ memorymonitor/AllocationAlarm.c \ memorymonitor/AllocationSize.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index e3d4bf4e5a1c7..50a6cf25a0024 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -352,6 +352,9 @@ CFLAGS += -DCIRCUITPY_LOCALE=$(CIRCUITPY_LOCALE) CIRCUITPY_MATH ?= 1 CFLAGS += -DCIRCUITPY_MATH=$(CIRCUITPY_MATH) +CIRCUITPY_MAX3421E ?= 0 +CFLAGS += -DCIRCUITPY_MAX3421E=$(CIRCUITPY_MAX3421E) + CIRCUITPY_MEMORYMAP ?= 0 CFLAGS += -DCIRCUITPY_MEMORYMAP=$(CIRCUITPY_MEMORYMAP) @@ -574,10 +577,13 @@ CFLAGS += -DCIRCUITPY_USB_VIDEO=$(CIRCUITPY_USB_VIDEO) CIRCUITPY_USB_HOST ?= 0 CFLAGS += -DCIRCUITPY_USB_HOST=$(CIRCUITPY_USB_HOST) +CIRCUITPY_PYUSB ?= $(call enable-if-any,$(CIRCUITPY_USB_HOST) $(CIRCUITPY_MAX3421E)) +CFLAGS += -DCIRCUITPY_PYUSB=$(CIRCUITPY_PYUSB) + CIRCUITPY_USB_IDENTIFICATION ?= $(CIRCUITPY_USB) CFLAGS += -DCIRCUITPY_USB_IDENTIFICATION=$(CIRCUITPY_USB_IDENTIFICATION) -CIRCUITPY_USB_KEYBOARD_WORKFLOW ?= $(CIRCUITPY_USB_HOST) +CIRCUITPY_USB_KEYBOARD_WORKFLOW ?= $(call enable-if-any,$(CIRCUITPY_USB_HOST) $(CIRCUITPY_MAX3421E)) CFLAGS += -DCIRCUITPY_USB_KEYBOARD_WORKFLOW=$(CIRCUITPY_USB_KEYBOARD_WORKFLOW) # MIDI is available by default, but is not turned on if there are fewer than 8 endpoints. diff --git a/shared-bindings/fourwire/__init__.c b/shared-bindings/fourwire/__init__.c index 7563682ce675c..d6f8b86528fd8 100644 --- a/shared-bindings/fourwire/__init__.c +++ b/shared-bindings/fourwire/__init__.c @@ -38,7 +38,7 @@ //| """ STATIC const mp_rom_map_elem_t fourwire_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_busdisplay) }, + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_fourwire) }, { MP_ROM_QSTR(MP_QSTR_FourWire), MP_ROM_PTR(&fourwire_fourwire_type) }, }; diff --git a/shared-bindings/max3421e/Max3421E.c b/shared-bindings/max3421e/Max3421E.c new file mode 100644 index 0000000000000..f6132a14f48b0 --- /dev/null +++ b/shared-bindings/max3421e/Max3421E.c @@ -0,0 +1,103 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#include "shared-bindings/max3421e/Max3421E.h" + +#include "py/runtime.h" +#include "shared-bindings/busio/SPI.h" +#include "shared-bindings/microcontroller/Pin.h" + +//| class Max3421E: +//| """Interface with a Max3421E usb host chip.""" +//| +//| def __init__( +//| self, +//| spi_bus: busio.SPI, +//| *, +//| chip_select: microcontroller.Pin, +//| irq: microcontroller.Pin, +//| baudrate: int = 26000000 +//| ) -> None: +//| """Create a Max3421E object associated with the given pins. +//| +//| Although this object isn't used directly for USB host (the `usb` module is). +//| You must keep it alive in memory. When deinit, it will shut down USB host functionality. +//| +//| :param busio.SPI spi_bus: The SPI bus that make up the clock and data lines +//| :param microcontroller.Pin chip_select: Chip select pin +//| :param microcontroller.Pin irq: Interrupt pin +//| :param int baudrate: Maximum baudrate to talk to the Max chip in Hz""" +//| ... +STATIC mp_obj_t max3421e_max3421e_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_spi_bus, ARG_chip_select, ARG_irq, ARG_baudrate }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_spi_bus, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_chip_select, MP_ARG_OBJ | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, + { MP_QSTR_irq, MP_ARG_OBJ | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, + { MP_QSTR_baudrate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 24000000} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + const mcu_pin_obj_t *chip_select = validate_obj_is_free_pin(args[ARG_chip_select].u_obj, MP_QSTR_chip_select); + const mcu_pin_obj_t *irq = validate_obj_is_free_pin(args[ARG_irq].u_obj, MP_QSTR_irq); + + mp_obj_t spi = mp_arg_validate_type(args[ARG_spi_bus].u_obj, &busio_spi_type, MP_QSTR_spi_bus); + + max3421e_max3421e_obj_t *self = m_new_obj_with_finaliser(max3421e_max3421e_obj_t); + self->base.type = &max3421e_max3421e_type; + common_hal_max3421e_max3421e_construct(self, + MP_OBJ_TO_PTR(spi), chip_select, irq, args[ARG_baudrate].u_int); + return self; +} + +//| def deinit(self) -> None: +//| """Shuts down USB host functionality and releases chip_select and irq pins.""" +//| ... +//| +STATIC mp_obj_t max3421e_max3421e_obj_deinit(mp_obj_t self_in) { + max3421e_max3421e_obj_t *self = self_in; + if (common_hal_max3421e_max3421e_deinited(self)) { + return mp_const_none; + } + common_hal_max3421e_max3421e_deinit(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(max3421e_max3421e_deinit_obj, max3421e_max3421e_obj_deinit); + +STATIC const mp_rom_map_elem_t max3421e_max3421e_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&max3421e_max3421e_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&max3421e_max3421e_deinit_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(max3421e_max3421e_locals_dict, max3421e_max3421e_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + max3421e_max3421e_type, + MP_QSTR_Max3421E, + MP_TYPE_FLAG_NONE, + make_new, max3421e_max3421e_make_new, + locals_dict, &max3421e_max3421e_locals_dict + ); diff --git a/shared-bindings/max3421e/Max3421E.h b/shared-bindings/max3421e/Max3421E.h new file mode 100644 index 0000000000000..994cec8c2e82d --- /dev/null +++ b/shared-bindings/max3421e/Max3421E.h @@ -0,0 +1,50 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#pragma once + +#include "shared-module/max3421e/Max3421E.h" + +extern const mp_obj_type_t max3421e_max3421e_type; + +void common_hal_max3421e_max3421e_construct(max3421e_max3421e_obj_t *self, + busio_spi_obj_t *spi, const mcu_pin_obj_t *chip_select, const mcu_pin_obj_t *irq, + uint32_t baudrate); + +bool common_hal_max3421e_max3421e_deinited(max3421e_max3421e_obj_t *self); +void common_hal_max3421e_max3421e_deinit(max3421e_max3421e_obj_t *self); + +// TinyUSB requires these three functions. + +// API to control MAX3421 SPI CS +extern void tuh_max3421_spi_cs_api(uint8_t rhport, bool active); + +// API to transfer data with MAX3421 SPI +// Either tx_buf or rx_buf can be NULL, which means transfer is write or read only +extern bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const *tx_buf, uint8_t *rx_buf, size_t xfer_bytes); + +// API to enable/disable MAX3421 INTR pin interrupt +extern void tuh_max3421_int_api(uint8_t rhport, bool enabled); diff --git a/shared-bindings/max3421e/__init__.c b/shared-bindings/max3421e/__init__.c new file mode 100644 index 0000000000000..7aa6584bbd52e --- /dev/null +++ b/shared-bindings/max3421e/__init__.c @@ -0,0 +1,73 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#include + +#include "py/enum.h" +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-bindings/max3421e/__init__.h" +#include "shared-bindings/max3421e/Max3421E.h" + +//| """Provide USB host via a connected MAX3421E chip. +//| +//| Here is how to test with the MAX3421E featherwing: +//| +//| .. code-block:: python +//| +//| import board +//| import max3421e +//| import time +//| import usb +//| +//| spi = board.SPI() +//| cs = board.D10 +//| irq = board.D9 +//| +//| host_chip = max3421e.Max3421E(spi, chip_select=cs, irq=irq) +//| +//| while True: +//| print("Finding devices:") +//| for device in usb.core.find(find_all=True): +//| print(f"{device.idVendor:04x}:{device.idProduct:04x}: {device.manufacturer} {device.product}") +//| time.sleep(5) +//| +//| """ + +STATIC const mp_rom_map_elem_t max3421e_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_max3421e) }, + + { MP_ROM_QSTR(MP_QSTR_Max3421E), MP_ROM_PTR(&max3421e_max3421e_type) }, +}; +STATIC MP_DEFINE_CONST_DICT(max3421e_module_globals, max3421e_module_globals_table); + +const mp_obj_module_t max3421e_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&max3421e_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_max3421e, max3421e_module); diff --git a/shared-bindings/max3421e/__init__.h b/shared-bindings/max3421e/__init__.h new file mode 100644 index 0000000000000..82eb9f8fbf335 --- /dev/null +++ b/shared-bindings/max3421e/__init__.h @@ -0,0 +1,27 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#pragma once diff --git a/shared-module/board/__init__.c b/shared-module/board/__init__.c index 08a9761ed5f7e..2b0a29248e2b5 100644 --- a/shared-module/board/__init__.c +++ b/shared-module/board/__init__.c @@ -229,8 +229,6 @@ void reset_board_buses(void) { } #endif if (spi_obj_created[instance]) { - // make sure SPI lock is not held over a soft reset - common_hal_busio_spi_unlock(&spi_obj[instance]); if (!display_using_spi) { common_hal_busio_spi_deinit(&spi_obj[instance]); spi_obj_created[instance] = false; diff --git a/shared-module/max3421e/Max3421E.c b/shared-module/max3421e/Max3421E.c new file mode 100644 index 0000000000000..b1bb6e1644452 --- /dev/null +++ b/shared-module/max3421e/Max3421E.c @@ -0,0 +1,192 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#include "shared-bindings/max3421e/Max3421E.h" + +#include + +#include "py/gc.h" +#include "py/runtime.h" +#include "shared-bindings/busio/SPI.h" +#include "shared-bindings/digitalio/DigitalInOut.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/time/__init__.h" +#include "supervisor/background_callback.h" +#include "supervisor/usb.h" + +#include "tusb.h" + +max3421e_max3421e_obj_t *_active; + +// Use CP's background callbacks to run the "interrupt" handler. The GPIO ISR +// stack is too small to do the SPI transactions that the interrupt handler does. +static background_callback_t tuh_callback; + +void common_hal_max3421e_max3421e_construct(max3421e_max3421e_obj_t *self, + busio_spi_obj_t *spi, const mcu_pin_obj_t *chip_select, + const mcu_pin_obj_t *irq, uint32_t baudrate) { + + #if CIRCUITPY_USB_HOST + if (tuh_inited()) { + mp_raise_RuntimeError_varg(MP_ERROR_TEXT("%q in use"), MP_QSTR_usb_host); + } + #endif + + if (_active != NULL && !common_hal_max3421e_max3421e_deinited(_active)) { + mp_raise_RuntimeError_varg(MP_ERROR_TEXT("%q in use"), MP_QSTR_max3421e); + return; + } + + common_hal_digitalio_digitalinout_construct(&self->chip_select, chip_select); + common_hal_digitalio_digitalinout_switch_to_output(&self->chip_select, true, DRIVE_MODE_PUSH_PULL); + + common_hal_digitalio_digitalinout_construct(&self->irq, irq); + + common_hal_max3421e_max3421e_init_irq(self); + + self->bus = spi; + self->baudrate = baudrate; + _active = self; + + tuh_configure(CIRCUITPY_USB_MAX3421_INSTANCE, 0, NULL); + tuh_init(CIRCUITPY_USB_MAX3421_INSTANCE); +} + +bool common_hal_max3421e_max3421e_deinited(max3421e_max3421e_obj_t *self) { + return self->bus == NULL; +} + +void common_hal_max3421e_max3421e_deinit(max3421e_max3421e_obj_t *self) { + if (common_hal_max3421e_max3421e_deinited(self)) { + return; + } + common_hal_max3421e_max3421e_deinit_irq(self); + + tuh_deinit(CIRCUITPY_USB_MAX3421_INSTANCE); + + common_hal_digitalio_digitalinout_deinit(&self->chip_select); + common_hal_digitalio_digitalinout_deinit(&self->irq); + self->bus = NULL; + + if (_active == self) { + _active = NULL; + } +} + +// TinyUSB uses the frame number from the host interface to measure time but it +// isn't updated for the Max3421E in an interrupt. Instead, use CP time keeping +// and run the interrupt handler as needed. Leave it in the background queue +// anyway. Don't run background tasks because this function is used by +// tuh_task() which is run as a background task. +#if CFG_TUSB_OS == OPT_OS_NONE +void osal_task_delay(uint32_t msec) { + uint32_t end_time = common_hal_time_monotonic_ms() + msec; + while (common_hal_time_monotonic_ms() < end_time) { + if (tuh_callback.prev != NULL) { + tuh_int_handler(CIRCUITPY_USB_MAX3421_INSTANCE, false); + } + } +} +#endif + +static void tuh_interrupt_callback(void *data) { + max3421e_max3421e_obj_t *self = (max3421e_max3421e_obj_t *)data; + // Check that the stack is still going. This callback may have been delayed + // enough that it was deinit in the meantime. + if (tuh_inited()) { + // Try and lock the spi bus. If we can't, then we may be in the middle of a transaction (it + // calls RUN_BACKGROUND_TASKS.) + if (!common_hal_busio_spi_try_lock(self->bus)) { + // Come back to us. + background_callback_add(&tuh_callback, tuh_interrupt_callback, (void *)self); + + return; + } + // Unlock the bus so the interrupt handler can use it. + common_hal_busio_spi_unlock(self->bus); + tuh_int_handler(CIRCUITPY_USB_MAX3421_INSTANCE, false); + common_hal_max3421e_max3421e_irq_enabled(self, true); + usb_background_schedule(); + } +} + +// Ports must call this from their interrupt handler. +void max3421e_interrupt_handler(max3421e_max3421e_obj_t *arg) { + max3421e_max3421e_obj_t *self = (max3421e_max3421e_obj_t *)arg; + // Schedule the CP background callback. + background_callback_add(&tuh_callback, tuh_interrupt_callback, (void *)self); + common_hal_max3421e_max3421e_irq_enabled(self, false); +} + +// API to control MAX3421 SPI CS +void tuh_max3421_spi_cs_api(uint8_t rhport, bool active) { + max3421e_max3421e_obj_t *self = _active; + // The SPI bus may be deinit before us so check for that. + if (common_hal_busio_spi_deinited(self->bus)) { + self->bus_locked = false; + return; + } + if (active) { + assert(self->bus_locked == false); + if (!common_hal_busio_spi_try_lock(self->bus)) { + return; + } + self->bus_locked = true; + common_hal_busio_spi_configure(self->bus, self->baudrate, 0, 0, 8); + common_hal_digitalio_digitalinout_set_value(&self->chip_select, false); + } else if (self->bus_locked) { + common_hal_digitalio_digitalinout_set_value(&self->chip_select, true); + common_hal_busio_spi_unlock(self->bus); + self->bus_locked = false; + } +} + +// API to transfer data with MAX3421 SPI +// Either tx_buf or rx_buf can be NULL, which means transfer is write or read only +extern bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const *tx_buf, uint8_t *rx_buf, size_t xfer_bytes) { + max3421e_max3421e_obj_t *self = _active; + if (!self->bus_locked || common_hal_busio_spi_deinited(self->bus)) { + return false; + } + if (tx_buf == NULL) { + return common_hal_busio_spi_read(self->bus, rx_buf, xfer_bytes, 0xff); + } + if (rx_buf == NULL) { + return common_hal_busio_spi_write(self->bus, tx_buf, xfer_bytes); + } + return common_hal_busio_spi_transfer(self->bus, tx_buf, rx_buf, xfer_bytes); +} + +// API to enable/disable MAX3421 INTR pin interrupt +void tuh_max3421_int_api(uint8_t rhport, bool enabled) { + max3421e_max3421e_obj_t *self = _active; + // Always turn off the interrupt if the SPI bus is deinit. + if (common_hal_busio_spi_deinited(self->bus)) { + enabled = false; + } + common_hal_max3421e_max3421e_irq_enabled(self, enabled); +} diff --git a/shared-module/max3421e/Max3421E.h b/shared-module/max3421e/Max3421E.h new file mode 100644 index 0000000000000..f434c1abef9e7 --- /dev/null +++ b/shared-module/max3421e/Max3421E.h @@ -0,0 +1,56 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#pragma once + +#include "common-hal/busio/SPI.h" +#include "common-hal/digitalio/DigitalInOut.h" +#include "shared-module/displayio/Group.h" + +#define CIRCUITPY_USB_MAX3421_INSTANCE 2 + +typedef struct { + mp_obj_base_t base; + busio_spi_obj_t *bus; + digitalio_digitalinout_obj_t chip_select; + digitalio_digitalinout_obj_t irq; + uint32_t baudrate; + bool bus_locked; +} max3421e_max3421e_obj_t; + +// Ports need to implement these two functions in order to support pin interrupts. + +// Setup irq on self->irq to call tuh_int_handler(rhport, true) on falling edge +void common_hal_max3421e_max3421e_init_irq(max3421e_max3421e_obj_t *self); + +// Deinit the irq +void common_hal_max3421e_max3421e_deinit_irq(max3421e_max3421e_obj_t *self); + +// Enable or disable the irq interrupt. +void common_hal_max3421e_max3421e_irq_enabled(max3421e_max3421e_obj_t *self, bool enabled); + +// Queue up the actual interrupt handler for when the SPI bus is free. +void max3421e_interrupt_handler(max3421e_max3421e_obj_t *self); diff --git a/shared-module/max3421e/__init__.c b/shared-module/max3421e/__init__.c new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/supervisor/shared/usb/host_keyboard.c b/supervisor/shared/usb/host_keyboard.c index cdbc1caae0a4f..1342c3b8c91c6 100644 --- a/supervisor/shared/usb/host_keyboard.c +++ b/supervisor/shared/usb/host_keyboard.c @@ -329,6 +329,9 @@ void usb_keyboard_detach(uint8_t dev_addr, uint8_t interface) { if (!usb_keyboard_in_use(dev_addr, interface)) { return; } + // No more key repeats + old_buf = NULL; + tuh_hid_receive_abort(dev_addr, interface); _dev_addr = 0; _interface = 0; diff --git a/supervisor/shared/usb/tusb_config.h b/supervisor/shared/usb/tusb_config.h index d097005aacbf5..5857f7d157bc3 100644 --- a/supervisor/shared/usb/tusb_config.h +++ b/supervisor/shared/usb/tusb_config.h @@ -52,6 +52,9 @@ extern "C" { #if CIRCUITPY_DEBUG_TINYUSB > 0 && defined(CIRCUITPY_CONSOLE_UART) #define CFG_TUSB_DEBUG CIRCUITPY_DEBUG_TINYUSB #define CFG_TUSB_DEBUG_PRINTF console_uart_printf + +// Raise the device log level to 3 so we can debug host-only at level 2. +#define CFG_TUD_LOG_LEVEL 3 #endif /*------------- RTOS -------------*/ @@ -126,11 +129,15 @@ extern "C" { // HOST CONFIGURATION // -------------------------------------------------------------------- -#if CIRCUITPY_USB_HOST +#if CIRCUITPY_USB_HOST || CIRCUITPY_MAX3421E #define CFG_TUH_ENABLED 1 // Always use PIO to do host on RP2. +#if !CIRCUITPY_MAX3421E +#define CFG_TUH_RPI_PIO_USB 1 +#else #define CFG_TUH_RPI_PIO_USB 1 +#endif #if CIRCUITPY_USB_HOST_INSTANCE == 0 #if USB_HIGHSPEED @@ -151,7 +158,12 @@ extern "C" { #define CFG_TUH_ENUMERATION_BUFSIZE 256 #endif +#if CIRCUITPY_USB_KEYBOARD_WORKFLOW #define CFG_TUH_HID 2 +#else +#define CFG_TUH_HID 0 +#endif + // 2 hubs so we can support "7 port" hubs which have two internal hubs. #define CFG_TUH_HUB 2 #define CFG_TUH_CDC 0 @@ -165,6 +177,9 @@ extern "C" { // Number of endpoints per device #define CFG_TUH_ENDPOINT_MAX 8 +// Enable MAX3421E support +#define CFG_TUH_MAX3421 (CIRCUITPY_MAX3421E) + #endif #ifdef __cplusplus diff --git a/supervisor/shared/usb/usb.c b/supervisor/shared/usb/usb.c index a536f154aa005..19a2a09921e52 100644 --- a/supervisor/shared/usb/usb.c +++ b/supervisor/shared/usb/usb.c @@ -168,7 +168,7 @@ void usb_background(void) { if (usb_enabled()) { #if CFG_TUSB_OS == OPT_OS_NONE || CFG_TUSB_OS == OPT_OS_PICO tud_task(); - #if CIRCUITPY_USB_HOST + #if CIRCUITPY_USB_HOST || CIRCUITPY_MAX3421E tuh_task(); #endif #elif CFG_TUSB_OS == OPT_OS_FREERTOS diff --git a/supervisor/supervisor.mk b/supervisor/supervisor.mk index 8299951f583c2..5848a7ed2b38c 100644 --- a/supervisor/supervisor.mk +++ b/supervisor/supervisor.mk @@ -176,14 +176,26 @@ ifeq ($(CIRCUITPY_USB),1) endif - ifeq ($(CIRCUITPY_USB_HOST), 1) + # This is a string comparison! + ifneq ($(CIRCUITPY_USB_HOST)$(CIRCUITPY_MAX3421E), 00) SRC_SUPERVISOR += \ - lib/tinyusb/src/class/hid/hid_host.c \ lib/tinyusb/src/host/hub.c \ lib/tinyusb/src/host/usbh.c \ + + endif + + ifeq ($(CIRCUITPY_USB_KEYBOARD_WORKFLOW), 1) + SRC_SUPERVISOR += \ + lib/tinyusb/src/class/hid/hid_host.c \ supervisor/shared/usb/host_keyboard.c \ endif + + ifeq ($(CIRCUITPY_MAX3421E), 1) + SRC_SUPERVISOR += \ + lib/tinyusb/src/portable/analog/max3421/hcd_max3421.c \ + + endif endif STATIC_RESOURCES = $(wildcard $(TOP)/supervisor/shared/web_workflow/static/*)