mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-07-10 13:29:52 -04:00
this commit is a combination of the following changes, which significantly refactors usb core and class drivers. 1. unify usb buffers of each class driver to reduce iram usage currently, many class drivers allocate their own buffer to receive control out data, which is a waste of iram. share one common buffer for that usage to address the issue. 2. simplify control request handling by implicitly receiving write request data packets change 1 above fixed the data destination. therefore, having the core receive the data allows us to reduce the class driver's work and simplifies the api. 3. enhance usb core's control request handling and unify the legacy driver api in order to implement change 2, both the legacy and new driver apis should be supported. so that, using the designware driver as a reference, the new driver api functionality is move into usb core. this simplifies the usb device drivers by requiring them to implement only the functionalities equivalent to the legacy api. tested with ipodvideo(arc) and erosqnative(designware) Change-Id: I3627daa90278751f599e2108ec150ec3f8f6c524
679 lines
24 KiB
C
679 lines
24 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
*
|
|
* Copyright (C) 2025 by Sho Tanimoto
|
|
*
|
|
* 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.
|
|
*
|
|
****************************************************************************/
|
|
#include "audio.h"
|
|
#include "pcm_mixer.h"
|
|
#include "pcm_sink.h"
|
|
#include "playback.h"
|
|
#include "powermgmt.h"
|
|
#include "timefuncs.h"
|
|
#include "usb_core.h"
|
|
#include "usb_drv.h"
|
|
#include "panic.h"
|
|
|
|
#include "iap/audio.h"
|
|
#include "iap/libiap/iap.h"
|
|
#include "iap/libiap/platform.h"
|
|
#include "iap/macros.h"
|
|
#include "iap/platform-macros.h"
|
|
#include "iap/platform.h"
|
|
#include "usb_audio_def.h"
|
|
#include "usb_class_driver.h"
|
|
#include "usb_hid_def.h"
|
|
#include "usb_iap.h"
|
|
|
|
struct usb_class_driver_ep_allocation usb_iap_ep_allocs[2] = {
|
|
/* uac input */
|
|
{.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_IN, .optional = false, .mps = 1024},
|
|
/* hid input */
|
|
{.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = false, .mps = 64},
|
|
};
|
|
|
|
/* interface 0 (audio control) */
|
|
static struct usb_interface_descriptor ipod_audio_control_desc = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bInterfaceNumber = -1, /* dynamic */
|
|
.bAlternateSetting = 0,
|
|
.bNumEndpoints = 0,
|
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
|
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL,
|
|
};
|
|
|
|
static struct usb_ac_header ipod_audio_control_uac_header = {
|
|
.bLength = USB_AC_SIZEOF_HEADER(1),
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubType = USB_AC_HEADER,
|
|
.bcdADC = 0x0100, /* 1.00 */
|
|
.wTotalLength = -1, /* dynamic */
|
|
.bInCollection = 1,
|
|
.baInterfaceNr = {-1}, /* dynamic */
|
|
};
|
|
|
|
static struct usb_ac_input_terminal ipod_audio_control_uac_input_terminal = {
|
|
.bLength = sizeof(struct usb_ac_input_terminal),
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubType = USB_AC_INPUT_TERMINAL,
|
|
.bTerminalId = 1,
|
|
.wTerminalType = USB_AC_INPUT_TERMINAL_MICROPHONE,
|
|
.bAssocTerminal = 2, /* ipod_audio_control_uac_output_terminal */
|
|
.bNrChannels = 2,
|
|
.wChannelConfig = USB_AC_CHANNELS_LEFT_RIGHT_FRONT,
|
|
};
|
|
|
|
static struct usb_ac_output_terminal ipod_audio_control_uac_output_terminal = {
|
|
.bLength = sizeof(struct usb_ac_output_terminal),
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubType = USB_AC_OUTPUT_TERMINAL,
|
|
.bTerminalId = 2,
|
|
.wTerminalType = USB_AC_TERMINAL_STREAMING,
|
|
.bAssocTerminal = 1, /* ipod_audio_control_uac_input_terminal */
|
|
.bSourceId = 1,
|
|
};
|
|
|
|
/* interface 1 (audio stream) */
|
|
/* interface 1 alt 0 */
|
|
static struct usb_interface_descriptor ipod_audio_stream_0_desc = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bInterfaceNumber = -1, /* dynamic */
|
|
.bAlternateSetting = 0,
|
|
.bNumEndpoints = 0,
|
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
|
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
|
|
};
|
|
|
|
/* interface 1 alt 1 */
|
|
static struct usb_interface_descriptor ipod_audio_stream_1_desc = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bInterfaceNumber = -1, /* dynamic */
|
|
.bAlternateSetting = 1,
|
|
.bNumEndpoints = 1,
|
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
|
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
|
|
};
|
|
|
|
static struct usb_as_interface ipod_audio_stream_1_uac_header = {
|
|
.bLength = sizeof(struct usb_as_interface),
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubType = USB_AS_GENERAL,
|
|
.bTerminalLink = 2, /* ipod_audio_control_uac_output_terminal */
|
|
.bDelay = 1,
|
|
.wFormatTag = USB_AS_FORMAT_TYPE_I_PCM,
|
|
};
|
|
|
|
/* TODO: remove unsupported freqs */
|
|
static struct usb_as_format_type_i_discrete ipod_audio_stream_1_uac_discrete = {
|
|
.bLength = USB_AS_SIZEOF_FORMAT_TYPE_I_DISCRETE(9),
|
|
.bDescriptorType = USB_DT_CS_INTERFACE,
|
|
.bDescriptorSubType = USB_AS_FORMAT_TYPE,
|
|
.bFormatType = USB_AS_FORMAT_TYPE_I,
|
|
.bNrChannels = 2,
|
|
.bSubframeSize = 2, /* bBitResolution / 8 */
|
|
.bBitResolution = 16,
|
|
.bSamFreqType = 9,
|
|
.tSamFreq = {
|
|
{0x40, 0x1F, 0x00}, /* 8000 */
|
|
{0x11, 0x2B, 0x00}, /* 11025 */
|
|
{0xE0, 0x2E, 0x00}, /* 12000 */
|
|
{0x80, 0x3E, 0x00}, /* 16000 */
|
|
{0x22, 0x56, 0x00}, /* 22050 */
|
|
{0xC0, 0x5D, 0x00}, /* 24000 */
|
|
{0x00, 0x7D, 0x00}, /* 32000 */
|
|
{0x44, 0xAC, 0x00}, /* 44100 */
|
|
{0x80, 0xBB, 0x00}, /* 48000 */
|
|
},
|
|
};
|
|
|
|
/* interface 1 endpoint 0 */
|
|
static struct usb_as_iso_audio_endpoint ipod_audio_stream_1_endpoint = {
|
|
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = -1, /* dynamic */
|
|
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
|
.wMaxPacketSize = AS_PACKET_SIZE,
|
|
.bInterval = -1, /* dynamic */
|
|
.bRefresh = 0,
|
|
.bSynchAddress = 0,
|
|
};
|
|
|
|
static struct usb_as_iso_ctrldata_endpoint ipod_audio_stream_1_endpoint_uac = {
|
|
.bLength = sizeof(struct usb_as_iso_ctrldata_endpoint),
|
|
.bDescriptorType = USB_DT_CS_ENDPOINT,
|
|
.bDescriptorSubType = USB_AS_EP_GENERAL,
|
|
.bmAttributes = USB_AS_EP_CS_SAMPLING_FREQ_CTL,
|
|
.bLockDelayUnits = 0,
|
|
.wLockDelay = 0,
|
|
};
|
|
|
|
/* interface 2 (hid) */
|
|
static struct usb_interface_descriptor ipod_hid_desc = {
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bInterfaceNumber = -1, /* dynamic */
|
|
.bAlternateSetting = 0,
|
|
.bNumEndpoints = 1,
|
|
.bInterfaceClass = USB_CLASS_HID,
|
|
.bInterfaceSubClass = 0,
|
|
};
|
|
|
|
#define INPUT_REPORT(id, count) 0x09, 0x01, /* Usage 0x01 */ \
|
|
0x85, id, /* Report ID */ \
|
|
0x95, count, /* Report Count */ \
|
|
0x82, 0x02, 0x01 /* Input 0x0102 (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes) */
|
|
|
|
#define OUTPUT_REPORT(id, count) 0x09, 0x01, /* Usage 0x01 */ \
|
|
0x85, id, /* Report ID */ \
|
|
0x95, count, /* Report Count */ \
|
|
0x92, 0x02, 0x01 /* Output 0x0102 (...) */
|
|
|
|
#define INPUT_REPORT2(id, count1, count2) 0x09, 0x01, /* Usage 0x01 */ \
|
|
0x85, id, /* Report ID */ \
|
|
0x96, count1, count2, /* Report Count */ \
|
|
0x82, 0x02, 0x01 /* Input 0x0102 (...) */
|
|
|
|
#define OUTPUT_REPORT2(id, count1, count2) 0x09, 0x01, /* Usage 0x01 */ \
|
|
0x85, id, /* Report ID */ \
|
|
0x96, count1, count2, /* Report Count */ \
|
|
0x92, 0x02, 0x01 /* Output 0x0102 (...) */
|
|
|
|
// clang-format off
|
|
static const uint8_t ipod_hid_report_fs[] = {
|
|
0x06, 0x00, 0xFF, /* Usage Page 0xFF00 (Vendor-defined) */
|
|
0x09, 0x01, /* Usage 0x01 */
|
|
0xA1, 0x01, /* Collection 0x01 (Application) */
|
|
0x75, 0x08, /* Report Size 0x08 */
|
|
0x26, 0x80, 0x00, /* Logical Maximum 0x0081 (128) */
|
|
0x15, 0x00, /* Logical Minumum 0x0000 (0) */
|
|
|
|
INPUT_REPORT(0x01, 0x0C),
|
|
INPUT_REPORT(0x02, 0x0E),
|
|
INPUT_REPORT(0x03, 0x14),
|
|
INPUT_REPORT(0x04, 0x3F),
|
|
|
|
OUTPUT_REPORT(0x05, 0x08),
|
|
OUTPUT_REPORT(0x06, 0x0A),
|
|
OUTPUT_REPORT(0x07, 0x0E),
|
|
OUTPUT_REPORT(0x08, 0x14),
|
|
OUTPUT_REPORT(0x09, 0x3F),
|
|
|
|
0xC0, /* End Collection */
|
|
};
|
|
|
|
static const uint8_t ipod_hid_report_hs[] = {
|
|
0x06, 0x00, 0xFF, /* Usage Page 0xFF00 (Vendor-defined) */
|
|
0x09, 0x01, /* Usage 0x01 */
|
|
0xA1, 0x01, /* Collection 0x01 (Application) */
|
|
0x75, 0x08, /* Report Size 0x08 */
|
|
0x26, 0x80, 0x00, /* Logical Maximum 0x0081 (128) */
|
|
0x15, 0x00, /* Logical Minumum 0x0000 (0) */
|
|
|
|
INPUT_REPORT(0x01, 0x05),
|
|
INPUT_REPORT(0x02, 0x09),
|
|
INPUT_REPORT(0x03, 0x0D),
|
|
INPUT_REPORT(0x04, 0x11),
|
|
INPUT_REPORT(0x05, 0x19),
|
|
INPUT_REPORT(0x06, 0x31),
|
|
INPUT_REPORT(0x07, 0x5F),
|
|
INPUT_REPORT(0x08, 0xC1),
|
|
INPUT_REPORT2(0x09, 0x01, 0x01),
|
|
INPUT_REPORT2(0x0A, 0x81, 0x01),
|
|
INPUT_REPORT2(0x0B, 0x01, 0x02),
|
|
INPUT_REPORT2(0x0C, 0xFF, 0x02),
|
|
|
|
OUTPUT_REPORT(0x0D, 0x05),
|
|
OUTPUT_REPORT(0x0E, 0x09),
|
|
OUTPUT_REPORT(0x1F, 0x0D),
|
|
OUTPUT_REPORT(0x10, 0x11),
|
|
OUTPUT_REPORT(0x11, 0x19),
|
|
OUTPUT_REPORT(0x12, 0x31),
|
|
OUTPUT_REPORT(0x13, 0x5F),
|
|
OUTPUT_REPORT(0x14, 0xC1),
|
|
OUTPUT_REPORT(0x15, 0xFF),
|
|
|
|
0xC0, /* End Collection */
|
|
};
|
|
// clang-format on
|
|
|
|
static struct usb_hid_descriptor ipod_hid_hid_desc = {
|
|
.bLength = sizeof(struct usb_hid_descriptor),
|
|
.bDescriptorType = USB_DT_HID,
|
|
.wBcdHID = 0x0111, /* 1.11 */
|
|
.bCountryCode = 0,
|
|
.bNumDescriptors = 1,
|
|
.bDescriptorType0 = USB_DT_REPORT,
|
|
.wDescriptorLength0 = -1, /* dynamic */
|
|
};
|
|
|
|
/* interface 2 endpoint 0 */
|
|
static struct usb_endpoint_descriptor ipod_hid_endpoint = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = -1, /* dynamic */
|
|
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
|
.wMaxPacketSize = -1, /* dynamic */
|
|
.bInterval = 1,
|
|
};
|
|
|
|
static struct {
|
|
int interface;
|
|
} ctrl;
|
|
|
|
static struct {
|
|
uint32_t sample_rate;
|
|
int interface;
|
|
int8_t alt;
|
|
} stream;
|
|
|
|
static struct {
|
|
int interface;
|
|
} hid;
|
|
|
|
static struct mutex iap_ctx_mutex;
|
|
static struct Platform platform;
|
|
static struct timeout tick_tmo;
|
|
static bool iap_ctx_mutex_initialized = false;
|
|
|
|
struct IAPContext* _iap_acquire_ctx(bool lock) {
|
|
static struct IAPContext ctx;
|
|
if(lock) {
|
|
mutex_lock(&iap_ctx_mutex);
|
|
}
|
|
return &ctx;
|
|
}
|
|
|
|
void _iap_release_ctx() {
|
|
mutex_unlock(&iap_ctx_mutex);
|
|
}
|
|
|
|
bool iap_initialized;
|
|
|
|
/* those notifications need pollling */
|
|
static enum charge_state_type last_charge_state;
|
|
static uint8_t last_battery_level;
|
|
static int8_t last_minute;
|
|
static int8_t last_hold_switch_state;
|
|
|
|
enum Notify {
|
|
Notify_Tick,
|
|
};
|
|
|
|
static int tick_callback(struct timeout* tmo) {
|
|
(void)tmo;
|
|
usb_signal_class_notify(USB_DRIVER_IAP, Notify_Tick);
|
|
return HZ / 10;
|
|
}
|
|
|
|
static int usb_iap_set_first_interface(int interface) {
|
|
ctrl.interface = interface + 0;
|
|
stream.interface = interface + 1;
|
|
hid.interface = interface + 2;
|
|
return interface + 3;
|
|
}
|
|
|
|
#define PACK_DESC(desc) pack_data(&dest, &desc, ((struct usb_descriptor_header*)&desc)->bLength)
|
|
|
|
static int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_size) {
|
|
(void)max_packet_size;
|
|
|
|
unsigned char* orig_dest = dest;
|
|
|
|
ipod_audio_control_desc.bInterfaceNumber = ctrl.interface;
|
|
PACK_DESC(ipod_audio_control_desc);
|
|
ipod_audio_control_uac_header.baInterfaceNr[0] = stream.interface;
|
|
ipod_audio_control_uac_header.wTotalLength =
|
|
sizeof(ipod_audio_control_uac_header) +
|
|
sizeof(ipod_audio_control_uac_input_terminal) +
|
|
sizeof(ipod_audio_control_uac_output_terminal);
|
|
PACK_DESC(ipod_audio_control_uac_header);
|
|
PACK_DESC(ipod_audio_control_uac_input_terminal);
|
|
PACK_DESC(ipod_audio_control_uac_output_terminal);
|
|
|
|
ipod_audio_stream_0_desc.bInterfaceNumber = stream.interface;
|
|
ipod_audio_stream_1_desc.bInterfaceNumber = stream.interface;
|
|
ipod_audio_stream_1_endpoint.bEndpointAddress = AS_EP_IN;
|
|
ipod_audio_stream_1_endpoint.bInterval = usb_drv_port_speed() ? 4 : 1;
|
|
PACK_DESC(ipod_audio_stream_0_desc);
|
|
PACK_DESC(ipod_audio_stream_1_desc);
|
|
PACK_DESC(ipod_audio_stream_1_uac_header);
|
|
PACK_DESC(ipod_audio_stream_1_uac_discrete);
|
|
PACK_DESC(ipod_audio_stream_1_endpoint);
|
|
PACK_DESC(ipod_audio_stream_1_endpoint_uac);
|
|
|
|
ipod_hid_desc.bInterfaceNumber = hid.interface;
|
|
ipod_hid_endpoint.bEndpointAddress = HID_EP_IN;
|
|
if(usb_drv_port_speed()) {
|
|
ipod_hid_hid_desc.wDescriptorLength0 = sizeof(ipod_hid_report_hs);
|
|
ipod_hid_endpoint.wMaxPacketSize = 64;
|
|
} else {
|
|
ipod_hid_hid_desc.wDescriptorLength0 = sizeof(ipod_hid_report_fs);
|
|
ipod_hid_endpoint.wMaxPacketSize = 64;
|
|
}
|
|
PACK_DESC(ipod_hid_desc);
|
|
PACK_DESC(ipod_hid_hid_desc);
|
|
PACK_DESC(ipod_hid_endpoint);
|
|
|
|
return dest - orig_dest;
|
|
}
|
|
|
|
static int usb_iap_init_connection(void) {
|
|
stream.sample_rate = 48000;
|
|
last_charge_state = -1;
|
|
last_minute = -1;
|
|
last_hold_switch_state = -1;
|
|
|
|
iap_debug_reset_timestamp();
|
|
|
|
/* init audio sink */
|
|
check_act(iap_audio_init(), return -1);
|
|
|
|
/* init libiap */
|
|
if(!iap_ctx_mutex_initialized) {
|
|
iap_ctx_mutex_initialized = true;
|
|
mutex_init(&iap_ctx_mutex);
|
|
}
|
|
|
|
struct IAPContext* ctx = _iap_acquire_ctx(true);
|
|
|
|
const struct IAPOpts opts = {
|
|
.usb_highspeed = usb_drv_port_speed(),
|
|
.ignore_hid_report_id = iap_true,
|
|
.artwork_single_report = iap_false,
|
|
.enable_packet_dump = iap_false,
|
|
};
|
|
check_act(iap_init_ctx(ctx, opts, &platform), goto cleanup_audio);
|
|
_iap_release_ctx();
|
|
|
|
/* prepare artwork */
|
|
struct dim dim = {IAP_ARTWORK_WIDTH, IAP_ARTWORK_HEIGHT};
|
|
platform.aa_slot = playback_claim_aa_slot(&dim);
|
|
if(platform.aa_slot < 0) {
|
|
ERROR("failed to claim albumart slot");
|
|
}
|
|
platform.control_pending = false;
|
|
|
|
/* register timer */
|
|
timeout_register(&tick_tmo, tick_callback, HZ / 10, 0);
|
|
|
|
iap_initialized = true;
|
|
LOG("initialized");
|
|
return 0;
|
|
|
|
cleanup_audio:
|
|
_iap_release_ctx();
|
|
iap_audio_deinit();
|
|
return -1;
|
|
}
|
|
|
|
static int usb_iap_set_interface(int intf, int alt) {
|
|
LOG("set interface interface=%d alt=%d", intf, alt);
|
|
check_act(intf == stream.interface, return -1);
|
|
if(alt == 0) {
|
|
check_act(iap_audio_disable(), return -1);
|
|
} else if(alt == 1) {
|
|
check_act(iap_audio_enable(), return -1);
|
|
} else {
|
|
ERROR("invalid alt %d", alt);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int usb_iap_get_interface(int intf) {
|
|
LOG("get interface interface=%d", intf);
|
|
check_act(intf == stream.interface, return -1);
|
|
return stream.alt;
|
|
}
|
|
|
|
static void usb_iap_init(void) {
|
|
LOG("init");
|
|
}
|
|
|
|
static void usb_iap_disconnect(void) {
|
|
iap_initialized = false;
|
|
audio_pause();
|
|
mixer_switch_sink(PCM_SINK_BUILTIN);
|
|
timeout_cancel(&tick_tmo);
|
|
if(platform.aa_slot >= 0) {
|
|
playback_release_aa_slot(platform.aa_slot);
|
|
}
|
|
struct IAPContext* ctx = _iap_acquire_ctx(true);
|
|
check_act(iap_deinit_ctx(ctx), );
|
|
_iap_release_ctx();
|
|
check_act(iap_audio_deinit(), );
|
|
LOG("disconnected");
|
|
}
|
|
|
|
static void usb_iap_transfer_complete(int ep, int dir, int status, int length) {
|
|
(void)length;
|
|
|
|
if((ep | dir) == HID_EP_IN) {
|
|
check_act(status == 0, return);
|
|
#if DEBUG_DUMP_TX
|
|
LOG("ep=%d dir=%d state=%d length=%d", ep, dir, status, length);
|
|
#endif
|
|
struct IAPContext* ctx = _iap_acquire_ctx(true);
|
|
check_act(iap_notify_send_complete(ctx), );
|
|
_iap_release_ctx();
|
|
}
|
|
}
|
|
|
|
static bool usb_iap_fast_transfer_complete(int ep, int dir, int status, int length) {
|
|
(void)status;
|
|
(void)length;
|
|
return (ep | dir) == AS_EP_IN;
|
|
}
|
|
|
|
static void respond_zero(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size) {
|
|
if(req->wLength > reqdata_size) {
|
|
ERROR("required data too long %u > %u", req->wLength, reqdata_size);
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
} else {
|
|
memset(reqdata, 0, req->wLength);
|
|
usb_core_control_response(USB_CONTROL_ACK, reqdata, req->wLength);
|
|
}
|
|
}
|
|
|
|
static bool control_request_if_std(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size) {
|
|
const void* src = NULL;
|
|
size_t size;
|
|
switch(req->bRequest) {
|
|
case USB_REQ_GET_DESCRIPTOR: {
|
|
const uint8_t desc_type = req->wValue >> 8;
|
|
const uint8_t desc_index = req->wValue & 0xff;
|
|
LOG("descriptor request type=%x index=%x", desc_type, desc_index);
|
|
(void)desc_index;
|
|
switch(desc_type) {
|
|
case USB_DT_HID:
|
|
src = &ipod_hid_hid_desc;
|
|
size = sizeof(ipod_hid_hid_desc);
|
|
break;
|
|
case USB_DT_REPORT:
|
|
if(usb_drv_port_speed()) {
|
|
src = &ipod_hid_report_hs;
|
|
size = sizeof(ipod_hid_report_hs);
|
|
} else {
|
|
src = &ipod_hid_report_fs;
|
|
size = sizeof(ipod_hid_report_fs);
|
|
}
|
|
break;
|
|
}
|
|
if(src != NULL) {
|
|
if(size > reqdata_size) {
|
|
LOG("descriptor size too large");
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
} else {
|
|
size = MIN(size, req->wLength);
|
|
memcpy(reqdata, src, size);
|
|
usb_core_control_response(USB_CONTROL_ACK, reqdata, size);
|
|
}
|
|
return true;
|
|
}
|
|
} break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool control_request_if_class(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size) {
|
|
const uint8_t recip_interface = req->wIndex & 0xff;
|
|
if(recip_interface == hid.interface) {
|
|
switch(req->bRequest) {
|
|
case USB_HID_GET_REPORT:
|
|
respond_zero(req, reqdata, reqdata_size);
|
|
return true;
|
|
case USB_HID_SET_REPORT: {
|
|
#if DEBUG_DUMP_RX == 1
|
|
logf("==== acc: %u bytes ====", req->wLength);
|
|
iap_platform_dump_hex(reqdata, req->wLength);
|
|
#endif
|
|
|
|
struct IAPContext* ctx = _iap_acquire_ctx(true);
|
|
const bool ret = iap_feed_hid_report(ctx, reqdata, req->wLength);
|
|
_iap_release_ctx();
|
|
|
|
check_act(ret, return false);
|
|
usb_core_control_response(USB_CONTROL_ACK, NULL, 0);
|
|
return true;
|
|
}
|
|
case USB_HID_SET_IDLE:
|
|
usb_core_control_response(USB_CONTROL_ACK, NULL, 0);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool control_request_if_endpoint(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size) {
|
|
LOG("ctrl to endpoint %x (stream=%x, hid=%x)", req->wIndex, AS_EP_IN, HID_EP_IN);
|
|
if(req->wIndex == AS_EP_IN) {
|
|
const uint8_t recip_entity = req->wIndex >> 8;
|
|
const uint8_t control_selector = req->wValue >> 8;
|
|
(void)recip_entity;
|
|
switch(req->bRequest) {
|
|
case USB_AC_SET_CUR:
|
|
LOG("audio ctrl to stream endpoint entity=0x%02X request=0x%02X length=%u", recip_entity, req->bRequest, req->wLength);
|
|
switch(control_selector) {
|
|
case USB_AS_EP_CS_SAMPLING_FREQ_CTL:
|
|
check_act(req->wLength == 3, goto stall);
|
|
stream.sample_rate = reqdata[0] | (reqdata[1] << 8) | (reqdata[2] << 16);
|
|
LOG("audio stream sampling rate %lu", stream.sample_rate);
|
|
check_act(iap_audio_set_sampr(stream.sample_rate), goto stall);
|
|
break;
|
|
}
|
|
usb_core_control_response(USB_CONTROL_ACK, NULL, 0);
|
|
return true;
|
|
case USB_AC_GET_CUR:
|
|
switch(control_selector) {
|
|
case USB_AS_EP_CS_SAMPLING_FREQ_CTL:
|
|
check_act(req->wLength == 3, goto stall);
|
|
reqdata[2] = (stream.sample_rate >> 16) & 0xff;
|
|
reqdata[1] = (stream.sample_rate >> 8) & 0xff;
|
|
reqdata[0] = (stream.sample_rate & 0xff);
|
|
usb_core_control_response(USB_CONTROL_ACK, reqdata, 3);
|
|
return true;
|
|
}
|
|
/* fallthrough */
|
|
case USB_AC_GET_MIN:
|
|
case USB_AC_GET_MAX:
|
|
case USB_AC_GET_RES:
|
|
respond_zero(req, reqdata, reqdata_size);
|
|
return true;
|
|
stall:
|
|
usb_core_control_response(USB_CONTROL_STALL, NULL, 0);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool usb_iap_control_request(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size) {
|
|
const uint8_t req_recipient = req->bRequestType & USB_RECIP_MASK;
|
|
const uint8_t req_type = req->bRequestType & USB_TYPE_MASK;
|
|
#if 0
|
|
LOG("bRequestType=%x, bRequest=%x, wValue=%x, wIndex=%x, wLength=%x", req->bRequestType, req->bRequest, req->wValue, req->wIndex, req->wLength);
|
|
LOG("recip=%x type=%x", req_recipient, req_type);
|
|
#endif
|
|
if(req_recipient == USB_RECIP_INTERFACE && req_type == USB_TYPE_STANDARD) {
|
|
return control_request_if_std(req, reqdata, reqdata_size);
|
|
} else if(req_recipient == USB_RECIP_INTERFACE && req_type == USB_TYPE_CLASS) {
|
|
return control_request_if_class(req, reqdata, reqdata_size);
|
|
} else if(req_recipient == USB_RECIP_ENDPOINT && req_type == USB_TYPE_CLASS) {
|
|
return control_request_if_endpoint(req, reqdata, reqdata_size);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void usb_iap_notify_event(intptr_t data) {
|
|
switch(data) {
|
|
case Notify_Tick: {
|
|
struct IAPContext* ctx = _iap_acquire_ctx(true);
|
|
struct Platform* plt = ctx->platform;
|
|
if(plt->control_pending) {
|
|
/* waiting for playback begins */
|
|
_iap_release_ctx();
|
|
return;
|
|
}
|
|
|
|
if(last_charge_state < 0 || last_charge_state != charge_state || last_battery_level != battery_level()) {
|
|
last_charge_state = charge_state;
|
|
last_battery_level = battery_level();
|
|
iap_notify_power_state(ctx, _iap_convert_charge_status(last_charge_state), _iap_convert_battery_level(last_battery_level));
|
|
}
|
|
|
|
struct tm* tm = get_time();
|
|
if(last_minute == -1 || last_minute != tm->tm_min) {
|
|
last_minute = tm->tm_min;
|
|
struct IAPDateTime time;
|
|
_iap_convert_datetime(get_time(), &time);
|
|
iap_notify_time_setting(ctx, &time);
|
|
}
|
|
|
|
#ifdef HAS_BUTTON_HOLD
|
|
int8_t hold = button_hold() ? 1 : 0;
|
|
if(hold != last_hold_switch_state) {
|
|
last_hold_switch_state = hold;
|
|
iap_notify_hold_switch_state(ctx, last_hold_switch_state);
|
|
}
|
|
#endif
|
|
|
|
check_act(iap_periodic_tick(ctx), );
|
|
_iap_release_ctx();
|
|
} break;
|
|
}
|
|
}
|
|
|
|
struct usb_class_driver usb_cdrv_iap = {
|
|
.needs_exclusive_storage = false,
|
|
.needs_cpu_boost = true,
|
|
.config = 2,
|
|
.ep_allocs_size = ARRAYLEN(usb_iap_ep_allocs),
|
|
.ep_allocs = usb_iap_ep_allocs,
|
|
.set_first_interface = usb_iap_set_first_interface,
|
|
.get_config_descriptor = usb_iap_get_config_descriptor,
|
|
.init_connection = usb_iap_init_connection,
|
|
.init = usb_iap_init,
|
|
.disconnect = usb_iap_disconnect,
|
|
.transfer_complete = usb_iap_transfer_complete,
|
|
.fast_transfer_complete = usb_iap_fast_transfer_complete,
|
|
.control_request = usb_iap_control_request,
|
|
.set_interface = usb_iap_set_interface,
|
|
.get_interface = usb_iap_get_interface,
|
|
.notify_event = usb_iap_notify_event,
|
|
};
|