mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-10 08:03:04 -04:00
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:
parent
ea775fa501
commit
dbd53ef8ae
7 changed files with 487 additions and 34 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
214
firmware/target/hosted/hiby/usb-dac-hiby.c
Normal file
214
firmware/target/hosted/hiby/usb-dac-hiby.c
Normal 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;
|
||||
}
|
||||
34
firmware/target/hosted/hiby/usb-dac-hiby.h
Normal file
34
firmware/target/hosted/hiby/usb-dac-hiby.h
Normal 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__ */
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue