/** * Copyright 2022 Colin Lam, Ploopy Corporation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPECIAL THANKS TO: * @kilograham (github.com/kilograham) * for his exceptional work on Pico Playground's usb-sound-card, on which * a large portion of this work is based. */ #include #include #include #include #include #include "hardware/vreg.h" #include "hardware/pwm.h" #include "hardware/i2c.h" #include "hardware/sync.h" #include "pico/stdlib.h" #include "pico/usb_device.h" #include "pico/usb_stream_helper.h" #include "pico/multicore.h" #include "pico/bootrom.h" #include "pico/unique_id.h" #include "AudioClassCommon.h" #include "run.h" #include "ringbuf.h" #include "i2s.h" #include "bqf.h" #include "os_descriptors.h" #include "configuration_manager.h" i2s_obj_t i2s_write_obj; static uint8_t *userbuf; audio_state_config audio_state = { .freq = 48000, .de_emphasis_frequency = 0x1, // 48khz .interface = 0 }; preprocessing_config preprocessing = { .preamp = fix16_one, .postEQGain = fix16_one, .reverse_stereo = false }; static char spi_serial_number[17] = ""; enum vendor_cmds { REBOOT_BOOTLOADER = 0, MICROSOFT_COMPATIBLE_ID_FEATURE_DESRIPTOR }; int main(void) { setup(); // Ask the configuration_manager to load a user config from flash, // or use the defaults. load_config(); // start second core (called "core 1" in the SDK) multicore_launch_core1(core1_entry); multicore_fifo_push_blocking((uintptr_t) userbuf); uint32_t ready = multicore_fifo_pop_blocking(); if (ready != CORE1_READY) { //printf("core 1 startup sequence is hella borked") exit(1); } usb_sound_card_init(); while (true) __wfi(); } static void update_volume() { if (audio_state._volume != audio_state._target_volume) { // PCM3060 volume attenuation: // 0: 0db (default) // 55: -100db // 56..: Mute uint8_t buf[3]; buf[0] = 65; // register addr buf[1] = 255 + (audio_state.target_volume[0] / 128); // data left buf[2] = 255 + (audio_state.target_volume[1] / 128); // data right i2c_write_blocking(i2c0, PCM_I2C_ADDR, buf, 3, false); audio_state._volume = audio_state._target_volume; } if (audio_state.pcm3060_registers != audio_state._target_pcm3060_registers) { uint8_t buf[3]; buf[0] = 68; // register addr buf[1] = audio_state.target_pcm3060_registers[0]; // Reg 68 buf[2] = audio_state.target_pcm3060_registers[1]; // Reg 69 i2c_write_blocking(i2c0, PCM_I2C_ADDR, buf, 3, false); audio_state.pcm3060_registers = audio_state._target_pcm3060_registers; } } // Here's the meat. It's where the data buffer from USB gets transformed from // PCM data into I2S data that gets shipped out to the PCM3060. It really // belongs with the other USB-related code due to its utter indecipherability, // but it's placed here to emphasize its importance. static void __no_inline_not_in_flash_func(_as_audio_packet)(struct usb_endpoint *ep) { struct usb_buffer *usb_buffer = usb_current_out_packet_buffer(ep); int16_t *in = (int16_t *) usb_buffer->data; int32_t *out = (int32_t *) userbuf; int samples = usb_buffer->data_len / 2; // Make sure core 1 is ready for us. multicore_fifo_pop_blocking(); if (save_config()) { // Skip processing while we are writing to flash multicore_fifo_push_blocking(CORE0_ABORTED); // keep on truckin' usb_grow_transfer(ep->current_transfer, 1); usb_packet_done(ep); return; } if (preprocessing.reverse_stereo) { for (int i = 0; i < samples; i+=2) { out[i] = in[i+1]; out[i+1] = in[i]; } } else { for (int i = 0; i < samples; i++) out[i] = in[i]; } multicore_fifo_push_blocking(CORE0_READY); multicore_fifo_push_blocking(samples); // Left channel filter for (int i = 0; i < samples; i += 2) { fix3_28_t x_f16 = fix16_mul(norm_fix3_28_from_s16sample((int16_t) out[i]), preprocessing.preamp); for (int j = 0; j < filter_stages; j++) { x_f16 = bqf_transform(x_f16, &bqf_filters_left[j], &bqf_filters_mem_left[j]); } /* Apply post-EQ gain. */ x_f16 = fix16_mul( x_f16, preprocessing.postEQGain); out[i] = (int32_t) norm_fix3_28_to_s16sample(x_f16); } // Block until core 1 has finished transforming the data uint32_t ready = multicore_fifo_pop_blocking(); multicore_fifo_push_blocking(CORE0_READY); // Update the volume if required. We do this from core1 as // core0 is more heavily loaded, doing this from core0 can // lead to audio crackling. update_volume(); // Update filters if required apply_config_changes(); // keep on truckin' usb_grow_transfer(ep->current_transfer, 1); usb_packet_done(ep); } void __no_inline_not_in_flash_func(core1_entry)() { uint8_t *userbuf = (uint8_t *) multicore_fifo_pop_blocking(); int32_t *out = (int32_t *) userbuf; // Signal that the thread has started multicore_fifo_push_blocking(CORE1_READY); while (true) { // Signal to core 0 that we are ready to accept new data multicore_fifo_push_blocking(CORE1_READY); // Block until the userbuf is filled with data uint32_t ready = multicore_fifo_pop_blocking(); if (ready == CORE0_ABORTED) continue; const uint32_t samples = multicore_fifo_pop_blocking(); /* Right channel EQ. */ for (int i = 1; i < samples; i += 2) { /* Apply EQ pre-filter gain to avoid clipping. */ fix3_28_t x_f16 = fix16_mul(norm_fix3_28_from_s16sample((int16_t) out[i]), preprocessing.preamp); /* Apply the biquad filters one by one. */ for (int j = 0; j < filter_stages; j++) { x_f16 = bqf_transform(x_f16, &bqf_filters_right[j], &bqf_filters_mem_right[j]); } /* Apply post-EQ gain. */ x_f16 = fix16_mul( x_f16, preprocessing.postEQGain); out[i] = (int32_t) norm_fix3_28_to_s16sample(x_f16); } // Signal to core 0 that the data has all been transformed multicore_fifo_push_blocking(CORE1_READY); // Wait for Core 0 to finish running its filtering before we apply config updates multicore_fifo_pop_blocking(); i2s_stream_write(&i2s_write_obj, userbuf, samples * 4); } } void setup() { set_sys_clock_khz(SYSTEM_FREQ / 1000, true); sleep_ms(100); stdio_init_all(); for (int i=0; icurrent_transfer); struct usb_buffer *buffer = usb_current_in_packet_buffer(ep); assert(buffer->data_max >= 3); buffer->data_len = 3; ring_buf_t rb = i2s_write_obj.ring_buffer; uint32_t feedback; size_t lower_limit = (RINGBUF_LEN_IN_BYTES / 2) - (RINGBUF_LEN_IN_BYTES / 4); size_t upper_limit = (RINGBUF_LEN_IN_BYTES / 2) + (RINGBUF_LEN_IN_BYTES / 4); if (ringbuf_available_data(&rb) > upper_limit) { // slow down feedback = 47 << 14; } else if (ringbuf_available_data(&rb) < lower_limit) { // we need more data feedback = 49 << 14; } else feedback = 48 << 14; //double temp = rate * 0x00004000; //temp += (double)((temp >= 0) ? 0.5f : -0.5f); //uint32_t feedback = (uint32_t) temp; // todo lie thru our teeth for now //uint feedback = 48 << 14u; buffer->data[0] = feedback; buffer->data[1] = feedback >> 8u; buffer->data[2] = feedback >> 16u; // keep on truckin' usb_grow_transfer(ep->current_transfer, 1); usb_packet_done(ep); } static const struct usb_transfer_type as_transfer_type = { .on_packet = _as_audio_packet, .initial_packet_count = 1, }; static const struct usb_transfer_type as_sync_transfer_type = { .on_packet = _as_sync_packet, .initial_packet_count = 1, }; static struct usb_transfer as_transfer; static struct usb_transfer as_sync_transfer; static const struct usb_transfer_type config_in_transfer_type = { .on_packet = config_in_packet, .initial_packet_count = 1, }; static const struct usb_transfer_type config_out_transfer_type = { .on_packet = config_out_packet, .on_cancel = configuration_ep_on_cancel, .initial_packet_count = 1, }; static struct usb_transfer config_in_transfer; static struct usb_transfer config_out_transfer; static bool do_get_current(struct usb_setup_packet *setup) { if ((setup->bmRequestType & USB_REQ_TYPE_RECIPIENT_MASK) == USB_REQ_TYPE_RECIPIENT_INTERFACE) { switch (setup->wValue >> 8u) { case 1: { // mute usb_start_tiny_control_in_transfer((audio_state.mute != 0), 1); return true; } case 2: { // volume /* Current volume. See UAC Spec 1.0 p.77 */ const uint8_t cn = (uint8_t) setup->wValue; if (cn == AUDIO_CHANNEL_LEFT_FRONT) { usb_start_tiny_control_in_transfer(audio_state.target_volume[0], 2); } else if (cn == AUDIO_CHANNEL_RIGHT_FRONT) { usb_start_tiny_control_in_transfer(audio_state.target_volume[1], 2); } else { return false; } return true; } } } else if ((setup->bmRequestType & USB_REQ_TYPE_RECIPIENT_MASK) == USB_REQ_TYPE_RECIPIENT_ENDPOINT) { if ((setup->wValue >> 8u) == 1) { // endpoint frequency control /* Current frequency */ usb_start_tiny_control_in_transfer(audio_state.freq, 3); return true; } } return false; } static bool do_get_minimum(struct usb_setup_packet *setup) { if ((setup->bmRequestType & USB_REQ_TYPE_RECIPIENT_MASK) == USB_REQ_TYPE_RECIPIENT_INTERFACE) { switch (setup->wValue >> 8u) { case 2: { // volume usb_start_tiny_control_in_transfer(MIN_VOLUME, 2); return true; } } } return false; } static bool do_get_maximum(struct usb_setup_packet *setup) { if ((setup->bmRequestType & USB_REQ_TYPE_RECIPIENT_MASK) == USB_REQ_TYPE_RECIPIENT_INTERFACE) { switch (setup->wValue >> 8u) { case 2: { // volume usb_start_tiny_control_in_transfer(MAX_VOLUME, 2); return true; } } } return false; } static bool do_get_resolution(struct usb_setup_packet *setup) { if ((setup->bmRequestType & USB_REQ_TYPE_RECIPIENT_MASK) == USB_REQ_TYPE_RECIPIENT_INTERFACE) { switch (setup->wValue >> 8u) { case 2: { // volume usb_start_tiny_control_in_transfer(VOLUME_RESOLUTION, 2); return true; } } } return false; } static struct audio_control_cmd { uint8_t cmd; uint8_t type; uint8_t cs; uint8_t cn; uint8_t unit; uint8_t len; } audio_control_cmd_t; static void _audio_reconfigure() { switch (audio_state.freq) { case 44100: case 48000: break; default: audio_state.freq = 48000; } } static void audio_set_volume(int8_t channel, int16_t volume) { // volume is in the range 127.9961dB (0x7FFF) .. -127.9961dB (0x8001). 0x8000 = mute // the old code reported a min..max volume of -90.9961dB (0xA500) .. 0dB (0x0) if (volume == 0x8000) { // Mute case } else if (volume > (int16_t) MAX_VOLUME) { volume = MAX_VOLUME; } else if (volume < (int16_t) MIN_VOLUME) { volume = MIN_VOLUME; } if (channel == AUDIO_CHANNEL_LEFT_FRONT || channel == 0) { audio_state.target_volume[0] = volume; } if (channel == AUDIO_CHANNEL_RIGHT_FRONT || channel == 0) { audio_state.target_volume[1] = volume; } } static void audio_cmd_packet(struct usb_endpoint *ep) { assert(audio_control_cmd_t.cmd == AUDIO_REQ_SetCurrent); struct usb_buffer *buffer = usb_current_out_packet_buffer(ep); // printf("%s: CMD: %u, Type: %u, CS: %u, CN: %u, Unit: %u, Len: %u\n", __PRETTY_FUNCTION__, audio_control_cmd_t.cmd, audio_control_cmd_t.type, // audio_control_cmd_t.cs, audio_control_cmd_t.cn, audio_control_cmd_t.unit, audio_control_cmd_t.len); audio_control_cmd_t.cmd = 0; if (buffer->data_len >= audio_control_cmd_t.len) { if (audio_control_cmd_t.type == USB_REQ_TYPE_RECIPIENT_INTERFACE) { switch (audio_control_cmd_t.cs) { case 1: { // mute audio_state.mute = buffer->data[0] ? 0x3 : 0x0; break; } case 2: { // volume audio_set_volume(audio_control_cmd_t.cn, *(int16_t *) buffer->data); break; } } } else if (audio_control_cmd_t.type == USB_REQ_TYPE_RECIPIENT_ENDPOINT) { if (audio_control_cmd_t.cs == 1) { // endpoint frequency control uint32_t new_freq = (*(uint32_t *) buffer->data) & 0x00ffffffu; if (audio_state.freq != new_freq) { audio_state.freq = new_freq; _audio_reconfigure(); } } } } usb_start_empty_control_in_transfer_null_completion(); // todo is there error handling? } static const struct usb_transfer_type _audio_cmd_transfer_type = { .on_packet = audio_cmd_packet, .initial_packet_count = 1, }; static bool as_set_alternate(struct usb_interface *interface, uint alt) { assert(interface == &as_op_interface); audio_state.interface = alt; switch (alt) { case 0: power_down_dac(); return true; case 1: power_up_dac(); return true; default: return false; } } static bool do_set_current(struct usb_setup_packet *setup) { if (setup->wLength && setup->wLength < 64) { audio_control_cmd_t.cmd = AUDIO_REQ_SetCurrent; audio_control_cmd_t.type = setup->bmRequestType & USB_REQ_TYPE_RECIPIENT_MASK; audio_control_cmd_t.len = (uint8_t) setup->wLength; audio_control_cmd_t.unit = setup->wIndex >> 8u; audio_control_cmd_t.cs = setup->wValue >> 8u; audio_control_cmd_t.cn = (uint8_t) setup->wValue; usb_start_control_out_transfer(&_audio_cmd_transfer_type); return true; } return false; } static void _tf_send_control_in_ack(__unused struct usb_endpoint *endpoint, __unused struct usb_transfer *transfer) { //assert(endpoint == &usb_control_in); //assert(transfer == &_control_in_transfer); usb_debug("_tf_setup_control_ack\n"); static struct usb_transfer _control_out_transfer; usb_start_empty_transfer(usb_get_control_out_endpoint(), &_control_out_transfer, 0); } static struct usb_stream_transfer _control_in_stream_transfer; #define _control_in_transfer _control_in_stream_transfer.core static struct usb_stream_transfer_funcs control_stream_funcs = { .on_chunk = usb_stream_noop_on_chunk, .on_packet_complete = usb_stream_noop_on_packet_complete }; static bool ad_setup_request_handler(__unused struct usb_device *device, struct usb_setup_packet *setup) { setup = __builtin_assume_aligned(setup, 4); //("ad_setup_request_handler: Type %u, Request %u, Value %u, Index %u, Length %u\n", setup->bmRequestType, setup->bRequest, setup->wValue, setup->wIndex, setup->wLength); if (setup->bmRequestType & USB_DIR_IN) { if (USB_REQ_TYPE_RECIPIENT_DEVICE == (setup->bmRequestType & USB_REQ_TYPE_TYPE_MASK)) { if ((setup->bRequest == USB_REQUEST_GET_DESCRIPTOR) && ((setup->wValue >> 8) == 0xF /* BOS */)) { struct usb_endpoint *usb_control_in = usb_get_control_in_endpoint(); static struct usb_stream_transfer_funcs control_stream_funcs = { .on_chunk = usb_stream_noop_on_chunk, .on_packet_complete = usb_stream_noop_on_packet_complete }; int len = 0x21; len = MIN(len, setup->wLength); usb_stream_setup_transfer(&_control_in_stream_transfer, &control_stream_funcs, ms_platform_capability_bos_descriptor, sizeof(ms_platform_capability_bos_descriptor), len, _tf_send_control_in_ack); _control_in_stream_transfer.ep = usb_control_in; usb_start_transfer(usb_control_in, &_control_in_stream_transfer.core); return true; } } } 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) { power_down_dac(); reset_usb_boot(0, 0); // reset_usb_boot does not return, so we will not respond to this command. return true; } else if (USB_REQ_TYPE_RECIPIENT_DEVICE == (setup->bmRequestType & USB_REQ_TYPE_RECIPIENT_MASK) && setup->bRequest == MICROSOFT_COMPATIBLE_ID_FEATURE_DESRIPTOR && setup->wIndex == 0x7) { const int length = MIN(MS_OS_20_DESC_LEN, setup->wLength); //printf("Sending %u bytes (%u %u)\n", length, MS_OS_20_DESC_LEN, sizeof(desc_ms_os_20)); struct usb_endpoint *usb_control_in = usb_get_control_in_endpoint(); usb_stream_setup_transfer(&_control_in_stream_transfer, &control_stream_funcs, desc_ms_os_20, sizeof(desc_ms_os_20), length, _tf_send_control_in_ack); _control_in_stream_transfer.ep = usb_control_in; usb_start_transfer(usb_control_in, &_control_in_stream_transfer.core); return true; } } return false; } static struct usb_stream_transfer _config_in_stream_transfer; static bool configuration_interface_setup_request_handler(__unused struct usb_interface *interface, struct usb_setup_packet *setup) { setup = __builtin_assume_aligned(setup, 4); //printf("configuration_interface_setup_request_handler: Type %u, Request %u, Value %u, Index %u, Length %u\n", setup->bmRequestType, setup->bRequest, setup->wValue, setup->wIndex, setup->wLength); return false; } static bool ac_setup_request_handler(__unused struct usb_interface *interface, struct usb_setup_packet *setup) { setup = __builtin_assume_aligned(setup, 4); //printf("ac_setup_request_handler: Type %u, Request %u, Value %u, Index %u, Length %u\n", setup->bmRequestType, setup->bRequest, setup->wValue, setup->wIndex, setup->wLength); if (USB_REQ_TYPE_TYPE_CLASS == (setup->bmRequestType & USB_REQ_TYPE_TYPE_MASK)) { switch (setup->bRequest) { case AUDIO_REQ_SetCurrent: return do_set_current(setup); case AUDIO_REQ_GetCurrent: return do_get_current(setup); case AUDIO_REQ_GetMinimum: return do_get_minimum(setup); case AUDIO_REQ_GetMaximum: return do_get_maximum(setup); case AUDIO_REQ_GetResolution: return do_get_resolution(setup); default: break; } } return false; } bool _as_setup_request_handler(__unused struct usb_endpoint *ep, struct usb_setup_packet *setup) { setup = __builtin_assume_aligned(setup, 4); //printf("as_setup_request_handler: Type %u, Request %u, Value %u, Index %u, Length %u\n", setup->bmRequestType, setup->bRequest, setup->wValue, setup->wIndex, setup->wLength); if (USB_REQ_TYPE_TYPE_CLASS == (setup->bmRequestType & USB_REQ_TYPE_TYPE_MASK)) { switch (setup->bRequest) { case AUDIO_REQ_SetCurrent: return do_set_current(setup); case AUDIO_REQ_GetCurrent: return do_get_current(setup); case AUDIO_REQ_GetMinimum: return do_get_minimum(setup); case AUDIO_REQ_GetMaximum: return do_get_maximum(setup); case AUDIO_REQ_GetResolution: return do_get_resolution(setup); default: break; } } return false; } void usb_sound_card_init() { usb_interface_init(&ac_interface, &ad_conf.ac_interface, NULL, 0, true); ac_interface.setup_request_handler = ac_setup_request_handler; static struct usb_endpoint *const op_endpoints[] = { &ep_op_out, &ep_op_sync }; usb_interface_init(&as_op_interface, &ad_conf.as_op_interface, op_endpoints, count_of(op_endpoints), true); as_op_interface.set_alternate_handler = as_set_alternate; ep_op_out.setup_request_handler = _as_setup_request_handler; as_transfer.type = &as_transfer_type; usb_set_default_transfer(&ep_op_out, &as_transfer); as_sync_transfer.type = &as_sync_transfer_type; usb_set_default_transfer(&ep_op_sync, &as_sync_transfer); static struct usb_endpoint *const configuration_endpoints[] = { &ep_configuration_out, &ep_configuration_in }; usb_interface_init(&configuration_interface, &ad_conf.configuration_interface, configuration_endpoints, 2, true); configuration_interface.setup_request_handler = configuration_interface_setup_request_handler; config_in_transfer.type = &config_in_transfer_type; usb_set_default_transfer(&ep_configuration_in, &config_in_transfer); config_out_transfer.type = &config_out_transfer_type; usb_set_default_transfer(&ep_configuration_out, &config_out_transfer); static struct usb_interface *const boot_device_interfaces[] = { &ac_interface, &as_op_interface, &configuration_interface }; __unused struct usb_device *device = usb_device_init(&boot_device_descriptor, &ad_conf.descriptor, boot_device_interfaces, count_of(boot_device_interfaces), _get_descriptor_string); assert(device); device->setup_request_handler = ad_setup_request_handler; audio_set_volume(0, DEFAULT_VOLUME); _audio_reconfigure(); usb_device_start(); } // Some operations will cause popping on the audio output, temporarily // disabling the DAC sounds much better. void power_down_dac() { uint8_t buf[2]; buf[0] = 64; // register addr buf[1] = 0xF0; // DAC low power mode i2c_write_blocking(i2c0, PCM_I2C_ADDR, buf, 2, false); } void power_up_dac() { uint8_t buf[2]; buf[0] = 64; // register addr buf[1] = 0xE0; // DAC normal mode i2c_write_blocking(i2c0, PCM_I2C_ADDR, buf, 2, false); } /***************************************************************************** * USB-related code ends here. ****************************************************************************/