diff --git a/firmware/code/run.c b/firmware/code/run.c index 18a0944..8008021 100644 --- a/firmware/code/run.c +++ b/firmware/code/run.c @@ -34,6 +34,7 @@ #include "pico/stdlib.h" #include "pico/usb_device.h" #include "pico/multicore.h" +#include "pico/bootrom.h" #include "AudioClassCommon.h" #include "run.h" @@ -59,6 +60,10 @@ static struct { .freq = 48000, }; +enum vendor_cmds { + REBOOT_BOOTLOADER = 0 +}; + int main(void) { setup(); @@ -646,6 +651,20 @@ static bool do_set_current(struct usb_setup_packet *setup) { return false; } +static bool ad_setup_request_handler(__unused struct usb_device *device, struct usb_setup_packet *setup) { + setup = __builtin_assume_aligned(setup, 4); + if (USB_REQ_TYPE_TYPE_VENDOR == (setup->bmRequestType & USB_REQ_TYPE_TYPE_MASK)) { + // To prevent badly behaving software from accidentally triggering a reboot, e expect + // the wValue to be equal to the Ploopy vendor id. + if (setup->bRequest == REBOOT_BOOTLOADER && setup->wValue == 0x2E8A) { + reset_usb_boot(0, 0); + // reset_usb_boot does not return, so we will not respond to this command. + return true; + } + } + return false; +} + static bool ac_setup_request_handler(__unused struct usb_interface *interface, struct usb_setup_packet *setup) { setup = __builtin_assume_aligned(setup, 4); if (USB_REQ_TYPE_TYPE_CLASS == (setup->bmRequestType & USB_REQ_TYPE_TYPE_MASK)) { @@ -726,6 +745,8 @@ void usb_sound_card_init() { count_of(boot_device_interfaces), _get_descriptor_string); assert(device); + + device->setup_request_handler = ad_setup_request_handler; audio_set_volume(DEFAULT_VOLUME); _audio_reconfigure(); diff --git a/firmware/tools/README.md b/firmware/tools/README.md index d67ca65..8bc9d82 100644 --- a/firmware/tools/README.md +++ b/firmware/tools/README.md @@ -17,7 +17,21 @@ Run `filter_test` to process the PCM samples. The `filter_test` program takes tw You can listen to the PCM files using ffplay (which is usually included with ffmpeg): ``` -./ffplay -f s16le -ar 48000 -ac 2 output.pcm +ffplay -f s16le -ar 48000 -ac 2 output.pcm ``` If there are no obvious problems, go ahead and flash your firmware. + +## reboot_bootloader.py +If your Ploopy Headphones firmware is new enough, it has support for a USB vendor command that will cause the RP2040 to reboot into the +bootloader. This will enable you to update the firmware without having to remove the case and short the pins on the board. + +### Usage +Connect the Ploopy headphones DAC and run: + +``` +./reboot_bootloader.py +``` + +You will need python3 and the pyusb module. If you get a permission denied error you may need to configure your udev rules, or run the +script with administrator privileges. \ No newline at end of file diff --git a/firmware/tools/reboot_bootloader.py b/firmware/tools/reboot_bootloader.py new file mode 100755 index 0000000..db3b077 --- /dev/null +++ b/firmware/tools/reboot_bootloader.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 +import usb.core +from usb.util import * + +REBOOT_BOOTLOADER = 0 +PLOOPY_VID = 0x2e8a +PLOOPY_PID = 0xfedd + +device_count = 0 + +# Find all connected Ploopy headphone devices +for dev in usb.core.find(find_all=True, idVendor=PLOOPY_VID, idProduct=PLOOPY_PID): + device_count += 1 + + # The vendor command expects the wValue to be equal to the Ploopy vendor id. This + # will hopefully prevent badly behaved software from accidentally triggering bootloader + # mode. + try: + dev.ctrl_transfer(CTRL_RECIPIENT_DEVICE | CTRL_TYPE_VENDOR, REBOOT_BOOTLOADER, PLOOPY_VID) + except Exception as e: + # The headphones do not respond to the vendor command, as they have already rebooted, + # so for now, we always end up here. + if e.errno == 32: + # libusb pipe error, this is expected. OpenUSB doesnt report an error. + pass + else: + print(e) + +print(f"Sent a reboot command to {device_count} Ploopy devices.") \ No newline at end of file