diff options
Diffstat (limited to 'drivers/usb/gadget/function/f_uac1.c')
| -rw-r--r-- | drivers/usb/gadget/function/f_uac1.c | 272 | 
1 files changed, 206 insertions, 66 deletions
| diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index 560382e0a8f3..d04707580068 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -19,6 +19,12 @@  #include "u_audio.h"  #include "u_uac1.h" +/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */ +#define UAC1_CHANNEL_MASK 0x0FFF + +#define EPIN_EN(_opts) ((_opts)->p_chmask != 0) +#define EPOUT_EN(_opts) ((_opts)->c_chmask != 0) +  struct f_uac1 {  	struct g_audio g_audio;  	u8 ac_intf, as_in_intf, as_out_intf; @@ -30,6 +36,11 @@ static inline struct f_uac1 *func_to_uac1(struct usb_function *f)  	return container_of(f, struct f_uac1, g_audio.func);  } +static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio) +{ +	return container_of(audio->func.fi, struct f_uac1_opts, func_inst); +} +  /*   * DESCRIPTORS ... most are static, but strings and full   * configuration descriptors are built on demand. @@ -42,11 +53,6 @@ static inline struct f_uac1 *func_to_uac1(struct usb_function *f)   * USB-OUT -> IT_1 -> OT_2 -> ALSA_Capture   * ALSA_Playback -> IT_3 -> OT_4 -> USB-IN   */ -#define F_AUDIO_AC_INTERFACE		0 -#define F_AUDIO_AS_OUT_INTERFACE	1 -#define F_AUDIO_AS_IN_INTERFACE		2 -/* Number of streaming interfaces */ -#define F_AUDIO_NUM_INTERFACES		2  /* B.3.1  Standard AC Interface Descriptor */  static struct usb_interface_descriptor ac_interface_desc = { @@ -57,73 +63,47 @@ static struct usb_interface_descriptor ac_interface_desc = {  	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOCONTROL,  }; -/* - * The number of AudioStreaming and MIDIStreaming interfaces - * in the Audio Interface Collection - */ -DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); - -#define UAC_DT_AC_HEADER_LENGTH	UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) -/* 2 input terminals and 2 output terminals */ -#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \ -	+ 2*UAC_DT_INPUT_TERMINAL_SIZE + 2*UAC_DT_OUTPUT_TERMINAL_SIZE)  /* B.3.2  Class-Specific AC Interface Descriptor */ -static struct uac1_ac_header_descriptor_2 ac_header_desc = { -	.bLength =		UAC_DT_AC_HEADER_LENGTH, -	.bDescriptorType =	USB_DT_CS_INTERFACE, -	.bDescriptorSubtype =	UAC_HEADER, -	.bcdADC =		cpu_to_le16(0x0100), -	.wTotalLength =		cpu_to_le16(UAC_DT_TOTAL_LENGTH), -	.bInCollection =	F_AUDIO_NUM_INTERFACES, -	.baInterfaceNr = { -	/* Interface number of the AudioStream interfaces */ -		[0] =		1, -		[1] =		2, -	} -}; +static struct uac1_ac_header_descriptor *ac_header_desc; -#define USB_OUT_IT_ID	1  static struct uac_input_terminal_descriptor usb_out_it_desc = {  	.bLength =		UAC_DT_INPUT_TERMINAL_SIZE,  	.bDescriptorType =	USB_DT_CS_INTERFACE,  	.bDescriptorSubtype =	UAC_INPUT_TERMINAL, -	.bTerminalID =		USB_OUT_IT_ID, +	/* .bTerminalID =	DYNAMIC */  	.wTerminalType =	cpu_to_le16(UAC_TERMINAL_STREAMING),  	.bAssocTerminal =	0,  	.wChannelConfig =	cpu_to_le16(0x3),  }; -#define IO_OUT_OT_ID	2  static struct uac1_output_terminal_descriptor io_out_ot_desc = {  	.bLength		= UAC_DT_OUTPUT_TERMINAL_SIZE,  	.bDescriptorType	= USB_DT_CS_INTERFACE,  	.bDescriptorSubtype	= UAC_OUTPUT_TERMINAL, -	.bTerminalID		= IO_OUT_OT_ID, +	/* .bTerminalID =	DYNAMIC */  	.wTerminalType		= cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),  	.bAssocTerminal		= 0, -	.bSourceID		= USB_OUT_IT_ID, +	/* .bSourceID =		DYNAMIC */  }; -#define IO_IN_IT_ID	3  static struct uac_input_terminal_descriptor io_in_it_desc = {  	.bLength		= UAC_DT_INPUT_TERMINAL_SIZE,  	.bDescriptorType	= USB_DT_CS_INTERFACE,  	.bDescriptorSubtype	= UAC_INPUT_TERMINAL, -	.bTerminalID		= IO_IN_IT_ID, +	/* .bTerminalID		= DYNAMIC */  	.wTerminalType		= cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),  	.bAssocTerminal		= 0,  	.wChannelConfig		= cpu_to_le16(0x3),  }; -#define USB_IN_OT_ID	4  static struct uac1_output_terminal_descriptor usb_in_ot_desc = {  	.bLength =		UAC_DT_OUTPUT_TERMINAL_SIZE,  	.bDescriptorType =	USB_DT_CS_INTERFACE,  	.bDescriptorSubtype =	UAC_OUTPUT_TERMINAL, -	.bTerminalID =		USB_IN_OT_ID, +	/* .bTerminalID =	DYNAMIC */  	.wTerminalType =	cpu_to_le16(UAC_TERMINAL_STREAMING),  	.bAssocTerminal =	0, -	.bSourceID =		IO_IN_IT_ID, +	/* .bSourceID =		DYNAMIC */  };  /* B.4.1  Standard AS Interface Descriptor */ @@ -168,7 +148,7 @@ static struct uac1_as_header_descriptor as_out_header_desc = {  	.bLength =		UAC_DT_AS_HEADER_SIZE,  	.bDescriptorType =	USB_DT_CS_INTERFACE,  	.bDescriptorSubtype =	UAC_AS_GENERAL, -	.bTerminalLink =	USB_OUT_IT_ID, +	/* .bTerminalLink =	DYNAMIC */  	.bDelay =		1,  	.wFormatTag =		cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),  }; @@ -177,7 +157,7 @@ static struct uac1_as_header_descriptor as_in_header_desc = {  	.bLength =		UAC_DT_AS_HEADER_SIZE,  	.bDescriptorType =	USB_DT_CS_INTERFACE,  	.bDescriptorSubtype =	UAC_AS_GENERAL, -	.bTerminalLink =	USB_IN_OT_ID, +	/* .bTerminalLink =	DYNAMIC */  	.bDelay =		1,  	.wFormatTag =		cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),  }; @@ -505,11 +485,144 @@ static void f_audio_disable(struct usb_function *f)  /*-------------------------------------------------------------------------*/ +static struct +uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1_opts *opts) +{ +	struct uac1_ac_header_descriptor *ac_desc; +	int ac_header_desc_size; +	int num_ifaces = 0; + +	if (EPOUT_EN(opts)) +		num_ifaces++; +	if (EPIN_EN(opts)) +		num_ifaces++; + +	ac_header_desc_size = UAC_DT_AC_HEADER_SIZE(num_ifaces); + +	ac_desc = kzalloc(ac_header_desc_size, GFP_KERNEL); +	if (!ac_desc) +		return NULL; + +	ac_desc->bLength = ac_header_desc_size; +	ac_desc->bDescriptorType = USB_DT_CS_INTERFACE; +	ac_desc->bDescriptorSubtype = UAC_HEADER; +	ac_desc->bcdADC = cpu_to_le16(0x0100); +	ac_desc->bInCollection = num_ifaces; + +	/* wTotalLength and baInterfaceNr will be defined later */ + +	return ac_desc; +} + +/* Use macro to overcome line length limitation */ +#define USBDHDR(p) (struct usb_descriptor_header *)(p) + +static void setup_descriptor(struct f_uac1_opts *opts) +{ +	/* patch descriptors */ +	int i = 1; /* ID's start with 1 */ + +	if (EPOUT_EN(opts)) +		usb_out_it_desc.bTerminalID = i++; +	if (EPIN_EN(opts)) +		io_in_it_desc.bTerminalID = i++; +	if (EPOUT_EN(opts)) +		io_out_ot_desc.bTerminalID = i++; +	if (EPIN_EN(opts)) +		usb_in_ot_desc.bTerminalID = i++; + +	usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; +	io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + +	as_out_header_desc.bTerminalLink = usb_out_it_desc.bTerminalID; +	as_in_header_desc.bTerminalLink = usb_in_ot_desc.bTerminalID; + +	ac_header_desc->wTotalLength = cpu_to_le16(ac_header_desc->bLength); + +	if (EPIN_EN(opts)) { +		u16 len = le16_to_cpu(ac_header_desc->wTotalLength); + +		len += sizeof(usb_in_ot_desc); +		len += sizeof(io_in_it_desc); +		ac_header_desc->wTotalLength = cpu_to_le16(len); +	} +	if (EPOUT_EN(opts)) { +		u16 len = le16_to_cpu(ac_header_desc->wTotalLength); + +		len += sizeof(usb_out_it_desc); +		len += sizeof(io_out_ot_desc); +		ac_header_desc->wTotalLength = cpu_to_le16(len); +	} + +	i = 0; +	f_audio_desc[i++] = USBDHDR(&ac_interface_desc); +	f_audio_desc[i++] = USBDHDR(ac_header_desc); + +	if (EPOUT_EN(opts)) { +		f_audio_desc[i++] = USBDHDR(&usb_out_it_desc); +		f_audio_desc[i++] = USBDHDR(&io_out_ot_desc); +	} + +	if (EPIN_EN(opts)) { +		f_audio_desc[i++] = USBDHDR(&io_in_it_desc); +		f_audio_desc[i++] = USBDHDR(&usb_in_ot_desc); +	} + +	if (EPOUT_EN(opts)) { +		f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_0_desc); +		f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_1_desc); +		f_audio_desc[i++] = USBDHDR(&as_out_header_desc); +		f_audio_desc[i++] = USBDHDR(&as_out_type_i_desc); +		f_audio_desc[i++] = USBDHDR(&as_out_ep_desc); +		f_audio_desc[i++] = USBDHDR(&as_iso_out_desc); +	} +	if (EPIN_EN(opts)) { +		f_audio_desc[i++] = USBDHDR(&as_in_interface_alt_0_desc); +		f_audio_desc[i++] = USBDHDR(&as_in_interface_alt_1_desc); +		f_audio_desc[i++] = USBDHDR(&as_in_header_desc); +		f_audio_desc[i++] = USBDHDR(&as_in_type_i_desc); +		f_audio_desc[i++] = USBDHDR(&as_in_ep_desc); +		f_audio_desc[i++] = USBDHDR(&as_iso_in_desc); +	} +	f_audio_desc[i] = NULL; +} + +static int f_audio_validate_opts(struct g_audio *audio, struct device *dev) +{ +	struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + +	if (!opts->p_chmask && !opts->c_chmask) { +		dev_err(dev, "Error: no playback and capture channels\n"); +		return -EINVAL; +	} else if (opts->p_chmask & ~UAC1_CHANNEL_MASK) { +		dev_err(dev, "Error: unsupported playback channels mask\n"); +		return -EINVAL; +	} else if (opts->c_chmask & ~UAC1_CHANNEL_MASK) { +		dev_err(dev, "Error: unsupported capture channels mask\n"); +		return -EINVAL; +	} else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) { +		dev_err(dev, "Error: incorrect playback sample size\n"); +		return -EINVAL; +	} else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) { +		dev_err(dev, "Error: incorrect capture sample size\n"); +		return -EINVAL; +	} else if (!opts->p_srate) { +		dev_err(dev, "Error: incorrect playback sampling rate\n"); +		return -EINVAL; +	} else if (!opts->c_srate) { +		dev_err(dev, "Error: incorrect capture sampling rate\n"); +		return -EINVAL; +	} + +	return 0; +} +  /* audio function driver setup/binding */  static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)  {  	struct usb_composite_dev	*cdev = c->cdev;  	struct usb_gadget		*gadget = cdev->gadget; +	struct device			*dev = &gadget->dev;  	struct f_uac1			*uac1 = func_to_uac1(f);  	struct g_audio			*audio = func_to_g_audio(f);  	struct f_uac1_opts		*audio_opts; @@ -517,13 +630,23 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)  	struct usb_string		*us;  	u8				*sam_freq;  	int				rate; +	int				ba_iface_id;  	int				status; +	status = f_audio_validate_opts(audio, dev); +	if (status) +		return status; +  	audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);  	us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));  	if (IS_ERR(us))  		return PTR_ERR(us); + +	ac_header_desc = build_ac_header_desc(audio_opts); +	if (!ac_header_desc) +		return -ENOMEM; +  	ac_interface_desc.iInterface = us[STR_AC_IF].id;  	usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id;  	usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id; @@ -564,40 +687,52 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)  	uac1->ac_intf = status;  	uac1->ac_alt = 0; -	status = usb_interface_id(c, f); -	if (status < 0) -		goto fail; -	as_out_interface_alt_0_desc.bInterfaceNumber = status; -	as_out_interface_alt_1_desc.bInterfaceNumber = status; -	ac_header_desc.baInterfaceNr[0] = status; -	uac1->as_out_intf = status; -	uac1->as_out_alt = 0; +	ba_iface_id = 0; + +	if (EPOUT_EN(audio_opts)) { +		status = usb_interface_id(c, f); +		if (status < 0) +			goto fail; +		as_out_interface_alt_0_desc.bInterfaceNumber = status; +		as_out_interface_alt_1_desc.bInterfaceNumber = status; +		ac_header_desc->baInterfaceNr[ba_iface_id++] = status; +		uac1->as_out_intf = status; +		uac1->as_out_alt = 0; +	} -	status = usb_interface_id(c, f); -	if (status < 0) -		goto fail; -	as_in_interface_alt_0_desc.bInterfaceNumber = status; -	as_in_interface_alt_1_desc.bInterfaceNumber = status; -	ac_header_desc.baInterfaceNr[1] = status; -	uac1->as_in_intf = status; -	uac1->as_in_alt = 0; +	if (EPIN_EN(audio_opts)) { +		status = usb_interface_id(c, f); +		if (status < 0) +			goto fail; +		as_in_interface_alt_0_desc.bInterfaceNumber = status; +		as_in_interface_alt_1_desc.bInterfaceNumber = status; +		ac_header_desc->baInterfaceNr[ba_iface_id++] = status; +		uac1->as_in_intf = status; +		uac1->as_in_alt = 0; +	}  	audio->gadget = gadget;  	status = -ENODEV;  	/* allocate instance-specific endpoints */ -	ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); -	if (!ep) -		goto fail; -	audio->out_ep = ep; -	audio->out_ep->desc = &as_out_ep_desc; +	if (EPOUT_EN(audio_opts)) { +		ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); +		if (!ep) +			goto fail; +		audio->out_ep = ep; +		audio->out_ep->desc = &as_out_ep_desc; +	} -	ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc); -	if (!ep) -		goto fail; -	audio->in_ep = ep; -	audio->in_ep->desc = &as_in_ep_desc; +	if (EPIN_EN(audio_opts)) { +		ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc); +		if (!ep) +			goto fail; +		audio->in_ep = ep; +		audio->in_ep->desc = &as_in_ep_desc; +	} + +	setup_descriptor(audio_opts);  	/* copy descriptors, and track endpoint copies */  	status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, @@ -624,6 +759,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)  err_card_register:  	usb_free_all_descriptors(f);  fail: +	kfree(ac_header_desc); +	ac_header_desc = NULL;  	return status;  } @@ -766,6 +903,9 @@ static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)  	g_audio_cleanup(audio);  	usb_free_all_descriptors(f); +	kfree(ac_header_desc); +	ac_header_desc = NULL; +  	audio->gadget = NULL;  } | 
