mirror of
https://github.com/torvalds/linux.git
synced 2026-06-04 04:23:35 +02:00
Merge branch 'tag/merge-20191121' into merge/upstream-merge-20191121
This commit is contained in:
commit
2a38242a90
|
|
@ -25,6 +25,13 @@ Required properties:
|
|||
|
||||
For required properties on SPI/I2C, consult SPI/I2C device tree documentation
|
||||
|
||||
Optional properties:
|
||||
|
||||
- reset-gpios : Optional reset gpio line connected to RST pin of the codec.
|
||||
The RST line is low active:
|
||||
RST = low: device power-down
|
||||
RST = high: device is enabled
|
||||
|
||||
Examples:
|
||||
|
||||
i2c0: i2c0@0 {
|
||||
|
|
@ -34,6 +41,7 @@ i2c0: i2c0@0 {
|
|||
pcm3168a: audio-codec@44 {
|
||||
compatible = "ti,pcm3168a";
|
||||
reg = <0x44>;
|
||||
reset-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>;
|
||||
clocks = <&clk_core CLK_AUDIO>;
|
||||
clock-names = "scki";
|
||||
VDD1-supply = <&supply3v3>;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,11 @@ Optional properties:
|
|||
3 or MICBIAS_AVDD - MICBIAS output is connected to AVDD
|
||||
If this node is not mentioned or if the value is unknown, then
|
||||
micbias is set to 2.0V.
|
||||
- ai31xx-ocmv - output common-mode voltage setting
|
||||
0 - 1.35V,
|
||||
1 - 1.5V,
|
||||
2 - 1.65V,
|
||||
3 - 1.8V
|
||||
|
||||
Deprecated properties:
|
||||
|
||||
|
|
|
|||
|
|
@ -805,6 +805,7 @@ destructor and PCI entries. Example code is shown first, below.
|
|||
return -EBUSY;
|
||||
}
|
||||
chip->irq = pci->irq;
|
||||
card->sync_irq = chip->irq;
|
||||
|
||||
/* (2) initialization of the chip hardware */
|
||||
.... /* (not implemented in this document) */
|
||||
|
|
@ -965,6 +966,15 @@ usually like the following:
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
After requesting the IRQ, you can passed it to ``card->sync_irq``
|
||||
field:
|
||||
::
|
||||
|
||||
card->irq = chip->irq;
|
||||
|
||||
This allows PCM core automatically performing
|
||||
:c:func:`synchronize_irq()` at the necessary timing like ``hw_free``.
|
||||
See the later section `sync_stop callback`_ for details.
|
||||
|
||||
Now let's write the corresponding destructor for the resources above.
|
||||
The role of destructor is simple: disable the hardware (if already
|
||||
|
|
@ -1270,21 +1280,23 @@ shows only the skeleton, how to build up the PCM interfaces.
|
|||
/* the hardware-specific codes will be here */
|
||||
....
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/* hw_params callback */
|
||||
static int snd_mychip_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
return snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
/* the hardware-specific codes will be here */
|
||||
....
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* hw_free callback */
|
||||
static int snd_mychip_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
/* the hardware-specific codes will be here */
|
||||
....
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* prepare callback */
|
||||
|
|
@ -1339,7 +1351,6 @@ shows only the skeleton, how to build up the PCM interfaces.
|
|||
static struct snd_pcm_ops snd_mychip_playback_ops = {
|
||||
.open = snd_mychip_playback_open,
|
||||
.close = snd_mychip_playback_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_mychip_pcm_hw_params,
|
||||
.hw_free = snd_mychip_pcm_hw_free,
|
||||
.prepare = snd_mychip_pcm_prepare,
|
||||
|
|
@ -1351,7 +1362,6 @@ shows only the skeleton, how to build up the PCM interfaces.
|
|||
static struct snd_pcm_ops snd_mychip_capture_ops = {
|
||||
.open = snd_mychip_capture_open,
|
||||
.close = snd_mychip_capture_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_mychip_pcm_hw_params,
|
||||
.hw_free = snd_mychip_pcm_hw_free,
|
||||
.prepare = snd_mychip_pcm_prepare,
|
||||
|
|
@ -1382,9 +1392,9 @@ shows only the skeleton, how to build up the PCM interfaces.
|
|||
&snd_mychip_capture_ops);
|
||||
/* pre-allocation of buffers */
|
||||
/* NOTE: this may fail */
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
&chip->pci->dev,
|
||||
64*1024, 64*1024);
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
&chip->pci->dev,
|
||||
64*1024, 64*1024);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1454,7 +1464,6 @@ The operators are defined typically like this:
|
|||
static struct snd_pcm_ops snd_mychip_playback_ops = {
|
||||
.open = snd_mychip_pcm_open,
|
||||
.close = snd_mychip_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_mychip_pcm_hw_params,
|
||||
.hw_free = snd_mychip_pcm_hw_free,
|
||||
.prepare = snd_mychip_pcm_prepare,
|
||||
|
|
@ -1465,13 +1474,14 @@ The operators are defined typically like this:
|
|||
All the callbacks are described in the Operators_ subsection.
|
||||
|
||||
After setting the operators, you probably will want to pre-allocate the
|
||||
buffer. For the pre-allocation, simply call the following:
|
||||
buffer and set up the managed allocation mode.
|
||||
For that, simply call the following:
|
||||
|
||||
::
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
&chip->pci->dev,
|
||||
64*1024, 64*1024);
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
&chip->pci->dev,
|
||||
64*1024, 64*1024);
|
||||
|
||||
It will allocate a buffer up to 64kB as default. Buffer management
|
||||
details will be described in the later section `Buffer and Memory
|
||||
|
|
@ -1621,8 +1631,7 @@ For the operators (callbacks) of each sound driver, most of these
|
|||
records are supposed to be read-only. Only the PCM middle-layer changes
|
||||
/ updates them. The exceptions are the hardware description (hw) DMA
|
||||
buffer information and the private data. Besides, if you use the
|
||||
standard buffer allocation method via
|
||||
:c:func:`snd_pcm_lib_malloc_pages()`, you don't need to set the
|
||||
standard managed buffer allocation mode, you don't need to set the
|
||||
DMA buffer information by yourself.
|
||||
|
||||
In the sections below, important records are explained.
|
||||
|
|
@ -1776,8 +1785,8 @@ the physical address of the buffer. This field is specified only when
|
|||
the buffer is a linear buffer. ``dma_bytes`` holds the size of buffer
|
||||
in bytes. ``dma_private`` is used for the ALSA DMA allocator.
|
||||
|
||||
If you use a standard ALSA function,
|
||||
:c:func:`snd_pcm_lib_malloc_pages()`, for allocating the buffer,
|
||||
If you use either the managed buffer allocation mode or the standard
|
||||
API function :c:func:`snd_pcm_lib_malloc_pages()` for allocating the buffer,
|
||||
these fields are set by the ALSA middle layer, and you should *not*
|
||||
change them by yourself. You can read them but not write them. On the
|
||||
other hand, if you want to allocate the buffer by yourself, you'll
|
||||
|
|
@ -1911,7 +1920,10 @@ ioctl callback
|
|||
~~~~~~~~~~~~~~
|
||||
|
||||
This is used for any special call to pcm ioctls. But usually you can
|
||||
pass a generic ioctl callback, :c:func:`snd_pcm_lib_ioctl()`.
|
||||
leave it as NULL, then PCM core calls the generic ioctl callback
|
||||
function :c:func:`snd_pcm_lib_ioctl()`. If you need to deal with the
|
||||
unique setup of channel info or reset procedure, you can pass your own
|
||||
callback function here.
|
||||
|
||||
hw_params callback
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -1929,8 +1941,12 @@ Many hardware setups should be done in this callback, including the
|
|||
allocation of buffers.
|
||||
|
||||
Parameters to be initialized are retrieved by
|
||||
:c:func:`params_xxx()` macros. To allocate buffer, you can call a
|
||||
helper function,
|
||||
:c:func:`params_xxx()` macros.
|
||||
|
||||
When you set up the managed buffer allocation mode for the substream,
|
||||
a buffer is already allocated before this callback gets
|
||||
called. Alternatively, you can call a helper function below for
|
||||
allocating the buffer, too.
|
||||
|
||||
::
|
||||
|
||||
|
|
@ -1964,18 +1980,23 @@ hw_free callback
|
|||
static int snd_xxx_hw_free(struct snd_pcm_substream *substream);
|
||||
|
||||
This is called to release the resources allocated via
|
||||
``hw_params``. For example, releasing the buffer via
|
||||
:c:func:`snd_pcm_lib_malloc_pages()` is done by calling the
|
||||
following:
|
||||
|
||||
::
|
||||
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
``hw_params``.
|
||||
|
||||
This function is always called before the close callback is called.
|
||||
Also, the callback may be called multiple times, too. Keep track
|
||||
whether the resource was already released.
|
||||
|
||||
When you have set up the managed buffer allocation mode for the PCM
|
||||
substream, the allocated PCM buffer will be automatically released
|
||||
after this callback gets called. Otherwise you'll have to release the
|
||||
buffer manually. Typically, when the buffer was allocated from the
|
||||
pre-allocated pool, you can use the standard API function
|
||||
:c:func:`snd_pcm_lib_malloc_pages()` like:
|
||||
|
||||
::
|
||||
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
|
||||
prepare callback
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
@ -2048,6 +2069,37 @@ flag set, and you cannot call functions which may sleep. The
|
|||
triggering the DMA. The other stuff should be initialized
|
||||
``hw_params`` and ``prepare`` callbacks properly beforehand.
|
||||
|
||||
sync_stop callback
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
static int snd_xxx_sync_stop(struct snd_pcm_substream *substream);
|
||||
|
||||
This callback is optional, and NULL can be passed. It's called after
|
||||
the PCM core stops the stream and changes the stream state
|
||||
``prepare``, ``hw_params`` or ``hw_free``.
|
||||
Since the IRQ handler might be still pending, we need to wait until
|
||||
the pending task finishes before moving to the next step; otherwise it
|
||||
might lead to a crash due to resource conflicts or access to the freed
|
||||
resources. A typical behavior is to call a synchronization function
|
||||
like :c:func:`synchronize_irq()` here.
|
||||
|
||||
For majority of drivers that need only a call of
|
||||
:c:func:`synchronize_irq()`, there is a simpler setup, too.
|
||||
While keeping NULL to ``sync_stop`` PCM callback, the driver can set
|
||||
``card->sync_irq`` field to store the valid interrupt number after
|
||||
requesting an IRQ, instead. Then PCM core will look call
|
||||
:c:func:`synchronize_irq()` with the given IRQ appropriately.
|
||||
|
||||
If the IRQ handler is released at the card destructor, you don't need
|
||||
to clear ``card->sync_irq``, as the card itself is being released.
|
||||
So, usually you'll need to add just a single line for assigning
|
||||
``card->sync_irq`` in the driver code unless the driver re-acquires
|
||||
the IRQ. When the driver frees and re-acquires the IRQ dynamically
|
||||
(e.g. for suspend/resume), it needs to clear and re-set
|
||||
``card->sync_irq`` again appropriately.
|
||||
|
||||
pointer callback
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
@ -3543,6 +3595,25 @@ Once the buffer is pre-allocated, you can use the allocator in the
|
|||
|
||||
Note that you have to pre-allocate to use this function.
|
||||
|
||||
Most of drivers use, though, rather the newly introduced "managed
|
||||
buffer allocation mode" instead of the manual allocation or release.
|
||||
This is done by calling :c:func:`snd_pcm_set_managed_buffer_all()`
|
||||
instead of :c:func:`snd_pcm_lib_preallocate_pages_for_all()`.
|
||||
|
||||
::
|
||||
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
&pci->dev, size, max);
|
||||
|
||||
where passed arguments are identical in both functions.
|
||||
The difference in the managed mode is that PCM core will call
|
||||
:c:func:`snd_pcm_lib_malloc_pages()` internally already before calling
|
||||
the PCM ``hw_params`` callback, and call :c:func:`snd_pcm_lib_free_pages()`
|
||||
after the PCM ``hw_free`` callback automatically. So the driver
|
||||
doesn't have to call these functions explicitly in its callback any
|
||||
longer. This made many driver code having NULL ``hw_params`` and
|
||||
``hw_free`` entries.
|
||||
|
||||
External Hardware Buffers
|
||||
-------------------------
|
||||
|
||||
|
|
@ -3697,8 +3768,8 @@ provides an interface for handling SG-buffers. The API is provided in
|
|||
``<sound/pcm.h>``.
|
||||
|
||||
For creating the SG-buffer handler, call
|
||||
:c:func:`snd_pcm_lib_preallocate_pages()` or
|
||||
:c:func:`snd_pcm_lib_preallocate_pages_for_all()` with
|
||||
:c:func:`snd_pcm_set_managed_buffer()` or
|
||||
:c:func:`snd_pcm_set_managed_buffer_all()` with
|
||||
``SNDRV_DMA_TYPE_DEV_SG`` in the PCM constructor like other PCI
|
||||
pre-allocator. You need to pass ``&pci->dev``, where pci is
|
||||
the :c:type:`struct pci_dev <pci_dev>` pointer of the chip as
|
||||
|
|
@ -3706,8 +3777,8 @@ well.
|
|||
|
||||
::
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
|
||||
&pci->dev, size, max);
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
|
||||
&pci->dev, size, max);
|
||||
|
||||
The ``struct snd_sg_buf`` instance is created as
|
||||
``substream->dma_private`` in turn. You can cast the pointer like:
|
||||
|
|
@ -3716,8 +3787,7 @@ The ``struct snd_sg_buf`` instance is created as
|
|||
|
||||
struct snd_sg_buf *sgbuf = (struct snd_sg_buf *)substream->dma_private;
|
||||
|
||||
Then call :c:func:`snd_pcm_lib_malloc_pages()` in the ``hw_params``
|
||||
callback as well as in the case of normal PCI buffer. The SG-buffer
|
||||
Then in :c:func:`snd_pcm_lib_malloc_pages()` call, the common SG-buffer
|
||||
handler will allocate the non-contiguous kernel pages of the given size
|
||||
and map them onto the virtually contiguous memory. The virtual pointer
|
||||
is addressed in runtime->dma_area. The physical address
|
||||
|
|
@ -3726,8 +3796,8 @@ physically non-contiguous. The physical address table is set up in
|
|||
``sgbuf->table``. You can get the physical address at a certain offset
|
||||
via :c:func:`snd_pcm_sgbuf_get_addr()`.
|
||||
|
||||
To release the data, call :c:func:`snd_pcm_lib_free_pages()` in
|
||||
the ``hw_free`` callback as usual.
|
||||
If you need to release the SG-buffer data explicitly, call the
|
||||
standard API function :c:func:`snd_pcm_lib_free_pages()` as usual.
|
||||
|
||||
Vmalloc'ed Buffers
|
||||
------------------
|
||||
|
|
@ -3740,8 +3810,8 @@ buffer preallocation with ``SNDRV_DMA_TYPE_VMALLOC`` type.
|
|||
|
||||
::
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
NULL, 0, 0);
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
NULL, 0, 0);
|
||||
|
||||
The NULL is passed to the device pointer argument, which indicates
|
||||
that the default pages (GFP_KERNEL and GFP_HIGHMEM) will be
|
||||
|
|
@ -3758,7 +3828,7 @@ argument.
|
|||
|
||||
::
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
snd_dma_continuous_data(GFP_KERNEL | __GFP_DMA32), 0, 0);
|
||||
|
||||
Proc Interface
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ struct snd_card {
|
|||
struct device card_dev; /* cardX object for sysfs */
|
||||
const struct attribute_group *dev_groups[4]; /* assigned sysfs attr */
|
||||
bool registered; /* card_dev is registered? */
|
||||
int sync_irq; /* assigned irq, used for PCM sync */
|
||||
wait_queue_head_t remove_sleep;
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ struct snd_pcm_ops {
|
|||
int (*hw_free)(struct snd_pcm_substream *substream);
|
||||
int (*prepare)(struct snd_pcm_substream *substream);
|
||||
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
|
||||
int (*sync_stop)(struct snd_pcm_substream *substream);
|
||||
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
|
||||
int (*get_time_info)(struct snd_pcm_substream *substream,
|
||||
struct timespec *system_ts, struct timespec *audio_ts,
|
||||
|
|
@ -395,6 +396,7 @@ struct snd_pcm_runtime {
|
|||
wait_queue_head_t sleep; /* poll sleep */
|
||||
wait_queue_head_t tsleep; /* transfer sleep */
|
||||
struct fasync_struct *fasync;
|
||||
bool stop_operating; /* sync_stop will be called */
|
||||
|
||||
/* -- private section -- */
|
||||
void *private_data;
|
||||
|
|
@ -414,6 +416,7 @@ struct snd_pcm_runtime {
|
|||
size_t dma_bytes; /* size of DMA area */
|
||||
|
||||
struct snd_dma_buffer *dma_buffer_p; /* allocated buffer */
|
||||
unsigned int buffer_changed:1; /* buffer allocation changed; set only in managed mode */
|
||||
|
||||
/* -- audio timestamp config -- */
|
||||
struct snd_pcm_audio_tstamp_config audio_tstamp_config;
|
||||
|
|
@ -475,6 +478,7 @@ struct snd_pcm_substream {
|
|||
#endif /* CONFIG_SND_VERBOSE_PROCFS */
|
||||
/* misc flags */
|
||||
unsigned int hw_opened: 1;
|
||||
unsigned int managed_buffer_alloc:1;
|
||||
};
|
||||
|
||||
#define SUBSTREAM_BUSY(substream) ((substream)->ref_count > 0)
|
||||
|
|
@ -1186,6 +1190,12 @@ void snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
|
|||
int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size);
|
||||
int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream);
|
||||
|
||||
void snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type,
|
||||
struct device *data, size_t size, size_t max);
|
||||
void snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm, int type,
|
||||
struct device *data,
|
||||
size_t size, size_t max);
|
||||
|
||||
int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream,
|
||||
size_t size, gfp_t gfp_flags);
|
||||
int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream);
|
||||
|
|
@ -1328,8 +1338,6 @@ static inline void snd_pcm_limit_isa_dma_size(int dma, size_t *max)
|
|||
(IEC958_AES1_CON_PCM_CODER<<8)|\
|
||||
(IEC958_AES3_CON_FS_48000<<24))
|
||||
|
||||
#define PCM_RUNTIME_CHECK(sub) snd_BUG_ON(!(sub) || !(sub)->runtime)
|
||||
|
||||
const char *snd_pcm_format_name(snd_pcm_format_t format);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ struct page *snd_soc_pcm_component_page(struct snd_pcm_substream *substream,
|
|||
unsigned long offset);
|
||||
int snd_soc_pcm_component_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma);
|
||||
int snd_soc_pcm_component_new(struct snd_pcm *pcm);
|
||||
void snd_soc_pcm_component_free(struct snd_pcm *pcm);
|
||||
int snd_soc_pcm_component_new(struct snd_soc_pcm_runtime *rtd);
|
||||
void snd_soc_pcm_component_free(struct snd_soc_pcm_runtime *rtd);
|
||||
|
||||
#endif /* __SOC_COMPONENT_H */
|
||||
|
|
|
|||
|
|
@ -299,6 +299,12 @@
|
|||
.put = snd_soc_bytes_put, .private_value = \
|
||||
((unsigned long)&(struct soc_bytes) \
|
||||
{.base = xbase, .num_regs = xregs }) }
|
||||
#define SND_SOC_BYTES_E(xname, xbase, xregs, xhandler_get, xhandler_put) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||
.info = snd_soc_bytes_info, .get = xhandler_get, \
|
||||
.put = xhandler_put, .private_value = \
|
||||
((unsigned long)&(struct soc_bytes) \
|
||||
{.base = xbase, .num_regs = xregs }) }
|
||||
|
||||
#define SND_SOC_BYTES_MASK(xname, xbase, xregs, xmask) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||
|
|
@ -982,6 +988,7 @@ struct snd_soc_card {
|
|||
const char *name;
|
||||
const char *long_name;
|
||||
const char *driver_name;
|
||||
const char *components;
|
||||
char dmi_longname[80];
|
||||
char topology_shortname[32];
|
||||
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ int snd_card_new(struct device *parent, int idx, const char *xid,
|
|||
init_waitqueue_head(&card->power_sleep);
|
||||
#endif
|
||||
init_waitqueue_head(&card->remove_sleep);
|
||||
card->sync_irq = -1;
|
||||
|
||||
device_initialize(&card->card_dev);
|
||||
card->card_dev.parent = parent;
|
||||
|
|
|
|||
|
|
@ -72,4 +72,6 @@ struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream,
|
|||
unsigned long offset);
|
||||
#endif
|
||||
|
||||
#define PCM_RUNTIME_CHECK(sub) snd_BUG_ON(!(sub) || !(sub)->runtime)
|
||||
|
||||
#endif /* __SOUND_CORE_PCM_LOCAL_H */
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <sound/pcm.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/initval.h>
|
||||
#include "pcm_local.h"
|
||||
|
||||
static int preallocate_dma = 1;
|
||||
module_param(preallocate_dma, int, 0444);
|
||||
|
|
@ -193,9 +194,15 @@ static inline void preallocate_info_init(struct snd_pcm_substream *substream)
|
|||
/*
|
||||
* pre-allocate the buffer and create a proc file for the substream
|
||||
*/
|
||||
static void snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
|
||||
size_t size, size_t max)
|
||||
static void preallocate_pages(struct snd_pcm_substream *substream,
|
||||
int type, struct device *data,
|
||||
size_t size, size_t max, bool managed)
|
||||
{
|
||||
if (snd_BUG_ON(substream->dma_buffer.dev.type))
|
||||
return;
|
||||
|
||||
substream->dma_buffer.dev.type = type;
|
||||
substream->dma_buffer.dev.dev = data;
|
||||
|
||||
if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
|
||||
preallocate_pcm_pages(substream, size);
|
||||
|
|
@ -205,8 +212,23 @@ static void snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
|
|||
substream->dma_max = max;
|
||||
if (max > 0)
|
||||
preallocate_info_init(substream);
|
||||
if (managed)
|
||||
substream->managed_buffer_alloc = 1;
|
||||
}
|
||||
|
||||
static void preallocate_pages_for_all(struct snd_pcm *pcm, int type,
|
||||
void *data, size_t size, size_t max,
|
||||
bool managed)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++)
|
||||
for (substream = pcm->streams[stream].substream; substream;
|
||||
substream = substream->next)
|
||||
preallocate_pages(substream, type, data, size, max,
|
||||
managed);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type
|
||||
|
|
@ -222,11 +244,7 @@ void snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,
|
|||
int type, struct device *data,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
if (snd_BUG_ON(substream->dma_buffer.dev.type))
|
||||
return;
|
||||
substream->dma_buffer.dev.type = type;
|
||||
substream->dma_buffer.dev.dev = data;
|
||||
snd_pcm_lib_preallocate_pages1(substream, size, max);
|
||||
preallocate_pages(substream, type, data, size, max, false);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages);
|
||||
|
||||
|
|
@ -245,15 +263,55 @@ void snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
|
|||
int type, void *data,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++)
|
||||
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
|
||||
snd_pcm_lib_preallocate_pages(substream, type, data, size, max);
|
||||
preallocate_pages_for_all(pcm, type, data, size, max, false);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all);
|
||||
|
||||
/**
|
||||
* snd_pcm_set_managed_buffer - set up buffer management for a substream
|
||||
* @substream: the pcm substream instance
|
||||
* @type: DMA type (SNDRV_DMA_TYPE_*)
|
||||
* @data: DMA type dependent data
|
||||
* @size: the requested pre-allocation size in bytes
|
||||
* @max: the max. allowed pre-allocation size
|
||||
*
|
||||
* Do pre-allocation for the given DMA buffer type, and set the managed
|
||||
* buffer allocation mode to the given substream.
|
||||
* In this mode, PCM core will allocate a buffer automatically before PCM
|
||||
* hw_params ops call, and release the buffer after PCM hw_free ops call
|
||||
* as well, so that the driver doesn't need to invoke the allocation and
|
||||
* the release explicitly in its callback.
|
||||
* When a buffer is actually allocated before the PCM hw_params call, it
|
||||
* turns on the runtime buffer_changed flag for drivers changing their h/w
|
||||
* parameters accordingly.
|
||||
*/
|
||||
void snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type,
|
||||
struct device *data, size_t size, size_t max)
|
||||
{
|
||||
preallocate_pages(substream, type, data, size, max, true);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_set_managed_buffer);
|
||||
|
||||
/**
|
||||
* snd_pcm_set_managed_buffer_all - set up buffer management for all substreams
|
||||
* for all substreams
|
||||
* @pcm: the pcm instance
|
||||
* @type: DMA type (SNDRV_DMA_TYPE_*)
|
||||
* @data: DMA type dependent data
|
||||
* @size: the requested pre-allocation size in bytes
|
||||
* @max: the max. allowed pre-allocation size
|
||||
*
|
||||
* Do pre-allocation to all substreams of the given pcm for the specified DMA
|
||||
* type and size, and set the managed_buffer_alloc flag to each substream.
|
||||
*/
|
||||
void snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm, int type,
|
||||
struct device *data,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
preallocate_pages_for_all(pcm, type, data, size, max, true);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_set_managed_buffer_all);
|
||||
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
/*
|
||||
* snd_pcm_sgbuf_ops_page - get the page struct at the given offset
|
||||
|
|
|
|||
|
|
@ -178,6 +178,16 @@ void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore);
|
||||
|
||||
/* Run PCM ioctl ops */
|
||||
static int snd_pcm_ops_ioctl(struct snd_pcm_substream *substream,
|
||||
unsigned cmd, void *arg)
|
||||
{
|
||||
if (substream->ops->ioctl)
|
||||
return substream->ops->ioctl(substream, cmd, arg);
|
||||
else
|
||||
return snd_pcm_lib_ioctl(substream, cmd, arg);
|
||||
}
|
||||
|
||||
int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info)
|
||||
{
|
||||
struct snd_pcm *pcm = substream->pcm;
|
||||
|
|
@ -448,8 +458,9 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream,
|
|||
m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||
i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
if (snd_mask_single(m) && snd_interval_single(i)) {
|
||||
err = substream->ops->ioctl(substream,
|
||||
SNDRV_PCM_IOCTL1_FIFO_SIZE, params);
|
||||
err = snd_pcm_ops_ioctl(substream,
|
||||
SNDRV_PCM_IOCTL1_FIFO_SIZE,
|
||||
params);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
|
@ -557,6 +568,17 @@ static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream,
|
|||
#endif
|
||||
}
|
||||
|
||||
static void snd_pcm_sync_stop(struct snd_pcm_substream *substream)
|
||||
{
|
||||
if (substream->runtime->stop_operating) {
|
||||
substream->runtime->stop_operating = false;
|
||||
if (substream->ops->sync_stop)
|
||||
substream->ops->sync_stop(substream);
|
||||
else if (substream->pcm->card->sync_irq > 0)
|
||||
synchronize_irq(substream->pcm->card->sync_irq);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_hw_param_choose - choose a configuration defined by @params
|
||||
* @pcm: PCM instance
|
||||
|
|
@ -649,6 +671,8 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
if (atomic_read(&substream->mmap_count))
|
||||
return -EBADFD;
|
||||
|
||||
snd_pcm_sync_stop(substream);
|
||||
|
||||
params->rmask = ~0U;
|
||||
err = snd_pcm_hw_refine(substream, params);
|
||||
if (err < 0)
|
||||
|
|
@ -662,6 +686,14 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
if (err < 0)
|
||||
goto _error;
|
||||
|
||||
if (substream->managed_buffer_alloc) {
|
||||
err = snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(params));
|
||||
if (err < 0)
|
||||
goto _error;
|
||||
runtime->buffer_changed = err > 0;
|
||||
}
|
||||
|
||||
if (substream->ops->hw_params != NULL) {
|
||||
err = substream->ops->hw_params(substream, params);
|
||||
if (err < 0)
|
||||
|
|
@ -723,6 +755,8 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
|
||||
if (substream->ops->hw_free != NULL)
|
||||
substream->ops->hw_free(substream);
|
||||
if (substream->managed_buffer_alloc)
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -767,8 +801,11 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
|
|||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (atomic_read(&substream->mmap_count))
|
||||
return -EBADFD;
|
||||
snd_pcm_sync_stop(substream);
|
||||
if (substream->ops->hw_free)
|
||||
result = substream->ops->hw_free(substream);
|
||||
if (substream->managed_buffer_alloc)
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
|
||||
pm_qos_remove_request(&substream->latency_pm_qos_req);
|
||||
return result;
|
||||
|
|
@ -959,7 +996,7 @@ static int snd_pcm_channel_info(struct snd_pcm_substream *substream,
|
|||
return -EINVAL;
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->channel = channel;
|
||||
return substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info);
|
||||
return snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info);
|
||||
}
|
||||
|
||||
static int snd_pcm_channel_info_user(struct snd_pcm_substream *substream,
|
||||
|
|
@ -1290,6 +1327,7 @@ static void snd_pcm_post_stop(struct snd_pcm_substream *substream, int state)
|
|||
runtime->status->state = state;
|
||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP);
|
||||
}
|
||||
runtime->stop_operating = true;
|
||||
wake_up(&runtime->sleep);
|
||||
wake_up(&runtime->tsleep);
|
||||
}
|
||||
|
|
@ -1566,6 +1604,7 @@ static void snd_pcm_post_resume(struct snd_pcm_substream *substream, int state)
|
|||
snd_pcm_trigger_tstamp(substream);
|
||||
runtime->status->state = runtime->status->suspended_state;
|
||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME);
|
||||
snd_pcm_sync_stop(substream);
|
||||
}
|
||||
|
||||
static const struct action_ops snd_pcm_action_resume = {
|
||||
|
|
@ -1635,7 +1674,7 @@ static int snd_pcm_pre_reset(struct snd_pcm_substream *substream, int state)
|
|||
static int snd_pcm_do_reset(struct snd_pcm_substream *substream, int state)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
|
||||
int err = snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
runtime->hw_ptr_base = 0;
|
||||
|
|
@ -1686,6 +1725,7 @@ static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream,
|
|||
static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, int state)
|
||||
{
|
||||
int err;
|
||||
snd_pcm_sync_stop(substream);
|
||||
err = substream->ops->prepare(substream);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include <sound/pcm_params.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/timer.h>
|
||||
|
||||
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
||||
MODULE_DESCRIPTION("A loopback soundcard");
|
||||
|
|
@ -41,6 +42,7 @@ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
|
|||
static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
|
||||
static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
|
||||
static int pcm_notify[SNDRV_CARDS];
|
||||
static char *timer_source[SNDRV_CARDS];
|
||||
|
||||
module_param_array(index, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(index, "Index value for loopback soundcard.");
|
||||
|
|
@ -52,11 +54,48 @@ module_param_array(pcm_substreams, int, NULL, 0444);
|
|||
MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver.");
|
||||
module_param_array(pcm_notify, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes.");
|
||||
module_param_array(timer_source, charp, NULL, 0444);
|
||||
MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default].");
|
||||
|
||||
#define NO_PITCH 100000
|
||||
|
||||
#define CABLE_VALID_PLAYBACK BIT(SNDRV_PCM_STREAM_PLAYBACK)
|
||||
#define CABLE_VALID_CAPTURE BIT(SNDRV_PCM_STREAM_CAPTURE)
|
||||
#define CABLE_VALID_BOTH (CABLE_VALID_PLAYBACK | CABLE_VALID_CAPTURE)
|
||||
|
||||
struct loopback_cable;
|
||||
struct loopback_pcm;
|
||||
|
||||
struct loopback_ops {
|
||||
/* optional
|
||||
* call in loopback->cable_lock
|
||||
*/
|
||||
int (*open)(struct loopback_pcm *dpcm);
|
||||
/* required
|
||||
* call in cable->lock
|
||||
*/
|
||||
int (*start)(struct loopback_pcm *dpcm);
|
||||
/* required
|
||||
* call in cable->lock
|
||||
*/
|
||||
int (*stop)(struct loopback_pcm *dpcm);
|
||||
/* optional */
|
||||
int (*stop_sync)(struct loopback_pcm *dpcm);
|
||||
/* optional */
|
||||
int (*close_substream)(struct loopback_pcm *dpcm);
|
||||
/* optional
|
||||
* call in loopback->cable_lock
|
||||
*/
|
||||
int (*close_cable)(struct loopback_pcm *dpcm);
|
||||
/* optional
|
||||
* call in cable->lock
|
||||
*/
|
||||
unsigned int (*pos_update)(struct loopback_cable *cable);
|
||||
/* optional */
|
||||
void (*dpcm_info)(struct loopback_pcm *dpcm,
|
||||
struct snd_info_buffer *buffer);
|
||||
};
|
||||
|
||||
struct loopback_cable {
|
||||
spinlock_t lock;
|
||||
struct loopback_pcm *streams[2];
|
||||
|
|
@ -65,6 +104,15 @@ struct loopback_cable {
|
|||
unsigned int valid;
|
||||
unsigned int running;
|
||||
unsigned int pause;
|
||||
/* timer specific */
|
||||
struct loopback_ops *ops;
|
||||
/* If sound timer is used */
|
||||
struct {
|
||||
int stream;
|
||||
struct snd_timer_id id;
|
||||
struct tasklet_struct event_tasklet;
|
||||
struct snd_timer_instance *instance;
|
||||
} snd_timer;
|
||||
};
|
||||
|
||||
struct loopback_setup {
|
||||
|
|
@ -85,6 +133,7 @@ struct loopback {
|
|||
struct loopback_cable *cables[MAX_PCM_SUBSTREAMS][2];
|
||||
struct snd_pcm *pcm[2];
|
||||
struct loopback_setup setup[MAX_PCM_SUBSTREAMS][2];
|
||||
const char *timer_source;
|
||||
};
|
||||
|
||||
struct loopback_pcm {
|
||||
|
|
@ -102,10 +151,13 @@ struct loopback_pcm {
|
|||
/* flags */
|
||||
unsigned int period_update_pending :1;
|
||||
/* timer stuff */
|
||||
unsigned int irq_pos; /* fractional IRQ position */
|
||||
unsigned int period_size_frac;
|
||||
unsigned int irq_pos; /* fractional IRQ position in jiffies
|
||||
* ticks
|
||||
*/
|
||||
unsigned int period_size_frac; /* period size in jiffies ticks */
|
||||
unsigned int last_drift;
|
||||
unsigned long last_jiffies;
|
||||
/* If jiffies timer is used */
|
||||
struct timer_list timer;
|
||||
};
|
||||
|
||||
|
|
@ -153,7 +205,7 @@ static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm)
|
|||
}
|
||||
|
||||
/* call in cable->lock */
|
||||
static void loopback_timer_start(struct loopback_pcm *dpcm)
|
||||
static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm)
|
||||
{
|
||||
unsigned long tick;
|
||||
unsigned int rate_shift = get_rate_shift(dpcm);
|
||||
|
|
@ -169,23 +221,101 @@ static void loopback_timer_start(struct loopback_pcm *dpcm)
|
|||
tick = dpcm->period_size_frac - dpcm->irq_pos;
|
||||
tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
|
||||
mod_timer(&dpcm->timer, jiffies + tick);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* call in cable->lock */
|
||||
static inline void loopback_timer_stop(struct loopback_pcm *dpcm)
|
||||
static int loopback_snd_timer_start(struct loopback_pcm *dpcm)
|
||||
{
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
int err;
|
||||
|
||||
/* Loopback device has to use same period as timer card. Therefore
|
||||
* wake up for each snd_pcm_period_elapsed() call of timer card.
|
||||
*/
|
||||
err = snd_timer_start(cable->snd_timer.instance, 1);
|
||||
if (err < 0) {
|
||||
/* do not report error if trying to start but already
|
||||
* running. For example called by opposite substream
|
||||
* of the same cable
|
||||
*/
|
||||
if (err == -EBUSY)
|
||||
return 0;
|
||||
|
||||
pcm_err(dpcm->substream->pcm,
|
||||
"snd_timer_start(%d,%d,%d) failed with %d",
|
||||
cable->snd_timer.id.card,
|
||||
cable->snd_timer.id.device,
|
||||
cable->snd_timer.id.subdevice,
|
||||
err);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* call in cable->lock */
|
||||
static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm)
|
||||
{
|
||||
del_timer(&dpcm->timer);
|
||||
dpcm->timer.expires = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void loopback_timer_stop_sync(struct loopback_pcm *dpcm)
|
||||
/* call in cable->lock */
|
||||
static int loopback_snd_timer_stop(struct loopback_pcm *dpcm)
|
||||
{
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
int err;
|
||||
|
||||
/* only stop if both devices (playback and capture) are not running */
|
||||
if (cable->running ^ cable->pause)
|
||||
return 0;
|
||||
|
||||
err = snd_timer_stop(cable->snd_timer.instance);
|
||||
if (err < 0) {
|
||||
pcm_err(dpcm->substream->pcm,
|
||||
"snd_timer_stop(%d,%d,%d) failed with %d",
|
||||
cable->snd_timer.id.card,
|
||||
cable->snd_timer.id.device,
|
||||
cable->snd_timer.id.subdevice,
|
||||
err);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
|
||||
{
|
||||
del_timer_sync(&dpcm->timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CABLE_VALID_PLAYBACK (1 << SNDRV_PCM_STREAM_PLAYBACK)
|
||||
#define CABLE_VALID_CAPTURE (1 << SNDRV_PCM_STREAM_CAPTURE)
|
||||
#define CABLE_VALID_BOTH (CABLE_VALID_PLAYBACK|CABLE_VALID_CAPTURE)
|
||||
/* call in loopback->cable_lock */
|
||||
static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
|
||||
{
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
|
||||
/* snd_timer was not opened */
|
||||
if (!cable->snd_timer.instance)
|
||||
return 0;
|
||||
|
||||
/* wait till drain tasklet has finished if requested */
|
||||
tasklet_kill(&cable->snd_timer.event_tasklet);
|
||||
|
||||
/* will only be called from free_cable() when other stream was
|
||||
* already closed. Other stream cannot be reopened as long as
|
||||
* loopback->cable_lock is locked. Therefore no need to lock
|
||||
* cable->lock;
|
||||
*/
|
||||
snd_timer_close(cable->snd_timer.instance);
|
||||
snd_timer_instance_free(cable->snd_timer.instance);
|
||||
memset(&cable->snd_timer, 0, sizeof(cable->snd_timer));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int loopback_check_format(struct loopback_cable *cable, int stream)
|
||||
{
|
||||
|
|
@ -249,7 +379,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct loopback_pcm *dpcm = runtime->private_data;
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
int err, stream = 1 << substream->stream;
|
||||
int err = 0, stream = 1 << substream->stream;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
|
|
@ -262,7 +392,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
spin_lock(&cable->lock);
|
||||
cable->running |= stream;
|
||||
cable->pause &= ~stream;
|
||||
loopback_timer_start(dpcm);
|
||||
err = cable->ops->start(dpcm);
|
||||
spin_unlock(&cable->lock);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
loopback_active_notify(dpcm);
|
||||
|
|
@ -271,7 +401,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
spin_lock(&cable->lock);
|
||||
cable->running &= ~stream;
|
||||
cable->pause &= ~stream;
|
||||
loopback_timer_stop(dpcm);
|
||||
err = cable->ops->stop(dpcm);
|
||||
spin_unlock(&cable->lock);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
loopback_active_notify(dpcm);
|
||||
|
|
@ -280,7 +410,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
spin_lock(&cable->lock);
|
||||
cable->pause |= stream;
|
||||
loopback_timer_stop(dpcm);
|
||||
err = cable->ops->stop(dpcm);
|
||||
spin_unlock(&cable->lock);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
loopback_active_notify(dpcm);
|
||||
|
|
@ -290,7 +420,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
spin_lock(&cable->lock);
|
||||
dpcm->last_jiffies = jiffies;
|
||||
cable->pause &= ~stream;
|
||||
loopback_timer_start(dpcm);
|
||||
err = cable->ops->start(dpcm);
|
||||
spin_unlock(&cable->lock);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
loopback_active_notify(dpcm);
|
||||
|
|
@ -298,7 +428,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static void params_change(struct snd_pcm_substream *substream)
|
||||
|
|
@ -312,6 +442,13 @@ static void params_change(struct snd_pcm_substream *substream)
|
|||
cable->hw.rate_max = runtime->rate;
|
||||
cable->hw.channels_min = runtime->channels;
|
||||
cable->hw.channels_max = runtime->channels;
|
||||
|
||||
if (cable->snd_timer.instance) {
|
||||
cable->hw.period_bytes_min =
|
||||
frames_to_bytes(runtime, runtime->period_size);
|
||||
cable->hw.period_bytes_max = cable->hw.period_bytes_min;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static int loopback_prepare(struct snd_pcm_substream *substream)
|
||||
|
|
@ -319,9 +456,13 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
|
|||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct loopback_pcm *dpcm = runtime->private_data;
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
int bps, salign;
|
||||
int err, bps, salign;
|
||||
|
||||
loopback_timer_stop_sync(dpcm);
|
||||
if (cable->ops->stop_sync) {
|
||||
err = cable->ops->stop_sync(dpcm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
salign = (snd_pcm_format_physical_width(runtime->format) *
|
||||
runtime->channels) / 8;
|
||||
|
|
@ -457,7 +598,8 @@ static inline void bytepos_finish(struct loopback_pcm *dpcm,
|
|||
}
|
||||
|
||||
/* call in cable->lock */
|
||||
static unsigned int loopback_pos_update(struct loopback_cable *cable)
|
||||
static unsigned int loopback_jiffies_timer_pos_update
|
||||
(struct loopback_cable *cable)
|
||||
{
|
||||
struct loopback_pcm *dpcm_play =
|
||||
cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
|
||||
|
|
@ -510,14 +652,15 @@ static unsigned int loopback_pos_update(struct loopback_cable *cable)
|
|||
return running;
|
||||
}
|
||||
|
||||
static void loopback_timer_function(struct timer_list *t)
|
||||
static void loopback_jiffies_timer_function(struct timer_list *t)
|
||||
{
|
||||
struct loopback_pcm *dpcm = from_timer(dpcm, t, timer);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dpcm->cable->lock, flags);
|
||||
if (loopback_pos_update(dpcm->cable) & (1 << dpcm->substream->stream)) {
|
||||
loopback_timer_start(dpcm);
|
||||
if (loopback_jiffies_timer_pos_update(dpcm->cable) &
|
||||
(1 << dpcm->substream->stream)) {
|
||||
loopback_jiffies_timer_start(dpcm);
|
||||
if (dpcm->period_update_pending) {
|
||||
dpcm->period_update_pending = 0;
|
||||
spin_unlock_irqrestore(&dpcm->cable->lock, flags);
|
||||
|
|
@ -529,6 +672,193 @@ static void loopback_timer_function(struct timer_list *t)
|
|||
spin_unlock_irqrestore(&dpcm->cable->lock, flags);
|
||||
}
|
||||
|
||||
/* call in cable->lock */
|
||||
static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime,
|
||||
unsigned long resolution)
|
||||
{
|
||||
if (resolution != runtime->timer_resolution) {
|
||||
struct loopback_pcm *dpcm = runtime->private_data;
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
/* Worst case estimation of possible values for resolution
|
||||
* resolution <= (512 * 1024) frames / 8kHz in nsec
|
||||
* resolution <= 65.536.000.000 nsec
|
||||
*
|
||||
* period_size <= 65.536.000.000 nsec / 1000nsec/usec * 192kHz +
|
||||
* 500.000
|
||||
* period_size <= 12.582.912.000.000 <64bit
|
||||
* / 1.000.000 usec/sec
|
||||
*/
|
||||
snd_pcm_uframes_t period_size_usec =
|
||||
resolution / 1000 * runtime->rate;
|
||||
/* round to nearest sample rate */
|
||||
snd_pcm_uframes_t period_size =
|
||||
(period_size_usec + 500 * 1000) / (1000 * 1000);
|
||||
|
||||
pcm_err(dpcm->substream->pcm,
|
||||
"Period size (%lu frames) of loopback device is not corresponding to timer resolution (%lu nsec = %lu frames) of card timer %d,%d,%d. Use period size of %lu frames for loopback device.",
|
||||
runtime->period_size, resolution, period_size,
|
||||
cable->snd_timer.id.card,
|
||||
cable->snd_timer.id.device,
|
||||
cable->snd_timer.id.subdevice,
|
||||
period_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void loopback_snd_timer_period_elapsed(struct loopback_cable *cable,
|
||||
int event,
|
||||
unsigned long resolution)
|
||||
{
|
||||
struct loopback_pcm *dpcm_play, *dpcm_capt;
|
||||
struct snd_pcm_substream *substream_play, *substream_capt;
|
||||
struct snd_pcm_runtime *valid_runtime;
|
||||
unsigned int running, elapsed_bytes;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&cable->lock, flags);
|
||||
running = cable->running ^ cable->pause;
|
||||
/* no need to do anything if no stream is running */
|
||||
if (!running) {
|
||||
spin_unlock_irqrestore(&cable->lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
dpcm_play = cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
|
||||
dpcm_capt = cable->streams[SNDRV_PCM_STREAM_CAPTURE];
|
||||
substream_play = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ?
|
||||
dpcm_play->substream : NULL;
|
||||
substream_capt = (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) ?
|
||||
dpcm_capt->substream : NULL;
|
||||
|
||||
if (event == SNDRV_TIMER_EVENT_MSTOP) {
|
||||
if (!dpcm_play ||
|
||||
dpcm_play->substream->runtime->status->state !=
|
||||
SNDRV_PCM_STATE_DRAINING) {
|
||||
spin_unlock_irqrestore(&cable->lock, flags);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
valid_runtime = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ?
|
||||
dpcm_play->substream->runtime :
|
||||
dpcm_capt->substream->runtime;
|
||||
|
||||
/* resolution is only valid for SNDRV_TIMER_EVENT_TICK events */
|
||||
if (event == SNDRV_TIMER_EVENT_TICK) {
|
||||
/* The hardware rules guarantee that playback and capture period
|
||||
* are the same. Therefore only one device has to be checked
|
||||
* here.
|
||||
*/
|
||||
if (loopback_snd_timer_check_resolution(valid_runtime,
|
||||
resolution) < 0) {
|
||||
spin_unlock_irqrestore(&cable->lock, flags);
|
||||
if (substream_play)
|
||||
snd_pcm_stop_xrun(substream_play);
|
||||
if (substream_capt)
|
||||
snd_pcm_stop_xrun(substream_capt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
elapsed_bytes = frames_to_bytes(valid_runtime,
|
||||
valid_runtime->period_size);
|
||||
/* The same timer interrupt is used for playback and capture device */
|
||||
if ((running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) &&
|
||||
(running & (1 << SNDRV_PCM_STREAM_CAPTURE))) {
|
||||
copy_play_buf(dpcm_play, dpcm_capt, elapsed_bytes);
|
||||
bytepos_finish(dpcm_play, elapsed_bytes);
|
||||
bytepos_finish(dpcm_capt, elapsed_bytes);
|
||||
} else if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) {
|
||||
bytepos_finish(dpcm_play, elapsed_bytes);
|
||||
} else if (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) {
|
||||
clear_capture_buf(dpcm_capt, elapsed_bytes);
|
||||
bytepos_finish(dpcm_capt, elapsed_bytes);
|
||||
}
|
||||
spin_unlock_irqrestore(&cable->lock, flags);
|
||||
|
||||
if (substream_play)
|
||||
snd_pcm_period_elapsed(substream_play);
|
||||
if (substream_capt)
|
||||
snd_pcm_period_elapsed(substream_capt);
|
||||
}
|
||||
|
||||
static void loopback_snd_timer_function(struct snd_timer_instance *timeri,
|
||||
unsigned long resolution,
|
||||
unsigned long ticks)
|
||||
{
|
||||
struct loopback_cable *cable = timeri->callback_data;
|
||||
|
||||
loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_TICK,
|
||||
resolution);
|
||||
}
|
||||
|
||||
static void loopback_snd_timer_tasklet(unsigned long arg)
|
||||
{
|
||||
struct snd_timer_instance *timeri = (struct snd_timer_instance *)arg;
|
||||
struct loopback_cable *cable = timeri->callback_data;
|
||||
|
||||
loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_MSTOP, 0);
|
||||
}
|
||||
|
||||
static void loopback_snd_timer_event(struct snd_timer_instance *timeri,
|
||||
int event,
|
||||
struct timespec *tstamp,
|
||||
unsigned long resolution)
|
||||
{
|
||||
/* Do not lock cable->lock here because timer->lock is already hold.
|
||||
* There are other functions which first lock cable->lock and than
|
||||
* timer->lock e.g.
|
||||
* loopback_trigger()
|
||||
* spin_lock(&cable->lock)
|
||||
* loopback_snd_timer_start()
|
||||
* snd_timer_start()
|
||||
* spin_lock(&timer->lock)
|
||||
* Therefore when using the oposit order of locks here it could result
|
||||
* in a deadlock.
|
||||
*/
|
||||
|
||||
if (event == SNDRV_TIMER_EVENT_MSTOP) {
|
||||
struct loopback_cable *cable = timeri->callback_data;
|
||||
|
||||
/* sound card of the timer was stopped. Therefore there will not
|
||||
* be any further timer callbacks. Due to this forward audio
|
||||
* data from here if in draining state. When still in running
|
||||
* state the streaming will be aborted by the usual timeout. It
|
||||
* should not be aborted here because may be the timer sound
|
||||
* card does only a recovery and the timer is back soon.
|
||||
* This tasklet triggers loopback_snd_timer_tasklet()
|
||||
*/
|
||||
tasklet_schedule(&cable->snd_timer.event_tasklet);
|
||||
}
|
||||
}
|
||||
|
||||
static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
snd_iprintf(buffer, " update_pending:\t%u\n",
|
||||
dpcm->period_update_pending);
|
||||
snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos);
|
||||
snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac);
|
||||
snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n",
|
||||
dpcm->last_jiffies, jiffies);
|
||||
snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires);
|
||||
}
|
||||
|
||||
static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
|
||||
snd_iprintf(buffer, " sound timer:\thw:%d,%d,%d\n",
|
||||
cable->snd_timer.id.card,
|
||||
cable->snd_timer.id.device,
|
||||
cable->snd_timer.id.subdevice);
|
||||
snd_iprintf(buffer, " timer open:\t\t%s\n",
|
||||
(cable->snd_timer.stream == SNDRV_PCM_STREAM_CAPTURE) ?
|
||||
"capture" : "playback");
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
|
@ -536,7 +866,8 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
|
|||
snd_pcm_uframes_t pos;
|
||||
|
||||
spin_lock(&dpcm->cable->lock);
|
||||
loopback_pos_update(dpcm->cable);
|
||||
if (dpcm->cable->ops->pos_update)
|
||||
dpcm->cable->ops->pos_update(dpcm->cable);
|
||||
pos = dpcm->buf_pos;
|
||||
spin_unlock(&dpcm->cable->lock);
|
||||
return bytes_to_frames(runtime, pos);
|
||||
|
|
@ -646,6 +977,23 @@ static int rule_channels(struct snd_pcm_hw_params *params,
|
|||
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
|
||||
}
|
||||
|
||||
static int rule_period_bytes(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct loopback_pcm *dpcm = rule->private;
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
struct snd_interval t;
|
||||
|
||||
mutex_lock(&dpcm->loopback->cable_lock);
|
||||
t.min = cable->hw.period_bytes_min;
|
||||
t.max = cable->hw.period_bytes_max;
|
||||
mutex_unlock(&dpcm->loopback->cable_lock);
|
||||
t.openmin = 0;
|
||||
t.openmax = 0;
|
||||
t.integer = 0;
|
||||
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
|
||||
}
|
||||
|
||||
static void free_cable(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct loopback *loopback = substream->private_data;
|
||||
|
|
@ -661,12 +1009,190 @@ static void free_cable(struct snd_pcm_substream *substream)
|
|||
cable->streams[substream->stream] = NULL;
|
||||
spin_unlock_irq(&cable->lock);
|
||||
} else {
|
||||
struct loopback_pcm *dpcm = substream->runtime->private_data;
|
||||
|
||||
if (cable->ops && cable->ops->close_cable && dpcm)
|
||||
cable->ops->close_cable(dpcm);
|
||||
/* free the cable */
|
||||
loopback->cables[substream->number][dev] = NULL;
|
||||
kfree(cable);
|
||||
}
|
||||
}
|
||||
|
||||
static int loopback_jiffies_timer_open(struct loopback_pcm *dpcm)
|
||||
{
|
||||
timer_setup(&dpcm->timer, loopback_jiffies_timer_function, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct loopback_ops loopback_jiffies_timer_ops = {
|
||||
.open = loopback_jiffies_timer_open,
|
||||
.start = loopback_jiffies_timer_start,
|
||||
.stop = loopback_jiffies_timer_stop,
|
||||
.stop_sync = loopback_jiffies_timer_stop_sync,
|
||||
.close_substream = loopback_jiffies_timer_stop_sync,
|
||||
.pos_update = loopback_jiffies_timer_pos_update,
|
||||
.dpcm_info = loopback_jiffies_timer_dpcm_info,
|
||||
};
|
||||
|
||||
static int loopback_parse_timer_id(const char *str,
|
||||
struct snd_timer_id *tid)
|
||||
{
|
||||
/* [<pref>:](<card name>|<card idx>)[{.,}<dev idx>[{.,}<subdev idx>]] */
|
||||
const char * const sep_dev = ".,";
|
||||
const char * const sep_pref = ":";
|
||||
const char *name = str;
|
||||
char *sep, save = '\0';
|
||||
int card_idx = 0, dev = 0, subdev = 0;
|
||||
int err;
|
||||
|
||||
sep = strpbrk(str, sep_pref);
|
||||
if (sep)
|
||||
name = sep + 1;
|
||||
sep = strpbrk(name, sep_dev);
|
||||
if (sep) {
|
||||
save = *sep;
|
||||
*sep = '\0';
|
||||
}
|
||||
err = kstrtoint(name, 0, &card_idx);
|
||||
if (err == -EINVAL) {
|
||||
/* Must be the name, not number */
|
||||
for (card_idx = 0; card_idx < snd_ecards_limit; card_idx++) {
|
||||
struct snd_card *card = snd_card_ref(card_idx);
|
||||
|
||||
if (card) {
|
||||
if (!strcmp(card->id, name))
|
||||
err = 0;
|
||||
snd_card_unref(card);
|
||||
}
|
||||
if (!err)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sep) {
|
||||
*sep = save;
|
||||
if (!err) {
|
||||
char *sep2, save2 = '\0';
|
||||
|
||||
sep2 = strpbrk(sep + 1, sep_dev);
|
||||
if (sep2) {
|
||||
save2 = *sep2;
|
||||
*sep2 = '\0';
|
||||
}
|
||||
err = kstrtoint(sep + 1, 0, &dev);
|
||||
if (sep2) {
|
||||
*sep2 = save2;
|
||||
if (!err)
|
||||
err = kstrtoint(sep2 + 1, 0, &subdev);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!err && tid) {
|
||||
tid->card = card_idx;
|
||||
tid->device = dev;
|
||||
tid->subdevice = subdev;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* call in loopback->cable_lock */
|
||||
static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
|
||||
{
|
||||
int err = 0;
|
||||
struct snd_timer_id tid = {
|
||||
.dev_class = SNDRV_TIMER_CLASS_PCM,
|
||||
.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION,
|
||||
};
|
||||
struct snd_timer_instance *timeri;
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
|
||||
spin_lock_irq(&cable->lock);
|
||||
|
||||
/* check if timer was already opened. It is only opened once
|
||||
* per playback and capture subdevice (aka cable).
|
||||
*/
|
||||
if (cable->snd_timer.instance)
|
||||
goto unlock;
|
||||
|
||||
err = loopback_parse_timer_id(dpcm->loopback->timer_source, &tid);
|
||||
if (err < 0) {
|
||||
pcm_err(dpcm->substream->pcm,
|
||||
"Parsing timer source \'%s\' failed with %d",
|
||||
dpcm->loopback->timer_source, err);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
cable->snd_timer.stream = dpcm->substream->stream;
|
||||
cable->snd_timer.id = tid;
|
||||
|
||||
timeri = snd_timer_instance_new(dpcm->loopback->card->id);
|
||||
if (!timeri) {
|
||||
err = -ENOMEM;
|
||||
goto unlock;
|
||||
}
|
||||
/* The callback has to be called from another tasklet. If
|
||||
* SNDRV_TIMER_IFLG_FAST is specified it will be called from the
|
||||
* snd_pcm_period_elapsed() call of the selected sound card.
|
||||
* snd_pcm_period_elapsed() helds snd_pcm_stream_lock_irqsave().
|
||||
* Due to our callback loopback_snd_timer_function() also calls
|
||||
* snd_pcm_period_elapsed() which calls snd_pcm_stream_lock_irqsave().
|
||||
* This would end up in a dead lock.
|
||||
*/
|
||||
timeri->flags |= SNDRV_TIMER_IFLG_AUTO;
|
||||
timeri->callback = loopback_snd_timer_function;
|
||||
timeri->callback_data = (void *)cable;
|
||||
timeri->ccallback = loopback_snd_timer_event;
|
||||
|
||||
/* initialise a tasklet used for draining */
|
||||
tasklet_init(&cable->snd_timer.event_tasklet,
|
||||
loopback_snd_timer_tasklet, (unsigned long)timeri);
|
||||
|
||||
/* snd_timer_close() and snd_timer_open() should not be called with
|
||||
* locked spinlock because both functions can block on a mutex. The
|
||||
* mutex loopback->cable_lock is kept locked. Therefore snd_timer_open()
|
||||
* cannot be called a second time by the other device of the same cable.
|
||||
* Therefore the following issue cannot happen:
|
||||
* [proc1] Call loopback_timer_open() ->
|
||||
* Unlock cable->lock for snd_timer_close/open() call
|
||||
* [proc2] Call loopback_timer_open() -> snd_timer_open(),
|
||||
* snd_timer_start()
|
||||
* [proc1] Call snd_timer_open() and overwrite running timer
|
||||
* instance
|
||||
*/
|
||||
spin_unlock_irq(&cable->lock);
|
||||
err = snd_timer_open(timeri, &cable->snd_timer.id, current->pid);
|
||||
spin_lock_irq(&cable->lock);
|
||||
if (err < 0) {
|
||||
pcm_err(dpcm->substream->pcm,
|
||||
"snd_timer_open (%d,%d,%d) failed with %d",
|
||||
cable->snd_timer.id.card,
|
||||
cable->snd_timer.id.device,
|
||||
cable->snd_timer.id.subdevice,
|
||||
err);
|
||||
snd_timer_instance_free(timeri);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
cable->snd_timer.instance = timeri;
|
||||
|
||||
unlock:
|
||||
spin_unlock_irq(&cable->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* stop_sync() is not required for sound timer because it does not need to be
|
||||
* restarted in loopback_prepare() on Xrun recovery
|
||||
*/
|
||||
static struct loopback_ops loopback_snd_timer_ops = {
|
||||
.open = loopback_snd_timer_open,
|
||||
.start = loopback_snd_timer_start,
|
||||
.stop = loopback_snd_timer_stop,
|
||||
.close_cable = loopback_snd_timer_close_cable,
|
||||
.dpcm_info = loopback_snd_timer_dpcm_info,
|
||||
};
|
||||
|
||||
static int loopback_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
|
@ -684,7 +1210,6 @@ static int loopback_open(struct snd_pcm_substream *substream)
|
|||
}
|
||||
dpcm->loopback = loopback;
|
||||
dpcm->substream = substream;
|
||||
timer_setup(&dpcm->timer, loopback_timer_function, 0);
|
||||
|
||||
cable = loopback->cables[substream->number][dev];
|
||||
if (!cable) {
|
||||
|
|
@ -695,9 +1220,20 @@ static int loopback_open(struct snd_pcm_substream *substream)
|
|||
}
|
||||
spin_lock_init(&cable->lock);
|
||||
cable->hw = loopback_pcm_hardware;
|
||||
if (loopback->timer_source)
|
||||
cable->ops = &loopback_snd_timer_ops;
|
||||
else
|
||||
cable->ops = &loopback_jiffies_timer_ops;
|
||||
loopback->cables[substream->number][dev] = cable;
|
||||
}
|
||||
dpcm->cable = cable;
|
||||
runtime->private_data = dpcm;
|
||||
|
||||
if (cable->ops->open) {
|
||||
err = cable->ops->open(dpcm);
|
||||
if (err < 0)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
|
||||
|
|
@ -723,7 +1259,22 @@ static int loopback_open(struct snd_pcm_substream *substream)
|
|||
if (err < 0)
|
||||
goto unlock;
|
||||
|
||||
runtime->private_data = dpcm;
|
||||
/* In case of sound timer the period time of both devices of the same
|
||||
* loop has to be the same.
|
||||
* This rule only takes effect if a sound timer was chosen
|
||||
*/
|
||||
if (cable->snd_timer.instance) {
|
||||
err = snd_pcm_hw_rule_add(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
||||
rule_period_bytes, dpcm,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, -1);
|
||||
if (err < 0)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* loopback_runtime_free() has not to be called if kfree(dpcm) was
|
||||
* already called here. Otherwise it will end up with a double free.
|
||||
*/
|
||||
runtime->private_free = loopback_runtime_free;
|
||||
if (get_notify(dpcm))
|
||||
runtime->hw = loopback_pcm_hardware;
|
||||
|
|
@ -747,12 +1298,14 @@ static int loopback_close(struct snd_pcm_substream *substream)
|
|||
{
|
||||
struct loopback *loopback = substream->private_data;
|
||||
struct loopback_pcm *dpcm = substream->runtime->private_data;
|
||||
int err = 0;
|
||||
|
||||
loopback_timer_stop_sync(dpcm);
|
||||
if (dpcm->cable->ops->close_substream)
|
||||
err = dpcm->cable->ops->close_substream(dpcm);
|
||||
mutex_lock(&loopback->cable_lock);
|
||||
free_cable(substream);
|
||||
mutex_unlock(&loopback->cable_lock);
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct snd_pcm_ops loopback_pcm_ops = {
|
||||
|
|
@ -1076,13 +1629,8 @@ static void print_dpcm_info(struct snd_info_buffer *buffer,
|
|||
snd_iprintf(buffer, " bytes_per_sec:\t%u\n", dpcm->pcm_bps);
|
||||
snd_iprintf(buffer, " sample_align:\t%u\n", dpcm->pcm_salign);
|
||||
snd_iprintf(buffer, " rate_shift:\t\t%u\n", dpcm->pcm_rate_shift);
|
||||
snd_iprintf(buffer, " update_pending:\t%u\n",
|
||||
dpcm->period_update_pending);
|
||||
snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos);
|
||||
snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac);
|
||||
snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n",
|
||||
dpcm->last_jiffies, jiffies);
|
||||
snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires);
|
||||
if (dpcm->cable->ops->dpcm_info)
|
||||
dpcm->cable->ops->dpcm_info(dpcm, buffer);
|
||||
}
|
||||
|
||||
static void print_substream_info(struct snd_info_buffer *buffer,
|
||||
|
|
@ -1118,7 +1666,7 @@ static void print_cable_info(struct snd_info_entry *entry,
|
|||
mutex_unlock(&loopback->cable_lock);
|
||||
}
|
||||
|
||||
static int loopback_proc_new(struct loopback *loopback, int cidx)
|
||||
static int loopback_cable_proc_new(struct loopback *loopback, int cidx)
|
||||
{
|
||||
char name[32];
|
||||
|
||||
|
|
@ -1127,6 +1675,48 @@ static int loopback_proc_new(struct loopback *loopback, int cidx)
|
|||
print_cable_info);
|
||||
}
|
||||
|
||||
static void loopback_set_timer_source(struct loopback *loopback,
|
||||
const char *value)
|
||||
{
|
||||
if (loopback->timer_source) {
|
||||
devm_kfree(loopback->card->dev, loopback->timer_source);
|
||||
loopback->timer_source = NULL;
|
||||
}
|
||||
if (value && *value)
|
||||
loopback->timer_source = devm_kstrdup(loopback->card->dev,
|
||||
value, GFP_KERNEL);
|
||||
}
|
||||
|
||||
static void print_timer_source_info(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct loopback *loopback = entry->private_data;
|
||||
|
||||
mutex_lock(&loopback->cable_lock);
|
||||
snd_iprintf(buffer, "%s\n",
|
||||
loopback->timer_source ? loopback->timer_source : "");
|
||||
mutex_unlock(&loopback->cable_lock);
|
||||
}
|
||||
|
||||
static void change_timer_source_info(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct loopback *loopback = entry->private_data;
|
||||
char line[64];
|
||||
|
||||
mutex_lock(&loopback->cable_lock);
|
||||
if (!snd_info_get_line(buffer, line, sizeof(line)))
|
||||
loopback_set_timer_source(loopback, strim(line));
|
||||
mutex_unlock(&loopback->cable_lock);
|
||||
}
|
||||
|
||||
static int loopback_timer_source_proc_new(struct loopback *loopback)
|
||||
{
|
||||
return snd_card_rw_proc_new(loopback->card, "timer_source", loopback,
|
||||
print_timer_source_info,
|
||||
change_timer_source_info);
|
||||
}
|
||||
|
||||
static int loopback_probe(struct platform_device *devptr)
|
||||
{
|
||||
struct snd_card *card;
|
||||
|
|
@ -1146,6 +1736,8 @@ static int loopback_probe(struct platform_device *devptr)
|
|||
pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
|
||||
|
||||
loopback->card = card;
|
||||
loopback_set_timer_source(loopback, timer_source[dev]);
|
||||
|
||||
mutex_init(&loopback->cable_lock);
|
||||
|
||||
err = loopback_pcm_new(loopback, 0, pcm_substreams[dev]);
|
||||
|
|
@ -1157,8 +1749,9 @@ static int loopback_probe(struct platform_device *devptr)
|
|||
err = loopback_mixer_new(loopback, pcm_notify[dev] ? 1 : 0);
|
||||
if (err < 0)
|
||||
goto __nodev;
|
||||
loopback_proc_new(loopback, 0);
|
||||
loopback_proc_new(loopback, 1);
|
||||
loopback_cable_proc_new(loopback, 0);
|
||||
loopback_cable_proc_new(loopback, 1);
|
||||
loopback_timer_source_proc_new(loopback);
|
||||
strcpy(card->driver, "Loopback");
|
||||
strcpy(card->shortname, "Loopback");
|
||||
sprintf(card->longname, "Loopback %i", dev + 1);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid)
|
|||
EXPORT_SYMBOL_GPL(is_jack_detectable);
|
||||
|
||||
/* execute pin sense measurement */
|
||||
static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid)
|
||||
static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id)
|
||||
{
|
||||
u32 pincap;
|
||||
u32 val;
|
||||
|
|
@ -55,19 +55,57 @@ static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid)
|
|||
AC_VERB_SET_PIN_SENSE, 0);
|
||||
}
|
||||
val = snd_hda_codec_read(codec, nid, 0,
|
||||
AC_VERB_GET_PIN_SENSE, 0);
|
||||
AC_VERB_GET_PIN_SENSE, dev_id);
|
||||
if (codec->inv_jack_detect)
|
||||
val ^= AC_PINSENSE_PRESENCE;
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_jack_tbl_get - query the jack-table entry for the given NID
|
||||
* snd_hda_jack_tbl_get_mst - query the jack-table entry for the given NID
|
||||
* @codec: the HDA codec
|
||||
* @nid: pin NID to refer to
|
||||
* @dev_id: pin device entry id
|
||||
*/
|
||||
struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid)
|
||||
snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id)
|
||||
{
|
||||
struct hda_jack_tbl *jack = codec->jacktbl.list;
|
||||
int i;
|
||||
|
||||
if (!nid || !jack)
|
||||
return NULL;
|
||||
for (i = 0; i < codec->jacktbl.used; i++, jack++)
|
||||
if (jack->nid == nid && jack->dev_id == dev_id)
|
||||
return jack;
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_mst);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_tbl_get_from_tag - query the jack-table entry for the given tag
|
||||
* @codec: the HDA codec
|
||||
* @tag: tag value to refer to
|
||||
* @dev_id: pin device entry id
|
||||
*/
|
||||
struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec,
|
||||
unsigned char tag, int dev_id)
|
||||
{
|
||||
struct hda_jack_tbl *jack = codec->jacktbl.list;
|
||||
int i;
|
||||
|
||||
if (!tag || !jack)
|
||||
return NULL;
|
||||
for (i = 0; i < codec->jacktbl.used; i++, jack++)
|
||||
if (jack->tag == tag && jack->dev_id == dev_id)
|
||||
return jack;
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag);
|
||||
|
||||
static struct hda_jack_tbl *
|
||||
any_jack_tbl_get_from_nid(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
struct hda_jack_tbl *jack = codec->jacktbl.list;
|
||||
int i;
|
||||
|
|
@ -79,27 +117,6 @@ snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid)
|
|||
return jack;
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_tbl_get_from_tag - query the jack-table entry for the given tag
|
||||
* @codec: the HDA codec
|
||||
* @tag: tag value to refer to
|
||||
*/
|
||||
struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag)
|
||||
{
|
||||
struct hda_jack_tbl *jack = codec->jacktbl.list;
|
||||
int i;
|
||||
|
||||
if (!tag || !jack)
|
||||
return NULL;
|
||||
for (i = 0; i < codec->jacktbl.used; i++, jack++)
|
||||
if (jack->tag == tag)
|
||||
return jack;
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_tbl_new - create a jack-table entry for the given NID
|
||||
|
|
@ -107,17 +124,36 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag);
|
|||
* @nid: pin NID to assign
|
||||
*/
|
||||
static struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid)
|
||||
snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id)
|
||||
{
|
||||
struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid);
|
||||
struct hda_jack_tbl *jack =
|
||||
snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
|
||||
struct hda_jack_tbl *existing_nid_jack =
|
||||
any_jack_tbl_get_from_nid(codec, nid);
|
||||
|
||||
WARN_ON(dev_id != 0 && !codec->dp_mst);
|
||||
|
||||
if (jack)
|
||||
return jack;
|
||||
jack = snd_array_new(&codec->jacktbl);
|
||||
if (!jack)
|
||||
return NULL;
|
||||
jack->nid = nid;
|
||||
jack->dev_id = dev_id;
|
||||
jack->jack_dirty = 1;
|
||||
jack->tag = codec->jacktbl.used;
|
||||
if (existing_nid_jack) {
|
||||
jack->tag = existing_nid_jack->tag;
|
||||
|
||||
/*
|
||||
* Copy jack_detect from existing_nid_jack to avoid
|
||||
* snd_hda_jack_detect_enable_callback_mst() making multiple
|
||||
* SET_UNSOLICITED_ENABLE calls on the same pin.
|
||||
*/
|
||||
jack->jack_detect = existing_nid_jack->jack_detect;
|
||||
} else {
|
||||
jack->tag = codec->jacktbl.used;
|
||||
}
|
||||
|
||||
return jack;
|
||||
}
|
||||
|
||||
|
|
@ -153,10 +189,12 @@ static void jack_detect_update(struct hda_codec *codec,
|
|||
if (jack->phantom_jack)
|
||||
jack->pin_sense = AC_PINSENSE_PRESENCE;
|
||||
else
|
||||
jack->pin_sense = read_pin_sense(codec, jack->nid);
|
||||
jack->pin_sense = read_pin_sense(codec, jack->nid,
|
||||
jack->dev_id);
|
||||
|
||||
/* A gating jack indicates the jack is invalid if gating is unplugged */
|
||||
if (jack->gating_jack && !snd_hda_jack_detect(codec, jack->gating_jack))
|
||||
if (jack->gating_jack &&
|
||||
!snd_hda_jack_detect_mst(codec, jack->gating_jack, jack->dev_id))
|
||||
jack->pin_sense &= ~AC_PINSENSE_PRESENCE;
|
||||
|
||||
jack->jack_dirty = 0;
|
||||
|
|
@ -164,7 +202,8 @@ static void jack_detect_update(struct hda_codec *codec,
|
|||
/* If a jack is gated by this one update it. */
|
||||
if (jack->gated_jack) {
|
||||
struct hda_jack_tbl *gated =
|
||||
snd_hda_jack_tbl_get(codec, jack->gated_jack);
|
||||
snd_hda_jack_tbl_get_mst(codec, jack->gated_jack,
|
||||
jack->dev_id);
|
||||
if (gated) {
|
||||
gated->jack_dirty = 1;
|
||||
jack_detect_update(codec, gated);
|
||||
|
|
@ -191,63 +230,69 @@ void snd_hda_jack_set_dirty_all(struct hda_codec *codec)
|
|||
EXPORT_SYMBOL_GPL(snd_hda_jack_set_dirty_all);
|
||||
|
||||
/**
|
||||
* snd_hda_pin_sense - execute pin sense measurement
|
||||
* snd_hda_jack_pin_sense - execute pin sense measurement
|
||||
* @codec: the CODEC to sense
|
||||
* @nid: the pin NID to sense
|
||||
*
|
||||
* Execute necessary pin sense measurement and return its Presence Detect,
|
||||
* Impedance, ELD Valid etc. status bits.
|
||||
*/
|
||||
u32 snd_hda_pin_sense(struct hda_codec *codec, hda_nid_t nid)
|
||||
u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id)
|
||||
{
|
||||
struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid);
|
||||
struct hda_jack_tbl *jack =
|
||||
snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
|
||||
if (jack) {
|
||||
jack_detect_update(codec, jack);
|
||||
return jack->pin_sense;
|
||||
}
|
||||
return read_pin_sense(codec, nid);
|
||||
return read_pin_sense(codec, nid, dev_id);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_pin_sense);
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_pin_sense);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_detect_state - query pin Presence Detect status
|
||||
* snd_hda_jack_detect_state_mst - query pin Presence Detect status
|
||||
* @codec: the CODEC to sense
|
||||
* @nid: the pin NID to sense
|
||||
* @dev_id: pin device entry id
|
||||
*
|
||||
* Query and return the pin's Presence Detect status, as either
|
||||
* HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM.
|
||||
*/
|
||||
int snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid)
|
||||
int snd_hda_jack_detect_state_mst(struct hda_codec *codec,
|
||||
hda_nid_t nid, int dev_id)
|
||||
{
|
||||
struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid);
|
||||
struct hda_jack_tbl *jack =
|
||||
snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
|
||||
if (jack && jack->phantom_jack)
|
||||
return HDA_JACK_PHANTOM;
|
||||
else if (snd_hda_pin_sense(codec, nid) & AC_PINSENSE_PRESENCE)
|
||||
else if (snd_hda_jack_pin_sense(codec, nid, dev_id) &
|
||||
AC_PINSENSE_PRESENCE)
|
||||
return HDA_JACK_PRESENT;
|
||||
else
|
||||
return HDA_JACK_NOT_PRESENT;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_detect_state);
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_detect_state_mst);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_detect_enable - enable the jack-detection
|
||||
* snd_hda_jack_detect_enable_mst - enable the jack-detection
|
||||
* @codec: the HDA codec
|
||||
* @nid: pin NID to enable
|
||||
* @func: callback function to register
|
||||
* @dev_id: pin device entry id
|
||||
*
|
||||
* In the case of error, the return value will be a pointer embedded with
|
||||
* errno. Check and handle the return value appropriately with standard
|
||||
* macros such as @IS_ERR() and @PTR_ERR().
|
||||
*/
|
||||
struct hda_jack_callback *
|
||||
snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid,
|
||||
hda_jack_callback_fn func)
|
||||
snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid,
|
||||
int dev_id, hda_jack_callback_fn func)
|
||||
{
|
||||
struct hda_jack_tbl *jack;
|
||||
struct hda_jack_callback *callback = NULL;
|
||||
int err;
|
||||
|
||||
jack = snd_hda_jack_tbl_new(codec, nid);
|
||||
jack = snd_hda_jack_tbl_new(codec, nid, dev_id);
|
||||
if (!jack)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
if (func) {
|
||||
|
|
@ -256,6 +301,7 @@ snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid,
|
|||
return ERR_PTR(-ENOMEM);
|
||||
callback->func = func;
|
||||
callback->nid = jack->nid;
|
||||
callback->dev_id = jack->dev_id;
|
||||
callback->next = jack->callback;
|
||||
jack->callback = callback;
|
||||
}
|
||||
|
|
@ -272,19 +318,24 @@ snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid,
|
|||
return ERR_PTR(err);
|
||||
return callback;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable_callback);
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable_callback_mst);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_detect_enable - Enable the jack detection on the given pin
|
||||
* @codec: the HDA codec
|
||||
* @nid: pin NID to enable jack detection
|
||||
* @dev_id: pin device entry id
|
||||
*
|
||||
* Enable the jack detection with the default callback. Returns zero if
|
||||
* successful or a negative error code.
|
||||
*/
|
||||
int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid)
|
||||
int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid,
|
||||
int dev_id)
|
||||
{
|
||||
return PTR_ERR_OR_ZERO(snd_hda_jack_detect_enable_callback(codec, nid, NULL));
|
||||
return PTR_ERR_OR_ZERO(snd_hda_jack_detect_enable_callback_mst(codec,
|
||||
nid,
|
||||
dev_id,
|
||||
NULL));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable);
|
||||
|
||||
|
|
@ -299,8 +350,11 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable);
|
|||
int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
|
||||
hda_nid_t gating_nid)
|
||||
{
|
||||
struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid);
|
||||
struct hda_jack_tbl *gating = snd_hda_jack_tbl_new(codec, gating_nid);
|
||||
struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid, 0);
|
||||
struct hda_jack_tbl *gating =
|
||||
snd_hda_jack_tbl_new(codec, gating_nid, 0);
|
||||
|
||||
WARN_ON(codec->dp_mst);
|
||||
|
||||
if (!gated || !gating)
|
||||
return -EINVAL;
|
||||
|
|
@ -376,9 +430,10 @@ static void hda_free_jack_priv(struct snd_jack *jack)
|
|||
}
|
||||
|
||||
/**
|
||||
* snd_hda_jack_add_kctl - Add a kctl for the given pin
|
||||
* snd_hda_jack_add_kctl_mst - Add a kctl for the given pin
|
||||
* @codec: the HDA codec
|
||||
* @nid: pin NID to assign
|
||||
* @dev_id : pin device entry id
|
||||
* @name: string name for the jack
|
||||
* @phantom_jack: flag to deal as a phantom jack
|
||||
* @type: jack type bits to be reported, 0 for guessing from pincfg
|
||||
|
|
@ -387,15 +442,15 @@ static void hda_free_jack_priv(struct snd_jack *jack)
|
|||
* This assigns a jack-detection kctl to the given pin. The kcontrol
|
||||
* will have the given name and index.
|
||||
*/
|
||||
int snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
|
||||
const char *name, bool phantom_jack,
|
||||
int type, const struct hda_jack_keymap *keymap)
|
||||
int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid,
|
||||
int dev_id, const char *name, bool phantom_jack,
|
||||
int type, const struct hda_jack_keymap *keymap)
|
||||
{
|
||||
struct hda_jack_tbl *jack;
|
||||
const struct hda_jack_keymap *map;
|
||||
int err, state, buttons;
|
||||
|
||||
jack = snd_hda_jack_tbl_new(codec, nid);
|
||||
jack = snd_hda_jack_tbl_new(codec, nid, dev_id);
|
||||
if (!jack)
|
||||
return 0;
|
||||
if (jack->jack)
|
||||
|
|
@ -425,12 +480,12 @@ int snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
|
|||
snd_jack_set_key(jack->jack, map->type, map->key);
|
||||
}
|
||||
|
||||
state = snd_hda_jack_detect(codec, nid);
|
||||
state = snd_hda_jack_detect_mst(codec, nid, dev_id);
|
||||
snd_jack_report(jack->jack, state ? jack->type : 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctl);
|
||||
EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctl_mst);
|
||||
|
||||
static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid,
|
||||
const struct auto_pin_cfg *cfg,
|
||||
|
|
@ -441,6 +496,8 @@ static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid,
|
|||
int err;
|
||||
bool phantom_jack;
|
||||
|
||||
WARN_ON(codec->dp_mst);
|
||||
|
||||
if (!nid)
|
||||
return 0;
|
||||
def_conf = snd_hda_codec_get_pincfg(codec, nid);
|
||||
|
|
@ -462,7 +519,7 @@ static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid,
|
|||
return err;
|
||||
|
||||
if (!phantom_jack)
|
||||
return snd_hda_jack_detect_enable(codec, nid);
|
||||
return snd_hda_jack_detect_enable(codec, nid, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -540,7 +597,8 @@ static void call_jack_callback(struct hda_codec *codec, unsigned int res,
|
|||
}
|
||||
if (jack->gated_jack) {
|
||||
struct hda_jack_tbl *gated =
|
||||
snd_hda_jack_tbl_get(codec, jack->gated_jack);
|
||||
snd_hda_jack_tbl_get_mst(codec, jack->gated_jack,
|
||||
jack->dev_id);
|
||||
if (gated) {
|
||||
for (cb = gated->callback; cb; cb = cb->next) {
|
||||
cb->jack = gated;
|
||||
|
|
@ -561,7 +619,14 @@ void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res)
|
|||
struct hda_jack_tbl *event;
|
||||
int tag = (res & AC_UNSOL_RES_TAG) >> AC_UNSOL_RES_TAG_SHIFT;
|
||||
|
||||
event = snd_hda_jack_tbl_get_from_tag(codec, tag);
|
||||
if (codec->dp_mst) {
|
||||
int dev_entry =
|
||||
(res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT;
|
||||
|
||||
event = snd_hda_jack_tbl_get_from_tag(codec, tag, dev_entry);
|
||||
} else {
|
||||
event = snd_hda_jack_tbl_get_from_tag(codec, tag, 0);
|
||||
}
|
||||
if (!event)
|
||||
return;
|
||||
event->jack_dirty = 1;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ typedef void (*hda_jack_callback_fn) (struct hda_codec *, struct hda_jack_callba
|
|||
|
||||
struct hda_jack_callback {
|
||||
hda_nid_t nid;
|
||||
int dev_id;
|
||||
hda_jack_callback_fn func;
|
||||
unsigned int private_data; /* arbitrary data */
|
||||
unsigned int unsol_res; /* unsolicited event bits */
|
||||
|
|
@ -28,6 +29,7 @@ struct hda_jack_callback {
|
|||
|
||||
struct hda_jack_tbl {
|
||||
hda_nid_t nid;
|
||||
int dev_id;
|
||||
unsigned char tag; /* unsol event tag */
|
||||
struct hda_jack_callback *callback;
|
||||
/* jack-detection stuff */
|
||||
|
|
@ -49,46 +51,129 @@ struct hda_jack_keymap {
|
|||
};
|
||||
|
||||
struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid);
|
||||
snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_tbl_get - query the jack-table entry for the given NID
|
||||
* @codec: the HDA codec
|
||||
* @nid: pin NID to refer to
|
||||
*/
|
||||
static inline struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
return snd_hda_jack_tbl_get_mst(codec, nid, 0);
|
||||
}
|
||||
|
||||
struct hda_jack_tbl *
|
||||
snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag);
|
||||
snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec,
|
||||
unsigned char tag, int dev_id);
|
||||
|
||||
void snd_hda_jack_tbl_clear(struct hda_codec *codec);
|
||||
|
||||
void snd_hda_jack_set_dirty_all(struct hda_codec *codec);
|
||||
|
||||
int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid);
|
||||
int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid,
|
||||
int dev_id);
|
||||
|
||||
struct hda_jack_callback *
|
||||
snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid,
|
||||
int dev_id, hda_jack_callback_fn cb);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_detect_enable - enable the jack-detection
|
||||
* @codec: the HDA codec
|
||||
* @nid: pin NID to enable
|
||||
* @func: callback function to register
|
||||
*
|
||||
* In the case of error, the return value will be a pointer embedded with
|
||||
* errno. Check and handle the return value appropriately with standard
|
||||
* macros such as @IS_ERR() and @PTR_ERR().
|
||||
*/
|
||||
static inline struct hda_jack_callback *
|
||||
snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid,
|
||||
hda_jack_callback_fn cb);
|
||||
hda_jack_callback_fn cb)
|
||||
{
|
||||
return snd_hda_jack_detect_enable_callback_mst(codec, nid, 0, cb);
|
||||
}
|
||||
|
||||
int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
|
||||
hda_nid_t gating_nid);
|
||||
|
||||
u32 snd_hda_pin_sense(struct hda_codec *codec, hda_nid_t nid);
|
||||
u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id);
|
||||
|
||||
/* the jack state returned from snd_hda_jack_detect_state() */
|
||||
enum {
|
||||
HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT, HDA_JACK_PHANTOM,
|
||||
};
|
||||
|
||||
int snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid);
|
||||
int snd_hda_jack_detect_state_mst(struct hda_codec *codec, hda_nid_t nid,
|
||||
int dev_id);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_detect_state - query pin Presence Detect status
|
||||
* @codec: the CODEC to sense
|
||||
* @nid: the pin NID to sense
|
||||
*
|
||||
* Query and return the pin's Presence Detect status, as either
|
||||
* HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM.
|
||||
*/
|
||||
static inline int
|
||||
snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
return snd_hda_jack_detect_state_mst(codec, nid, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_jack_detect_mst - Detect the jack
|
||||
* @codec: the HDA codec
|
||||
* @nid: pin NID to check jack detection
|
||||
* @dev_id: pin device entry id
|
||||
*/
|
||||
static inline bool
|
||||
snd_hda_jack_detect_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id)
|
||||
{
|
||||
return snd_hda_jack_detect_state_mst(codec, nid, dev_id) !=
|
||||
HDA_JACK_NOT_PRESENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_jack_detect - Detect the jack
|
||||
* @codec: the HDA codec
|
||||
* @nid: pin NID to check jack detection
|
||||
*/
|
||||
static inline bool snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid)
|
||||
static inline bool
|
||||
snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
return snd_hda_jack_detect_state(codec, nid) != HDA_JACK_NOT_PRESENT;
|
||||
return snd_hda_jack_detect_mst(codec, nid, 0);
|
||||
}
|
||||
|
||||
bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid);
|
||||
|
||||
int snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
|
||||
const char *name, bool phantom_jack,
|
||||
int type, const struct hda_jack_keymap *keymap);
|
||||
int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid,
|
||||
int dev_id, const char *name, bool phantom_jack,
|
||||
int type, const struct hda_jack_keymap *keymap);
|
||||
|
||||
/**
|
||||
* snd_hda_jack_add_kctl - Add a kctl for the given pin
|
||||
* @codec: the HDA codec
|
||||
* @nid: pin NID to assign
|
||||
* @name: string name for the jack
|
||||
* @phantom_jack: flag to deal as a phantom jack
|
||||
* @type: jack type bits to be reported, 0 for guessing from pincfg
|
||||
* @keymap: optional jack / key mapping
|
||||
*
|
||||
* This assigns a jack-detection kctl to the given pin. The kcontrol
|
||||
* will have the given name and index.
|
||||
*/
|
||||
static inline int
|
||||
snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
|
||||
const char *name, bool phantom_jack,
|
||||
int type, const struct hda_jack_keymap *keymap)
|
||||
{
|
||||
return snd_hda_jack_add_kctl_mst(codec, nid, 0,
|
||||
name, phantom_jack, type, keymap);
|
||||
}
|
||||
|
||||
int snd_hda_jack_add_kctls(struct hda_codec *codec,
|
||||
const struct auto_pin_cfg *cfg);
|
||||
|
||||
|
|
|
|||
|
|
@ -910,6 +910,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = {
|
|||
SND_PCI_QUIRK(0x103c, 0x837f, "HP ProBook 470 G5", CXT_FIXUP_MUTE_LED_GPIO),
|
||||
SND_PCI_QUIRK(0x103c, 0x8299, "HP 800 G3 SFF", CXT_FIXUP_HP_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x103c, 0x8402, "HP ProBook 645 G4", CXT_FIXUP_MUTE_LED_GPIO),
|
||||
SND_PCI_QUIRK(0x103c, 0x8455, "HP Z2 G4", CXT_FIXUP_HP_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x103c, 0x8456, "HP Z2 G4 SFF", CXT_FIXUP_HP_MIC_NO_PRESENCE),
|
||||
SND_PCI_QUIRK(0x103c, 0x8457, "HP Z2 G4 mini", CXT_FIXUP_HP_MIC_NO_PRESENCE),
|
||||
|
|
|
|||
|
|
@ -80,16 +80,19 @@ struct hdmi_spec_per_pin {
|
|||
/* operations used by generic code that can be overridden by patches */
|
||||
struct hdmi_ops {
|
||||
int (*pin_get_eld)(struct hda_codec *codec, hda_nid_t pin_nid,
|
||||
unsigned char *buf, int *eld_size);
|
||||
int dev_id, unsigned char *buf, int *eld_size);
|
||||
|
||||
void (*pin_setup_infoframe)(struct hda_codec *codec, hda_nid_t pin_nid,
|
||||
int dev_id,
|
||||
int ca, int active_channels, int conn_type);
|
||||
|
||||
/* enable/disable HBR (HD passthrough) */
|
||||
int (*pin_hbr_setup)(struct hda_codec *codec, hda_nid_t pin_nid, bool hbr);
|
||||
int (*pin_hbr_setup)(struct hda_codec *codec, hda_nid_t pin_nid,
|
||||
int dev_id, bool hbr);
|
||||
|
||||
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, int dev_id, u32 stream_tag,
|
||||
int format);
|
||||
|
||||
void (*pin_cvt_fixup)(struct hda_codec *codec,
|
||||
struct hdmi_spec_per_pin *per_pin,
|
||||
|
|
@ -636,8 +639,16 @@ static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid,
|
|||
return true;
|
||||
}
|
||||
|
||||
static int hdmi_pin_get_eld(struct hda_codec *codec, hda_nid_t nid,
|
||||
int dev_id, unsigned char *buf, int *eld_size)
|
||||
{
|
||||
snd_hda_set_dev_select(codec, nid, dev_id);
|
||||
|
||||
return snd_hdmi_get_eld(codec, nid, buf, eld_size);
|
||||
}
|
||||
|
||||
static void hdmi_pin_setup_infoframe(struct hda_codec *codec,
|
||||
hda_nid_t pin_nid,
|
||||
hda_nid_t pin_nid, int dev_id,
|
||||
int ca, int active_channels,
|
||||
int conn_type)
|
||||
{
|
||||
|
|
@ -667,6 +678,8 @@ static void hdmi_pin_setup_infoframe(struct hda_codec *codec,
|
|||
return;
|
||||
}
|
||||
|
||||
snd_hda_set_dev_select(codec, pin_nid, dev_id);
|
||||
|
||||
/*
|
||||
* sizeof(ai) is used instead of sizeof(*hdmi_ai) or
|
||||
* sizeof(*dp_ai) to avoid partial match/update problems when
|
||||
|
|
@ -692,6 +705,7 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
|
|||
struct hdmi_spec *spec = codec->spec;
|
||||
struct hdac_chmap *chmap = &spec->chmap;
|
||||
hda_nid_t pin_nid = per_pin->pin_nid;
|
||||
int dev_id = per_pin->dev_id;
|
||||
int channels = per_pin->channels;
|
||||
int active_channels;
|
||||
struct hdmi_eld *eld;
|
||||
|
|
@ -700,6 +714,8 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
|
|||
if (!channels)
|
||||
return;
|
||||
|
||||
snd_hda_set_dev_select(codec, pin_nid, dev_id);
|
||||
|
||||
/* 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,
|
||||
|
|
@ -725,8 +741,8 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
|
|||
pin_nid, non_pcm, ca, channels,
|
||||
per_pin->chmap, per_pin->chmap_set);
|
||||
|
||||
spec->ops.pin_setup_infoframe(codec, pin_nid, ca, active_channels,
|
||||
eld->info.conn_type);
|
||||
spec->ops.pin_setup_infoframe(codec, pin_nid, dev_id,
|
||||
ca, active_channels, eld->info.conn_type);
|
||||
|
||||
per_pin->non_pcm = non_pcm;
|
||||
}
|
||||
|
|
@ -758,34 +774,32 @@ static void jack_callback(struct hda_codec *codec,
|
|||
if (codec_has_acomp(codec))
|
||||
return;
|
||||
|
||||
/* hda_jack don't support DP MST */
|
||||
check_presence_and_report(codec, jack->nid, 0);
|
||||
check_presence_and_report(codec, jack->nid, jack->dev_id);
|
||||
}
|
||||
|
||||
static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res)
|
||||
{
|
||||
int tag = res >> AC_UNSOL_RES_TAG_SHIFT;
|
||||
struct hda_jack_tbl *jack;
|
||||
int dev_entry = (res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT;
|
||||
|
||||
/*
|
||||
* assume DP MST uses dyn_pcm_assign and acomp and
|
||||
* never comes here
|
||||
* if DP MST supports unsol event, below code need
|
||||
* consider dev_entry
|
||||
*/
|
||||
jack = snd_hda_jack_tbl_get_from_tag(codec, tag);
|
||||
if (codec->dp_mst) {
|
||||
int dev_entry =
|
||||
(res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT;
|
||||
|
||||
jack = snd_hda_jack_tbl_get_from_tag(codec, tag, dev_entry);
|
||||
} else {
|
||||
jack = snd_hda_jack_tbl_get_from_tag(codec, tag, 0);
|
||||
}
|
||||
if (!jack)
|
||||
return;
|
||||
jack->jack_dirty = 1;
|
||||
|
||||
codec_dbg(codec,
|
||||
"HDMI hot plug event: Codec=%d Pin=%d Device=%d Inactive=%d Presence_Detect=%d ELD_Valid=%d\n",
|
||||
codec->addr, jack->nid, dev_entry, !!(res & AC_UNSOL_RES_IA),
|
||||
codec->addr, jack->nid, jack->dev_id, !!(res & AC_UNSOL_RES_IA),
|
||||
!!(res & AC_UNSOL_RES_PD), !!(res & AC_UNSOL_RES_ELDV));
|
||||
|
||||
/* hda_jack don't support DP MST */
|
||||
check_presence_and_report(codec, jack->nid, 0);
|
||||
check_presence_and_report(codec, jack->nid, jack->dev_id);
|
||||
}
|
||||
|
||||
static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res)
|
||||
|
|
@ -815,11 +829,21 @@ static void hdmi_unsol_event(struct hda_codec *codec, unsigned int res)
|
|||
{
|
||||
int tag = res >> AC_UNSOL_RES_TAG_SHIFT;
|
||||
int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT;
|
||||
struct hda_jack_tbl *jack;
|
||||
|
||||
if (codec_has_acomp(codec))
|
||||
return;
|
||||
|
||||
if (!snd_hda_jack_tbl_get_from_tag(codec, tag)) {
|
||||
if (codec->dp_mst) {
|
||||
int dev_entry =
|
||||
(res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT;
|
||||
|
||||
jack = snd_hda_jack_tbl_get_from_tag(codec, tag, dev_entry);
|
||||
} else {
|
||||
jack = snd_hda_jack_tbl_get_from_tag(codec, tag, 0);
|
||||
}
|
||||
|
||||
if (!jack) {
|
||||
codec_dbg(codec, "Unexpected HDMI event tag 0x%x\n", tag);
|
||||
return;
|
||||
}
|
||||
|
|
@ -860,11 +884,12 @@ static void haswell_verify_D0(struct hda_codec *codec,
|
|||
((format & AC_FMT_TYPE_NON_PCM) && (format & AC_FMT_CHAN_MASK) == 7)
|
||||
|
||||
static int hdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid,
|
||||
bool hbr)
|
||||
int dev_id, bool hbr)
|
||||
{
|
||||
int pinctl, new_pinctl;
|
||||
|
||||
if (snd_hda_query_pin_caps(codec, pin_nid) & AC_PINCAP_HBR) {
|
||||
snd_hda_set_dev_select(codec, pin_nid, dev_id);
|
||||
pinctl = snd_hda_codec_read(codec, pin_nid, 0,
|
||||
AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
|
||||
|
||||
|
|
@ -894,13 +919,15 @@ static int hdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid,
|
|||
}
|
||||
|
||||
static int hdmi_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, int dev_id,
|
||||
u32 stream_tag, int format)
|
||||
{
|
||||
struct hdmi_spec *spec = codec->spec;
|
||||
unsigned int param;
|
||||
int err;
|
||||
|
||||
err = spec->ops.pin_hbr_setup(codec, pin_nid, is_hbr_format(format));
|
||||
err = spec->ops.pin_hbr_setup(codec, pin_nid, dev_id,
|
||||
is_hbr_format(format));
|
||||
|
||||
if (err) {
|
||||
codec_dbg(codec, "hdmi_setup_stream: HBR is not supported\n");
|
||||
|
|
@ -1274,6 +1301,7 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
|
|||
struct hdmi_spec *spec = codec->spec;
|
||||
struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
|
||||
hda_nid_t pin_nid = per_pin->pin_nid;
|
||||
int dev_id = per_pin->dev_id;
|
||||
|
||||
if (!(get_wcaps(codec, pin_nid) & AC_WCAP_CONN_LIST)) {
|
||||
codec_warn(codec,
|
||||
|
|
@ -1282,24 +1310,43 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_hda_set_dev_select(codec, pin_nid, dev_id);
|
||||
|
||||
/* all the device entries on the same pin have the same conn list */
|
||||
per_pin->num_mux_nids = snd_hda_get_connections(codec, pin_nid,
|
||||
per_pin->mux_nids,
|
||||
HDA_MAX_CONNECTIONS);
|
||||
per_pin->num_mux_nids =
|
||||
snd_hda_get_raw_connections(codec, pin_nid, per_pin->mux_nids,
|
||||
HDA_MAX_CONNECTIONS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hdmi_find_pcm_slot(struct hdmi_spec *spec,
|
||||
struct hdmi_spec_per_pin *per_pin)
|
||||
struct hdmi_spec_per_pin *per_pin)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* try the prefer PCM */
|
||||
if (!test_bit(per_pin->pin_nid_idx, &spec->pcm_bitmap))
|
||||
/*
|
||||
* generic_hdmi_build_pcms() allocates (num_nids + dev_num - 1)
|
||||
* number of pcms.
|
||||
*
|
||||
* The per_pin of pin_nid_idx=n and dev_id=m prefers to get pcm-n
|
||||
* if m==0. This guarantees that dynamic pcm assignments are compatible
|
||||
* with the legacy static per_pin-pmc assignment that existed in the
|
||||
* days before DP-MST.
|
||||
*
|
||||
* per_pin of m!=0 prefers to get pcm=(num_nids + (m - 1)).
|
||||
*/
|
||||
if (per_pin->dev_id == 0 &&
|
||||
!test_bit(per_pin->pin_nid_idx, &spec->pcm_bitmap))
|
||||
return per_pin->pin_nid_idx;
|
||||
|
||||
/* have a second try; check the "reserved area" over num_pins */
|
||||
if (per_pin->dev_id != 0 &&
|
||||
!(test_bit(spec->num_nids + (per_pin->dev_id - 1),
|
||||
&spec->pcm_bitmap))) {
|
||||
return spec->num_nids + (per_pin->dev_id - 1);
|
||||
}
|
||||
|
||||
/* have a second try; check the area over num_nids */
|
||||
for (i = spec->num_nids; i < spec->pcm_used; i++) {
|
||||
if (!test_bit(i, &spec->pcm_bitmap))
|
||||
return i;
|
||||
|
|
@ -1493,6 +1540,7 @@ static bool hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin,
|
|||
struct hdmi_spec *spec = codec->spec;
|
||||
struct hdmi_eld *eld = &spec->temp_eld;
|
||||
hda_nid_t pin_nid = per_pin->pin_nid;
|
||||
int dev_id = per_pin->dev_id;
|
||||
/*
|
||||
* Always execute a GetPinSense verb here, even when called from
|
||||
* hdmi_intrinsic_event; for some NVIDIA HW, the unsolicited
|
||||
|
|
@ -1505,7 +1553,7 @@ static bool hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin,
|
|||
bool ret;
|
||||
bool do_repoll = false;
|
||||
|
||||
present = snd_hda_pin_sense(codec, pin_nid);
|
||||
present = snd_hda_jack_pin_sense(codec, pin_nid, dev_id);
|
||||
|
||||
mutex_lock(&per_pin->lock);
|
||||
eld->monitor_present = !!(present & AC_PINSENSE_PRESENCE);
|
||||
|
|
@ -1519,8 +1567,8 @@ static bool hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin,
|
|||
codec->addr, pin_nid, eld->monitor_present, eld->eld_valid);
|
||||
|
||||
if (eld->eld_valid) {
|
||||
if (spec->ops.pin_get_eld(codec, pin_nid, eld->eld_buffer,
|
||||
&eld->eld_size) < 0)
|
||||
if (spec->ops.pin_get_eld(codec, pin_nid, dev_id,
|
||||
eld->eld_buffer, &eld->eld_size) < 0)
|
||||
eld->eld_valid = false;
|
||||
else {
|
||||
if (snd_hdmi_parse_eld(codec, &eld->info, eld->eld_buffer,
|
||||
|
|
@ -1538,7 +1586,7 @@ static bool hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin,
|
|||
|
||||
ret = !repoll || !eld->monitor_present || eld->eld_valid;
|
||||
|
||||
jack = snd_hda_jack_tbl_get(codec, pin_nid);
|
||||
jack = snd_hda_jack_tbl_get_mst(codec, pin_nid, per_pin->dev_id);
|
||||
if (jack) {
|
||||
jack->block_report = !ret;
|
||||
jack->pin_sense = (eld->monitor_present && eld->eld_valid) ?
|
||||
|
|
@ -1569,7 +1617,8 @@ static struct snd_jack *pin_idx_to_jack(struct hda_codec *codec,
|
|||
* DP MST will use dyn_pcm_assign,
|
||||
* so DP MST will never come here
|
||||
*/
|
||||
jack_tbl = snd_hda_jack_tbl_get(codec, per_pin->pin_nid);
|
||||
jack_tbl = snd_hda_jack_tbl_get_mst(codec, per_pin->pin_nid,
|
||||
per_pin->dev_id);
|
||||
if (jack_tbl)
|
||||
jack = jack_tbl->jack;
|
||||
}
|
||||
|
|
@ -1650,7 +1699,8 @@ static void hdmi_repoll_eld(struct work_struct *work)
|
|||
struct hdmi_spec *spec = codec->spec;
|
||||
struct hda_jack_tbl *jack;
|
||||
|
||||
jack = snd_hda_jack_tbl_get(codec, per_pin->pin_nid);
|
||||
jack = snd_hda_jack_tbl_get_mst(codec, per_pin->pin_nid,
|
||||
per_pin->dev_id);
|
||||
if (jack)
|
||||
jack->jack_dirty = 1;
|
||||
|
||||
|
|
@ -1855,7 +1905,6 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|||
struct hdmi_spec *spec = codec->spec;
|
||||
int pin_idx;
|
||||
struct hdmi_spec_per_pin *per_pin;
|
||||
hda_nid_t pin_nid;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
bool non_pcm;
|
||||
int pinctl, stripe;
|
||||
|
|
@ -1879,7 +1928,6 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|||
goto unlock;
|
||||
}
|
||||
per_pin = get_pin(spec, pin_idx);
|
||||
pin_nid = per_pin->pin_nid;
|
||||
|
||||
/* Verify pin:cvt selections to avoid silent audio after S3.
|
||||
* After S3, the audio driver restores pin:cvt selections
|
||||
|
|
@ -1894,8 +1942,8 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|||
/* Call sync_audio_rate to set the N/CTS/M manually if necessary */
|
||||
/* Todo: add DP1.2 MST audio support later */
|
||||
if (codec_has_acomp(codec))
|
||||
snd_hdac_sync_audio_rate(&codec->core, pin_nid, per_pin->dev_id,
|
||||
runtime->rate);
|
||||
snd_hdac_sync_audio_rate(&codec->core, per_pin->pin_nid,
|
||||
per_pin->dev_id, runtime->rate);
|
||||
|
||||
non_pcm = check_non_pcm_per_cvt(codec, cvt_nid);
|
||||
mutex_lock(&per_pin->lock);
|
||||
|
|
@ -1913,16 +1961,18 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
|
|||
hdmi_setup_audio_infoframe(codec, per_pin, non_pcm);
|
||||
mutex_unlock(&per_pin->lock);
|
||||
if (spec->dyn_pin_out) {
|
||||
pinctl = snd_hda_codec_read(codec, pin_nid, 0,
|
||||
snd_hda_set_dev_select(codec, per_pin->pin_nid,
|
||||
per_pin->dev_id);
|
||||
pinctl = snd_hda_codec_read(codec, per_pin->pin_nid, 0,
|
||||
AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
|
||||
snd_hda_codec_write(codec, pin_nid, 0,
|
||||
snd_hda_codec_write(codec, per_pin->pin_nid, 0,
|
||||
AC_VERB_SET_PIN_WIDGET_CONTROL,
|
||||
pinctl | PIN_OUT);
|
||||
}
|
||||
|
||||
/* snd_hda_set_dev_select() has been called before */
|
||||
err = spec->ops.setup_stream(codec, cvt_nid, pin_nid,
|
||||
stream_tag, format);
|
||||
err = spec->ops.setup_stream(codec, cvt_nid, per_pin->pin_nid,
|
||||
per_pin->dev_id, stream_tag, format);
|
||||
unlock:
|
||||
mutex_unlock(&spec->pcm_lock);
|
||||
return err;
|
||||
|
|
@ -1974,6 +2024,8 @@ static int hdmi_pcm_close(struct hda_pcm_stream *hinfo,
|
|||
per_pin = get_pin(spec, pin_idx);
|
||||
|
||||
if (spec->dyn_pin_out) {
|
||||
snd_hda_set_dev_select(codec, per_pin->pin_nid,
|
||||
per_pin->dev_id);
|
||||
pinctl = snd_hda_codec_read(codec, per_pin->pin_nid, 0,
|
||||
AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
|
||||
snd_hda_codec_write(codec, per_pin->pin_nid, 0,
|
||||
|
|
@ -2151,11 +2203,13 @@ static int generic_hdmi_build_jack(struct hda_codec *codec, int pcm_idx)
|
|||
if (phantom_jack)
|
||||
strncat(hdmi_str, " Phantom",
|
||||
sizeof(hdmi_str) - strlen(hdmi_str) - 1);
|
||||
ret = snd_hda_jack_add_kctl(codec, per_pin->pin_nid, hdmi_str,
|
||||
phantom_jack, 0, NULL);
|
||||
ret = snd_hda_jack_add_kctl_mst(codec, per_pin->pin_nid,
|
||||
per_pin->dev_id, hdmi_str, phantom_jack,
|
||||
0, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
jack = snd_hda_jack_tbl_get(codec, per_pin->pin_nid);
|
||||
jack = snd_hda_jack_tbl_get_mst(codec, per_pin->pin_nid,
|
||||
per_pin->dev_id);
|
||||
if (jack == NULL)
|
||||
return 0;
|
||||
/* assign jack->jack to pcm_rec[].jack to
|
||||
|
|
@ -2264,10 +2318,11 @@ static int generic_hdmi_init(struct hda_codec *codec)
|
|||
if (codec_has_acomp(codec))
|
||||
continue;
|
||||
if (spec->use_jack_detect)
|
||||
snd_hda_jack_detect_enable(codec, pin_nid);
|
||||
snd_hda_jack_detect_enable(codec, pin_nid, dev_id);
|
||||
else
|
||||
snd_hda_jack_detect_enable_callback(codec, pin_nid,
|
||||
jack_callback);
|
||||
snd_hda_jack_detect_enable_callback_mst(codec, pin_nid,
|
||||
dev_id,
|
||||
jack_callback);
|
||||
}
|
||||
mutex_unlock(&spec->bind_lock);
|
||||
return 0;
|
||||
|
|
@ -2357,7 +2412,7 @@ static const struct hda_codec_ops generic_hdmi_patch_ops = {
|
|||
};
|
||||
|
||||
static const struct hdmi_ops generic_standard_hdmi_ops = {
|
||||
.pin_get_eld = snd_hdmi_get_eld,
|
||||
.pin_get_eld = hdmi_pin_get_eld,
|
||||
.pin_setup_infoframe = hdmi_pin_setup_infoframe,
|
||||
.pin_hbr_setup = hdmi_pin_hbr_setup,
|
||||
.setup_stream = hdmi_setup_stream,
|
||||
|
|
@ -2417,11 +2472,11 @@ static int patch_generic_hdmi(struct hda_codec *codec)
|
|||
|
||||
/* turn on / off the unsol event jack detection dynamically */
|
||||
static void reprogram_jack_detect(struct hda_codec *codec, hda_nid_t nid,
|
||||
bool use_acomp)
|
||||
int dev_id, bool use_acomp)
|
||||
{
|
||||
struct hda_jack_tbl *tbl;
|
||||
|
||||
tbl = snd_hda_jack_tbl_get(codec, nid);
|
||||
tbl = snd_hda_jack_tbl_get_mst(codec, nid, dev_id);
|
||||
if (tbl) {
|
||||
/* clear unsol even if component notifier is used, or re-enable
|
||||
* if notifier is cleared
|
||||
|
|
@ -2434,7 +2489,7 @@ static void reprogram_jack_detect(struct hda_codec *codec, hda_nid_t nid,
|
|||
* at need (i.e. only when notifier is cleared)
|
||||
*/
|
||||
if (!use_acomp)
|
||||
snd_hda_jack_detect_enable(codec, nid);
|
||||
snd_hda_jack_detect_enable(codec, nid, dev_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2454,6 +2509,7 @@ static void generic_acomp_notifier_set(struct drm_audio_component *acomp,
|
|||
for (i = 0; i < spec->num_pins; i++)
|
||||
reprogram_jack_detect(spec->codec,
|
||||
get_pin(spec, i)->pin_nid,
|
||||
get_pin(spec, i)->dev_id,
|
||||
use_acomp);
|
||||
}
|
||||
mutex_unlock(&spec->bind_lock);
|
||||
|
|
@ -2554,7 +2610,8 @@ static void intel_haswell_fixup_connect_list(struct hda_codec *codec,
|
|||
hda_nid_t conns[4];
|
||||
int nconns;
|
||||
|
||||
nconns = snd_hda_get_connections(codec, nid, conns, ARRAY_SIZE(conns));
|
||||
nconns = snd_hda_get_raw_connections(codec, nid, conns,
|
||||
ARRAY_SIZE(conns));
|
||||
if (nconns == spec->num_cvts &&
|
||||
!memcmp(conns, spec->cvt_nids, spec->num_cvts * sizeof(hda_nid_t)))
|
||||
return;
|
||||
|
|
@ -2730,10 +2787,12 @@ static void register_i915_notifier(struct hda_codec *codec)
|
|||
|
||||
/* 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)
|
||||
hda_nid_t pin_nid, int dev_id, 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);
|
||||
return hdmi_setup_stream(codec, cvt_nid, pin_nid, dev_id,
|
||||
stream_tag, format);
|
||||
}
|
||||
|
||||
/* pin_cvt_fixup ops override for HSW+ and VLV+ */
|
||||
|
|
@ -2959,7 +3018,7 @@ static int simple_playback_init(struct hda_codec *codec)
|
|||
if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP)
|
||||
snd_hda_codec_write(codec, pin, 0, AC_VERB_SET_AMP_GAIN_MUTE,
|
||||
AMP_OUT_UNMUTE);
|
||||
snd_hda_jack_detect_enable(codec, pin);
|
||||
snd_hda_jack_detect_enable(codec, pin, per_pin->dev_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -3468,6 +3527,40 @@ static int patch_nvhdmi(struct hda_codec *codec)
|
|||
struct hdmi_spec *spec;
|
||||
int err;
|
||||
|
||||
err = alloc_generic_hdmi(codec);
|
||||
if (err < 0)
|
||||
return err;
|
||||
codec->dp_mst = true;
|
||||
|
||||
spec = codec->spec;
|
||||
spec->dyn_pcm_assign = true;
|
||||
|
||||
err = hdmi_parse_codec(codec);
|
||||
if (err < 0) {
|
||||
generic_spec_free(codec);
|
||||
return err;
|
||||
}
|
||||
|
||||
generic_hdmi_init_per_pins(codec);
|
||||
|
||||
spec->dyn_pin_out = true;
|
||||
|
||||
spec->chmap.ops.chmap_cea_alloc_validate_get_type =
|
||||
nvhdmi_chmap_cea_alloc_validate_get_type;
|
||||
spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate;
|
||||
|
||||
codec->link_down_at_suspend = 1;
|
||||
|
||||
generic_acomp_init(codec, &nvhdmi_audio_ops, nvhdmi_port2pin);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int patch_nvhdmi_legacy(struct hda_codec *codec)
|
||||
{
|
||||
struct hdmi_spec *spec;
|
||||
int err;
|
||||
|
||||
err = patch_generic_hdmi(codec);
|
||||
if (err)
|
||||
return err;
|
||||
|
|
@ -3699,16 +3792,19 @@ static int patch_tegra_hdmi(struct hda_codec *codec)
|
|||
#define ATI_HBR_ENABLE 0x10
|
||||
|
||||
static int atihdmi_pin_get_eld(struct hda_codec *codec, hda_nid_t nid,
|
||||
unsigned char *buf, int *eld_size)
|
||||
int dev_id, unsigned char *buf, int *eld_size)
|
||||
{
|
||||
WARN_ON(dev_id != 0);
|
||||
/* call hda_eld.c ATI/AMD-specific function */
|
||||
return snd_hdmi_get_eld_ati(codec, nid, buf, eld_size,
|
||||
is_amdhdmi_rev3_or_later(codec));
|
||||
}
|
||||
|
||||
static void atihdmi_pin_setup_infoframe(struct hda_codec *codec, hda_nid_t pin_nid, int ca,
|
||||
static void atihdmi_pin_setup_infoframe(struct hda_codec *codec,
|
||||
hda_nid_t pin_nid, int dev_id, int ca,
|
||||
int active_channels, int conn_type)
|
||||
{
|
||||
WARN_ON(dev_id != 0);
|
||||
snd_hda_codec_write(codec, pin_nid, 0, ATI_VERB_SET_CHANNEL_ALLOCATION, ca);
|
||||
}
|
||||
|
||||
|
|
@ -3899,10 +3995,12 @@ static void atihdmi_paired_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap,
|
|||
}
|
||||
|
||||
static int atihdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid,
|
||||
bool hbr)
|
||||
int dev_id, bool hbr)
|
||||
{
|
||||
int hbr_ctl, hbr_ctl_new;
|
||||
|
||||
WARN_ON(dev_id != 0);
|
||||
|
||||
hbr_ctl = snd_hda_codec_read(codec, pin_nid, 0, ATI_VERB_GET_HBR_CONTROL, 0);
|
||||
if (hbr_ctl >= 0 && (hbr_ctl & ATI_HBR_CAPABLE)) {
|
||||
if (hbr)
|
||||
|
|
@ -3928,9 +4026,9 @@ static int atihdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid,
|
|||
}
|
||||
|
||||
static int atihdmi_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, int dev_id,
|
||||
u32 stream_tag, int format)
|
||||
{
|
||||
|
||||
if (is_amdhdmi_rev3_or_later(codec)) {
|
||||
int ramp_rate = 180; /* default as per AMD spec */
|
||||
/* disable ramp-up/down for non-pcm as per AMD spec */
|
||||
|
|
@ -3940,7 +4038,8 @@ static int atihdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
|
|||
snd_hda_codec_write(codec, cvt_nid, 0, ATI_VERB_SET_RAMP_RATE, ramp_rate);
|
||||
}
|
||||
|
||||
return hdmi_setup_stream(codec, cvt_nid, pin_nid, stream_tag, format);
|
||||
return hdmi_setup_stream(codec, cvt_nid, pin_nid, dev_id,
|
||||
stream_tag, format);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -4070,25 +4169,25 @@ HDA_CODEC_ENTRY(0x10de0004, "GPU 04 HDMI", patch_nvhdmi_8ch_7x),
|
|||
HDA_CODEC_ENTRY(0x10de0005, "MCP77/78 HDMI", patch_nvhdmi_8ch_7x),
|
||||
HDA_CODEC_ENTRY(0x10de0006, "MCP77/78 HDMI", patch_nvhdmi_8ch_7x),
|
||||
HDA_CODEC_ENTRY(0x10de0007, "MCP79/7A HDMI", patch_nvhdmi_8ch_7x),
|
||||
HDA_CODEC_ENTRY(0x10de0008, "GPU 08 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0009, "GPU 09 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de000a, "GPU 0a HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de000b, "GPU 0b HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de000c, "MCP89 HDMI", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de000d, "GPU 0d HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0010, "GPU 10 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0011, "GPU 11 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0012, "GPU 12 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0013, "GPU 13 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0014, "GPU 14 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0015, "GPU 15 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0016, "GPU 16 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0008, "GPU 08 HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de0009, "GPU 09 HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de000a, "GPU 0a HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de000b, "GPU 0b HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de000c, "MCP89 HDMI", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de000d, "GPU 0d HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de0010, "GPU 10 HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de0011, "GPU 11 HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de0012, "GPU 12 HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de0013, "GPU 13 HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de0014, "GPU 14 HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de0015, "GPU 15 HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de0016, "GPU 16 HDMI/DP", patch_nvhdmi_legacy),
|
||||
/* 17 is known to be absent */
|
||||
HDA_CODEC_ENTRY(0x10de0018, "GPU 18 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0019, "GPU 19 HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de001a, "GPU 1a HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de001b, "GPU 1b HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de001c, "GPU 1c HDMI/DP", patch_nvhdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0018, "GPU 18 HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de0019, "GPU 19 HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de001a, "GPU 1a HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de001b, "GPU 1b HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de001c, "GPU 1c HDMI/DP", patch_nvhdmi_legacy),
|
||||
HDA_CODEC_ENTRY(0x10de0020, "Tegra30 HDMI", patch_tegra_hdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0022, "Tegra114 HDMI", patch_tegra_hdmi),
|
||||
HDA_CODEC_ENTRY(0x10de0028, "Tegra124 HDMI", patch_tegra_hdmi),
|
||||
|
|
|
|||
|
|
@ -7248,6 +7248,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
|
|||
SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD),
|
||||
SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS),
|
||||
SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */
|
||||
SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC),
|
||||
SND_PCI_QUIRK(0x10ec, 0x118c, "Medion EE4254 MD62100", ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE),
|
||||
|
||||
#if 0
|
||||
|
|
@ -7512,20 +7513,6 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = {
|
|||
{0x19, 0x02a11020},
|
||||
{0x1a, 0x02a11030},
|
||||
{0x21, 0x0221101f}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x12, 0x90a60140},
|
||||
{0x14, 0x90170110},
|
||||
{0x21, 0x02211020}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x12, 0x90a60140},
|
||||
{0x14, 0x90170150},
|
||||
{0x21, 0x02211020}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x21, 0x02211020}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x12, 0x40000000},
|
||||
{0x14, 0x90170110},
|
||||
{0x21, 0x02211020}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL2_MIC_NO_PRESENCE,
|
||||
{0x14, 0x90170110},
|
||||
{0x21, 0x02211020}),
|
||||
|
|
@ -7608,38 +7595,6 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = {
|
|||
SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x1b, 0x01011020},
|
||||
{0x21, 0x02211010}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x12, 0x90a60130},
|
||||
{0x14, 0x90170110},
|
||||
{0x1b, 0x01011020},
|
||||
{0x21, 0x0221101f}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x12, 0x90a60160},
|
||||
{0x14, 0x90170120},
|
||||
{0x21, 0x02211030}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x12, 0x90a60170},
|
||||
{0x14, 0x90170120},
|
||||
{0x21, 0x02211030}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell Inspiron 5468", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x12, 0x90a60180},
|
||||
{0x14, 0x90170120},
|
||||
{0x21, 0x02211030}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x12, 0xb7a60130},
|
||||
{0x14, 0x90170110},
|
||||
{0x21, 0x02211020}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x12, 0x90a60130},
|
||||
{0x14, 0x90170110},
|
||||
{0x14, 0x01011020},
|
||||
{0x21, 0x0221101f}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
ALC256_STANDARD_PINS),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x14, 0x90170110},
|
||||
{0x1b, 0x01011020},
|
||||
{0x21, 0x0221101f}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC,
|
||||
{0x14, 0x90170110},
|
||||
{0x1b, 0x90a70130},
|
||||
|
|
@ -7852,6 +7807,12 @@ static const struct snd_hda_pin_quirk alc269_fallback_pin_fixup_tbl[] = {
|
|||
SND_HDA_PIN_QUIRK(0x10ec0289, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE,
|
||||
{0x19, 0x40000000},
|
||||
{0x1b, 0x40000000}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x19, 0x40000000},
|
||||
{0x1a, 0x40000000}),
|
||||
SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE,
|
||||
{0x19, 0x40000000},
|
||||
{0x1a, 0x40000000}),
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
|
|
@ -59,6 +61,7 @@ struct pcm3168a_priv {
|
|||
struct regulator_bulk_data supplies[PCM3168A_NUM_SUPPLIES];
|
||||
struct regmap *regmap;
|
||||
struct clk *scki;
|
||||
struct gpio_desc *gpio_rst;
|
||||
unsigned long sysclk;
|
||||
|
||||
struct pcm3168a_io_params io_params[2];
|
||||
|
|
@ -643,6 +646,7 @@ static bool pcm3168a_readable_register(struct device *dev, unsigned int reg)
|
|||
static bool pcm3168a_volatile_register(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case PCM3168A_RST_SMODE:
|
||||
case PCM3168A_DAC_ZERO:
|
||||
case PCM3168A_ADC_OV:
|
||||
return true;
|
||||
|
|
@ -702,6 +706,25 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap)
|
|||
|
||||
dev_set_drvdata(dev, pcm3168a);
|
||||
|
||||
/*
|
||||
* Request the reset (connected to RST pin) gpio line as non exclusive
|
||||
* as the same reset line might be connected to multiple pcm3168a codec
|
||||
*
|
||||
* The RST is low active, we want the GPIO line to be high initially, so
|
||||
* request the initial level to LOW which in practice means DEASSERTED:
|
||||
* The deasserted level of GPIO_ACTIVE_LOW is HIGH.
|
||||
*/
|
||||
pcm3168a->gpio_rst = devm_gpiod_get_optional(dev, "reset",
|
||||
GPIOD_OUT_LOW |
|
||||
GPIOD_FLAGS_BIT_NONEXCLUSIVE);
|
||||
if (IS_ERR(pcm3168a->gpio_rst)) {
|
||||
ret = PTR_ERR(pcm3168a->gpio_rst);
|
||||
if (ret != -EPROBE_DEFER )
|
||||
dev_err(dev, "failed to acquire RST gpio: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
pcm3168a->scki = devm_clk_get(dev, "scki");
|
||||
if (IS_ERR(pcm3168a->scki)) {
|
||||
ret = PTR_ERR(pcm3168a->scki);
|
||||
|
|
@ -743,10 +766,18 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap)
|
|||
goto err_regulator;
|
||||
}
|
||||
|
||||
ret = pcm3168a_reset(pcm3168a);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to reset device: %d\n", ret);
|
||||
goto err_regulator;
|
||||
if (pcm3168a->gpio_rst) {
|
||||
/*
|
||||
* The device is taken out from reset via GPIO line, wait for
|
||||
* 3846 SCKI clock cycles for the internal reset de-assertion
|
||||
*/
|
||||
msleep(DIV_ROUND_UP(3846 * 1000, pcm3168a->sysclk));
|
||||
} else {
|
||||
ret = pcm3168a_reset(pcm3168a);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to reset device: %d\n", ret);
|
||||
goto err_regulator;
|
||||
}
|
||||
}
|
||||
|
||||
pm_runtime_set_active(dev);
|
||||
|
|
@ -785,6 +816,15 @@ static void pcm3168a_disable(struct device *dev)
|
|||
|
||||
void pcm3168a_remove(struct device *dev)
|
||||
{
|
||||
struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev);
|
||||
|
||||
/*
|
||||
* The RST is low active, we want the GPIO line to be low when the
|
||||
* driver is removed, so set level to 1 which in practice means
|
||||
* ASSERTED:
|
||||
* The asserted level of GPIO_ACTIVE_LOW is LOW.
|
||||
*/
|
||||
gpiod_set_value_cansleep(pcm3168a->gpio_rst, 1);
|
||||
pm_runtime_disable(dev);
|
||||
#ifndef CONFIG_PM
|
||||
pcm3168a_disable(dev);
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ struct aic31xx_priv {
|
|||
int rate_div_line;
|
||||
bool master_dapm_route_applied;
|
||||
int irq;
|
||||
u8 ocmv; /* output common-mode voltage */
|
||||
};
|
||||
|
||||
struct aic31xx_rate_divs {
|
||||
|
|
@ -1312,6 +1313,11 @@ static int aic31xx_codec_probe(struct snd_soc_component *component)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* set output common-mode voltage */
|
||||
snd_soc_component_update_bits(component, AIC31XX_HPDRIVER,
|
||||
AIC31XX_HPD_OCMV_MASK,
|
||||
aic31xx->ocmv << AIC31XX_HPD_OCMV_SHIFT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1501,6 +1507,43 @@ static irqreturn_t aic31xx_irq(int irq, void *data)
|
|||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
static void aic31xx_configure_ocmv(struct aic31xx_priv *priv)
|
||||
{
|
||||
struct device *dev = priv->dev;
|
||||
int dvdd, avdd;
|
||||
u32 value;
|
||||
|
||||
if (dev->fwnode &&
|
||||
fwnode_property_read_u32(dev->fwnode, "ai31xx-ocmv", &value)) {
|
||||
/* OCMV setting is forced by DT */
|
||||
if (value <= 3) {
|
||||
priv->ocmv = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
avdd = regulator_get_voltage(priv->supplies[3].consumer);
|
||||
dvdd = regulator_get_voltage(priv->supplies[5].consumer);
|
||||
|
||||
if (avdd > 3600000 || dvdd > 1950000) {
|
||||
dev_warn(dev,
|
||||
"Too high supply voltage(s) AVDD: %d, DVDD: %d\n",
|
||||
avdd, dvdd);
|
||||
} else if (avdd == 3600000 && dvdd == 1950000) {
|
||||
priv->ocmv = AIC31XX_HPD_OCMV_1_8V;
|
||||
} else if (avdd >= 3300000 && dvdd >= 1800000) {
|
||||
priv->ocmv = AIC31XX_HPD_OCMV_1_65V;
|
||||
} else if (avdd >= 3000000 && dvdd >= 1650000) {
|
||||
priv->ocmv = AIC31XX_HPD_OCMV_1_5V;
|
||||
} else if (avdd >= 2700000 && dvdd >= 1525000) {
|
||||
priv->ocmv = AIC31XX_HPD_OCMV_1_35V;
|
||||
} else {
|
||||
dev_warn(dev,
|
||||
"Invalid supply voltage(s) AVDD: %d, DVDD: %d\n",
|
||||
avdd, dvdd);
|
||||
}
|
||||
}
|
||||
|
||||
static int aic31xx_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
|
|
@ -1570,6 +1613,8 @@ static int aic31xx_i2c_probe(struct i2c_client *i2c,
|
|||
return ret;
|
||||
}
|
||||
|
||||
aic31xx_configure_ocmv(aic31xx);
|
||||
|
||||
if (aic31xx->irq > 0) {
|
||||
regmap_update_bits(aic31xx->regmap, AIC31XX_GPIO1,
|
||||
AIC31XX_GPIO1_FUNC_MASK,
|
||||
|
|
|
|||
|
|
@ -232,6 +232,14 @@ struct aic31xx_pdata {
|
|||
#define AIC31XX_HSD_HP 0x01
|
||||
#define AIC31XX_HSD_HS 0x03
|
||||
|
||||
/* AIC31XX_HPDRIVER */
|
||||
#define AIC31XX_HPD_OCMV_MASK GENMASK(4, 3)
|
||||
#define AIC31XX_HPD_OCMV_SHIFT 3
|
||||
#define AIC31XX_HPD_OCMV_1_35V 0x0
|
||||
#define AIC31XX_HPD_OCMV_1_5V 0x1
|
||||
#define AIC31XX_HPD_OCMV_1_65V 0x2
|
||||
#define AIC31XX_HPD_OCMV_1_8V 0x3
|
||||
|
||||
/* AIC31XX_MICBIAS */
|
||||
#define AIC31XX_MICBIAS_MASK GENMASK(1, 0)
|
||||
#define AIC31XX_MICBIAS_SHIFT 0
|
||||
|
|
|
|||
|
|
@ -599,6 +599,9 @@ struct wm_coeff_ctl_ops {
|
|||
struct wm_coeff_ctl {
|
||||
const char *name;
|
||||
const char *fw_name;
|
||||
/* Subname is needed to match with firmware */
|
||||
const char *subname;
|
||||
unsigned int subname_len;
|
||||
struct wm_adsp_alg_region alg_region;
|
||||
struct wm_coeff_ctl_ops ops;
|
||||
struct wm_adsp *dsp;
|
||||
|
|
@ -1399,6 +1402,7 @@ static void wm_adsp_free_ctl_blk(struct wm_coeff_ctl *ctl)
|
|||
{
|
||||
kfree(ctl->cache);
|
||||
kfree(ctl->name);
|
||||
kfree(ctl->subname);
|
||||
kfree(ctl);
|
||||
}
|
||||
|
||||
|
|
@ -1472,6 +1476,15 @@ static int wm_adsp_create_control(struct wm_adsp *dsp,
|
|||
ret = -ENOMEM;
|
||||
goto err_ctl;
|
||||
}
|
||||
if (subname) {
|
||||
ctl->subname_len = subname_len;
|
||||
ctl->subname = kmemdup(subname,
|
||||
strlen(subname) + 1, GFP_KERNEL);
|
||||
if (!ctl->subname) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ctl_name;
|
||||
}
|
||||
}
|
||||
ctl->enabled = 1;
|
||||
ctl->set = 0;
|
||||
ctl->ops.xget = wm_coeff_get;
|
||||
|
|
@ -1485,7 +1498,7 @@ static int wm_adsp_create_control(struct wm_adsp *dsp,
|
|||
ctl->cache = kzalloc(ctl->len, GFP_KERNEL);
|
||||
if (!ctl->cache) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ctl_name;
|
||||
goto err_ctl_subname;
|
||||
}
|
||||
|
||||
list_add(&ctl->list, &dsp->ctl_list);
|
||||
|
|
@ -1508,6 +1521,8 @@ static int wm_adsp_create_control(struct wm_adsp *dsp,
|
|||
|
||||
err_ctl_cache:
|
||||
kfree(ctl->cache);
|
||||
err_ctl_subname:
|
||||
kfree(ctl->subname);
|
||||
err_ctl_name:
|
||||
kfree(ctl->name);
|
||||
err_ctl:
|
||||
|
|
@ -1995,6 +2010,70 @@ static int wm_adsp_load(struct wm_adsp *dsp)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find wm_coeff_ctl with input name as its subname
|
||||
* If not found, return NULL
|
||||
*/
|
||||
static struct wm_coeff_ctl *wm_adsp_get_ctl(struct wm_adsp *dsp,
|
||||
const char *name, int type,
|
||||
unsigned int alg)
|
||||
{
|
||||
struct wm_coeff_ctl *pos, *rslt = NULL;
|
||||
|
||||
list_for_each_entry(pos, &dsp->ctl_list, list) {
|
||||
if (!pos->subname)
|
||||
continue;
|
||||
if (strncmp(pos->subname, name, pos->subname_len) == 0 &&
|
||||
pos->alg_region.alg == alg &&
|
||||
pos->alg_region.type == type) {
|
||||
rslt = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return rslt;
|
||||
}
|
||||
|
||||
int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type,
|
||||
unsigned int alg, void *buf, size_t len)
|
||||
{
|
||||
struct wm_coeff_ctl *ctl;
|
||||
struct snd_kcontrol *kcontrol;
|
||||
int ret;
|
||||
|
||||
ctl = wm_adsp_get_ctl(dsp, name, type, alg);
|
||||
if (!ctl)
|
||||
return -EINVAL;
|
||||
|
||||
if (len > ctl->len)
|
||||
return -EINVAL;
|
||||
|
||||
ret = wm_coeff_write_control(ctl, buf, len);
|
||||
|
||||
kcontrol = snd_soc_card_get_kcontrol(dsp->component->card, ctl->name);
|
||||
snd_ctl_notify(dsp->component->card->snd_card,
|
||||
SNDRV_CTL_EVENT_MASK_VALUE, &kcontrol->id);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm_adsp_write_ctl);
|
||||
|
||||
int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type,
|
||||
unsigned int alg, void *buf, size_t len)
|
||||
{
|
||||
struct wm_coeff_ctl *ctl;
|
||||
|
||||
ctl = wm_adsp_get_ctl(dsp, name, type, alg);
|
||||
if (!ctl)
|
||||
return -EINVAL;
|
||||
|
||||
if (len > ctl->len)
|
||||
return -EINVAL;
|
||||
|
||||
return wm_coeff_read_control(ctl, buf, len);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm_adsp_read_ctl);
|
||||
|
||||
static void wm_adsp_ctl_fixup_base(struct wm_adsp *dsp,
|
||||
const struct wm_adsp_alg_region *alg_region)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -201,5 +201,9 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream,
|
|||
struct snd_compr_tstamp *tstamp);
|
||||
int wm_adsp_compr_copy(struct snd_compr_stream *stream,
|
||||
char __user *buf, size_t count);
|
||||
int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type,
|
||||
unsigned int alg, void *buf, size_t len);
|
||||
int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type,
|
||||
unsigned int alg, void *buf, size_t len);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -466,18 +466,18 @@ config SND_SOC_INTEL_CML_LP_DA7219_MAX98357A_MACH
|
|||
If unsure select "N".
|
||||
|
||||
config SND_SOC_INTEL_SOF_CML_RT1011_RT5682_MACH
|
||||
tristate "CML with RT1011 and RT5682 in I2S Mode"
|
||||
depends on I2C && ACPI
|
||||
depends on MFD_INTEL_LPSS || COMPILE_TEST
|
||||
select SND_SOC_RT1011
|
||||
select SND_SOC_RT5682
|
||||
select SND_SOC_DMIC
|
||||
select SND_SOC_HDAC_HDMI
|
||||
help
|
||||
This adds support for ASoC machine driver for SOF platform with
|
||||
RT1011 + RT5682 I2S codec.
|
||||
Say Y if you have such a device.
|
||||
If unsure select "N".
|
||||
tristate "CML with RT1011 and RT5682 in I2S Mode"
|
||||
depends on I2C && ACPI
|
||||
depends on MFD_INTEL_LPSS || COMPILE_TEST
|
||||
select SND_SOC_RT1011
|
||||
select SND_SOC_RT5682
|
||||
select SND_SOC_DMIC
|
||||
select SND_SOC_HDAC_HDMI
|
||||
help
|
||||
This adds support for ASoC machine driver for SOF platform with
|
||||
RT1011 + RT5682 I2S codec.
|
||||
Say Y if you have such a device.
|
||||
If unsure select "N".
|
||||
|
||||
endif ## SND_SOC_SOF_COMETLAKE_LP && SND_SOC_SOF_HDA_LINK
|
||||
|
||||
|
|
|
|||
|
|
@ -405,10 +405,12 @@ static const struct dmi_system_id byt_rt5640_quirk_table[] = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW5-012"),
|
||||
},
|
||||
.driver_data = (void *)(BYT_RT5640_IN1_MAP |
|
||||
BYT_RT5640_MCLK_EN |
|
||||
BYT_RT5640_SSP0_AIF1),
|
||||
|
||||
.driver_data = (void *)(BYT_RT5640_DMIC1_MAP |
|
||||
BYT_RT5640_JD_SRC_JD2_IN4N |
|
||||
BYT_RT5640_OVCD_TH_2000UA |
|
||||
BYT_RT5640_OVCD_SF_0P75 |
|
||||
BYT_RT5640_SSP0_AIF1 |
|
||||
BYT_RT5640_MCLK_EN),
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
|
|
|
|||
|
|
@ -498,9 +498,8 @@ int snd_soc_pcm_component_mmap(struct snd_pcm_substream *substream,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
int snd_soc_pcm_component_new(struct snd_pcm *pcm)
|
||||
int snd_soc_pcm_component_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
||||
struct snd_soc_rtdcom_list *rtdcom;
|
||||
struct snd_soc_component *component;
|
||||
int ret;
|
||||
|
|
@ -516,13 +515,12 @@ int snd_soc_pcm_component_new(struct snd_pcm *pcm)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void snd_soc_pcm_component_free(struct snd_pcm *pcm)
|
||||
void snd_soc_pcm_component_free(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
||||
struct snd_soc_rtdcom_list *rtdcom;
|
||||
struct snd_soc_component *component;
|
||||
|
||||
for_each_rtd_components(rtd, rtdcom, component)
|
||||
if (component->driver->pcm_destruct)
|
||||
component->driver->pcm_destruct(component, pcm);
|
||||
component->driver->pcm_destruct(component, rtd->pcm);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -419,6 +419,9 @@ static void soc_free_pcm_runtime(struct snd_soc_pcm_runtime *rtd)
|
|||
|
||||
list_del(&rtd->list);
|
||||
|
||||
flush_delayed_work(&rtd->delayed_work);
|
||||
snd_soc_pcm_component_free(rtd);
|
||||
|
||||
/*
|
||||
* we don't need to call kfree() for rtd->dev
|
||||
* see
|
||||
|
|
@ -1945,19 +1948,14 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card,
|
|||
{
|
||||
struct snd_soc_dai_link *link, *_link;
|
||||
|
||||
/* This should be called before snd_card_free() */
|
||||
soc_remove_link_components(card);
|
||||
|
||||
/* free the ALSA card at first; this syncs with pending operations */
|
||||
if (card->snd_card) {
|
||||
snd_card_free(card->snd_card);
|
||||
card->snd_card = NULL;
|
||||
}
|
||||
if (card->snd_card)
|
||||
snd_card_disconnect_sync(card->snd_card);
|
||||
|
||||
snd_soc_dapm_shutdown(card);
|
||||
|
||||
/* remove and free each DAI */
|
||||
soc_remove_link_dais(card);
|
||||
soc_remove_link_components(card);
|
||||
|
||||
for_each_card_links_safe(card, link, _link)
|
||||
snd_soc_remove_dai_link(card, link);
|
||||
|
|
@ -1972,6 +1970,11 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card,
|
|||
/* remove the card */
|
||||
if (card_probed && card->remove)
|
||||
card->remove(card);
|
||||
|
||||
if (card->snd_card) {
|
||||
snd_card_free(card->snd_card);
|
||||
card->snd_card = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_soc_unbind_card(struct snd_soc_card *card, bool unregister)
|
||||
|
|
@ -2105,6 +2108,19 @@ static int snd_soc_bind_card(struct snd_soc_card *card)
|
|||
soc_setup_card_name(card->snd_card->driver,
|
||||
card->driver_name, card->name, 1);
|
||||
|
||||
if (card->components) {
|
||||
/* the current implementation of snd_component_add() accepts */
|
||||
/* multiple components in the string separated by space, */
|
||||
/* but the string collision (identical string) check might */
|
||||
/* not work correctly */
|
||||
ret = snd_component_add(card->snd_card, card->components);
|
||||
if (ret < 0) {
|
||||
dev_err(card->dev, "ASoC: %s snd_component_add() failed: %d\n",
|
||||
card->name, ret);
|
||||
goto probe_end;
|
||||
}
|
||||
}
|
||||
|
||||
if (card->late_probe) {
|
||||
ret = card->late_probe(card);
|
||||
if (ret < 0) {
|
||||
|
|
|
|||
|
|
@ -861,6 +861,11 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
int i, ret = 0;
|
||||
|
||||
mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass);
|
||||
|
||||
ret = soc_pcm_params_symmetry(substream, params);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (rtd->dai_link->ops->hw_params) {
|
||||
ret = rtd->dai_link->ops->hw_params(substream, params);
|
||||
if (ret < 0) {
|
||||
|
|
@ -940,9 +945,6 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
}
|
||||
component = NULL;
|
||||
|
||||
ret = soc_pcm_params_symmetry(substream, params);
|
||||
if (ret)
|
||||
goto component_err;
|
||||
out:
|
||||
mutex_unlock(&rtd->card->pcm_mutex);
|
||||
return ret;
|
||||
|
|
@ -2892,15 +2894,6 @@ static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void soc_pcm_private_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
||||
|
||||
/* need to sync the delayed work before releasing resources */
|
||||
flush_delayed_work(&rtd->delayed_work);
|
||||
snd_soc_pcm_component_free(pcm);
|
||||
}
|
||||
|
||||
/* create a new pcm */
|
||||
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
|
||||
{
|
||||
|
|
@ -3036,13 +3029,12 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
|
|||
if (capture)
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
|
||||
|
||||
ret = snd_soc_pcm_component_new(pcm);
|
||||
ret = snd_soc_pcm_component_new(rtd);
|
||||
if (ret < 0) {
|
||||
dev_err(rtd->dev, "ASoC: pcm constructor failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pcm->private_free = soc_pcm_private_free;
|
||||
pcm->no_device_suspend = true;
|
||||
out:
|
||||
dev_info(rtd->card->dev, "%s <-> %s mapping ok\n",
|
||||
|
|
|
|||
|
|
@ -264,16 +264,16 @@ config SND_SOC_SOF_ELKHARTLAKE
|
|||
config SND_SOC_SOF_JASPERLAKE_SUPPORT
|
||||
bool "SOF support for JasperLake"
|
||||
help
|
||||
This adds support for Sound Open Firmware for Intel(R) platforms
|
||||
using the JasperLake processors.
|
||||
Say Y if you have such a device.
|
||||
If unsure select "N".
|
||||
This adds support for Sound Open Firmware for Intel(R) platforms
|
||||
using the JasperLake processors.
|
||||
Say Y if you have such a device.
|
||||
If unsure select "N".
|
||||
|
||||
config SND_SOC_SOF_JASPERLAKE
|
||||
tristate
|
||||
select SND_SOC_SOF_HDA_COMMON
|
||||
help
|
||||
This option is not user-selectable but automagically handled by
|
||||
This option is not user-selectable but automagically handled by
|
||||
'select' statements at a higher level
|
||||
|
||||
config SND_SOC_SOF_HDA_COMMON
|
||||
|
|
|
|||
|
|
@ -261,34 +261,34 @@ static const struct scarlett2_device_info s6i6_gen2_info = {
|
|||
},
|
||||
|
||||
.ports = {
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_NONE] = {
|
||||
.id = 0x000,
|
||||
.num = { 1, 0, 8, 8, 8 },
|
||||
.src_descr = "Off",
|
||||
.src_num_offset = 0,
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_ANALOGUE] = {
|
||||
.id = 0x080,
|
||||
.num = { 4, 4, 4, 4, 4 },
|
||||
.src_descr = "Analogue %d",
|
||||
.src_num_offset = 1,
|
||||
.dst_descr = "Analogue Output %02d Playback"
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_SPDIF] = {
|
||||
.id = 0x180,
|
||||
.num = { 2, 2, 2, 2, 2 },
|
||||
.src_descr = "S/PDIF %d",
|
||||
.src_num_offset = 1,
|
||||
.dst_descr = "S/PDIF Output %d Playback"
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_MIX] = {
|
||||
.id = 0x300,
|
||||
.num = { 10, 18, 18, 18, 18 },
|
||||
.src_descr = "Mix %c",
|
||||
.src_num_offset = 65,
|
||||
.dst_descr = "Mixer Input %02d Capture"
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_PCM] = {
|
||||
.id = 0x600,
|
||||
.num = { 6, 6, 6, 6, 6 },
|
||||
.src_descr = "PCM %d",
|
||||
|
|
@ -317,44 +317,44 @@ static const struct scarlett2_device_info s18i8_gen2_info = {
|
|||
},
|
||||
|
||||
.ports = {
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_NONE] = {
|
||||
.id = 0x000,
|
||||
.num = { 1, 0, 8, 8, 4 },
|
||||
.src_descr = "Off",
|
||||
.src_num_offset = 0,
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_ANALOGUE] = {
|
||||
.id = 0x080,
|
||||
.num = { 8, 6, 6, 6, 6 },
|
||||
.src_descr = "Analogue %d",
|
||||
.src_num_offset = 1,
|
||||
.dst_descr = "Analogue Output %02d Playback"
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_SPDIF] = {
|
||||
.id = 0x180,
|
||||
/* S/PDIF outputs aren't available at 192KHz
|
||||
* but are included in the USB mux I/O
|
||||
* assignment message anyway
|
||||
*/
|
||||
.id = 0x180,
|
||||
.num = { 2, 2, 2, 2, 2 },
|
||||
.src_descr = "S/PDIF %d",
|
||||
.src_num_offset = 1,
|
||||
.dst_descr = "S/PDIF Output %d Playback"
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_ADAT] = {
|
||||
.id = 0x200,
|
||||
.num = { 8, 0, 0, 0, 0 },
|
||||
.src_descr = "ADAT %d",
|
||||
.src_num_offset = 1,
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_MIX] = {
|
||||
.id = 0x300,
|
||||
.num = { 10, 18, 18, 18, 18 },
|
||||
.src_descr = "Mix %c",
|
||||
.src_num_offset = 65,
|
||||
.dst_descr = "Mixer Input %02d Capture"
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_PCM] = {
|
||||
.id = 0x600,
|
||||
.num = { 20, 18, 18, 14, 10 },
|
||||
.src_descr = "PCM %d",
|
||||
|
|
@ -387,20 +387,20 @@ static const struct scarlett2_device_info s18i20_gen2_info = {
|
|||
},
|
||||
|
||||
.ports = {
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_NONE] = {
|
||||
.id = 0x000,
|
||||
.num = { 1, 0, 8, 8, 6 },
|
||||
.src_descr = "Off",
|
||||
.src_num_offset = 0,
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_ANALOGUE] = {
|
||||
.id = 0x080,
|
||||
.num = { 8, 10, 10, 10, 10 },
|
||||
.src_descr = "Analogue %d",
|
||||
.src_num_offset = 1,
|
||||
.dst_descr = "Analogue Output %02d Playback"
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_SPDIF] = {
|
||||
/* S/PDIF outputs aren't available at 192KHz
|
||||
* but are included in the USB mux I/O
|
||||
* assignment message anyway
|
||||
|
|
@ -411,21 +411,21 @@ static const struct scarlett2_device_info s18i20_gen2_info = {
|
|||
.src_num_offset = 1,
|
||||
.dst_descr = "S/PDIF Output %d Playback"
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_ADAT] = {
|
||||
.id = 0x200,
|
||||
.num = { 8, 8, 8, 4, 0 },
|
||||
.src_descr = "ADAT %d",
|
||||
.src_num_offset = 1,
|
||||
.dst_descr = "ADAT Output %d Playback"
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_MIX] = {
|
||||
.id = 0x300,
|
||||
.num = { 10, 18, 18, 18, 18 },
|
||||
.src_descr = "Mix %c",
|
||||
.src_num_offset = 65,
|
||||
.dst_descr = "Mixer Input %02d Capture"
|
||||
},
|
||||
{
|
||||
[SCARLETT2_PORT_TYPE_PCM] = {
|
||||
.id = 0x600,
|
||||
.num = { 20, 18, 18, 14, 10 },
|
||||
.src_descr = "PCM %d",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user