summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/function
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-06-20 11:39:34 +0800
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-06-20 11:39:34 +0800
commit24040a58379e2f2fa6aa9466911b758073b6bdfa (patch)
tree772a93302d0e2e0f2aa333bb10a060e2e886c645 /drivers/usb/gadget/function
parent571949a40a9c00fdaa3d0256cf43c9e0c25ff2d1 (diff)
parent0591bc2360152f851e29246884805bb77a2c3b9d (diff)
Merge tag 'usb-for-v4.13' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-testing
Felipe writes: usb: changes for v4.13 merge window This time around we have a total of 57 non-merge commits. A list of most important changes follows: - Improvements to dwc3 tracing interface - Initial dual-role support for dwc3 - Improvements to how we handle DMA resources in dwc3 - A new f_uac1 implementation which much more flexible - Removal of AVR32 bits - Improvements to f_mass_storage driver
Diffstat (limited to 'drivers/usb/gadget/function')
-rw-r--r--drivers/usb/gadget/function/Makefile5
-rw-r--r--drivers/usb/gadget/function/f_fs.c110
-rw-r--r--drivers/usb/gadget/function/f_mass_storage.c430
-rw-r--r--drivers/usb/gadget/function/f_uac1.c896
-rw-r--r--drivers/usb/gadget/function/f_uac1_legacy.c1021
-rw-r--r--drivers/usb/gadget/function/f_uac2.c807
-rw-r--r--drivers/usb/gadget/function/storage_common.h18
-rw-r--r--drivers/usb/gadget/function/u_audio.c662
-rw-r--r--drivers/usb/gadget/function/u_audio.h95
-rw-r--r--drivers/usb/gadget/function/u_fs.h3
-rw-r--r--drivers/usb/gadget/function/u_uac1.h87
-rw-r--r--drivers/usb/gadget/function/u_uac1_legacy.c (renamed from drivers/usb/gadget/function/u_uac1.c)7
-rw-r--r--drivers/usb/gadget/function/u_uac1_legacy.h82
13 files changed, 2595 insertions, 1628 deletions
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index cb8c225e8549..86e825269947 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -32,8 +32,11 @@ usb_f_mass_storage-y := f_mass_storage.o storage_common.o
obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o
usb_f_fs-y := f_fs.o
obj-$(CONFIG_USB_F_FS) += usb_f_fs.o
-usb_f_uac1-y := f_uac1.o u_uac1.o
+obj-$(CONFIG_USB_U_AUDIO) += u_audio.o
+usb_f_uac1-y := f_uac1.o
obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o
+usb_f_uac1_legacy-y := f_uac1_legacy.o u_uac1_legacy.o
+obj-$(CONFIG_USB_F_UAC1_LEGACY) += usb_f_uac1_legacy.o
usb_f_uac2-y := f_uac2.o
obj-$(CONFIG_USB_F_UAC2) += usb_f_uac2.o
usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 47dda3450abd..d21874b35cf6 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -127,7 +127,6 @@ struct ffs_ep {
struct ffs_epfile {
/* Protects ep->ep and ep->req. */
struct mutex mutex;
- wait_queue_head_t wait;
struct ffs_data *ffs;
struct ffs_ep *ep; /* P: ffs->eps_lock */
@@ -889,7 +888,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
- ret = wait_event_interruptible(epfile->wait, (ep = epfile->ep));
+ ret = wait_event_interruptible(
+ epfile->ffs->wait, (ep = epfile->ep));
if (ret)
return -EINTR;
}
@@ -1189,6 +1189,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
unsigned long value)
{
struct ffs_epfile *epfile = file->private_data;
+ struct ffs_ep *ep;
int ret;
ENTER();
@@ -1196,50 +1197,65 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
return -ENODEV;
+ /* Wait for endpoint to be enabled */
+ ep = epfile->ep;
+ if (!ep) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(
+ epfile->ffs->wait, (ep = epfile->ep));
+ if (ret)
+ return -EINTR;
+ }
+
spin_lock_irq(&epfile->ffs->eps_lock);
- if (likely(epfile->ep)) {
- switch (code) {
- case FUNCTIONFS_FIFO_STATUS:
- ret = usb_ep_fifo_status(epfile->ep->ep);
- break;
- case FUNCTIONFS_FIFO_FLUSH:
- usb_ep_fifo_flush(epfile->ep->ep);
- ret = 0;
- break;
- case FUNCTIONFS_CLEAR_HALT:
- ret = usb_ep_clear_halt(epfile->ep->ep);
- break;
- case FUNCTIONFS_ENDPOINT_REVMAP:
- ret = epfile->ep->num;
- break;
- case FUNCTIONFS_ENDPOINT_DESC:
- {
- int desc_idx;
- struct usb_endpoint_descriptor *desc;
- switch (epfile->ffs->gadget->speed) {
- case USB_SPEED_SUPER:
- desc_idx = 2;
- break;
- case USB_SPEED_HIGH:
- desc_idx = 1;
- break;
- default:
- desc_idx = 0;
- }
- desc = epfile->ep->descs[desc_idx];
+ /* In the meantime, endpoint got disabled or changed. */
+ if (epfile->ep != ep) {
+ spin_unlock_irq(&epfile->ffs->eps_lock);
+ return -ESHUTDOWN;
+ }
- spin_unlock_irq(&epfile->ffs->eps_lock);
- ret = copy_to_user((void *)value, desc, desc->bLength);
- if (ret)
- ret = -EFAULT;
- return ret;
- }
+ switch (code) {
+ case FUNCTIONFS_FIFO_STATUS:
+ ret = usb_ep_fifo_status(epfile->ep->ep);
+ break;
+ case FUNCTIONFS_FIFO_FLUSH:
+ usb_ep_fifo_flush(epfile->ep->ep);
+ ret = 0;
+ break;
+ case FUNCTIONFS_CLEAR_HALT:
+ ret = usb_ep_clear_halt(epfile->ep->ep);
+ break;
+ case FUNCTIONFS_ENDPOINT_REVMAP:
+ ret = epfile->ep->num;
+ break;
+ case FUNCTIONFS_ENDPOINT_DESC:
+ {
+ int desc_idx;
+ struct usb_endpoint_descriptor *desc;
+
+ switch (epfile->ffs->gadget->speed) {
+ case USB_SPEED_SUPER:
+ desc_idx = 2;
+ break;
+ case USB_SPEED_HIGH:
+ desc_idx = 1;
+ break;
default:
- ret = -ENOTTY;
+ desc_idx = 0;
}
- } else {
- ret = -ENODEV;
+ desc = epfile->ep->descs[desc_idx];
+
+ spin_unlock_irq(&epfile->ffs->eps_lock);
+ ret = copy_to_user((void *)value, desc, desc->bLength);
+ if (ret)
+ ret = -EFAULT;
+ return ret;
+ }
+ default:
+ ret = -ENOTTY;
}
spin_unlock_irq(&epfile->ffs->eps_lock);
@@ -1593,7 +1609,8 @@ static void ffs_data_put(struct ffs_data *ffs)
pr_info("%s(): freeing\n", __func__);
ffs_data_clear(ffs);
BUG_ON(waitqueue_active(&ffs->ev.waitq) ||
- waitqueue_active(&ffs->ep0req_completion.wait));
+ waitqueue_active(&ffs->ep0req_completion.wait) ||
+ waitqueue_active(&ffs->wait));
kfree(ffs->dev_name);
kfree(ffs);
}
@@ -1640,6 +1657,7 @@ static struct ffs_data *ffs_data_new(void)
mutex_init(&ffs->mutex);
spin_lock_init(&ffs->eps_lock);
init_waitqueue_head(&ffs->ev.waitq);
+ init_waitqueue_head(&ffs->wait);
init_completion(&ffs->ep0req_completion);
/* XXX REVISIT need to update it in some places, or do we? */
@@ -1761,7 +1779,6 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
for (i = 1; i <= count; ++i, ++epfile) {
epfile->ffs = ffs;
mutex_init(&epfile->mutex);
- init_waitqueue_head(&epfile->wait);
if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]);
else
@@ -1786,8 +1803,7 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count)
ENTER();
for (; count; --count, ++epfile) {
- BUG_ON(mutex_is_locked(&epfile->mutex) ||
- waitqueue_active(&epfile->wait));
+ BUG_ON(mutex_is_locked(&epfile->mutex));
if (epfile->dentry) {
d_delete(epfile->dentry);
dput(epfile->dentry);
@@ -1874,11 +1890,11 @@ static int ffs_func_eps_enable(struct ffs_function *func)
break;
}
- wake_up(&epfile->wait);
-
++ep;
++epfile;
}
+
+ wake_up_interruptible(&ffs->wait);
spin_unlock_irqrestore(&func->ffs->eps_lock, flags);
return ret;
diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c
index 74d57d6994da..e80b9c123a9d 100644
--- a/drivers/usb/gadget/function/f_mass_storage.c
+++ b/drivers/usb/gadget/function/f_mass_storage.c
@@ -260,12 +260,13 @@ struct fsg_common {
struct usb_gadget *gadget;
struct usb_composite_dev *cdev;
struct fsg_dev *fsg, *new_fsg;
+ wait_queue_head_t io_wait;
wait_queue_head_t fsg_wait;
/* filesem protects: backing files in use */
struct rw_semaphore filesem;
- /* lock protects: state, all the req_busy's */
+ /* lock protects: state and thread_task */
spinlock_t lock;
struct usb_ep *ep0; /* Copy of gadget->ep0 */
@@ -303,7 +304,6 @@ struct fsg_common {
unsigned int running:1;
unsigned int sysfs:1;
- int thread_wakeup_needed;
struct completion thread_notifier;
struct task_struct *thread_task;
@@ -355,7 +355,7 @@ typedef void (*fsg_routine_t)(struct fsg_dev *);
static int exception_in_progress(struct fsg_common *common)
{
- return common->state > FSG_STATE_IDLE;
+ return common->state > FSG_STATE_NORMAL;
}
/* Make bulk-out requests be divisible by the maxpacket size */
@@ -393,20 +393,6 @@ static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep)
/* These routines may be called in process context or in_irq */
-/* Caller must hold fsg->lock */
-static void wakeup_thread(struct fsg_common *common)
-{
- /*
- * Ensure the reading of thread_wakeup_needed
- * and the writing of bh->state are completed
- */
- smp_mb();
- /* Tell the main thread that something has happened */
- common->thread_wakeup_needed = 1;
- if (common->thread_task)
- wake_up_process(common->thread_task);
-}
-
static void raise_exception(struct fsg_common *common, enum fsg_state new_state)
{
unsigned long flags;
@@ -460,13 +446,9 @@ static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req)
if (req->status == -ECONNRESET) /* Request was cancelled */
usb_ep_fifo_flush(ep);
- /* Hold the lock while we update the request and buffer states */
- smp_wmb();
- spin_lock(&common->lock);
- bh->inreq_busy = 0;
- bh->state = BUF_STATE_EMPTY;
- wakeup_thread(common);
- spin_unlock(&common->lock);
+ /* Synchronize with the smp_load_acquire() in sleep_thread() */
+ smp_store_release(&bh->state, BUF_STATE_EMPTY);
+ wake_up(&common->io_wait);
}
static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
@@ -481,13 +463,9 @@ static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
if (req->status == -ECONNRESET) /* Request was cancelled */
usb_ep_fifo_flush(ep);
- /* Hold the lock while we update the request and buffer states */
- smp_wmb();
- spin_lock(&common->lock);
- bh->outreq_busy = 0;
- bh->state = BUF_STATE_FULL;
- wakeup_thread(common);
- spin_unlock(&common->lock);
+ /* Synchronize with the smp_load_acquire() in sleep_thread() */
+ smp_store_release(&bh->state, BUF_STATE_FULL);
+ wake_up(&common->io_wait);
}
static int _fsg_common_get_max_lun(struct fsg_common *common)
@@ -532,7 +510,7 @@ static int fsg_setup(struct usb_function *f,
* and reinitialize our state.
*/
DBG(fsg, "bulk reset request\n");
- raise_exception(fsg->common, FSG_STATE_RESET);
+ raise_exception(fsg->common, FSG_STATE_PROTOCOL_RESET);
return USB_GADGET_DELAYED_STATUS;
case US_BULK_GET_MAX_LUN:
@@ -563,43 +541,39 @@ static int fsg_setup(struct usb_function *f,
/* All the following routines run in process context */
/* Use this for bulk or interrupt transfers, not ep0 */
-static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
- struct usb_request *req, int *pbusy,
- enum fsg_buffer_state *state)
+static int start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
+ struct usb_request *req)
{
int rc;
if (ep == fsg->bulk_in)
dump_msg(fsg, "bulk-in", req->buf, req->length);
- spin_lock_irq(&fsg->common->lock);
- *pbusy = 1;
- *state = BUF_STATE_BUSY;
- spin_unlock_irq(&fsg->common->lock);
-
rc = usb_ep_queue(ep, req, GFP_KERNEL);
- if (rc == 0)
- return; /* All good, we're done */
-
- *pbusy = 0;
- *state = BUF_STATE_EMPTY;
+ if (rc) {
- /* We can't do much more than wait for a reset */
+ /* We can't do much more than wait for a reset */
+ req->status = rc;
- /*
- * Note: currently the net2280 driver fails zero-length
- * submissions if DMA is enabled.
- */
- if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && req->length == 0))
- WARNING(fsg, "error in submission: %s --> %d\n", ep->name, rc);
+ /*
+ * Note: currently the net2280 driver fails zero-length
+ * submissions if DMA is enabled.
+ */
+ if (rc != -ESHUTDOWN &&
+ !(rc == -EOPNOTSUPP && req->length == 0))
+ WARNING(fsg, "error in submission: %s --> %d\n",
+ ep->name, rc);
+ }
+ return rc;
}
static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
{
if (!fsg_is_set(common))
return false;
- start_transfer(common->fsg, common->fsg->bulk_in,
- bh->inreq, &bh->inreq_busy, &bh->state);
+ bh->state = BUF_STATE_SENDING;
+ if (start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq))
+ bh->state = BUF_STATE_EMPTY;
return true;
}
@@ -607,37 +581,31 @@ static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
{
if (!fsg_is_set(common))
return false;
- start_transfer(common->fsg, common->fsg->bulk_out,
- bh->outreq, &bh->outreq_busy, &bh->state);
+ bh->state = BUF_STATE_RECEIVING;
+ if (start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq))
+ bh->state = BUF_STATE_FULL;
return true;
}
-static int sleep_thread(struct fsg_common *common, bool can_freeze)
+static int sleep_thread(struct fsg_common *common, bool can_freeze,
+ struct fsg_buffhd *bh)
{
- int rc = 0;
-
- /* Wait until a signal arrives or we are woken up */
- for (;;) {
- if (can_freeze)
- try_to_freeze();
- set_current_state(TASK_INTERRUPTIBLE);
- if (signal_pending(current)) {
- rc = -EINTR;
- break;
- }
- if (common->thread_wakeup_needed)
- break;
- schedule();
- }
- __set_current_state(TASK_RUNNING);
- common->thread_wakeup_needed = 0;
+ int rc;
- /*
- * Ensure the writing of thread_wakeup_needed
- * and the reading of bh->state are completed
- */
- smp_mb();
- return rc;
+ /* Wait until a signal arrives or bh is no longer busy */
+ if (can_freeze)
+ /*
+ * synchronize with the smp_store_release(&bh->state) in
+ * bulk_in_complete() or bulk_out_complete()
+ */
+ rc = wait_event_freezable(common->io_wait,
+ bh && smp_load_acquire(&bh->state) >=
+ BUF_STATE_EMPTY);
+ else
+ rc = wait_event_interruptible(common->io_wait,
+ bh && smp_load_acquire(&bh->state) >=
+ BUF_STATE_EMPTY);
+ return rc ? -EINTR : 0;
}
@@ -697,11 +665,9 @@ static int do_read(struct fsg_common *common)
/* Wait for the next buffer to become available */
bh = common->next_buffhd_to_fill;
- while (bh->state != BUF_STATE_EMPTY) {
- rc = sleep_thread(common, false);
- if (rc)
- return rc;
- }
+ rc = sleep_thread(common, false, bh);
+ if (rc)
+ return rc;
/*
* If we were asked to read past the end of file,
@@ -878,84 +844,80 @@ static int do_write(struct fsg_common *common)
bh = common->next_buffhd_to_drain;
if (bh->state == BUF_STATE_EMPTY && !get_some_more)
break; /* We stopped early */
- if (bh->state == BUF_STATE_FULL) {
- smp_rmb();
- common->next_buffhd_to_drain = bh->next;
- bh->state = BUF_STATE_EMPTY;
-
- /* Did something go wrong with the transfer? */
- if (bh->outreq->status != 0) {
- curlun->sense_data = SS_COMMUNICATION_FAILURE;
- curlun->sense_data_info =
+
+ /* Wait for the data to be received */
+ rc = sleep_thread(common, false, bh);
+ if (rc)
+ return rc;
+
+ common->next_buffhd_to_drain = bh->next;
+ bh->state = BUF_STATE_EMPTY;
+
+ /* Did something go wrong with the transfer? */
+ if (bh->outreq->status != 0) {
+ curlun->sense_data = SS_COMMUNICATION_FAILURE;
+ curlun->sense_data_info =
file_offset >> curlun->blkbits;
- curlun->info_valid = 1;
- break;
- }
+ curlun->info_valid = 1;
+ break;
+ }
- amount = bh->outreq->actual;
- if (curlun->file_length - file_offset < amount) {
- LERROR(curlun,
- "write %u @ %llu beyond end %llu\n",
+ amount = bh->outreq->actual;
+ if (curlun->file_length - file_offset < amount) {
+ LERROR(curlun, "write %u @ %llu beyond end %llu\n",
amount, (unsigned long long)file_offset,
(unsigned long long)curlun->file_length);
- amount = curlun->file_length - file_offset;
- }
+ amount = curlun->file_length - file_offset;
+ }
- /* Don't accept excess data. The spec doesn't say
- * what to do in this case. We'll ignore the error.
- */
- amount = min(amount, bh->bulk_out_intended_length);
-
- /* Don't write a partial block */
- amount = round_down(amount, curlun->blksize);
- if (amount == 0)
- goto empty_write;
-
- /* Perform the write */
- file_offset_tmp = file_offset;
- nwritten = vfs_write(curlun->filp,
- (char __user *)bh->buf,
- amount, &file_offset_tmp);
- VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
- (unsigned long long)file_offset, (int)nwritten);
- if (signal_pending(current))
- return -EINTR; /* Interrupted! */
-
- if (nwritten < 0) {
- LDBG(curlun, "error in file write: %d\n",
- (int)nwritten);
- nwritten = 0;
- } else if (nwritten < amount) {
- LDBG(curlun, "partial file write: %d/%u\n",
- (int)nwritten, amount);
- nwritten = round_down(nwritten, curlun->blksize);
- }
- file_offset += nwritten;
- amount_left_to_write -= nwritten;
- common->residue -= nwritten;
+ /*
+ * Don't accept excess data. The spec doesn't say
+ * what to do in this case. We'll ignore the error.
+ */
+ amount = min(amount, bh->bulk_out_intended_length);
- /* If an error occurred, report it and its position */
- if (nwritten < amount) {
- curlun->sense_data = SS_WRITE_ERROR;
- curlun->sense_data_info =
+ /* Don't write a partial block */
+ amount = round_down(amount, curlun->blksize);
+ if (amount == 0)
+ goto empty_write;
+
+ /* Perform the write */
+ file_offset_tmp = file_offset;
+ nwritten = vfs_write(curlun->filp, (char __user *)bh->buf,
+ amount, &file_offset_tmp);
+ VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
+ (unsigned long long)file_offset, (int)nwritten);
+ if (signal_pending(current))
+ return -EINTR; /* Interrupted! */
+
+ if (nwritten < 0) {
+ LDBG(curlun, "error in file write: %d\n",
+ (int) nwritten);
+ nwritten = 0;
+ } else if (nwritten < amount) {
+ LDBG(curlun, "partial file write: %d/%u\n",
+ (int) nwritten, amount);
+ nwritten = round_down(nwritten, curlun->blksize);
+ }
+ file_offset += nwritten;
+ amount_left_to_write -= nwritten;
+ common->residue -= nwritten;
+
+ /* If an error occurred, report it and its position */
+ if (nwritten < amount) {
+ curlun->sense_data = SS_WRITE_ERROR;
+ curlun->sense_data_info =
file_offset >> curlun->blkbits;
- curlun->info_valid = 1;
- break;
- }
+ curlun->info_valid = 1;
+ break;
+ }
empty_write:
- /* Did the host decide to stop early? */
- if (bh->outreq->actual < bh->bulk_out_intended_length) {
- common->short_packet_received = 1;
- break;
- }
- continue;
+ /* Did the host decide to stop early? */
+ if (bh->outreq->actual < bh->bulk_out_intended_length) {
+ common->short_packet_received = 1;
+ break;
}
-
- /* Wait for something to happen */
- rc = sleep_thread(common, false);
- if (rc)
- return rc;
}
return -EIO; /* No default reply */
@@ -1480,7 +1442,7 @@ static int wedge_bulk_in_endpoint(struct fsg_dev *fsg)
static int throw_away_data(struct fsg_common *common)
{
- struct fsg_buffhd *bh;
+ struct fsg_buffhd *bh, *bh2;
u32 amount;
int rc;
@@ -1488,26 +1450,10 @@ static int throw_away_data(struct fsg_common *common)
bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0;
bh = common->next_buffhd_to_drain) {
- /* Throw away the data in a filled buffer */
- if (bh->state == BUF_STATE_FULL) {
- smp_rmb();
- bh->state = BUF_STATE_EMPTY;
- common->next_buffhd_to_drain = bh->next;
-
- /* A short packet or an error ends everything */
- if (bh->outreq->actual < bh->bulk_out_intended_length ||
- bh->outreq->status != 0) {
- raise_exception(common,
- FSG_STATE_ABORT_BULK_OUT);
- return -EINTR;
- }
- continue;
- }
-
/* Try to submit another request if we need one */
- bh = common->next_buffhd_to_fill;
- if (bh->state == BUF_STATE_EMPTY
- && common->usb_amount_left > 0) {
+ bh2 = common->next_buffhd_to_fill;
+ if (bh2->state == BUF_STATE_EMPTY &&
+ common->usb_amount_left > 0) {
amount = min(common->usb_amount_left, FSG_BUFLEN);
/*
@@ -1515,19 +1461,30 @@ static int throw_away_data(struct fsg_common *common)
* equal to the buffer size, which is divisible by
* the bulk-out maxpacket size.
*/
- set_bulk_out_req_length(common, bh, amount);
- if (!start_out_transfer(common, bh))
+ set_bulk_out_req_length(common, bh2, amount);
+ if (!start_out_transfer(common, bh2))
/* Dunno what to do if common->fsg is NULL */
return -EIO;
- common->next_buffhd_to_fill = bh->next;
+ common->next_buffhd_to_fill = bh2->next;
common->usb_amount_left -= amount;
continue;
}
- /* Otherwise wait for something to happen */
- rc = sleep_thread(common, true);
+ /* Wait for the data to be received */
+ rc = sleep_thread(common, false, bh);
if (rc)
return rc;
+
+ /* Throw away the data in a filled buffer */
+ bh->state = BUF_STATE_EMPTY;
+ common->next_buffhd_to_drain = bh->next;
+
+ /* A short packet or an error ends everything */
+ if (bh->outreq->actual < bh->bulk_out_intended_length ||
+ bh->outreq->status != 0) {
+ raise_exception(common, FSG_STATE_ABORT_BULK_OUT);
+ return -EINTR;
+ }
}
return 0;
}
@@ -1634,7 +1591,7 @@ static int finish_reply(struct fsg_common *common)
return rc;
}
-static int send_status(struct fsg_common *common)
+static void send_status(struct fsg_common *common)
{
struct fsg_lun *curlun = common->curlun;
struct fsg_buffhd *bh;
@@ -1645,11 +1602,9 @@ static int send_status(struct fsg_common *common)
/* Wait for the next buffer to become available */
bh = common->next_buffhd_to_fill;
- while (bh->state != BUF_STATE_EMPTY) {
- rc = sleep_thread(common, true);
- if (rc)
- return rc;
- }
+ rc = sleep_thread(common, false, bh);
+ if (rc)
+ return;
if (curlun) {
sd = curlun->sense_data;
@@ -1683,10 +1638,10 @@ static int send_status(struct fsg_common *common)
bh->inreq->zero = 0;
if (!start_in_transfer(common, bh))
/* Don't know what to do if common->fsg is NULL */
- return -EIO;
+ return;
common->next_buffhd_to_fill = bh->next;
- return 0;
+ return;
}
@@ -1848,11 +1803,10 @@ static int do_scsi_command(struct fsg_common *common)
/* Wait for the next buffer to become available for data or status */
bh = common->next_buffhd_to_fill;
common->next_buffhd_to_drain = bh;
- while (bh->state != BUF_STATE_EMPTY) {
- rc = sleep_thread(common, true);
- if (rc)
- return rc;
- }
+ rc = sleep_thread(common, false, bh);
+ if (rc)
+ return rc;
+
common->phase_error = 0;
common->short_packet_received = 0;
@@ -2195,11 +2149,9 @@ static int get_next_command(struct fsg_common *common)
/* Wait for the next buffer to become available */
bh = common->next_buffhd_to_fill;
- while (bh->state != BUF_STATE_EMPTY) {
- rc = sleep_thread(common, true);
- if (rc)
- return rc;
- }
+ rc = sleep_thread(common, true, bh);
+ if (rc)
+ return rc;
/* Queue a request to read a Bulk-only CBW */
set_bulk_out_req_length(common, bh, US_BULK_CB_WRAP_LEN);
@@ -2214,12 +2166,10 @@ static int get_next_command(struct fsg_common *common)
*/
/* Wait for the CBW to arrive */
- while (bh->state != BUF_STATE_FULL) {
- rc = sleep_thread(common, true);
- if (rc)
- return rc;
- }
- smp_rmb();
+ rc = sleep_thread(common, true, bh);
+ if (rc)
+ return rc;
+
rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO;
bh->state = BUF_STATE_EMPTY;
@@ -2371,9 +2321,11 @@ static void handle_exception(struct fsg_common *common)
if (!sig)
break;
if (sig != SIGUSR1) {
+ spin_lock_irq(&common->lock);
if (common->state < FSG_STATE_EXIT)
DBG(common, "Main thread exiting on signal\n");
- raise_exception(common, FSG_STATE_EXIT);
+ common->state = FSG_STATE_EXIT;
+ spin_unlock_irq(&common->lock);
}
}
@@ -2381,23 +2333,14 @@ static void handle_exception(struct fsg_common *common)
if (likely(common->fsg)) {
for (i = 0; i < common->fsg_num_buffers; ++i) {
bh = &common->buffhds[i];
- if (bh->inreq_busy)
+ if (bh->state == BUF_STATE_SENDING)
usb_ep_dequeue(common->fsg->bulk_in, bh->inreq);
- if (bh->outreq_busy)
+ if (bh->state == BUF_STATE_RECEIVING)
usb_ep_dequeue(common->fsg->bulk_out,
bh->outreq);
- }
- /* Wait until everything is idle */
- for (;;) {
- int num_active = 0;
- for (i = 0; i < common->fsg_num_buffers; ++i) {
- bh = &common->buffhds[i];
- num_active += bh->inreq_busy + bh->outreq_busy;
- }
- if (num_active == 0)
- break;
- if (sleep_thread(common, true))
+ /* Wait for a transfer to become idle */
+ if (sleep_thread(common, false, bh))
return;
}
@@ -2422,10 +2365,9 @@ static void handle_exception(struct fsg_common *common)
common->next_buffhd_to_drain = &common->buffhds[0];
exception_req_tag = common->exception_req_tag;
old_state = common->state;
+ common->state = FSG_STATE_NORMAL;
- if (old_state == FSG_STATE_ABORT_BULK_OUT)
- common->state = FSG_STATE_STATUS_PHASE;
- else {
+ if (old_state != FSG_STATE_ABORT_BULK_OUT) {
for (i = 0; i < ARRAY_SIZE(common->luns); ++i) {
curlun = common->luns[i];
if (!curlun)
@@ -2436,21 +2378,19 @@ static void handle_exception(struct fsg_common *common)
curlun->sense_data_info = 0;
curlun->info_valid = 0;
}
- common->state = FSG_STATE_IDLE;
}
spin_unlock_irq(&common->lock);
/* Carry out any extra actions required for the exception */
switch (old_state) {
+ case FSG_STATE_NORMAL:
+ break;
+
case FSG_STATE_ABORT_BULK_OUT:
send_status(common);
- spin_lock_irq(&common->lock);
- if (common->state == FSG_STATE_STATUS_PHASE)
- common->state = FSG_STATE_IDLE;
- spin_unlock_irq(&common->lock);
break;
- case FSG_STATE_RESET:
+ case FSG_STATE_PROTOCOL_RESET:
/*
* In case we were forced against our will to halt a
* bulk endpoint, clear the halt now. (The SuperH UDC
@@ -2483,19 +2423,13 @@ static void handle_exception(struct fsg_common *common)
break;
case FSG_STATE_EXIT:
- case FSG_STATE_TERMINATED:
do_set_interface(common, NULL); /* Free resources */
spin_lock_irq(&common->lock);
common->state = FSG_STATE_TERMINATED; /* Stop the thread */
spin_unlock_irq(&common->lock);
break;
- case FSG_STATE_INTERFACE_CHANGE:
- case FSG_STATE_DISCONNECT:
- case FSG_STATE_COMMAND_PHASE:
- case FSG_STATE_DATA_PHASE:
- case FSG_STATE_STATUS_PHASE:
- case FSG_STATE_IDLE:
+ case FSG_STATE_TERMINATED:
break;
}
}
@@ -2534,33 +2468,17 @@ static int fsg_main_thread(void *common_)
}
if (!common->running) {
- sleep_thread(common, true);
+ sleep_thread(common, true, NULL);
continue;
}
- if (get_next_command(common))
+ if (get_next_command(common) || exception_in_progress(common))
continue;
-
- spin_lock_irq(&common->lock);
- if (!exception_in_progress(common))
- common->state = FSG_STATE_DATA_PHASE;
- spin_unlock_irq(&common->lock);
-
- if (do_scsi_command(common) || finish_reply(common))
+ if (do_scsi_command(common) || exception_in_progress(common))
continue;
-
- spin_lock_irq(&common->lock);
- if (!exception_in_progress(common))
- common->state = FSG_STATE_STATUS_PHASE;
- spin_unlock_irq(&common->lock);
-
- if (send_status(common))
+ if (finish_reply(common) || exception_in_progress(common))
continue;
-
- spin_lock_irq(&common->lock);
- if (!exception_in_progress(common))
- common->state = FSG_STATE_IDLE;
- spin_unlock_irq(&common->lock);
+ send_status(common);
}
spin_lock_irq(&common->lock);
@@ -2680,6 +2598,7 @@ static struct fsg_common *fsg_common_setup(struct fsg_common *common)
spin_lock_init(&common->lock);
kref_init(&common->ref);
init_completion(&common->thread_notifier);
+ init_waitqueue_head(&common->io_wait);
init_waitqueue_head(&common->fsg_wait);
common->state = FSG_STATE_TERMINATED;
memset(common->luns, 0, sizeof(common->luns));
@@ -2981,7 +2900,6 @@ static void fsg_common_release(struct kref *ref)
if (common->state != FSG_STATE_TERMINATED) {
raise_exception(common, FSG_STATE_EXIT);
wait_for_completion(&common->thread_notifier);
- common->thread_task = NULL;
}
for (i = 0; i < ARRAY_SIZE(common->luns); ++i) {
@@ -3030,11 +2948,11 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
}
if (!common->thread_task) {
- common->state = FSG_STATE_IDLE;
+ common->state = FSG_STATE_NORMAL;
common->thread_task =
kthread_create(fsg_main_thread, common, "file-storage");
if (IS_ERR(common->thread_task)) {
- int ret = PTR_ERR(common->thread_task);
+ ret = PTR_ERR(common->thread_task);
common->thread_task = NULL;
common->state = FSG_STATE_TERMINATED;
return ret;
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index f2ac0cbc29a4..8656f84e17d9 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -1,24 +1,38 @@
/*
- * f_audio.c -- USB Audio class function driver
- *
- * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
- * Copyright (C) 2008 Analog Devices, Inc
+ * f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API)
*
- * Enter bugs at http://blackfin.uclinux.org/
+ * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
*
- * Licensed under the GPL-2 or later.
+ * This driver doesn't expect any real Audio codec to be present
+ * on the device - the audio streams are simply sinked to and
+ * sourced from a virtual ALSA sound card created.
+ *
+ * This file is based on f_uac1.c which is
+ * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ * Copyright (C) 2008 Analog Devices, Inc
+ *
+ * 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.
*/
-#include <linux/slab.h>
-#include <linux/kernel.h>
+#include <linux/usb/audio.h>
#include <linux/module.h>
-#include <linux/device.h>
-#include <linux/atomic.h>
+#include "u_audio.h"
#include "u_uac1.h"
-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);
+struct f_uac1 {
+ struct g_audio g_audio;
+ u8 ac_intf, as_in_intf, as_out_intf;
+ u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */
+};
+
+static inline struct f_uac1 *func_to_uac1(struct usb_function *f)
+{
+ return container_of(f, struct f_uac1, g_audio.func);
+}
/*
* DESCRIPTORS ... most are static, but strings and full
@@ -26,12 +40,17 @@ static int generic_get_cmd(struct usb_audio_control *con, u8 cmd);
*/
/*
- * We have two interfaces- AudioControl and AudioStreaming
- * TODO: only supcard playback currently
+ * We have three interfaces - one AudioControl and two AudioStreaming
+ *
+ * The driver implements a simple UAC_1 topology.
+ * 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_INTERFACE 1
-#define F_AUDIO_NUM_INTERFACES 1
+#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 = {
@@ -46,89 +65,91 @@ static struct usb_interface_descriptor ac_interface_desc = {
* The number of AudioStreaming and MIDIStreaming interfaces
* in the Audio Interface Collection
*/
-DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
+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))
+/* 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_1 ac_header_desc = {
+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),
+ .bcdADC = cpu_to_le16(0x0100),
+ .wTotalLength = cpu_to_le16(UAC_DT_TOTAL_LENGTH),
.bInCollection = F_AUDIO_NUM_INTERFACES,
.baInterfaceNr = {
- /* Interface number of the first AudioStream interface */
+ /* Interface number of the AudioStream interfaces */
[0] = 1,
+ [1] = 2,
}
};
-#define INPUT_TERMINAL_ID 1
-static struct uac_input_terminal_descriptor input_terminal_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 = INPUT_TERMINAL_ID,
+ .bTerminalID = USB_OUT_IT_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),
+#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_FEATURE_UNIT,
- .bUnitID = FEATURE_UNIT_ID,
- .bSourceID = INPUT_TERMINAL_ID,
- .bControlSize = 2,
- .bmaControls[0] = (UAC_FU_MUTE | UAC_FU_VOLUME),
+ .bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
+ .bTerminalID = IO_OUT_OT_ID,
+ .wTerminalType = UAC_OUTPUT_TERMINAL_SPEAKER,
+ .bAssocTerminal = 0,
+ .bSourceID = USB_OUT_IT_ID,
};
-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,
+#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,
+ .wTerminalType = UAC_INPUT_TERMINAL_MICROPHONE,
+ .bAssocTerminal = 0,
+ .wChannelConfig = 0x3,
};
-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,
+#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,
+ .wTerminalType = UAC_TERMINAL_STREAMING,
+ .bAssocTerminal = 0,
+ .bSourceID = IO_IN_IT_ID,
};
-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,
+/* B.4.1 Standard AS Interface Descriptor */
+static struct usb_interface_descriptor as_out_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,
};
-#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,
+static struct usb_interface_descriptor as_out_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.1 Standard AS Interface Descriptor */
-static struct usb_interface_descriptor as_interface_alt_0_desc = {
+static struct usb_interface_descriptor as_in_interface_alt_0_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bAlternateSetting = 0,
@@ -137,7 +158,7 @@ static struct usb_interface_descriptor as_interface_alt_0_desc = {
.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
};
-static struct usb_interface_descriptor as_interface_alt_1_desc = {
+static struct usb_interface_descriptor as_in_interface_alt_1_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bAlternateSetting = 1,
@@ -147,18 +168,27 @@ static struct usb_interface_descriptor as_interface_alt_1_desc = {
};
/* B.4.2 Class-Specific AS Interface Descriptor */
-static struct uac1_as_header_descriptor as_header_desc = {
+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 = INPUT_TERMINAL_ID,
+ .bTerminalLink = USB_OUT_IT_ID,
+ .bDelay = 1,
+ .wFormatTag = UAC_FORMAT_TYPE_I_PCM,
+};
+
+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,
.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 = {
+static struct uac_format_type_i_discrete_descriptor_1 as_out_type_i_desc = {
.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_FORMAT_TYPE,
@@ -184,48 +214,97 @@ static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
.bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
.bDescriptorType = USB_DT_CS_ENDPOINT,
.bDescriptorSubtype = UAC_EP_GENERAL,
- .bmAttributes = 1,
+ .bmAttributes = 1,
.bLockDelayUnits = 1,
- .wLockDelay = __constant_cpu_to_le16(1),
+ .wLockDelay = cpu_to_le16(1),
+};
+
+static struct uac_format_type_i_discrete_descriptor_1 as_in_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_in_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_SYNC_ASYNC
+ | USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+ .bInterval = 4,
+};
+
+/* Class-specific AS ISO OUT Endpoint Descriptor */
+static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
+ .bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
+ .bDescriptorType = USB_DT_CS_ENDPOINT,
+ .bDescriptorSubtype = UAC_EP_GENERAL,
+ .bmAttributes = 1,
+ .bLockDelayUnits = 0,
+ .wLockDelay = 0,
};
static struct usb_descriptor_header *f_audio_desc[] = {
(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 *)&usb_out_it_desc,
+ (struct usb_descriptor_header *)&io_out_ot_desc,
+ (struct usb_descriptor_header *)&io_in_it_desc,
+ (struct usb_descriptor_header *)&usb_in_ot_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_out_interface_alt_0_desc,
+ (struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
+ (struct usb_descriptor_header *)&as_out_header_desc,
- (struct usb_descriptor_header *)&as_type_i_desc,
+ (struct usb_descriptor_header *)&as_out_type_i_desc,
(struct usb_descriptor_header *)&as_out_ep_desc,
(struct usb_descriptor_header *)&as_iso_out_desc,
+
+ (struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
+ (struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
+ (struct usb_descriptor_header *)&as_in_header_desc,
+
+ (struct usb_descriptor_header *)&as_in_type_i_desc,
+
+ (struct usb_descriptor_header *)&as_in_ep_desc,
+ (struct usb_descriptor_header *)&as_iso_in_desc,
NULL,
};
enum {
STR_AC_IF,
- STR_INPUT_TERMINAL,
- STR_INPUT_TERMINAL_CH_NAMES,
- STR_FEAT_DESC_0,
- STR_OUTPUT_TERMINAL,
- STR_AS_IF_ALT0,
- STR_AS_IF_ALT1,
+ STR_USB_OUT_IT,
+ STR_USB_OUT_IT_CH_NAMES,
+ STR_IO_OUT_OT,
+ STR_IO_IN_IT,
+ STR_IO_IN_IT_CH_NAMES,
+ STR_USB_IN_OT,
+ STR_AS_OUT_IF_ALT0,
+ STR_AS_OUT_IF_ALT1,
+ STR_AS_IN_IF_ALT0,
+ STR_AS_IN_IF_ALT1,
};
static struct usb_string strings_uac1[] = {
[STR_AC_IF].s = "AC Interface",
- [STR_INPUT_TERMINAL].s = "Input terminal",
- [STR_INPUT_TERMINAL_CH_NAMES].s = "Channels",
- [STR_FEAT_DESC_0].s = "Volume control & mute",
- [STR_OUTPUT_TERMINAL].s = "Output terminal",
- [STR_AS_IF_ALT0].s = "AS Interface",
- [STR_AS_IF_ALT1].s = "AS Interface",
+ [STR_USB_OUT_IT].s = "Playback Input terminal",
+ [STR_USB_OUT_IT_CH_NAMES].s = "Playback Channels",
+ [STR_IO_OUT_OT].s = "Playback Output terminal",
+ [STR_IO_IN_IT].s = "Capture Input terminal",
+ [STR_IO_IN_IT_CH_NAMES].s = "Capture Channels",
+ [STR_USB_IN_OT].s = "Capture Output terminal",
+ [STR_AS_OUT_IF_ALT0].s = "Playback Inactive",
+ [STR_AS_OUT_IF_ALT1].s = "Playback Active",
+ [STR_AS_IN_IF_ALT0].s = "Capture Inactive",
+ [STR_AS_IN_IF_ALT1].s = "Capture Active",
{ },
};
@@ -243,216 +322,6 @@ static struct usb_gadget_strings *uac1_strings[] = {
* 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;
- struct f_uac1_opts *opts;
- int audio_buf_size;
- int err;
-
- opts = container_of(audio->card.func.fi, struct f_uac1_opts,
- func_inst);
- audio_buf_size = opts->audio_buf_size;
-
- 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(&copy_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)
{
@@ -531,14 +400,6 @@ f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
* 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;
@@ -571,143 +432,158 @@ f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
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;
- struct f_uac1_opts *opts;
- int req_buf_size, req_count, audio_buf_size;
- int i = 0, err = 0;
-
- DBG(cdev, "intf %d, alt %d\n", intf, alt);
+ struct usb_gadget *gadget = cdev->gadget;
+ struct device *dev = &gadget->dev;
+ struct f_uac1 *uac1 = func_to_uac1(f);
+ int ret = 0;
+
+ /* No i/f has more than 2 alt settings */
+ if (alt > 1) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return -EINVAL;
+ }
- opts = container_of(f->fi, struct f_uac1_opts, func_inst);
- req_buf_size = opts->req_buf_size;
- req_count = opts->req_count;
- audio_buf_size = opts->audio_buf_size;
-
- if (intf == 1) {
- if (alt == 1) {
- err = config_ep_by_speed(cdev->gadget, f, out_ep);
- if (err)
- return err;
-
- usb_ep_enable(out_ep);
- 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(&copy_buf->list,
- &audio->play_queue);
- schedule_work(&audio->playback_work);
- }
+ if (intf == uac1->ac_intf) {
+ /* Control I/f has only 1 AltSetting - 0 */
+ if (alt) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return -EINVAL;
}
+ return 0;
}
- return err;
+ if (intf == uac1->as_out_intf) {
+ uac1->as_out_alt = alt;
+
+ if (alt)
+ ret = u_audio_start_capture(&uac1->g_audio);
+ else
+ u_audio_stop_capture(&uac1->g_audio);
+ } else if (intf == uac1->as_in_intf) {
+ uac1->as_in_alt = alt;
+
+ if (alt)
+ ret = u_audio_start_playback(&uac1->g_audio);
+ else
+ u_audio_stop_playback(&uac1->g_audio);
+ } else {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+
+ return ret;
}
-static void f_audio_disable(struct usb_function *f)
+static int f_audio_get_alt(struct usb_function *f, unsigned intf)
{
- return;
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_gadget *gadget = cdev->gadget;
+ struct device *dev = &gadget->dev;
+ struct f_uac1 *uac1 = func_to_uac1(f);
+
+ if (intf == uac1->ac_intf)
+ return uac1->ac_alt;
+ else if (intf == uac1->as_out_intf)
+ return uac1->as_out_alt;
+ else if (intf == uac1->as_in_intf)
+ return uac1->as_in_alt;
+ else
+ dev_err(dev, "%s:%d Invalid Interface %d!\n",
+ __func__, __LINE__, intf);
+
+ return -EINVAL;
}
-/*-------------------------------------------------------------------------*/
-static void f_audio_build_desc(struct f_audio *audio)
+static void f_audio_disable(struct usb_function *f)
{
- struct gaudio *card = &audio->card;
- u8 *sam_freq;
- int rate;
+ struct f_uac1 *uac1 = func_to_uac1(f);
- /* Set channel numbers */
- input_terminal_desc.bNrChannels = u_audio_get_playback_channels(card);
- as_type_i_desc.bNrChannels = u_audio_get_playback_channels(card);
+ uac1->as_out_alt = 0;
+ uac1->as_in_alt = 0;
- /* 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;
+ u_audio_stop_capture(&uac1->g_audio);
}
+/*-------------------------------------------------------------------------*/
+
/* audio function driver setup/binding */
-static int
-f_audio_bind(struct usb_configuration *c, struct usb_function *f)
+static int 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);
- struct usb_string *us;
- int status;
- struct usb_ep *ep = NULL;
- struct f_uac1_opts *audio_opts;
+ struct usb_composite_dev *cdev = c->cdev;
+ struct usb_gadget *gadget = cdev->gadget;
+ struct f_uac1 *uac1 = func_to_uac1(f);
+ struct g_audio *audio = func_to_g_audio(f);
+ struct f_uac1_opts *audio_opts;
+ struct usb_ep *ep = NULL;
+ struct usb_string *us;
+ u8 *sam_freq;
+ int rate;
+ int status;
audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
- audio->card.gadget = c->cdev->gadget;
- /* set up ASLA audio devices */
- if (!audio_opts->bound) {
- status = gaudio_setup(&audio->card);
- if (status < 0)
- return status;
- audio_opts->bound = true;
- }
+
us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
if (IS_ERR(us))
return PTR_ERR(us);
ac_interface_desc.iInterface = us[STR_AC_IF].id;
- input_terminal_desc.iTerminal = us[STR_INPUT_TERMINAL].id;
- input_terminal_desc.iChannelNames = us[STR_INPUT_TERMINAL_CH_NAMES].id;
- feature_unit_desc.iFeature = us[STR_FEAT_DESC_0].id;
- output_terminal_desc.iTerminal = us[STR_OUTPUT_TERMINAL].id;
- as_interface_alt_0_desc.iInterface = us[STR_AS_IF_ALT0].id;
- as_interface_alt_1_desc.iInterface = us[STR_AS_IF_ALT1].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;
+ io_out_ot_desc.iTerminal = us[STR_IO_OUT_OT].id;
+ as_out_interface_alt_0_desc.iInterface = us[STR_AS_OUT_IF_ALT0].id;
+ as_out_interface_alt_1_desc.iInterface = us[STR_AS_OUT_IF_ALT1].id;
+ io_in_it_desc.iTerminal = us[STR_IO_IN_IT].id;
+ io_in_it_desc.iChannelNames = us[STR_IO_IN_IT_CH_NAMES].id;
+ usb_in_ot_desc.iTerminal = us[STR_USB_IN_OT].id;
+ as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id;
+ as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id;
+ /* Set channel numbers */
+ usb_out_it_desc.bNrChannels = num_channels(audio_opts->c_chmask);
+ usb_out_it_desc.wChannelConfig = cpu_to_le16(audio_opts->c_chmask);
+ as_out_type_i_desc.bNrChannels = num_channels(audio_opts->c_chmask);
+ as_out_type_i_desc.bSubframeSize = audio_opts->c_ssize;
+ as_out_type_i_desc.bBitResolution = audio_opts->c_ssize * 8;
+ io_in_it_desc.bNrChannels = num_channels(audio_opts->p_chmask);
+ io_in_it_desc.wChannelConfig = cpu_to_le16(audio_opts->p_chmask);
+ as_in_type_i_desc.bNrChannels = num_channels(audio_opts->p_chmask);
+ as_in_type_i_desc.bSubframeSize = audio_opts->p_ssize;
+ as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8;
- f_audio_build_desc(audio);
+ /* Set sample rates */
+ rate = audio_opts->c_srate;
+ sam_freq = as_out_type_i_desc.tSamFreq[0];
+ memcpy(sam_freq, &rate, 3);
+ rate = audio_opts->p_srate;
+ sam_freq = as_in_type_i_desc.tSamFreq[0];
+ memcpy(sam_freq, &rate, 3);
/* allocate instance-specific interface IDs, and patch descriptors */
status = usb_interface_id(c, f);
if (status < 0)
goto fail;
ac_interface_desc.bInterfaceNumber = status;
+ uac1->ac_intf = status;
+ uac1->ac_alt = 0;
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;
+ as_out_interface_alt_0_desc.bInterfaceNumber = status;
+ as_out_interface_alt_1_desc.bInterfaceNumber = 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;
+ uac1->as_in_intf = status;
+ uac1->as_in_alt = 0;
+
+ audio->gadget = gadget;
status = -ENODEV;
@@ -718,52 +594,42 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f)
audio->out_ep = ep;
audio->out_ep->desc = &as_out_ep_desc;
- status = -ENOMEM;
+ 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;
/* copy descriptors, and track endpoint copies */
status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL,
NULL);
if (status)
goto fail;
+
+ audio->out_ep_maxpsize = as_out_ep_desc.wMaxPacketSize;
+ audio->in_ep_maxpsize = as_in_ep_desc.wMaxPacketSize;
+ audio->params.c_chmask = audio_opts->c_chmask;
+ audio->params.c_srate = audio_opts->c_srate;
+ audio->params.c_ssize = audio_opts->c_ssize;
+ audio->params.p_chmask = audio_opts->p_chmask;
+ audio->params.p_srate = audio_opts->p_srate;
+ audio->params.p_ssize = audio_opts->p_ssize;
+ audio->params.req_number = audio_opts->req_number;
+
+ status = g_audio_setup(audio, "UAC1_PCM", "UAC1_Gadget");
+ if (status)
+ goto err_card_register;
+
return 0;
+err_card_register:
+ usb_free_all_descriptors(f);
fail:
- gaudio_cleanup(&audio->card);
return status;
}
/*-------------------------------------------------------------------------*/
-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 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;
-}
-
static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item)
{
return container_of(to_config_group(item), struct f_uac1_opts,
@@ -781,9 +647,10 @@ static struct configfs_item_operations f_uac1_item_ops = {
.release = f_uac1_attr_release,
};
-#define UAC1_INT_ATTRIBUTE(name) \
-static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
- char *page) \
+#define UAC1_ATTRIBUTE(name) \
+static ssize_t f_uac1_opts_##name##_show( \
+ struct config_item *item, \
+ char *page) \
{ \
struct f_uac1_opts *opts = to_f_uac1_opts(item); \
int result; \
@@ -795,7 +662,8 @@ static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
return result; \
} \
\
-static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
+static ssize_t f_uac1_opts_##name##_store( \
+ struct config_item *item, \
const char *page, size_t len) \
{ \
struct f_uac1_opts *opts = to_f_uac1_opts(item); \
@@ -822,64 +690,22 @@ end: \
\
CONFIGFS_ATTR(f_uac1_opts_, name)
-UAC1_INT_ATTRIBUTE(req_buf_size);
-UAC1_INT_ATTRIBUTE(req_count);
-UAC1_INT_ATTRIBUTE(audio_buf_size);
-
-#define UAC1_STR_ATTRIBUTE(name) \
-static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
- char *page) \
-{ \
- struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- int result; \
- \
- mutex_lock(&opts->lock); \
- result = sprintf(page, "%s\n", opts->name); \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- int ret = -EBUSY; \
- char *tmp; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) \
- goto end; \
- \
- tmp = kstrndup(page, len, GFP_KERNEL); \
- if (tmp) { \
- ret = -ENOMEM; \
- goto end; \
- } \
- if (opts->name##_alloc) \
- kfree(opts->name); \
- opts->name##_alloc = true; \
- opts->name = tmp; \
- ret = len; \
- \
-end: \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac1_opts_, name)
-
-UAC1_STR_ATTRIBUTE(fn_play);
-UAC1_STR_ATTRIBUTE(fn_cap);
-UAC1_STR_ATTRIBUTE(fn_cntl);
+UAC1_ATTRIBUTE(c_chmask);
+UAC1_ATTRIBUTE(c_srate);
+UAC1_ATTRIBUTE(c_ssize);
+UAC1_ATTRIBUTE(p_chmask);
+UAC1_ATTRIBUTE(p_srate);
+UAC1_ATTRIBUTE(p_ssize);
+UAC1_ATTRIBUTE(req_number);
static struct configfs_attribute *f_uac1_attrs[] = {
- &f_uac1_opts_attr_req_buf_size,
- &f_uac1_opts_attr_req_count,
- &f_uac1_opts_attr_audio_buf_size,
- &f_uac1_opts_attr_fn_play,
- &f_uac1_opts_attr_fn_cap,
- &f_uac1_opts_attr_fn_cntl,
+ &f_uac1_opts_attr_c_chmask,
+ &f_uac1_opts_attr_c_srate,
+ &f_uac1_opts_attr_c_ssize,
+ &f_uac1_opts_attr_p_chmask,
+ &f_uac1_opts_attr_p_srate,
+ &f_uac1_opts_attr_p_ssize,
+ &f_uac1_opts_attr_req_number,
NULL,
};
@@ -894,12 +720,6 @@ static void f_audio_free_inst(struct usb_function_instance *f)
struct f_uac1_opts *opts;
opts = container_of(f, struct f_uac1_opts, func_inst);
- if (opts->fn_play_alloc)
- kfree(opts->fn_play);
- if (opts->fn_cap_alloc)
- kfree(opts->fn_cap);
- if (opts->fn_cntl_alloc)
- kfree(opts->fn_cntl);
kfree(opts);
}
@@ -917,21 +737,22 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
config_group_init_type_name(&opts->func_inst.group, "",
&f_uac1_func_type);
- opts->req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE;
- opts->req_count = UAC1_REQ_COUNT;
- opts->audio_buf_size = UAC1_AUDIO_BUF_SIZE;
- opts->fn_play = FILE_PCM_PLAYBACK;
- opts->fn_cap = FILE_PCM_CAPTURE;
- opts->fn_cntl = FILE_CONTROL;
+ opts->c_chmask = UAC1_DEF_CCHMASK;
+ opts->c_srate = UAC1_DEF_CSRATE;
+ opts->c_ssize = UAC1_DEF_CSSIZE;
+ opts->p_chmask = UAC1_DEF_PCHMASK;
+ opts->p_srate = UAC1_DEF_PSRATE;
+ opts->p_ssize = UAC1_DEF_PSSIZE;
+ opts->req_number = UAC1_DEF_REQ_NUM;
return &opts->func_inst;
}
static void f_audio_free(struct usb_function *f)
{
- struct f_audio *audio = func_to_audio(f);
+ struct g_audio *audio;
struct f_uac1_opts *opts;
- gaudio_cleanup(&audio->card);
+ audio = func_to_g_audio(f);
opts = container_of(f->fi, struct f_uac1_opts, func_inst);
kfree(audio);
mutex_lock(&opts->lock);
@@ -941,42 +762,41 @@ static void f_audio_free(struct usb_function *f)
static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
{
+ struct g_audio *audio = func_to_g_audio(f);
+
+ g_audio_cleanup(audio);
usb_free_all_descriptors(f);
+
+ audio->gadget = NULL;
}
static struct usb_function *f_audio_alloc(struct usb_function_instance *fi)
{
- struct f_audio *audio;
+ struct f_uac1 *uac1;
struct f_uac1_opts *opts;
/* allocate and initialize one new instance */
- audio = kzalloc(sizeof(*audio), GFP_KERNEL);
- if (!audio)
+ uac1 = kzalloc(sizeof(*uac1), GFP_KERNEL);
+ if (!uac1)
return ERR_PTR(-ENOMEM);
- audio->card.func.name = "g_audio";
-
opts = container_of(fi, struct f_uac1_opts, func_inst);
mutex_lock(&opts->lock);
++opts->refcnt;
mutex_unlock(&opts->lock);
- INIT_LIST_HEAD(&audio->play_queue);
- spin_lock_init(&audio->lock);
-
- 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;
- audio->card.func.free_func = f_audio_free;
-
- control_selector_init(audio);
- INIT_WORK(&audio->playback_work, f_audio_playback_work);
+ uac1->g_audio.func.name = "uac1_func";
+ uac1->g_audio.func.bind = f_audio_bind;
+ uac1->g_audio.func.unbind = f_audio_unbind;
+ uac1->g_audio.func.set_alt = f_audio_set_alt;
+ uac1->g_audio.func.get_alt = f_audio_get_alt;
+ uac1->g_audio.func.setup = f_audio_setup;
+ uac1->g_audio.func.disable = f_audio_disable;
+ uac1->g_audio.func.free_func = f_audio_free;
- return &audio->card.func;
+ return &uac1->g_audio.func;
}
DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc);
MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Bryan Wu");
+MODULE_AUTHOR("Ruslan Bilovol");
diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c
new file mode 100644
index 000000000000..5d229e72912e
--- /dev/null
+++ b/drivers/usb/gadget/function/f_uac1_legacy.c
@@ -0,0 +1,1021 @@
+/*
+ * f_audio.c -- USB Audio class function driver
+ *
+ * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ * 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/module.h>
+#include <linux/device.h>
+#include <linux/atomic.h>
+
+#include "u_uac1_legacy.h"
+
+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 1
+
+/* B.3.1 Standard AC Interface Descriptor */
+static struct usb_interface_descriptor ac_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+};
+
+/*
+ * The number of AudioStreaming and MIDIStreaming interfaces
+ * in the Audio Interface Collection
+ */
+DECLARE_UAC_AC_HEADER_DESCRIPTOR(1);
+
+#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_1 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 = {
+ /* Interface number of the first AudioStream interface */
+ [0] = 1,
+ }
+};
+
+#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 = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+ .bInterval = 4,
+};
+
+/* Class-specific AS ISO OUT Endpoint Descriptor */
+static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
+ .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[] = {
+ (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,
+};
+
+enum {
+ STR_AC_IF,
+ STR_INPUT_TERMINAL,
+ STR_INPUT_TERMINAL_CH_NAMES,
+ STR_FEAT_DESC_0,
+ STR_OUTPUT_TERMINAL,
+ STR_AS_IF_ALT0,
+ STR_AS_IF_ALT1,
+};
+
+static struct usb_string strings_uac1[] = {
+ [STR_AC_IF].s = "AC Interface",
+ [STR_INPUT_TERMINAL].s = "Input terminal",
+ [STR_INPUT_TERMINAL_CH_NAMES].s = "Channels",
+ [STR_FEAT_DESC_0].s = "Volume control & mute",
+ [STR_OUTPUT_TERMINAL].s = "Output terminal",
+ [STR_AS_IF_ALT0].s = "AS Interface",
+ [STR_AS_IF_ALT1].s = "AS Interface",
+ { },
+};
+
+static struct usb_gadget_strings str_uac1 = {
+ .language = 0x0409, /* en-us */
+ .strings = strings_uac1,
+};
+
+static struct usb_gadget_strings *uac1_strings[] = {
+ &str_uac1,
+ 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;
+
+ u8 ac_intf, ac_alt;
+ u8 as_intf, as_alt;
+
+ /* 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;
+ struct f_uac1_legacy_opts *opts;
+ int audio_buf_size;
+ int err;
+
+ opts = container_of(audio->card.func.fi, struct f_uac1_legacy_opts,
+ func_inst);
+ audio_buf_size = opts->audio_buf_size;
+
+ 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(&copy_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;
+ struct f_uac1_legacy_opts *opts;
+ int req_buf_size, req_count, audio_buf_size;
+ int i = 0, err = 0;
+
+ DBG(cdev, "intf %d, alt %d\n", intf, alt);
+
+ opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst);
+ req_buf_size = opts->req_buf_size;
+ req_count = opts->req_count;
+ audio_buf_size = opts->audio_buf_size;
+
+ /* No i/f has more than 2 alt settings */
+ if (alt > 1) {
+ ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+
+ if (intf == audio->ac_intf) {
+ /* Control I/f has only 1 AltSetting - 0 */
+ if (alt) {
+ ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+ return 0;
+ } else if (intf == audio->as_intf) {
+ if (alt == 1) {
+ err = config_ep_by_speed(cdev->gadget, f, out_ep);
+ if (err)
+ return err;
+
+ usb_ep_enable(out_ep);
+ 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(&copy_buf->list,
+ &audio->play_queue);
+ schedule_work(&audio->playback_work);
+ }
+ }
+ audio->as_alt = alt;
+ }
+
+ return err;
+}
+
+static int f_audio_get_alt(struct usb_function *f, unsigned intf)
+{
+ struct f_audio *audio = func_to_audio(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ if (intf == audio->ac_intf)
+ return audio->ac_alt;
+ else if (intf == audio->as_intf)
+ return audio->as_alt;
+ else
+ ERROR(cdev, "%s:%d Invalid Interface %d!\n",
+ __func__, __LINE__, intf);
+
+ return -EINVAL;
+}
+
+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
+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);
+ struct usb_string *us;
+ int status;
+ struct usb_ep *ep = NULL;
+ struct f_uac1_legacy_opts *audio_opts;
+
+ audio_opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst);
+ audio->card.gadget = c->cdev->gadget;
+ /* set up ASLA audio devices */
+ if (!audio_opts->bound) {
+ status = gaudio_setup(&audio->card);
+ if (status < 0)
+ return status;
+ audio_opts->bound = true;
+ }
+ us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
+ if (IS_ERR(us))
+ return PTR_ERR(us);
+ ac_interface_desc.iInterface = us[STR_AC_IF].id;
+ input_terminal_desc.iTerminal = us[STR_INPUT_TERMINAL].id;
+ input_terminal_desc.iChannelNames = us[STR_INPUT_TERMINAL_CH_NAMES].id;
+ feature_unit_desc.iFeature = us[STR_FEAT_DESC_0].id;
+ output_terminal_desc.iTerminal = us[STR_OUTPUT_TERMINAL].id;
+ as_interface_alt_0_desc.iInterface = us[STR_AS_IF_ALT0].id;
+ as_interface_alt_1_desc.iInterface = us[STR_AS_IF_ALT1].id;
+
+
+ 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;
+ audio->ac_intf = status;
+ audio->ac_alt = 0;
+
+ 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;
+ audio->as_intf = status;
+ audio->as_alt = 0;
+
+ 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;
+
+ status = -ENOMEM;
+
+ /* copy descriptors, and track endpoint copies */
+ status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL,
+ NULL);
+ if (status)
+ goto fail;
+ return 0;
+
+fail:
+ gaudio_cleanup(&audio->card);
+ return status;
+}
+
+/*-------------------------------------------------------------------------*/
+
+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 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;
+}
+
+static inline
+struct f_uac1_legacy_opts *to_f_uac1_opts(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct f_uac1_legacy_opts,
+ func_inst.group);
+}
+
+static void f_uac1_attr_release(struct config_item *item)
+{
+ struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item);
+
+ usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations f_uac1_item_ops = {
+ .release = f_uac1_attr_release,
+};
+
+#define UAC1_INT_ATTRIBUTE(name) \
+static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
+ char *page) \
+{ \
+ struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \
+ int result; \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%u\n", opts->name); \
+ mutex_unlock(&opts->lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \
+ int ret; \
+ u32 num; \
+ \
+ mutex_lock(&opts->lock); \
+ if (opts->refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ ret = kstrtou32(page, 0, &num); \
+ if (ret) \
+ goto end; \
+ \
+ opts->name = num; \
+ ret = len; \
+ \
+end: \
+ mutex_unlock(&opts->lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(f_uac1_opts_, name)
+
+UAC1_INT_ATTRIBUTE(req_buf_size);
+UAC1_INT_ATTRIBUTE(req_count);
+UAC1_INT_ATTRIBUTE(audio_buf_size);
+
+#define UAC1_STR_ATTRIBUTE(name) \
+static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
+ char *page) \
+{ \
+ struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \
+ int result; \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%s\n", opts->name); \
+ mutex_unlock(&opts->lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \
+ int ret = -EBUSY; \
+ char *tmp; \
+ \
+ mutex_lock(&opts->lock); \
+ if (opts->refcnt) \
+ goto end; \
+ \
+ tmp = kstrndup(page, len, GFP_KERNEL); \
+ if (tmp) { \
+ ret = -ENOMEM; \
+ goto end; \
+ } \
+ if (opts->name##_alloc) \
+ kfree(opts->name); \
+ opts->name##_alloc = true; \
+ opts->name = tmp; \
+ ret = len; \
+ \
+end: \
+ mutex_unlock(&opts->lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(f_uac1_opts_, name)
+
+UAC1_STR_ATTRIBUTE(fn_play);
+UAC1_STR_ATTRIBUTE(fn_cap);
+UAC1_STR_ATTRIBUTE(fn_cntl);
+
+static struct configfs_attribute *f_uac1_attrs[] = {
+ &f_uac1_opts_attr_req_buf_size,
+ &f_uac1_opts_attr_req_count,
+ &f_uac1_opts_attr_audio_buf_size,
+ &f_uac1_opts_attr_fn_play,
+ &f_uac1_opts_attr_fn_cap,
+ &f_uac1_opts_attr_fn_cntl,
+ NULL,
+};
+
+static struct config_item_type f_uac1_func_type = {
+ .ct_item_ops = &f_uac1_item_ops,
+ .ct_attrs = f_uac1_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static void f_audio_free_inst(struct usb_function_instance *f)
+{
+ struct f_uac1_legacy_opts *opts;
+
+ opts = container_of(f, struct f_uac1_legacy_opts, func_inst);
+ if (opts->fn_play_alloc)
+ kfree(opts->fn_play);
+ if (opts->fn_cap_alloc)
+ kfree(opts->fn_cap);
+ if (opts->fn_cntl_alloc)
+ kfree(opts->fn_cntl);
+ kfree(opts);
+}
+
+static struct usb_function_instance *f_audio_alloc_inst(void)
+{
+ struct f_uac1_legacy_opts *opts;
+
+ opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+ if (!opts)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&opts->lock);
+ opts->func_inst.free_func_inst = f_audio_free_inst;
+
+ config_group_init_type_name(&opts->func_inst.group, "",
+ &f_uac1_func_type);
+
+ opts->req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE;
+ opts->req_count = UAC1_REQ_COUNT;
+ opts->audio_buf_size = UAC1_AUDIO_BUF_SIZE;
+ opts->fn_play = FILE_PCM_PLAYBACK;
+ opts->fn_cap = FILE_PCM_CAPTURE;
+ opts->fn_cntl = FILE_CONTROL;
+ return &opts->func_inst;
+}
+
+static void f_audio_free(struct usb_function *f)
+{
+ struct f_audio *audio = func_to_audio(f);
+ struct f_uac1_legacy_opts *opts;
+
+ gaudio_cleanup(&audio->card);
+ opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst);
+ kfree(audio);
+ mutex_lock(&opts->lock);
+ --opts->refcnt;
+ mutex_unlock(&opts->lock);
+}
+
+static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ usb_free_all_descriptors(f);
+}
+
+static struct usb_function *f_audio_alloc(struct usb_function_instance *fi)
+{
+ struct f_audio *audio;
+ struct f_uac1_legacy_opts *opts;
+
+ /* allocate and initialize one new instance */
+ audio = kzalloc(sizeof(*audio), GFP_KERNEL);
+ if (!audio)
+ return ERR_PTR(-ENOMEM);
+
+ audio->card.func.name = "g_audio";
+
+ opts = container_of(fi, struct f_uac1_legacy_opts, func_inst);
+ mutex_lock(&opts->lock);
+ ++opts->refcnt;
+ mutex_unlock(&opts->lock);
+ INIT_LIST_HEAD(&audio->play_queue);
+ spin_lock_init(&audio->lock);
+
+ 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.get_alt = f_audio_get_alt;
+ audio->card.func.setup = f_audio_setup;
+ audio->card.func.disable = f_audio_disable;
+ audio->card.func.free_func = f_audio_free;
+
+ control_selector_init(audio);
+
+ INIT_WORK(&audio->playback_work, f_audio_playback_work);
+
+ return &audio->card.func;
+}
+
+DECLARE_USB_FUNCTION_INIT(uac1_legacy, f_audio_alloc_inst, f_audio_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Bryan Wu");
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index f6a0d3a1311b..9082ce261e70 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -13,13 +13,9 @@
#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
-#include <linux/platform_device.h>
#include <linux/module.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
-
+#include "u_audio.h"
#include "u_uac2.h"
/*
@@ -51,506 +47,23 @@
#define UNFLW_CTRL 8
#define OVFLW_CTRL 10
-static const char *uac2_name = "snd_uac2";
-
-struct uac2_req {
- struct uac2_rtd_params *pp; /* parent param */
- struct usb_request *req;
-};
-
-struct uac2_rtd_params {
- struct snd_uac2_chip *uac2; /* parent chip */
- bool ep_enabled; /* if the ep is enabled */
- /* Size of the ring buffer */
- size_t dma_bytes;
- unsigned char *dma_area;
-
- struct snd_pcm_substream *ss;
-
- /* Ring buffer */
- ssize_t hw_ptr;
-
- void *rbuf;
-
- size_t period_size;
-
- unsigned max_psize;
- struct uac2_req *ureq;
-
- spinlock_t lock;
-};
-
-struct snd_uac2_chip {
- struct platform_device pdev;
- struct platform_driver pdrv;
-
- struct uac2_rtd_params p_prm;
- struct uac2_rtd_params c_prm;
-
- struct snd_card *card;
- struct snd_pcm *pcm;
-
- /* timekeeping for the playback endpoint */
- unsigned int p_interval;
- unsigned int p_residue;
-
- /* pre-calculated values for playback iso completion */
- unsigned int p_pktsize;
- unsigned int p_pktsize_residue;
- unsigned int p_framesize;
+struct f_uac2 {
+ struct g_audio g_audio;
+ u8 ac_intf, as_in_intf, as_out_intf;
+ u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */
};
-#define BUFF_SIZE_MAX (PAGE_SIZE * 16)
-#define PRD_SIZE_MAX PAGE_SIZE
-#define MIN_PERIODS 4
-
-static struct snd_pcm_hardware uac2_pcm_hardware = {
- .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER
- | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
- | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
- .rates = SNDRV_PCM_RATE_CONTINUOUS,
- .periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX,
- .buffer_bytes_max = BUFF_SIZE_MAX,
- .period_bytes_max = PRD_SIZE_MAX,
- .periods_min = MIN_PERIODS,
-};
-
-struct audio_dev {
- u8 ac_intf, ac_alt;
- u8 as_out_intf, as_out_alt;
- u8 as_in_intf, as_in_alt;
-
- struct usb_ep *in_ep, *out_ep;
- struct usb_function func;
-
- /* The ALSA Sound Card it represents on the USB-Client side */
- struct snd_uac2_chip uac2;
-};
-
-static inline
-struct audio_dev *func_to_agdev(struct usb_function *f)
+static inline struct f_uac2 *func_to_uac2(struct usb_function *f)
{
- return container_of(f, struct audio_dev, func);
+ return container_of(f, struct f_uac2, g_audio.func);
}
static inline
-struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u)
-{
- return container_of(u, struct audio_dev, uac2);
-}
-
-static inline
-struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p)
-{
- return container_of(p, struct snd_uac2_chip, pdev);
-}
-
-static inline
-struct f_uac2_opts *agdev_to_uac2_opts(struct audio_dev *agdev)
+struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev)
{
return container_of(agdev->func.fi, struct f_uac2_opts, func_inst);
}
-static inline
-uint num_channels(uint chanmask)
-{
- uint num = 0;
-
- while (chanmask) {
- num += (chanmask & 1);
- chanmask >>= 1;
- }
-
- return num;
-}
-
-static void
-agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
-{
- unsigned pending;
- unsigned long flags;
- unsigned int hw_ptr;
- bool update_alsa = false;
- int status = req->status;
- struct uac2_req *ur = req->context;
- struct snd_pcm_substream *substream;
- struct uac2_rtd_params *prm = ur->pp;
- struct snd_uac2_chip *uac2 = prm->uac2;
-
- /* i/f shutting down */
- if (!prm->ep_enabled || req->status == -ESHUTDOWN)
- return;
-
- /*
- * We can't really do much about bad xfers.
- * Afterall, the ISOCH xfers could fail legitimately.
- */
- if (status)
- pr_debug("%s: iso_complete status(%d) %d/%d\n",
- __func__, status, req->actual, req->length);
-
- substream = prm->ss;
-
- /* Do nothing if ALSA isn't active */
- if (!substream)
- goto exit;
-
- spin_lock_irqsave(&prm->lock, flags);
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- /*
- * For each IN packet, take the quotient of the current data
- * rate and the endpoint's interval as the base packet size.
- * If there is a residue from this division, add it to the
- * residue accumulator.
- */
- req->length = uac2->p_pktsize;
- uac2->p_residue += uac2->p_pktsize_residue;
-
- /*
- * Whenever there are more bytes in the accumulator than we
- * need to add one more sample frame, increase this packet's
- * size and decrease the accumulator.
- */
- if (uac2->p_residue / uac2->p_interval >= uac2->p_framesize) {
- req->length += uac2->p_framesize;
- uac2->p_residue -= uac2->p_framesize *
- uac2->p_interval;
- }
-
- req->actual = req->length;
- }
-
- pending = prm->hw_ptr % prm->period_size;
- pending += req->actual;
- if (pending >= prm->period_size)
- update_alsa = true;
-
- hw_ptr = prm->hw_ptr;
- prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes;
-
- spin_unlock_irqrestore(&prm->lock, flags);
-
- /* Pack USB load in ALSA ring buffer */
- pending = prm->dma_bytes - hw_ptr;
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- if (unlikely(pending < req->actual)) {
- memcpy(req->buf, prm->dma_area + hw_ptr, pending);
- memcpy(req->buf + pending, prm->dma_area,
- req->actual - pending);
- } else {
- memcpy(req->buf, prm->dma_area + hw_ptr, req->actual);
- }
- } else {
- if (unlikely(pending < req->actual)) {
- memcpy(prm->dma_area + hw_ptr, req->buf, pending);
- memcpy(prm->dma_area, req->buf + pending,
- req->actual - pending);
- } else {
- memcpy(prm->dma_area + hw_ptr, req->buf, req->actual);
- }
- }
-
-exit:
- if (usb_ep_queue(ep, req, GFP_ATOMIC))
- dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
-
- if (update_alsa)
- snd_pcm_period_elapsed(substream);
-
- return;
-}
-
-static int
-uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
-{
- struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
- struct audio_dev *agdev = uac2_to_agdev(uac2);
- struct f_uac2_opts *uac2_opts = agdev_to_uac2_opts(agdev);
- struct uac2_rtd_params *prm;
- unsigned long flags;
- int err = 0;
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- prm = &uac2->p_prm;
- else
- prm = &uac2->c_prm;
-
- spin_lock_irqsave(&prm->lock, flags);
-
- /* Reset */
- prm->hw_ptr = 0;
-
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- prm->ss = substream;
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- prm->ss = NULL;
- break;
- default:
- err = -EINVAL;
- }
-
- spin_unlock_irqrestore(&prm->lock, flags);
-
- /* Clear buffer after Play stops */
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss)
- memset(prm->rbuf, 0, prm->max_psize * uac2_opts->req_number);
-
- return err;
-}
-
-static snd_pcm_uframes_t uac2_pcm_pointer(struct snd_pcm_substream *substream)
-{
- struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
- struct uac2_rtd_params *prm;
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- prm = &uac2->p_prm;
- else
- prm = &uac2->c_prm;
-
- return bytes_to_frames(substream->runtime, prm->hw_ptr);
-}
-
-static int uac2_pcm_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *hw_params)
-{
- struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
- struct uac2_rtd_params *prm;
- int err;
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- prm = &uac2->p_prm;
- else
- prm = &uac2->c_prm;
-
- err = snd_pcm_lib_malloc_pages(substream,
- params_buffer_bytes(hw_params));
- if (err >= 0) {
- prm->dma_bytes = substream->runtime->dma_bytes;
- prm->dma_area = substream->runtime->dma_area;
- prm->period_size = params_period_bytes(hw_params);
- }
-
- return err;
-}
-
-static int uac2_pcm_hw_free(struct snd_pcm_substream *substream)
-{
- struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
- struct uac2_rtd_params *prm;
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- prm = &uac2->p_prm;
- else
- prm = &uac2->c_prm;
-
- prm->dma_area = NULL;
- prm->dma_bytes = 0;
- prm->period_size = 0;
-
- return snd_pcm_lib_free_pages(substream);
-}
-
-static int uac2_pcm_open(struct snd_pcm_substream *substream)
-{
- struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct audio_dev *audio_dev;
- struct f_uac2_opts *opts;
- int p_ssize, c_ssize;
- int p_srate, c_srate;
- int p_chmask, c_chmask;
-
- audio_dev = uac2_to_agdev(uac2);
- opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst);
- p_ssize = opts->p_ssize;
- c_ssize = opts->c_ssize;
- p_srate = opts->p_srate;
- c_srate = opts->c_srate;
- p_chmask = opts->p_chmask;
- c_chmask = opts->c_chmask;
- uac2->p_residue = 0;
-
- runtime->hw = uac2_pcm_hardware;
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- spin_lock_init(&uac2->p_prm.lock);
- runtime->hw.rate_min = p_srate;
- switch (p_ssize) {
- case 3:
- runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE;
- break;
- case 4:
- runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
- break;
- default:
- runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
- break;
- }
- runtime->hw.channels_min = num_channels(p_chmask);
- runtime->hw.period_bytes_min = 2 * uac2->p_prm.max_psize
- / runtime->hw.periods_min;
- } else {
- spin_lock_init(&uac2->c_prm.lock);
- runtime->hw.rate_min = c_srate;
- switch (c_ssize) {
- case 3:
- runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE;
- break;
- case 4:
- runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
- break;
- default:
- runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
- break;
- }
- runtime->hw.channels_min = num_channels(c_chmask);
- runtime->hw.period_bytes_min = 2 * uac2->c_prm.max_psize
- / runtime->hw.periods_min;
- }
-
- runtime->hw.rate_max = runtime->hw.rate_min;
- runtime->hw.channels_max = runtime->hw.channels_min;
-
- snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
-
- return 0;
-}
-
-/* ALSA cries without these function pointers */
-static int uac2_pcm_null(struct snd_pcm_substream *substream)
-{
- return 0;
-}
-
-static struct snd_pcm_ops uac2_pcm_ops = {
- .open = uac2_pcm_open,
- .close = uac2_pcm_null,
- .ioctl = snd_pcm_lib_ioctl,
- .hw_params = uac2_pcm_hw_params,
- .hw_free = uac2_pcm_hw_free,
- .trigger = uac2_pcm_trigger,
- .pointer = uac2_pcm_pointer,
- .prepare = uac2_pcm_null,
-};
-
-static int snd_uac2_probe(struct platform_device *pdev)
-{
- struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev);
- struct snd_card *card;
- struct snd_pcm *pcm;
- struct audio_dev *audio_dev;
- struct f_uac2_opts *opts;
- int err;
- int p_chmask, c_chmask;
-
- audio_dev = uac2_to_agdev(uac2);
- opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst);
- p_chmask = opts->p_chmask;
- c_chmask = opts->c_chmask;
-
- /* Choose any slot, with no id */
- err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card);
- if (err < 0)
- return err;
-
- uac2->card = card;
-
- /*
- * Create first PCM device
- * Create a substream only for non-zero channel streams
- */
- err = snd_pcm_new(uac2->card, "UAC2 PCM", 0,
- p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm);
- if (err < 0)
- goto snd_fail;
-
- strcpy(pcm->name, "UAC2 PCM");
- pcm->private_data = uac2;
-
- uac2->pcm = pcm;
-
- snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops);
- snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops);
-
- strcpy(card->driver, "UAC2_Gadget");
- strcpy(card->shortname, "UAC2_Gadget");
- sprintf(card->longname, "UAC2_Gadget %i", pdev->id);
-
- snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
- snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
-
- err = snd_card_register(card);
- if (!err) {
- platform_set_drvdata(pdev, card);
- return 0;
- }
-
-snd_fail:
- snd_card_free(card);
-
- uac2->pcm = NULL;
- uac2->card = NULL;
-
- return err;
-}
-
-static int snd_uac2_remove(struct platform_device *pdev)
-{
- struct snd_card *card = platform_get_drvdata(pdev);
-
- if (card)
- return snd_card_free(card);
-
- return 0;
-}
-
-static void snd_uac2_release(struct device *dev)
-{
- dev_dbg(dev, "releasing '%s'\n", dev_name(dev));
-}
-
-static int alsa_uac2_init(struct audio_dev *agdev)
-{
- struct snd_uac2_chip *uac2 = &agdev->uac2;
- int err;
-
- uac2->pdrv.probe = snd_uac2_probe;
- uac2->pdrv.remove = snd_uac2_remove;
- uac2->pdrv.driver.name = uac2_name;
-
- uac2->pdev.id = 0;
- uac2->pdev.name = uac2_name;
- uac2->pdev.dev.release = snd_uac2_release;
-
- /* Register snd_uac2 driver */
- err = platform_driver_register(&uac2->pdrv);
- if (err)
- return err;
-
- /* Register snd_uac2 device */
- err = platform_device_register(&uac2->pdev);
- if (err)
- platform_driver_unregister(&uac2->pdrv);
-
- return err;
-}
-
-static void alsa_uac2_exit(struct audio_dev *agdev)
-{
- struct snd_uac2_chip *uac2 = &agdev->uac2;
-
- platform_driver_unregister(&uac2->pdrv);
- platform_device_unregister(&uac2->pdev);
-}
-
-
/* --------- USB Function Interface ------------- */
enum {
@@ -938,32 +451,6 @@ struct cntrl_range_lay3 {
__u32 dRES;
} __packed;
-static inline void
-free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep)
-{
- struct snd_uac2_chip *uac2 = prm->uac2;
- struct audio_dev *agdev = uac2_to_agdev(uac2);
- struct f_uac2_opts *uac2_opts = agdev_to_uac2_opts(agdev);
- int i;
-
- if (!prm->ep_enabled)
- return;
-
- prm->ep_enabled = false;
-
- for (i = 0; i < uac2_opts->req_number; i++) {
- if (prm->ureq[i].req) {
- usb_ep_dequeue(ep, prm->ureq[i].req);
- usb_ep_free_request(ep, prm->ureq[i].req);
- prm->ureq[i].req = NULL;
- }
- }
-
- if (usb_ep_disable(ep))
- dev_err(&uac2->pdev.dev,
- "%s:%d Error!\n", __func__, __LINE__);
-}
-
static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
struct usb_endpoint_descriptor *ep_desc,
unsigned int factor, bool is_playback)
@@ -990,12 +477,11 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
static int
afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
{
- struct audio_dev *agdev = func_to_agdev(fn);
- struct snd_uac2_chip *uac2 = &agdev->uac2;
+ struct f_uac2 *uac2 = func_to_uac2(fn);
+ struct g_audio *agdev = func_to_g_audio(fn);
struct usb_composite_dev *cdev = cfg->cdev;
struct usb_gadget *gadget = cdev->gadget;
- struct device *dev = &uac2->pdev.dev;
- struct uac2_rtd_params *prm;
+ struct device *dev = &gadget->dev;
struct f_uac2_opts *uac2_opts;
struct usb_string *us;
int ret;
@@ -1042,8 +528,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
return ret;
}
std_ac_if_desc.bInterfaceNumber = ret;
- agdev->ac_intf = ret;
- agdev->ac_alt = 0;
+ uac2->ac_intf = ret;
+ uac2->ac_alt = 0;
ret = usb_interface_id(cfg, fn);
if (ret < 0) {
@@ -1052,8 +538,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
}
std_as_out_if0_desc.bInterfaceNumber = ret;
std_as_out_if1_desc.bInterfaceNumber = ret;
- agdev->as_out_intf = ret;
- agdev->as_out_alt = 0;
+ uac2->as_out_intf = ret;
+ uac2->as_out_alt = 0;
ret = usb_interface_id(cfg, fn);
if (ret < 0) {
@@ -1062,8 +548,14 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
}
std_as_in_if0_desc.bInterfaceNumber = ret;
std_as_in_if1_desc.bInterfaceNumber = ret;
- agdev->as_in_intf = ret;
- agdev->as_in_alt = 0;
+ uac2->as_in_intf = ret;
+ uac2->as_in_alt = 0;
+
+ /* Calculate wMaxPacketSize according to audio bandwidth */
+ set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true);
+ set_ep_max_packet_size(uac2_opts, &fs_epout_desc, 1000, false);
+ set_ep_max_packet_size(uac2_opts, &hs_epin_desc, 8000, true);
+ set_ep_max_packet_size(uac2_opts, &hs_epout_desc, 8000, false);
agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
if (!agdev->out_ep) {
@@ -1077,14 +569,10 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
return ret;
}
- uac2->p_prm.uac2 = uac2;
- uac2->c_prm.uac2 = uac2;
-
- /* Calculate wMaxPacketSize according to audio bandwidth */
- set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true);
- set_ep_max_packet_size(uac2_opts, &fs_epout_desc, 1000, false);
- set_ep_max_packet_size(uac2_opts, &hs_epin_desc, 8000, true);
- set_ep_max_packet_size(uac2_opts, &hs_epout_desc, 8000, false);
+ agdev->in_ep_maxpsize = max(fs_epin_desc.wMaxPacketSize,
+ hs_epin_desc.wMaxPacketSize);
+ agdev->out_ep_maxpsize = max(fs_epout_desc.wMaxPacketSize,
+ hs_epout_desc.wMaxPacketSize);
hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
@@ -1094,48 +582,23 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
if (ret)
return ret;
- prm = &agdev->uac2.c_prm;
- prm->max_psize = hs_epout_desc.wMaxPacketSize;
- prm->ureq = kcalloc(uac2_opts->req_number, sizeof(struct uac2_req),
- GFP_KERNEL);
- if (!prm->ureq) {
- ret = -ENOMEM;
- goto err_free_descs;
- }
- prm->rbuf = kcalloc(uac2_opts->req_number, prm->max_psize, GFP_KERNEL);
- if (!prm->rbuf) {
- prm->max_psize = 0;
- ret = -ENOMEM;
- goto err_free_descs;
- }
-
- prm = &agdev->uac2.p_prm;
- prm->max_psize = hs_epin_desc.wMaxPacketSize;
- prm->ureq = kcalloc(uac2_opts->req_number, sizeof(struct uac2_req),
- GFP_KERNEL);
- if (!prm->ureq) {
- ret = -ENOMEM;
- goto err_free_descs;
- }
- prm->rbuf = kcalloc(uac2_opts->req_number, prm->max_psize, GFP_KERNEL);
- if (!prm->rbuf) {
- prm->max_psize = 0;
- ret = -ENOMEM;
- goto err_no_memory;
- }
+ agdev->gadget = gadget;
- ret = alsa_uac2_init(agdev);
+ agdev->params.p_chmask = uac2_opts->p_chmask;
+ agdev->params.p_srate = uac2_opts->p_srate;
+ agdev->params.p_ssize = uac2_opts->p_ssize;
+ agdev->params.c_chmask = uac2_opts->c_chmask;
+ agdev->params.c_srate = uac2_opts->c_srate;
+ agdev->params.c_ssize = uac2_opts->c_ssize;
+ agdev->params.req_number = uac2_opts->req_number;
+ ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget");
if (ret)
- goto err_no_memory;
+ goto err_free_descs;
return 0;
-err_no_memory:
- kfree(agdev->uac2.p_prm.ureq);
- kfree(agdev->uac2.c_prm.ureq);
- kfree(agdev->uac2.p_prm.rbuf);
- kfree(agdev->uac2.c_prm.rbuf);
err_free_descs:
usb_free_all_descriptors(fn);
+ agdev->gadget = NULL;
return ret;
}
@@ -1143,15 +606,10 @@ static int
afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
{
struct usb_composite_dev *cdev = fn->config->cdev;
- struct audio_dev *agdev = func_to_agdev(fn);
- struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev);
- struct snd_uac2_chip *uac2 = &agdev->uac2;
+ struct f_uac2 *uac2 = func_to_uac2(fn);
struct usb_gadget *gadget = cdev->gadget;
- struct device *dev = &uac2->pdev.dev;
- struct usb_request *req;
- struct usb_ep *ep;
- struct uac2_rtd_params *prm;
- int req_len, i;
+ struct device *dev = &gadget->dev;
+ int ret = 0;
/* No i/f has more than 2 alt settings */
if (alt > 1) {
@@ -1159,7 +617,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
return -EINVAL;
}
- if (intf == agdev->ac_intf) {
+ if (intf == uac2->ac_intf) {
/* Control I/f has only 1 AltSetting - 0 */
if (alt) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
@@ -1168,95 +626,42 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
return 0;
}
- if (intf == agdev->as_out_intf) {
- ep = agdev->out_ep;
- prm = &uac2->c_prm;
- config_ep_by_speed(gadget, fn, ep);
- agdev->as_out_alt = alt;
- req_len = prm->max_psize;
- } else if (intf == agdev->as_in_intf) {
- unsigned int factor, rate;
- struct usb_endpoint_descriptor *ep_desc;
-
- ep = agdev->in_ep;
- prm = &uac2->p_prm;
- config_ep_by_speed(gadget, fn, ep);
- agdev->as_in_alt = alt;
-
- /* pre-calculate the playback endpoint's interval */
- if (gadget->speed == USB_SPEED_FULL) {
- ep_desc = &fs_epin_desc;
- factor = 1000;
- } else {
- ep_desc = &hs_epin_desc;
- factor = 8000;
- }
-
- /* pre-compute some values for iso_complete() */
- uac2->p_framesize = opts->p_ssize *
- num_channels(opts->p_chmask);
- rate = opts->p_srate * uac2->p_framesize;
- uac2->p_interval = factor / (1 << (ep_desc->bInterval - 1));
- uac2->p_pktsize = min_t(unsigned int, rate / uac2->p_interval,
- prm->max_psize);
+ if (intf == uac2->as_out_intf) {
+ uac2->as_out_alt = alt;
- if (uac2->p_pktsize < prm->max_psize)
- uac2->p_pktsize_residue = rate % uac2->p_interval;
+ if (alt)
+ ret = u_audio_start_capture(&uac2->g_audio);
else
- uac2->p_pktsize_residue = 0;
+ u_audio_stop_capture(&uac2->g_audio);
+ } else if (intf == uac2->as_in_intf) {
+ uac2->as_in_alt = alt;
- req_len = uac2->p_pktsize;
- uac2->p_residue = 0;
+ if (alt)
+ ret = u_audio_start_playback(&uac2->g_audio);
+ else
+ u_audio_stop_playback(&uac2->g_audio);
} else {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return -EINVAL;
}
- if (alt == 0) {
- free_ep(prm, ep);
- return 0;
- }
-
- prm->ep_enabled = true;
- usb_ep_enable(ep);
-
- for (i = 0; i < opts->req_number; i++) {
- if (!prm->ureq[i].req) {
- req = usb_ep_alloc_request(ep, GFP_ATOMIC);
- if (req == NULL)
- return -ENOMEM;
-
- prm->ureq[i].req = req;
- prm->ureq[i].pp = prm;
-
- req->zero = 0;
- req->context = &prm->ureq[i];
- req->length = req_len;
- req->complete = agdev_iso_complete;
- req->buf = prm->rbuf + i * prm->max_psize;
- }
-
- if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- }
-
- return 0;
+ return ret;
}
static int
afunc_get_alt(struct usb_function *fn, unsigned intf)
{
- struct audio_dev *agdev = func_to_agdev(fn);
- struct snd_uac2_chip *uac2 = &agdev->uac2;
-
- if (intf == agdev->ac_intf)
- return agdev->ac_alt;
- else if (intf == agdev->as_out_intf)
- return agdev->as_out_alt;
- else if (intf == agdev->as_in_intf)
- return agdev->as_in_alt;
+ struct f_uac2 *uac2 = func_to_uac2(fn);
+ struct g_audio *agdev = func_to_g_audio(fn);
+
+ if (intf == uac2->ac_intf)
+ return uac2->ac_alt;
+ else if (intf == uac2->as_out_intf)
+ return uac2->as_out_alt;
+ else if (intf == uac2->as_in_intf)
+ return uac2->as_in_alt;
else
- dev_err(&uac2->pdev.dev,
+ dev_err(&agdev->gadget->dev,
"%s:%d Invalid Interface %d!\n",
__func__, __LINE__, intf);
@@ -1266,22 +671,19 @@ afunc_get_alt(struct usb_function *fn, unsigned intf)
static void
afunc_disable(struct usb_function *fn)
{
- struct audio_dev *agdev = func_to_agdev(fn);
- struct snd_uac2_chip *uac2 = &agdev->uac2;
-
- free_ep(&uac2->p_prm, agdev->in_ep);
- agdev->as_in_alt = 0;
+ struct f_uac2 *uac2 = func_to_uac2(fn);
- free_ep(&uac2->c_prm, agdev->out_ep);
- agdev->as_out_alt = 0;
+ uac2->as_in_alt = 0;
+ uac2->as_out_alt = 0;
+ u_audio_stop_capture(&uac2->g_audio);
+ u_audio_stop_playback(&uac2->g_audio);
}
static int
in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
struct usb_request *req = fn->config->cdev->req;
- struct audio_dev *agdev = func_to_agdev(fn);
- struct snd_uac2_chip *uac2 = &agdev->uac2;
+ struct g_audio *agdev = func_to_g_audio(fn);
struct f_uac2_opts *opts;
u16 w_length = le16_to_cpu(cr->wLength);
u16 w_index = le16_to_cpu(cr->wIndex);
@@ -1291,7 +693,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
int value = -EOPNOTSUPP;
int p_srate, c_srate;
- opts = agdev_to_uac2_opts(agdev);
+ opts = g_audio_to_uac2_opts(agdev);
p_srate = opts->p_srate;
c_srate = opts->c_srate;
@@ -1310,7 +712,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
*(u8 *)req->buf = 1;
value = min_t(unsigned, w_length, 1);
} else {
- dev_err(&uac2->pdev.dev,
+ dev_err(&agdev->gadget->dev,
"%s:%d control_selector=%d TODO!\n",
__func__, __LINE__, control_selector);
}
@@ -1322,8 +724,7 @@ static int
in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
struct usb_request *req = fn->config->cdev->req;
- struct audio_dev *agdev = func_to_agdev(fn);
- struct snd_uac2_chip *uac2 = &agdev->uac2;
+ struct g_audio *agdev = func_to_g_audio(fn);
struct f_uac2_opts *opts;
u16 w_length = le16_to_cpu(cr->wLength);
u16 w_index = le16_to_cpu(cr->wIndex);
@@ -1334,7 +735,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
int value = -EOPNOTSUPP;
int p_srate, c_srate;
- opts = agdev_to_uac2_opts(agdev);
+ opts = g_audio_to_uac2_opts(agdev);
p_srate = opts->p_srate;
c_srate = opts->c_srate;
@@ -1353,7 +754,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
value = min_t(unsigned, w_length, sizeof r);
memcpy(req->buf, &r, value);
} else {
- dev_err(&uac2->pdev.dev,
+ dev_err(&agdev->gadget->dev,
"%s:%d control_selector=%d TODO!\n",
__func__, __LINE__, control_selector);
}
@@ -1388,13 +789,13 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
static int
setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
- struct audio_dev *agdev = func_to_agdev(fn);
- struct snd_uac2_chip *uac2 = &agdev->uac2;
+ struct f_uac2 *uac2 = func_to_uac2(fn);
+ struct g_audio *agdev = func_to_g_audio(fn);
u16 w_index = le16_to_cpu(cr->wIndex);
u8 intf = w_index & 0xff;
- if (intf != agdev->ac_intf) {
- dev_err(&uac2->pdev.dev,
+ if (intf != uac2->ac_intf) {
+ dev_err(&agdev->gadget->dev,
"%s:%d Error!\n", __func__, __LINE__);
return -EOPNOTSUPP;
}
@@ -1411,8 +812,7 @@ static int
afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
struct usb_composite_dev *cdev = fn->config->cdev;
- struct audio_dev *agdev = func_to_agdev(fn);
- struct snd_uac2_chip *uac2 = &agdev->uac2;
+ struct g_audio *agdev = func_to_g_audio(fn);
struct usb_request *req = cdev->req;
u16 w_length = le16_to_cpu(cr->wLength);
int value = -EOPNOTSUPP;
@@ -1424,14 +824,15 @@ afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr)
if ((cr->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE)
value = setup_rq_inf(fn, cr);
else
- dev_err(&uac2->pdev.dev, "%s:%d Error!\n", __func__, __LINE__);
+ dev_err(&agdev->gadget->dev, "%s:%d Error!\n",
+ __func__, __LINE__);
if (value >= 0) {
req->length = value;
req->zero = value < w_length;
value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
if (value < 0) {
- dev_err(&uac2->pdev.dev,
+ dev_err(&agdev->gadget->dev,
"%s:%d Error!\n", __func__, __LINE__);
req->status = 0;
}
@@ -1557,10 +958,10 @@ static struct usb_function_instance *afunc_alloc_inst(void)
static void afunc_free(struct usb_function *f)
{
- struct audio_dev *agdev;
+ struct g_audio *agdev;
struct f_uac2_opts *opts;
- agdev = func_to_agdev(f);
+ agdev = func_to_g_audio(f);
opts = container_of(f->fi, struct f_uac2_opts, func_inst);
kfree(agdev);
mutex_lock(&opts->lock);
@@ -1570,27 +971,21 @@ static void afunc_free(struct usb_function *f)
static void afunc_unbind(struct usb_configuration *c, struct usb_function *f)
{
- struct audio_dev *agdev = func_to_agdev(f);
- struct uac2_rtd_params *prm;
+ struct g_audio *agdev = func_to_g_audio(f);
- alsa_uac2_exit(agdev);
-
- prm = &agdev->uac2.p_prm;
- kfree(prm->rbuf);
-
- prm = &agdev->uac2.c_prm;
- kfree(prm->rbuf);
- kfree(prm->ureq);
+ g_audio_cleanup(agdev);
usb_free_all_descriptors(f);
+
+ agdev->gadget = NULL;
}
static struct usb_function *afunc_alloc(struct usb_function_instance *fi)
{
- struct audio_dev *agdev;
+ struct f_uac2 *uac2;
struct f_uac2_opts *opts;
- agdev = kzalloc(sizeof(*agdev), GFP_KERNEL);
- if (agdev == NULL)
+ uac2 = kzalloc(sizeof(*uac2), GFP_KERNEL);
+ if (uac2 == NULL)
return ERR_PTR(-ENOMEM);
opts = container_of(fi, struct f_uac2_opts, func_inst);
@@ -1598,16 +993,16 @@ static struct usb_function *afunc_alloc(struct usb_function_instance *fi)
++opts->refcnt;
mutex_unlock(&opts->lock);
- agdev->func.name = "uac2_func";
- agdev->func.bind = afunc_bind;
- agdev->func.unbind = afunc_unbind;
- agdev->func.set_alt = afunc_set_alt;
- agdev->func.get_alt = afunc_get_alt;
- agdev->func.disable = afunc_disable;
- agdev->func.setup = afunc_setup;
- agdev->func.free_func = afunc_free;
+ uac2->g_audio.func.name = "uac2_func";
+ uac2->g_audio.func.bind = afunc_bind;
+ uac2->g_audio.func.unbind = afunc_unbind;
+ uac2->g_audio.func.set_alt = afunc_set_alt;
+ uac2->g_audio.func.get_alt = afunc_get_alt;
+ uac2->g_audio.func.disable = afunc_disable;
+ uac2->g_audio.func.setup = afunc_setup;
+ uac2->g_audio.func.free_func = afunc_free;
- return &agdev->func;
+ return &uac2->g_audio.func;
}
DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc);
diff --git a/drivers/usb/gadget/function/storage_common.h b/drivers/usb/gadget/function/storage_common.h
index e69848994cb4..e0814a960132 100644
--- a/drivers/usb/gadget/function/storage_common.h
+++ b/drivers/usb/gadget/function/storage_common.h
@@ -133,9 +133,10 @@ static inline bool fsg_lun_is_open(struct fsg_lun *curlun)
#define FSG_MAX_LUNS 16
enum fsg_buffer_state {
+ BUF_STATE_SENDING = -2,
+ BUF_STATE_RECEIVING,
BUF_STATE_EMPTY = 0,
- BUF_STATE_FULL,
- BUF_STATE_BUSY
+ BUF_STATE_FULL
};
struct fsg_buffhd {
@@ -151,23 +152,14 @@ struct fsg_buffhd {
unsigned int bulk_out_intended_length;
struct usb_request *inreq;
- int inreq_busy;
struct usb_request *outreq;
- int outreq_busy;
};
enum fsg_state {
- /* This one isn't used anywhere */
- FSG_STATE_COMMAND_PHASE = -10,
- FSG_STATE_DATA_PHASE,
- FSG_STATE_STATUS_PHASE,
-
- FSG_STATE_IDLE = 0,
+ FSG_STATE_NORMAL,
FSG_STATE_ABORT_BULK_OUT,
- FSG_STATE_RESET,
- FSG_STATE_INTERFACE_CHANGE,
+ FSG_STATE_PROTOCOL_RESET,
FSG_STATE_CONFIG_CHANGE,
- FSG_STATE_DISCONNECT,
FSG_STATE_EXIT,
FSG_STATE_TERMINATED
};
diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c
new file mode 100644
index 000000000000..5dd73b9e5172
--- /dev/null
+++ b/drivers/usb/gadget/function/u_audio.c
@@ -0,0 +1,662 @@
+/*
+ * u_audio.c -- interface to USB gadget "ALSA sound card" utilities
+ *
+ * Copyright (C) 2016
+ * Author: Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ *
+ * Sound card implementation was cut-and-pasted with changes
+ * from f_uac2.c and has:
+ * Copyright (C) 2011
+ * Yadwinder Singh (yadi.brar01@gmail.com)
+ * Jaswinder Singh (jaswinder.singh@linaro.org)
+ *
+ * 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "u_audio.h"
+
+#define BUFF_SIZE_MAX (PAGE_SIZE * 16)
+#define PRD_SIZE_MAX PAGE_SIZE
+#define MIN_PERIODS 4
+
+struct uac_req {
+ struct uac_rtd_params *pp; /* parent param */
+ struct usb_request *req;
+};
+
+/* Runtime data params for one stream */
+struct uac_rtd_params {
+ struct snd_uac_chip *uac; /* parent chip */
+ bool ep_enabled; /* if the ep is enabled */
+ /* Size of the ring buffer */
+ size_t dma_bytes;
+ unsigned char *dma_area;
+
+ struct snd_pcm_substream *ss;
+
+ /* Ring buffer */
+ ssize_t hw_ptr;
+
+ void *rbuf;
+
+ size_t period_size;
+
+ unsigned max_psize; /* MaxPacketSize of endpoint */
+ struct uac_req *ureq;
+
+ spinlock_t lock;
+};
+
+struct snd_uac_chip {
+ struct g_audio *audio_dev;
+
+ struct uac_rtd_params p_prm;
+ struct uac_rtd_params c_prm;
+
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+
+ /* timekeeping for the playback endpoint */
+ unsigned int p_interval;
+ unsigned int p_residue;
+
+ /* pre-calculated values for playback iso completion */
+ unsigned int p_pktsize;
+ unsigned int p_pktsize_residue;
+ unsigned int p_framesize;
+};
+
+static struct snd_pcm_hardware uac_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER
+ | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
+ | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX,
+ .buffer_bytes_max = BUFF_SIZE_MAX,
+ .period_bytes_max = PRD_SIZE_MAX,
+ .periods_min = MIN_PERIODS,
+};
+
+static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ unsigned pending;
+ unsigned long flags;
+ unsigned int hw_ptr;
+ bool update_alsa = false;
+ int status = req->status;
+ struct uac_req *ur = req->context;
+ struct snd_pcm_substream *substream;
+ struct uac_rtd_params *prm = ur->pp;
+ struct snd_uac_chip *uac = prm->uac;
+
+ /* i/f shutting down */
+ if (!prm->ep_enabled || req->status == -ESHUTDOWN)
+ return;
+
+ /*
+ * We can't really do much about bad xfers.
+ * Afterall, the ISOCH xfers could fail legitimately.
+ */
+ if (status)
+ pr_debug("%s: iso_complete status(%d) %d/%d\n",
+ __func__, status, req->actual, req->length);
+
+ substream = prm->ss;
+
+ /* Do nothing if ALSA isn't active */
+ if (!substream)
+ goto exit;
+
+ spin_lock_irqsave(&prm->lock, flags);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /*
+ * For each IN packet, take the quotient of the current data
+ * rate and the endpoint's interval as the base packet size.
+ * If there is a residue from this division, add it to the
+ * residue accumulator.
+ */
+ req->length = uac->p_pktsize;
+ uac->p_residue += uac->p_pktsize_residue;
+
+ /*
+ * Whenever there are more bytes in the accumulator than we
+ * need to add one more sample frame, increase this packet's
+ * size and decrease the accumulator.
+ */
+ if (uac->p_residue / uac->p_interval >= uac->p_framesize) {
+ req->length += uac->p_framesize;
+ uac->p_residue -= uac->p_framesize *
+ uac->p_interval;
+ }
+
+ req->actual = req->length;
+ }
+
+ pending = prm->hw_ptr % prm->period_size;
+ pending += req->actual;
+ if (pending >= prm->period_size)
+ update_alsa = true;
+
+ hw_ptr = prm->hw_ptr;
+ prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes;
+
+ spin_unlock_irqrestore(&prm->lock, flags);
+
+ /* Pack USB load in ALSA ring buffer */
+ pending = prm->dma_bytes - hw_ptr;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (unlikely(pending < req->actual)) {
+ memcpy(req->buf, prm->dma_area + hw_ptr, pending);
+ memcpy(req->buf + pending, prm->dma_area,
+ req->actual - pending);
+ } else {
+ memcpy(req->buf, prm->dma_area + hw_ptr, req->actual);
+ }
+ } else {
+ if (unlikely(pending < req->actual)) {
+ memcpy(prm->dma_area + hw_ptr, req->buf, pending);
+ memcpy(prm->dma_area, req->buf + pending,
+ req->actual - pending);
+ } else {
+ memcpy(prm->dma_area + hw_ptr, req->buf, req->actual);
+ }
+ }
+
+exit:
+ if (usb_ep_queue(ep, req, GFP_ATOMIC))
+ dev_err(uac->card->dev, "%d Error!\n", __LINE__);
+
+ if (update_alsa)
+ snd_pcm_period_elapsed(substream);
+}
+
+static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
+ struct uac_rtd_params *prm;
+ struct g_audio *audio_dev;
+ struct uac_params *params;
+ unsigned long flags;
+ int err = 0;
+
+ audio_dev = uac->audio_dev;
+ params = &audio_dev->params;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ prm = &uac->p_prm;
+ else
+ prm = &uac->c_prm;
+
+ spin_lock_irqsave(&prm->lock, flags);
+
+ /* Reset */
+ prm->hw_ptr = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ prm->ss = substream;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ prm->ss = NULL;
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ spin_unlock_irqrestore(&prm->lock, flags);
+
+ /* Clear buffer after Play stops */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss)
+ memset(prm->rbuf, 0, prm->max_psize * params->req_number);
+
+ return err;
+}
+
+static snd_pcm_uframes_t uac_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
+ struct uac_rtd_params *prm;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ prm = &uac->p_prm;
+ else
+ prm = &uac->c_prm;
+
+ return bytes_to_frames(substream->runtime, prm->hw_ptr);
+}
+
+static int uac_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
+ struct uac_rtd_params *prm;
+ int err;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ prm = &uac->p_prm;
+ else
+ prm = &uac->c_prm;
+
+ err = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ if (err >= 0) {
+ prm->dma_bytes = substream->runtime->dma_bytes;
+ prm->dma_area = substream->runtime->dma_area;
+ prm->period_size = params_period_bytes(hw_params);
+ }
+
+ return err;
+}
+
+static int uac_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
+ struct uac_rtd_params *prm;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ prm = &uac->p_prm;
+ else
+ prm = &uac->c_prm;
+
+ prm->dma_area = NULL;
+ prm->dma_bytes = 0;
+ prm->period_size = 0;
+
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int uac_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct g_audio *audio_dev;
+ struct uac_params *params;
+ int p_ssize, c_ssize;
+ int p_srate, c_srate;
+ int p_chmask, c_chmask;
+
+ audio_dev = uac->audio_dev;
+ params = &audio_dev->params;
+ p_ssize = params->p_ssize;
+ c_ssize = params->c_ssize;
+ p_srate = params->p_srate;
+ c_srate = params->c_srate;
+ p_chmask = params->p_chmask;
+ c_chmask = params->c_chmask;
+ uac->p_residue = 0;
+
+ runtime->hw = uac_pcm_hardware;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ spin_lock_init(&uac->p_prm.lock);
+ runtime->hw.rate_min = p_srate;
+ switch (p_ssize) {
+ case 3:
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE;
+ break;
+ case 4:
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+ break;
+ default:
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ break;
+ }
+ runtime->hw.channels_min = num_channels(p_chmask);
+ runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize
+ / runtime->hw.periods_min;
+ } else {
+ spin_lock_init(&uac->c_prm.lock);
+ runtime->hw.rate_min = c_srate;
+ switch (c_ssize) {
+ case 3:
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE;
+ break;
+ case 4:
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+ break;
+ default:
+ runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+ break;
+ }
+ runtime->hw.channels_min = num_channels(c_chmask);
+ runtime->hw.period_bytes_min = 2 * uac->c_prm.max_psize
+ / runtime->hw.periods_min;
+ }
+
+ runtime->hw.rate_max = runtime->hw.rate_min;
+ runtime->hw.channels_max = runtime->hw.channels_min;
+
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+ return 0;
+}
+
+/* ALSA cries without these function pointers */
+static int uac_pcm_null(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static struct snd_pcm_ops uac_pcm_ops = {
+ .open = uac_pcm_open,
+ .close = uac_pcm_null,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = uac_pcm_hw_params,
+ .hw_free = uac_pcm_hw_free,
+ .trigger = uac_pcm_trigger,
+ .pointer = uac_pcm_pointer,
+ .prepare = uac_pcm_null,
+};
+
+static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep)
+{
+ struct snd_uac_chip *uac = prm->uac;
+ struct g_audio *audio_dev;
+ struct uac_params *params;
+ int i;
+
+ if (!prm->ep_enabled)
+ return;
+
+ prm->ep_enabled = false;
+
+ audio_dev = uac->audio_dev;
+ params = &audio_dev->params;
+
+ for (i = 0; i < params->req_number; i++) {
+ if (prm->ureq[i].req) {
+ usb_ep_dequeue(ep, prm->ureq[i].req);
+ usb_ep_free_request(ep, prm->ureq[i].req);
+ prm->ureq[i].req = NULL;
+ }
+ }
+
+ if (usb_ep_disable(ep))
+ dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__);
+}
+
+
+int u_audio_start_capture(struct g_audio *audio_dev)
+{
+ struct snd_uac_chip *uac = audio_dev->uac;
+ struct usb_gadget *gadget = audio_dev->gadget;
+ struct device *dev = &gadget->dev;
+ struct usb_request *req;
+ struct usb_ep *ep;
+ struct uac_rtd_params *prm;
+ struct uac_params *params = &audio_dev->params;
+ int req_len, i;
+
+ ep = audio_dev->out_ep;
+ prm = &uac->c_prm;
+ config_ep_by_speed(gadget, &audio_dev->func, ep);
+ req_len = prm->max_psize;
+
+ prm->ep_enabled = true;
+ usb_ep_enable(ep);
+
+ for (i = 0; i < params->req_number; i++) {
+ if (!prm->ureq[i].req) {
+ req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+ if (req == NULL)
+ return -ENOMEM;
+
+ prm->ureq[i].req = req;
+ prm->ureq[i].pp = prm;
+
+ req->zero = 0;
+ req->context = &prm->ureq[i];
+ req->length = req_len;
+ req->complete = u_audio_iso_complete;
+ req->buf = prm->rbuf + i * prm->max_psize;
+ }
+
+ if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_start_capture);
+
+void u_audio_stop_capture(struct g_audio *audio_dev)
+{
+ struct snd_uac_chip *uac = audio_dev->uac;
+
+ free_ep(&uac->c_prm, audio_dev->out_ep);
+}
+EXPORT_SYMBOL_GPL(u_audio_stop_capture);
+
+int u_audio_start_playback(struct g_audio *audio_dev)
+{
+ struct snd_uac_chip *uac = audio_dev->uac;
+ struct usb_gadget *gadget = audio_dev->gadget;
+ struct device *dev = &gadget->dev;
+ struct usb_request *req;
+ struct usb_ep *ep;
+ struct uac_rtd_params *prm;
+ struct uac_params *params = &audio_dev->params;
+ unsigned int factor, rate;
+ const struct usb_endpoint_descriptor *ep_desc;
+ int req_len, i;
+
+ ep = audio_dev->in_ep;
+ prm = &uac->p_prm;
+ config_ep_by_speed(gadget, &audio_dev->func, ep);
+
+ ep_desc = ep->desc;
+
+ /* pre-calculate the playback endpoint's interval */
+ if (gadget->speed == USB_SPEED_FULL)
+ factor = 1000;
+ else
+ factor = 8000;
+
+ /* pre-compute some values for iso_complete() */
+ uac->p_framesize = params->p_ssize *
+ num_channels(params->p_chmask);
+ rate = params->p_srate * uac->p_framesize;
+ uac->p_interval = factor / (1 << (ep_desc->bInterval - 1));
+ uac->p_pktsize = min_t(unsigned int, rate / uac->p_interval,
+ prm->max_psize);
+
+ if (uac->p_pktsize < prm->max_psize)
+ uac->p_pktsize_residue = rate % uac->p_interval;
+ else
+ uac->p_pktsize_residue = 0;
+
+ req_len = uac->p_pktsize;
+ uac->p_residue = 0;
+
+ prm->ep_enabled = true;
+ usb_ep_enable(ep);
+
+ for (i = 0; i < params->req_number; i++) {
+ if (!prm->ureq[i].req) {
+ req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+ if (req == NULL)
+ return -ENOMEM;
+
+ prm->ureq[i].req = req;
+ prm->ureq[i].pp = prm;
+
+ req->zero = 0;
+ req->context = &prm->ureq[i];
+ req->length = req_len;
+ req->complete = u_audio_iso_complete;
+ req->buf = prm->rbuf + i * prm->max_psize;
+ }
+
+ if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_start_playback);
+
+void u_audio_stop_playback(struct g_audio *audio_dev)
+{
+ struct snd_uac_chip *uac = audio_dev->uac;
+
+ free_ep(&uac->p_prm, audio_dev->in_ep);
+}
+EXPORT_SYMBOL_GPL(u_audio_stop_playback);
+
+int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
+ const char *card_name)
+{
+ struct snd_uac_chip *uac;
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ struct uac_params *params;
+ int p_chmask, c_chmask;
+ int err;
+
+ if (!g_audio)
+ return -EINVAL;
+
+ uac = kzalloc(sizeof(*uac), GFP_KERNEL);
+ if (!uac)
+ return -ENOMEM;
+ g_audio->uac = uac;
+ uac->audio_dev = g_audio;
+
+ params = &g_audio->params;
+ p_chmask = params->p_chmask;
+ c_chmask = params->c_chmask;
+
+ if (c_chmask) {
+ struct uac_rtd_params *prm = &uac->c_prm;
+
+ uac->c_prm.uac = uac;
+ prm->max_psize = g_audio->out_ep_maxpsize;
+
+ prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req),
+ GFP_KERNEL);
+ if (!prm->ureq) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ prm->rbuf = kcalloc(params->req_number, prm->max_psize,
+ GFP_KERNEL);
+ if (!prm->rbuf) {
+ prm->max_psize = 0;
+ err = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (p_chmask) {
+ struct uac_rtd_params *prm = &uac->p_prm;
+
+ uac->p_prm.uac = uac;
+ prm->max_psize = g_audio->in_ep_maxpsize;
+
+ prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req),
+ GFP_KERNEL);
+ if (!prm->ureq) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ prm->rbuf = kcalloc(params->req_number, prm->max_psize,
+ GFP_KERNEL);
+ if (!prm->rbuf) {
+ prm->max_psize = 0;
+ err = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ /* Choose any slot, with no id */
+ err = snd_card_new(&g_audio->gadget->dev,
+ -1, NULL, THIS_MODULE, 0, &card);
+ if (err < 0)
+ goto fail;
+
+ uac->card = card;
+
+ /*
+ * Create first PCM device
+ * Create a substream only for non-zero channel streams
+ */
+ err = snd_pcm_new(uac->card, pcm_name, 0,
+ p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm);
+ if (err < 0)
+ goto snd_fail;
+
+ strcpy(pcm->name, pcm_name);
+ pcm->private_data = uac;
+ uac->pcm = pcm;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops);
+
+ strcpy(card->driver, card_name);
+ strcpy(card->shortname, card_name);
+ sprintf(card->longname, "%s %i", card_name, card->dev->id);
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
+
+ err = snd_card_register(card);
+
+ if (!err)
+ return 0;
+
+snd_fail:
+ snd_card_free(card);
+fail:
+ kfree(uac->p_prm.ureq);
+ kfree(uac->c_prm.ureq);
+ kfree(uac->p_prm.rbuf);
+ kfree(uac->c_prm.rbuf);
+ kfree(uac);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(g_audio_setup);
+
+void g_audio_cleanup(struct g_audio *g_audio)
+{
+ struct snd_uac_chip *uac;
+ struct snd_card *card;
+
+ if (!g_audio || !g_audio->uac)
+ return;
+
+ uac = g_audio->uac;
+ card = uac->card;
+ if (card)
+ snd_card_free(card);
+
+ kfree(uac->p_prm.ureq);
+ kfree(uac->c_prm.ureq);
+ kfree(uac->p_prm.rbuf);
+ kfree(uac->c_prm.rbuf);
+ kfree(uac);
+}
+EXPORT_SYMBOL_GPL(g_audio_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("USB gadget \"ALSA sound card\" utilities");
+MODULE_AUTHOR("Ruslan Bilovol");
diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h
new file mode 100644
index 000000000000..07e13784cbb8
--- /dev/null
+++ b/drivers/usb/gadget/function/u_audio.h
@@ -0,0 +1,95 @@
+/*
+ * u_audio.h -- interface to USB gadget "ALSA sound card" utilities
+ *
+ * Copyright (C) 2016
+ * Author: Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ *
+ * 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __U_AUDIO_H
+#define __U_AUDIO_H
+
+#include <linux/usb/composite.h>
+
+struct uac_params {
+ /* playback */
+ int p_chmask; /* channel mask */
+ int p_srate; /* rate in Hz */
+ int p_ssize; /* sample size */
+
+ /* capture */
+ int c_chmask; /* channel mask */
+ int c_srate; /* rate in Hz */
+ int c_ssize; /* sample size */
+
+ int req_number; /* number of preallocated requests */
+};
+
+struct g_audio {
+ struct usb_function func;
+ struct usb_gadget *gadget;
+
+ struct usb_ep *in_ep;
+ struct usb_ep *out_ep;
+
+ /* Max packet size for all in_ep possible speeds */
+ unsigned int in_ep_maxpsize;
+ /* Max packet size for all out_ep possible speeds */
+ unsigned int out_ep_maxpsize;
+
+ /* The ALSA Sound Card it represents on the USB-Client side */
+ struct snd_uac_chip *uac;
+
+ struct uac_params params;
+};
+
+static inline struct g_audio *func_to_g_audio(struct usb_function *f)
+{
+ return container_of(f, struct g_audio, func);
+}
+
+static inline uint num_channels(uint chanmask)
+{
+ uint num = 0;
+
+ while (chanmask) {
+ num += (chanmask & 1);
+ chanmask >>= 1;
+ }
+
+ return num;
+}
+
+/*
+ * g_audio_setup - initialize one virtual ALSA sound card
+ * @g_audio: struct with filled params, in_ep_maxpsize, out_ep_maxpsize
+ * @pcm_name: the id string for a PCM instance of this sound card
+ * @card_name: name of this soundcard
+ *
+ * This sets up the single virtual ALSA sound card that may be exported by a
+ * gadget driver using this framework.
+ *
+ * Context: may sleep
+ *
+ * Returns zero on success, or a negative error on failure.
+ */
+int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
+ const char *card_name);
+void g_audio_cleanup(struct g_audio *g_audio);
+
+int u_audio_start_capture(struct g_audio *g_audio);
+void u_audio_stop_capture(struct g_audio *g_audio);
+int u_audio_start_playback(struct g_audio *g_audio);
+void u_audio_stop_playback(struct g_audio *g_audio);
+
+#endif /* __U_AUDIO_H */
diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h
index 4378cc2fcac3..540f1c48c1a8 100644
--- a/drivers/usb/gadget/function/u_fs.h
+++ b/drivers/usb/gadget/function/u_fs.h
@@ -216,6 +216,9 @@ struct ffs_data {
#define FFS_FL_CALL_CLOSED_CALLBACK 0
#define FFS_FL_BOUND 1
+ /* For waking up blocked threads when function is enabled. */
+ wait_queue_head_t wait;
+
/* Active function */
struct ffs_function *func;
diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h
index 5c2ac8e8456d..6f188fd8633f 100644
--- a/drivers/usb/gadget/function/u_uac1.h
+++ b/drivers/usb/gadget/function/u_uac1.h
@@ -1,82 +1,41 @@
/*
- * u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities
+ * u_uac1.h - Utility definitions for UAC1 function
*
- * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
- * Copyright (C) 2008 Analog Devices, Inc
+ * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
*
- * Enter bugs at http://blackfin.uclinux.org/
- *
- * Licensed under the GPL-2 or later.
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
*/
-#ifndef __U_AUDIO_H
-#define __U_AUDIO_H
+#ifndef __U_UAC1_H
+#define __U_UAC1_H
-#include <linux/device.h>
-#include <linux/err.h>
-#include <linux/usb/audio.h>
#include <linux/usb/composite.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
-
-#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p"
-#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c"
-#define FILE_CONTROL "/dev/snd/controlC0"
-
#define UAC1_OUT_EP_MAX_PACKET_SIZE 200
-#define UAC1_REQ_COUNT 256
-#define UAC1_AUDIO_BUF_SIZE 48000
-
-/*
- * This represents the USB side of an audio card device, managed by a USB
- * function which provides control and stream interfaces.
- */
-
-struct gaudio_snd_dev {
- struct gaudio *card;
- struct file *filp;
- struct snd_pcm_substream *substream;
- int access;
- int format;
- int channels;
- int rate;
-};
-
-struct gaudio {
- struct usb_function func;
- struct usb_gadget *gadget;
+#define UAC1_DEF_CCHMASK 0x3
+#define UAC1_DEF_CSRATE 48000
+#define UAC1_DEF_CSSIZE 2
+#define UAC1_DEF_PCHMASK 0x3
+#define UAC1_DEF_PSRATE 48000
+#define UAC1_DEF_PSSIZE 2
+#define UAC1_DEF_REQ_NUM 2
- /* ALSA sound device interfaces */
- struct gaudio_snd_dev control;
- struct gaudio_snd_dev playback;
- struct gaudio_snd_dev capture;
-
- /* TODO */
-};
struct f_uac1_opts {
struct usb_function_instance func_inst;
- int req_buf_size;
- int req_count;
- int audio_buf_size;
- char *fn_play;
- char *fn_cap;
- char *fn_cntl;
+ int c_chmask;
+ int c_srate;
+ int c_ssize;
+ int p_chmask;
+ int p_srate;
+ int p_ssize;
+ int req_number;
unsigned bound:1;
- unsigned fn_play_alloc:1;
- unsigned fn_cap_alloc:1;
- unsigned fn_cntl_alloc:1;
+
struct mutex lock;
int refcnt;
};
-int gaudio_setup(struct gaudio *card);
-void gaudio_cleanup(struct gaudio *the_card);
-
-size_t u_audio_playback(struct gaudio *card, void *buf, size_t count);
-int u_audio_get_playback_channels(struct gaudio *card);
-int u_audio_get_playback_rate(struct gaudio *card);
-
-#endif /* __U_AUDIO_H */
+#endif /* __U_UAC1_H */
diff --git a/drivers/usb/gadget/function/u_uac1.c b/drivers/usb/gadget/function/u_uac1_legacy.c
index c78c84138a28..8aa76b4dc117 100644
--- a/drivers/usb/gadget/function/u_uac1.c
+++ b/drivers/usb/gadget/function/u_uac1_legacy.c
@@ -18,7 +18,7 @@
#include <linux/random.h>
#include <linux/syscalls.h>
-#include "u_uac1.h"
+#include "u_uac1_legacy.h"
/*
* This component encapsulates the ALSA devices for USB audio gadget
@@ -205,10 +205,11 @@ static int gaudio_open_snd_dev(struct gaudio *card)
{
struct snd_pcm_file *pcm_file;
struct gaudio_snd_dev *snd;
- struct f_uac1_opts *opts;
+ struct f_uac1_legacy_opts *opts;
char *fn_play, *fn_cap, *fn_cntl;
- opts = container_of(card->func.fi, struct f_uac1_opts, func_inst);
+ opts = container_of(card->func.fi, struct f_uac1_legacy_opts,
+ func_inst);
fn_play = opts->fn_play;
fn_cap = opts->fn_cap;
fn_cntl = opts->fn_cntl;
diff --git a/drivers/usb/gadget/function/u_uac1_legacy.h b/drivers/usb/gadget/function/u_uac1_legacy.h
new file mode 100644
index 000000000000..d715b1af56a4
--- /dev/null
+++ b/drivers/usb/gadget/function/u_uac1_legacy.h
@@ -0,0 +1,82 @@
+/*
+ * u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities
+ *
+ * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ * Copyright (C) 2008 Analog Devices, Inc
+ *
+ * Enter bugs at http://blackfin.uclinux.org/
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef __U_UAC1_LEGACY_H
+#define __U_UAC1_LEGACY_H
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/usb/audio.h>
+#include <linux/usb/composite.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p"
+#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c"
+#define FILE_CONTROL "/dev/snd/controlC0"
+
+#define UAC1_OUT_EP_MAX_PACKET_SIZE 200
+#define UAC1_REQ_COUNT 256
+#define UAC1_AUDIO_BUF_SIZE 48000
+
+/*
+ * This represents the USB side of an audio card device, managed by a USB
+ * function which provides control and stream interfaces.
+ */
+
+struct gaudio_snd_dev {
+ struct gaudio *card;
+ struct file *filp;
+ struct snd_pcm_substream *substream;
+ int access;
+ int format;
+ int channels;
+ int rate;
+};
+
+struct gaudio {
+ struct usb_function func;
+ struct usb_gadget *gadget;
+
+ /* ALSA sound device interfaces */
+ struct gaudio_snd_dev control;
+ struct gaudio_snd_dev playback;
+ struct gaudio_snd_dev capture;
+
+ /* TODO */
+};
+
+struct f_uac1_legacy_opts {
+ struct usb_function_instance func_inst;
+ int req_buf_size;
+ int req_count;
+ int audio_buf_size;
+ char *fn_play;
+ char *fn_cap;
+ char *fn_cntl;
+ unsigned bound:1;
+ unsigned fn_play_alloc:1;
+ unsigned fn_cap_alloc:1;
+ unsigned fn_cntl_alloc:1;
+ struct mutex lock;
+ int refcnt;
+};
+
+int gaudio_setup(struct gaudio *card);
+void gaudio_cleanup(struct gaudio *the_card);
+
+size_t u_audio_playback(struct gaudio *card, void *buf, size_t count);
+int u_audio_get_playback_channels(struct gaudio *card);
+int u_audio_get_playback_rate(struct gaudio *card);
+
+#endif /* __U_UAC1_LEGACY_H */