summaryrefslogtreecommitdiff
path: root/class
diff options
context:
space:
mode:
authorsakimisu <[email protected]>2022-12-31 22:44:08 +0800
committersakimisu <[email protected]>2022-12-31 22:44:08 +0800
commit914d3cbf424946bdf370e02191a467c8f1cbae11 (patch)
tree2bb7f6afa9ad669601809cfcbfb87f6916c34a81 /class
parentdb330b42e7bba489c6b42ad2119a2e1df4a58c2b (diff)
add audio host driver
Diffstat (limited to 'class')
-rw-r--r--class/audio/usbh_audio.c384
-rw-r--r--class/audio/usbh_audio.h67
2 files changed, 451 insertions, 0 deletions
diff --git a/class/audio/usbh_audio.c b/class/audio/usbh_audio.c
new file mode 100644
index 00000000..dda74dc3
--- /dev/null
+++ b/class/audio/usbh_audio.c
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) 2022, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbh_core.h"
+#include "usbh_audio.h"
+
+#define DEV_FORMAT "/dev/audio%d"
+
+/* general descriptor field offsets */
+#define DESC_bLength 0 /** Length offset */
+#define DESC_bDescriptorType 1 /** Descriptor type offset */
+#define DESC_bDescriptorSubType 2 /** Descriptor subtype offset */
+
+/* interface descriptor field offsets */
+#define INTF_DESC_bInterfaceNumber 2 /** Interface number offset */
+#define INTF_DESC_bAlternateSetting 3 /** Alternate setting offset */
+
+static uint32_t g_devinuse = 0;
+
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_audio_buf[128];
+
+static int usbh_audio_devno_alloc(struct usbh_audio *audio_class)
+{
+ int devno;
+
+ for (devno = 0; devno < 32; devno++) {
+ uint32_t bitno = 1 << devno;
+ if ((g_devinuse & bitno) == 0) {
+ g_devinuse |= bitno;
+ audio_class->minor = devno;
+ return 0;
+ }
+ }
+
+ return -EMFILE;
+}
+
+static void usbh_audio_devno_free(struct usbh_audio *audio_class)
+{
+ int devno = audio_class->minor;
+
+ if (devno >= 0 && devno < 32) {
+ g_devinuse &= ~(1 << devno);
+ }
+}
+
+int usbh_audio_open(struct usbh_audio *audio_class, const char *name)
+{
+ struct usb_setup_packet *setup = &audio_class->hport->setup;
+ struct usb_endpoint_descriptor *ep_desc;
+ uint8_t mult;
+ uint16_t mps;
+ int ret;
+ uint8_t intf = 0xff;
+ uint8_t altsetting = 1;
+
+ if (audio_class->is_opened) {
+ return -EMFILE;
+ }
+
+ for (size_t i = 0; i < audio_class->module_num; i++) {
+ if (strcmp(name, audio_class->module[i].name) == 0) {
+ intf = audio_class->module[i].data_intf;
+ }
+ }
+
+ if (intf == 0xff) {
+ return -ENODEV;
+ }
+
+ ep_desc = &audio_class->hport->config.intf[intf].altsetting[altsetting].ep[0].ep_desc;
+ mult = (ep_desc->wMaxPacketSize & USB_MAXPACKETSIZE_ADDITIONAL_TRANSCATION_MASK) >> USB_MAXPACKETSIZE_ADDITIONAL_TRANSCATION_SHIFT;
+ mps = ep_desc->wMaxPacketSize & USB_MAXPACKETSIZE_MASK;
+ if (ep_desc->bEndpointAddress & 0x80) {
+ audio_class->isoin_mps = mps * (mult + 1);
+ usbh_hport_activate_epx(&audio_class->isoin, audio_class->hport, ep_desc);
+ } else {
+ audio_class->isoout_mps = mps * (mult + 1);
+ usbh_hport_activate_epx(&audio_class->isoout, audio_class->hport, ep_desc);
+ }
+
+ setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_STANDARD | USB_REQUEST_RECIPIENT_INTERFACE;
+ setup->bRequest = USB_REQUEST_SET_INTERFACE;
+ setup->wValue = 1;
+ setup->wIndex = intf;
+ setup->wLength = 0;
+
+ ret = usbh_control_transfer(audio_class->hport->ep0, setup, NULL);
+ if (ret < 0) {
+ return ret;
+ }
+
+ USB_LOG_INFO("Open audio module :%s\r\n", name);
+ audio_class->is_opened = false;
+ return ret;
+}
+
+int usbh_audio_close(struct usbh_audio *audio_class, const char *name)
+{
+ struct usb_setup_packet *setup = &audio_class->hport->setup;
+ struct usb_endpoint_descriptor *ep_desc;
+ int ret;
+ uint8_t intf = 0xff;
+ uint8_t altsetting = 1;
+
+ for (size_t i = 0; i < audio_class->module_num; i++) {
+ if (strcmp(name, audio_class->module[i].name) == 0) {
+ intf = audio_class->module[i].data_intf;
+ }
+ }
+
+ if (intf == 0xff) {
+ return -ENODEV;
+ }
+
+ ep_desc = &audio_class->hport->config.intf[intf].altsetting[altsetting].ep[0].ep_desc;
+ if (ep_desc->bEndpointAddress & 0x80) {
+ if (audio_class->isoin) {
+ usbh_pipe_free(audio_class->isoin);
+ audio_class->isoin = NULL;
+ }
+ } else {
+ if (audio_class->isoout) {
+ usbh_pipe_free(audio_class->isoout);
+ audio_class->isoout = NULL;
+ }
+ }
+
+ setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_STANDARD | USB_REQUEST_RECIPIENT_INTERFACE;
+ setup->bRequest = USB_REQUEST_SET_INTERFACE;
+ setup->wValue = 0;
+ setup->wIndex = intf;
+ setup->wLength = 0;
+
+ ret = usbh_control_transfer(audio_class->hport->ep0, setup, NULL);
+ if (ret < 0) {
+ return ret;
+ }
+
+ USB_LOG_INFO("Close audio module :%s\r\n", name);
+ audio_class->is_opened = false;
+ return ret;
+}
+
+void usbh_audio_list_module(struct usbh_audio *audio_class)
+{
+ USB_LOG_INFO("============= Audio module information ===================\r\n");
+ USB_LOG_INFO("bcdADC :%04x\r\n", audio_class->bcdADC);
+ USB_LOG_INFO("Num of modules :%u\r\n", audio_class->module_num);
+
+ for (uint8_t i = 0; i < audio_class->module_num; i++) {
+ USB_LOG_INFO(" module name :%s\r\n", audio_class->module[i].name);
+ USB_LOG_INFO(" module feature unit id :%d\r\n", audio_class->module[i].feature_unit_id);
+ USB_LOG_INFO(" module channels :%u\r\n", audio_class->module[i].channels);
+ //USB_LOG_INFO(" module format_type :%u\r\n",audio_class->module[i].format_type);
+ USB_LOG_INFO(" module bitresolution :%u\r\n", audio_class->module[i].bitresolution);
+ USB_LOG_INFO(" module sampfreq num :%u\r\n", audio_class->module[i].sampfreq_num);
+
+ for (uint8_t j = 0; j < audio_class->module[i].sampfreq_num; j++) {
+ USB_LOG_INFO(" module sampfreq :%d hz\r\n", audio_class->module[i].sampfreq[j]);
+ }
+ }
+
+ USB_LOG_INFO("============= Audio module information ===================\r\n");
+}
+
+static int usbh_audio_ctrl_connect(struct usbh_hubport *hport, uint8_t intf)
+{
+ int ret;
+ uint8_t cur_iface = 0xff;
+ uint8_t input_offset = 0;
+ uint8_t output_offset = 0;
+ uint8_t feature_unit_offset = 0;
+ uint8_t format_offset = 0;
+ uint8_t *p;
+
+ struct usbh_audio *audio_class = usb_malloc(sizeof(struct usbh_audio));
+ if (audio_class == NULL) {
+ USB_LOG_ERR("Fail to alloc audio_class\r\n");
+ return -ENOMEM;
+ }
+
+ memset(audio_class, 0, sizeof(struct usbh_audio));
+ usbh_audio_devno_alloc(audio_class);
+ audio_class->hport = hport;
+ audio_class->ctrl_intf = intf;
+
+ hport->config.intf[intf].priv = audio_class;
+
+ p = hport->raw_config_desc;
+ while (p[DESC_bLength]) {
+ switch (p[DESC_bDescriptorType]) {
+ case USB_DESCRIPTOR_TYPE_INTERFACE:
+ cur_iface = p[INTF_DESC_bInterfaceNumber];
+ break;
+ case USB_DESCRIPTOR_TYPE_ENDPOINT:
+ break;
+ case AUDIO_INTERFACE_DESCRIPTOR_TYPE:
+ if (cur_iface == audio_class->ctrl_intf) {
+ switch (p[DESC_bDescriptorSubType]) {
+ case AUDIO_CONTROL_HEADER: {
+ struct audio_cs_if_ac_header_descriptor *desc = (struct audio_cs_if_ac_header_descriptor *)p;
+ audio_class->bcdADC = desc->bcdADC;
+ audio_class->bInCollection = desc->bInCollection;
+ } break;
+ case AUDIO_CONTROL_INPUT_TERMINAL: {
+ struct audio_cs_if_ac_input_terminal_descriptor *desc = (struct audio_cs_if_ac_input_terminal_descriptor *)p;
+
+ audio_class->module[input_offset].input_terminal_id = desc->bTerminalID;
+ audio_class->module[input_offset].input_terminal_type = desc->wTerminalType;
+ audio_class->module[input_offset].input_channel_config = desc->wChannelConfig;
+
+ if (desc->wTerminalType == AUDIO_TERMINAL_STREAMING) {
+ audio_class->module[input_offset].terminal_link_id = desc->bTerminalID;
+ }
+ if (desc->wTerminalType == AUDIO_INTERM_MIC) {
+ audio_class->module[input_offset].name = "mic";
+ }
+ input_offset++;
+ } break;
+ break;
+ case AUDIO_CONTROL_OUTPUT_TERMINAL: {
+ struct audio_cs_if_ac_output_terminal_descriptor *desc = (struct audio_cs_if_ac_output_terminal_descriptor *)p;
+ audio_class->module[output_offset].output_terminal_id = desc->bTerminalID;
+ audio_class->module[output_offset].output_terminal_type = desc->wTerminalType;
+ if (desc->wTerminalType == AUDIO_TERMINAL_STREAMING) {
+ audio_class->module[output_offset].terminal_link_id = desc->bTerminalID;
+ }
+ if (desc->wTerminalType == AUDIO_OUTTERM_SPEAKER) {
+ audio_class->module[output_offset].name = "speaker";
+ }
+ output_offset++;
+ } break;
+ case AUDIO_CONTROL_FEATURE_UNIT: {
+ struct audio_cs_if_ac_feature_unit_descriptor *desc = (struct audio_cs_if_ac_feature_unit_descriptor *)p;
+ audio_class->module[feature_unit_offset].feature_unit_id = desc->bUnitID;
+ audio_class->module[feature_unit_offset].feature_unit_controlsize = desc->bControlSize;
+
+ for (uint8_t j = 0; j < desc->bControlSize; j++) {
+ audio_class->module[feature_unit_offset].feature_unit_controls[j] = p[6 + j];
+ }
+ feature_unit_offset++;
+ } break;
+ case AUDIO_CONTROL_PROCESSING_UNIT:
+
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (p[DESC_bDescriptorSubType]) {
+ case AUDIO_STREAMING_GENERAL:
+
+ break;
+ case AUDIO_STREAMING_FORMAT_TYPE: {
+ struct audio_cs_if_as_format_type_descriptor *desc = (struct audio_cs_if_as_format_type_descriptor *)p;
+
+ audio_class->module[format_offset].data_intf = cur_iface;
+ audio_class->module[format_offset].channels = desc->bNrChannels;
+ audio_class->module[format_offset].format_type = desc->bFormatType;
+ audio_class->module[format_offset].bitresolution = desc->bBitResolution;
+ audio_class->module[format_offset].sampfreq_num = desc->bSamFreqType;
+
+ for (uint8_t j = 0; j < desc->bSamFreqType; j++) {
+ audio_class->module[format_offset].sampfreq[j] = (uint32_t)(p[10 + j] << 16) |
+ (uint32_t)(p[9 + j] << 8) |
+ (uint32_t)(p[8 + j] << 0);
+ }
+ format_offset++;
+ } break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ /* skip to next descriptor */
+ p += p[DESC_bLength];
+ }
+
+ if ((input_offset != output_offset) && (input_offset != feature_unit_offset) && (input_offset != format_offset)) {
+ return -EINVAL;
+ }
+
+ audio_class->module_num = input_offset;
+
+ for (size_t i = 0; i < audio_class->module_num; i++) {
+ ret = usbh_audio_close(audio_class, audio_class->module[i].name);
+ if (ret < 0) {
+ USB_LOG_ERR("Fail to close audio module :%s\r\n", audio_class->module[i].name);
+ return ret;
+ }
+ }
+
+ usbh_audio_list_module(audio_class);
+
+ snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, audio_class->minor);
+ USB_LOG_INFO("Register Audio Class:%s\r\n", hport->config.intf[intf].devname);
+
+ usbh_audio_run(audio_class);
+ return 0;
+}
+
+static int usbh_audio_ctrl_disconnect(struct usbh_hubport *hport, uint8_t intf)
+{
+ int ret = 0;
+
+ struct usbh_audio *audio_class = (struct usbh_audio *)hport->config.intf[intf].priv;
+
+ if (audio_class) {
+ usbh_audio_devno_free(audio_class);
+
+ if (audio_class->isoin) {
+ usbh_pipe_free(audio_class->isoin);
+ }
+
+ if (audio_class->isoout) {
+ usbh_pipe_free(audio_class->isoout);
+ }
+
+ usbh_audio_stop(audio_class);
+ memset(audio_class, 0, sizeof(struct usbh_audio));
+ usb_free(audio_class);
+
+ if (hport->config.intf[intf].devname[0] != '\0')
+ USB_LOG_INFO("Unregister Audio Class:%s\r\n", hport->config.intf[intf].devname);
+ }
+
+ return ret;
+}
+
+static int usbh_audio_data_connect(struct usbh_hubport *hport, uint8_t intf)
+{
+ return 0;
+}
+
+static int usbh_audio_data_disconnect(struct usbh_hubport *hport, uint8_t intf)
+{
+ return 0;
+}
+
+__WEAK void usbh_audio_run(struct usbh_audio *audio_class)
+{
+}
+
+__WEAK void usbh_audio_stop(struct usbh_audio *audio_class)
+{
+}
+
+const struct usbh_class_driver audio_ctrl_class_driver = {
+ .driver_name = "audio_ctrl",
+ .connect = usbh_audio_ctrl_connect,
+ .disconnect = usbh_audio_ctrl_disconnect
+};
+
+const struct usbh_class_driver audio_streaming_class_driver = {
+ .driver_name = "audio_streaming",
+ .connect = usbh_audio_data_connect,
+ .disconnect = usbh_audio_data_disconnect
+};
+
+CLASS_INFO_DEFINE const struct usbh_class_info audio_ctrl_intf_class_info = {
+ .match_flags = USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS,
+ .class = USB_DEVICE_CLASS_AUDIO,
+ .subclass = AUDIO_SUBCLASS_AUDIOCONTROL,
+ .protocol = 0x00,
+ .vid = 0x00,
+ .pid = 0x00,
+ .class_driver = &audio_ctrl_class_driver
+};
+
+CLASS_INFO_DEFINE const struct usbh_class_info audio_streaming_intf_class_info = {
+ .match_flags = USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS,
+ .class = USB_DEVICE_CLASS_AUDIO,
+ .subclass = AUDIO_SUBCLASS_AUDIOSTREAMING,
+ .protocol = 0x00,
+ .vid = 0x00,
+ .pid = 0x00,
+ .class_driver = &audio_streaming_class_driver
+}; \ No newline at end of file
diff --git a/class/audio/usbh_audio.h b/class/audio/usbh_audio.h
new file mode 100644
index 00000000..df00114a
--- /dev/null
+++ b/class/audio/usbh_audio.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2022, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef USBH_AUDIO_H
+#define USBH_AUDIO_H
+
+#include "usb_audio.h"
+
+/**
+ * bSourceID in feature_unit = input_terminal_id
+ * bSourceID in output_terminal = feature_unit_id
+ * terminal_link_id = input_terminal_id or output_terminal_id (if input_terminal_type or output_terminal_type is 0x0101)
+ *
+ *
+*/
+struct usbh_audio_module {
+ const char *name;
+ uint8_t data_intf;
+ uint8_t input_terminal_id;
+ uint16_t input_terminal_type;
+ uint16_t input_channel_config;
+ uint8_t output_terminal_id;
+ uint16_t output_terminal_type;
+ uint8_t feature_unit_id;
+ uint8_t feature_unit_controlsize;
+ uint8_t feature_unit_controls[8];
+ uint8_t terminal_link_id;
+ uint8_t channels;
+ uint8_t format_type;
+ uint8_t bitresolution;
+ uint8_t sampfreq_num;
+ uint32_t sampfreq[3];
+};
+
+struct usbh_audio {
+ struct usbh_hubport *hport;
+
+ uint8_t ctrl_intf; /* interface number */
+ uint8_t minor;
+ usbh_pipe_t isoin; /* ISO IN endpoint */
+ usbh_pipe_t isoout; /* ISO OUT endpoint */
+ uint16_t isoin_mps;
+ uint16_t isoout_mps;
+ bool is_opened;
+ uint16_t bcdADC;
+ uint8_t bInCollection;
+ struct usbh_audio_module module[2];
+ uint8_t module_num;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int usbh_audio_open(struct usbh_audio *audio_class, const char *name);
+int usbh_audio_close(struct usbh_audio *audio_class, const char *name);
+
+void usbh_audio_run(struct usbh_audio *audio_class);
+void usbh_audio_stop(struct usbh_audio *audio_class);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* USBH_AUDIO_H */