hibyr1: implement USB DAC

Implement the USB Audio Class DAC on the HiBy R1 and R3ProII, driven by
the usb_audio setting from the scaffolding patch: the host plays audio
over USB and the player's CS43131 renders it.

The vendor kernel provides a UAC gadget function "uac_sa" whose char
device /dev/uac_sa 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 a non-blocking read(). The data path lives in
usb-dac-hiby.c -- a pump thread drains /dev/uac_sa into a small
single-producer/single-consumer ring and the mixer callback hands that
PCM to the codec through the normal ALSA output path. No resampling is
needed because Rockbox clocks the codec at the host-negotiated rate.

The DAC is built on the existing configfs gadget and binds the UDC like
ADB, while usb_power_only keeps the USB thread out of the way. In charge
mode it is a standalone self-bound gadget. In adb mode it is composed
alongside adb on one gadget so the host gets a sound card and adb at
once; uac_sa must be the first-linked function (audio interfaces 0-1)
because the vendor function hard-codes its interface numbers.

The pump is hardware-only, so usb-dac-hiby.c is excluded from simulator
builds; the usb_audio_get_active() playback-interlock stub lives with the
scaffolding.

The R3ProII shares this hosted HiBy port and the same userspace gadget,
and the data path is codec-agnostic, so nothing here is R1-specific.
Only the R1 has been validated on-device.

Co-Authored-By: Claude Opus 4.8
Change-Id: I64c57ede27f411a2c61d41a7e0fa127b51a7b5b9
This commit is contained in:
Michael McAllister 2026-07-21 00:38:53 +01:00 • committed by Solomon Peachy
parent ea775fa501
commit dbd53ef8ae
7 changed files with 487 additions and 34 deletions

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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 <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#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;
}

View file

@ -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__ */

View file

@ -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 <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <string.h>
@ -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 <stdio.h>
/* 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)
{