diff --git a/docs/CREDITS b/docs/CREDITS index d1ac0cfdc7..d43382efb5 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -763,6 +763,7 @@ Eivind Ødegård Teun van Dalen Adam N. Burke Lê Khánh Bình +Michael McAllister The libmad team The wavpack team diff --git a/firmware/SOURCES b/firmware/SOURCES index 58bc753fc4..202575595f 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -187,6 +187,10 @@ target/hosted/hiby/hibylinux_codec.c #endif #endif +#if (defined(HIBY_R1) || defined(HIBY_R3PROII)) && !defined(SIMULATOR) +target/hosted/hiby/usb-dac-hiby.c +#endif + #if defined(AGPTEK_ROCKER) && !defined(SIMULATOR) target/hosted/agptek/button-agptek.c target/hosted/agptek/debug-agptek.c diff --git a/firmware/export/config/hibyr1.h b/firmware/export/config/hibyr1.h index f830791d9e..bb20b0b1a2 100644 --- a/firmware/export/config/hibyr1.h +++ b/firmware/export/config/hibyr1.h @@ -84,6 +84,7 @@ /* USB */ #define HAVE_USB_ADB +#define HAVE_HOST_USB_AUDIO #define HAVE_USB_POWER #define USB_VID_STR "32BB" #define USB_PID_STR "0101" diff --git a/firmware/export/config/hibyr3proii.h b/firmware/export/config/hibyr3proii.h index ffb0c69f16..92d338aee2 100644 --- a/firmware/export/config/hibyr3proii.h +++ b/firmware/export/config/hibyr3proii.h @@ -87,6 +87,7 @@ /* USB */ #define HAVE_USB_ADB +#define HAVE_HOST_USB_AUDIO #define HAVE_USB_POWER #define USB_VID_STR "32BB" #define USB_PID_STR "0101" diff --git a/firmware/target/hosted/hiby/usb-dac-hiby.c b/firmware/target/hosted/hiby/usb-dac-hiby.c new file mode 100644 index 0000000000..4b641a2314 --- /dev/null +++ b/firmware/target/hosted/hiby/usb-dac-hiby.c @@ -0,0 +1,214 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Michael McAllister + * + * 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 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* Host-PCM pump for the HiBy USB DAC gadget mode. + * + * The vendor kernel's UAC gadget function "uac_sa" exposes the char device + * /dev/uac_sa, which delivers the host's PCM: the isochronous OUT frames are + * converted to left-justified S16-in-S32 stereo and queued in a kernel ring, + * drained with read(). read() is non-blocking and returns -1 until at least + * the requested count (which must be <= the kernel ring/2) is buffered. + * + * A pump thread drains it into a small single-producer/single-consumer + * ring, the mixer's get_more callback hands that PCM to the CS43131 through + * the normal ALSA output path, the same way firmware/usbstack/usb_audio.c + * drives its host-PCM playback. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "kernel.h" +#include "pcm_mixer.h" +#include "pcm_sampr.h" +#include "system.h" +#include "thread.h" +#include "usb.h" +#include "usb-dac-hiby.h" + +/*#define LOGF_ENABLE*/ +#include "logf.h" + +#define UAC_SA_DEV "/dev/uac_sa" +/* ioctl: fills int[3] = { fmt, rate_hz, bits } */ +#define UAC_SA_GET_STATUS 1 +/* uac_sa delivers S32 stereo: 8 bytes per frame */ +#define UAC_SA_FRAME_SIZE 8 + +/* Ring of S16 stereo frames. A power of two so head/tail wrap cleanly. */ +#define DAC_RING_FRAMES 8192 /* ~186 ms at 44.1 kHz */ +#define DAC_CHUNK_FRAMES 512 /* mixer buffer granularity */ +/* one read()'s worth. Must stay <= the kernel ring/2 */ +#define DAC_READ_BYTES (DAC_CHUNK_FRAMES * UAC_SA_FRAME_SIZE) + +static int16_t dac_ring[DAC_RING_FRAMES * 2]; +static volatile unsigned int dac_head; /* frames produced; pump only */ +static volatile unsigned int dac_tail; /* frames consumed; mixer only */ +static pthread_t dac_thread; +static volatile bool dac_running; +static int dac_fd = -1; + +/* Mixer callback (PCM feed context). Returns the next chunk from the ring, + * zero-padded with silence on underrun so the stream never stalls. Double + * buffered so the returned pointer stays valid until the following call. */ +static void dac_get_more(const void **start, size_t *size) +{ + static int16_t out[2][DAC_CHUNK_FRAMES * 2]; + static int which; + int16_t *buf; + + which ^= 1; + buf = out[which]; + + unsigned int tail = dac_tail; + unsigned int avail = dac_head - tail; /* wrap-safe */ + avail = MIN(avail, DAC_CHUNK_FRAMES); + + for (unsigned int i = 0; i < avail; i++) + { + unsigned int idx = (tail + i) & (DAC_RING_FRAMES - 1); + buf[2 * i] = dac_ring[2 * idx]; + buf[2 * i + 1] = dac_ring[2 * idx + 1]; + } + memset(&buf[2 * avail], 0, + (DAC_CHUNK_FRAMES - avail) * 2 * sizeof(int16_t)); + dac_tail = tail + avail; + + *start = buf; + *size = DAC_CHUNK_FRAMES * 2 * sizeof(int16_t); +} + +/* Drain /dev/uac_sa into the ring. The driver's only file + * operations are open/read/ioctl (there is no poll()), so retry with a + * short sleep when the host has not queued a full chunk yet. + * + * This is a raw pthread, not a Rockbox thread, so it must use usleep() + * rather than the cooperative sleep() (which only the scheduler's own + * threads may call). */ +static void *dac_pump_thread(void *arg) +{ + int32_t rbuf[DAC_READ_BYTES / sizeof(int32_t)]; + (void)arg; + + while (dac_running) + { + unsigned int head = dac_head; + + /* Leave room for a full read; else wait for the mixer to drain */ + if (head - dac_tail > DAC_RING_FRAMES - DAC_CHUNK_FRAMES) + { + usleep(1000); + continue; + } + + ssize_t n = read(dac_fd, rbuf, DAC_READ_BYTES); + if (n <= 0) + { + usleep(1000); /* no host data queued yet */ + continue; + } + + int frames = n / UAC_SA_FRAME_SIZE; + for (int i = 0; i < frames; i++) + { + unsigned int idx = (head + i) & (DAC_RING_FRAMES - 1); + dac_ring[2 * idx] = (int16_t)(rbuf[2 * i] >> 16); + dac_ring[2 * idx + 1] = (int16_t)(rbuf[2 * i + 1] >> 16); + } + dac_head = head + frames; + } + + return NULL; +} + +bool usb_dac_start(void) +{ + static const struct mixer_play_cbs cbs = { .get_more = dac_get_more }; + int st[3] = {0, 0, 0}; + unsigned int rate; + + /* The node is created asynchronously when the UDC binds; during boot + * the hotplug helper can lag behind, so wait briefly for it. This runs + * on a Rockbox thread, so sleep() yields to the cooperative scheduler + * instead of stalling every other thread the way usleep() would. */ + for (int tries = 50; tries > 0; tries--) + { + dac_fd = open(UAC_SA_DEV, O_RDWR); + if (dac_fd >= 0) + break; + sleep(HZ / 50); /* 20 ms */ + } + if (dac_fd < 0) + { + logf("uac_sa open failed (%d)", errno); + return false; + } + + /* Play at the rate the host negotiated (reported by the kernel). */ + ioctl(dac_fd, UAC_SA_GET_STATUS, st); + if (st[1] >= SAMPR_8 && st[1] <= SAMPR_192) + rate = (unsigned int)st[1]; + else + rate = SAMPR_44; + mixer_set_frequency(rate); + + dac_head = dac_tail = 0; + dac_running = true; + if (pthread_create(&dac_thread, NULL, dac_pump_thread, NULL) != 0) + { + logf("uac_sa pump thread failed"); + dac_running = false; + close(dac_fd); + dac_fd = -1; + return false; + } + + mixer_channel_set_amplitude(PCM_MIXER_CHAN_USBAUDIO, MIX_AMP_UNITY); + mixer_channel_play_data(PCM_MIXER_CHAN_USBAUDIO, &cbs, NULL, 0); + return true; +} + +void usb_dac_stop(void) +{ + if (!dac_running) + return; + + dac_running = false; + pthread_join(dac_thread, NULL); + mixer_channel_stop(PCM_MIXER_CHAN_USBAUDIO); + + if (dac_fd >= 0) + { + close(dac_fd); + dac_fd = -1; + } +} + +bool usb_audio_get_active(void) +{ + return dac_running; +} diff --git a/firmware/target/hosted/hiby/usb-dac-hiby.h b/firmware/target/hosted/hiby/usb-dac-hiby.h new file mode 100644 index 0000000000..0e1c85d0a9 --- /dev/null +++ b/firmware/target/hosted/hiby/usb-dac-hiby.h @@ -0,0 +1,34 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Michael McAllister + * + * 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 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef __USB_DAC_HIBY_H__ +#define __USB_DAC_HIBY_H__ + +/** + * Host-PCM pump for the HiBy USB DAC gadget mode: drains /dev/uac_sa + * into the PCM mixer. Started/stopped by the USB gadget driver when the + * uac_sa function is brought up/torn down. + * + * usb_dac_start() returns false if the uac_sa device never appears (the + * pump is not started); the caller should fall back to a safe USB mode. + */ +bool usb_dac_start(void); +void usb_dac_stop(void); + +#endif /* __USB_DAC_HIBY_H__ */ diff --git a/firmware/target/hosted/hiby/usb-hiby-gadget.c b/firmware/target/hosted/hiby/usb-hiby-gadget.c index af04690248..5f9f055c0c 100644 --- a/firmware/target/hosted/hiby/usb-hiby-gadget.c +++ b/firmware/target/hosted/hiby/usb-hiby-gadget.c @@ -8,6 +8,7 @@ * * Copyright (C) 2018 by Marcin Bukat * Copyright (C) 2025 by Melissa Autumn + * Copyright (C) 2026 by Michael McAllister * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,6 +21,7 @@ ****************************************************************************/ #include +#include #include #include #include @@ -32,6 +34,12 @@ #include "power.h" #include "usb-hiby-gadget.h" +#include "audio.h" +#include "pcm.h" +#include "pcm_sampr.h" +#include "usb_ch9.h" +#include "usb-dac-hiby.h" + #ifdef HAVE_GENERAL_PURPOSE_LED #include "led-general-purpose.h" #endif @@ -40,14 +48,18 @@ #include "logf.h" #include +/* Configfs root of the gadget created by usb_init_device() */ +#define USB_GADGET_PATH "/sys/kernel/config/usb_gadget/adb_demo" + static int _usb_mode = -1; static bool _usb_init = false; static bool is_adb_running = false; +static int _usb_audio = 0; /* usb_audio setting: 0 never .. 3 while mass-storage */ void enable_adb(void); void enable_mass_storage(void); void enable_charging(void); -void enable_usb_audio(void); +bool enable_usb_audio(void); void disable_adb(void); void disable_mass_storage(void); @@ -58,16 +70,9 @@ void cleanup_rbhome(void); void startup_rbhome(void); #endif -void hiby_set_usb_mode(int mode) { - logf(">>>>>>>>>>>>>>>>> hiby_set_usb_mode(%d)\n", mode); - if (!_usb_init) { - logf("Need to init usb!\n"); - usb_init_device(); - } - - if (_usb_mode == mode) - return; - +/* Bring up the gadget for a base USB mode (mass storage / charge / adb). */ +static void configure_usb_mode(int mode) +{ switch(mode) { case USB_MODE_MASS_STORAGE: logf("Enabling Mass Storage\n"); @@ -84,8 +89,104 @@ void hiby_set_usb_mode(int mode) { default: break; } +} +/* Whether the USB-DAC function should be running, given the usb_audio + * setting (never/always/while_charge_only/while_mass_storage) and the + * current base USB mode. */ +static bool dac_should_run(void) +{ + switch (_usb_audio) { + case 1: return true; /* always */ + case 2: return _usb_mode == USB_MODE_CHARGE; /* while charge-only */ + case 3: return _usb_mode == USB_MODE_MASS_STORAGE; /* while mass-storage */ + default: return false; /* never */ + } +} + +/* Reconcile the DAC gadget with the setting + current mode. + * + * In adb mode the DAC rides alongside adb as a composite function, so we + * rebuild the adb gadget (enable_adb() composes uac_sa when dac_should_run()). + * In charge/mass-storage mode the DAC is a standalone self-bound gadget. */ +static void apply_usb_audio(void) +{ + if (_usb_mode == USB_MODE_ADB) { + enable_adb(); + return; + } + + bool want = dac_should_run(); + bool active = usb_audio_get_active(); + + if (want && !active) { + /* enable_usb_audio() returns false if applied before audio_init() + * (PCM mixer not up yet -- e.g. at boot) or if uac_sa never comes + * up. Either way restore the base mode so we never leave a dead + * gadget behind. */ + if (!enable_usb_audio()) + configure_usb_mode(_usb_mode); + } else if (!want && active) { + disable_usb_audio(); + configure_usb_mode(_usb_mode); + } +} + +/* usb_set_audio() is the target's implementation of the usb_audio setting + * callback declared in usb.h (HAVE_HOST_USB_AUDIO); it drives the DAC gadget + * up or down. */ +void usb_set_audio(int value) +{ + _usb_audio = value; + apply_usb_audio(); +} + +/* Compose uac_sa into the adb config, linked FIRST so the audio interfaces + * are 0-1 (the vendor uac_sa function hard-codes them), with the DAC VID/PID + * + IAD descriptor. Returns true if it composed the DAC. Skipped before + * audio_init() (no PCM mixer yet); re-applied once it is up. */ +static bool adb_compose_dac(void) +{ + if (!dac_should_run() || !pcm_is_initialized()) + return false; + + sysfs_set_string(USB_GADGET_PATH "/idVendor", "0x" USB_VID_STR); + sysfs_set_string(USB_GADGET_PATH "/idProduct", "0x0004"); + sysfs_set_int(USB_GADGET_PATH "/bDeviceClass", USB_CLASS_MISC); + sysfs_set_int(USB_GADGET_PATH "/bDeviceSubClass", 2); + sysfs_set_int(USB_GADGET_PATH "/bDeviceProtocol", 1); + + system("mkdir -p " USB_GADGET_PATH "/functions/uac_sa.a"); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_chmask", 3); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_ssize", 2); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_srate", SAMPR_48); + system("ln -sf " USB_GADGET_PATH "/functions/uac_sa.a " USB_GADGET_PATH "/configs/c.1/"); + sysfs_set_string(USB_GADGET_PATH "/configs/c.1/strings/0x409/configuration", "uac,adb"); + return true; +} + +void hiby_set_usb_mode(int mode) { + logf(">>>>>>>>>>>>>>>>> hiby_set_usb_mode(%d)\n", mode); + if (!_usb_init) { + logf("Need to init usb!\n"); + usb_init_device(); + } + + if (_usb_mode == mode) + return; + + /* Drop any active DAC before reconfiguring the base gadget. */ + if (usb_audio_get_active()) + disable_usb_audio(); + + /* Set the mode first so enable_adb()/dac_should_run() see it. */ _usb_mode = mode; + configure_usb_mode(mode); + + /* adb composes the DAC itself in enable_adb(); charge/mass-storage get + * the standalone self-bound DAC here. */ + if (mode != USB_MODE_ADB) + apply_usb_audio(); } /* TODO: implement usb detection properly */ @@ -113,7 +214,9 @@ void usb_enable(bool on) logf(">>>>>>>>>>>>>>>>> usb_enable(%d)\n", on); logf("usb enable %d %d\n", on, _usb_mode); - if (_usb_mode == USB_MODE_ADB) + /* ADB binds the UDC itself, and so does the USB-DAC while it is active, + * so leave the UDC alone in those cases. */ + if (_usb_mode == USB_MODE_ADB || usb_audio_get_active()) return; // Re-arm the LUN on each connect, otherwise the disk is only exported the first time. @@ -164,35 +267,51 @@ void enable_charging(void) { disable_mass_storage(); disable_adb(); + disable_usb_audio(); } void enable_adb(void) { logf(">>>>>>>>>>>>>>>>> set_adb()\n"); - // Disable mass storage if it was running - disable_mass_storage(); - - // Remove any lingering adb daemon and its respawner + // Stop the respawner FIRST so it can't rebind adbd under us. system("killall adbserver.sh adbd 2>/dev/null"); - system("mkdir -p /sys/kernel/config/usb_gadget/adb_demo/configs/c.1/strings/0x409"); - system("mkdir -p /sys/kernel/config/usb_gadget/adb_demo/functions/ffs.adb"); + /* Disable mass storage and USB DAC if they were running (unbinds the UDC + * and clears any uac_sa link/descriptor). */ + disable_mass_storage(); + disable_usb_audio(); - // Use the adb VID/PID so the host recognises the device - sysfs_set_string("/sys/kernel/config/usb_gadget/adb_demo/idVendor", "0x18d1"); - sysfs_set_string("/sys/kernel/config/usb_gadget/adb_demo/idProduct", "0xd002"); + /* Also drop the adb function link so we control the link order below + * (uac_sa must be linked before adb -- see adb_compose_dac()). */ + if (access(USB_GADGET_PATH "/configs/c.1/ffs.adb", F_OK) == 0) + system("rm " USB_GADGET_PATH "/configs/c.1/ffs.adb"); - // Now we'll override configuration and MaxPower - sysfs_set_string("/sys/kernel/config/usb_gadget/adb_demo/configs/c.1/strings/0x409/configuration", "adb"); - sysfs_set_int("/sys/kernel/config/usb_gadget/adb_demo/configs/c.1/MaxPower", 120); + system("mkdir -p " USB_GADGET_PATH "/configs/c.1/strings/0x409"); + system("mkdir -p " USB_GADGET_PATH "/functions/ffs.adb"); - // And link up the adb function to the usb gadget config - system("ln -sf /sys/kernel/config/usb_gadget/adb_demo/functions/ffs.adb /sys/kernel/config/usb_gadget/adb_demo/configs/c.1/"); + /* Compose the USB-DAC (uac_sa, linked first) alongside adb when the + * usb_audio setting wants it; otherwise present plain adb. */ + bool with_dac = adb_compose_dac(); + if (!with_dac) { + // Use the adb VID/PID so the host recognises the device + sysfs_set_string(USB_GADGET_PATH "/idVendor", "0x18d1"); + sysfs_set_string(USB_GADGET_PATH "/idProduct", "0xd002"); + sysfs_set_string(USB_GADGET_PATH "/configs/c.1/strings/0x409/configuration", "adb"); + } + + sysfs_set_int(USB_GADGET_PATH "/configs/c.1/MaxPower", 120); + + // Link adb into the config (after uac_sa, so adb is the later interface) + system("ln -sf " USB_GADGET_PATH "/functions/ffs.adb " USB_GADGET_PATH "/configs/c.1/"); system("mkdir -p /dev/usb-ffs/adb"); // Let the vendor respawner mount functionfs and run adbd, which binds the UDC. system("/sbin/adbserver.sh 440 &"); + + /* uac_sa is bound by the same auto-bind; start pumping /dev/uac_sa. */ + if (with_dac) + usb_dac_start(); } @@ -230,8 +349,9 @@ void disable_adb(void) { void enable_mass_storage(void) { logf(">>>>>>>>>>>>>>>>> set_mass_storage()\n"); - // Disable adb if it's running + /* Disable adb and USB DAC if they're running */ disable_adb(); + disable_usb_audio(); system("mkdir -p /sys/kernel/config/usb_gadget/adb_demo/functions/mass_storage.0/lun.0"); system("mkdir -p /sys/kernel/config/usb_gadget/adb_demo/configs/c.1/strings/0x409"); @@ -270,18 +390,96 @@ void disable_mass_storage(void) { } } -#if defined(HAVE_USB_AUDIO) -void enable_usb_audio(void) { +/* USB DAC (USB Audio Class gadget). + * + * The vendor kernel provides a UAC function "uac_sa"; building it into the + * gadget and binding the UDC creates /dev/uac_sa, which usb-dac-hiby.c pumps + * into the PCM mixer. enable_usb_audio() is the standalone path used in + * charge/mass-storage mode; adb mode composes uac_sa alongside adb in + * enable_adb() instead. */ +bool enable_usb_audio(void) +{ + logf(">>>>>>>>>>>>>>>>> enable_usb_audio()\n"); + + /* The DAC pump feeds the PCM mixer, which isn't up until audio_init(), so + * a DAC setting applied at boot lands too early. Bail before touching + * anything so the caller can restore the base mode. */ + if (!pcm_is_initialized()) + return false; + + /* The DAC hands the output path to the host's PCM; stop local playback + * first (it can't restart while the DAC is active). */ + audio_stop(); + + /* Standalone DAC: tear down the other gadget functions first. */ + disable_mass_storage(); + disable_adb(); + + /* Build the "uac_sa" function: stereo, 16-bit, capture (host->device). + * Set all three explicitly: the kernel's defaults are 3/2/64000, so at + * least the 64 kHz capture rate must always be overridden. */ + system("mkdir -p " USB_GADGET_PATH "/functions/uac_sa.a"); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_chmask", 3); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_ssize", 2); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_srate", SAMPR_48); + + /* Present an IAD audio device with the HiBy DAC VID/PID so the host + * recognises it as a USB DAC. */ + sysfs_set_int(USB_GADGET_PATH "/bDeviceClass", USB_CLASS_MISC); + sysfs_set_int(USB_GADGET_PATH "/bDeviceSubClass", 2); + sysfs_set_int(USB_GADGET_PATH "/bDeviceProtocol", 1); + sysfs_set_string(USB_GADGET_PATH "/idVendor", "0x" USB_VID_STR); + sysfs_set_string(USB_GADGET_PATH "/idProduct", "0x0004"); + + system("mkdir -p " USB_GADGET_PATH "/configs/c.1/strings/0x409"); + sysfs_set_string(USB_GADGET_PATH + "/configs/c.1/strings/0x409/configuration", "uac"); + sysfs_set_int(USB_GADGET_PATH "/configs/c.1/bmAttributes", + USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER); + sysfs_set_int(USB_GADGET_PATH "/configs/c.1/MaxPower", 120); + + system("ln -sf " USB_GADGET_PATH "/functions/uac_sa.a " + USB_GADGET_PATH "/configs/c.1/"); + + /* Bind the UDC ourselves -- in charge mode the usb thread is power-only, + * and usb_enable() bails while the DAC is active, so nothing else binds + * it. This creates /dev/uac_sa. */ + sysfs_set_string(USB_GADGET_PATH "/UDC", "13500000.otg_new"); + + return usb_dac_start(); } -void disable_usb_audio(void) { +void disable_usb_audio(void) +{ + logf(">>>>>>>>>>>>>>>>> disable_usb_audio()\n"); + usb_dac_stop(); + + /* Unbind the UDC and tear the function down so the next mode starts + * from a clean gadget. */ + if (access(USB_GADGET_PATH "/UDC", F_OK) == 0) + sysfs_set_string(USB_GADGET_PATH "/UDC", "\n"); + + if (access(USB_GADGET_PATH "/configs/c.1/uac_sa.a", F_OK) == 0) + system("rm " USB_GADGET_PATH "/configs/c.1/uac_sa.a"); + + if (access(USB_GADGET_PATH "/functions/uac_sa.a", F_OK) == 0) + system("rmdir " USB_GADGET_PATH "/functions/uac_sa.a"); + + /* Restore a plain (non-IAD) device descriptor and the target's own + * VID/PID for the other modes. */ + sysfs_set_int(USB_GADGET_PATH "/bDeviceClass", USB_CLASS_PER_INTERFACE); + sysfs_set_int(USB_GADGET_PATH "/bDeviceSubClass", 0); + sysfs_set_int(USB_GADGET_PATH "/bDeviceProtocol", 0); + sysfs_set_string(USB_GADGET_PATH "/idVendor", "0x" USB_VID_STR); + sysfs_set_string(USB_GADGET_PATH "/idProduct", "0x" USB_PID_STR); + + if (access(USB_GADGET_PATH + "/configs/c.1/strings/0x409/configuration", F_OK) == 0) + sysfs_set_string(USB_GADGET_PATH + "/configs/c.1/strings/0x409/configuration", ""); } -#else -void enable_usb_audio(void) {} -void disable_usb_audio(void) {} -#endif void usb_init_device(void) {