mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
307 lines
7.1 KiB
307 lines
7.1 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
/* |
|
* u_uac1.c -- ALSA audio utilities for Gadget stack |
|
* |
|
* Copyright (C) 2008 Bryan Wu <[email protected]> |
|
* Copyright (C) 2008 Analog Devices, Inc |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/slab.h> |
|
#include <linux/device.h> |
|
#include <linux/delay.h> |
|
#include <linux/ctype.h> |
|
#include <linux/random.h> |
|
#include <linux/syscalls.h> |
|
|
|
#include "u_uac1_legacy.h" |
|
|
|
/* |
|
* This component encapsulates the ALSA devices for USB audio gadget |
|
*/ |
|
|
|
/*-------------------------------------------------------------------------*/ |
|
|
|
/* |
|
* Some ALSA internal helper functions |
|
*/ |
|
static int snd_interval_refine_set(struct snd_interval *i, unsigned int val) |
|
{ |
|
struct snd_interval t; |
|
t.empty = 0; |
|
t.min = t.max = val; |
|
t.openmin = t.openmax = 0; |
|
t.integer = 1; |
|
return snd_interval_refine(i, &t); |
|
} |
|
|
|
static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params, |
|
snd_pcm_hw_param_t var, unsigned int val, |
|
int dir) |
|
{ |
|
int changed; |
|
if (hw_is_mask(var)) { |
|
struct snd_mask *m = hw_param_mask(params, var); |
|
if (val == 0 && dir < 0) { |
|
changed = -EINVAL; |
|
snd_mask_none(m); |
|
} else { |
|
if (dir > 0) |
|
val++; |
|
else if (dir < 0) |
|
val--; |
|
changed = snd_mask_refine_set( |
|
hw_param_mask(params, var), val); |
|
} |
|
} else if (hw_is_interval(var)) { |
|
struct snd_interval *i = hw_param_interval(params, var); |
|
if (val == 0 && dir < 0) { |
|
changed = -EINVAL; |
|
snd_interval_none(i); |
|
} else if (dir == 0) |
|
changed = snd_interval_refine_set(i, val); |
|
else { |
|
struct snd_interval t; |
|
t.openmin = 1; |
|
t.openmax = 1; |
|
t.empty = 0; |
|
t.integer = 0; |
|
if (dir < 0) { |
|
t.min = val - 1; |
|
t.max = val; |
|
} else { |
|
t.min = val; |
|
t.max = val+1; |
|
} |
|
changed = snd_interval_refine(i, &t); |
|
} |
|
} else |
|
return -EINVAL; |
|
if (changed) { |
|
params->cmask |= 1 << var; |
|
params->rmask |= 1 << var; |
|
} |
|
return changed; |
|
} |
|
/*-------------------------------------------------------------------------*/ |
|
|
|
/* |
|
* Set default hardware params |
|
*/ |
|
static int playback_default_hw_params(struct gaudio_snd_dev *snd) |
|
{ |
|
struct snd_pcm_substream *substream = snd->substream; |
|
struct snd_pcm_hw_params *params; |
|
snd_pcm_sframes_t result; |
|
|
|
/* |
|
* SNDRV_PCM_ACCESS_RW_INTERLEAVED, |
|
* SNDRV_PCM_FORMAT_S16_LE |
|
* CHANNELS: 2 |
|
* RATE: 48000 |
|
*/ |
|
snd->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; |
|
snd->format = SNDRV_PCM_FORMAT_S16_LE; |
|
snd->channels = 2; |
|
snd->rate = 48000; |
|
|
|
params = kzalloc(sizeof(*params), GFP_KERNEL); |
|
if (!params) |
|
return -ENOMEM; |
|
|
|
_snd_pcm_hw_params_any(params); |
|
_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, |
|
snd->access, 0); |
|
_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, |
|
snd->format, 0); |
|
_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, |
|
snd->channels, 0); |
|
_snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, |
|
snd->rate, 0); |
|
|
|
snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); |
|
snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params); |
|
|
|
result = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL); |
|
if (result < 0) { |
|
ERROR(snd->card, |
|
"Preparing sound card failed: %d\n", (int)result); |
|
kfree(params); |
|
return result; |
|
} |
|
|
|
/* Store the hardware parameters */ |
|
snd->access = params_access(params); |
|
snd->format = params_format(params); |
|
snd->channels = params_channels(params); |
|
snd->rate = params_rate(params); |
|
|
|
kfree(params); |
|
|
|
INFO(snd->card, |
|
"Hardware params: access %x, format %x, channels %d, rate %d\n", |
|
snd->access, snd->format, snd->channels, snd->rate); |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* Playback audio buffer data by ALSA PCM device |
|
*/ |
|
size_t u_audio_playback(struct gaudio *card, void *buf, size_t count) |
|
{ |
|
struct gaudio_snd_dev *snd = &card->playback; |
|
struct snd_pcm_substream *substream = snd->substream; |
|
struct snd_pcm_runtime *runtime = substream->runtime; |
|
ssize_t result; |
|
snd_pcm_sframes_t frames; |
|
|
|
try_again: |
|
if (runtime->status->state == SNDRV_PCM_STATE_XRUN || |
|
runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { |
|
result = snd_pcm_kernel_ioctl(substream, |
|
SNDRV_PCM_IOCTL_PREPARE, NULL); |
|
if (result < 0) { |
|
ERROR(card, "Preparing sound card failed: %d\n", |
|
(int)result); |
|
return result; |
|
} |
|
} |
|
|
|
frames = bytes_to_frames(runtime, count); |
|
result = snd_pcm_kernel_write(snd->substream, buf, frames); |
|
if (result != frames) { |
|
ERROR(card, "Playback error: %d\n", (int)result); |
|
goto try_again; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int u_audio_get_playback_channels(struct gaudio *card) |
|
{ |
|
return card->playback.channels; |
|
} |
|
|
|
int u_audio_get_playback_rate(struct gaudio *card) |
|
{ |
|
return card->playback.rate; |
|
} |
|
|
|
/* |
|
* Open ALSA PCM and control device files |
|
* Initial the PCM or control device |
|
*/ |
|
static int gaudio_open_snd_dev(struct gaudio *card) |
|
{ |
|
struct snd_pcm_file *pcm_file; |
|
struct gaudio_snd_dev *snd; |
|
struct f_uac1_legacy_opts *opts; |
|
char *fn_play, *fn_cap, *fn_cntl; |
|
|
|
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; |
|
|
|
/* Open control device */ |
|
snd = &card->control; |
|
snd->filp = filp_open(fn_cntl, O_RDWR, 0); |
|
if (IS_ERR(snd->filp)) { |
|
int ret = PTR_ERR(snd->filp); |
|
ERROR(card, "unable to open sound control device file: %s\n", |
|
fn_cntl); |
|
snd->filp = NULL; |
|
return ret; |
|
} |
|
snd->card = card; |
|
|
|
/* Open PCM playback device and setup substream */ |
|
snd = &card->playback; |
|
snd->filp = filp_open(fn_play, O_WRONLY, 0); |
|
if (IS_ERR(snd->filp)) { |
|
int ret = PTR_ERR(snd->filp); |
|
|
|
ERROR(card, "No such PCM playback device: %s\n", fn_play); |
|
snd->filp = NULL; |
|
return ret; |
|
} |
|
pcm_file = snd->filp->private_data; |
|
snd->substream = pcm_file->substream; |
|
snd->card = card; |
|
playback_default_hw_params(snd); |
|
|
|
/* Open PCM capture device and setup substream */ |
|
snd = &card->capture; |
|
snd->filp = filp_open(fn_cap, O_RDONLY, 0); |
|
if (IS_ERR(snd->filp)) { |
|
ERROR(card, "No such PCM capture device: %s\n", fn_cap); |
|
snd->substream = NULL; |
|
snd->card = NULL; |
|
snd->filp = NULL; |
|
} else { |
|
pcm_file = snd->filp->private_data; |
|
snd->substream = pcm_file->substream; |
|
snd->card = card; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* Close ALSA PCM and control device files |
|
*/ |
|
static int gaudio_close_snd_dev(struct gaudio *gau) |
|
{ |
|
struct gaudio_snd_dev *snd; |
|
|
|
/* Close control device */ |
|
snd = &gau->control; |
|
if (snd->filp) |
|
filp_close(snd->filp, NULL); |
|
|
|
/* Close PCM playback device and setup substream */ |
|
snd = &gau->playback; |
|
if (snd->filp) |
|
filp_close(snd->filp, NULL); |
|
|
|
/* Close PCM capture device and setup substream */ |
|
snd = &gau->capture; |
|
if (snd->filp) |
|
filp_close(snd->filp, NULL); |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* gaudio_setup - setup ALSA interface and preparing for USB transfer |
|
* |
|
* This sets up PCM, mixer or MIDI ALSA devices fore USB gadget using. |
|
* |
|
* Returns negative errno, or zero on success |
|
*/ |
|
int gaudio_setup(struct gaudio *card) |
|
{ |
|
int ret; |
|
|
|
ret = gaudio_open_snd_dev(card); |
|
if (ret) |
|
ERROR(card, "we need at least one control device\n"); |
|
|
|
return ret; |
|
|
|
} |
|
|
|
/* |
|
* gaudio_cleanup - remove ALSA device interface |
|
* |
|
* This is called to free all resources allocated by @gaudio_setup(). |
|
*/ |
|
void gaudio_cleanup(struct gaudio *the_card) |
|
{ |
|
if (the_card) |
|
gaudio_close_snd_dev(the_card); |
|
} |
|
|
|
|