mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-09 12:17:12 +09:00
usb: gadget: uac: fix error of uac unit and add control unit
Accordding to the "Universal Serial Bus Device Class Definition for Audio Devices", there are some error in the f_audio_source driver. The audio source can't be recognize by the Windows PC if control unit is not support. In order to support Windows PC, this patch fix error in f_audio_source driver and add support of control unit. Change-Id: Ie508141f032adecfbe2e951ad36d9b8c271b28ba Signed-off-by: Meng Dongyang <daniel.meng@rock-chips.com>
This commit is contained in:
@@ -41,9 +41,12 @@
|
||||
|
||||
#define AUDIO_AC_INTERFACE 0
|
||||
#define AUDIO_AS_INTERFACE 1
|
||||
#define AUDIO_NUM_INTERFACES 2
|
||||
#define AUDIO_NUM_INTERFACES 1
|
||||
#define MAX_INST_NAME_LEN 40
|
||||
|
||||
static int audio_set_cmd(struct usb_audio_control *con, u8 cmd, int value);
|
||||
static int audio_get_cmd(struct usb_audio_control *con, u8 cmd);
|
||||
|
||||
static u32 sample_rate_table[] = {
|
||||
8000, 11025, 16000, 22050, 24000,
|
||||
32000, 40000, 44100, 48000, 56000,
|
||||
@@ -59,7 +62,7 @@ static struct usb_interface_descriptor ac_interface_desc = {
|
||||
.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
|
||||
};
|
||||
|
||||
DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
|
||||
DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
|
||||
|
||||
#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(AUDIO_NUM_INTERFACES)
|
||||
/* 1 input terminal, 1 output terminal and 1 feature unit */
|
||||
@@ -67,7 +70,7 @@ DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
|
||||
+ UAC_DT_INPUT_TERMINAL_SIZE + UAC_DT_OUTPUT_TERMINAL_SIZE \
|
||||
+ UAC_DT_FEATURE_UNIT_SIZE(0))
|
||||
/* B.3.2 Class-Specific AC Interface Descriptor */
|
||||
static struct uac1_ac_header_descriptor_2 ac_header_desc = {
|
||||
static struct uac1_ac_header_descriptor_1 ac_header_desc = {
|
||||
.bLength = UAC_DT_AC_HEADER_LENGTH,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubtype = UAC_HEADER,
|
||||
@@ -75,8 +78,7 @@ static struct uac1_ac_header_descriptor_2 ac_header_desc = {
|
||||
.wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH),
|
||||
.bInCollection = AUDIO_NUM_INTERFACES,
|
||||
.baInterfaceNr = {
|
||||
[0] = AUDIO_AC_INTERFACE,
|
||||
[1] = AUDIO_AS_INTERFACE,
|
||||
[0] = AUDIO_AS_INTERFACE,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -88,7 +90,7 @@ static struct uac_input_terminal_descriptor input_terminal_desc = {
|
||||
.bTerminalID = INPUT_TERMINAL_ID,
|
||||
.wTerminalType = UAC_INPUT_TERMINAL_MICROPHONE,
|
||||
.bAssocTerminal = 0,
|
||||
.wChannelConfig = 0x3,
|
||||
.wChannelConfig = 0,
|
||||
};
|
||||
|
||||
DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0);
|
||||
@@ -100,7 +102,34 @@ static struct uac_feature_unit_descriptor_0 feature_unit_desc = {
|
||||
.bDescriptorSubtype = UAC_FEATURE_UNIT,
|
||||
.bUnitID = FEATURE_UNIT_ID,
|
||||
.bSourceID = INPUT_TERMINAL_ID,
|
||||
.bControlSize = 2,
|
||||
.bControlSize = 1,
|
||||
.bmaControls[0] = (UAC_FU_MUTE | UAC_FU_VOLUME),
|
||||
};
|
||||
|
||||
static struct usb_audio_control mute_control = {
|
||||
.list = LIST_HEAD_INIT(mute_control.list),
|
||||
.name = "Mute Control",
|
||||
.type = UAC_FU_MUTE,
|
||||
/* Todo: add real Mute control code */
|
||||
.set = audio_set_cmd,
|
||||
.get = audio_get_cmd,
|
||||
};
|
||||
|
||||
static struct usb_audio_control volume_control = {
|
||||
.list = LIST_HEAD_INIT(volume_control.list),
|
||||
.name = "Volume Control",
|
||||
.type = UAC_FU_VOLUME,
|
||||
/* Todo: add real Volume control code */
|
||||
.set = audio_set_cmd,
|
||||
.get = audio_get_cmd,
|
||||
};
|
||||
|
||||
static struct usb_audio_control_selector feature_unit = {
|
||||
.list = LIST_HEAD_INIT(feature_unit.list),
|
||||
.id = FEATURE_UNIT_ID,
|
||||
.name = "Mute & Volume Control",
|
||||
.type = UAC_FEATURE_UNIT,
|
||||
.desc = (struct usb_descriptor_header *)&feature_unit_desc,
|
||||
};
|
||||
|
||||
#define OUTPUT_TERMINAL_ID 3
|
||||
@@ -138,7 +167,7 @@ static struct uac1_as_header_descriptor as_header_desc = {
|
||||
.bLength = UAC_DT_AS_HEADER_SIZE,
|
||||
.bDescriptorType = USB_DT_CS_INTERFACE,
|
||||
.bDescriptorSubtype = UAC_AS_GENERAL,
|
||||
.bTerminalLink = INPUT_TERMINAL_ID,
|
||||
.bTerminalLink = OUTPUT_TERMINAL_ID,
|
||||
.bDelay = 1,
|
||||
.wFormatTag = UAC_FORMAT_TYPE_I_PCM,
|
||||
};
|
||||
@@ -233,8 +262,8 @@ static struct snd_pcm_hardware audio_hw_info = {
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER,
|
||||
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
.rate_min = MIN_SAMPLE_RATE,
|
||||
.rate_max = MAX_SAMPLE_RATE,
|
||||
|
||||
@@ -278,6 +307,11 @@ struct audio_dev {
|
||||
|
||||
unsigned int alt;
|
||||
|
||||
/* Control Set command */
|
||||
u8 set_cmd;
|
||||
struct list_head cs;
|
||||
struct usb_audio_control *set_con;
|
||||
|
||||
/* byte size of a "period" */
|
||||
unsigned int period;
|
||||
/* bytes sent since last call to snd_pcm_period_elapsed */
|
||||
@@ -437,6 +471,36 @@ static void audio_request_free(struct usb_request *req, struct usb_ep *ep)
|
||||
}
|
||||
}
|
||||
|
||||
static int audio_set_cmd(struct usb_audio_control *con, u8 cmd, int value)
|
||||
{
|
||||
con->data[cmd] = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int audio_get_cmd(struct usb_audio_control *con, u8 cmd)
|
||||
{
|
||||
return con->data[cmd];
|
||||
}
|
||||
|
||||
/* Todo: add more control selecotor dynamically */
|
||||
static int control_selector_init(struct audio_dev *audio)
|
||||
{
|
||||
INIT_LIST_HEAD(&audio->cs);
|
||||
list_add(&feature_unit.list, &audio->cs);
|
||||
|
||||
INIT_LIST_HEAD(&feature_unit.control);
|
||||
list_add(&mute_control.list, &feature_unit.control);
|
||||
list_add(&volume_control.list, &feature_unit.control);
|
||||
|
||||
volume_control.data[UAC__CUR] = 0xffc0;
|
||||
volume_control.data[UAC__MIN] = 0xe3a0;
|
||||
volume_control.data[UAC__MAX] = 0xfff0;
|
||||
volume_control.data[UAC__RES] = 0x0030;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void audio_req_put(struct audio_dev *audio, struct usb_request *req)
|
||||
{
|
||||
unsigned long flags;
|
||||
@@ -544,8 +608,9 @@ static void audio_send(struct audio_dev *audio)
|
||||
|
||||
static void audio_control_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
{
|
||||
struct audio_dev *audio = ep->driver_data;
|
||||
struct audio_dev *audio = req->context;
|
||||
u8 *buf = req->buf;
|
||||
u32 data = 0;
|
||||
|
||||
pr_debug("audio_control_complete req->status %d req->actual %d\n",
|
||||
req->status, req->actual);
|
||||
@@ -578,6 +643,12 @@ static void audio_control_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (audio->set_con) {
|
||||
memcpy(&data, req->buf, req->length);
|
||||
audio->set_con->set(audio->set_con, audio->set_cmd,
|
||||
le16_to_cpu(data));
|
||||
audio->set_con = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_data_complete(struct usb_ep *ep, struct usb_request *req)
|
||||
@@ -684,9 +755,78 @@ static int audio_get_endpoint_req(struct usb_function *f,
|
||||
return value;
|
||||
}
|
||||
|
||||
static int audio_set_intf_req(struct usb_function *f,
|
||||
const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct audio_dev *audio = func_to_audio(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
|
||||
u16 len = le16_to_cpu(ctrl->wLength);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u8 con_sel = (w_value >> 8) & 0xFF;
|
||||
u8 cmd = (ctrl->bRequest & 0x0F);
|
||||
struct usb_audio_control_selector *cs;
|
||||
struct usb_audio_control *con;
|
||||
|
||||
DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n",
|
||||
ctrl->bRequest, w_value, len, id);
|
||||
|
||||
list_for_each_entry(cs, &audio->cs, list) {
|
||||
if (cs->id == id) {
|
||||
list_for_each_entry(con, &cs->control, list) {
|
||||
if (con->type == con_sel) {
|
||||
audio->set_con = con;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
audio->set_cmd = cmd;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int audio_get_intf_req(struct usb_function *f,
|
||||
const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct audio_dev *audio = func_to_audio(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
int value = -EOPNOTSUPP;
|
||||
u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
|
||||
u16 len = le16_to_cpu(ctrl->wLength);
|
||||
u16 w_value = le16_to_cpu(ctrl->wValue);
|
||||
u8 con_sel = (w_value >> 8) & 0xFF;
|
||||
u8 cmd = (ctrl->bRequest & 0x0F);
|
||||
struct usb_audio_control_selector *cs;
|
||||
struct usb_audio_control *con;
|
||||
|
||||
DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n",
|
||||
ctrl->bRequest, w_value, len, id);
|
||||
|
||||
list_for_each_entry(cs, &audio->cs, list) {
|
||||
if (cs->id == id) {
|
||||
list_for_each_entry(con, &cs->control, list) {
|
||||
if (con->type == con_sel && con->get) {
|
||||
value = con->get(con, cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
len = min_t(size_t, sizeof(value), len);
|
||||
memcpy(req->buf, &value, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int
|
||||
audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct audio_dev *audio = func_to_audio(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
struct usb_request *req = cdev->req;
|
||||
int value = -EOPNOTSUPP;
|
||||
@@ -705,6 +845,14 @@ audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
|
||||
value = audio_get_endpoint_req(f, ctrl);
|
||||
break;
|
||||
|
||||
case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
|
||||
value = audio_set_intf_req(f, ctrl);
|
||||
break;
|
||||
|
||||
case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE:
|
||||
value = audio_get_intf_req(f, ctrl);
|
||||
break;
|
||||
}
|
||||
|
||||
/* respond with data transfer or status phase? */
|
||||
@@ -715,6 +863,7 @@ audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
|
||||
req->zero = 0;
|
||||
req->length = value;
|
||||
req->complete = audio_control_complete;
|
||||
req->context = audio;
|
||||
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
||||
if (value < 0)
|
||||
pr_err("audio response on err %d\n", value);
|
||||
@@ -745,6 +894,15 @@ static int audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int audio_get_alt(struct usb_function *f, unsigned intf)
|
||||
{
|
||||
struct audio_dev *audio = func_to_audio(f);
|
||||
|
||||
pr_debug("%s intf %d, alt %d\n", __func__, intf, audio->alt);
|
||||
|
||||
return audio->alt;
|
||||
}
|
||||
|
||||
static void audio_disable(struct usb_function *f)
|
||||
{
|
||||
struct audio_dev *audio = func_to_audio(f);
|
||||
@@ -766,8 +924,8 @@ static void audio_build_desc(struct audio_dev *audio)
|
||||
u32 rate, i;
|
||||
|
||||
/* Set channel numbers */
|
||||
input_terminal_desc.bNrChannels = 2;
|
||||
as_type_i_desc.bNrChannels = 2;
|
||||
input_terminal_desc.bNrChannels = 1;
|
||||
as_type_i_desc.bNrChannels = 1;
|
||||
|
||||
/* Set sample rates */
|
||||
for (i = 0; i < ARRAY_SIZE(sample_rate_table); i++) {
|
||||
@@ -806,6 +964,8 @@ audio_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
audio->sample_rate = DEFAULT_SAMPLE_RATE;
|
||||
audio->frames_per_msec = DEFAULT_FRAMES_PER_MSEC;
|
||||
|
||||
control_selector_init(audio);
|
||||
|
||||
err = snd_card_setup(c, config);
|
||||
if (err)
|
||||
return err;
|
||||
@@ -826,9 +986,6 @@ audio_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
goto fail;
|
||||
ac_interface_desc.bInterfaceNumber = status;
|
||||
|
||||
/* AUDIO_AC_INTERFACE */
|
||||
ac_header_desc.baInterfaceNr[0] = status;
|
||||
|
||||
status = usb_interface_id(c, f);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
@@ -836,7 +993,7 @@ audio_bind(struct usb_configuration *c, struct usb_function *f)
|
||||
as_interface_alt_1_desc.bInterfaceNumber = status;
|
||||
|
||||
/* AUDIO_AS_INTERFACE */
|
||||
ac_header_desc.baInterfaceNr[1] = status;
|
||||
ac_header_desc.baInterfaceNr[0] = status;
|
||||
|
||||
status = -ENODEV;
|
||||
|
||||
@@ -925,7 +1082,7 @@ static int audio_pcm_open(struct snd_pcm_substream *substream)
|
||||
runtime->private_data = audio;
|
||||
runtime->hw = audio_hw_info;
|
||||
snd_pcm_limit_hw_rates(runtime);
|
||||
runtime->hw.channels_max = 2;
|
||||
runtime->hw.channels_max = 1;
|
||||
|
||||
audio->substream = substream;
|
||||
|
||||
@@ -959,7 +1116,7 @@ static int audio_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
|
||||
if (rate > MAX_SAMPLE_RATE || rate < MIN_SAMPLE_RATE)
|
||||
return -EINVAL;
|
||||
if (channels != 2)
|
||||
if (channels != 1)
|
||||
return -EINVAL;
|
||||
|
||||
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
@@ -1026,6 +1183,7 @@ static struct audio_dev _audio_dev = {
|
||||
.bind = audio_bind,
|
||||
.unbind = audio_unbind,
|
||||
.set_alt = audio_set_alt,
|
||||
.get_alt = audio_get_alt,
|
||||
.setup = audio_setup,
|
||||
.disable = audio_disable,
|
||||
.free_func = audio_free_func,
|
||||
@@ -1064,6 +1222,8 @@ int audio_source_bind_config(struct usb_configuration *c,
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
control_selector_init(audio);
|
||||
|
||||
err = usb_add_function(c, &audio->func);
|
||||
if (err)
|
||||
goto add_fail;
|
||||
|
||||
Reference in New Issue
Block a user