Merge branch 'for-next' into for-linus
This commit is contained in:
commit
581abbaa03
|
@ -655,17 +655,6 @@ development branches in general while the development for the current
|
||||||
and next kernels are found in for-linus and for-next branches,
|
and next kernels are found in for-linus and for-next branches,
|
||||||
respectively.
|
respectively.
|
||||||
|
|
||||||
If you are using the latest Linus tree, it'd be better to pull the
|
|
||||||
above GIT tree onto it. If you are using the older kernels, an easy
|
|
||||||
way to try the latest ALSA code is to build from the snapshot
|
|
||||||
tarball. There are daily tarballs and the latest snapshot tarball.
|
|
||||||
All can be built just like normal alsa-driver release packages, that
|
|
||||||
is, installed via the usual spells: configure, make and make
|
|
||||||
install(-modules). See INSTALL in the package. The snapshot tarballs
|
|
||||||
are found at:
|
|
||||||
|
|
||||||
- ftp://ftp.suse.com/pub/people/tiwai/snapshot/
|
|
||||||
|
|
||||||
|
|
||||||
Sending a Bug Report
|
Sending a Bug Report
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -699,7 +688,12 @@ problems.
|
||||||
alsa-info
|
alsa-info
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
The script `alsa-info.sh` is a very useful tool to gather the audio
|
The script `alsa-info.sh` is a very useful tool to gather the audio
|
||||||
device information. You can fetch the latest version from:
|
device information. It's included in alsa-utils package. The latest
|
||||||
|
version can be found on git repository:
|
||||||
|
|
||||||
|
- git://git.alsa-project.org/alsa-utils.git
|
||||||
|
|
||||||
|
The script can be fetched directly from the following URL, too:
|
||||||
|
|
||||||
- http://www.alsa-project.org/alsa-info.sh
|
- http://www.alsa-project.org/alsa-info.sh
|
||||||
|
|
||||||
|
@ -836,15 +830,11 @@ can get a proc-file dump at the current state, get a list of control
|
||||||
(mixer) elements, set/get the control element value, simulate the PCM
|
(mixer) elements, set/get the control element value, simulate the PCM
|
||||||
operation, the jack plugging simulation, etc.
|
operation, the jack plugging simulation, etc.
|
||||||
|
|
||||||
The package is found in:
|
The program is found in the git repository below:
|
||||||
|
|
||||||
- ftp://ftp.suse.com/pub/people/tiwai/misc/
|
|
||||||
|
|
||||||
A git repository is available:
|
|
||||||
|
|
||||||
- git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/hda-emu.git
|
- git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/hda-emu.git
|
||||||
|
|
||||||
See README file in the tarball for more details about hda-emu
|
See README file in the repository for more details about hda-emu
|
||||||
program.
|
program.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ Gapless Playback
|
||||||
================
|
================
|
||||||
When playing thru an album, the decoders have the ability to skip the encoder
|
When playing thru an album, the decoders have the ability to skip the encoder
|
||||||
delay and padding and directly move from one track content to another. The end
|
delay and padding and directly move from one track content to another. The end
|
||||||
user can perceive this as gapless playback as we dont have silence while
|
user can perceive this as gapless playback as we don't have silence while
|
||||||
switching from one track to another
|
switching from one track to another
|
||||||
|
|
||||||
Also, there might be low-intensity noises due to encoding. Perfect gapless is
|
Also, there might be low-intensity noises due to encoding. Perfect gapless is
|
||||||
|
@ -184,7 +184,7 @@ Sequence flow for gapless would be:
|
||||||
- Fill data of the first track
|
- Fill data of the first track
|
||||||
- Trigger start
|
- Trigger start
|
||||||
- User-space finished sending all,
|
- User-space finished sending all,
|
||||||
- Indicaite next track data by sending set_next_track
|
- Indicate next track data by sending set_next_track
|
||||||
- Set metadata of the next track
|
- Set metadata of the next track
|
||||||
- then call partial_drain to flush most of buffer in DSP
|
- then call partial_drain to flush most of buffer in DSP
|
||||||
- Fill data of the next track
|
- Fill data of the next track
|
||||||
|
|
|
@ -132,7 +132,7 @@ SOC_DAPM_SINGLE("HiFi Playback Switch", WM8731_APANA, 4, 1, 0),
|
||||||
SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1, wm8731_output_mixer_controls,
|
SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1, wm8731_output_mixer_controls,
|
||||||
ARRAY_SIZE(wm8731_output_mixer_controls)),
|
ARRAY_SIZE(wm8731_output_mixer_controls)),
|
||||||
|
|
||||||
If you dont want the mixer elements prefixed with the name of the mixer widget,
|
If you don't want the mixer elements prefixed with the name of the mixer widget,
|
||||||
you can use SND_SOC_DAPM_MIXER_NAMED_CTL instead. the parameters are the same
|
you can use SND_SOC_DAPM_MIXER_NAMED_CTL instead. the parameters are the same
|
||||||
as for SND_SOC_DAPM_MIXER.
|
as for SND_SOC_DAPM_MIXER.
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ multiple re-usable component drivers :-
|
||||||
and any audio DSP drivers for that platform.
|
and any audio DSP drivers for that platform.
|
||||||
|
|
||||||
* Machine class driver: The machine driver class acts as the glue that
|
* Machine class driver: The machine driver class acts as the glue that
|
||||||
decribes and binds the other component drivers together to form an ALSA
|
describes and binds the other component drivers together to form an ALSA
|
||||||
"sound card device". It handles any machine specific controls and
|
"sound card device". It handles any machine specific controls and
|
||||||
machine level audio events (e.g. turning on an amp at start of playback).
|
machine level audio events (e.g. turning on an amp at start of playback).
|
||||||
|
|
||||||
|
|
|
@ -129,7 +129,7 @@ will be required to issue multiple queries and perform an
|
||||||
interpolation of the results
|
interpolation of the results
|
||||||
|
|
||||||
In some hardware-specific configuration, the system timestamp is
|
In some hardware-specific configuration, the system timestamp is
|
||||||
latched by a low-level audio subsytem, and the information provided
|
latched by a low-level audio subsystem, and the information provided
|
||||||
back to the driver. Due to potential delays in the communication with
|
back to the driver. Due to potential delays in the communication with
|
||||||
the hardware, there is a risk of misalignment with the avail and delay
|
the hardware, there is a risk of misalignment with the avail and delay
|
||||||
information. To make sure applications are not confused, a
|
information. To make sure applications are not confused, a
|
||||||
|
|
|
@ -36,6 +36,8 @@ struct hdac_chmap_ops {
|
||||||
int (*chmap_validate)(struct hdac_chmap *hchmap, int ca,
|
int (*chmap_validate)(struct hdac_chmap *hchmap, int ca,
|
||||||
int channels, unsigned char *chmap);
|
int channels, unsigned char *chmap);
|
||||||
|
|
||||||
|
int (*get_spk_alloc)(struct hdac_device *hdac, int pcm_idx);
|
||||||
|
|
||||||
void (*get_chmap)(struct hdac_device *hdac, int pcm_idx,
|
void (*get_chmap)(struct hdac_device *hdac, int pcm_idx,
|
||||||
unsigned char *chmap);
|
unsigned char *chmap);
|
||||||
void (*set_chmap)(struct hdac_device *hdac, int pcm_idx,
|
void (*set_chmap)(struct hdac_device *hdac, int pcm_idx,
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
int snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable);
|
int snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable);
|
||||||
int snd_hdac_display_power(struct hdac_bus *bus, bool enable);
|
int snd_hdac_display_power(struct hdac_bus *bus, bool enable);
|
||||||
void snd_hdac_i915_set_bclk(struct hdac_bus *bus);
|
void snd_hdac_i915_set_bclk(struct hdac_bus *bus);
|
||||||
int snd_hdac_sync_audio_rate(struct hdac_bus *bus, hda_nid_t nid, int rate);
|
int snd_hdac_sync_audio_rate(struct hdac_device *codec, hda_nid_t nid, int rate);
|
||||||
int snd_hdac_acomp_get_eld(struct hdac_bus *bus, hda_nid_t nid,
|
int snd_hdac_acomp_get_eld(struct hdac_device *codec, hda_nid_t nid,
|
||||||
bool *audio_enabled, char *buffer, int max_bytes);
|
bool *audio_enabled, char *buffer, int max_bytes);
|
||||||
int snd_hdac_i915_init(struct hdac_bus *bus);
|
int snd_hdac_i915_init(struct hdac_bus *bus);
|
||||||
int snd_hdac_i915_exit(struct hdac_bus *bus);
|
int snd_hdac_i915_exit(struct hdac_bus *bus);
|
||||||
|
@ -28,12 +28,12 @@ static inline int snd_hdac_display_power(struct hdac_bus *bus, bool enable)
|
||||||
static inline void snd_hdac_i915_set_bclk(struct hdac_bus *bus)
|
static inline void snd_hdac_i915_set_bclk(struct hdac_bus *bus)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
static inline int snd_hdac_sync_audio_rate(struct hdac_bus *bus, hda_nid_t nid,
|
static inline int snd_hdac_sync_audio_rate(struct hdac_device *codec,
|
||||||
int rate)
|
hda_nid_t nid, int rate)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
static inline int snd_hdac_acomp_get_eld(struct hdac_bus *bus, hda_nid_t nid,
|
static inline int snd_hdac_acomp_get_eld(struct hdac_device *codec, hda_nid_t nid,
|
||||||
bool *audio_enabled, char *buffer,
|
bool *audio_enabled, char *buffer,
|
||||||
int max_bytes)
|
int max_bytes)
|
||||||
{
|
{
|
||||||
|
|
|
@ -672,7 +672,7 @@ enum {
|
||||||
|
|
||||||
/* global timers (device member) */
|
/* global timers (device member) */
|
||||||
#define SNDRV_TIMER_GLOBAL_SYSTEM 0
|
#define SNDRV_TIMER_GLOBAL_SYSTEM 0
|
||||||
#define SNDRV_TIMER_GLOBAL_RTC 1
|
#define SNDRV_TIMER_GLOBAL_RTC 1 /* unused */
|
||||||
#define SNDRV_TIMER_GLOBAL_HPET 2
|
#define SNDRV_TIMER_GLOBAL_HPET 2
|
||||||
#define SNDRV_TIMER_GLOBAL_HRTIMER 3
|
#define SNDRV_TIMER_GLOBAL_HRTIMER 3
|
||||||
|
|
||||||
|
|
|
@ -141,35 +141,6 @@ config SND_SEQ_HRTIMER_DEFAULT
|
||||||
Say Y here to use the HR-timer backend as the default sequencer
|
Say Y here to use the HR-timer backend as the default sequencer
|
||||||
timer.
|
timer.
|
||||||
|
|
||||||
config SND_RTCTIMER
|
|
||||||
tristate "RTC Timer support"
|
|
||||||
depends on RTC
|
|
||||||
select SND_TIMER
|
|
||||||
help
|
|
||||||
Say Y here to enable RTC timer support for ALSA. ALSA uses
|
|
||||||
the RTC timer as a precise timing source and maps the RTC
|
|
||||||
timer to ALSA's timer interface. The ALSA sequencer code also
|
|
||||||
can use this timing source.
|
|
||||||
|
|
||||||
To compile this driver as a module, choose M here: the module
|
|
||||||
will be called snd-rtctimer.
|
|
||||||
|
|
||||||
Note that this option is exclusive with the new RTC drivers
|
|
||||||
(CONFIG_RTC_CLASS) since this requires the old API.
|
|
||||||
|
|
||||||
config SND_SEQ_RTCTIMER_DEFAULT
|
|
||||||
bool "Use RTC as default sequencer timer"
|
|
||||||
depends on SND_RTCTIMER && SND_SEQUENCER
|
|
||||||
depends on !SND_SEQ_HRTIMER_DEFAULT
|
|
||||||
default y
|
|
||||||
help
|
|
||||||
Say Y here to use the RTC timer as the default sequencer
|
|
||||||
timer. This is strongly recommended because it ensures
|
|
||||||
precise MIDI timing even when the system timer runs at less
|
|
||||||
than 1000 Hz.
|
|
||||||
|
|
||||||
If in doubt, say Y.
|
|
||||||
|
|
||||||
config SND_DYNAMIC_MINORS
|
config SND_DYNAMIC_MINORS
|
||||||
bool "Dynamic device file minor numbers"
|
bool "Dynamic device file minor numbers"
|
||||||
help
|
help
|
||||||
|
|
|
@ -37,7 +37,6 @@ obj-$(CONFIG_SND) += snd.o
|
||||||
obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o
|
obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o
|
||||||
obj-$(CONFIG_SND_TIMER) += snd-timer.o
|
obj-$(CONFIG_SND_TIMER) += snd-timer.o
|
||||||
obj-$(CONFIG_SND_HRTIMER) += snd-hrtimer.o
|
obj-$(CONFIG_SND_HRTIMER) += snd-hrtimer.o
|
||||||
obj-$(CONFIG_SND_RTCTIMER) += snd-rtctimer.o
|
|
||||||
obj-$(CONFIG_SND_PCM) += snd-pcm.o
|
obj-$(CONFIG_SND_PCM) += snd-pcm.o
|
||||||
obj-$(CONFIG_SND_DMAENGINE_PCM) += snd-pcm-dmaengine.o
|
obj-$(CONFIG_SND_DMAENGINE_PCM) += snd-pcm-dmaengine.o
|
||||||
obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o
|
obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o
|
||||||
|
|
|
@ -288,9 +288,12 @@ static ssize_t snd_compr_write(struct file *f, const char __user *buf,
|
||||||
stream = &data->stream;
|
stream = &data->stream;
|
||||||
mutex_lock(&stream->device->lock);
|
mutex_lock(&stream->device->lock);
|
||||||
/* write is allowed when stream is running or has been steup */
|
/* write is allowed when stream is running or has been steup */
|
||||||
if (stream->runtime->state != SNDRV_PCM_STATE_SETUP &&
|
switch (stream->runtime->state) {
|
||||||
stream->runtime->state != SNDRV_PCM_STATE_PREPARED &&
|
case SNDRV_PCM_STATE_SETUP:
|
||||||
stream->runtime->state != SNDRV_PCM_STATE_RUNNING) {
|
case SNDRV_PCM_STATE_PREPARED:
|
||||||
|
case SNDRV_PCM_STATE_RUNNING:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
mutex_unlock(&stream->device->lock);
|
mutex_unlock(&stream->device->lock);
|
||||||
return -EBADFD;
|
return -EBADFD;
|
||||||
}
|
}
|
||||||
|
@ -391,14 +394,13 @@ static unsigned int snd_compr_poll(struct file *f, poll_table *wait)
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
|
||||||
if (snd_BUG_ON(!data))
|
if (snd_BUG_ON(!data))
|
||||||
return -EFAULT;
|
return POLLERR;
|
||||||
|
|
||||||
stream = &data->stream;
|
stream = &data->stream;
|
||||||
if (snd_BUG_ON(!stream))
|
|
||||||
return -EFAULT;
|
|
||||||
|
|
||||||
mutex_lock(&stream->device->lock);
|
mutex_lock(&stream->device->lock);
|
||||||
if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) {
|
if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) {
|
||||||
retval = -EBADFD;
|
retval = snd_compr_get_poll(stream) | POLLERR;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
poll_wait(f, &stream->runtime->sleep, wait);
|
poll_wait(f, &stream->runtime->sleep, wait);
|
||||||
|
@ -421,10 +423,7 @@ static unsigned int snd_compr_poll(struct file *f, poll_table *wait)
|
||||||
retval = snd_compr_get_poll(stream);
|
retval = snd_compr_get_poll(stream);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (stream->direction == SND_COMPRESS_PLAYBACK)
|
retval = snd_compr_get_poll(stream) | POLLERR;
|
||||||
retval = POLLOUT | POLLWRNORM | POLLERR;
|
|
||||||
else
|
|
||||||
retval = POLLIN | POLLRDNORM | POLLERR;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
out:
|
out:
|
||||||
|
@ -802,9 +801,9 @@ static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
|
||||||
|
|
||||||
if (snd_BUG_ON(!data))
|
if (snd_BUG_ON(!data))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
stream = &data->stream;
|
stream = &data->stream;
|
||||||
if (snd_BUG_ON(!stream))
|
|
||||||
return -EFAULT;
|
|
||||||
mutex_lock(&stream->device->lock);
|
mutex_lock(&stream->device->lock);
|
||||||
switch (_IOC_NR(cmd)) {
|
switch (_IOC_NR(cmd)) {
|
||||||
case _IOC_NR(SNDRV_COMPRESS_IOCTL_VERSION):
|
case _IOC_NR(SNDRV_COMPRESS_IOCTL_VERSION):
|
||||||
|
|
|
@ -38,37 +38,53 @@ static unsigned int resolution;
|
||||||
struct snd_hrtimer {
|
struct snd_hrtimer {
|
||||||
struct snd_timer *timer;
|
struct snd_timer *timer;
|
||||||
struct hrtimer hrt;
|
struct hrtimer hrt;
|
||||||
atomic_t running;
|
bool in_callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
|
static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
|
||||||
{
|
{
|
||||||
struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt);
|
struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt);
|
||||||
struct snd_timer *t = stime->timer;
|
struct snd_timer *t = stime->timer;
|
||||||
unsigned long oruns;
|
ktime_t delta;
|
||||||
|
unsigned long ticks;
|
||||||
|
enum hrtimer_restart ret = HRTIMER_NORESTART;
|
||||||
|
|
||||||
if (!atomic_read(&stime->running))
|
spin_lock(&t->lock);
|
||||||
return HRTIMER_NORESTART;
|
if (!t->running)
|
||||||
|
goto out; /* fast path */
|
||||||
|
stime->in_callback = true;
|
||||||
|
ticks = t->sticks;
|
||||||
|
spin_unlock(&t->lock);
|
||||||
|
|
||||||
oruns = hrtimer_forward_now(hrt, ns_to_ktime(t->sticks * resolution));
|
/* calculate the drift */
|
||||||
snd_timer_interrupt(stime->timer, t->sticks * oruns);
|
delta = ktime_sub(hrt->base->get_time(), hrtimer_get_expires(hrt));
|
||||||
|
if (delta.tv64 > 0)
|
||||||
|
ticks += ktime_divns(delta, ticks * resolution);
|
||||||
|
|
||||||
if (!atomic_read(&stime->running))
|
snd_timer_interrupt(stime->timer, ticks);
|
||||||
return HRTIMER_NORESTART;
|
|
||||||
return HRTIMER_RESTART;
|
spin_lock(&t->lock);
|
||||||
|
if (t->running) {
|
||||||
|
hrtimer_add_expires_ns(hrt, t->sticks * resolution);
|
||||||
|
ret = HRTIMER_RESTART;
|
||||||
|
}
|
||||||
|
|
||||||
|
stime->in_callback = false;
|
||||||
|
out:
|
||||||
|
spin_unlock(&t->lock);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int snd_hrtimer_open(struct snd_timer *t)
|
static int snd_hrtimer_open(struct snd_timer *t)
|
||||||
{
|
{
|
||||||
struct snd_hrtimer *stime;
|
struct snd_hrtimer *stime;
|
||||||
|
|
||||||
stime = kmalloc(sizeof(*stime), GFP_KERNEL);
|
stime = kzalloc(sizeof(*stime), GFP_KERNEL);
|
||||||
if (!stime)
|
if (!stime)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||||
stime->timer = t;
|
stime->timer = t;
|
||||||
stime->hrt.function = snd_hrtimer_callback;
|
stime->hrt.function = snd_hrtimer_callback;
|
||||||
atomic_set(&stime->running, 0);
|
|
||||||
t->private_data = stime;
|
t->private_data = stime;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -78,6 +94,11 @@ static int snd_hrtimer_close(struct snd_timer *t)
|
||||||
struct snd_hrtimer *stime = t->private_data;
|
struct snd_hrtimer *stime = t->private_data;
|
||||||
|
|
||||||
if (stime) {
|
if (stime) {
|
||||||
|
spin_lock_irq(&t->lock);
|
||||||
|
t->running = 0; /* just to be sure */
|
||||||
|
stime->in_callback = 1; /* skip start/stop */
|
||||||
|
spin_unlock_irq(&t->lock);
|
||||||
|
|
||||||
hrtimer_cancel(&stime->hrt);
|
hrtimer_cancel(&stime->hrt);
|
||||||
kfree(stime);
|
kfree(stime);
|
||||||
t->private_data = NULL;
|
t->private_data = NULL;
|
||||||
|
@ -89,18 +110,19 @@ static int snd_hrtimer_start(struct snd_timer *t)
|
||||||
{
|
{
|
||||||
struct snd_hrtimer *stime = t->private_data;
|
struct snd_hrtimer *stime = t->private_data;
|
||||||
|
|
||||||
atomic_set(&stime->running, 0);
|
if (stime->in_callback)
|
||||||
hrtimer_try_to_cancel(&stime->hrt);
|
return 0;
|
||||||
hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution),
|
hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution),
|
||||||
HRTIMER_MODE_REL);
|
HRTIMER_MODE_REL);
|
||||||
atomic_set(&stime->running, 1);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int snd_hrtimer_stop(struct snd_timer *t)
|
static int snd_hrtimer_stop(struct snd_timer *t)
|
||||||
{
|
{
|
||||||
struct snd_hrtimer *stime = t->private_data;
|
struct snd_hrtimer *stime = t->private_data;
|
||||||
atomic_set(&stime->running, 0);
|
|
||||||
|
if (stime->in_callback)
|
||||||
|
return 0;
|
||||||
hrtimer_try_to_cancel(&stime->hrt);
|
hrtimer_try_to_cancel(&stime->hrt);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1886,8 +1886,8 @@ void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
|
||||||
snd_timer_interrupt(substream->timer, 1);
|
snd_timer_interrupt(substream->timer, 1);
|
||||||
#endif
|
#endif
|
||||||
_end:
|
_end:
|
||||||
snd_pcm_stream_unlock_irqrestore(substream, flags);
|
|
||||||
kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
|
kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
|
||||||
|
snd_pcm_stream_unlock_irqrestore(substream, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPORT_SYMBOL(snd_pcm_period_elapsed);
|
EXPORT_SYMBOL(snd_pcm_period_elapsed);
|
||||||
|
@ -2595,6 +2595,8 @@ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
|
||||||
};
|
};
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
if (WARN_ON(pcm->streams[stream].chmap_kctl))
|
||||||
|
return -EBUSY;
|
||||||
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
||||||
if (!info)
|
if (!info)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
|
@ -3161,7 +3161,7 @@ static unsigned int snd_pcm_playback_poll(struct file *file, poll_table * wait)
|
||||||
|
|
||||||
substream = pcm_file->substream;
|
substream = pcm_file->substream;
|
||||||
if (PCM_RUNTIME_CHECK(substream))
|
if (PCM_RUNTIME_CHECK(substream))
|
||||||
return -ENXIO;
|
return POLLOUT | POLLWRNORM | POLLERR;
|
||||||
runtime = substream->runtime;
|
runtime = substream->runtime;
|
||||||
|
|
||||||
poll_wait(file, &runtime->sleep, wait);
|
poll_wait(file, &runtime->sleep, wait);
|
||||||
|
@ -3200,7 +3200,7 @@ static unsigned int snd_pcm_capture_poll(struct file *file, poll_table * wait)
|
||||||
|
|
||||||
substream = pcm_file->substream;
|
substream = pcm_file->substream;
|
||||||
if (PCM_RUNTIME_CHECK(substream))
|
if (PCM_RUNTIME_CHECK(substream))
|
||||||
return -ENXIO;
|
return POLLIN | POLLRDNORM | POLLERR;
|
||||||
runtime = substream->runtime;
|
runtime = substream->runtime;
|
||||||
|
|
||||||
poll_wait(file, &runtime->sleep, wait);
|
poll_wait(file, &runtime->sleep, wait);
|
||||||
|
|
|
@ -1,187 +0,0 @@
|
||||||
/*
|
|
||||||
* RTC based high-frequency timer
|
|
||||||
*
|
|
||||||
* Copyright (C) 2000 Takashi Iwai
|
|
||||||
* based on rtctimer.c by Steve Ratcliffe
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/init.h>
|
|
||||||
#include <linux/interrupt.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/log2.h>
|
|
||||||
#include <sound/core.h>
|
|
||||||
#include <sound/timer.h>
|
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_RTC)
|
|
||||||
|
|
||||||
#include <linux/mc146818rtc.h>
|
|
||||||
|
|
||||||
#define RTC_FREQ 1024 /* default frequency */
|
|
||||||
#define NANO_SEC 1000000000L /* 10^9 in sec */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* prototypes
|
|
||||||
*/
|
|
||||||
static int rtctimer_open(struct snd_timer *t);
|
|
||||||
static int rtctimer_close(struct snd_timer *t);
|
|
||||||
static int rtctimer_start(struct snd_timer *t);
|
|
||||||
static int rtctimer_stop(struct snd_timer *t);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The hardware dependent description for this timer.
|
|
||||||
*/
|
|
||||||
static struct snd_timer_hardware rtc_hw = {
|
|
||||||
.flags = SNDRV_TIMER_HW_AUTO |
|
|
||||||
SNDRV_TIMER_HW_FIRST |
|
|
||||||
SNDRV_TIMER_HW_TASKLET,
|
|
||||||
.ticks = 100000000L, /* FIXME: XXX */
|
|
||||||
.open = rtctimer_open,
|
|
||||||
.close = rtctimer_close,
|
|
||||||
.start = rtctimer_start,
|
|
||||||
.stop = rtctimer_stop,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int rtctimer_freq = RTC_FREQ; /* frequency */
|
|
||||||
static struct snd_timer *rtctimer;
|
|
||||||
static struct tasklet_struct rtc_tasklet;
|
|
||||||
static rtc_task_t rtc_task;
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
rtctimer_open(struct snd_timer *t)
|
|
||||||
{
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = rtc_register(&rtc_task);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
t->private_data = &rtc_task;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
rtctimer_close(struct snd_timer *t)
|
|
||||||
{
|
|
||||||
rtc_task_t *rtc = t->private_data;
|
|
||||||
if (rtc) {
|
|
||||||
rtc_unregister(rtc);
|
|
||||||
tasklet_kill(&rtc_tasklet);
|
|
||||||
t->private_data = NULL;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
rtctimer_start(struct snd_timer *timer)
|
|
||||||
{
|
|
||||||
rtc_task_t *rtc = timer->private_data;
|
|
||||||
if (snd_BUG_ON(!rtc))
|
|
||||||
return -EINVAL;
|
|
||||||
rtc_control(rtc, RTC_IRQP_SET, rtctimer_freq);
|
|
||||||
rtc_control(rtc, RTC_PIE_ON, 0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
rtctimer_stop(struct snd_timer *timer)
|
|
||||||
{
|
|
||||||
rtc_task_t *rtc = timer->private_data;
|
|
||||||
if (snd_BUG_ON(!rtc))
|
|
||||||
return -EINVAL;
|
|
||||||
rtc_control(rtc, RTC_PIE_OFF, 0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void rtctimer_tasklet(unsigned long data)
|
|
||||||
{
|
|
||||||
snd_timer_interrupt((struct snd_timer *)data, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* interrupt
|
|
||||||
*/
|
|
||||||
static void rtctimer_interrupt(void *private_data)
|
|
||||||
{
|
|
||||||
tasklet_schedule(private_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ENTRY functions
|
|
||||||
*/
|
|
||||||
static int __init rtctimer_init(void)
|
|
||||||
{
|
|
||||||
int err;
|
|
||||||
struct snd_timer *timer;
|
|
||||||
|
|
||||||
if (rtctimer_freq < 2 || rtctimer_freq > 8192 ||
|
|
||||||
!is_power_of_2(rtctimer_freq)) {
|
|
||||||
pr_err("ALSA: rtctimer: invalid frequency %d\n", rtctimer_freq);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create a new timer and set up the fields */
|
|
||||||
err = snd_timer_global_new("rtc", SNDRV_TIMER_GLOBAL_RTC, &timer);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
timer->module = THIS_MODULE;
|
|
||||||
strcpy(timer->name, "RTC timer");
|
|
||||||
timer->hw = rtc_hw;
|
|
||||||
timer->hw.resolution = NANO_SEC / rtctimer_freq;
|
|
||||||
|
|
||||||
tasklet_init(&rtc_tasklet, rtctimer_tasklet, (unsigned long)timer);
|
|
||||||
|
|
||||||
/* set up RTC callback */
|
|
||||||
rtc_task.func = rtctimer_interrupt;
|
|
||||||
rtc_task.private_data = &rtc_tasklet;
|
|
||||||
|
|
||||||
err = snd_timer_global_register(timer);
|
|
||||||
if (err < 0) {
|
|
||||||
snd_timer_global_free(timer);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
rtctimer = timer; /* remember this */
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit rtctimer_exit(void)
|
|
||||||
{
|
|
||||||
if (rtctimer) {
|
|
||||||
snd_timer_global_free(rtctimer);
|
|
||||||
rtctimer = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* exported stuff
|
|
||||||
*/
|
|
||||||
module_init(rtctimer_init)
|
|
||||||
module_exit(rtctimer_exit)
|
|
||||||
|
|
||||||
module_param(rtctimer_freq, int, 0444);
|
|
||||||
MODULE_PARM_DESC(rtctimer_freq, "timer frequency in Hz");
|
|
||||||
|
|
||||||
MODULE_LICENSE("GPL");
|
|
||||||
|
|
||||||
MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_RTC));
|
|
||||||
|
|
||||||
#endif /* IS_ENABLED(CONFIG_RTC) */
|
|
|
@ -47,8 +47,6 @@ int seq_default_timer_card = -1;
|
||||||
int seq_default_timer_device =
|
int seq_default_timer_device =
|
||||||
#ifdef CONFIG_SND_SEQ_HRTIMER_DEFAULT
|
#ifdef CONFIG_SND_SEQ_HRTIMER_DEFAULT
|
||||||
SNDRV_TIMER_GLOBAL_HRTIMER
|
SNDRV_TIMER_GLOBAL_HRTIMER
|
||||||
#elif defined(CONFIG_SND_SEQ_RTCTIMER_DEFAULT)
|
|
||||||
SNDRV_TIMER_GLOBAL_RTC
|
|
||||||
#else
|
#else
|
||||||
SNDRV_TIMER_GLOBAL_SYSTEM
|
SNDRV_TIMER_GLOBAL_SYSTEM
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -37,8 +37,6 @@
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_SND_HRTIMER)
|
#if IS_ENABLED(CONFIG_SND_HRTIMER)
|
||||||
#define DEFAULT_TIMER_LIMIT 4
|
#define DEFAULT_TIMER_LIMIT 4
|
||||||
#elif IS_ENABLED(CONFIG_SND_RTCTIMER)
|
|
||||||
#define DEFAULT_TIMER_LIMIT 2
|
|
||||||
#else
|
#else
|
||||||
#define DEFAULT_TIMER_LIMIT 1
|
#define DEFAULT_TIMER_LIMIT 1
|
||||||
#endif
|
#endif
|
||||||
|
@ -1225,6 +1223,7 @@ static void snd_timer_user_ccallback(struct snd_timer_instance *timeri,
|
||||||
tu->tstamp = *tstamp;
|
tu->tstamp = *tstamp;
|
||||||
if ((tu->filter & (1 << event)) == 0 || !tu->tread)
|
if ((tu->filter & (1 << event)) == 0 || !tu->tread)
|
||||||
return;
|
return;
|
||||||
|
memset(&r1, 0, sizeof(r1));
|
||||||
r1.event = event;
|
r1.event = event;
|
||||||
r1.tstamp = *tstamp;
|
r1.tstamp = *tstamp;
|
||||||
r1.val = resolution;
|
r1.val = resolution;
|
||||||
|
@ -1267,6 +1266,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri,
|
||||||
}
|
}
|
||||||
if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) &&
|
if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) &&
|
||||||
tu->last_resolution != resolution) {
|
tu->last_resolution != resolution) {
|
||||||
|
memset(&r1, 0, sizeof(r1));
|
||||||
r1.event = SNDRV_TIMER_EVENT_RESOLUTION;
|
r1.event = SNDRV_TIMER_EVENT_RESOLUTION;
|
||||||
r1.tstamp = tstamp;
|
r1.tstamp = tstamp;
|
||||||
r1.val = resolution;
|
r1.val = resolution;
|
||||||
|
@ -1739,6 +1739,7 @@ static int snd_timer_user_params(struct file *file,
|
||||||
if (tu->timeri->flags & SNDRV_TIMER_IFLG_EARLY_EVENT) {
|
if (tu->timeri->flags & SNDRV_TIMER_IFLG_EARLY_EVENT) {
|
||||||
if (tu->tread) {
|
if (tu->tread) {
|
||||||
struct snd_timer_tread tread;
|
struct snd_timer_tread tread;
|
||||||
|
memset(&tread, 0, sizeof(tread));
|
||||||
tread.event = SNDRV_TIMER_EVENT_EARLY;
|
tread.event = SNDRV_TIMER_EVENT_EARLY;
|
||||||
tread.tstamp.tv_sec = 0;
|
tread.tstamp.tv_sec = 0;
|
||||||
tread.tstamp.tv_nsec = 0;
|
tread.tstamp.tv_nsec = 0;
|
||||||
|
|
|
@ -134,6 +134,7 @@ config SND_FIREWIRE_TASCAM
|
||||||
Say Y here to include support for TASCAM.
|
Say Y here to include support for TASCAM.
|
||||||
* FW-1884
|
* FW-1884
|
||||||
* FW-1082
|
* FW-1082
|
||||||
|
* FW-1804
|
||||||
|
|
||||||
To compile this driver as a module, choose M here: the module
|
To compile this driver as a module, choose M here: the module
|
||||||
will be called snd-firewire-tascam.
|
will be called snd-firewire-tascam.
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# To find a header included by define_trace.h.
|
||||||
|
CFLAGS_amdtp-stream.o := -I$(src)
|
||||||
|
|
||||||
snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
|
snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
|
||||||
fcp.o cmp.o amdtp-stream.o amdtp-am824.o
|
fcp.o cmp.o amdtp-stream.o amdtp-am824.o
|
||||||
snd-isight-objs := isight.o
|
snd-isight-objs := isight.o
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* amdtp-stream-trace.h - tracepoint definitions to dump a part of packet data
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Takashi Sakamoto
|
||||||
|
* Licensed under the terms of the GNU General Public License, version 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#undef TRACE_SYSTEM
|
||||||
|
#define TRACE_SYSTEM snd_firewire_lib
|
||||||
|
|
||||||
|
#if !defined(_AMDTP_STREAM_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||||
|
#define _AMDTP_STREAM_TRACE_H
|
||||||
|
|
||||||
|
#include <linux/tracepoint.h>
|
||||||
|
|
||||||
|
TRACE_EVENT(in_packet,
|
||||||
|
TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_quadlets, unsigned int index),
|
||||||
|
TP_ARGS(s, cycles, cip_header, payload_quadlets, index),
|
||||||
|
TP_STRUCT__entry(
|
||||||
|
__field(unsigned int, second)
|
||||||
|
__field(unsigned int, cycle)
|
||||||
|
__field(int, channel)
|
||||||
|
__field(int, src)
|
||||||
|
__field(int, dest)
|
||||||
|
__field(u32, cip_header0)
|
||||||
|
__field(u32, cip_header1)
|
||||||
|
__field(unsigned int, payload_quadlets)
|
||||||
|
__field(unsigned int, packet_index)
|
||||||
|
__field(bool, irq)
|
||||||
|
__field(unsigned int, index)
|
||||||
|
),
|
||||||
|
TP_fast_assign(
|
||||||
|
__entry->second = cycles / CYCLES_PER_SECOND;
|
||||||
|
__entry->cycle = cycles % CYCLES_PER_SECOND;
|
||||||
|
__entry->channel = s->context->channel;
|
||||||
|
__entry->src = fw_parent_device(s->unit)->node_id;
|
||||||
|
__entry->dest = fw_parent_device(s->unit)->card->node_id;
|
||||||
|
__entry->cip_header0 = cip_header[0];
|
||||||
|
__entry->cip_header1 = cip_header[1];
|
||||||
|
__entry->payload_quadlets = payload_quadlets;
|
||||||
|
__entry->packet_index = s->packet_index;
|
||||||
|
__entry->irq = in_interrupt();
|
||||||
|
__entry->index = index;
|
||||||
|
),
|
||||||
|
TP_printk(
|
||||||
|
"%02u %04u %04x %04x %02d %08x %08x %03u %02u %01u %02u",
|
||||||
|
__entry->second,
|
||||||
|
__entry->cycle,
|
||||||
|
__entry->src,
|
||||||
|
__entry->dest,
|
||||||
|
__entry->channel,
|
||||||
|
__entry->cip_header0,
|
||||||
|
__entry->cip_header1,
|
||||||
|
__entry->payload_quadlets,
|
||||||
|
__entry->packet_index,
|
||||||
|
__entry->irq,
|
||||||
|
__entry->index)
|
||||||
|
);
|
||||||
|
|
||||||
|
TRACE_EVENT(out_packet,
|
||||||
|
TP_PROTO(const struct amdtp_stream *s, u32 cycles, __be32 *cip_header, unsigned int payload_length, unsigned int index),
|
||||||
|
TP_ARGS(s, cycles, cip_header, payload_length, index),
|
||||||
|
TP_STRUCT__entry(
|
||||||
|
__field(unsigned int, second)
|
||||||
|
__field(unsigned int, cycle)
|
||||||
|
__field(int, channel)
|
||||||
|
__field(int, src)
|
||||||
|
__field(int, dest)
|
||||||
|
__field(u32, cip_header0)
|
||||||
|
__field(u32, cip_header1)
|
||||||
|
__field(unsigned int, payload_quadlets)
|
||||||
|
__field(unsigned int, packet_index)
|
||||||
|
__field(bool, irq)
|
||||||
|
__field(unsigned int, index)
|
||||||
|
),
|
||||||
|
TP_fast_assign(
|
||||||
|
__entry->second = cycles / CYCLES_PER_SECOND;
|
||||||
|
__entry->cycle = cycles % CYCLES_PER_SECOND;
|
||||||
|
__entry->channel = s->context->channel;
|
||||||
|
__entry->src = fw_parent_device(s->unit)->card->node_id;
|
||||||
|
__entry->dest = fw_parent_device(s->unit)->node_id;
|
||||||
|
__entry->cip_header0 = be32_to_cpu(cip_header[0]);
|
||||||
|
__entry->cip_header1 = be32_to_cpu(cip_header[1]);
|
||||||
|
__entry->payload_quadlets = payload_length / 4;
|
||||||
|
__entry->packet_index = s->packet_index;
|
||||||
|
__entry->irq = in_interrupt();
|
||||||
|
__entry->index = index;
|
||||||
|
),
|
||||||
|
TP_printk(
|
||||||
|
"%02u %04u %04x %04x %02d %08x %08x %03u %02u %01u %02u",
|
||||||
|
__entry->second,
|
||||||
|
__entry->cycle,
|
||||||
|
__entry->src,
|
||||||
|
__entry->dest,
|
||||||
|
__entry->channel,
|
||||||
|
__entry->cip_header0,
|
||||||
|
__entry->cip_header1,
|
||||||
|
__entry->payload_quadlets,
|
||||||
|
__entry->packet_index,
|
||||||
|
__entry->irq,
|
||||||
|
__entry->index)
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef TRACE_INCLUDE_PATH
|
||||||
|
#define TRACE_INCLUDE_PATH .
|
||||||
|
#undef TRACE_INCLUDE_FILE
|
||||||
|
#define TRACE_INCLUDE_FILE amdtp-stream-trace
|
||||||
|
#include <trace/define_trace.h>
|
|
@ -19,6 +19,10 @@
|
||||||
#define CYCLES_PER_SECOND 8000
|
#define CYCLES_PER_SECOND 8000
|
||||||
#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND)
|
#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND)
|
||||||
|
|
||||||
|
/* Always support Linux tracing subsystem. */
|
||||||
|
#define CREATE_TRACE_POINTS
|
||||||
|
#include "amdtp-stream-trace.h"
|
||||||
|
|
||||||
#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 microseconds */
|
#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 microseconds */
|
||||||
|
|
||||||
/* isochronous header parameters */
|
/* isochronous header parameters */
|
||||||
|
@ -87,7 +91,6 @@ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||||
|
|
||||||
init_waitqueue_head(&s->callback_wait);
|
init_waitqueue_head(&s->callback_wait);
|
||||||
s->callbacked = false;
|
s->callbacked = false;
|
||||||
s->sync_slave = NULL;
|
|
||||||
|
|
||||||
s->fmt = fmt;
|
s->fmt = fmt;
|
||||||
s->process_data_blocks = process_data_blocks;
|
s->process_data_blocks = process_data_blocks;
|
||||||
|
@ -102,6 +105,10 @@ EXPORT_SYMBOL(amdtp_stream_init);
|
||||||
*/
|
*/
|
||||||
void amdtp_stream_destroy(struct amdtp_stream *s)
|
void amdtp_stream_destroy(struct amdtp_stream *s)
|
||||||
{
|
{
|
||||||
|
/* Not initialized. */
|
||||||
|
if (s->protocol == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
WARN_ON(amdtp_stream_running(s));
|
WARN_ON(amdtp_stream_running(s));
|
||||||
kfree(s->protocol);
|
kfree(s->protocol);
|
||||||
mutex_destroy(&s->mutex);
|
mutex_destroy(&s->mutex);
|
||||||
|
@ -244,7 +251,6 @@ void amdtp_stream_pcm_prepare(struct amdtp_stream *s)
|
||||||
tasklet_kill(&s->period_tasklet);
|
tasklet_kill(&s->period_tasklet);
|
||||||
s->pcm_buffer_pointer = 0;
|
s->pcm_buffer_pointer = 0;
|
||||||
s->pcm_period_pointer = 0;
|
s->pcm_period_pointer = 0;
|
||||||
s->pointer_flush = true;
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(amdtp_stream_pcm_prepare);
|
EXPORT_SYMBOL(amdtp_stream_pcm_prepare);
|
||||||
|
|
||||||
|
@ -349,7 +355,6 @@ static void update_pcm_pointers(struct amdtp_stream *s,
|
||||||
s->pcm_period_pointer += frames;
|
s->pcm_period_pointer += frames;
|
||||||
if (s->pcm_period_pointer >= pcm->runtime->period_size) {
|
if (s->pcm_period_pointer >= pcm->runtime->period_size) {
|
||||||
s->pcm_period_pointer -= pcm->runtime->period_size;
|
s->pcm_period_pointer -= pcm->runtime->period_size;
|
||||||
s->pointer_flush = false;
|
|
||||||
tasklet_hi_schedule(&s->period_tasklet);
|
tasklet_hi_schedule(&s->period_tasklet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -363,9 +368,8 @@ static void pcm_period_tasklet(unsigned long data)
|
||||||
snd_pcm_period_elapsed(pcm);
|
snd_pcm_period_elapsed(pcm);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int queue_packet(struct amdtp_stream *s,
|
static int queue_packet(struct amdtp_stream *s, unsigned int header_length,
|
||||||
unsigned int header_length,
|
unsigned int payload_length)
|
||||||
unsigned int payload_length, bool skip)
|
|
||||||
{
|
{
|
||||||
struct fw_iso_packet p = {0};
|
struct fw_iso_packet p = {0};
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
@ -376,8 +380,10 @@ static int queue_packet(struct amdtp_stream *s,
|
||||||
p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL);
|
p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL);
|
||||||
p.tag = TAG_CIP;
|
p.tag = TAG_CIP;
|
||||||
p.header_length = header_length;
|
p.header_length = header_length;
|
||||||
p.payload_length = (!skip) ? payload_length : 0;
|
if (payload_length > 0)
|
||||||
p.skip = skip;
|
p.payload_length = payload_length;
|
||||||
|
else
|
||||||
|
p.skip = true;
|
||||||
err = fw_iso_context_queue(s->context, &p, &s->buffer.iso_buffer,
|
err = fw_iso_context_queue(s->context, &p, &s->buffer.iso_buffer,
|
||||||
s->buffer.packets[s->packet_index].offset);
|
s->buffer.packets[s->packet_index].offset);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
|
@ -392,27 +398,30 @@ end:
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int queue_out_packet(struct amdtp_stream *s,
|
static inline int queue_out_packet(struct amdtp_stream *s,
|
||||||
unsigned int payload_length, bool skip)
|
unsigned int payload_length)
|
||||||
{
|
{
|
||||||
return queue_packet(s, OUT_PACKET_HEADER_SIZE,
|
return queue_packet(s, OUT_PACKET_HEADER_SIZE, payload_length);
|
||||||
payload_length, skip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int queue_in_packet(struct amdtp_stream *s)
|
static inline int queue_in_packet(struct amdtp_stream *s)
|
||||||
{
|
{
|
||||||
return queue_packet(s, IN_PACKET_HEADER_SIZE,
|
return queue_packet(s, IN_PACKET_HEADER_SIZE,
|
||||||
amdtp_stream_get_max_payload(s), false);
|
amdtp_stream_get_max_payload(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
|
static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
|
||||||
unsigned int syt)
|
unsigned int index)
|
||||||
{
|
{
|
||||||
__be32 *buffer;
|
__be32 *buffer;
|
||||||
|
unsigned int syt;
|
||||||
|
unsigned int data_blocks;
|
||||||
unsigned int payload_length;
|
unsigned int payload_length;
|
||||||
unsigned int pcm_frames;
|
unsigned int pcm_frames;
|
||||||
struct snd_pcm_substream *pcm;
|
struct snd_pcm_substream *pcm;
|
||||||
|
|
||||||
buffer = s->buffer.packets[s->packet_index].buffer;
|
buffer = s->buffer.packets[s->packet_index].buffer;
|
||||||
|
syt = calculate_syt(s, cycle);
|
||||||
|
data_blocks = calculate_data_blocks(s, syt);
|
||||||
pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt);
|
pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt);
|
||||||
|
|
||||||
buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) |
|
buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) |
|
||||||
|
@ -424,9 +433,11 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
|
||||||
(syt & CIP_SYT_MASK));
|
(syt & CIP_SYT_MASK));
|
||||||
|
|
||||||
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
|
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
|
||||||
|
|
||||||
payload_length = 8 + data_blocks * 4 * s->data_block_quadlets;
|
payload_length = 8 + data_blocks * 4 * s->data_block_quadlets;
|
||||||
if (queue_out_packet(s, payload_length, false) < 0)
|
|
||||||
|
trace_out_packet(s, cycle, buffer, payload_length, index);
|
||||||
|
|
||||||
|
if (queue_out_packet(s, payload_length) < 0)
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
|
||||||
pcm = ACCESS_ONCE(s->pcm);
|
pcm = ACCESS_ONCE(s->pcm);
|
||||||
|
@ -438,19 +449,24 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int handle_in_packet(struct amdtp_stream *s,
|
static int handle_in_packet(struct amdtp_stream *s,
|
||||||
unsigned int payload_quadlets, __be32 *buffer,
|
unsigned int payload_quadlets, unsigned int cycle,
|
||||||
unsigned int *data_blocks, unsigned int syt)
|
unsigned int index)
|
||||||
{
|
{
|
||||||
|
__be32 *buffer;
|
||||||
u32 cip_header[2];
|
u32 cip_header[2];
|
||||||
unsigned int fmt, fdf;
|
unsigned int fmt, fdf, syt;
|
||||||
unsigned int data_block_quadlets, data_block_counter, dbc_interval;
|
unsigned int data_block_quadlets, data_block_counter, dbc_interval;
|
||||||
|
unsigned int data_blocks;
|
||||||
struct snd_pcm_substream *pcm;
|
struct snd_pcm_substream *pcm;
|
||||||
unsigned int pcm_frames;
|
unsigned int pcm_frames;
|
||||||
bool lost;
|
bool lost;
|
||||||
|
|
||||||
|
buffer = s->buffer.packets[s->packet_index].buffer;
|
||||||
cip_header[0] = be32_to_cpu(buffer[0]);
|
cip_header[0] = be32_to_cpu(buffer[0]);
|
||||||
cip_header[1] = be32_to_cpu(buffer[1]);
|
cip_header[1] = be32_to_cpu(buffer[1]);
|
||||||
|
|
||||||
|
trace_in_packet(s, cycle, cip_header, payload_quadlets, index);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This module supports 'Two-quadlet CIP header with SYT field'.
|
* This module supports 'Two-quadlet CIP header with SYT field'.
|
||||||
* For convenience, also check FMT field is AM824 or not.
|
* For convenience, also check FMT field is AM824 or not.
|
||||||
|
@ -460,7 +476,7 @@ static int handle_in_packet(struct amdtp_stream *s,
|
||||||
dev_info_ratelimited(&s->unit->device,
|
dev_info_ratelimited(&s->unit->device,
|
||||||
"Invalid CIP header for AMDTP: %08X:%08X\n",
|
"Invalid CIP header for AMDTP: %08X:%08X\n",
|
||||||
cip_header[0], cip_header[1]);
|
cip_header[0], cip_header[1]);
|
||||||
*data_blocks = 0;
|
data_blocks = 0;
|
||||||
pcm_frames = 0;
|
pcm_frames = 0;
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
@ -471,7 +487,7 @@ static int handle_in_packet(struct amdtp_stream *s,
|
||||||
dev_info_ratelimited(&s->unit->device,
|
dev_info_ratelimited(&s->unit->device,
|
||||||
"Detect unexpected protocol: %08x %08x\n",
|
"Detect unexpected protocol: %08x %08x\n",
|
||||||
cip_header[0], cip_header[1]);
|
cip_header[0], cip_header[1]);
|
||||||
*data_blocks = 0;
|
data_blocks = 0;
|
||||||
pcm_frames = 0;
|
pcm_frames = 0;
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
@ -480,7 +496,7 @@ static int handle_in_packet(struct amdtp_stream *s,
|
||||||
fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT;
|
fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT;
|
||||||
if (payload_quadlets < 3 ||
|
if (payload_quadlets < 3 ||
|
||||||
(fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) {
|
(fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) {
|
||||||
*data_blocks = 0;
|
data_blocks = 0;
|
||||||
} else {
|
} else {
|
||||||
data_block_quadlets =
|
data_block_quadlets =
|
||||||
(cip_header[0] & CIP_DBS_MASK) >> CIP_DBS_SHIFT;
|
(cip_header[0] & CIP_DBS_MASK) >> CIP_DBS_SHIFT;
|
||||||
|
@ -494,12 +510,12 @@ static int handle_in_packet(struct amdtp_stream *s,
|
||||||
if (s->flags & CIP_WRONG_DBS)
|
if (s->flags & CIP_WRONG_DBS)
|
||||||
data_block_quadlets = s->data_block_quadlets;
|
data_block_quadlets = s->data_block_quadlets;
|
||||||
|
|
||||||
*data_blocks = (payload_quadlets - 2) / data_block_quadlets;
|
data_blocks = (payload_quadlets - 2) / data_block_quadlets;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check data block counter continuity */
|
/* Check data block counter continuity */
|
||||||
data_block_counter = cip_header[0] & CIP_DBC_MASK;
|
data_block_counter = cip_header[0] & CIP_DBC_MASK;
|
||||||
if (*data_blocks == 0 && (s->flags & CIP_EMPTY_HAS_WRONG_DBC) &&
|
if (data_blocks == 0 && (s->flags & CIP_EMPTY_HAS_WRONG_DBC) &&
|
||||||
s->data_block_counter != UINT_MAX)
|
s->data_block_counter != UINT_MAX)
|
||||||
data_block_counter = s->data_block_counter;
|
data_block_counter = s->data_block_counter;
|
||||||
|
|
||||||
|
@ -510,10 +526,10 @@ static int handle_in_packet(struct amdtp_stream *s,
|
||||||
} else if (!(s->flags & CIP_DBC_IS_END_EVENT)) {
|
} else if (!(s->flags & CIP_DBC_IS_END_EVENT)) {
|
||||||
lost = data_block_counter != s->data_block_counter;
|
lost = data_block_counter != s->data_block_counter;
|
||||||
} else {
|
} else {
|
||||||
if ((*data_blocks > 0) && (s->tx_dbc_interval > 0))
|
if (data_blocks > 0 && s->tx_dbc_interval > 0)
|
||||||
dbc_interval = s->tx_dbc_interval;
|
dbc_interval = s->tx_dbc_interval;
|
||||||
else
|
else
|
||||||
dbc_interval = *data_blocks;
|
dbc_interval = data_blocks;
|
||||||
|
|
||||||
lost = data_block_counter !=
|
lost = data_block_counter !=
|
||||||
((s->data_block_counter + dbc_interval) & 0xff);
|
((s->data_block_counter + dbc_interval) & 0xff);
|
||||||
|
@ -526,13 +542,14 @@ static int handle_in_packet(struct amdtp_stream *s,
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
pcm_frames = s->process_data_blocks(s, buffer + 2, *data_blocks, &syt);
|
syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK;
|
||||||
|
pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt);
|
||||||
|
|
||||||
if (s->flags & CIP_DBC_IS_END_EVENT)
|
if (s->flags & CIP_DBC_IS_END_EVENT)
|
||||||
s->data_block_counter = data_block_counter;
|
s->data_block_counter = data_block_counter;
|
||||||
else
|
else
|
||||||
s->data_block_counter =
|
s->data_block_counter =
|
||||||
(data_block_counter + *data_blocks) & 0xff;
|
(data_block_counter + data_blocks) & 0xff;
|
||||||
end:
|
end:
|
||||||
if (queue_in_packet(s) < 0)
|
if (queue_in_packet(s) < 0)
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
@ -544,29 +561,50 @@ end:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void out_stream_callback(struct fw_iso_context *context, u32 cycle,
|
/*
|
||||||
|
* In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On
|
||||||
|
* the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent
|
||||||
|
* it. Thus, via Linux firewire subsystem, we can get the 3 bits for second.
|
||||||
|
*/
|
||||||
|
static inline u32 compute_cycle_count(u32 tstamp)
|
||||||
|
{
|
||||||
|
return (((tstamp >> 13) & 0x07) * 8000) + (tstamp & 0x1fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u32 increment_cycle_count(u32 cycle, unsigned int addend)
|
||||||
|
{
|
||||||
|
cycle += addend;
|
||||||
|
if (cycle >= 8 * CYCLES_PER_SECOND)
|
||||||
|
cycle -= 8 * CYCLES_PER_SECOND;
|
||||||
|
return cycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u32 decrement_cycle_count(u32 cycle, unsigned int subtrahend)
|
||||||
|
{
|
||||||
|
if (cycle < subtrahend)
|
||||||
|
cycle += 8 * CYCLES_PER_SECOND;
|
||||||
|
return cycle - subtrahend;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
||||||
size_t header_length, void *header,
|
size_t header_length, void *header,
|
||||||
void *private_data)
|
void *private_data)
|
||||||
{
|
{
|
||||||
struct amdtp_stream *s = private_data;
|
struct amdtp_stream *s = private_data;
|
||||||
unsigned int i, syt, packets = header_length / 4;
|
unsigned int i, packets = header_length / 4;
|
||||||
unsigned int data_blocks;
|
u32 cycle;
|
||||||
|
|
||||||
if (s->packet_index < 0)
|
if (s->packet_index < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/*
|
cycle = compute_cycle_count(tstamp);
|
||||||
* Compute the cycle of the last queued packet.
|
|
||||||
* (We need only the four lowest bits for the SYT, so we can ignore
|
/* Align to actual cycle count for the last packet. */
|
||||||
* that bits 0-11 must wrap around at 3072.)
|
cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets);
|
||||||
*/
|
|
||||||
cycle += QUEUE_LENGTH - packets;
|
|
||||||
|
|
||||||
for (i = 0; i < packets; ++i) {
|
for (i = 0; i < packets; ++i) {
|
||||||
syt = calculate_syt(s, ++cycle);
|
cycle = increment_cycle_count(cycle, 1);
|
||||||
data_blocks = calculate_data_blocks(s, syt);
|
if (handle_out_packet(s, cycle, i) < 0) {
|
||||||
|
|
||||||
if (handle_out_packet(s, data_blocks, syt) < 0) {
|
|
||||||
s->packet_index = -1;
|
s->packet_index = -1;
|
||||||
amdtp_stream_pcm_abort(s);
|
amdtp_stream_pcm_abort(s);
|
||||||
return;
|
return;
|
||||||
|
@ -576,15 +614,15 @@ static void out_stream_callback(struct fw_iso_context *context, u32 cycle,
|
||||||
fw_iso_context_queue_flush(s->context);
|
fw_iso_context_queue_flush(s->context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void in_stream_callback(struct fw_iso_context *context, u32 cycle,
|
static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
||||||
size_t header_length, void *header,
|
size_t header_length, void *header,
|
||||||
void *private_data)
|
void *private_data)
|
||||||
{
|
{
|
||||||
struct amdtp_stream *s = private_data;
|
struct amdtp_stream *s = private_data;
|
||||||
unsigned int p, syt, packets;
|
unsigned int i, packets;
|
||||||
unsigned int payload_quadlets, max_payload_quadlets;
|
unsigned int payload_quadlets, max_payload_quadlets;
|
||||||
unsigned int data_blocks;
|
__be32 *headers = header;
|
||||||
__be32 *buffer, *headers = header;
|
u32 cycle;
|
||||||
|
|
||||||
if (s->packet_index < 0)
|
if (s->packet_index < 0)
|
||||||
return;
|
return;
|
||||||
|
@ -592,70 +630,44 @@ static void in_stream_callback(struct fw_iso_context *context, u32 cycle,
|
||||||
/* The number of packets in buffer */
|
/* The number of packets in buffer */
|
||||||
packets = header_length / IN_PACKET_HEADER_SIZE;
|
packets = header_length / IN_PACKET_HEADER_SIZE;
|
||||||
|
|
||||||
|
cycle = compute_cycle_count(tstamp);
|
||||||
|
|
||||||
|
/* Align to actual cycle count for the last packet. */
|
||||||
|
cycle = decrement_cycle_count(cycle, packets);
|
||||||
|
|
||||||
/* For buffer-over-run prevention. */
|
/* For buffer-over-run prevention. */
|
||||||
max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4;
|
max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4;
|
||||||
|
|
||||||
for (p = 0; p < packets; p++) {
|
for (i = 0; i < packets; i++) {
|
||||||
buffer = s->buffer.packets[s->packet_index].buffer;
|
cycle = increment_cycle_count(cycle, 1);
|
||||||
|
|
||||||
/* The number of quadlets in this packet */
|
/* The number of quadlets in this packet */
|
||||||
payload_quadlets =
|
payload_quadlets =
|
||||||
(be32_to_cpu(headers[p]) >> ISO_DATA_LENGTH_SHIFT) / 4;
|
(be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT) / 4;
|
||||||
if (payload_quadlets > max_payload_quadlets) {
|
if (payload_quadlets > max_payload_quadlets) {
|
||||||
dev_err(&s->unit->device,
|
dev_err(&s->unit->device,
|
||||||
"Detect jumbo payload: %02x %02x\n",
|
"Detect jumbo payload: %02x %02x\n",
|
||||||
payload_quadlets, max_payload_quadlets);
|
payload_quadlets, max_payload_quadlets);
|
||||||
s->packet_index = -1;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK;
|
if (handle_in_packet(s, payload_quadlets, cycle, i) < 0)
|
||||||
if (handle_in_packet(s, payload_quadlets, buffer,
|
|
||||||
&data_blocks, syt) < 0) {
|
|
||||||
s->packet_index = -1;
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
/* Process sync slave stream */
|
|
||||||
if (s->sync_slave && s->sync_slave->callbacked) {
|
|
||||||
if (handle_out_packet(s->sync_slave,
|
|
||||||
data_blocks, syt) < 0) {
|
|
||||||
s->packet_index = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Queueing error or detecting discontinuity */
|
/* Queueing error or detecting invalid payload. */
|
||||||
if (s->packet_index < 0) {
|
if (i < packets) {
|
||||||
|
s->packet_index = -1;
|
||||||
amdtp_stream_pcm_abort(s);
|
amdtp_stream_pcm_abort(s);
|
||||||
|
|
||||||
/* Abort sync slave. */
|
|
||||||
if (s->sync_slave) {
|
|
||||||
s->sync_slave->packet_index = -1;
|
|
||||||
amdtp_stream_pcm_abort(s->sync_slave);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* when sync to device, flush the packets for slave stream */
|
|
||||||
if (s->sync_slave && s->sync_slave->callbacked)
|
|
||||||
fw_iso_context_queue_flush(s->sync_slave->context);
|
|
||||||
|
|
||||||
fw_iso_context_queue_flush(s->context);
|
fw_iso_context_queue_flush(s->context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* processing is done by master callback */
|
|
||||||
static void slave_stream_callback(struct fw_iso_context *context, u32 cycle,
|
|
||||||
size_t header_length, void *header,
|
|
||||||
void *private_data)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* this is executed one time */
|
/* this is executed one time */
|
||||||
static void amdtp_stream_first_callback(struct fw_iso_context *context,
|
static void amdtp_stream_first_callback(struct fw_iso_context *context,
|
||||||
u32 cycle, size_t header_length,
|
u32 tstamp, size_t header_length,
|
||||||
void *header, void *private_data)
|
void *header, void *private_data)
|
||||||
{
|
{
|
||||||
struct amdtp_stream *s = private_data;
|
struct amdtp_stream *s = private_data;
|
||||||
|
@ -669,12 +681,10 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
|
||||||
|
|
||||||
if (s->direction == AMDTP_IN_STREAM)
|
if (s->direction == AMDTP_IN_STREAM)
|
||||||
context->callback.sc = in_stream_callback;
|
context->callback.sc = in_stream_callback;
|
||||||
else if (s->flags & CIP_SYNC_TO_DEVICE)
|
|
||||||
context->callback.sc = slave_stream_callback;
|
|
||||||
else
|
else
|
||||||
context->callback.sc = out_stream_callback;
|
context->callback.sc = out_stream_callback;
|
||||||
|
|
||||||
context->callback.sc(context, cycle, header_length, header, s);
|
context->callback.sc(context, tstamp, header_length, header, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -713,8 +723,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
||||||
goto err_unlock;
|
goto err_unlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->direction == AMDTP_IN_STREAM &&
|
if (s->direction == AMDTP_IN_STREAM)
|
||||||
s->flags & CIP_SKIP_INIT_DBC_CHECK)
|
|
||||||
s->data_block_counter = UINT_MAX;
|
s->data_block_counter = UINT_MAX;
|
||||||
else
|
else
|
||||||
s->data_block_counter = 0;
|
s->data_block_counter = 0;
|
||||||
|
@ -755,7 +764,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
||||||
if (s->direction == AMDTP_IN_STREAM)
|
if (s->direction == AMDTP_IN_STREAM)
|
||||||
err = queue_in_packet(s);
|
err = queue_in_packet(s);
|
||||||
else
|
else
|
||||||
err = queue_out_packet(s, 0, true);
|
err = queue_out_packet(s, 0);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto err_context;
|
goto err_context;
|
||||||
} while (s->packet_index > 0);
|
} while (s->packet_index > 0);
|
||||||
|
@ -794,11 +803,24 @@ EXPORT_SYMBOL(amdtp_stream_start);
|
||||||
*/
|
*/
|
||||||
unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s)
|
unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s)
|
||||||
{
|
{
|
||||||
/* this optimization is allowed to be racy */
|
/*
|
||||||
if (s->pointer_flush && amdtp_stream_running(s))
|
* This function is called in software IRQ context of period_tasklet or
|
||||||
|
* process context.
|
||||||
|
*
|
||||||
|
* When the software IRQ context was scheduled by software IRQ context
|
||||||
|
* of IR/IT contexts, queued packets were already handled. Therefore,
|
||||||
|
* no need to flush the queue in buffer anymore.
|
||||||
|
*
|
||||||
|
* When the process context reach here, some packets will be already
|
||||||
|
* queued in the buffer. These packets should be handled immediately
|
||||||
|
* to keep better granularity of PCM pointer.
|
||||||
|
*
|
||||||
|
* Later, the process context will sometimes schedules software IRQ
|
||||||
|
* context of the period_tasklet. Then, no need to flush the queue by
|
||||||
|
* the same reason as described for IR/IT contexts.
|
||||||
|
*/
|
||||||
|
if (!in_interrupt() && amdtp_stream_running(s))
|
||||||
fw_iso_context_flush_completions(s->context);
|
fw_iso_context_flush_completions(s->context);
|
||||||
else
|
|
||||||
s->pointer_flush = true;
|
|
||||||
|
|
||||||
return ACCESS_ONCE(s->pcm_buffer_pointer);
|
return ACCESS_ONCE(s->pcm_buffer_pointer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
* @CIP_BLOCKING: In blocking mode, each packet contains either zero or
|
* @CIP_BLOCKING: In blocking mode, each packet contains either zero or
|
||||||
* SYT_INTERVAL samples, with these two types alternating so that
|
* SYT_INTERVAL samples, with these two types alternating so that
|
||||||
* the overall sample rate comes out right.
|
* the overall sample rate comes out right.
|
||||||
* @CIP_SYNC_TO_DEVICE: In sync to device mode, time stamp in out packets is
|
|
||||||
* generated by in packets. Defaultly this driver generates timestamp.
|
|
||||||
* @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0.
|
* @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0.
|
||||||
* @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet
|
* @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet
|
||||||
* corresponds to the end of event in the packet. Out of IEC 61883.
|
* corresponds to the end of event in the packet. Out of IEC 61883.
|
||||||
|
@ -26,8 +24,6 @@
|
||||||
* The value of data_block_quadlets is used instead of reported value.
|
* The value of data_block_quadlets is used instead of reported value.
|
||||||
* @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is
|
* @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is
|
||||||
* skipped for detecting discontinuity.
|
* skipped for detecting discontinuity.
|
||||||
* @CIP_SKIP_INIT_DBC_CHECK: Only for in-stream. The value of dbc in first
|
|
||||||
* packet is not continuous from an initial value.
|
|
||||||
* @CIP_EMPTY_HAS_WRONG_DBC: Only for in-stream. The value of dbc in empty
|
* @CIP_EMPTY_HAS_WRONG_DBC: Only for in-stream. The value of dbc in empty
|
||||||
* packet is wrong but the others are correct.
|
* packet is wrong but the others are correct.
|
||||||
* @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an
|
* @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an
|
||||||
|
@ -37,14 +33,12 @@
|
||||||
enum cip_flags {
|
enum cip_flags {
|
||||||
CIP_NONBLOCKING = 0x00,
|
CIP_NONBLOCKING = 0x00,
|
||||||
CIP_BLOCKING = 0x01,
|
CIP_BLOCKING = 0x01,
|
||||||
CIP_SYNC_TO_DEVICE = 0x02,
|
CIP_EMPTY_WITH_TAG0 = 0x02,
|
||||||
CIP_EMPTY_WITH_TAG0 = 0x04,
|
CIP_DBC_IS_END_EVENT = 0x04,
|
||||||
CIP_DBC_IS_END_EVENT = 0x08,
|
CIP_WRONG_DBS = 0x08,
|
||||||
CIP_WRONG_DBS = 0x10,
|
CIP_SKIP_DBC_ZERO_CHECK = 0x10,
|
||||||
CIP_SKIP_DBC_ZERO_CHECK = 0x20,
|
CIP_EMPTY_HAS_WRONG_DBC = 0x20,
|
||||||
CIP_SKIP_INIT_DBC_CHECK = 0x40,
|
CIP_JUMBO_PAYLOAD = 0x40,
|
||||||
CIP_EMPTY_HAS_WRONG_DBC = 0x80,
|
|
||||||
CIP_JUMBO_PAYLOAD = 0x100,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -132,12 +126,10 @@ struct amdtp_stream {
|
||||||
struct tasklet_struct period_tasklet;
|
struct tasklet_struct period_tasklet;
|
||||||
unsigned int pcm_buffer_pointer;
|
unsigned int pcm_buffer_pointer;
|
||||||
unsigned int pcm_period_pointer;
|
unsigned int pcm_period_pointer;
|
||||||
bool pointer_flush;
|
|
||||||
|
|
||||||
/* To wait for first packet. */
|
/* To wait for first packet. */
|
||||||
bool callbacked;
|
bool callbacked;
|
||||||
wait_queue_head_t callback_wait;
|
wait_queue_head_t callback_wait;
|
||||||
struct amdtp_stream *sync_slave;
|
|
||||||
|
|
||||||
/* For backends to process data blocks. */
|
/* For backends to process data blocks. */
|
||||||
void *protocol;
|
void *protocol;
|
||||||
|
@ -223,23 +215,6 @@ static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc)
|
||||||
return sfc & 1;
|
return sfc & 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void amdtp_stream_set_sync(enum cip_flags sync_mode,
|
|
||||||
struct amdtp_stream *master,
|
|
||||||
struct amdtp_stream *slave)
|
|
||||||
{
|
|
||||||
if (sync_mode == CIP_SYNC_TO_DEVICE) {
|
|
||||||
master->flags |= CIP_SYNC_TO_DEVICE;
|
|
||||||
slave->flags |= CIP_SYNC_TO_DEVICE;
|
|
||||||
master->sync_slave = slave;
|
|
||||||
} else {
|
|
||||||
master->flags &= ~CIP_SYNC_TO_DEVICE;
|
|
||||||
slave->flags &= ~CIP_SYNC_TO_DEVICE;
|
|
||||||
master->sync_slave = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
slave->sync_slave = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* amdtp_stream_wait_callback - sleep till callbacked or timeout
|
* amdtp_stream_wait_callback - sleep till callbacked or timeout
|
||||||
* @s: the AMDTP stream
|
* @s: the AMDTP stream
|
||||||
|
|
|
@ -67,7 +67,7 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
|
||||||
#define MODEL_MAUDIO_PROJECTMIX 0x00010091
|
#define MODEL_MAUDIO_PROJECTMIX 0x00010091
|
||||||
|
|
||||||
static int
|
static int
|
||||||
name_device(struct snd_bebob *bebob, unsigned int vendor_id)
|
name_device(struct snd_bebob *bebob)
|
||||||
{
|
{
|
||||||
struct fw_device *fw_dev = fw_parent_device(bebob->unit);
|
struct fw_device *fw_dev = fw_parent_device(bebob->unit);
|
||||||
char vendor[24] = {0};
|
char vendor[24] = {0};
|
||||||
|
@ -126,6 +126,17 @@ end:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void bebob_free(struct snd_bebob *bebob)
|
||||||
|
{
|
||||||
|
snd_bebob_stream_destroy_duplex(bebob);
|
||||||
|
fw_unit_put(bebob->unit);
|
||||||
|
|
||||||
|
kfree(bebob->maudio_special_quirk);
|
||||||
|
|
||||||
|
mutex_destroy(&bebob->mutex);
|
||||||
|
kfree(bebob);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This module releases the FireWire unit data after all ALSA character devices
|
* This module releases the FireWire unit data after all ALSA character devices
|
||||||
* are released by applications. This is for releasing stream data or finishing
|
* are released by applications. This is for releasing stream data or finishing
|
||||||
|
@ -137,18 +148,11 @@ bebob_card_free(struct snd_card *card)
|
||||||
{
|
{
|
||||||
struct snd_bebob *bebob = card->private_data;
|
struct snd_bebob *bebob = card->private_data;
|
||||||
|
|
||||||
snd_bebob_stream_destroy_duplex(bebob);
|
mutex_lock(&devices_mutex);
|
||||||
fw_unit_put(bebob->unit);
|
clear_bit(bebob->card_index, devices_used);
|
||||||
|
mutex_unlock(&devices_mutex);
|
||||||
|
|
||||||
kfree(bebob->maudio_special_quirk);
|
bebob_free(card->private_data);
|
||||||
|
|
||||||
if (bebob->card_index >= 0) {
|
|
||||||
mutex_lock(&devices_mutex);
|
|
||||||
clear_bit(bebob->card_index, devices_used);
|
|
||||||
mutex_unlock(&devices_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_destroy(&bebob->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct snd_bebob_spec *
|
static const struct snd_bebob_spec *
|
||||||
|
@ -176,16 +180,17 @@ check_audiophile_booted(struct fw_unit *unit)
|
||||||
return strncmp(name, "FW Audiophile Bootloader", 15) != 0;
|
return strncmp(name, "FW Audiophile Bootloader", 15) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static void
|
||||||
bebob_probe(struct fw_unit *unit,
|
do_registration(struct work_struct *work)
|
||||||
const struct ieee1394_device_id *entry)
|
|
||||||
{
|
{
|
||||||
struct snd_card *card;
|
struct snd_bebob *bebob =
|
||||||
struct snd_bebob *bebob;
|
container_of(work, struct snd_bebob, dwork.work);
|
||||||
const struct snd_bebob_spec *spec;
|
|
||||||
unsigned int card_index;
|
unsigned int card_index;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
if (bebob->registered)
|
||||||
|
return;
|
||||||
|
|
||||||
mutex_lock(&devices_mutex);
|
mutex_lock(&devices_mutex);
|
||||||
|
|
||||||
for (card_index = 0; card_index < SNDRV_CARDS; card_index++) {
|
for (card_index = 0; card_index < SNDRV_CARDS; card_index++) {
|
||||||
|
@ -193,64 +198,39 @@ bebob_probe(struct fw_unit *unit,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (card_index >= SNDRV_CARDS) {
|
if (card_index >= SNDRV_CARDS) {
|
||||||
err = -ENOENT;
|
mutex_unlock(&devices_mutex);
|
||||||
goto end;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((entry->vendor_id == VEN_FOCUSRITE) &&
|
err = snd_card_new(&bebob->unit->device, index[card_index],
|
||||||
(entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH))
|
id[card_index], THIS_MODULE, 0, &bebob->card);
|
||||||
spec = get_saffire_spec(unit);
|
if (err < 0) {
|
||||||
else if ((entry->vendor_id == VEN_MAUDIO1) &&
|
mutex_unlock(&devices_mutex);
|
||||||
(entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH) &&
|
return;
|
||||||
!check_audiophile_booted(unit))
|
|
||||||
spec = NULL;
|
|
||||||
else
|
|
||||||
spec = (const struct snd_bebob_spec *)entry->driver_data;
|
|
||||||
|
|
||||||
if (spec == NULL) {
|
|
||||||
if ((entry->vendor_id == VEN_MAUDIO1) ||
|
|
||||||
(entry->vendor_id == VEN_MAUDIO2))
|
|
||||||
err = snd_bebob_maudio_load_firmware(unit);
|
|
||||||
else
|
|
||||||
err = -ENOSYS;
|
|
||||||
goto end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = snd_card_new(&unit->device, index[card_index], id[card_index],
|
err = name_device(bebob);
|
||||||
THIS_MODULE, sizeof(struct snd_bebob), &card);
|
|
||||||
if (err < 0)
|
|
||||||
goto end;
|
|
||||||
bebob = card->private_data;
|
|
||||||
bebob->card_index = card_index;
|
|
||||||
set_bit(card_index, devices_used);
|
|
||||||
card->private_free = bebob_card_free;
|
|
||||||
|
|
||||||
bebob->card = card;
|
|
||||||
bebob->unit = fw_unit_get(unit);
|
|
||||||
bebob->spec = spec;
|
|
||||||
mutex_init(&bebob->mutex);
|
|
||||||
spin_lock_init(&bebob->lock);
|
|
||||||
init_waitqueue_head(&bebob->hwdep_wait);
|
|
||||||
|
|
||||||
err = name_device(bebob, entry->vendor_id);
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
if ((entry->vendor_id == VEN_MAUDIO1) &&
|
if (bebob->spec == &maudio_special_spec) {
|
||||||
(entry->model_id == MODEL_MAUDIO_FW1814))
|
if (bebob->entry->model_id == MODEL_MAUDIO_FW1814)
|
||||||
err = snd_bebob_maudio_special_discover(bebob, true);
|
err = snd_bebob_maudio_special_discover(bebob, true);
|
||||||
else if ((entry->vendor_id == VEN_MAUDIO1) &&
|
else
|
||||||
(entry->model_id == MODEL_MAUDIO_PROJECTMIX))
|
err = snd_bebob_maudio_special_discover(bebob, false);
|
||||||
err = snd_bebob_maudio_special_discover(bebob, false);
|
} else {
|
||||||
else
|
|
||||||
err = snd_bebob_stream_discover(bebob);
|
err = snd_bebob_stream_discover(bebob);
|
||||||
|
}
|
||||||
|
if (err < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
err = snd_bebob_stream_init_duplex(bebob);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
snd_bebob_proc_init(bebob);
|
snd_bebob_proc_init(bebob);
|
||||||
|
|
||||||
if ((bebob->midi_input_ports > 0) ||
|
if (bebob->midi_input_ports > 0 || bebob->midi_output_ports > 0) {
|
||||||
(bebob->midi_output_ports > 0)) {
|
|
||||||
err = snd_bebob_create_midi_devices(bebob);
|
err = snd_bebob_create_midi_devices(bebob);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -264,16 +244,75 @@ bebob_probe(struct fw_unit *unit,
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = snd_bebob_stream_init_duplex(bebob);
|
err = snd_card_register(bebob->card);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
if (!bebob->maudio_special_quirk) {
|
set_bit(card_index, devices_used);
|
||||||
err = snd_card_register(card);
|
mutex_unlock(&devices_mutex);
|
||||||
if (err < 0) {
|
|
||||||
snd_bebob_stream_destroy_duplex(bebob);
|
/*
|
||||||
goto error;
|
* After registered, bebob instance can be released corresponding to
|
||||||
}
|
* releasing the sound card instance.
|
||||||
|
*/
|
||||||
|
bebob->card->private_free = bebob_card_free;
|
||||||
|
bebob->card->private_data = bebob;
|
||||||
|
bebob->registered = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
error:
|
||||||
|
mutex_unlock(&devices_mutex);
|
||||||
|
snd_bebob_stream_destroy_duplex(bebob);
|
||||||
|
snd_card_free(bebob->card);
|
||||||
|
dev_info(&bebob->unit->device,
|
||||||
|
"Sound card registration failed: %d\n", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
bebob_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
|
||||||
|
{
|
||||||
|
struct snd_bebob *bebob;
|
||||||
|
const struct snd_bebob_spec *spec;
|
||||||
|
|
||||||
|
if (entry->vendor_id == VEN_FOCUSRITE &&
|
||||||
|
entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)
|
||||||
|
spec = get_saffire_spec(unit);
|
||||||
|
else if (entry->vendor_id == VEN_MAUDIO1 &&
|
||||||
|
entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH &&
|
||||||
|
!check_audiophile_booted(unit))
|
||||||
|
spec = NULL;
|
||||||
|
else
|
||||||
|
spec = (const struct snd_bebob_spec *)entry->driver_data;
|
||||||
|
|
||||||
|
if (spec == NULL) {
|
||||||
|
if (entry->vendor_id == VEN_MAUDIO1 ||
|
||||||
|
entry->vendor_id == VEN_MAUDIO2)
|
||||||
|
return snd_bebob_maudio_load_firmware(unit);
|
||||||
|
else
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate this independent of sound card instance. */
|
||||||
|
bebob = kzalloc(sizeof(struct snd_bebob), GFP_KERNEL);
|
||||||
|
if (bebob == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
bebob->unit = fw_unit_get(unit);
|
||||||
|
bebob->entry = entry;
|
||||||
|
bebob->spec = spec;
|
||||||
|
dev_set_drvdata(&unit->device, bebob);
|
||||||
|
|
||||||
|
mutex_init(&bebob->mutex);
|
||||||
|
spin_lock_init(&bebob->lock);
|
||||||
|
init_waitqueue_head(&bebob->hwdep_wait);
|
||||||
|
|
||||||
|
/* Allocate and register this sound card later. */
|
||||||
|
INIT_DEFERRABLE_WORK(&bebob->dwork, do_registration);
|
||||||
|
|
||||||
|
if (entry->vendor_id != VEN_MAUDIO1 ||
|
||||||
|
(entry->model_id != MODEL_MAUDIO_FW1814 &&
|
||||||
|
entry->model_id != MODEL_MAUDIO_PROJECTMIX)) {
|
||||||
|
snd_fw_schedule_registration(unit, &bebob->dwork);
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* This is a workaround. This bus reset seems to have an effect
|
* This is a workaround. This bus reset seems to have an effect
|
||||||
|
@ -285,19 +324,11 @@ bebob_probe(struct fw_unit *unit,
|
||||||
* signals from dbus and starts I/Os. To avoid I/Os till the
|
* signals from dbus and starts I/Os. To avoid I/Os till the
|
||||||
* future bus reset, registration is done in next update().
|
* future bus reset, registration is done in next update().
|
||||||
*/
|
*/
|
||||||
bebob->deferred_registration = true;
|
|
||||||
fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card,
|
fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card,
|
||||||
false, true);
|
false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_set_drvdata(&unit->device, bebob);
|
return 0;
|
||||||
end:
|
|
||||||
mutex_unlock(&devices_mutex);
|
|
||||||
return err;
|
|
||||||
error:
|
|
||||||
mutex_unlock(&devices_mutex);
|
|
||||||
snd_card_free(card);
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -324,15 +355,11 @@ bebob_update(struct fw_unit *unit)
|
||||||
if (bebob == NULL)
|
if (bebob == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fcp_bus_reset(bebob->unit);
|
/* Postpone a workqueue for deferred registration. */
|
||||||
|
if (!bebob->registered)
|
||||||
if (bebob->deferred_registration) {
|
snd_fw_schedule_registration(unit, &bebob->dwork);
|
||||||
if (snd_card_register(bebob->card) < 0) {
|
else
|
||||||
snd_bebob_stream_destroy_duplex(bebob);
|
fcp_bus_reset(bebob->unit);
|
||||||
snd_card_free(bebob->card);
|
|
||||||
}
|
|
||||||
bebob->deferred_registration = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bebob_remove(struct fw_unit *unit)
|
static void bebob_remove(struct fw_unit *unit)
|
||||||
|
@ -342,8 +369,20 @@ static void bebob_remove(struct fw_unit *unit)
|
||||||
if (bebob == NULL)
|
if (bebob == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* No need to wait for releasing card object in this context. */
|
/*
|
||||||
snd_card_free_when_closed(bebob->card);
|
* Confirm to stop the work for registration before the sound card is
|
||||||
|
* going to be released. The work is not scheduled again because bus
|
||||||
|
* reset handler is not called anymore.
|
||||||
|
*/
|
||||||
|
cancel_delayed_work_sync(&bebob->dwork);
|
||||||
|
|
||||||
|
if (bebob->registered) {
|
||||||
|
/* No need to wait for releasing card object in this context. */
|
||||||
|
snd_card_free_when_closed(bebob->card);
|
||||||
|
} else {
|
||||||
|
/* Don't forget this case. */
|
||||||
|
bebob_free(bebob);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct snd_bebob_rate_spec normal_rate_spec = {
|
static const struct snd_bebob_rate_spec normal_rate_spec = {
|
||||||
|
|
|
@ -83,6 +83,10 @@ struct snd_bebob {
|
||||||
struct mutex mutex;
|
struct mutex mutex;
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
|
|
||||||
|
bool registered;
|
||||||
|
struct delayed_work dwork;
|
||||||
|
|
||||||
|
const struct ieee1394_device_id *entry;
|
||||||
const struct snd_bebob_spec *spec;
|
const struct snd_bebob_spec *spec;
|
||||||
|
|
||||||
unsigned int midi_input_ports;
|
unsigned int midi_input_ports;
|
||||||
|
@ -90,7 +94,6 @@ struct snd_bebob {
|
||||||
|
|
||||||
bool connected;
|
bool connected;
|
||||||
|
|
||||||
struct amdtp_stream *master;
|
|
||||||
struct amdtp_stream tx_stream;
|
struct amdtp_stream tx_stream;
|
||||||
struct amdtp_stream rx_stream;
|
struct amdtp_stream rx_stream;
|
||||||
struct cmp_connection out_conn;
|
struct cmp_connection out_conn;
|
||||||
|
@ -111,7 +114,6 @@ struct snd_bebob {
|
||||||
|
|
||||||
/* for M-Audio special devices */
|
/* for M-Audio special devices */
|
||||||
void *maudio_special_quirk;
|
void *maudio_special_quirk;
|
||||||
bool deferred_registration;
|
|
||||||
|
|
||||||
/* For BeBoB version quirk. */
|
/* For BeBoB version quirk. */
|
||||||
unsigned int version;
|
unsigned int version;
|
||||||
|
|
|
@ -483,30 +483,6 @@ destroy_both_connections(struct snd_bebob *bebob)
|
||||||
cmp_connection_destroy(&bebob->out_conn);
|
cmp_connection_destroy(&bebob->out_conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
get_sync_mode(struct snd_bebob *bebob, enum cip_flags *sync_mode)
|
|
||||||
{
|
|
||||||
enum snd_bebob_clock_type src;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = snd_bebob_stream_get_clock_src(bebob, &src);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
switch (src) {
|
|
||||||
case SND_BEBOB_CLOCK_TYPE_INTERNAL:
|
|
||||||
case SND_BEBOB_CLOCK_TYPE_EXTERNAL:
|
|
||||||
*sync_mode = CIP_SYNC_TO_DEVICE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
case SND_BEBOB_CLOCK_TYPE_SYT:
|
|
||||||
*sync_mode = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream,
|
start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream,
|
||||||
unsigned int rate)
|
unsigned int rate)
|
||||||
|
@ -550,8 +526,6 @@ int snd_bebob_stream_init_duplex(struct snd_bebob *bebob)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
bebob->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BeBoB v3 transfers packets with these qurks:
|
* BeBoB v3 transfers packets with these qurks:
|
||||||
* - In the beginning of streaming, the value of dbc is incremented
|
* - In the beginning of streaming, the value of dbc is incremented
|
||||||
|
@ -584,8 +558,6 @@ end:
|
||||||
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
|
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
|
||||||
{
|
{
|
||||||
const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
|
const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
|
||||||
struct amdtp_stream *master, *slave;
|
|
||||||
enum cip_flags sync_mode;
|
|
||||||
unsigned int curr_rate;
|
unsigned int curr_rate;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
|
@ -593,22 +565,11 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
|
||||||
if (bebob->substreams_counter == 0)
|
if (bebob->substreams_counter == 0)
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
err = get_sync_mode(bebob, &sync_mode);
|
|
||||||
if (err < 0)
|
|
||||||
goto end;
|
|
||||||
if (sync_mode == CIP_SYNC_TO_DEVICE) {
|
|
||||||
master = &bebob->tx_stream;
|
|
||||||
slave = &bebob->rx_stream;
|
|
||||||
} else {
|
|
||||||
master = &bebob->rx_stream;
|
|
||||||
slave = &bebob->tx_stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Considering JACK/FFADO streaming:
|
* Considering JACK/FFADO streaming:
|
||||||
* TODO: This can be removed hwdep functionality becomes popular.
|
* TODO: This can be removed hwdep functionality becomes popular.
|
||||||
*/
|
*/
|
||||||
err = check_connection_used_by_others(bebob, master);
|
err = check_connection_used_by_others(bebob, &bebob->rx_stream);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
|
@ -618,11 +579,12 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
|
||||||
* At bus reset, connections should not be broken here. So streams need
|
* At bus reset, connections should not be broken here. So streams need
|
||||||
* to be re-started. This is a reason to use SKIP_INIT_DBC_CHECK flag.
|
* to be re-started. This is a reason to use SKIP_INIT_DBC_CHECK flag.
|
||||||
*/
|
*/
|
||||||
if (amdtp_streaming_error(master))
|
if (amdtp_streaming_error(&bebob->rx_stream))
|
||||||
amdtp_stream_stop(master);
|
amdtp_stream_stop(&bebob->rx_stream);
|
||||||
if (amdtp_streaming_error(slave))
|
if (amdtp_streaming_error(&bebob->tx_stream))
|
||||||
amdtp_stream_stop(slave);
|
amdtp_stream_stop(&bebob->tx_stream);
|
||||||
if (!amdtp_stream_running(master) && !amdtp_stream_running(slave))
|
if (!amdtp_stream_running(&bebob->rx_stream) &&
|
||||||
|
!amdtp_stream_running(&bebob->tx_stream))
|
||||||
break_both_connections(bebob);
|
break_both_connections(bebob);
|
||||||
|
|
||||||
/* stop streams if rate is different */
|
/* stop streams if rate is different */
|
||||||
|
@ -635,16 +597,13 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
|
||||||
if (rate == 0)
|
if (rate == 0)
|
||||||
rate = curr_rate;
|
rate = curr_rate;
|
||||||
if (rate != curr_rate) {
|
if (rate != curr_rate) {
|
||||||
amdtp_stream_stop(master);
|
amdtp_stream_stop(&bebob->rx_stream);
|
||||||
amdtp_stream_stop(slave);
|
amdtp_stream_stop(&bebob->tx_stream);
|
||||||
break_both_connections(bebob);
|
break_both_connections(bebob);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* master should be always running */
|
/* master should be always running */
|
||||||
if (!amdtp_stream_running(master)) {
|
if (!amdtp_stream_running(&bebob->rx_stream)) {
|
||||||
amdtp_stream_set_sync(sync_mode, master, slave);
|
|
||||||
bebob->master = master;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NOTE:
|
* NOTE:
|
||||||
* If establishing connections at first, Yamaha GO46
|
* If establishing connections at first, Yamaha GO46
|
||||||
|
@ -666,7 +625,7 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
err = start_stream(bebob, master, rate);
|
err = start_stream(bebob, &bebob->rx_stream, rate);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
dev_err(&bebob->unit->device,
|
dev_err(&bebob->unit->device,
|
||||||
"fail to run AMDTP master stream:%d\n", err);
|
"fail to run AMDTP master stream:%d\n", err);
|
||||||
|
@ -685,15 +644,16 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
|
||||||
dev_err(&bebob->unit->device,
|
dev_err(&bebob->unit->device,
|
||||||
"fail to ensure sampling rate: %d\n",
|
"fail to ensure sampling rate: %d\n",
|
||||||
err);
|
err);
|
||||||
amdtp_stream_stop(master);
|
amdtp_stream_stop(&bebob->rx_stream);
|
||||||
break_both_connections(bebob);
|
break_both_connections(bebob);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* wait first callback */
|
/* wait first callback */
|
||||||
if (!amdtp_stream_wait_callback(master, CALLBACK_TIMEOUT)) {
|
if (!amdtp_stream_wait_callback(&bebob->rx_stream,
|
||||||
amdtp_stream_stop(master);
|
CALLBACK_TIMEOUT)) {
|
||||||
|
amdtp_stream_stop(&bebob->rx_stream);
|
||||||
break_both_connections(bebob);
|
break_both_connections(bebob);
|
||||||
err = -ETIMEDOUT;
|
err = -ETIMEDOUT;
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -701,20 +661,21 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* start slave if needed */
|
/* start slave if needed */
|
||||||
if (!amdtp_stream_running(slave)) {
|
if (!amdtp_stream_running(&bebob->tx_stream)) {
|
||||||
err = start_stream(bebob, slave, rate);
|
err = start_stream(bebob, &bebob->tx_stream, rate);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
dev_err(&bebob->unit->device,
|
dev_err(&bebob->unit->device,
|
||||||
"fail to run AMDTP slave stream:%d\n", err);
|
"fail to run AMDTP slave stream:%d\n", err);
|
||||||
amdtp_stream_stop(master);
|
amdtp_stream_stop(&bebob->rx_stream);
|
||||||
break_both_connections(bebob);
|
break_both_connections(bebob);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* wait first callback */
|
/* wait first callback */
|
||||||
if (!amdtp_stream_wait_callback(slave, CALLBACK_TIMEOUT)) {
|
if (!amdtp_stream_wait_callback(&bebob->tx_stream,
|
||||||
amdtp_stream_stop(slave);
|
CALLBACK_TIMEOUT)) {
|
||||||
amdtp_stream_stop(master);
|
amdtp_stream_stop(&bebob->tx_stream);
|
||||||
|
amdtp_stream_stop(&bebob->rx_stream);
|
||||||
break_both_connections(bebob);
|
break_both_connections(bebob);
|
||||||
err = -ETIMEDOUT;
|
err = -ETIMEDOUT;
|
||||||
}
|
}
|
||||||
|
@ -725,22 +686,12 @@ end:
|
||||||
|
|
||||||
void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob)
|
void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob)
|
||||||
{
|
{
|
||||||
struct amdtp_stream *master, *slave;
|
|
||||||
|
|
||||||
if (bebob->master == &bebob->rx_stream) {
|
|
||||||
slave = &bebob->tx_stream;
|
|
||||||
master = &bebob->rx_stream;
|
|
||||||
} else {
|
|
||||||
slave = &bebob->rx_stream;
|
|
||||||
master = &bebob->tx_stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bebob->substreams_counter == 0) {
|
if (bebob->substreams_counter == 0) {
|
||||||
amdtp_stream_pcm_abort(master);
|
amdtp_stream_pcm_abort(&bebob->rx_stream);
|
||||||
amdtp_stream_stop(master);
|
amdtp_stream_stop(&bebob->rx_stream);
|
||||||
|
|
||||||
amdtp_stream_pcm_abort(slave);
|
amdtp_stream_pcm_abort(&bebob->tx_stream);
|
||||||
amdtp_stream_stop(slave);
|
amdtp_stream_stop(&bebob->tx_stream);
|
||||||
|
|
||||||
break_both_connections(bebob);
|
break_both_connections(bebob);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,6 @@ MODULE_LICENSE("GPL v2");
|
||||||
#define WEISS_CATEGORY_ID 0x00
|
#define WEISS_CATEGORY_ID 0x00
|
||||||
#define LOUD_CATEGORY_ID 0x10
|
#define LOUD_CATEGORY_ID 0x10
|
||||||
|
|
||||||
#define PROBE_DELAY_MS (2 * MSEC_PER_SEC)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Some models support several isochronous channels, while these streams are not
|
* Some models support several isochronous channels, while these streams are not
|
||||||
* always available. In this case, add the model name to this list.
|
* always available. In this case, add the model name to this list.
|
||||||
|
@ -201,6 +199,10 @@ static void do_registration(struct work_struct *work)
|
||||||
|
|
||||||
dice_card_strings(dice);
|
dice_card_strings(dice);
|
||||||
|
|
||||||
|
err = snd_dice_stream_init_duplex(dice);
|
||||||
|
if (err < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
snd_dice_create_proc(dice);
|
snd_dice_create_proc(dice);
|
||||||
|
|
||||||
err = snd_dice_create_pcm(dice);
|
err = snd_dice_create_pcm(dice);
|
||||||
|
@ -229,28 +231,14 @@ static void do_registration(struct work_struct *work)
|
||||||
|
|
||||||
return;
|
return;
|
||||||
error:
|
error:
|
||||||
|
snd_dice_stream_destroy_duplex(dice);
|
||||||
snd_dice_transaction_destroy(dice);
|
snd_dice_transaction_destroy(dice);
|
||||||
|
snd_dice_stream_destroy_duplex(dice);
|
||||||
snd_card_free(dice->card);
|
snd_card_free(dice->card);
|
||||||
dev_info(&dice->unit->device,
|
dev_info(&dice->unit->device,
|
||||||
"Sound card registration failed: %d\n", err);
|
"Sound card registration failed: %d\n", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void schedule_registration(struct snd_dice *dice)
|
|
||||||
{
|
|
||||||
struct fw_card *fw_card = fw_parent_device(dice->unit)->card;
|
|
||||||
u64 now, delay;
|
|
||||||
|
|
||||||
now = get_jiffies_64();
|
|
||||||
delay = fw_card->reset_jiffies + msecs_to_jiffies(PROBE_DELAY_MS);
|
|
||||||
|
|
||||||
if (time_after64(delay, now))
|
|
||||||
delay -= now;
|
|
||||||
else
|
|
||||||
delay = 0;
|
|
||||||
|
|
||||||
mod_delayed_work(system_wq, &dice->dwork, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
|
static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
|
||||||
{
|
{
|
||||||
struct snd_dice *dice;
|
struct snd_dice *dice;
|
||||||
|
@ -273,15 +261,9 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
|
||||||
init_completion(&dice->clock_accepted);
|
init_completion(&dice->clock_accepted);
|
||||||
init_waitqueue_head(&dice->hwdep_wait);
|
init_waitqueue_head(&dice->hwdep_wait);
|
||||||
|
|
||||||
err = snd_dice_stream_init_duplex(dice);
|
|
||||||
if (err < 0) {
|
|
||||||
dice_free(dice);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate and register this sound card later. */
|
/* Allocate and register this sound card later. */
|
||||||
INIT_DEFERRABLE_WORK(&dice->dwork, do_registration);
|
INIT_DEFERRABLE_WORK(&dice->dwork, do_registration);
|
||||||
schedule_registration(dice);
|
snd_fw_schedule_registration(unit, &dice->dwork);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -312,7 +294,7 @@ static void dice_bus_reset(struct fw_unit *unit)
|
||||||
|
|
||||||
/* Postpone a workqueue for deferred registration. */
|
/* Postpone a workqueue for deferred registration. */
|
||||||
if (!dice->registered)
|
if (!dice->registered)
|
||||||
schedule_registration(dice);
|
snd_fw_schedule_registration(unit, &dice->dwork);
|
||||||
|
|
||||||
/* The handler address register becomes initialized. */
|
/* The handler address register becomes initialized. */
|
||||||
snd_dice_transaction_reinit(dice);
|
snd_dice_transaction_reinit(dice);
|
||||||
|
@ -335,6 +317,13 @@ static const struct ieee1394_device_id dice_id_table[] = {
|
||||||
.match_flags = IEEE1394_MATCH_VERSION,
|
.match_flags = IEEE1394_MATCH_VERSION,
|
||||||
.version = DICE_INTERFACE,
|
.version = DICE_INTERFACE,
|
||||||
},
|
},
|
||||||
|
/* M-Audio Profire 610/2626 has a different value in version field. */
|
||||||
|
{
|
||||||
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||||
|
IEEE1394_MATCH_SPECIFIER_ID,
|
||||||
|
.vendor_id = 0x000d6c,
|
||||||
|
.specifier_id = 0x000d6c,
|
||||||
|
},
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(ieee1394, dice_id_table);
|
MODULE_DEVICE_TABLE(ieee1394, dice_id_table);
|
||||||
|
|
|
@ -421,7 +421,7 @@ int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||||
|
|
||||||
/* Use different mode between incoming/outgoing. */
|
/* Use different mode between incoming/outgoing. */
|
||||||
if (dir == AMDTP_IN_STREAM) {
|
if (dir == AMDTP_IN_STREAM) {
|
||||||
flags = CIP_NONBLOCKING | CIP_SKIP_INIT_DBC_CHECK;
|
flags = CIP_NONBLOCKING;
|
||||||
process_data_blocks = process_tx_data_blocks;
|
process_data_blocks = process_tx_data_blocks;
|
||||||
} else {
|
} else {
|
||||||
flags = CIP_BLOCKING;
|
flags = CIP_BLOCKING;
|
||||||
|
|
|
@ -126,12 +126,17 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x)
|
||||||
return err;
|
return err;
|
||||||
error:
|
error:
|
||||||
fw_core_remove_address_handler(&dg00x->async_handler);
|
fw_core_remove_address_handler(&dg00x->async_handler);
|
||||||
dg00x->async_handler.address_callback = NULL;
|
dg00x->async_handler.callback_data = NULL;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x)
|
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x)
|
||||||
{
|
{
|
||||||
|
if (dg00x->async_handler.callback_data == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
snd_fw_async_midi_port_destroy(&dg00x->out_control);
|
snd_fw_async_midi_port_destroy(&dg00x->out_control);
|
||||||
fw_core_remove_address_handler(&dg00x->async_handler);
|
fw_core_remove_address_handler(&dg00x->async_handler);
|
||||||
|
|
||||||
|
dg00x->async_handler.callback_data = NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,10 +40,8 @@ static int name_card(struct snd_dg00x *dg00x)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dg00x_card_free(struct snd_card *card)
|
static void dg00x_free(struct snd_dg00x *dg00x)
|
||||||
{
|
{
|
||||||
struct snd_dg00x *dg00x = card->private_data;
|
|
||||||
|
|
||||||
snd_dg00x_stream_destroy_duplex(dg00x);
|
snd_dg00x_stream_destroy_duplex(dg00x);
|
||||||
snd_dg00x_transaction_unregister(dg00x);
|
snd_dg00x_transaction_unregister(dg00x);
|
||||||
|
|
||||||
|
@ -52,28 +50,24 @@ static void dg00x_card_free(struct snd_card *card)
|
||||||
mutex_destroy(&dg00x->mutex);
|
mutex_destroy(&dg00x->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int snd_dg00x_probe(struct fw_unit *unit,
|
static void dg00x_card_free(struct snd_card *card)
|
||||||
const struct ieee1394_device_id *entry)
|
|
||||||
{
|
{
|
||||||
struct snd_card *card;
|
dg00x_free(card->private_data);
|
||||||
struct snd_dg00x *dg00x;
|
}
|
||||||
|
|
||||||
|
static void do_registration(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct snd_dg00x *dg00x =
|
||||||
|
container_of(work, struct snd_dg00x, dwork.work);
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
/* create card */
|
if (dg00x->registered)
|
||||||
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
|
return;
|
||||||
sizeof(struct snd_dg00x), &card);
|
|
||||||
|
err = snd_card_new(&dg00x->unit->device, -1, NULL, THIS_MODULE, 0,
|
||||||
|
&dg00x->card);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return;
|
||||||
card->private_free = dg00x_card_free;
|
|
||||||
|
|
||||||
/* initialize myself */
|
|
||||||
dg00x = card->private_data;
|
|
||||||
dg00x->card = card;
|
|
||||||
dg00x->unit = fw_unit_get(unit);
|
|
||||||
|
|
||||||
mutex_init(&dg00x->mutex);
|
|
||||||
spin_lock_init(&dg00x->lock);
|
|
||||||
init_waitqueue_head(&dg00x->hwdep_wait);
|
|
||||||
|
|
||||||
err = name_card(dg00x);
|
err = name_card(dg00x);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
|
@ -101,35 +95,86 @@ static int snd_dg00x_probe(struct fw_unit *unit,
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = snd_card_register(card);
|
err = snd_card_register(dg00x->card);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
dg00x->card->private_free = dg00x_card_free;
|
||||||
|
dg00x->card->private_data = dg00x;
|
||||||
|
dg00x->registered = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
error:
|
||||||
|
snd_dg00x_transaction_unregister(dg00x);
|
||||||
|
snd_dg00x_stream_destroy_duplex(dg00x);
|
||||||
|
snd_card_free(dg00x->card);
|
||||||
|
dev_info(&dg00x->unit->device,
|
||||||
|
"Sound card registration failed: %d\n", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int snd_dg00x_probe(struct fw_unit *unit,
|
||||||
|
const struct ieee1394_device_id *entry)
|
||||||
|
{
|
||||||
|
struct snd_dg00x *dg00x;
|
||||||
|
|
||||||
|
/* Allocate this independent of sound card instance. */
|
||||||
|
dg00x = kzalloc(sizeof(struct snd_dg00x), GFP_KERNEL);
|
||||||
|
if (dg00x == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
dg00x->unit = fw_unit_get(unit);
|
||||||
dev_set_drvdata(&unit->device, dg00x);
|
dev_set_drvdata(&unit->device, dg00x);
|
||||||
|
|
||||||
return err;
|
mutex_init(&dg00x->mutex);
|
||||||
error:
|
spin_lock_init(&dg00x->lock);
|
||||||
snd_card_free(card);
|
init_waitqueue_head(&dg00x->hwdep_wait);
|
||||||
return err;
|
|
||||||
|
/* Allocate and register this sound card later. */
|
||||||
|
INIT_DEFERRABLE_WORK(&dg00x->dwork, do_registration);
|
||||||
|
snd_fw_schedule_registration(unit, &dg00x->dwork);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void snd_dg00x_update(struct fw_unit *unit)
|
static void snd_dg00x_update(struct fw_unit *unit)
|
||||||
{
|
{
|
||||||
struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
|
struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
|
||||||
|
|
||||||
|
/* Postpone a workqueue for deferred registration. */
|
||||||
|
if (!dg00x->registered)
|
||||||
|
snd_fw_schedule_registration(unit, &dg00x->dwork);
|
||||||
|
|
||||||
snd_dg00x_transaction_reregister(dg00x);
|
snd_dg00x_transaction_reregister(dg00x);
|
||||||
|
|
||||||
mutex_lock(&dg00x->mutex);
|
/*
|
||||||
snd_dg00x_stream_update_duplex(dg00x);
|
* After registration, userspace can start packet streaming, then this
|
||||||
mutex_unlock(&dg00x->mutex);
|
* code block works fine.
|
||||||
|
*/
|
||||||
|
if (dg00x->registered) {
|
||||||
|
mutex_lock(&dg00x->mutex);
|
||||||
|
snd_dg00x_stream_update_duplex(dg00x);
|
||||||
|
mutex_unlock(&dg00x->mutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void snd_dg00x_remove(struct fw_unit *unit)
|
static void snd_dg00x_remove(struct fw_unit *unit)
|
||||||
{
|
{
|
||||||
struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
|
struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
|
||||||
|
|
||||||
/* No need to wait for releasing card object in this context. */
|
/*
|
||||||
snd_card_free_when_closed(dg00x->card);
|
* Confirm to stop the work for registration before the sound card is
|
||||||
|
* going to be released. The work is not scheduled again because bus
|
||||||
|
* reset handler is not called anymore.
|
||||||
|
*/
|
||||||
|
cancel_delayed_work_sync(&dg00x->dwork);
|
||||||
|
|
||||||
|
if (dg00x->registered) {
|
||||||
|
/* No need to wait for releasing card object in this context. */
|
||||||
|
snd_card_free_when_closed(dg00x->card);
|
||||||
|
} else {
|
||||||
|
/* Don't forget this case. */
|
||||||
|
dg00x_free(dg00x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct ieee1394_device_id snd_dg00x_id_table[] = {
|
static const struct ieee1394_device_id snd_dg00x_id_table[] = {
|
||||||
|
|
|
@ -37,6 +37,9 @@ struct snd_dg00x {
|
||||||
struct mutex mutex;
|
struct mutex mutex;
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
|
|
||||||
|
bool registered;
|
||||||
|
struct delayed_work dwork;
|
||||||
|
|
||||||
struct amdtp_stream tx_stream;
|
struct amdtp_stream tx_stream;
|
||||||
struct fw_iso_resources tx_resources;
|
struct fw_iso_resources tx_resources;
|
||||||
|
|
||||||
|
|
|
@ -168,11 +168,34 @@ get_hardware_info(struct snd_efw *efw)
|
||||||
sizeof(struct snd_efw_phys_grp) * hwinfo->phys_in_grp_count);
|
sizeof(struct snd_efw_phys_grp) * hwinfo->phys_in_grp_count);
|
||||||
memcpy(&efw->phys_out_grps, hwinfo->phys_out_grps,
|
memcpy(&efw->phys_out_grps, hwinfo->phys_out_grps,
|
||||||
sizeof(struct snd_efw_phys_grp) * hwinfo->phys_out_grp_count);
|
sizeof(struct snd_efw_phys_grp) * hwinfo->phys_out_grp_count);
|
||||||
|
|
||||||
|
/* AudioFire8 (since 2009) and AudioFirePre8 */
|
||||||
|
if (hwinfo->type == MODEL_ECHO_AUDIOFIRE_9)
|
||||||
|
efw->is_af9 = true;
|
||||||
|
/* These models uses the same firmware. */
|
||||||
|
if (hwinfo->type == MODEL_ECHO_AUDIOFIRE_2 ||
|
||||||
|
hwinfo->type == MODEL_ECHO_AUDIOFIRE_4 ||
|
||||||
|
hwinfo->type == MODEL_ECHO_AUDIOFIRE_9 ||
|
||||||
|
hwinfo->type == MODEL_GIBSON_RIP ||
|
||||||
|
hwinfo->type == MODEL_GIBSON_GOLDTOP)
|
||||||
|
efw->is_fireworks3 = true;
|
||||||
end:
|
end:
|
||||||
kfree(hwinfo);
|
kfree(hwinfo);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void efw_free(struct snd_efw *efw)
|
||||||
|
{
|
||||||
|
snd_efw_stream_destroy_duplex(efw);
|
||||||
|
snd_efw_transaction_remove_instance(efw);
|
||||||
|
fw_unit_put(efw->unit);
|
||||||
|
|
||||||
|
kfree(efw->resp_buf);
|
||||||
|
|
||||||
|
mutex_destroy(&efw->mutex);
|
||||||
|
kfree(efw);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This module releases the FireWire unit data after all ALSA character devices
|
* This module releases the FireWire unit data after all ALSA character devices
|
||||||
* are released by applications. This is for releasing stream data or finishing
|
* are released by applications. This is for releasing stream data or finishing
|
||||||
|
@ -184,28 +207,24 @@ efw_card_free(struct snd_card *card)
|
||||||
{
|
{
|
||||||
struct snd_efw *efw = card->private_data;
|
struct snd_efw *efw = card->private_data;
|
||||||
|
|
||||||
snd_efw_stream_destroy_duplex(efw);
|
|
||||||
snd_efw_transaction_remove_instance(efw);
|
|
||||||
fw_unit_put(efw->unit);
|
|
||||||
|
|
||||||
kfree(efw->resp_buf);
|
|
||||||
|
|
||||||
if (efw->card_index >= 0) {
|
if (efw->card_index >= 0) {
|
||||||
mutex_lock(&devices_mutex);
|
mutex_lock(&devices_mutex);
|
||||||
clear_bit(efw->card_index, devices_used);
|
clear_bit(efw->card_index, devices_used);
|
||||||
mutex_unlock(&devices_mutex);
|
mutex_unlock(&devices_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_destroy(&efw->mutex);
|
efw_free(card->private_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static void
|
||||||
efw_probe(struct fw_unit *unit,
|
do_registration(struct work_struct *work)
|
||||||
const struct ieee1394_device_id *entry)
|
|
||||||
{
|
{
|
||||||
struct snd_card *card;
|
struct snd_efw *efw = container_of(work, struct snd_efw, dwork.work);
|
||||||
struct snd_efw *efw;
|
unsigned int card_index;
|
||||||
int card_index, err;
|
int err;
|
||||||
|
|
||||||
|
if (efw->registered)
|
||||||
|
return;
|
||||||
|
|
||||||
mutex_lock(&devices_mutex);
|
mutex_lock(&devices_mutex);
|
||||||
|
|
||||||
|
@ -215,24 +234,16 @@ efw_probe(struct fw_unit *unit,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (card_index >= SNDRV_CARDS) {
|
if (card_index >= SNDRV_CARDS) {
|
||||||
err = -ENOENT;
|
mutex_unlock(&devices_mutex);
|
||||||
goto end;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = snd_card_new(&unit->device, index[card_index], id[card_index],
|
err = snd_card_new(&efw->unit->device, index[card_index],
|
||||||
THIS_MODULE, sizeof(struct snd_efw), &card);
|
id[card_index], THIS_MODULE, 0, &efw->card);
|
||||||
if (err < 0)
|
if (err < 0) {
|
||||||
goto end;
|
mutex_unlock(&devices_mutex);
|
||||||
efw = card->private_data;
|
return;
|
||||||
efw->card_index = card_index;
|
}
|
||||||
set_bit(card_index, devices_used);
|
|
||||||
card->private_free = efw_card_free;
|
|
||||||
|
|
||||||
efw->card = card;
|
|
||||||
efw->unit = fw_unit_get(unit);
|
|
||||||
mutex_init(&efw->mutex);
|
|
||||||
spin_lock_init(&efw->lock);
|
|
||||||
init_waitqueue_head(&efw->hwdep_wait);
|
|
||||||
|
|
||||||
/* prepare response buffer */
|
/* prepare response buffer */
|
||||||
snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size,
|
snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size,
|
||||||
|
@ -248,16 +259,10 @@ efw_probe(struct fw_unit *unit,
|
||||||
err = get_hardware_info(efw);
|
err = get_hardware_info(efw);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
/* AudioFire8 (since 2009) and AudioFirePre8 */
|
|
||||||
if (entry->model_id == MODEL_ECHO_AUDIOFIRE_9)
|
err = snd_efw_stream_init_duplex(efw);
|
||||||
efw->is_af9 = true;
|
if (err < 0)
|
||||||
/* These models uses the same firmware. */
|
goto error;
|
||||||
if (entry->model_id == MODEL_ECHO_AUDIOFIRE_2 ||
|
|
||||||
entry->model_id == MODEL_ECHO_AUDIOFIRE_4 ||
|
|
||||||
entry->model_id == MODEL_ECHO_AUDIOFIRE_9 ||
|
|
||||||
entry->model_id == MODEL_GIBSON_RIP ||
|
|
||||||
entry->model_id == MODEL_GIBSON_GOLDTOP)
|
|
||||||
efw->is_fireworks3 = true;
|
|
||||||
|
|
||||||
snd_efw_proc_init(efw);
|
snd_efw_proc_init(efw);
|
||||||
|
|
||||||
|
@ -275,44 +280,93 @@ efw_probe(struct fw_unit *unit,
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = snd_efw_stream_init_duplex(efw);
|
err = snd_card_register(efw->card);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = snd_card_register(card);
|
set_bit(card_index, devices_used);
|
||||||
if (err < 0) {
|
mutex_unlock(&devices_mutex);
|
||||||
snd_efw_stream_destroy_duplex(efw);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
dev_set_drvdata(&unit->device, efw);
|
/*
|
||||||
end:
|
* After registered, efw instance can be released corresponding to
|
||||||
mutex_unlock(&devices_mutex);
|
* releasing the sound card instance.
|
||||||
return err;
|
*/
|
||||||
|
efw->card->private_free = efw_card_free;
|
||||||
|
efw->card->private_data = efw;
|
||||||
|
efw->registered = true;
|
||||||
|
|
||||||
|
return;
|
||||||
error:
|
error:
|
||||||
snd_efw_transaction_remove_instance(efw);
|
|
||||||
mutex_unlock(&devices_mutex);
|
mutex_unlock(&devices_mutex);
|
||||||
snd_card_free(card);
|
snd_efw_transaction_remove_instance(efw);
|
||||||
return err;
|
snd_efw_stream_destroy_duplex(efw);
|
||||||
|
snd_card_free(efw->card);
|
||||||
|
dev_info(&efw->unit->device,
|
||||||
|
"Sound card registration failed: %d\n", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
efw_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
|
||||||
|
{
|
||||||
|
struct snd_efw *efw;
|
||||||
|
|
||||||
|
efw = kzalloc(sizeof(struct snd_efw), GFP_KERNEL);
|
||||||
|
if (efw == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
efw->unit = fw_unit_get(unit);
|
||||||
|
dev_set_drvdata(&unit->device, efw);
|
||||||
|
|
||||||
|
mutex_init(&efw->mutex);
|
||||||
|
spin_lock_init(&efw->lock);
|
||||||
|
init_waitqueue_head(&efw->hwdep_wait);
|
||||||
|
|
||||||
|
/* Allocate and register this sound card later. */
|
||||||
|
INIT_DEFERRABLE_WORK(&efw->dwork, do_registration);
|
||||||
|
snd_fw_schedule_registration(unit, &efw->dwork);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void efw_update(struct fw_unit *unit)
|
static void efw_update(struct fw_unit *unit)
|
||||||
{
|
{
|
||||||
struct snd_efw *efw = dev_get_drvdata(&unit->device);
|
struct snd_efw *efw = dev_get_drvdata(&unit->device);
|
||||||
|
|
||||||
|
/* Postpone a workqueue for deferred registration. */
|
||||||
|
if (!efw->registered)
|
||||||
|
snd_fw_schedule_registration(unit, &efw->dwork);
|
||||||
|
|
||||||
snd_efw_transaction_bus_reset(efw->unit);
|
snd_efw_transaction_bus_reset(efw->unit);
|
||||||
|
|
||||||
mutex_lock(&efw->mutex);
|
/*
|
||||||
snd_efw_stream_update_duplex(efw);
|
* After registration, userspace can start packet streaming, then this
|
||||||
mutex_unlock(&efw->mutex);
|
* code block works fine.
|
||||||
|
*/
|
||||||
|
if (efw->registered) {
|
||||||
|
mutex_lock(&efw->mutex);
|
||||||
|
snd_efw_stream_update_duplex(efw);
|
||||||
|
mutex_unlock(&efw->mutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void efw_remove(struct fw_unit *unit)
|
static void efw_remove(struct fw_unit *unit)
|
||||||
{
|
{
|
||||||
struct snd_efw *efw = dev_get_drvdata(&unit->device);
|
struct snd_efw *efw = dev_get_drvdata(&unit->device);
|
||||||
|
|
||||||
/* No need to wait for releasing card object in this context. */
|
/*
|
||||||
snd_card_free_when_closed(efw->card);
|
* Confirm to stop the work for registration before the sound card is
|
||||||
|
* going to be released. The work is not scheduled again because bus
|
||||||
|
* reset handler is not called anymore.
|
||||||
|
*/
|
||||||
|
cancel_delayed_work_sync(&efw->dwork);
|
||||||
|
|
||||||
|
if (efw->registered) {
|
||||||
|
/* No need to wait for releasing card object in this context. */
|
||||||
|
snd_card_free_when_closed(efw->card);
|
||||||
|
} else {
|
||||||
|
/* Don't forget this case. */
|
||||||
|
efw_free(efw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct ieee1394_device_id efw_id_table[] = {
|
static const struct ieee1394_device_id efw_id_table[] = {
|
||||||
|
|
|
@ -65,6 +65,9 @@ struct snd_efw {
|
||||||
struct mutex mutex;
|
struct mutex mutex;
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
|
|
||||||
|
bool registered;
|
||||||
|
struct delayed_work dwork;
|
||||||
|
|
||||||
/* for transaction */
|
/* for transaction */
|
||||||
u32 seqnum;
|
u32 seqnum;
|
||||||
bool resp_addr_changable;
|
bool resp_addr_changable;
|
||||||
|
@ -81,7 +84,6 @@ struct snd_efw {
|
||||||
unsigned int pcm_capture_channels[SND_EFW_MULTIPLIER_MODES];
|
unsigned int pcm_capture_channels[SND_EFW_MULTIPLIER_MODES];
|
||||||
unsigned int pcm_playback_channels[SND_EFW_MULTIPLIER_MODES];
|
unsigned int pcm_playback_channels[SND_EFW_MULTIPLIER_MODES];
|
||||||
|
|
||||||
struct amdtp_stream *master;
|
|
||||||
struct amdtp_stream tx_stream;
|
struct amdtp_stream tx_stream;
|
||||||
struct amdtp_stream rx_stream;
|
struct amdtp_stream rx_stream;
|
||||||
struct cmp_connection out_conn;
|
struct cmp_connection out_conn;
|
||||||
|
|
|
@ -120,23 +120,6 @@ destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream)
|
||||||
cmp_connection_destroy(&efw->out_conn);
|
cmp_connection_destroy(&efw->out_conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
get_sync_mode(struct snd_efw *efw, enum cip_flags *sync_mode)
|
|
||||||
{
|
|
||||||
enum snd_efw_clock_source clock_source;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = snd_efw_command_get_clock_source(efw, &clock_source);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
if (clock_source == SND_EFW_CLOCK_SOURCE_SYTMATCH)
|
|
||||||
return -ENOSYS;
|
|
||||||
|
|
||||||
*sync_mode = CIP_SYNC_TO_DEVICE;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s)
|
check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s)
|
||||||
{
|
{
|
||||||
|
@ -208,9 +191,6 @@ end:
|
||||||
|
|
||||||
int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
|
int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
|
||||||
{
|
{
|
||||||
struct amdtp_stream *master, *slave;
|
|
||||||
unsigned int slave_substreams;
|
|
||||||
enum cip_flags sync_mode;
|
|
||||||
unsigned int curr_rate;
|
unsigned int curr_rate;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
|
@ -218,32 +198,19 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
|
||||||
if (efw->playback_substreams == 0 && efw->capture_substreams == 0)
|
if (efw->playback_substreams == 0 && efw->capture_substreams == 0)
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
err = get_sync_mode(efw, &sync_mode);
|
|
||||||
if (err < 0)
|
|
||||||
goto end;
|
|
||||||
if (sync_mode == CIP_SYNC_TO_DEVICE) {
|
|
||||||
master = &efw->tx_stream;
|
|
||||||
slave = &efw->rx_stream;
|
|
||||||
slave_substreams = efw->playback_substreams;
|
|
||||||
} else {
|
|
||||||
master = &efw->rx_stream;
|
|
||||||
slave = &efw->tx_stream;
|
|
||||||
slave_substreams = efw->capture_substreams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Considering JACK/FFADO streaming:
|
* Considering JACK/FFADO streaming:
|
||||||
* TODO: This can be removed hwdep functionality becomes popular.
|
* TODO: This can be removed hwdep functionality becomes popular.
|
||||||
*/
|
*/
|
||||||
err = check_connection_used_by_others(efw, master);
|
err = check_connection_used_by_others(efw, &efw->rx_stream);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
/* packet queueing error */
|
/* packet queueing error */
|
||||||
if (amdtp_streaming_error(slave))
|
if (amdtp_streaming_error(&efw->tx_stream))
|
||||||
stop_stream(efw, slave);
|
stop_stream(efw, &efw->tx_stream);
|
||||||
if (amdtp_streaming_error(master))
|
if (amdtp_streaming_error(&efw->rx_stream))
|
||||||
stop_stream(efw, master);
|
stop_stream(efw, &efw->rx_stream);
|
||||||
|
|
||||||
/* stop streams if rate is different */
|
/* stop streams if rate is different */
|
||||||
err = snd_efw_command_get_sampling_rate(efw, &curr_rate);
|
err = snd_efw_command_get_sampling_rate(efw, &curr_rate);
|
||||||
|
@ -252,20 +219,17 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
|
||||||
if (rate == 0)
|
if (rate == 0)
|
||||||
rate = curr_rate;
|
rate = curr_rate;
|
||||||
if (rate != curr_rate) {
|
if (rate != curr_rate) {
|
||||||
stop_stream(efw, slave);
|
stop_stream(efw, &efw->tx_stream);
|
||||||
stop_stream(efw, master);
|
stop_stream(efw, &efw->rx_stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* master should be always running */
|
/* master should be always running */
|
||||||
if (!amdtp_stream_running(master)) {
|
if (!amdtp_stream_running(&efw->rx_stream)) {
|
||||||
amdtp_stream_set_sync(sync_mode, master, slave);
|
|
||||||
efw->master = master;
|
|
||||||
|
|
||||||
err = snd_efw_command_set_sampling_rate(efw, rate);
|
err = snd_efw_command_set_sampling_rate(efw, rate);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
err = start_stream(efw, master, rate);
|
err = start_stream(efw, &efw->rx_stream, rate);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
dev_err(&efw->unit->device,
|
dev_err(&efw->unit->device,
|
||||||
"fail to start AMDTP master stream:%d\n", err);
|
"fail to start AMDTP master stream:%d\n", err);
|
||||||
|
@ -274,12 +238,13 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* start slave if needed */
|
/* start slave if needed */
|
||||||
if (slave_substreams > 0 && !amdtp_stream_running(slave)) {
|
if (efw->capture_substreams > 0 &&
|
||||||
err = start_stream(efw, slave, rate);
|
!amdtp_stream_running(&efw->tx_stream)) {
|
||||||
|
err = start_stream(efw, &efw->tx_stream, rate);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
dev_err(&efw->unit->device,
|
dev_err(&efw->unit->device,
|
||||||
"fail to start AMDTP slave stream:%d\n", err);
|
"fail to start AMDTP slave stream:%d\n", err);
|
||||||
stop_stream(efw, master);
|
stop_stream(efw, &efw->rx_stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end:
|
end:
|
||||||
|
@ -288,26 +253,11 @@ end:
|
||||||
|
|
||||||
void snd_efw_stream_stop_duplex(struct snd_efw *efw)
|
void snd_efw_stream_stop_duplex(struct snd_efw *efw)
|
||||||
{
|
{
|
||||||
struct amdtp_stream *master, *slave;
|
if (efw->capture_substreams == 0) {
|
||||||
unsigned int master_substreams, slave_substreams;
|
stop_stream(efw, &efw->tx_stream);
|
||||||
|
|
||||||
if (efw->master == &efw->rx_stream) {
|
if (efw->playback_substreams == 0)
|
||||||
slave = &efw->tx_stream;
|
stop_stream(efw, &efw->rx_stream);
|
||||||
master = &efw->rx_stream;
|
|
||||||
slave_substreams = efw->capture_substreams;
|
|
||||||
master_substreams = efw->playback_substreams;
|
|
||||||
} else {
|
|
||||||
slave = &efw->rx_stream;
|
|
||||||
master = &efw->tx_stream;
|
|
||||||
slave_substreams = efw->playback_substreams;
|
|
||||||
master_substreams = efw->capture_substreams;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slave_substreams == 0) {
|
|
||||||
stop_stream(efw, slave);
|
|
||||||
|
|
||||||
if (master_substreams == 0)
|
|
||||||
stop_stream(efw, master);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,38 @@ int snd_fw_transaction(struct fw_unit *unit, int tcode,
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_fw_transaction);
|
EXPORT_SYMBOL(snd_fw_transaction);
|
||||||
|
|
||||||
|
#define PROBE_DELAY_MS (2 * MSEC_PER_SEC)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* snd_fw_schedule_registration - schedule work for sound card registration
|
||||||
|
* @unit: an instance for unit on IEEE 1394 bus
|
||||||
|
* @dwork: delayed work with callback function
|
||||||
|
*
|
||||||
|
* This function is not designed for general purposes. When new unit is
|
||||||
|
* connected to IEEE 1394 bus, the bus is under bus-reset state because of
|
||||||
|
* topological change. In this state, units tend to fail both of asynchronous
|
||||||
|
* and isochronous communication. To avoid this problem, this function is used
|
||||||
|
* to postpone sound card registration after the state. The callers must
|
||||||
|
* set up instance of delayed work in advance.
|
||||||
|
*/
|
||||||
|
void snd_fw_schedule_registration(struct fw_unit *unit,
|
||||||
|
struct delayed_work *dwork)
|
||||||
|
{
|
||||||
|
u64 now, delay;
|
||||||
|
|
||||||
|
now = get_jiffies_64();
|
||||||
|
delay = fw_parent_device(unit)->card->reset_jiffies
|
||||||
|
+ msecs_to_jiffies(PROBE_DELAY_MS);
|
||||||
|
|
||||||
|
if (time_after64(delay, now))
|
||||||
|
delay -= now;
|
||||||
|
else
|
||||||
|
delay = 0;
|
||||||
|
|
||||||
|
mod_delayed_work(system_wq, dwork, delay);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(snd_fw_schedule_registration);
|
||||||
|
|
||||||
static void async_midi_port_callback(struct fw_card *card, int rcode,
|
static void async_midi_port_callback(struct fw_card *card, int rcode,
|
||||||
void *data, size_t length,
|
void *data, size_t length,
|
||||||
void *callback_data)
|
void *callback_data)
|
||||||
|
|
|
@ -22,6 +22,9 @@ static inline bool rcode_is_permanent_error(int rcode)
|
||||||
return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR;
|
return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void snd_fw_schedule_registration(struct fw_unit *unit,
|
||||||
|
struct delayed_work *dwork);
|
||||||
|
|
||||||
struct snd_fw_async_midi_port;
|
struct snd_fw_async_midi_port;
|
||||||
typedef int (*snd_fw_async_midi_port_fill)(
|
typedef int (*snd_fw_async_midi_port_fill)(
|
||||||
struct snd_rawmidi_substream *substream,
|
struct snd_rawmidi_substream *substream,
|
||||||
|
|
|
@ -242,8 +242,7 @@ int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw,
|
||||||
* blocks than IEC 61883-6 defines.
|
* blocks than IEC 61883-6 defines.
|
||||||
*/
|
*/
|
||||||
if (stream == &oxfw->tx_stream) {
|
if (stream == &oxfw->tx_stream) {
|
||||||
oxfw->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK |
|
oxfw->tx_stream.flags |= CIP_JUMBO_PAYLOAD;
|
||||||
CIP_JUMBO_PAYLOAD;
|
|
||||||
if (oxfw->wrong_dbs)
|
if (oxfw->wrong_dbs)
|
||||||
oxfw->tx_stream.flags |= CIP_WRONG_DBS;
|
oxfw->tx_stream.flags |= CIP_WRONG_DBS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,15 +118,8 @@ end:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static void oxfw_free(struct snd_oxfw *oxfw)
|
||||||
* This module releases the FireWire unit data after all ALSA character devices
|
|
||||||
* are released by applications. This is for releasing stream data or finishing
|
|
||||||
* transactions safely. Thus at returning from .remove(), this module still keep
|
|
||||||
* references for the unit.
|
|
||||||
*/
|
|
||||||
static void oxfw_card_free(struct snd_card *card)
|
|
||||||
{
|
{
|
||||||
struct snd_oxfw *oxfw = card->private_data;
|
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
|
||||||
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream);
|
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream);
|
||||||
|
@ -144,6 +137,17 @@ static void oxfw_card_free(struct snd_card *card)
|
||||||
mutex_destroy(&oxfw->mutex);
|
mutex_destroy(&oxfw->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This module releases the FireWire unit data after all ALSA character devices
|
||||||
|
* are released by applications. This is for releasing stream data or finishing
|
||||||
|
* transactions safely. Thus at returning from .remove(), this module still keep
|
||||||
|
* references for the unit.
|
||||||
|
*/
|
||||||
|
static void oxfw_card_free(struct snd_card *card)
|
||||||
|
{
|
||||||
|
oxfw_free(card->private_data);
|
||||||
|
}
|
||||||
|
|
||||||
static int detect_quirks(struct snd_oxfw *oxfw)
|
static int detect_quirks(struct snd_oxfw *oxfw)
|
||||||
{
|
{
|
||||||
struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
|
struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
|
||||||
|
@ -205,33 +209,18 @@ static int detect_quirks(struct snd_oxfw *oxfw)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int oxfw_probe(struct fw_unit *unit,
|
static void do_registration(struct work_struct *work)
|
||||||
const struct ieee1394_device_id *entry)
|
|
||||||
{
|
{
|
||||||
struct snd_card *card;
|
struct snd_oxfw *oxfw = container_of(work, struct snd_oxfw, dwork.work);
|
||||||
struct snd_oxfw *oxfw;
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (entry->vendor_id == VENDOR_LOUD && !detect_loud_models(unit))
|
if (oxfw->registered)
|
||||||
return -ENODEV;
|
return;
|
||||||
|
|
||||||
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
|
err = snd_card_new(&oxfw->unit->device, -1, NULL, THIS_MODULE, 0,
|
||||||
sizeof(*oxfw), &card);
|
&oxfw->card);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return;
|
||||||
|
|
||||||
card->private_free = oxfw_card_free;
|
|
||||||
oxfw = card->private_data;
|
|
||||||
oxfw->card = card;
|
|
||||||
mutex_init(&oxfw->mutex);
|
|
||||||
oxfw->unit = fw_unit_get(unit);
|
|
||||||
oxfw->entry = entry;
|
|
||||||
spin_lock_init(&oxfw->lock);
|
|
||||||
init_waitqueue_head(&oxfw->hwdep_wait);
|
|
||||||
|
|
||||||
err = snd_oxfw_stream_discover(oxfw);
|
|
||||||
if (err < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
err = name_card(oxfw);
|
err = name_card(oxfw);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
|
@ -241,6 +230,19 @@ static int oxfw_probe(struct fw_unit *unit,
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
err = snd_oxfw_stream_discover(oxfw);
|
||||||
|
if (err < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream);
|
||||||
|
if (err < 0)
|
||||||
|
goto error;
|
||||||
|
if (oxfw->has_output) {
|
||||||
|
err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->tx_stream);
|
||||||
|
if (err < 0)
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
err = snd_oxfw_create_pcm(oxfw);
|
err = snd_oxfw_create_pcm(oxfw);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -255,54 +257,97 @@ static int oxfw_probe(struct fw_unit *unit,
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream);
|
err = snd_card_register(oxfw->card);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
if (oxfw->has_output) {
|
|
||||||
err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->tx_stream);
|
|
||||||
if (err < 0)
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = snd_card_register(card);
|
/*
|
||||||
if (err < 0) {
|
* After registered, oxfw instance can be released corresponding to
|
||||||
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream);
|
* releasing the sound card instance.
|
||||||
if (oxfw->has_output)
|
*/
|
||||||
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream);
|
oxfw->card->private_free = oxfw_card_free;
|
||||||
goto error;
|
oxfw->card->private_data = oxfw;
|
||||||
}
|
oxfw->registered = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
error:
|
||||||
|
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream);
|
||||||
|
if (oxfw->has_output)
|
||||||
|
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream);
|
||||||
|
snd_card_free(oxfw->card);
|
||||||
|
dev_info(&oxfw->unit->device,
|
||||||
|
"Sound card registration failed: %d\n", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int oxfw_probe(struct fw_unit *unit,
|
||||||
|
const struct ieee1394_device_id *entry)
|
||||||
|
{
|
||||||
|
struct snd_oxfw *oxfw;
|
||||||
|
|
||||||
|
if (entry->vendor_id == VENDOR_LOUD && !detect_loud_models(unit))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/* Allocate this independent of sound card instance. */
|
||||||
|
oxfw = kzalloc(sizeof(struct snd_oxfw), GFP_KERNEL);
|
||||||
|
if (oxfw == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
oxfw->entry = entry;
|
||||||
|
oxfw->unit = fw_unit_get(unit);
|
||||||
dev_set_drvdata(&unit->device, oxfw);
|
dev_set_drvdata(&unit->device, oxfw);
|
||||||
|
|
||||||
|
mutex_init(&oxfw->mutex);
|
||||||
|
spin_lock_init(&oxfw->lock);
|
||||||
|
init_waitqueue_head(&oxfw->hwdep_wait);
|
||||||
|
|
||||||
|
/* Allocate and register this sound card later. */
|
||||||
|
INIT_DEFERRABLE_WORK(&oxfw->dwork, do_registration);
|
||||||
|
snd_fw_schedule_registration(unit, &oxfw->dwork);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
error:
|
|
||||||
snd_card_free(card);
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void oxfw_bus_reset(struct fw_unit *unit)
|
static void oxfw_bus_reset(struct fw_unit *unit)
|
||||||
{
|
{
|
||||||
struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
|
struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
|
||||||
|
|
||||||
|
if (!oxfw->registered)
|
||||||
|
snd_fw_schedule_registration(unit, &oxfw->dwork);
|
||||||
|
|
||||||
fcp_bus_reset(oxfw->unit);
|
fcp_bus_reset(oxfw->unit);
|
||||||
|
|
||||||
mutex_lock(&oxfw->mutex);
|
if (oxfw->registered) {
|
||||||
|
mutex_lock(&oxfw->mutex);
|
||||||
|
|
||||||
snd_oxfw_stream_update_simplex(oxfw, &oxfw->rx_stream);
|
snd_oxfw_stream_update_simplex(oxfw, &oxfw->rx_stream);
|
||||||
if (oxfw->has_output)
|
if (oxfw->has_output)
|
||||||
snd_oxfw_stream_update_simplex(oxfw, &oxfw->tx_stream);
|
snd_oxfw_stream_update_simplex(oxfw, &oxfw->tx_stream);
|
||||||
|
|
||||||
mutex_unlock(&oxfw->mutex);
|
mutex_unlock(&oxfw->mutex);
|
||||||
|
|
||||||
if (oxfw->entry->vendor_id == OUI_STANTON)
|
if (oxfw->entry->vendor_id == OUI_STANTON)
|
||||||
snd_oxfw_scs1x_update(oxfw);
|
snd_oxfw_scs1x_update(oxfw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void oxfw_remove(struct fw_unit *unit)
|
static void oxfw_remove(struct fw_unit *unit)
|
||||||
{
|
{
|
||||||
struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
|
struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
|
||||||
|
|
||||||
/* No need to wait for releasing card object in this context. */
|
/*
|
||||||
snd_card_free_when_closed(oxfw->card);
|
* Confirm to stop the work for registration before the sound card is
|
||||||
|
* going to be released. The work is not scheduled again because bus
|
||||||
|
* reset handler is not called anymore.
|
||||||
|
*/
|
||||||
|
cancel_delayed_work_sync(&oxfw->dwork);
|
||||||
|
|
||||||
|
if (oxfw->registered) {
|
||||||
|
/* No need to wait for releasing card object in this context. */
|
||||||
|
snd_card_free_when_closed(oxfw->card);
|
||||||
|
} else {
|
||||||
|
/* Don't forget this case. */
|
||||||
|
oxfw_free(oxfw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct compat_info griffin_firewave = {
|
static const struct compat_info griffin_firewave = {
|
||||||
|
|
|
@ -36,10 +36,12 @@
|
||||||
struct snd_oxfw {
|
struct snd_oxfw {
|
||||||
struct snd_card *card;
|
struct snd_card *card;
|
||||||
struct fw_unit *unit;
|
struct fw_unit *unit;
|
||||||
const struct device_info *device_info;
|
|
||||||
struct mutex mutex;
|
struct mutex mutex;
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
|
|
||||||
|
bool registered;
|
||||||
|
struct delayed_work dwork;
|
||||||
|
|
||||||
bool wrong_dbs;
|
bool wrong_dbs;
|
||||||
bool has_output;
|
bool has_output;
|
||||||
u8 *tx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES];
|
u8 *tx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES];
|
||||||
|
|
|
@ -381,19 +381,17 @@ int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate)
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
if (curr_rate != rate ||
|
if (curr_rate != rate ||
|
||||||
amdtp_streaming_error(&tscm->tx_stream) ||
|
amdtp_streaming_error(&tscm->rx_stream) ||
|
||||||
amdtp_streaming_error(&tscm->rx_stream)) {
|
amdtp_streaming_error(&tscm->tx_stream)) {
|
||||||
finish_session(tscm);
|
finish_session(tscm);
|
||||||
|
|
||||||
amdtp_stream_stop(&tscm->tx_stream);
|
|
||||||
amdtp_stream_stop(&tscm->rx_stream);
|
amdtp_stream_stop(&tscm->rx_stream);
|
||||||
|
amdtp_stream_stop(&tscm->tx_stream);
|
||||||
|
|
||||||
release_resources(tscm);
|
release_resources(tscm);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!amdtp_stream_running(&tscm->tx_stream)) {
|
if (!amdtp_stream_running(&tscm->rx_stream)) {
|
||||||
amdtp_stream_set_sync(CIP_SYNC_TO_DEVICE,
|
|
||||||
&tscm->tx_stream, &tscm->rx_stream);
|
|
||||||
err = keep_resources(tscm, rate);
|
err = keep_resources(tscm, rate);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -406,20 +404,6 @@ int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate)
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = amdtp_stream_start(&tscm->tx_stream,
|
|
||||||
tscm->tx_resources.channel,
|
|
||||||
fw_parent_device(tscm->unit)->max_speed);
|
|
||||||
if (err < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
if (!amdtp_stream_wait_callback(&tscm->tx_stream,
|
|
||||||
CALLBACK_TIMEOUT)) {
|
|
||||||
err = -ETIMEDOUT;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!amdtp_stream_running(&tscm->rx_stream)) {
|
|
||||||
err = amdtp_stream_start(&tscm->rx_stream,
|
err = amdtp_stream_start(&tscm->rx_stream,
|
||||||
tscm->rx_resources.channel,
|
tscm->rx_resources.channel,
|
||||||
fw_parent_device(tscm->unit)->max_speed);
|
fw_parent_device(tscm->unit)->max_speed);
|
||||||
|
@ -433,10 +417,24 @@ int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!amdtp_stream_running(&tscm->tx_stream)) {
|
||||||
|
err = amdtp_stream_start(&tscm->tx_stream,
|
||||||
|
tscm->tx_resources.channel,
|
||||||
|
fw_parent_device(tscm->unit)->max_speed);
|
||||||
|
if (err < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
if (!amdtp_stream_wait_callback(&tscm->tx_stream,
|
||||||
|
CALLBACK_TIMEOUT)) {
|
||||||
|
err = -ETIMEDOUT;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
error:
|
error:
|
||||||
amdtp_stream_stop(&tscm->tx_stream);
|
|
||||||
amdtp_stream_stop(&tscm->rx_stream);
|
amdtp_stream_stop(&tscm->rx_stream);
|
||||||
|
amdtp_stream_stop(&tscm->tx_stream);
|
||||||
|
|
||||||
finish_session(tscm);
|
finish_session(tscm);
|
||||||
release_resources(tscm);
|
release_resources(tscm);
|
||||||
|
|
|
@ -85,10 +85,8 @@ static int identify_model(struct snd_tscm *tscm)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tscm_card_free(struct snd_card *card)
|
static void tscm_free(struct snd_tscm *tscm)
|
||||||
{
|
{
|
||||||
struct snd_tscm *tscm = card->private_data;
|
|
||||||
|
|
||||||
snd_tscm_transaction_unregister(tscm);
|
snd_tscm_transaction_unregister(tscm);
|
||||||
snd_tscm_stream_destroy_duplex(tscm);
|
snd_tscm_stream_destroy_duplex(tscm);
|
||||||
|
|
||||||
|
@ -97,44 +95,36 @@ static void tscm_card_free(struct snd_card *card)
|
||||||
mutex_destroy(&tscm->mutex);
|
mutex_destroy(&tscm->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int snd_tscm_probe(struct fw_unit *unit,
|
static void tscm_card_free(struct snd_card *card)
|
||||||
const struct ieee1394_device_id *entry)
|
|
||||||
{
|
{
|
||||||
struct snd_card *card;
|
tscm_free(card->private_data);
|
||||||
struct snd_tscm *tscm;
|
}
|
||||||
|
|
||||||
|
static void do_registration(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct snd_tscm *tscm = container_of(work, struct snd_tscm, dwork.work);
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
/* create card */
|
err = snd_card_new(&tscm->unit->device, -1, NULL, THIS_MODULE, 0,
|
||||||
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
|
&tscm->card);
|
||||||
sizeof(struct snd_tscm), &card);
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return;
|
||||||
card->private_free = tscm_card_free;
|
|
||||||
|
|
||||||
/* initialize myself */
|
|
||||||
tscm = card->private_data;
|
|
||||||
tscm->card = card;
|
|
||||||
tscm->unit = fw_unit_get(unit);
|
|
||||||
|
|
||||||
mutex_init(&tscm->mutex);
|
|
||||||
spin_lock_init(&tscm->lock);
|
|
||||||
init_waitqueue_head(&tscm->hwdep_wait);
|
|
||||||
|
|
||||||
err = identify_model(tscm);
|
err = identify_model(tscm);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
snd_tscm_proc_init(tscm);
|
err = snd_tscm_transaction_register(tscm);
|
||||||
|
if (err < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
err = snd_tscm_stream_init_duplex(tscm);
|
err = snd_tscm_stream_init_duplex(tscm);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = snd_tscm_create_pcm_devices(tscm);
|
snd_tscm_proc_init(tscm);
|
||||||
if (err < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
err = snd_tscm_transaction_register(tscm);
|
err = snd_tscm_create_pcm_devices(tscm);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
@ -146,35 +136,91 @@ static int snd_tscm_probe(struct fw_unit *unit,
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = snd_card_register(card);
|
err = snd_card_register(tscm->card);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After registered, tscm instance can be released corresponding to
|
||||||
|
* releasing the sound card instance.
|
||||||
|
*/
|
||||||
|
tscm->card->private_free = tscm_card_free;
|
||||||
|
tscm->card->private_data = tscm;
|
||||||
|
tscm->registered = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
error:
|
||||||
|
snd_tscm_transaction_unregister(tscm);
|
||||||
|
snd_tscm_stream_destroy_duplex(tscm);
|
||||||
|
snd_card_free(tscm->card);
|
||||||
|
dev_info(&tscm->unit->device,
|
||||||
|
"Sound card registration failed: %d\n", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int snd_tscm_probe(struct fw_unit *unit,
|
||||||
|
const struct ieee1394_device_id *entry)
|
||||||
|
{
|
||||||
|
struct snd_tscm *tscm;
|
||||||
|
|
||||||
|
/* Allocate this independent of sound card instance. */
|
||||||
|
tscm = kzalloc(sizeof(struct snd_tscm), GFP_KERNEL);
|
||||||
|
if (tscm == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* initialize myself */
|
||||||
|
tscm->unit = fw_unit_get(unit);
|
||||||
dev_set_drvdata(&unit->device, tscm);
|
dev_set_drvdata(&unit->device, tscm);
|
||||||
|
|
||||||
return err;
|
mutex_init(&tscm->mutex);
|
||||||
error:
|
spin_lock_init(&tscm->lock);
|
||||||
snd_card_free(card);
|
init_waitqueue_head(&tscm->hwdep_wait);
|
||||||
return err;
|
|
||||||
|
/* Allocate and register this sound card later. */
|
||||||
|
INIT_DEFERRABLE_WORK(&tscm->dwork, do_registration);
|
||||||
|
snd_fw_schedule_registration(unit, &tscm->dwork);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void snd_tscm_update(struct fw_unit *unit)
|
static void snd_tscm_update(struct fw_unit *unit)
|
||||||
{
|
{
|
||||||
struct snd_tscm *tscm = dev_get_drvdata(&unit->device);
|
struct snd_tscm *tscm = dev_get_drvdata(&unit->device);
|
||||||
|
|
||||||
|
/* Postpone a workqueue for deferred registration. */
|
||||||
|
if (!tscm->registered)
|
||||||
|
snd_fw_schedule_registration(unit, &tscm->dwork);
|
||||||
|
|
||||||
snd_tscm_transaction_reregister(tscm);
|
snd_tscm_transaction_reregister(tscm);
|
||||||
|
|
||||||
mutex_lock(&tscm->mutex);
|
/*
|
||||||
snd_tscm_stream_update_duplex(tscm);
|
* After registration, userspace can start packet streaming, then this
|
||||||
mutex_unlock(&tscm->mutex);
|
* code block works fine.
|
||||||
|
*/
|
||||||
|
if (tscm->registered) {
|
||||||
|
mutex_lock(&tscm->mutex);
|
||||||
|
snd_tscm_stream_update_duplex(tscm);
|
||||||
|
mutex_unlock(&tscm->mutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void snd_tscm_remove(struct fw_unit *unit)
|
static void snd_tscm_remove(struct fw_unit *unit)
|
||||||
{
|
{
|
||||||
struct snd_tscm *tscm = dev_get_drvdata(&unit->device);
|
struct snd_tscm *tscm = dev_get_drvdata(&unit->device);
|
||||||
|
|
||||||
/* No need to wait for releasing card object in this context. */
|
/*
|
||||||
snd_card_free_when_closed(tscm->card);
|
* Confirm to stop the work for registration before the sound card is
|
||||||
|
* going to be released. The work is not scheduled again because bus
|
||||||
|
* reset handler is not called anymore.
|
||||||
|
*/
|
||||||
|
cancel_delayed_work_sync(&tscm->dwork);
|
||||||
|
|
||||||
|
if (tscm->registered) {
|
||||||
|
/* No need to wait for releasing card object in this context. */
|
||||||
|
snd_card_free_when_closed(tscm->card);
|
||||||
|
} else {
|
||||||
|
/* Don't forget this case. */
|
||||||
|
tscm_free(tscm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct ieee1394_device_id snd_tscm_id_table[] = {
|
static const struct ieee1394_device_id snd_tscm_id_table[] = {
|
||||||
|
|
|
@ -51,6 +51,8 @@ struct snd_tscm {
|
||||||
struct mutex mutex;
|
struct mutex mutex;
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
|
|
||||||
|
bool registered;
|
||||||
|
struct delayed_work dwork;
|
||||||
const struct snd_tscm_spec *spec;
|
const struct snd_tscm_spec *spec;
|
||||||
|
|
||||||
struct fw_iso_resources tx_resources;
|
struct fw_iso_resources tx_resources;
|
||||||
|
|
|
@ -144,6 +144,7 @@ int snd_hdac_ext_bus_device_init(struct hdac_ext_bus *ebus, int addr)
|
||||||
if (!edev)
|
if (!edev)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
hdev = &edev->hdac;
|
hdev = &edev->hdac;
|
||||||
|
edev->ebus = ebus;
|
||||||
|
|
||||||
snprintf(name, sizeof(name), "ehdaudio%dD%d", ebus->idx, addr);
|
snprintf(name, sizeof(name), "ehdaudio%dD%d", ebus->idx, addr);
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,22 @@ void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(snd_hdac_bus_init_cmd_io);
|
EXPORT_SYMBOL_GPL(snd_hdac_bus_init_cmd_io);
|
||||||
|
|
||||||
|
/* wait for cmd dmas till they are stopped */
|
||||||
|
static void hdac_wait_for_cmd_dmas(struct hdac_bus *bus)
|
||||||
|
{
|
||||||
|
unsigned long timeout;
|
||||||
|
|
||||||
|
timeout = jiffies + msecs_to_jiffies(100);
|
||||||
|
while ((snd_hdac_chip_readb(bus, RIRBCTL) & AZX_RBCTL_DMA_EN)
|
||||||
|
&& time_before(jiffies, timeout))
|
||||||
|
udelay(10);
|
||||||
|
|
||||||
|
timeout = jiffies + msecs_to_jiffies(100);
|
||||||
|
while ((snd_hdac_chip_readb(bus, CORBCTL) & AZX_CORBCTL_RUN)
|
||||||
|
&& time_before(jiffies, timeout))
|
||||||
|
udelay(10);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* snd_hdac_bus_stop_cmd_io - clean up CORB/RIRB buffers
|
* snd_hdac_bus_stop_cmd_io - clean up CORB/RIRB buffers
|
||||||
* @bus: HD-audio core bus
|
* @bus: HD-audio core bus
|
||||||
|
@ -90,6 +106,7 @@ void snd_hdac_bus_stop_cmd_io(struct hdac_bus *bus)
|
||||||
/* disable ringbuffer DMAs */
|
/* disable ringbuffer DMAs */
|
||||||
snd_hdac_chip_writeb(bus, RIRBCTL, 0);
|
snd_hdac_chip_writeb(bus, RIRBCTL, 0);
|
||||||
snd_hdac_chip_writeb(bus, CORBCTL, 0);
|
snd_hdac_chip_writeb(bus, CORBCTL, 0);
|
||||||
|
hdac_wait_for_cmd_dmas(bus);
|
||||||
/* disable unsolicited responses */
|
/* disable unsolicited responses */
|
||||||
snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, 0);
|
snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, 0);
|
||||||
spin_unlock_irq(&bus->reg_lock);
|
spin_unlock_irq(&bus->reg_lock);
|
||||||
|
|
|
@ -158,22 +158,40 @@ void snd_hdac_i915_set_bclk(struct hdac_bus *bus)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(snd_hdac_i915_set_bclk);
|
EXPORT_SYMBOL_GPL(snd_hdac_i915_set_bclk);
|
||||||
|
|
||||||
/* There is a fixed mapping between audio pin node and display port
|
/* There is a fixed mapping between audio pin node and display port.
|
||||||
* on current Intel platforms:
|
* on SNB, IVY, HSW, BSW, SKL, BXT, KBL:
|
||||||
* Pin Widget 5 - PORT B (port = 1 in i915 driver)
|
* Pin Widget 5 - PORT B (port = 1 in i915 driver)
|
||||||
* Pin Widget 6 - PORT C (port = 2 in i915 driver)
|
* Pin Widget 6 - PORT C (port = 2 in i915 driver)
|
||||||
* Pin Widget 7 - PORT D (port = 3 in i915 driver)
|
* Pin Widget 7 - PORT D (port = 3 in i915 driver)
|
||||||
|
*
|
||||||
|
* on VLV, ILK:
|
||||||
|
* Pin Widget 4 - PORT B (port = 1 in i915 driver)
|
||||||
|
* Pin Widget 5 - PORT C (port = 2 in i915 driver)
|
||||||
|
* Pin Widget 6 - PORT D (port = 3 in i915 driver)
|
||||||
*/
|
*/
|
||||||
static int pin2port(hda_nid_t pin_nid)
|
static int pin2port(struct hdac_device *codec, hda_nid_t pin_nid)
|
||||||
{
|
{
|
||||||
if (WARN_ON(pin_nid < 5 || pin_nid > 7))
|
int base_nid;
|
||||||
|
|
||||||
|
switch (codec->vendor_id) {
|
||||||
|
case 0x80860054: /* ILK */
|
||||||
|
case 0x80862804: /* ILK */
|
||||||
|
case 0x80862882: /* VLV */
|
||||||
|
base_nid = 3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
base_nid = 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WARN_ON(pin_nid <= base_nid || pin_nid > base_nid + 3))
|
||||||
return -1;
|
return -1;
|
||||||
return pin_nid - 4;
|
return pin_nid - base_nid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* snd_hdac_sync_audio_rate - Set N/CTS based on the sample rate
|
* snd_hdac_sync_audio_rate - Set N/CTS based on the sample rate
|
||||||
* @bus: HDA core bus
|
* @codec: HDA codec
|
||||||
* @nid: the pin widget NID
|
* @nid: the pin widget NID
|
||||||
* @rate: the sample rate to set
|
* @rate: the sample rate to set
|
||||||
*
|
*
|
||||||
|
@ -183,14 +201,15 @@ static int pin2port(hda_nid_t pin_nid)
|
||||||
* This function sets N/CTS value based on the given sample rate.
|
* This function sets N/CTS value based on the given sample rate.
|
||||||
* Returns zero for success, or a negative error code.
|
* Returns zero for success, or a negative error code.
|
||||||
*/
|
*/
|
||||||
int snd_hdac_sync_audio_rate(struct hdac_bus *bus, hda_nid_t nid, int rate)
|
int snd_hdac_sync_audio_rate(struct hdac_device *codec, hda_nid_t nid, int rate)
|
||||||
{
|
{
|
||||||
|
struct hdac_bus *bus = codec->bus;
|
||||||
struct i915_audio_component *acomp = bus->audio_component;
|
struct i915_audio_component *acomp = bus->audio_component;
|
||||||
int port;
|
int port;
|
||||||
|
|
||||||
if (!acomp || !acomp->ops || !acomp->ops->sync_audio_rate)
|
if (!acomp || !acomp->ops || !acomp->ops->sync_audio_rate)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
port = pin2port(nid);
|
port = pin2port(codec, nid);
|
||||||
if (port < 0)
|
if (port < 0)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
return acomp->ops->sync_audio_rate(acomp->dev, port, rate);
|
return acomp->ops->sync_audio_rate(acomp->dev, port, rate);
|
||||||
|
@ -199,7 +218,7 @@ EXPORT_SYMBOL_GPL(snd_hdac_sync_audio_rate);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* snd_hdac_acomp_get_eld - Get the audio state and ELD via component
|
* snd_hdac_acomp_get_eld - Get the audio state and ELD via component
|
||||||
* @bus: HDA core bus
|
* @codec: HDA codec
|
||||||
* @nid: the pin widget NID
|
* @nid: the pin widget NID
|
||||||
* @audio_enabled: the pointer to store the current audio state
|
* @audio_enabled: the pointer to store the current audio state
|
||||||
* @buffer: the buffer pointer to store ELD bytes
|
* @buffer: the buffer pointer to store ELD bytes
|
||||||
|
@ -217,16 +236,17 @@ EXPORT_SYMBOL_GPL(snd_hdac_sync_audio_rate);
|
||||||
* thus it may be over @max_bytes. If it's over @max_bytes, it implies
|
* thus it may be over @max_bytes. If it's over @max_bytes, it implies
|
||||||
* that only a part of ELD bytes have been fetched.
|
* that only a part of ELD bytes have been fetched.
|
||||||
*/
|
*/
|
||||||
int snd_hdac_acomp_get_eld(struct hdac_bus *bus, hda_nid_t nid,
|
int snd_hdac_acomp_get_eld(struct hdac_device *codec, hda_nid_t nid,
|
||||||
bool *audio_enabled, char *buffer, int max_bytes)
|
bool *audio_enabled, char *buffer, int max_bytes)
|
||||||
{
|
{
|
||||||
|
struct hdac_bus *bus = codec->bus;
|
||||||
struct i915_audio_component *acomp = bus->audio_component;
|
struct i915_audio_component *acomp = bus->audio_component;
|
||||||
int port;
|
int port;
|
||||||
|
|
||||||
if (!acomp || !acomp->ops || !acomp->ops->get_eld)
|
if (!acomp || !acomp->ops || !acomp->ops->get_eld)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
port = pin2port(nid);
|
port = pin2port(codec, nid);
|
||||||
if (port < 0)
|
if (port < 0)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
return acomp->ops->get_eld(acomp->dev, port, audio_enabled,
|
return acomp->ops->get_eld(acomp->dev, port, audio_enabled,
|
||||||
|
@ -338,6 +358,9 @@ int snd_hdac_i915_init(struct hdac_bus *bus)
|
||||||
struct i915_audio_component *acomp;
|
struct i915_audio_component *acomp;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (WARN_ON(hdac_acomp))
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
if (!i915_gfx_present())
|
if (!i915_gfx_present())
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
|
@ -371,6 +394,7 @@ out_master_del:
|
||||||
out_err:
|
out_err:
|
||||||
kfree(acomp);
|
kfree(acomp);
|
||||||
bus->audio_component = NULL;
|
bus->audio_component = NULL;
|
||||||
|
hdac_acomp = NULL;
|
||||||
dev_info(dev, "failed to add i915 component master (%d)\n", ret);
|
dev_info(dev, "failed to add i915 component master (%d)\n", ret);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -404,6 +428,7 @@ int snd_hdac_i915_exit(struct hdac_bus *bus)
|
||||||
|
|
||||||
kfree(acomp);
|
kfree(acomp);
|
||||||
bus->audio_component = NULL;
|
bus->audio_component = NULL;
|
||||||
|
hdac_acomp = NULL;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -625,13 +625,30 @@ static void hdmi_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap,
|
||||||
WARN_ON(count != channels);
|
WARN_ON(count != channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int spk_mask_from_spk_alloc(int spk_alloc)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int spk_mask = eld_speaker_allocation_bits[0];
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) {
|
||||||
|
if (spk_alloc & (1 << i))
|
||||||
|
spk_mask |= eld_speaker_allocation_bits[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return spk_mask;
|
||||||
|
}
|
||||||
|
|
||||||
static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
|
static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
|
||||||
unsigned int size, unsigned int __user *tlv)
|
unsigned int size, unsigned int __user *tlv)
|
||||||
{
|
{
|
||||||
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
|
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
|
||||||
struct hdac_chmap *chmap = info->private_data;
|
struct hdac_chmap *chmap = info->private_data;
|
||||||
|
int pcm_idx = kcontrol->private_value;
|
||||||
unsigned int __user *dst;
|
unsigned int __user *dst;
|
||||||
int chs, count = 0;
|
int chs, count = 0;
|
||||||
|
unsigned long max_chs;
|
||||||
|
int type;
|
||||||
|
int spk_alloc, spk_mask;
|
||||||
|
|
||||||
if (size < 8)
|
if (size < 8)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
@ -639,40 +656,59 @@ static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
size -= 8;
|
size -= 8;
|
||||||
dst = tlv + 2;
|
dst = tlv + 2;
|
||||||
for (chs = 2; chs <= chmap->channels_max; chs++) {
|
|
||||||
|
spk_alloc = chmap->ops.get_spk_alloc(chmap->hdac, pcm_idx);
|
||||||
|
spk_mask = spk_mask_from_spk_alloc(spk_alloc);
|
||||||
|
|
||||||
|
max_chs = hweight_long(spk_mask);
|
||||||
|
|
||||||
|
for (chs = 2; chs <= max_chs; chs++) {
|
||||||
int i;
|
int i;
|
||||||
struct hdac_cea_channel_speaker_allocation *cap;
|
struct hdac_cea_channel_speaker_allocation *cap;
|
||||||
|
|
||||||
cap = channel_allocations;
|
cap = channel_allocations;
|
||||||
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++, cap++) {
|
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++, cap++) {
|
||||||
int chs_bytes = chs * 4;
|
int chs_bytes = chs * 4;
|
||||||
int type = chmap->ops.chmap_cea_alloc_validate_get_type(
|
|
||||||
chmap, cap, chs);
|
|
||||||
unsigned int tlv_chmap[8];
|
unsigned int tlv_chmap[8];
|
||||||
|
|
||||||
if (type < 0)
|
if (cap->channels != chs)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (!(cap->spk_mask == (spk_mask & cap->spk_mask)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
type = chmap->ops.chmap_cea_alloc_validate_get_type(
|
||||||
|
chmap, cap, chs);
|
||||||
|
if (type < 0)
|
||||||
|
return -ENODEV;
|
||||||
if (size < 8)
|
if (size < 8)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
if (put_user(type, dst) ||
|
if (put_user(type, dst) ||
|
||||||
put_user(chs_bytes, dst + 1))
|
put_user(chs_bytes, dst + 1))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
dst += 2;
|
dst += 2;
|
||||||
size -= 8;
|
size -= 8;
|
||||||
count += 8;
|
count += 8;
|
||||||
|
|
||||||
if (size < chs_bytes)
|
if (size < chs_bytes)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
size -= chs_bytes;
|
size -= chs_bytes;
|
||||||
count += chs_bytes;
|
count += chs_bytes;
|
||||||
chmap->ops.cea_alloc_to_tlv_chmap(chmap, cap,
|
chmap->ops.cea_alloc_to_tlv_chmap(chmap, cap,
|
||||||
tlv_chmap, chs);
|
tlv_chmap, chs);
|
||||||
|
|
||||||
if (copy_to_user(dst, tlv_chmap, chs_bytes))
|
if (copy_to_user(dst, tlv_chmap, chs_bytes))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
dst += chs;
|
dst += chs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (put_user(count, tlv + 1))
|
if (put_user(count, tlv + 1))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -785,6 +785,9 @@ wavefront_send_patch (snd_wavefront_t *dev, wavefront_patch_info *header)
|
||||||
DPRINT (WF_DEBUG_LOAD_PATCH, "downloading patch %d\n",
|
DPRINT (WF_DEBUG_LOAD_PATCH, "downloading patch %d\n",
|
||||||
header->number);
|
header->number);
|
||||||
|
|
||||||
|
if (header->number >= ARRAY_SIZE(dev->patch_status))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
dev->patch_status[header->number] |= WF_SLOT_FILLED;
|
dev->patch_status[header->number] |= WF_SLOT_FILLED;
|
||||||
|
|
||||||
bptr = buf;
|
bptr = buf;
|
||||||
|
@ -809,6 +812,9 @@ wavefront_send_program (snd_wavefront_t *dev, wavefront_patch_info *header)
|
||||||
DPRINT (WF_DEBUG_LOAD_PATCH, "downloading program %d\n",
|
DPRINT (WF_DEBUG_LOAD_PATCH, "downloading program %d\n",
|
||||||
header->number);
|
header->number);
|
||||||
|
|
||||||
|
if (header->number >= ARRAY_SIZE(dev->prog_status))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
dev->prog_status[header->number] = WF_SLOT_USED;
|
dev->prog_status[header->number] = WF_SLOT_USED;
|
||||||
|
|
||||||
/* XXX need to zero existing SLOT_USED bit for program_status[i]
|
/* XXX need to zero existing SLOT_USED bit for program_status[i]
|
||||||
|
@ -898,6 +904,9 @@ wavefront_send_sample (snd_wavefront_t *dev,
|
||||||
header->number = x;
|
header->number = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (header->number >= WF_MAX_SAMPLE)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
if (header->size) {
|
if (header->size) {
|
||||||
|
|
||||||
/* XXX it's a debatable point whether or not RDONLY semantics
|
/* XXX it's a debatable point whether or not RDONLY semantics
|
||||||
|
|
|
@ -2151,8 +2151,7 @@ vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir,
|
||||||
stream->resources, en,
|
stream->resources, en,
|
||||||
VORTEX_RESOURCE_SRC)) < 0) {
|
VORTEX_RESOURCE_SRC)) < 0) {
|
||||||
memset(stream->resources, 0,
|
memset(stream->resources, 0,
|
||||||
sizeof(unsigned char) *
|
sizeof(stream->resources));
|
||||||
VORTEX_RESOURCE_LAST);
|
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
if (stream->type != VORTEX_PCM_A3D) {
|
if (stream->type != VORTEX_PCM_A3D) {
|
||||||
|
@ -2162,7 +2161,7 @@ vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir,
|
||||||
VORTEX_RESOURCE_MIXIN)) < 0) {
|
VORTEX_RESOURCE_MIXIN)) < 0) {
|
||||||
memset(stream->resources,
|
memset(stream->resources,
|
||||||
0,
|
0,
|
||||||
sizeof(unsigned char) * VORTEX_RESOURCE_LAST);
|
sizeof(stream->resources));
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2175,8 +2174,7 @@ vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir,
|
||||||
stream->resources, en,
|
stream->resources, en,
|
||||||
VORTEX_RESOURCE_A3D)) < 0) {
|
VORTEX_RESOURCE_A3D)) < 0) {
|
||||||
memset(stream->resources, 0,
|
memset(stream->resources, 0,
|
||||||
sizeof(unsigned char) *
|
sizeof(stream->resources));
|
||||||
VORTEX_RESOURCE_LAST);
|
|
||||||
dev_err(vortex->card->dev,
|
dev_err(vortex->card->dev,
|
||||||
"out of A3D sources. Sorry\n");
|
"out of A3D sources. Sorry\n");
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
|
@ -2290,8 +2288,7 @@ vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir,
|
||||||
VORTEX_RESOURCE_MIXOUT))
|
VORTEX_RESOURCE_MIXOUT))
|
||||||
< 0) {
|
< 0) {
|
||||||
memset(stream->resources, 0,
|
memset(stream->resources, 0,
|
||||||
sizeof(unsigned char) *
|
sizeof(stream->resources));
|
||||||
VORTEX_RESOURCE_LAST);
|
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
if ((src[i] =
|
if ((src[i] =
|
||||||
|
@ -2299,8 +2296,7 @@ vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir,
|
||||||
stream->resources, en,
|
stream->resources, en,
|
||||||
VORTEX_RESOURCE_SRC)) < 0) {
|
VORTEX_RESOURCE_SRC)) < 0) {
|
||||||
memset(stream->resources, 0,
|
memset(stream->resources, 0,
|
||||||
sizeof(unsigned char) *
|
sizeof(stream->resources));
|
||||||
VORTEX_RESOURCE_LAST);
|
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -432,7 +432,10 @@ static snd_pcm_uframes_t snd_vortex_pcm_pointer(struct snd_pcm_substream *substr
|
||||||
#endif
|
#endif
|
||||||
//printk(KERN_INFO "vortex: pointer = 0x%x\n", current_ptr);
|
//printk(KERN_INFO "vortex: pointer = 0x%x\n", current_ptr);
|
||||||
spin_unlock(&chip->lock);
|
spin_unlock(&chip->lock);
|
||||||
return (bytes_to_frames(substream->runtime, current_ptr));
|
current_ptr = bytes_to_frames(substream->runtime, current_ptr);
|
||||||
|
if (current_ptr >= substream->runtime->buffer_size)
|
||||||
|
current_ptr = 0;
|
||||||
|
return current_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* operators */
|
/* operators */
|
||||||
|
|
|
@ -49,7 +49,7 @@ struct ct_timer {
|
||||||
spinlock_t lock; /* global timer lock (for xfitimer) */
|
spinlock_t lock; /* global timer lock (for xfitimer) */
|
||||||
spinlock_t list_lock; /* lock for instance list */
|
spinlock_t list_lock; /* lock for instance list */
|
||||||
struct ct_atc *atc;
|
struct ct_atc *atc;
|
||||||
struct ct_timer_ops *ops;
|
const struct ct_timer_ops *ops;
|
||||||
struct list_head instance_head;
|
struct list_head instance_head;
|
||||||
struct list_head running_head;
|
struct list_head running_head;
|
||||||
unsigned int wc; /* current wallclock */
|
unsigned int wc; /* current wallclock */
|
||||||
|
@ -128,7 +128,7 @@ static void ct_systimer_prepare(struct ct_timer_instance *ti)
|
||||||
|
|
||||||
#define ct_systimer_free ct_systimer_prepare
|
#define ct_systimer_free ct_systimer_prepare
|
||||||
|
|
||||||
static struct ct_timer_ops ct_systimer_ops = {
|
static const struct ct_timer_ops ct_systimer_ops = {
|
||||||
.init = ct_systimer_init,
|
.init = ct_systimer_init,
|
||||||
.free_instance = ct_systimer_free,
|
.free_instance = ct_systimer_free,
|
||||||
.prepare = ct_systimer_prepare,
|
.prepare = ct_systimer_prepare,
|
||||||
|
@ -322,7 +322,7 @@ static void ct_xfitimer_free_global(struct ct_timer *atimer)
|
||||||
ct_xfitimer_irq_stop(atimer);
|
ct_xfitimer_irq_stop(atimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct ct_timer_ops ct_xfitimer_ops = {
|
static const struct ct_timer_ops ct_xfitimer_ops = {
|
||||||
.prepare = ct_xfitimer_prepare,
|
.prepare = ct_xfitimer_prepare,
|
||||||
.start = ct_xfitimer_start,
|
.start = ct_xfitimer_start,
|
||||||
.stop = ct_xfitimer_stop,
|
.stop = ct_xfitimer_stop,
|
||||||
|
|
|
@ -1548,7 +1548,7 @@ static int snd_es1373_line_get(struct snd_kcontrol *kcontrol,
|
||||||
int val = 0;
|
int val = 0;
|
||||||
|
|
||||||
spin_lock_irq(&ensoniq->reg_lock);
|
spin_lock_irq(&ensoniq->reg_lock);
|
||||||
if ((ensoniq->ctrl & ES_1371_GPIO_OUTM) >= 4)
|
if (ensoniq->ctrl & ES_1371_GPIO_OUT(4))
|
||||||
val = 1;
|
val = 1;
|
||||||
ucontrol->value.integer.value[0] = val;
|
ucontrol->value.integer.value[0] = val;
|
||||||
spin_unlock_irq(&ensoniq->reg_lock);
|
spin_unlock_irq(&ensoniq->reg_lock);
|
||||||
|
|
|
@ -50,9 +50,13 @@ config SND_HDA_RECONFIG
|
||||||
bool "Allow dynamic codec reconfiguration"
|
bool "Allow dynamic codec reconfiguration"
|
||||||
help
|
help
|
||||||
Say Y here to enable the HD-audio codec re-configuration feature.
|
Say Y here to enable the HD-audio codec re-configuration feature.
|
||||||
This adds the sysfs interfaces to allow user to clear the whole
|
It allows user to clear the whole codec configuration, change the
|
||||||
codec configuration, change the codec setup, add extra verbs,
|
codec setup, add extra verbs, and re-configure the codec dynamically.
|
||||||
and re-configure the codec dynamically.
|
|
||||||
|
Note that this item alone doesn't provide the sysfs interface, but
|
||||||
|
enables the feature just for the patch loader below.
|
||||||
|
If you need the traditional sysfs entries for the manual interaction,
|
||||||
|
turn on CONFIG_SND_HDA_HWDEP as well.
|
||||||
|
|
||||||
config SND_HDA_INPUT_BEEP
|
config SND_HDA_INPUT_BEEP
|
||||||
bool "Support digital beep via input layer"
|
bool "Support digital beep via input layer"
|
||||||
|
|
|
@ -5434,6 +5434,7 @@ static int dyn_adc_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
|
||||||
spec->cur_adc_stream_tag = stream_tag;
|
spec->cur_adc_stream_tag = stream_tag;
|
||||||
spec->cur_adc_format = format;
|
spec->cur_adc_format = format;
|
||||||
snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
|
snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format);
|
||||||
|
call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_PREPARE);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5444,6 +5445,7 @@ static int dyn_adc_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
|
||||||
struct hda_gen_spec *spec = codec->spec;
|
struct hda_gen_spec *spec = codec->spec;
|
||||||
snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
|
snd_hda_codec_cleanup_stream(codec, spec->cur_adc);
|
||||||
spec->cur_adc = 0;
|
spec->cur_adc = 0;
|
||||||
|
call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_CLEANUP);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,9 @@ struct hdmi_ops {
|
||||||
int (*setup_stream)(struct hda_codec *codec, hda_nid_t cvt_nid,
|
int (*setup_stream)(struct hda_codec *codec, hda_nid_t cvt_nid,
|
||||||
hda_nid_t pin_nid, u32 stream_tag, int format);
|
hda_nid_t pin_nid, u32 stream_tag, int format);
|
||||||
|
|
||||||
|
void (*pin_cvt_fixup)(struct hda_codec *codec,
|
||||||
|
struct hdmi_spec_per_pin *per_pin,
|
||||||
|
hda_nid_t cvt_nid);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct hdmi_pcm {
|
struct hdmi_pcm {
|
||||||
|
@ -684,7 +687,8 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
|
||||||
if (!channels)
|
if (!channels)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (is_haswell_plus(codec))
|
/* some HW (e.g. HSW+) needs reprogramming the amp at each time */
|
||||||
|
if (get_wcaps(codec, pin_nid) & AC_WCAP_OUT_AMP)
|
||||||
snd_hda_codec_write(codec, pin_nid, 0,
|
snd_hda_codec_write(codec, pin_nid, 0,
|
||||||
AC_VERB_SET_AMP_GAIN_MUTE,
|
AC_VERB_SET_AMP_GAIN_MUTE,
|
||||||
AMP_OUT_UNMUTE);
|
AMP_OUT_UNMUTE);
|
||||||
|
@ -864,9 +868,6 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
|
||||||
struct hdmi_spec *spec = codec->spec;
|
struct hdmi_spec *spec = codec->spec;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (is_haswell_plus(codec))
|
|
||||||
haswell_verify_D0(codec, cvt_nid, pin_nid);
|
|
||||||
|
|
||||||
err = spec->ops.pin_hbr_setup(codec, pin_nid, is_hbr_format(format));
|
err = spec->ops.pin_hbr_setup(codec, pin_nid, is_hbr_format(format));
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -884,7 +885,7 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
|
||||||
* of the pin.
|
* of the pin.
|
||||||
*/
|
*/
|
||||||
static int hdmi_choose_cvt(struct hda_codec *codec,
|
static int hdmi_choose_cvt(struct hda_codec *codec,
|
||||||
int pin_idx, int *cvt_id, int *mux_id)
|
int pin_idx, int *cvt_id)
|
||||||
{
|
{
|
||||||
struct hdmi_spec *spec = codec->spec;
|
struct hdmi_spec *spec = codec->spec;
|
||||||
struct hdmi_spec_per_pin *per_pin;
|
struct hdmi_spec_per_pin *per_pin;
|
||||||
|
@ -925,8 +926,6 @@ static int hdmi_choose_cvt(struct hda_codec *codec,
|
||||||
|
|
||||||
if (cvt_id)
|
if (cvt_id)
|
||||||
*cvt_id = cvt_idx;
|
*cvt_id = cvt_idx;
|
||||||
if (mux_id)
|
|
||||||
*mux_id = mux_idx;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1019,9 +1018,6 @@ static void intel_not_share_assigned_cvt_nid(struct hda_codec *codec,
|
||||||
int mux_idx;
|
int mux_idx;
|
||||||
struct hdmi_spec *spec = codec->spec;
|
struct hdmi_spec *spec = codec->spec;
|
||||||
|
|
||||||
if (!is_haswell_plus(codec) && !is_valleyview_plus(codec))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* On Intel platform, the mapping of converter nid to
|
/* On Intel platform, the mapping of converter nid to
|
||||||
* mux index of the pins are always the same.
|
* mux index of the pins are always the same.
|
||||||
* The pin nid may be 0, this means all pins will not
|
* The pin nid may be 0, this means all pins will not
|
||||||
|
@ -1032,6 +1028,17 @@ static void intel_not_share_assigned_cvt_nid(struct hda_codec *codec,
|
||||||
intel_not_share_assigned_cvt(codec, pin_nid, mux_idx);
|
intel_not_share_assigned_cvt(codec, pin_nid, mux_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* skeleton caller of pin_cvt_fixup ops */
|
||||||
|
static void pin_cvt_fixup(struct hda_codec *codec,
|
||||||
|
struct hdmi_spec_per_pin *per_pin,
|
||||||
|
hda_nid_t cvt_nid)
|
||||||
|
{
|
||||||
|
struct hdmi_spec *spec = codec->spec;
|
||||||
|
|
||||||
|
if (spec->ops.pin_cvt_fixup)
|
||||||
|
spec->ops.pin_cvt_fixup(codec, per_pin, cvt_nid);
|
||||||
|
}
|
||||||
|
|
||||||
/* called in hdmi_pcm_open when no pin is assigned to the PCM
|
/* called in hdmi_pcm_open when no pin is assigned to the PCM
|
||||||
* in dyn_pcm_assign mode.
|
* in dyn_pcm_assign mode.
|
||||||
*/
|
*/
|
||||||
|
@ -1049,7 +1056,7 @@ static int hdmi_pcm_open_no_pin(struct hda_pcm_stream *hinfo,
|
||||||
if (pcm_idx < 0)
|
if (pcm_idx < 0)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
err = hdmi_choose_cvt(codec, -1, &cvt_idx, NULL);
|
err = hdmi_choose_cvt(codec, -1, &cvt_idx);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
|
@ -1057,7 +1064,7 @@ static int hdmi_pcm_open_no_pin(struct hda_pcm_stream *hinfo,
|
||||||
per_cvt->assigned = 1;
|
per_cvt->assigned = 1;
|
||||||
hinfo->nid = per_cvt->cvt_nid;
|
hinfo->nid = per_cvt->cvt_nid;
|
||||||
|
|
||||||
intel_not_share_assigned_cvt_nid(codec, 0, per_cvt->cvt_nid);
|
pin_cvt_fixup(codec, NULL, per_cvt->cvt_nid);
|
||||||
|
|
||||||
set_bit(pcm_idx, &spec->pcm_in_use);
|
set_bit(pcm_idx, &spec->pcm_in_use);
|
||||||
/* todo: setup spdif ctls assign */
|
/* todo: setup spdif ctls assign */
|
||||||
|
@ -1089,7 +1096,7 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
|
||||||
{
|
{
|
||||||
struct hdmi_spec *spec = codec->spec;
|
struct hdmi_spec *spec = codec->spec;
|
||||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||||
int pin_idx, cvt_idx, pcm_idx, mux_idx = 0;
|
int pin_idx, cvt_idx, pcm_idx;
|
||||||
struct hdmi_spec_per_pin *per_pin;
|
struct hdmi_spec_per_pin *per_pin;
|
||||||
struct hdmi_eld *eld;
|
struct hdmi_eld *eld;
|
||||||
struct hdmi_spec_per_cvt *per_cvt = NULL;
|
struct hdmi_spec_per_cvt *per_cvt = NULL;
|
||||||
|
@ -1118,7 +1125,7 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx, &mux_idx);
|
err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
mutex_unlock(&spec->pcm_lock);
|
mutex_unlock(&spec->pcm_lock);
|
||||||
return err;
|
return err;
|
||||||
|
@ -1135,11 +1142,10 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
|
||||||
|
|
||||||
snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0,
|
snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0,
|
||||||
AC_VERB_SET_CONNECT_SEL,
|
AC_VERB_SET_CONNECT_SEL,
|
||||||
mux_idx);
|
per_pin->mux_idx);
|
||||||
|
|
||||||
/* configure unused pins to choose other converters */
|
/* configure unused pins to choose other converters */
|
||||||
if (is_haswell_plus(codec) || is_valleyview_plus(codec))
|
pin_cvt_fixup(codec, per_pin, 0);
|
||||||
intel_not_share_assigned_cvt(codec, per_pin->pin_nid, mux_idx);
|
|
||||||
|
|
||||||
snd_hda_spdif_ctls_assign(codec, pcm_idx, per_cvt->cvt_nid);
|
snd_hda_spdif_ctls_assign(codec, pcm_idx, per_cvt->cvt_nid);
|
||||||
|
|
||||||
|
@ -1372,12 +1378,7 @@ static void update_eld(struct hda_codec *codec,
|
||||||
* and this can make HW reset converter selection on a pin.
|
* and this can make HW reset converter selection on a pin.
|
||||||
*/
|
*/
|
||||||
if (eld->eld_valid && !old_eld_valid && per_pin->setup) {
|
if (eld->eld_valid && !old_eld_valid && per_pin->setup) {
|
||||||
if (is_haswell_plus(codec) || is_valleyview_plus(codec)) {
|
pin_cvt_fixup(codec, per_pin, 0);
|
||||||
intel_verify_pin_cvt_connect(codec, per_pin);
|
|
||||||
intel_not_share_assigned_cvt(codec, per_pin->pin_nid,
|
|
||||||
per_pin->mux_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm);
|
hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1484,7 +1485,7 @@ static void sync_eld_via_acomp(struct hda_codec *codec,
|
||||||
|
|
||||||
mutex_lock(&per_pin->lock);
|
mutex_lock(&per_pin->lock);
|
||||||
eld->monitor_present = false;
|
eld->monitor_present = false;
|
||||||
size = snd_hdac_acomp_get_eld(&codec->bus->core, per_pin->pin_nid,
|
size = snd_hdac_acomp_get_eld(&codec->core, per_pin->pin_nid,
|
||||||
&eld->monitor_present, eld->eld_buffer,
|
&eld->monitor_present, eld->eld_buffer,
|
||||||
ELD_MAX_SIZE);
|
ELD_MAX_SIZE);
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
|
@ -1711,7 +1712,7 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
||||||
* skip pin setup and return 0 to make audio playback
|
* skip pin setup and return 0 to make audio playback
|
||||||
* be ongoing
|
* be ongoing
|
||||||
*/
|
*/
|
||||||
intel_not_share_assigned_cvt_nid(codec, 0, cvt_nid);
|
pin_cvt_fixup(codec, NULL, cvt_nid);
|
||||||
snd_hda_codec_setup_stream(codec, cvt_nid,
|
snd_hda_codec_setup_stream(codec, cvt_nid,
|
||||||
stream_tag, 0, format);
|
stream_tag, 0, format);
|
||||||
mutex_unlock(&spec->pcm_lock);
|
mutex_unlock(&spec->pcm_lock);
|
||||||
|
@ -1724,23 +1725,21 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
||||||
}
|
}
|
||||||
per_pin = get_pin(spec, pin_idx);
|
per_pin = get_pin(spec, pin_idx);
|
||||||
pin_nid = per_pin->pin_nid;
|
pin_nid = per_pin->pin_nid;
|
||||||
if (is_haswell_plus(codec) || is_valleyview_plus(codec)) {
|
|
||||||
/* Verify pin:cvt selections to avoid silent audio after S3.
|
/* Verify pin:cvt selections to avoid silent audio after S3.
|
||||||
* After S3, the audio driver restores pin:cvt selections
|
* After S3, the audio driver restores pin:cvt selections
|
||||||
* but this can happen before gfx is ready and such selection
|
* but this can happen before gfx is ready and such selection
|
||||||
* is overlooked by HW. Thus multiple pins can share a same
|
* is overlooked by HW. Thus multiple pins can share a same
|
||||||
* default convertor and mute control will affect each other,
|
* default convertor and mute control will affect each other,
|
||||||
* which can cause a resumed audio playback become silent
|
* which can cause a resumed audio playback become silent
|
||||||
* after S3.
|
* after S3.
|
||||||
*/
|
*/
|
||||||
intel_verify_pin_cvt_connect(codec, per_pin);
|
pin_cvt_fixup(codec, per_pin, 0);
|
||||||
intel_not_share_assigned_cvt(codec, pin_nid, per_pin->mux_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Call sync_audio_rate to set the N/CTS/M manually if necessary */
|
/* Call sync_audio_rate to set the N/CTS/M manually if necessary */
|
||||||
/* Todo: add DP1.2 MST audio support later */
|
/* Todo: add DP1.2 MST audio support later */
|
||||||
if (codec_has_acomp(codec))
|
if (codec_has_acomp(codec))
|
||||||
snd_hdac_sync_audio_rate(&codec->bus->core, pin_nid, runtime->rate);
|
snd_hdac_sync_audio_rate(&codec->core, pin_nid, runtime->rate);
|
||||||
|
|
||||||
non_pcm = check_non_pcm_per_cvt(codec, cvt_nid);
|
non_pcm = check_non_pcm_per_cvt(codec, cvt_nid);
|
||||||
mutex_lock(&per_pin->lock);
|
mutex_lock(&per_pin->lock);
|
||||||
|
@ -1837,6 +1836,18 @@ static const struct hda_pcm_ops generic_ops = {
|
||||||
.cleanup = generic_hdmi_playback_pcm_cleanup,
|
.cleanup = generic_hdmi_playback_pcm_cleanup,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int hdmi_get_spk_alloc(struct hdac_device *hdac, int pcm_idx)
|
||||||
|
{
|
||||||
|
struct hda_codec *codec = container_of(hdac, struct hda_codec, core);
|
||||||
|
struct hdmi_spec *spec = codec->spec;
|
||||||
|
struct hdmi_spec_per_pin *per_pin = pcm_idx_to_pin(spec, pcm_idx);
|
||||||
|
|
||||||
|
if (!per_pin)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return per_pin->sink_eld.info.spk_alloc;
|
||||||
|
}
|
||||||
|
|
||||||
static void hdmi_get_chmap(struct hdac_device *hdac, int pcm_idx,
|
static void hdmi_get_chmap(struct hdac_device *hdac, int pcm_idx,
|
||||||
unsigned char *chmap)
|
unsigned char *chmap)
|
||||||
{
|
{
|
||||||
|
@ -2075,6 +2086,20 @@ static void hdmi_array_free(struct hdmi_spec *spec)
|
||||||
snd_array_free(&spec->cvts);
|
snd_array_free(&spec->cvts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void generic_spec_free(struct hda_codec *codec)
|
||||||
|
{
|
||||||
|
struct hdmi_spec *spec = codec->spec;
|
||||||
|
|
||||||
|
if (spec) {
|
||||||
|
if (spec->i915_bound)
|
||||||
|
snd_hdac_i915_exit(&codec->bus->core);
|
||||||
|
hdmi_array_free(spec);
|
||||||
|
kfree(spec);
|
||||||
|
codec->spec = NULL;
|
||||||
|
}
|
||||||
|
codec->dp_mst = false;
|
||||||
|
}
|
||||||
|
|
||||||
static void generic_hdmi_free(struct hda_codec *codec)
|
static void generic_hdmi_free(struct hda_codec *codec)
|
||||||
{
|
{
|
||||||
struct hdmi_spec *spec = codec->spec;
|
struct hdmi_spec *spec = codec->spec;
|
||||||
|
@ -2099,10 +2124,7 @@ static void generic_hdmi_free(struct hda_codec *codec)
|
||||||
spec->pcm_rec[pcm_idx].jack = NULL;
|
spec->pcm_rec[pcm_idx].jack = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec->i915_bound)
|
generic_spec_free(codec);
|
||||||
snd_hdac_i915_exit(&codec->bus->core);
|
|
||||||
hdmi_array_free(spec);
|
|
||||||
kfree(spec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM
|
||||||
|
@ -2140,6 +2162,55 @@ static const struct hdmi_ops generic_standard_hdmi_ops = {
|
||||||
.setup_stream = hdmi_setup_stream,
|
.setup_stream = hdmi_setup_stream,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* allocate codec->spec and assign/initialize generic parser ops */
|
||||||
|
static int alloc_generic_hdmi(struct hda_codec *codec)
|
||||||
|
{
|
||||||
|
struct hdmi_spec *spec;
|
||||||
|
|
||||||
|
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
||||||
|
if (!spec)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
spec->ops = generic_standard_hdmi_ops;
|
||||||
|
mutex_init(&spec->pcm_lock);
|
||||||
|
snd_hdac_register_chmap_ops(&codec->core, &spec->chmap);
|
||||||
|
|
||||||
|
spec->chmap.ops.get_chmap = hdmi_get_chmap;
|
||||||
|
spec->chmap.ops.set_chmap = hdmi_set_chmap;
|
||||||
|
spec->chmap.ops.is_pcm_attached = is_hdmi_pcm_attached;
|
||||||
|
spec->chmap.ops.get_spk_alloc = hdmi_get_spk_alloc,
|
||||||
|
|
||||||
|
codec->spec = spec;
|
||||||
|
hdmi_array_init(spec, 4);
|
||||||
|
|
||||||
|
codec->patch_ops = generic_hdmi_patch_ops;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* generic HDMI parser */
|
||||||
|
static int patch_generic_hdmi(struct hda_codec *codec)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = alloc_generic_hdmi(codec);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = hdmi_parse_codec(codec);
|
||||||
|
if (err < 0) {
|
||||||
|
generic_spec_free(codec);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
generic_hdmi_init_per_pins(codec);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Intel codec parsers and helpers
|
||||||
|
*/
|
||||||
|
|
||||||
static void intel_haswell_fixup_connect_list(struct hda_codec *codec,
|
static void intel_haswell_fixup_connect_list(struct hda_codec *codec,
|
||||||
hda_nid_t nid)
|
hda_nid_t nid)
|
||||||
{
|
{
|
||||||
|
@ -2217,12 +2288,23 @@ static void haswell_set_power_state(struct hda_codec *codec, hda_nid_t fg,
|
||||||
static void intel_pin_eld_notify(void *audio_ptr, int port)
|
static void intel_pin_eld_notify(void *audio_ptr, int port)
|
||||||
{
|
{
|
||||||
struct hda_codec *codec = audio_ptr;
|
struct hda_codec *codec = audio_ptr;
|
||||||
int pin_nid = port + 0x04;
|
int pin_nid;
|
||||||
|
|
||||||
/* we assume only from port-B to port-D */
|
/* we assume only from port-B to port-D */
|
||||||
if (port < 1 || port > 3)
|
if (port < 1 || port > 3)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
switch (codec->core.vendor_id) {
|
||||||
|
case 0x80860054: /* ILK */
|
||||||
|
case 0x80862804: /* ILK */
|
||||||
|
case 0x80862882: /* VLV */
|
||||||
|
pin_nid = port + 0x03;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pin_nid = port + 0x04;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* skip notification during system suspend (but not in runtime PM);
|
/* skip notification during system suspend (but not in runtime PM);
|
||||||
* the state will be updated at resume
|
* the state will be updated at resume
|
||||||
*/
|
*/
|
||||||
|
@ -2236,95 +2318,161 @@ static void intel_pin_eld_notify(void *audio_ptr, int port)
|
||||||
check_presence_and_report(codec, pin_nid);
|
check_presence_and_report(codec, pin_nid);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int patch_generic_hdmi(struct hda_codec *codec)
|
/* register i915 component pin_eld_notify callback */
|
||||||
|
static void register_i915_notifier(struct hda_codec *codec)
|
||||||
|
{
|
||||||
|
struct hdmi_spec *spec = codec->spec;
|
||||||
|
|
||||||
|
spec->use_acomp_notifier = true;
|
||||||
|
spec->i915_audio_ops.audio_ptr = codec;
|
||||||
|
/* intel_audio_codec_enable() or intel_audio_codec_disable()
|
||||||
|
* will call pin_eld_notify with using audio_ptr pointer
|
||||||
|
* We need make sure audio_ptr is really setup
|
||||||
|
*/
|
||||||
|
wmb();
|
||||||
|
spec->i915_audio_ops.pin_eld_notify = intel_pin_eld_notify;
|
||||||
|
snd_hdac_i915_register_notifier(&spec->i915_audio_ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* setup_stream ops override for HSW+ */
|
||||||
|
static int i915_hsw_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
|
||||||
|
hda_nid_t pin_nid, u32 stream_tag, int format)
|
||||||
|
{
|
||||||
|
haswell_verify_D0(codec, cvt_nid, pin_nid);
|
||||||
|
return hdmi_setup_stream(codec, cvt_nid, pin_nid, stream_tag, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pin_cvt_fixup ops override for HSW+ and VLV+ */
|
||||||
|
static void i915_pin_cvt_fixup(struct hda_codec *codec,
|
||||||
|
struct hdmi_spec_per_pin *per_pin,
|
||||||
|
hda_nid_t cvt_nid)
|
||||||
|
{
|
||||||
|
if (per_pin) {
|
||||||
|
intel_verify_pin_cvt_connect(codec, per_pin);
|
||||||
|
intel_not_share_assigned_cvt(codec, per_pin->pin_nid,
|
||||||
|
per_pin->mux_idx);
|
||||||
|
} else {
|
||||||
|
intel_not_share_assigned_cvt_nid(codec, 0, cvt_nid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Intel Haswell and onwards; audio component with eld notifier */
|
||||||
|
static int patch_i915_hsw_hdmi(struct hda_codec *codec)
|
||||||
{
|
{
|
||||||
struct hdmi_spec *spec;
|
struct hdmi_spec *spec;
|
||||||
|
int err;
|
||||||
|
|
||||||
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
|
/* HSW+ requires i915 binding */
|
||||||
if (spec == NULL)
|
if (!codec->bus->core.audio_component) {
|
||||||
return -ENOMEM;
|
codec_info(codec, "No i915 binding for Intel HDMI/DP codec\n");
|
||||||
|
return -ENODEV;
|
||||||
spec->ops = generic_standard_hdmi_ops;
|
|
||||||
mutex_init(&spec->pcm_lock);
|
|
||||||
snd_hdac_register_chmap_ops(&codec->core, &spec->chmap);
|
|
||||||
|
|
||||||
spec->chmap.ops.get_chmap = hdmi_get_chmap;
|
|
||||||
spec->chmap.ops.set_chmap = hdmi_set_chmap;
|
|
||||||
spec->chmap.ops.is_pcm_attached = is_hdmi_pcm_attached;
|
|
||||||
|
|
||||||
codec->spec = spec;
|
|
||||||
hdmi_array_init(spec, 4);
|
|
||||||
|
|
||||||
#ifdef CONFIG_SND_HDA_I915
|
|
||||||
/* Try to bind with i915 for Intel HSW+ codecs (if not done yet) */
|
|
||||||
if ((codec->core.vendor_id >> 16) == 0x8086 &&
|
|
||||||
is_haswell_plus(codec)) {
|
|
||||||
#if 0
|
|
||||||
/* on-demand binding leads to an unbalanced refcount when
|
|
||||||
* both i915 and hda drivers are probed concurrently;
|
|
||||||
* disabled temporarily for now
|
|
||||||
*/
|
|
||||||
if (!codec->bus->core.audio_component)
|
|
||||||
if (!snd_hdac_i915_init(&codec->bus->core))
|
|
||||||
spec->i915_bound = true;
|
|
||||||
#endif
|
|
||||||
/* use i915 audio component notifier for hotplug */
|
|
||||||
if (codec->bus->core.audio_component)
|
|
||||||
spec->use_acomp_notifier = true;
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
if (is_haswell_plus(codec)) {
|
err = alloc_generic_hdmi(codec);
|
||||||
intel_haswell_enable_all_pins(codec, true);
|
if (err < 0)
|
||||||
intel_haswell_fixup_enable_dp12(codec);
|
return err;
|
||||||
|
spec = codec->spec;
|
||||||
|
|
||||||
|
intel_haswell_enable_all_pins(codec, true);
|
||||||
|
intel_haswell_fixup_enable_dp12(codec);
|
||||||
|
|
||||||
|
/* For Haswell/Broadwell, the controller is also in the power well and
|
||||||
|
* can cover the codec power request, and so need not set this flag.
|
||||||
|
*/
|
||||||
|
if (!is_haswell(codec) && !is_broadwell(codec))
|
||||||
|
codec->core.link_power_control = 1;
|
||||||
|
|
||||||
|
codec->patch_ops.set_power_state = haswell_set_power_state;
|
||||||
|
codec->dp_mst = true;
|
||||||
|
codec->depop_delay = 0;
|
||||||
|
codec->auto_runtime_pm = 1;
|
||||||
|
|
||||||
|
spec->ops.setup_stream = i915_hsw_setup_stream;
|
||||||
|
spec->ops.pin_cvt_fixup = i915_pin_cvt_fixup;
|
||||||
|
|
||||||
|
err = hdmi_parse_codec(codec);
|
||||||
|
if (err < 0) {
|
||||||
|
generic_spec_free(codec);
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generic_hdmi_init_per_pins(codec);
|
||||||
|
register_i915_notifier(codec);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Intel Baytrail and Braswell; with eld notifier */
|
||||||
|
static int patch_i915_byt_hdmi(struct hda_codec *codec)
|
||||||
|
{
|
||||||
|
struct hdmi_spec *spec;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* requires i915 binding */
|
||||||
|
if (!codec->bus->core.audio_component) {
|
||||||
|
codec_info(codec, "No i915 binding for Intel HDMI/DP codec\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = alloc_generic_hdmi(codec);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
spec = codec->spec;
|
||||||
|
|
||||||
/* For Valleyview/Cherryview, only the display codec is in the display
|
/* For Valleyview/Cherryview, only the display codec is in the display
|
||||||
* power well and can use link_power ops to request/release the power.
|
* power well and can use link_power ops to request/release the power.
|
||||||
* For Haswell/Broadwell, the controller is also in the power well and
|
|
||||||
* can cover the codec power request, and so need not set this flag.
|
|
||||||
* For previous platforms, there is no such power well feature.
|
|
||||||
*/
|
*/
|
||||||
if (is_valleyview_plus(codec) || is_skylake(codec) ||
|
codec->core.link_power_control = 1;
|
||||||
is_broxton(codec))
|
|
||||||
codec->core.link_power_control = 1;
|
|
||||||
|
|
||||||
if (hdmi_parse_codec(codec) < 0) {
|
codec->depop_delay = 0;
|
||||||
if (spec->i915_bound)
|
codec->auto_runtime_pm = 1;
|
||||||
snd_hdac_i915_exit(&codec->bus->core);
|
|
||||||
codec->spec = NULL;
|
|
||||||
kfree(spec);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
codec->patch_ops = generic_hdmi_patch_ops;
|
|
||||||
if (is_haswell_plus(codec)) {
|
|
||||||
codec->patch_ops.set_power_state = haswell_set_power_state;
|
|
||||||
codec->dp_mst = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Enable runtime pm for HDMI audio codec of HSW/BDW/SKL/BYT/BSW */
|
spec->ops.pin_cvt_fixup = i915_pin_cvt_fixup;
|
||||||
if (is_haswell_plus(codec) || is_valleyview_plus(codec))
|
|
||||||
codec->auto_runtime_pm = 1;
|
err = hdmi_parse_codec(codec);
|
||||||
|
if (err < 0) {
|
||||||
|
generic_spec_free(codec);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
generic_hdmi_init_per_pins(codec);
|
generic_hdmi_init_per_pins(codec);
|
||||||
|
register_i915_notifier(codec);
|
||||||
|
|
||||||
if (codec_has_acomp(codec)) {
|
|
||||||
codec->depop_delay = 0;
|
|
||||||
spec->i915_audio_ops.audio_ptr = codec;
|
|
||||||
/* intel_audio_codec_enable() or intel_audio_codec_disable()
|
|
||||||
* will call pin_eld_notify with using audio_ptr pointer
|
|
||||||
* We need make sure audio_ptr is really setup
|
|
||||||
*/
|
|
||||||
wmb();
|
|
||||||
spec->i915_audio_ops.pin_eld_notify = intel_pin_eld_notify;
|
|
||||||
snd_hdac_i915_register_notifier(&spec->i915_audio_ops);
|
|
||||||
}
|
|
||||||
|
|
||||||
WARN_ON(spec->dyn_pcm_assign && !codec_has_acomp(codec));
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Intel IronLake, SandyBridge and IvyBridge; with eld notifier */
|
||||||
|
static int patch_i915_cpt_hdmi(struct hda_codec *codec)
|
||||||
|
{
|
||||||
|
struct hdmi_spec *spec;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* no i915 component should have been bound before this */
|
||||||
|
if (WARN_ON(codec->bus->core.audio_component))
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
|
err = alloc_generic_hdmi(codec);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
spec = codec->spec;
|
||||||
|
|
||||||
|
/* Try to bind with i915 now */
|
||||||
|
err = snd_hdac_i915_init(&codec->bus->core);
|
||||||
|
if (err < 0)
|
||||||
|
goto error;
|
||||||
|
spec->i915_bound = true;
|
||||||
|
|
||||||
|
err = hdmi_parse_codec(codec);
|
||||||
|
if (err < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
generic_hdmi_init_per_pins(codec);
|
||||||
|
register_i915_notifier(codec);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
generic_spec_free(codec);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Shared non-generic implementations
|
* Shared non-generic implementations
|
||||||
*/
|
*/
|
||||||
|
@ -3492,21 +3640,21 @@ HDA_CODEC_ENTRY(0x11069f80, "VX900 HDMI/DP", patch_via_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x11069f81, "VX900 HDMI/DP", patch_via_hdmi),
|
HDA_CODEC_ENTRY(0x11069f81, "VX900 HDMI/DP", patch_via_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x11069f84, "VX11 HDMI/DP", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x11069f84, "VX11 HDMI/DP", patch_generic_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x11069f85, "VX11 HDMI/DP", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x11069f85, "VX11 HDMI/DP", patch_generic_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80860054, "IbexPeak HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80860054, "IbexPeak HDMI", patch_i915_cpt_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862801, "Bearlake HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862801, "Bearlake HDMI", patch_generic_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862802, "Cantiga HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862802, "Cantiga HDMI", patch_generic_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862803, "Eaglelake HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862803, "Eaglelake HDMI", patch_generic_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862804, "IbexPeak HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862804, "IbexPeak HDMI", patch_i915_cpt_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862805, "CougarPoint HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862805, "CougarPoint HDMI", patch_i915_cpt_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862806, "PantherPoint HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862806, "PantherPoint HDMI", patch_i915_cpt_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862807, "Haswell HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862807, "Haswell HDMI", patch_i915_hsw_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862808, "Broadwell HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862808, "Broadwell HDMI", patch_i915_hsw_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862809, "Skylake HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862809, "Skylake HDMI", patch_i915_hsw_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x8086280a, "Broxton HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x8086280a, "Broxton HDMI", patch_i915_hsw_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x8086280b, "Kabylake HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x8086280b, "Kabylake HDMI", patch_i915_hsw_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862880, "CedarTrail HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862880, "CedarTrail HDMI", patch_generic_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862882, "Valleyview2 HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862882, "Valleyview2 HDMI", patch_i915_byt_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x80862883, "Braswell HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x80862883, "Braswell HDMI", patch_i915_byt_hdmi),
|
||||||
HDA_CODEC_ENTRY(0x808629fb, "Crestline HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(0x808629fb, "Crestline HDMI", patch_generic_hdmi),
|
||||||
/* special ID for generic HDMI */
|
/* special ID for generic HDMI */
|
||||||
HDA_CODEC_ENTRY(HDA_CODEC_ID_GENERIC_HDMI, "Generic HDMI", patch_generic_hdmi),
|
HDA_CODEC_ENTRY(HDA_CODEC_ID_GENERIC_HDMI, "Generic HDMI", patch_generic_hdmi),
|
||||||
|
|
|
@ -342,6 +342,11 @@ static void alc_fill_eapd_coef(struct hda_codec *codec)
|
||||||
case 0x10ec0293:
|
case 0x10ec0293:
|
||||||
alc_update_coef_idx(codec, 0xa, 1<<13, 0);
|
alc_update_coef_idx(codec, 0xa, 1<<13, 0);
|
||||||
break;
|
break;
|
||||||
|
case 0x10ec0234:
|
||||||
|
case 0x10ec0274:
|
||||||
|
case 0x10ec0294:
|
||||||
|
alc_update_coef_idx(codec, 0x10, 1<<15, 0);
|
||||||
|
break;
|
||||||
case 0x10ec0662:
|
case 0x10ec0662:
|
||||||
if ((coef & 0x00f0) == 0x0030)
|
if ((coef & 0x00f0) == 0x0030)
|
||||||
alc_update_coef_idx(codec, 0x4, 1<<10, 0); /* EAPD Ctrl */
|
alc_update_coef_idx(codec, 0x4, 1<<10, 0); /* EAPD Ctrl */
|
||||||
|
@ -2647,6 +2652,7 @@ enum {
|
||||||
ALC269_TYPE_ALC255,
|
ALC269_TYPE_ALC255,
|
||||||
ALC269_TYPE_ALC256,
|
ALC269_TYPE_ALC256,
|
||||||
ALC269_TYPE_ALC225,
|
ALC269_TYPE_ALC225,
|
||||||
|
ALC269_TYPE_ALC294,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2677,6 +2683,7 @@ static int alc269_parse_auto_config(struct hda_codec *codec)
|
||||||
case ALC269_TYPE_ALC255:
|
case ALC269_TYPE_ALC255:
|
||||||
case ALC269_TYPE_ALC256:
|
case ALC269_TYPE_ALC256:
|
||||||
case ALC269_TYPE_ALC225:
|
case ALC269_TYPE_ALC225:
|
||||||
|
case ALC269_TYPE_ALC294:
|
||||||
ssids = alc269_ssids;
|
ssids = alc269_ssids;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -6028,6 +6035,11 @@ static int patch_alc269(struct hda_codec *codec)
|
||||||
case 0x10ec0225:
|
case 0x10ec0225:
|
||||||
spec->codec_variant = ALC269_TYPE_ALC225;
|
spec->codec_variant = ALC269_TYPE_ALC225;
|
||||||
break;
|
break;
|
||||||
|
case 0x10ec0234:
|
||||||
|
case 0x10ec0274:
|
||||||
|
case 0x10ec0294:
|
||||||
|
spec->codec_variant = ALC269_TYPE_ALC294;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snd_hda_codec_read(codec, 0x51, 0, AC_VERB_PARAMETERS, 0) == 0x10ec5505) {
|
if (snd_hda_codec_read(codec, 0x51, 0, AC_VERB_PARAMETERS, 0) == 0x10ec5505) {
|
||||||
|
@ -6942,6 +6954,7 @@ static const struct hda_device_id snd_hda_id_realtek[] = {
|
||||||
HDA_CODEC_ENTRY(0x10ec0225, "ALC225", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0225, "ALC225", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0231, "ALC231", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0231, "ALC231", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0233, "ALC233", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0233, "ALC233", patch_alc269),
|
||||||
|
HDA_CODEC_ENTRY(0x10ec0234, "ALC234", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0235, "ALC233", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0235, "ALC233", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0255, "ALC255", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0255, "ALC255", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0256, "ALC256", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0256, "ALC256", patch_alc269),
|
||||||
|
@ -6952,6 +6965,7 @@ static const struct hda_device_id snd_hda_id_realtek[] = {
|
||||||
HDA_CODEC_ENTRY(0x10ec0269, "ALC269", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0269, "ALC269", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0270, "ALC270", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0270, "ALC270", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0272, "ALC272", patch_alc662),
|
HDA_CODEC_ENTRY(0x10ec0272, "ALC272", patch_alc662),
|
||||||
|
HDA_CODEC_ENTRY(0x10ec0274, "ALC274", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0275, "ALC275", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0275, "ALC275", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0276, "ALC276", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0276, "ALC276", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0280, "ALC280", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0280, "ALC280", patch_alc269),
|
||||||
|
@ -6964,6 +6978,7 @@ static const struct hda_device_id snd_hda_id_realtek[] = {
|
||||||
HDA_CODEC_ENTRY(0x10ec0290, "ALC290", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0290, "ALC290", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0292, "ALC292", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0292, "ALC292", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0293, "ALC293", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0293, "ALC293", patch_alc269),
|
||||||
|
HDA_CODEC_ENTRY(0x10ec0294, "ALC294", patch_alc269),
|
||||||
HDA_CODEC_ENTRY(0x10ec0298, "ALC298", patch_alc269),
|
HDA_CODEC_ENTRY(0x10ec0298, "ALC298", patch_alc269),
|
||||||
HDA_CODEC_REV_ENTRY(0x10ec0861, 0x100340, "ALC660", patch_alc861),
|
HDA_CODEC_REV_ENTRY(0x10ec0861, 0x100340, "ALC660", patch_alc861),
|
||||||
HDA_CODEC_ENTRY(0x10ec0660, "ALC660-VD", patch_alc861vd),
|
HDA_CODEC_ENTRY(0x10ec0660, "ALC660-VD", patch_alc861vd),
|
||||||
|
|
|
@ -42,12 +42,6 @@
|
||||||
#include <asm/pgtable.h>
|
#include <asm/pgtable.h>
|
||||||
#include <asm/cacheflush.h>
|
#include <asm/cacheflush.h>
|
||||||
|
|
||||||
#ifdef CONFIG_KVM_GUEST
|
|
||||||
#include <linux/kvm_para.h>
|
|
||||||
#else
|
|
||||||
#define kvm_para_available() (0)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
||||||
MODULE_DESCRIPTION("Intel 82801AA,82901AB,i810,i820,i830,i840,i845,MX440; SiS 7012; Ali 5455");
|
MODULE_DESCRIPTION("Intel 82801AA,82901AB,i810,i820,i830,i840,i845,MX440; SiS 7012; Ali 5455");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
@ -2972,25 +2966,17 @@ static int snd_intel8x0_inside_vm(struct pci_dev *pci)
|
||||||
goto fini;
|
goto fini;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* detect KVM and Parallels virtual environments */
|
|
||||||
result = kvm_para_available();
|
|
||||||
#ifdef X86_FEATURE_HYPERVISOR
|
|
||||||
result = result || boot_cpu_has(X86_FEATURE_HYPERVISOR);
|
|
||||||
#endif
|
|
||||||
if (!result)
|
|
||||||
goto fini;
|
|
||||||
|
|
||||||
/* check for known (emulated) devices */
|
/* check for known (emulated) devices */
|
||||||
|
result = 0;
|
||||||
if (pci->subsystem_vendor == PCI_SUBVENDOR_ID_REDHAT_QUMRANET &&
|
if (pci->subsystem_vendor == PCI_SUBVENDOR_ID_REDHAT_QUMRANET &&
|
||||||
pci->subsystem_device == PCI_SUBDEVICE_ID_QEMU) {
|
pci->subsystem_device == PCI_SUBDEVICE_ID_QEMU) {
|
||||||
/* KVM emulated sound, PCI SSID: 1af4:1100 */
|
/* KVM emulated sound, PCI SSID: 1af4:1100 */
|
||||||
msg = "enable KVM";
|
msg = "enable KVM";
|
||||||
|
result = 1;
|
||||||
} else if (pci->subsystem_vendor == 0x1ab8) {
|
} else if (pci->subsystem_vendor == 0x1ab8) {
|
||||||
/* Parallels VM emulated sound, PCI SSID: 1ab8:xxxx */
|
/* Parallels VM emulated sound, PCI SSID: 1ab8:xxxx */
|
||||||
msg = "enable Parallels VM";
|
msg = "enable Parallels VM";
|
||||||
} else {
|
result = 1;
|
||||||
msg = "disable (unknown or VT-d) VM";
|
|
||||||
result = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fini:
|
fini:
|
||||||
|
|
|
@ -644,7 +644,7 @@ static int lx_pipe_wait_for_state(struct lx6464es *chip, u32 pipe,
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (current_state == state)
|
if (!err && current_state == state)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
mdelay(1);
|
mdelay(1);
|
||||||
|
|
|
@ -350,6 +350,7 @@ static int snd_usb_audio_create(struct usb_interface *intf,
|
||||||
case USB_SPEED_HIGH:
|
case USB_SPEED_HIGH:
|
||||||
case USB_SPEED_WIRELESS:
|
case USB_SPEED_WIRELESS:
|
||||||
case USB_SPEED_SUPER:
|
case USB_SPEED_SUPER:
|
||||||
|
case USB_SPEED_SUPER_PLUS:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
dev_err(&dev->dev, "unknown device speed %d\n", snd_usb_get_speed(dev));
|
dev_err(&dev->dev, "unknown device speed %d\n", snd_usb_get_speed(dev));
|
||||||
|
@ -450,6 +451,9 @@ static int snd_usb_audio_create(struct usb_interface *intf,
|
||||||
case USB_SPEED_SUPER:
|
case USB_SPEED_SUPER:
|
||||||
strlcat(card->longname, ", super speed", sizeof(card->longname));
|
strlcat(card->longname, ", super speed", sizeof(card->longname));
|
||||||
break;
|
break;
|
||||||
|
case USB_SPEED_SUPER_PLUS:
|
||||||
|
strlcat(card->longname, ", super speed plus", sizeof(card->longname));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,6 +309,9 @@ static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface,
|
||||||
* support reading */
|
* support reading */
|
||||||
if (snd_usb_get_sample_rate_quirk(chip))
|
if (snd_usb_get_sample_rate_quirk(chip))
|
||||||
return 0;
|
return 0;
|
||||||
|
/* the firmware is likely buggy, don't repeat to fail too many times */
|
||||||
|
if (chip->sample_rate_read_error > 2)
|
||||||
|
return 0;
|
||||||
|
|
||||||
if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR,
|
if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR,
|
||||||
USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN,
|
USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN,
|
||||||
|
@ -316,6 +319,7 @@ static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface,
|
||||||
data, sizeof(data))) < 0) {
|
data, sizeof(data))) < 0) {
|
||||||
dev_err(&dev->dev, "%d:%d: cannot get freq at ep %#x\n",
|
dev_err(&dev->dev, "%d:%d: cannot get freq at ep %#x\n",
|
||||||
iface, fmt->altsetting, ep);
|
iface, fmt->altsetting, ep);
|
||||||
|
chip->sample_rate_read_error++;
|
||||||
return 0; /* some devices don't support reading */
|
return 0; /* some devices don't support reading */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,7 @@ unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip,
|
||||||
case USB_SPEED_HIGH:
|
case USB_SPEED_HIGH:
|
||||||
case USB_SPEED_WIRELESS:
|
case USB_SPEED_WIRELESS:
|
||||||
case USB_SPEED_SUPER:
|
case USB_SPEED_SUPER:
|
||||||
|
case USB_SPEED_SUPER_PLUS:
|
||||||
if (get_endpoint(alts, 0)->bInterval >= 1 &&
|
if (get_endpoint(alts, 0)->bInterval >= 1 &&
|
||||||
get_endpoint(alts, 0)->bInterval <= 4)
|
get_endpoint(alts, 0)->bInterval <= 4)
|
||||||
return get_endpoint(alts, 0)->bInterval - 1;
|
return get_endpoint(alts, 0)->bInterval - 1;
|
||||||
|
|
|
@ -911,6 +911,7 @@ static void snd_usbmidi_us122l_output(struct snd_usb_midi_out_endpoint *ep,
|
||||||
switch (snd_usb_get_speed(ep->umidi->dev)) {
|
switch (snd_usb_get_speed(ep->umidi->dev)) {
|
||||||
case USB_SPEED_HIGH:
|
case USB_SPEED_HIGH:
|
||||||
case USB_SPEED_SUPER:
|
case USB_SPEED_SUPER:
|
||||||
|
case USB_SPEED_SUPER_PLUS:
|
||||||
count = 1;
|
count = 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/list.h>
|
#include <linux/list.h>
|
||||||
|
#include <linux/log2.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/string.h>
|
#include <linux/string.h>
|
||||||
#include <linux/usb.h>
|
#include <linux/usb.h>
|
||||||
|
@ -1378,6 +1379,71 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
|
||||||
snd_usb_mixer_add_control(&cval->head, kctl);
|
snd_usb_mixer_add_control(&cval->head, kctl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int parse_clock_source_unit(struct mixer_build *state, int unitid,
|
||||||
|
void *_ftr)
|
||||||
|
{
|
||||||
|
struct uac_clock_source_descriptor *hdr = _ftr;
|
||||||
|
struct usb_mixer_elem_info *cval;
|
||||||
|
struct snd_kcontrol *kctl;
|
||||||
|
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (state->mixer->protocol != UAC_VERSION_2)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (hdr->bLength != sizeof(*hdr)) {
|
||||||
|
usb_audio_dbg(state->chip,
|
||||||
|
"Bogus clock source descriptor length of %d, ignoring.\n",
|
||||||
|
hdr->bLength);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The only property of this unit we are interested in is the
|
||||||
|
* clock source validity. If that isn't readable, just bail out.
|
||||||
|
*/
|
||||||
|
if (!uac2_control_is_readable(hdr->bmControls,
|
||||||
|
ilog2(UAC2_CS_CONTROL_CLOCK_VALID)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
cval = kzalloc(sizeof(*cval), GFP_KERNEL);
|
||||||
|
if (!cval)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
snd_usb_mixer_elem_init_std(&cval->head, state->mixer, hdr->bClockID);
|
||||||
|
|
||||||
|
cval->min = 0;
|
||||||
|
cval->max = 1;
|
||||||
|
cval->channels = 1;
|
||||||
|
cval->val_type = USB_MIXER_BOOLEAN;
|
||||||
|
cval->control = UAC2_CS_CONTROL_CLOCK_VALID;
|
||||||
|
|
||||||
|
if (uac2_control_is_writeable(hdr->bmControls,
|
||||||
|
ilog2(UAC2_CS_CONTROL_CLOCK_VALID)))
|
||||||
|
kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
|
||||||
|
else {
|
||||||
|
cval->master_readonly = 1;
|
||||||
|
kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kctl) {
|
||||||
|
kfree(cval);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
kctl->private_free = snd_usb_mixer_elem_free;
|
||||||
|
ret = snd_usb_copy_string_desc(state, hdr->iClockSource,
|
||||||
|
name, sizeof(name));
|
||||||
|
if (ret > 0)
|
||||||
|
snprintf(kctl->id.name, sizeof(kctl->id.name),
|
||||||
|
"%s Validity", name);
|
||||||
|
else
|
||||||
|
snprintf(kctl->id.name, sizeof(kctl->id.name),
|
||||||
|
"Clock Source %d Validity", hdr->bClockID);
|
||||||
|
|
||||||
|
return snd_usb_mixer_add_control(&cval->head, kctl);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* parse a feature unit
|
* parse a feature unit
|
||||||
*
|
*
|
||||||
|
@ -2126,10 +2192,11 @@ static int parse_audio_unit(struct mixer_build *state, int unitid)
|
||||||
|
|
||||||
switch (p1[2]) {
|
switch (p1[2]) {
|
||||||
case UAC_INPUT_TERMINAL:
|
case UAC_INPUT_TERMINAL:
|
||||||
case UAC2_CLOCK_SOURCE:
|
|
||||||
return 0; /* NOP */
|
return 0; /* NOP */
|
||||||
case UAC_MIXER_UNIT:
|
case UAC_MIXER_UNIT:
|
||||||
return parse_audio_mixer_unit(state, unitid, p1);
|
return parse_audio_mixer_unit(state, unitid, p1);
|
||||||
|
case UAC2_CLOCK_SOURCE:
|
||||||
|
return parse_clock_source_unit(state, unitid, p1);
|
||||||
case UAC_SELECTOR_UNIT:
|
case UAC_SELECTOR_UNIT:
|
||||||
case UAC2_CLOCK_SELECTOR:
|
case UAC2_CLOCK_SELECTOR:
|
||||||
return parse_audio_selector_unit(state, unitid, p1);
|
return parse_audio_selector_unit(state, unitid, p1);
|
||||||
|
@ -2307,6 +2374,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
|
||||||
__u8 unitid = (index >> 8) & 0xff;
|
__u8 unitid = (index >> 8) & 0xff;
|
||||||
__u8 control = (value >> 8) & 0xff;
|
__u8 control = (value >> 8) & 0xff;
|
||||||
__u8 channel = value & 0xff;
|
__u8 channel = value & 0xff;
|
||||||
|
unsigned int count = 0;
|
||||||
|
|
||||||
if (channel >= MAX_CHANNELS) {
|
if (channel >= MAX_CHANNELS) {
|
||||||
usb_audio_dbg(mixer->chip,
|
usb_audio_dbg(mixer->chip,
|
||||||
|
@ -2315,6 +2383,12 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (list = mixer->id_elems[unitid]; list; list = list->next_id_elem)
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
for (list = mixer->id_elems[unitid]; list; list = list->next_id_elem) {
|
for (list = mixer->id_elems[unitid]; list; list = list->next_id_elem) {
|
||||||
struct usb_mixer_elem_info *info;
|
struct usb_mixer_elem_info *info;
|
||||||
|
|
||||||
|
@ -2322,7 +2396,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
info = (struct usb_mixer_elem_info *)list;
|
info = (struct usb_mixer_elem_info *)list;
|
||||||
if (info->control != control)
|
if (count > 1 && info->control != control)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
switch (attribute) {
|
switch (attribute) {
|
||||||
|
|
|
@ -47,6 +47,7 @@ struct snd_usb_audio {
|
||||||
|
|
||||||
int num_interfaces;
|
int num_interfaces;
|
||||||
int num_suspended_intf;
|
int num_suspended_intf;
|
||||||
|
int sample_rate_read_error;
|
||||||
|
|
||||||
struct list_head pcm_list; /* list of pcm streams */
|
struct list_head pcm_list; /* list of pcm streams */
|
||||||
struct list_head ep_list; /* list of audio-related endpoints */
|
struct list_head ep_list; /* list of audio-related endpoints */
|
||||||
|
|
Loading…
Reference in New Issue