diff options
Diffstat (limited to 'drivers/usb/gadget/function/f_uac1.c')
| -rw-r--r-- | drivers/usb/gadget/function/f_uac1.c | 768 | 
1 files changed, 768 insertions, 0 deletions
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c new file mode 100644 index 000000000000..2b4c82d84bfc --- /dev/null +++ b/drivers/usb/gadget/function/f_uac1.c @@ -0,0 +1,768 @@ +/* + * f_audio.c -- USB Audio class function driver +  * + * Copyright (C) 2008 Bryan Wu <[email protected]> + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/atomic.h> + +#include "u_uac1.h" + +#define OUT_EP_MAX_PACKET_SIZE	200 +static int req_buf_size = OUT_EP_MAX_PACKET_SIZE; +module_param(req_buf_size, int, S_IRUGO); +MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size"); + +static int req_count = 256; +module_param(req_count, int, S_IRUGO); +MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count"); + +static int audio_buf_size = 48000; +module_param(audio_buf_size, int, S_IRUGO); +MODULE_PARM_DESC(audio_buf_size, "Audio buffer size"); + +static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value); +static int generic_get_cmd(struct usb_audio_control *con, u8 cmd); + +/* + * DESCRIPTORS ... most are static, but strings and full + * configuration descriptors are built on demand. + */ + +/* + * We have two interfaces- AudioControl and AudioStreaming + * TODO: only supcard playback currently + */ +#define F_AUDIO_AC_INTERFACE	0 +#define F_AUDIO_AS_INTERFACE	1 +#define F_AUDIO_NUM_INTERFACES	2 + +/* B.3.1  Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc __initdata = { +	.bLength =		USB_DT_INTERFACE_SIZE, +	.bDescriptorType =	USB_DT_INTERFACE, +	.bNumEndpoints =	0, +	.bInterfaceClass =	USB_CLASS_AUDIO, +	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOCONTROL, +}; + +DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); + +#define UAC_DT_AC_HEADER_LENGTH	UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) +/* 1 input terminal, 1 output terminal and 1 feature unit */ +#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH + 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 = { +	.bLength =		UAC_DT_AC_HEADER_LENGTH, +	.bDescriptorType =	USB_DT_CS_INTERFACE, +	.bDescriptorSubtype =	UAC_HEADER, +	.bcdADC =		__constant_cpu_to_le16(0x0100), +	.wTotalLength =		__constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), +	.bInCollection =	F_AUDIO_NUM_INTERFACES, +	.baInterfaceNr = { +		[0] =		F_AUDIO_AC_INTERFACE, +		[1] =		F_AUDIO_AS_INTERFACE, +	} +}; + +#define INPUT_TERMINAL_ID	1 +static struct uac_input_terminal_descriptor input_terminal_desc = { +	.bLength =		UAC_DT_INPUT_TERMINAL_SIZE, +	.bDescriptorType =	USB_DT_CS_INTERFACE, +	.bDescriptorSubtype =	UAC_INPUT_TERMINAL, +	.bTerminalID =		INPUT_TERMINAL_ID, +	.wTerminalType =	UAC_TERMINAL_STREAMING, +	.bAssocTerminal =	0, +	.wChannelConfig =	0x3, +}; + +DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0); + +#define FEATURE_UNIT_ID		2 +static struct uac_feature_unit_descriptor_0 feature_unit_desc = { +	.bLength		= UAC_DT_FEATURE_UNIT_SIZE(0), +	.bDescriptorType	= USB_DT_CS_INTERFACE, +	.bDescriptorSubtype	= UAC_FEATURE_UNIT, +	.bUnitID		= FEATURE_UNIT_ID, +	.bSourceID		= INPUT_TERMINAL_ID, +	.bControlSize		= 2, +	.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 = generic_set_cmd, +	.get = generic_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 = generic_set_cmd, +	.get = generic_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 +static struct uac1_output_terminal_descriptor output_terminal_desc = { +	.bLength		= UAC_DT_OUTPUT_TERMINAL_SIZE, +	.bDescriptorType	= USB_DT_CS_INTERFACE, +	.bDescriptorSubtype	= UAC_OUTPUT_TERMINAL, +	.bTerminalID		= OUTPUT_TERMINAL_ID, +	.wTerminalType		= UAC_OUTPUT_TERMINAL_SPEAKER, +	.bAssocTerminal		= FEATURE_UNIT_ID, +	.bSourceID		= FEATURE_UNIT_ID, +}; + +/* B.4.1  Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_interface_alt_0_desc = { +	.bLength =		USB_DT_INTERFACE_SIZE, +	.bDescriptorType =	USB_DT_INTERFACE, +	.bAlternateSetting =	0, +	.bNumEndpoints =	0, +	.bInterfaceClass =	USB_CLASS_AUDIO, +	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_interface_alt_1_desc = { +	.bLength =		USB_DT_INTERFACE_SIZE, +	.bDescriptorType =	USB_DT_INTERFACE, +	.bAlternateSetting =	1, +	.bNumEndpoints =	1, +	.bInterfaceClass =	USB_CLASS_AUDIO, +	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOSTREAMING, +}; + +/* B.4.2  Class-Specific AS Interface Descriptor */ +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, +	.bDelay =		1, +	.wFormatTag =		UAC_FORMAT_TYPE_I_PCM, +}; + +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = { +	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), +	.bDescriptorType =	USB_DT_CS_INTERFACE, +	.bDescriptorSubtype =	UAC_FORMAT_TYPE, +	.bFormatType =		UAC_FORMAT_TYPE_I, +	.bSubframeSize =	2, +	.bBitResolution =	16, +	.bSamFreqType =		1, +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor as_out_ep_desc  = { +	.bLength =		USB_DT_ENDPOINT_AUDIO_SIZE, +	.bDescriptorType =	USB_DT_ENDPOINT, +	.bEndpointAddress =	USB_DIR_OUT, +	.bmAttributes =		USB_ENDPOINT_SYNC_ADAPTIVE +				| USB_ENDPOINT_XFER_ISOC, +	.wMaxPacketSize =	__constant_cpu_to_le16(OUT_EP_MAX_PACKET_SIZE), +	.bInterval =		4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_out_desc __initdata = { +	.bLength =		UAC_ISO_ENDPOINT_DESC_SIZE, +	.bDescriptorType =	USB_DT_CS_ENDPOINT, +	.bDescriptorSubtype =	UAC_EP_GENERAL, +	.bmAttributes = 	1, +	.bLockDelayUnits =	1, +	.wLockDelay =		__constant_cpu_to_le16(1), +}; + +static struct usb_descriptor_header *f_audio_desc[] __initdata = { +	(struct usb_descriptor_header *)&ac_interface_desc, +	(struct usb_descriptor_header *)&ac_header_desc, + +	(struct usb_descriptor_header *)&input_terminal_desc, +	(struct usb_descriptor_header *)&output_terminal_desc, +	(struct usb_descriptor_header *)&feature_unit_desc, + +	(struct usb_descriptor_header *)&as_interface_alt_0_desc, +	(struct usb_descriptor_header *)&as_interface_alt_1_desc, +	(struct usb_descriptor_header *)&as_header_desc, + +	(struct usb_descriptor_header *)&as_type_i_desc, + +	(struct usb_descriptor_header *)&as_out_ep_desc, +	(struct usb_descriptor_header *)&as_iso_out_desc, +	NULL, +}; + +/* + * This function is an ALSA sound card following USB Audio Class Spec 1.0. + */ + +/*-------------------------------------------------------------------------*/ +struct f_audio_buf { +	u8 *buf; +	int actual; +	struct list_head list; +}; + +static struct f_audio_buf *f_audio_buffer_alloc(int buf_size) +{ +	struct f_audio_buf *copy_buf; + +	copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC); +	if (!copy_buf) +		return ERR_PTR(-ENOMEM); + +	copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC); +	if (!copy_buf->buf) { +		kfree(copy_buf); +		return ERR_PTR(-ENOMEM); +	} + +	return copy_buf; +} + +static void f_audio_buffer_free(struct f_audio_buf *audio_buf) +{ +	kfree(audio_buf->buf); +	kfree(audio_buf); +} +/*-------------------------------------------------------------------------*/ + +struct f_audio { +	struct gaudio			card; + +	/* endpoints handle full and/or high speeds */ +	struct usb_ep			*out_ep; + +	spinlock_t			lock; +	struct f_audio_buf *copy_buf; +	struct work_struct playback_work; +	struct list_head play_queue; + +	/* Control Set command */ +	struct list_head cs; +	u8 set_cmd; +	struct usb_audio_control *set_con; +}; + +static inline struct f_audio *func_to_audio(struct usb_function *f) +{ +	return container_of(f, struct f_audio, card.func); +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_playback_work(struct work_struct *data) +{ +	struct f_audio *audio = container_of(data, struct f_audio, +					playback_work); +	struct f_audio_buf *play_buf; + +	spin_lock_irq(&audio->lock); +	if (list_empty(&audio->play_queue)) { +		spin_unlock_irq(&audio->lock); +		return; +	} +	play_buf = list_first_entry(&audio->play_queue, +			struct f_audio_buf, list); +	list_del(&play_buf->list); +	spin_unlock_irq(&audio->lock); + +	u_audio_playback(&audio->card, play_buf->buf, play_buf->actual); +	f_audio_buffer_free(play_buf); +} + +static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req) +{ +	struct f_audio *audio = req->context; +	struct usb_composite_dev *cdev = audio->card.func.config->cdev; +	struct f_audio_buf *copy_buf = audio->copy_buf; +	int err; + +	if (!copy_buf) +		return -EINVAL; + +	/* Copy buffer is full, add it to the play_queue */ +	if (audio_buf_size - copy_buf->actual < req->actual) { +		list_add_tail(©_buf->list, &audio->play_queue); +		schedule_work(&audio->playback_work); +		copy_buf = f_audio_buffer_alloc(audio_buf_size); +		if (IS_ERR(copy_buf)) +			return -ENOMEM; +	} + +	memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual); +	copy_buf->actual += req->actual; +	audio->copy_buf = copy_buf; + +	err = usb_ep_queue(ep, req, GFP_ATOMIC); +	if (err) +		ERROR(cdev, "%s queue req: %d\n", ep->name, err); + +	return 0; + +} + +static void f_audio_complete(struct usb_ep *ep, struct usb_request *req) +{ +	struct f_audio *audio = req->context; +	int status = req->status; +	u32 data = 0; +	struct usb_ep *out_ep = audio->out_ep; + +	switch (status) { + +	case 0:				/* normal completion? */ +		if (ep == out_ep) +			f_audio_out_ep_complete(ep, req); +		else 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; +		} +		break; +	default: +		break; +	} +} + +static int audio_set_intf_req(struct usb_function *f, +		const struct usb_ctrlrequest *ctrl) +{ +	struct f_audio		*audio = func_to_audio(f); +	struct usb_composite_dev *cdev = f->config->cdev; +	struct usb_request	*req = cdev->req; +	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; +	req->context = audio; +	req->complete = f_audio_complete; + +	return len; +} + +static int audio_get_intf_req(struct usb_function *f, +		const struct usb_ctrlrequest *ctrl) +{ +	struct f_audio		*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; +				} +			} +			break; +		} +	} + +	req->context = audio; +	req->complete = f_audio_complete; +	len = min_t(size_t, sizeof(value), len); +	memcpy(req->buf, &value, len); + +	return len; +} + +static int audio_set_endpoint_req(struct usb_function *f, +		const struct usb_ctrlrequest *ctrl) +{ +	struct usb_composite_dev *cdev = f->config->cdev; +	int			value = -EOPNOTSUPP; +	u16			ep = le16_to_cpu(ctrl->wIndex); +	u16			len = le16_to_cpu(ctrl->wLength); +	u16			w_value = le16_to_cpu(ctrl->wValue); + +	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", +			ctrl->bRequest, w_value, len, ep); + +	switch (ctrl->bRequest) { +	case UAC_SET_CUR: +		value = len; +		break; + +	case UAC_SET_MIN: +		break; + +	case UAC_SET_MAX: +		break; + +	case UAC_SET_RES: +		break; + +	case UAC_SET_MEM: +		break; + +	default: +		break; +	} + +	return value; +} + +static int audio_get_endpoint_req(struct usb_function *f, +		const struct usb_ctrlrequest *ctrl) +{ +	struct usb_composite_dev *cdev = f->config->cdev; +	int value = -EOPNOTSUPP; +	u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); +	u16 len = le16_to_cpu(ctrl->wLength); +	u16 w_value = le16_to_cpu(ctrl->wValue); + +	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", +			ctrl->bRequest, w_value, len, ep); + +	switch (ctrl->bRequest) { +	case UAC_GET_CUR: +	case UAC_GET_MIN: +	case UAC_GET_MAX: +	case UAC_GET_RES: +		value = len; +		break; +	case UAC_GET_MEM: +		break; +	default: +		break; +	} + +	return value; +} + +static int +f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ +	struct usb_composite_dev *cdev = f->config->cdev; +	struct usb_request	*req = cdev->req; +	int			value = -EOPNOTSUPP; +	u16			w_index = le16_to_cpu(ctrl->wIndex); +	u16			w_value = le16_to_cpu(ctrl->wValue); +	u16			w_length = le16_to_cpu(ctrl->wLength); + +	/* composite driver infrastructure handles everything; interface +	 * activation uses set_alt(). +	 */ +	switch (ctrl->bRequestType) { +	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; + +	case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: +		value = audio_set_endpoint_req(f, ctrl); +		break; + +	case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: +		value = audio_get_endpoint_req(f, ctrl); +		break; + +	default: +		ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", +			ctrl->bRequestType, ctrl->bRequest, +			w_value, w_index, w_length); +	} + +	/* respond with data transfer or status phase? */ +	if (value >= 0) { +		DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n", +			ctrl->bRequestType, ctrl->bRequest, +			w_value, w_index, w_length); +		req->zero = 0; +		req->length = value; +		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); +		if (value < 0) +			ERROR(cdev, "audio response on err %d\n", value); +	} + +	/* device either stalls (value < 0) or reports success */ +	return value; +} + +static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ +	struct f_audio		*audio = func_to_audio(f); +	struct usb_composite_dev *cdev = f->config->cdev; +	struct usb_ep *out_ep = audio->out_ep; +	struct usb_request *req; +	int i = 0, err = 0; + +	DBG(cdev, "intf %d, alt %d\n", intf, alt); + +	if (intf == 1) { +		if (alt == 1) { +			usb_ep_enable(out_ep); +			out_ep->driver_data = audio; +			audio->copy_buf = f_audio_buffer_alloc(audio_buf_size); +			if (IS_ERR(audio->copy_buf)) +				return -ENOMEM; + +			/* +			 * allocate a bunch of read buffers +			 * and queue them all at once. +			 */ +			for (i = 0; i < req_count && err == 0; i++) { +				req = usb_ep_alloc_request(out_ep, GFP_ATOMIC); +				if (req) { +					req->buf = kzalloc(req_buf_size, +							GFP_ATOMIC); +					if (req->buf) { +						req->length = req_buf_size; +						req->context = audio; +						req->complete = +							f_audio_complete; +						err = usb_ep_queue(out_ep, +							req, GFP_ATOMIC); +						if (err) +							ERROR(cdev, +							"%s queue req: %d\n", +							out_ep->name, err); +					} else +						err = -ENOMEM; +				} else +					err = -ENOMEM; +			} + +		} else { +			struct f_audio_buf *copy_buf = audio->copy_buf; +			if (copy_buf) { +				list_add_tail(©_buf->list, +						&audio->play_queue); +				schedule_work(&audio->playback_work); +			} +		} +	} + +	return err; +} + +static void f_audio_disable(struct usb_function *f) +{ +	return; +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_build_desc(struct f_audio *audio) +{ +	struct gaudio *card = &audio->card; +	u8 *sam_freq; +	int rate; + +	/* Set channel numbers */ +	input_terminal_desc.bNrChannels = u_audio_get_playback_channels(card); +	as_type_i_desc.bNrChannels = u_audio_get_playback_channels(card); + +	/* Set sample rates */ +	rate = u_audio_get_playback_rate(card); +	sam_freq = as_type_i_desc.tSamFreq[0]; +	memcpy(sam_freq, &rate, 3); + +	/* Todo: Set Sample bits and other parameters */ + +	return; +} + +/* audio function driver setup/binding */ +static int __init +f_audio_bind(struct usb_configuration *c, struct usb_function *f) +{ +	struct usb_composite_dev *cdev = c->cdev; +	struct f_audio		*audio = func_to_audio(f); +	int			status; +	struct usb_ep		*ep = NULL; + +	f_audio_build_desc(audio); + +	/* allocate instance-specific interface IDs, and patch descriptors */ +	status = usb_interface_id(c, f); +	if (status < 0) +		goto fail; +	ac_interface_desc.bInterfaceNumber = status; + +	status = usb_interface_id(c, f); +	if (status < 0) +		goto fail; +	as_interface_alt_0_desc.bInterfaceNumber = status; +	as_interface_alt_1_desc.bInterfaceNumber = status; + +	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; +	ep->driver_data = cdev;	/* claim */ + +	status = -ENOMEM; + +	/* copy descriptors, and track endpoint copies */ +	status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL); +	if (status) +		goto fail; +	return 0; + +fail: +	if (ep) +		ep->driver_data = NULL; +	return status; +} + +static void +f_audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ +	struct f_audio		*audio = func_to_audio(f); + +	usb_free_all_descriptors(f); +	kfree(audio); +} + +/*-------------------------------------------------------------------------*/ + +static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) +{ +	con->data[cmd] = value; + +	return 0; +} + +static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) +{ +	return con->data[cmd]; +} + +/* Todo: add more control selecotor dynamically */ +static int __init control_selector_init(struct f_audio *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; +} + +/** + * audio_bind_config - add USB audio function to a configuration + * @c: the configuration to supcard the USB audio function + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + */ +static int __init audio_bind_config(struct usb_configuration *c) +{ +	struct f_audio *audio; +	int status; + +	/* allocate and initialize one new instance */ +	audio = kzalloc(sizeof *audio, GFP_KERNEL); +	if (!audio) +		return -ENOMEM; + +	audio->card.func.name = "g_audio"; +	audio->card.gadget = c->cdev->gadget; + +	INIT_LIST_HEAD(&audio->play_queue); +	spin_lock_init(&audio->lock); + +	/* set up ASLA audio devices */ +	status = gaudio_setup(&audio->card); +	if (status < 0) +		goto setup_fail; + +	audio->card.func.strings = audio_strings; +	audio->card.func.bind = f_audio_bind; +	audio->card.func.unbind = f_audio_unbind; +	audio->card.func.set_alt = f_audio_set_alt; +	audio->card.func.setup = f_audio_setup; +	audio->card.func.disable = f_audio_disable; + +	control_selector_init(audio); + +	INIT_WORK(&audio->playback_work, f_audio_playback_work); + +	status = usb_add_function(c, &audio->card.func); +	if (status) +		goto add_fail; + +	INFO(c->cdev, "audio_buf_size %d, req_buf_size %d, req_count %d\n", +		audio_buf_size, req_buf_size, req_count); + +	return status; + +add_fail: +	gaudio_cleanup(); +setup_fail: +	kfree(audio); +	return status; +}  |