Merge branch 'for-next' into for-linus
This commit is contained in:
commit
9a4f35865f
@ -18,6 +18,7 @@ Required properties:
|
||||
* Headphones
|
||||
* Speakers
|
||||
* Mic Jack
|
||||
* Int Mic
|
||||
|
||||
- nvidia,i2s-controller : The phandle of the Tegra I2S controller that's
|
||||
connected to the CODEC.
|
||||
|
@ -466,7 +466,11 @@ The generic parser supports the following hints:
|
||||
- add_jack_modes (bool): add "xxx Jack Mode" enum controls to each
|
||||
I/O jack for allowing to change the headphone amp and mic bias VREF
|
||||
capabilities
|
||||
- power_down_unused (bool): power down the unused widgets
|
||||
- power_save_node (bool): advanced power management for each widget,
|
||||
controlling the power sate (D0/D3) of each widget node depending on
|
||||
the actual pin and stream states
|
||||
- power_down_unused (bool): power down the unused widgets, a subset of
|
||||
power_save_node, and will be dropped in future
|
||||
- add_hp_mic (bool): add the headphone to capture source if possible
|
||||
- hp_mic_detect (bool): enable/disable the hp/mic shared input for a
|
||||
single built-in mic case; default true
|
||||
|
200
Documentation/sound/alsa/timestamping.txt
Normal file
200
Documentation/sound/alsa/timestamping.txt
Normal file
@ -0,0 +1,200 @@
|
||||
The ALSA API can provide two different system timestamps:
|
||||
|
||||
- Trigger_tstamp is the system time snapshot taken when the .trigger
|
||||
callback is invoked. This snapshot is taken by the ALSA core in the
|
||||
general case, but specific hardware may have synchronization
|
||||
capabilities or conversely may only be able to provide a correct
|
||||
estimate with a delay. In the latter two cases, the low-level driver
|
||||
is responsible for updating the trigger_tstamp at the most appropriate
|
||||
and precise moment. Applications should not rely solely on the first
|
||||
trigger_tstamp but update their internal calculations if the driver
|
||||
provides a refined estimate with a delay.
|
||||
|
||||
- tstamp is the current system timestamp updated during the last
|
||||
event or application query.
|
||||
The difference (tstamp - trigger_tstamp) defines the elapsed time.
|
||||
|
||||
The ALSA API provides reports two basic pieces of information, avail
|
||||
and delay, which combined with the trigger and current system
|
||||
timestamps allow for applications to keep track of the 'fullness' of
|
||||
the ring buffer and the amount of queued samples.
|
||||
|
||||
The use of these different pointers and time information depends on
|
||||
the application needs:
|
||||
|
||||
- 'avail' reports how much can be written in the ring buffer
|
||||
- 'delay' reports the time it will take to hear a new sample after all
|
||||
queued samples have been played out.
|
||||
|
||||
When timestamps are enabled, the avail/delay information is reported
|
||||
along with a snapshot of system time. Applications can select from
|
||||
CLOCK_REALTIME (NTP corrections including going backwards),
|
||||
CLOCK_MONOTONIC (NTP corrections but never going backwards),
|
||||
CLOCK_MONOTIC_RAW (without NTP corrections) and change the mode
|
||||
dynamically with sw_params
|
||||
|
||||
|
||||
The ALSA API also provide an audio_tstamp which reflects the passage
|
||||
of time as measured by different components of audio hardware. In
|
||||
ascii-art, this could be represented as follows (for the playback
|
||||
case):
|
||||
|
||||
|
||||
--------------------------------------------------------------> time
|
||||
^ ^ ^ ^ ^
|
||||
| | | | |
|
||||
analog link dma app FullBuffer
|
||||
time time time time time
|
||||
| | | | |
|
||||
|< codec delay >|<--hw delay-->|<queued samples>|<---avail->|
|
||||
|<----------------- delay---------------------->| |
|
||||
|<----ring buffer length---->|
|
||||
|
||||
The analog time is taken at the last stage of the playback, as close
|
||||
as possible to the actual transducer
|
||||
|
||||
The link time is taken at the output of the SOC/chipset as the samples
|
||||
are pushed on a link. The link time can be directly measured if
|
||||
supported in hardware by sample counters or wallclocks (e.g. with
|
||||
HDAudio 24MHz or PTP clock for networked solutions) or indirectly
|
||||
estimated (e.g. with the frame counter in USB).
|
||||
|
||||
The DMA time is measured using counters - typically the least reliable
|
||||
of all measurements due to the bursty natured of DMA transfers.
|
||||
|
||||
The app time corresponds to the time tracked by an application after
|
||||
writing in the ring buffer.
|
||||
|
||||
The application can query what the hardware supports, define which
|
||||
audio time it wants reported by selecting the relevant settings in
|
||||
audio_tstamp_config fields, get an estimate of the timestamp
|
||||
accuracy. It can also request the delay-to-analog be included in the
|
||||
measurement. Direct access to the link time is very interesting on
|
||||
platforms that provide an embedded DSP; measuring directly the link
|
||||
time with dedicated hardware, possibly synchronized with system time,
|
||||
removes the need to keep track of internal DSP processing times and
|
||||
latency.
|
||||
|
||||
In case the application requests an audio tstamp that is not supported
|
||||
in hardware/low-level driver, the type is overridden as DEFAULT and the
|
||||
timestamp will report the DMA time based on the hw_pointer value.
|
||||
|
||||
For backwards compatibility with previous implementations that did not
|
||||
provide timestamp selection, with a zero-valued COMPAT timestamp type
|
||||
the results will default to the HDAudio wall clock for playback
|
||||
streams and to the DMA time (hw_ptr) in all other cases.
|
||||
|
||||
The audio timestamp accuracy can be returned to user-space, so that
|
||||
appropriate decisions are made:
|
||||
|
||||
- for dma time (default), the granularity of the transfers can be
|
||||
inferred from the steps between updates and in turn provide
|
||||
information on how much the application pointer can be rewound
|
||||
safely.
|
||||
|
||||
- the link time can be used to track long-term drifts between audio
|
||||
and system time using the (tstamp-trigger_tstamp)/audio_tstamp
|
||||
ratio, the precision helps define how much smoothing/low-pass
|
||||
filtering is required. The link time can be either reset on startup
|
||||
or reported as is (the latter being useful to compare progress of
|
||||
different streams - but may require the wallclock to be always
|
||||
running and not wrap-around during idle periods). If supported in
|
||||
hardware, the absolute link time could also be used to define a
|
||||
precise start time (patches WIP)
|
||||
|
||||
- including the delay in the audio timestamp may
|
||||
counter-intuitively not increase the precision of timestamps, e.g. if a
|
||||
codec includes variable-latency DSP processing or a chain of
|
||||
hardware components the delay is typically not known with precision.
|
||||
|
||||
The accuracy is reported in nanosecond units (using an unsigned 32-bit
|
||||
word), which gives a max precision of 4.29s, more than enough for
|
||||
audio applications...
|
||||
|
||||
Due to the varied nature of timestamping needs, even for a single
|
||||
application, the audio_tstamp_config can be changed dynamically. In
|
||||
the STATUS ioctl, the parameters are read-only and do not allow for
|
||||
any application selection. To work around this limitation without
|
||||
impacting legacy applications, a new STATUS_EXT ioctl is introduced
|
||||
with read/write parameters. ALSA-lib will be modified to make use of
|
||||
STATUS_EXT and effectively deprecate STATUS.
|
||||
|
||||
The ALSA API only allows for a single audio timestamp to be reported
|
||||
at a time. This is a conscious design decision, reading the audio
|
||||
timestamps from hardware registers or from IPC takes time, the more
|
||||
timestamps are read the more imprecise the combined measurements
|
||||
are. To avoid any interpretation issues, a single (system, audio)
|
||||
timestamp is reported. Applications that need different timestamps
|
||||
will be required to issue multiple queries and perform an
|
||||
interpolation of the results
|
||||
|
||||
In some hardware-specific configuration, the system timestamp is
|
||||
latched by a low-level audio subsytem, and the information provided
|
||||
back to the driver. Due to potential delays in the communication with
|
||||
the hardware, there is a risk of misalignment with the avail and delay
|
||||
information. To make sure applications are not confused, a
|
||||
driver_timestamp field is added in the snd_pcm_status structure; this
|
||||
timestamp shows when the information is put together by the driver
|
||||
before returning from the STATUS and STATUS_EXT ioctl. in most cases
|
||||
this driver_timestamp will be identical to the regular system tstamp.
|
||||
|
||||
Examples of typestamping with HDaudio:
|
||||
|
||||
1. DMA timestamp, no compensation for DMA+analog delay
|
||||
$ ./audio_time -p --ts_type=1
|
||||
playback: systime: 341121338 nsec, audio time 342000000 nsec, systime delta -878662
|
||||
playback: systime: 426236663 nsec, audio time 427187500 nsec, systime delta -950837
|
||||
playback: systime: 597080580 nsec, audio time 598000000 nsec, systime delta -919420
|
||||
playback: systime: 682059782 nsec, audio time 683020833 nsec, systime delta -961051
|
||||
playback: systime: 852896415 nsec, audio time 853854166 nsec, systime delta -957751
|
||||
playback: systime: 937903344 nsec, audio time 938854166 nsec, systime delta -950822
|
||||
|
||||
2. DMA timestamp, compensation for DMA+analog delay
|
||||
$ ./audio_time -p --ts_type=1 -d
|
||||
playback: systime: 341053347 nsec, audio time 341062500 nsec, systime delta -9153
|
||||
playback: systime: 426072447 nsec, audio time 426062500 nsec, systime delta 9947
|
||||
playback: systime: 596899518 nsec, audio time 596895833 nsec, systime delta 3685
|
||||
playback: systime: 681915317 nsec, audio time 681916666 nsec, systime delta -1349
|
||||
playback: systime: 852741306 nsec, audio time 852750000 nsec, systime delta -8694
|
||||
|
||||
3. link timestamp, compensation for DMA+analog delay
|
||||
$ ./audio_time -p --ts_type=2 -d
|
||||
playback: systime: 341060004 nsec, audio time 341062791 nsec, systime delta -2787
|
||||
playback: systime: 426242074 nsec, audio time 426244875 nsec, systime delta -2801
|
||||
playback: systime: 597080992 nsec, audio time 597084583 nsec, systime delta -3591
|
||||
playback: systime: 682084512 nsec, audio time 682088291 nsec, systime delta -3779
|
||||
playback: systime: 852936229 nsec, audio time 852940916 nsec, systime delta -4687
|
||||
playback: systime: 938107562 nsec, audio time 938112708 nsec, systime delta -5146
|
||||
|
||||
Example 1 shows that the timestamp at the DMA level is close to 1ms
|
||||
ahead of the actual playback time (as a side time this sort of
|
||||
measurement can help define rewind safeguards). Compensating for the
|
||||
DMA-link delay in example 2 helps remove the hardware buffering abut
|
||||
the information is still very jittery, with up to one sample of
|
||||
error. In example 3 where the timestamps are measured with the link
|
||||
wallclock, the timestamps show a monotonic behavior and a lower
|
||||
dispersion.
|
||||
|
||||
Example 3 and 4 are with USB audio class. Example 3 shows a high
|
||||
offset between audio time and system time due to buffering. Example 4
|
||||
shows how compensating for the delay exposes a 1ms accuracy (due to
|
||||
the use of the frame counter by the driver)
|
||||
|
||||
Example 3: DMA timestamp, no compensation for delay, delta of ~5ms
|
||||
$ ./audio_time -p -Dhw:1 -t1
|
||||
playback: systime: 120174019 nsec, audio time 125000000 nsec, systime delta -4825981
|
||||
playback: systime: 245041136 nsec, audio time 250000000 nsec, systime delta -4958864
|
||||
playback: systime: 370106088 nsec, audio time 375000000 nsec, systime delta -4893912
|
||||
playback: systime: 495040065 nsec, audio time 500000000 nsec, systime delta -4959935
|
||||
playback: systime: 620038179 nsec, audio time 625000000 nsec, systime delta -4961821
|
||||
playback: systime: 745087741 nsec, audio time 750000000 nsec, systime delta -4912259
|
||||
playback: systime: 870037336 nsec, audio time 875000000 nsec, systime delta -4962664
|
||||
|
||||
Example 4: DMA timestamp, compensation for delay, delay of ~1ms
|
||||
$ ./audio_time -p -Dhw:1 -t1 -d
|
||||
playback: systime: 120190520 nsec, audio time 120000000 nsec, systime delta 190520
|
||||
playback: systime: 245036740 nsec, audio time 244000000 nsec, systime delta 1036740
|
||||
playback: systime: 370034081 nsec, audio time 369000000 nsec, systime delta 1034081
|
||||
playback: systime: 495159907 nsec, audio time 494000000 nsec, systime delta 1159907
|
||||
playback: systime: 620098824 nsec, audio time 619000000 nsec, systime delta 1098824
|
||||
playback: systime: 745031847 nsec, audio time 744000000 nsec, systime delta 1031847
|
@ -608,7 +608,9 @@ struct ac97_quirk {
|
||||
int type; /* quirk type above */
|
||||
};
|
||||
|
||||
int snd_ac97_tune_hardware(struct snd_ac97 *ac97, struct ac97_quirk *quirk, const char *override);
|
||||
int snd_ac97_tune_hardware(struct snd_ac97 *ac97,
|
||||
const struct ac97_quirk *quirk,
|
||||
const char *override);
|
||||
int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate);
|
||||
|
||||
/*
|
||||
|
@ -70,7 +70,7 @@ struct snd_compr_runtime {
|
||||
* @device: device pointer
|
||||
* @direction: stream direction, playback/recording
|
||||
* @metadata_set: metadata set flag, true when set
|
||||
* @next_track: has userspace signall next track transistion, true when set
|
||||
* @next_track: has userspace signal next track transition, true when set
|
||||
* @private_data: pointer to DSP private data
|
||||
*/
|
||||
struct snd_compr_stream {
|
||||
@ -95,7 +95,7 @@ struct snd_compr_stream {
|
||||
* and the stream properties
|
||||
* @get_params: retrieve the codec parameters, mandatory
|
||||
* @set_metadata: Set the metadata values for a stream
|
||||
* @get_metadata: retreives the requested metadata values from stream
|
||||
* @get_metadata: retrieves the requested metadata values from stream
|
||||
* @trigger: Trigger operations like start, pause, resume, drain, stop.
|
||||
* This callback is mandatory
|
||||
* @pointer: Retrieve current h/w pointer information. Mandatory
|
||||
|
@ -227,7 +227,7 @@ snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave)
|
||||
* Add a virtual slave control to the given master.
|
||||
* Unlike snd_ctl_add_slave(), the element added via this function
|
||||
* is supposed to have volatile values, and get callback is called
|
||||
* at each time quried from the master.
|
||||
* at each time queried from the master.
|
||||
*
|
||||
* When the control peeks the hardware values directly and the value
|
||||
* can be changed by other means than the put callback of the element,
|
||||
|
@ -278,7 +278,8 @@ int snd_device_new(struct snd_card *card, enum snd_device_type type,
|
||||
void *device_data, struct snd_device_ops *ops);
|
||||
int snd_device_register(struct snd_card *card, void *device_data);
|
||||
int snd_device_register_all(struct snd_card *card);
|
||||
int snd_device_disconnect_all(struct snd_card *card);
|
||||
void snd_device_disconnect(struct snd_card *card, void *device_data);
|
||||
void snd_device_disconnect_all(struct snd_card *card);
|
||||
void snd_device_free(struct snd_card *card, void *device_data);
|
||||
void snd_device_free_all(struct snd_card *card);
|
||||
|
||||
|
217
include/sound/hda_regmap.h
Normal file
217
include/sound/hda_regmap.h
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* HD-audio regmap helpers
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_HDA_REGMAP_H
|
||||
#define __SOUND_HDA_REGMAP_H
|
||||
|
||||
#include <linux/regmap.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/hdaudio.h>
|
||||
|
||||
int snd_hdac_regmap_init(struct hdac_device *codec);
|
||||
void snd_hdac_regmap_exit(struct hdac_device *codec);
|
||||
int snd_hdac_regmap_add_vendor_verb(struct hdac_device *codec,
|
||||
unsigned int verb);
|
||||
int snd_hdac_regmap_read_raw(struct hdac_device *codec, unsigned int reg,
|
||||
unsigned int *val);
|
||||
int snd_hdac_regmap_write_raw(struct hdac_device *codec, unsigned int reg,
|
||||
unsigned int val);
|
||||
int snd_hdac_regmap_update_raw(struct hdac_device *codec, unsigned int reg,
|
||||
unsigned int mask, unsigned int val);
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_encode_verb - encode the verb to a pseudo register
|
||||
* @nid: widget NID
|
||||
* @verb: codec verb
|
||||
*
|
||||
* Returns an encoded pseudo register.
|
||||
*/
|
||||
#define snd_hdac_regmap_encode_verb(nid, verb) \
|
||||
(((verb) << 8) | 0x80000 | ((unsigned int)(nid) << 20))
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_encode_amp - encode the AMP verb to a pseudo register
|
||||
* @nid: widget NID
|
||||
* @ch: channel (left = 0, right = 1)
|
||||
* @dir: direction (#HDA_INPUT, #HDA_OUTPUT)
|
||||
* @idx: input index value
|
||||
*
|
||||
* Returns an encoded pseudo register.
|
||||
*/
|
||||
#define snd_hdac_regmap_encode_amp(nid, ch, dir, idx) \
|
||||
(snd_hdac_regmap_encode_verb(nid, AC_VERB_GET_AMP_GAIN_MUTE) | \
|
||||
((ch) ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT) | \
|
||||
((dir) == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT) | \
|
||||
(idx))
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_encode_amp_stereo - encode a pseudo register for stereo AMPs
|
||||
* @nid: widget NID
|
||||
* @dir: direction (#HDA_INPUT, #HDA_OUTPUT)
|
||||
* @idx: input index value
|
||||
*
|
||||
* Returns an encoded pseudo register.
|
||||
*/
|
||||
#define snd_hdac_regmap_encode_amp_stereo(nid, dir, idx) \
|
||||
(snd_hdac_regmap_encode_verb(nid, AC_VERB_GET_AMP_GAIN_MUTE) | \
|
||||
AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT | /* both bits set! */ \
|
||||
((dir) == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT) | \
|
||||
(idx))
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_write - Write a verb with caching
|
||||
* @nid: codec NID
|
||||
* @reg: verb to write
|
||||
* @val: value to write
|
||||
*
|
||||
* For writing an amp value, use snd_hda_regmap_amp_update().
|
||||
*/
|
||||
static inline int
|
||||
snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid,
|
||||
unsigned int verb, unsigned int val)
|
||||
{
|
||||
unsigned int cmd = snd_hdac_regmap_encode_verb(nid, verb);
|
||||
|
||||
return snd_hdac_regmap_write_raw(codec, cmd, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_regmap_update - Update a verb value with caching
|
||||
* @nid: codec NID
|
||||
* @verb: verb to update
|
||||
* @mask: bit mask to update
|
||||
* @val: value to update
|
||||
*
|
||||
* For updating an amp value, use snd_hda_regmap_amp_update().
|
||||
*/
|
||||
static inline int
|
||||
snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid,
|
||||
unsigned int verb, unsigned int mask,
|
||||
unsigned int val)
|
||||
{
|
||||
unsigned int cmd = snd_hdac_regmap_encode_verb(nid, verb);
|
||||
|
||||
return snd_hdac_regmap_update_raw(codec, cmd, mask, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_regmap_read - Read a verb with caching
|
||||
* @nid: codec NID
|
||||
* @verb: verb to read
|
||||
* @val: pointer to store the value
|
||||
*
|
||||
* For reading an amp value, use snd_hda_regmap_get_amp().
|
||||
*/
|
||||
static inline int
|
||||
snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid,
|
||||
unsigned int verb, unsigned int *val)
|
||||
{
|
||||
unsigned int cmd = snd_hdac_regmap_encode_verb(nid, verb);
|
||||
|
||||
return snd_hdac_regmap_read_raw(codec, cmd, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_get_amp - Read AMP value
|
||||
* @codec: HD-audio codec
|
||||
* @nid: NID to read the AMP value
|
||||
* @ch: channel (left=0 or right=1)
|
||||
* @direction: #HDA_INPUT or #HDA_OUTPUT
|
||||
* @index: the index value (only for input direction)
|
||||
* @val: the pointer to store the value
|
||||
*
|
||||
* Read AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit.
|
||||
* Returns the value or a negative error.
|
||||
*/
|
||||
static inline int
|
||||
snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid,
|
||||
int ch, int dir, int idx)
|
||||
{
|
||||
unsigned int cmd = snd_hdac_regmap_encode_amp(nid, ch, dir, idx);
|
||||
int err, val;
|
||||
|
||||
err = snd_hdac_regmap_read_raw(codec, cmd, &val);
|
||||
return err < 0 ? err : val;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_update_amp - update the AMP value
|
||||
* @codec: HD-audio codec
|
||||
* @nid: NID to read the AMP value
|
||||
* @ch: channel (left=0 or right=1)
|
||||
* @direction: #HDA_INPUT or #HDA_OUTPUT
|
||||
* @idx: the index value (only for input direction)
|
||||
* @mask: bit mask to set
|
||||
* @val: the bits value to set
|
||||
*
|
||||
* Update the AMP value with a bit mask.
|
||||
* Returns 0 if the value is unchanged, 1 if changed, or a negative error.
|
||||
*/
|
||||
static inline int
|
||||
snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid,
|
||||
int ch, int dir, int idx, int mask, int val)
|
||||
{
|
||||
unsigned int cmd = snd_hdac_regmap_encode_amp(nid, ch, dir, idx);
|
||||
|
||||
return snd_hdac_regmap_update_raw(codec, cmd, mask, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_get_amp_stereo - Read stereo AMP values
|
||||
* @codec: HD-audio codec
|
||||
* @nid: NID to read the AMP value
|
||||
* @ch: channel (left=0 or right=1)
|
||||
* @direction: #HDA_INPUT or #HDA_OUTPUT
|
||||
* @index: the index value (only for input direction)
|
||||
* @val: the pointer to store the value
|
||||
*
|
||||
* Read stereo AMP values. The lower byte is left, the upper byte is right.
|
||||
* Returns the value or a negative error.
|
||||
*/
|
||||
static inline int
|
||||
snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid,
|
||||
int dir, int idx)
|
||||
{
|
||||
unsigned int cmd = snd_hdac_regmap_encode_amp_stereo(nid, dir, idx);
|
||||
int err, val;
|
||||
|
||||
err = snd_hdac_regmap_read_raw(codec, cmd, &val);
|
||||
return err < 0 ? err : val;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_update_amp_stereo - update the stereo AMP value
|
||||
* @codec: HD-audio codec
|
||||
* @nid: NID to read the AMP value
|
||||
* @direction: #HDA_INPUT or #HDA_OUTPUT
|
||||
* @idx: the index value (only for input direction)
|
||||
* @mask: bit mask to set
|
||||
* @val: the bits value to set
|
||||
*
|
||||
* Update the stereo AMP value with a bit mask.
|
||||
* The lower byte is left, the upper byte is right.
|
||||
* Returns 0 if the value is unchanged, 1 if changed, or a negative error.
|
||||
*/
|
||||
static inline int
|
||||
snd_hdac_regmap_update_amp_stereo(struct hdac_device *codec, hda_nid_t nid,
|
||||
int dir, int idx, int mask, int val)
|
||||
{
|
||||
unsigned int cmd = snd_hdac_regmap_encode_amp_stereo(nid, dir, idx);
|
||||
|
||||
return snd_hdac_regmap_update_raw(codec, cmd, mask, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_sync_node - sync the widget node attributes
|
||||
* @codec: HD-audio codec
|
||||
* @nid: NID to sync
|
||||
*/
|
||||
static inline void
|
||||
snd_hdac_regmap_sync_node(struct hdac_device *codec, hda_nid_t nid)
|
||||
{
|
||||
regcache_mark_dirty(codec->regmap);
|
||||
regcache_sync_region(codec->regmap, nid << 20, ((nid + 1) << 20) - 1);
|
||||
}
|
||||
|
||||
#endif /* __SOUND_HDA_REGMAP_H */
|
271
include/sound/hdaudio.h
Normal file
271
include/sound/hdaudio.h
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* HD-audio core stuff
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_HDAUDIO_H
|
||||
#define __SOUND_HDAUDIO_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <sound/hda_verbs.h>
|
||||
|
||||
/* codec node id */
|
||||
typedef u16 hda_nid_t;
|
||||
|
||||
struct hdac_bus;
|
||||
struct hdac_device;
|
||||
struct hdac_driver;
|
||||
struct hdac_widget_tree;
|
||||
|
||||
/*
|
||||
* exported bus type
|
||||
*/
|
||||
extern struct bus_type snd_hda_bus_type;
|
||||
|
||||
/*
|
||||
* generic arrays
|
||||
*/
|
||||
struct snd_array {
|
||||
unsigned int used;
|
||||
unsigned int alloced;
|
||||
unsigned int elem_size;
|
||||
unsigned int alloc_align;
|
||||
void *list;
|
||||
};
|
||||
|
||||
/*
|
||||
* HD-audio codec base device
|
||||
*/
|
||||
struct hdac_device {
|
||||
struct device dev;
|
||||
int type;
|
||||
struct hdac_bus *bus;
|
||||
unsigned int addr; /* codec address */
|
||||
struct list_head list; /* list point for bus codec_list */
|
||||
|
||||
hda_nid_t afg; /* AFG node id */
|
||||
hda_nid_t mfg; /* MFG node id */
|
||||
|
||||
/* ids */
|
||||
unsigned int vendor_id;
|
||||
unsigned int subsystem_id;
|
||||
unsigned int revision_id;
|
||||
unsigned int afg_function_id;
|
||||
unsigned int mfg_function_id;
|
||||
unsigned int afg_unsol:1;
|
||||
unsigned int mfg_unsol:1;
|
||||
|
||||
unsigned int power_caps; /* FG power caps */
|
||||
|
||||
const char *vendor_name; /* codec vendor name */
|
||||
const char *chip_name; /* codec chip name */
|
||||
|
||||
/* verb exec op override */
|
||||
int (*exec_verb)(struct hdac_device *dev, unsigned int cmd,
|
||||
unsigned int flags, unsigned int *res);
|
||||
|
||||
/* widgets */
|
||||
unsigned int num_nodes;
|
||||
hda_nid_t start_nid, end_nid;
|
||||
|
||||
/* misc flags */
|
||||
atomic_t in_pm; /* suspend/resume being performed */
|
||||
|
||||
/* sysfs */
|
||||
struct hdac_widget_tree *widgets;
|
||||
|
||||
/* regmap */
|
||||
struct regmap *regmap;
|
||||
struct snd_array vendor_verbs;
|
||||
bool lazy_cache:1; /* don't wake up for writes */
|
||||
bool caps_overwriting:1; /* caps overwrite being in process */
|
||||
bool cache_coef:1; /* cache COEF read/write too */
|
||||
};
|
||||
|
||||
/* device/driver type used for matching */
|
||||
enum {
|
||||
HDA_DEV_CORE,
|
||||
HDA_DEV_LEGACY,
|
||||
};
|
||||
|
||||
/* direction */
|
||||
enum {
|
||||
HDA_INPUT, HDA_OUTPUT
|
||||
};
|
||||
|
||||
#define dev_to_hdac_dev(_dev) container_of(_dev, struct hdac_device, dev)
|
||||
|
||||
int snd_hdac_device_init(struct hdac_device *dev, struct hdac_bus *bus,
|
||||
const char *name, unsigned int addr);
|
||||
void snd_hdac_device_exit(struct hdac_device *dev);
|
||||
int snd_hdac_device_register(struct hdac_device *codec);
|
||||
void snd_hdac_device_unregister(struct hdac_device *codec);
|
||||
|
||||
int snd_hdac_refresh_widgets(struct hdac_device *codec);
|
||||
|
||||
unsigned int snd_hdac_make_cmd(struct hdac_device *codec, hda_nid_t nid,
|
||||
unsigned int verb, unsigned int parm);
|
||||
int snd_hdac_exec_verb(struct hdac_device *codec, unsigned int cmd,
|
||||
unsigned int flags, unsigned int *res);
|
||||
int snd_hdac_read(struct hdac_device *codec, hda_nid_t nid,
|
||||
unsigned int verb, unsigned int parm, unsigned int *res);
|
||||
int _snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid, int parm,
|
||||
unsigned int *res);
|
||||
int snd_hdac_read_parm_uncached(struct hdac_device *codec, hda_nid_t nid,
|
||||
int parm);
|
||||
int snd_hdac_override_parm(struct hdac_device *codec, hda_nid_t nid,
|
||||
unsigned int parm, unsigned int val);
|
||||
int snd_hdac_get_connections(struct hdac_device *codec, hda_nid_t nid,
|
||||
hda_nid_t *conn_list, int max_conns);
|
||||
int snd_hdac_get_sub_nodes(struct hdac_device *codec, hda_nid_t nid,
|
||||
hda_nid_t *start_id);
|
||||
|
||||
/**
|
||||
* snd_hdac_read_parm - read a codec parameter
|
||||
* @codec: the codec object
|
||||
* @nid: NID to read a parameter
|
||||
* @parm: parameter to read
|
||||
*
|
||||
* Returns -1 for error. If you need to distinguish the error more
|
||||
* strictly, use _snd_hdac_read_parm() directly.
|
||||
*/
|
||||
static inline int snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid,
|
||||
int parm)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
return _snd_hdac_read_parm(codec, nid, parm, &val) < 0 ? -1 : val;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
void snd_hdac_power_up(struct hdac_device *codec);
|
||||
void snd_hdac_power_down(struct hdac_device *codec);
|
||||
#else
|
||||
static inline void snd_hdac_power_up(struct hdac_device *codec) {}
|
||||
static inline void snd_hdac_power_down(struct hdac_device *codec) {}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* snd_hdac_power_up_pm - power up the codec
|
||||
* @codec: the codec object
|
||||
*
|
||||
* This function can be called in a recursive code path like init code
|
||||
* which may be called by PM suspend/resume again. OTOH, if a power-up
|
||||
* call must wake up the sleeper (e.g. in a kctl callback), use
|
||||
* snd_hdac_power_up() instead.
|
||||
*/
|
||||
static inline void snd_hdac_power_up_pm(struct hdac_device *codec)
|
||||
{
|
||||
if (!atomic_read(&codec->in_pm))
|
||||
snd_hdac_power_up(codec);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_power_down_pm - power down the codec
|
||||
* @codec: the codec object
|
||||
*
|
||||
* Like snd_hdac_power_up_pm(), this function is used in a recursive
|
||||
* code path like init code which may be called by PM suspend/resume again.
|
||||
*/
|
||||
static inline void snd_hdac_power_down_pm(struct hdac_device *codec)
|
||||
{
|
||||
if (!atomic_read(&codec->in_pm))
|
||||
snd_hdac_power_down(codec);
|
||||
}
|
||||
|
||||
/*
|
||||
* HD-audio codec base driver
|
||||
*/
|
||||
struct hdac_driver {
|
||||
struct device_driver driver;
|
||||
int type;
|
||||
int (*match)(struct hdac_device *dev, struct hdac_driver *drv);
|
||||
void (*unsol_event)(struct hdac_device *dev, unsigned int event);
|
||||
};
|
||||
|
||||
#define drv_to_hdac_driver(_drv) container_of(_drv, struct hdac_driver, driver)
|
||||
|
||||
/*
|
||||
* HD-audio bus base driver
|
||||
*/
|
||||
struct hdac_bus_ops {
|
||||
/* send a single command */
|
||||
int (*command)(struct hdac_bus *bus, unsigned int cmd);
|
||||
/* get a response from the last command */
|
||||
int (*get_response)(struct hdac_bus *bus, unsigned int addr,
|
||||
unsigned int *res);
|
||||
};
|
||||
|
||||
#define HDA_UNSOL_QUEUE_SIZE 64
|
||||
|
||||
struct hdac_bus {
|
||||
struct device *dev;
|
||||
const struct hdac_bus_ops *ops;
|
||||
|
||||
/* codec linked list */
|
||||
struct list_head codec_list;
|
||||
unsigned int num_codecs;
|
||||
|
||||
/* link caddr -> codec */
|
||||
struct hdac_device *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1];
|
||||
|
||||
/* unsolicited event queue */
|
||||
u32 unsol_queue[HDA_UNSOL_QUEUE_SIZE * 2]; /* ring buffer */
|
||||
unsigned int unsol_rp, unsol_wp;
|
||||
struct work_struct unsol_work;
|
||||
|
||||
/* bit flags of powered codecs */
|
||||
unsigned long codec_powered;
|
||||
|
||||
/* flags */
|
||||
bool sync_write:1; /* sync after verb write */
|
||||
|
||||
/* locks */
|
||||
struct mutex cmd_mutex;
|
||||
};
|
||||
|
||||
int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev,
|
||||
const struct hdac_bus_ops *ops);
|
||||
void snd_hdac_bus_exit(struct hdac_bus *bus);
|
||||
int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr,
|
||||
unsigned int cmd, unsigned int *res);
|
||||
int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr,
|
||||
unsigned int cmd, unsigned int *res);
|
||||
void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex);
|
||||
|
||||
int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec);
|
||||
void snd_hdac_bus_remove_device(struct hdac_bus *bus,
|
||||
struct hdac_device *codec);
|
||||
|
||||
static inline void snd_hdac_codec_link_up(struct hdac_device *codec)
|
||||
{
|
||||
set_bit(codec->addr, &codec->bus->codec_powered);
|
||||
}
|
||||
|
||||
static inline void snd_hdac_codec_link_down(struct hdac_device *codec)
|
||||
{
|
||||
clear_bit(codec->addr, &codec->bus->codec_powered);
|
||||
}
|
||||
|
||||
/*
|
||||
* generic array helpers
|
||||
*/
|
||||
void *snd_array_new(struct snd_array *array);
|
||||
void snd_array_free(struct snd_array *array);
|
||||
static inline void snd_array_init(struct snd_array *array, unsigned int size,
|
||||
unsigned int align)
|
||||
{
|
||||
array->elem_size = size;
|
||||
array->alloc_align = align;
|
||||
}
|
||||
|
||||
static inline void *snd_array_elem(struct snd_array *array, unsigned int idx)
|
||||
{
|
||||
return array->list + idx * array->elem_size;
|
||||
}
|
||||
|
||||
static inline unsigned int snd_array_index(struct snd_array *array, void *ptr)
|
||||
{
|
||||
return (unsigned long)(ptr - array->list) / array->elem_size;
|
||||
}
|
||||
|
||||
#endif /* __SOUND_HDAUDIO_H */
|
@ -60,6 +60,9 @@ struct snd_pcm_hardware {
|
||||
|
||||
struct snd_pcm_substream;
|
||||
|
||||
struct snd_pcm_audio_tstamp_config; /* definitions further down */
|
||||
struct snd_pcm_audio_tstamp_report;
|
||||
|
||||
struct snd_pcm_ops {
|
||||
int (*open)(struct snd_pcm_substream *substream);
|
||||
int (*close)(struct snd_pcm_substream *substream);
|
||||
@ -71,8 +74,10 @@ struct snd_pcm_ops {
|
||||
int (*prepare)(struct snd_pcm_substream *substream);
|
||||
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
|
||||
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
|
||||
int (*wall_clock)(struct snd_pcm_substream *substream,
|
||||
struct timespec *audio_ts);
|
||||
int (*get_time_info)(struct snd_pcm_substream *substream,
|
||||
struct timespec *system_ts, struct timespec *audio_ts,
|
||||
struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
|
||||
struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
|
||||
int (*copy)(struct snd_pcm_substream *substream, int channel,
|
||||
snd_pcm_uframes_t pos,
|
||||
void __user *buf, snd_pcm_uframes_t count);
|
||||
@ -281,6 +286,58 @@ struct snd_pcm_hw_constraint_ranges {
|
||||
|
||||
struct snd_pcm_hwptr_log;
|
||||
|
||||
/*
|
||||
* userspace-provided audio timestamp config to kernel,
|
||||
* structure is for internal use only and filled with dedicated unpack routine
|
||||
*/
|
||||
struct snd_pcm_audio_tstamp_config {
|
||||
/* 5 of max 16 bits used */
|
||||
u32 type_requested:4;
|
||||
u32 report_delay:1; /* add total delay to A/D or D/A */
|
||||
};
|
||||
|
||||
static inline void snd_pcm_unpack_audio_tstamp_config(__u32 data,
|
||||
struct snd_pcm_audio_tstamp_config *config)
|
||||
{
|
||||
config->type_requested = data & 0xF;
|
||||
config->report_delay = (data >> 4) & 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* kernel-provided audio timestamp report to user-space
|
||||
* structure is for internal use only and read by dedicated pack routine
|
||||
*/
|
||||
struct snd_pcm_audio_tstamp_report {
|
||||
/* 6 of max 16 bits used for bit-fields */
|
||||
|
||||
/* for backwards compatibility */
|
||||
u32 valid:1;
|
||||
|
||||
/* actual type if hardware could not support requested timestamp */
|
||||
u32 actual_type:4;
|
||||
|
||||
/* accuracy represented in ns units */
|
||||
u32 accuracy_report:1; /* 0 if accuracy unknown, 1 if accuracy field is valid */
|
||||
u32 accuracy; /* up to 4.29s, will be packed in separate field */
|
||||
};
|
||||
|
||||
static inline void snd_pcm_pack_audio_tstamp_report(__u32 *data, __u32 *accuracy,
|
||||
const struct snd_pcm_audio_tstamp_report *report)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
tmp = report->accuracy_report;
|
||||
tmp <<= 4;
|
||||
tmp |= report->actual_type;
|
||||
tmp <<= 1;
|
||||
tmp |= report->valid;
|
||||
|
||||
*data &= 0xffff; /* zero-clear MSBs */
|
||||
*data |= (tmp << 16);
|
||||
*accuracy = report->accuracy;
|
||||
}
|
||||
|
||||
|
||||
struct snd_pcm_runtime {
|
||||
/* -- Status -- */
|
||||
struct snd_pcm_substream *trigger_master;
|
||||
@ -361,6 +418,11 @@ struct snd_pcm_runtime {
|
||||
|
||||
struct snd_dma_buffer *dma_buffer_p; /* allocated buffer */
|
||||
|
||||
/* -- audio timestamp config -- */
|
||||
struct snd_pcm_audio_tstamp_config audio_tstamp_config;
|
||||
struct snd_pcm_audio_tstamp_report audio_tstamp_report;
|
||||
struct timespec driver_tstamp;
|
||||
|
||||
#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
|
||||
/* -- OSS things -- */
|
||||
struct snd_pcm_oss_runtime oss;
|
||||
|
@ -366,4 +366,11 @@ static inline int params_physical_width(const struct snd_pcm_hw_params *p)
|
||||
return snd_pcm_format_physical_width(params_format(p));
|
||||
}
|
||||
|
||||
static inline void
|
||||
params_set_format(struct snd_pcm_hw_params *p, snd_pcm_format_t fmt)
|
||||
{
|
||||
snd_mask_set(hw_param_mask(p, SNDRV_PCM_HW_PARAM_FORMAT),
|
||||
(__force int)fmt);
|
||||
}
|
||||
|
||||
#endif /* __SOUND_PCM_PARAMS_H */
|
||||
|
@ -25,29 +25,26 @@
|
||||
* registered device information
|
||||
*/
|
||||
|
||||
#define ID_LEN 32
|
||||
|
||||
/* status flag */
|
||||
#define SNDRV_SEQ_DEVICE_FREE 0
|
||||
#define SNDRV_SEQ_DEVICE_REGISTERED 1
|
||||
|
||||
struct snd_seq_device {
|
||||
/* device info */
|
||||
struct snd_card *card; /* sound card */
|
||||
int device; /* device number */
|
||||
char id[ID_LEN]; /* driver id */
|
||||
const char *id; /* driver id */
|
||||
char name[80]; /* device name */
|
||||
int argsize; /* size of the argument */
|
||||
void *driver_data; /* private data for driver */
|
||||
int status; /* flag - read only */
|
||||
void *private_data; /* private data for the caller */
|
||||
void (*private_free)(struct snd_seq_device *device);
|
||||
struct list_head list; /* link to next device */
|
||||
struct device dev;
|
||||
};
|
||||
|
||||
#define to_seq_dev(_dev) \
|
||||
container_of(_dev, struct snd_seq_device, dev)
|
||||
|
||||
/* sequencer driver */
|
||||
|
||||
/* driver operators
|
||||
* init_device:
|
||||
* probe:
|
||||
* Initialize the device with given parameters.
|
||||
* Typically,
|
||||
* 1. call snd_hwdep_new
|
||||
@ -55,25 +52,40 @@ struct snd_seq_device {
|
||||
* 3. call snd_hwdep_register
|
||||
* 4. store the instance to dev->driver_data pointer.
|
||||
*
|
||||
* free_device:
|
||||
* remove:
|
||||
* Release the private data.
|
||||
* Typically, call snd_device_free(dev->card, dev->driver_data)
|
||||
*/
|
||||
struct snd_seq_dev_ops {
|
||||
int (*init_device)(struct snd_seq_device *dev);
|
||||
int (*free_device)(struct snd_seq_device *dev);
|
||||
struct snd_seq_driver {
|
||||
struct device_driver driver;
|
||||
char *id;
|
||||
int argsize;
|
||||
};
|
||||
|
||||
#define to_seq_drv(_drv) \
|
||||
container_of(_drv, struct snd_seq_driver, driver)
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
#ifdef CONFIG_MODULES
|
||||
void snd_seq_device_load_drivers(void);
|
||||
int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize, struct snd_seq_device **result);
|
||||
int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry, int argsize);
|
||||
int snd_seq_device_unregister_driver(char *id);
|
||||
#else
|
||||
#define snd_seq_device_load_drivers()
|
||||
#endif
|
||||
int snd_seq_device_new(struct snd_card *card, int device, const char *id,
|
||||
int argsize, struct snd_seq_device **result);
|
||||
|
||||
#define SNDRV_SEQ_DEVICE_ARGPTR(dev) (void *)((char *)(dev) + sizeof(struct snd_seq_device))
|
||||
|
||||
int __must_check __snd_seq_driver_register(struct snd_seq_driver *drv,
|
||||
struct module *mod);
|
||||
#define snd_seq_driver_register(drv) \
|
||||
__snd_seq_driver_register(drv, THIS_MODULE)
|
||||
void snd_seq_driver_unregister(struct snd_seq_driver *drv);
|
||||
|
||||
#define module_snd_seq_driver(drv) \
|
||||
module_driver(drv, snd_seq_driver_register, snd_seq_driver_unregister)
|
||||
|
||||
/*
|
||||
* id strings for generic devices
|
||||
|
@ -99,13 +99,9 @@ int snd_seq_event_port_attach(int client, struct snd_seq_port_callback *pcbp,
|
||||
int snd_seq_event_port_detach(int client, int port);
|
||||
|
||||
#ifdef CONFIG_MODULES
|
||||
void snd_seq_autoload_lock(void);
|
||||
void snd_seq_autoload_unlock(void);
|
||||
void snd_seq_autoload_init(void);
|
||||
#define snd_seq_autoload_exit() snd_seq_autoload_lock()
|
||||
void snd_seq_autoload_exit(void);
|
||||
#else
|
||||
#define snd_seq_autoload_lock()
|
||||
#define snd_seq_autoload_unlock()
|
||||
#define snd_seq_autoload_init()
|
||||
#define snd_seq_autoload_exit()
|
||||
#endif
|
||||
|
@ -450,8 +450,10 @@ int soc_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai);
|
||||
|
||||
/* Jack reporting */
|
||||
int snd_soc_jack_new(struct snd_soc_codec *codec, const char *id, int type,
|
||||
struct snd_soc_jack *jack);
|
||||
int snd_soc_card_jack_new(struct snd_soc_card *card, const char *id, int type,
|
||||
struct snd_soc_jack *jack, struct snd_soc_jack_pin *pins,
|
||||
unsigned int num_pins);
|
||||
|
||||
void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask);
|
||||
int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count,
|
||||
struct snd_soc_jack_pin *pins);
|
||||
@ -659,7 +661,7 @@ struct snd_soc_jack_gpio {
|
||||
struct snd_soc_jack {
|
||||
struct mutex mutex;
|
||||
struct snd_jack *jack;
|
||||
struct snd_soc_codec *codec;
|
||||
struct snd_soc_card *card;
|
||||
struct list_head pins;
|
||||
int status;
|
||||
struct blocking_notifier_head notifier;
|
||||
@ -954,6 +956,9 @@ struct snd_soc_dai_link {
|
||||
unsigned int symmetric_channels:1;
|
||||
unsigned int symmetric_samplebits:1;
|
||||
|
||||
/* Mark this pcm with non atomic ops */
|
||||
bool nonatomic;
|
||||
|
||||
/* Do not create a PCM for this DAI link (Backend link) */
|
||||
unsigned int no_pcm:1;
|
||||
|
||||
@ -1071,11 +1076,16 @@ struct snd_soc_card {
|
||||
|
||||
/*
|
||||
* Card-specific routes and widgets.
|
||||
* Note: of_dapm_xxx for Device Tree; Otherwise for driver build-in.
|
||||
*/
|
||||
const struct snd_soc_dapm_widget *dapm_widgets;
|
||||
int num_dapm_widgets;
|
||||
const struct snd_soc_dapm_route *dapm_routes;
|
||||
int num_dapm_routes;
|
||||
const struct snd_soc_dapm_widget *of_dapm_widgets;
|
||||
int num_of_dapm_widgets;
|
||||
const struct snd_soc_dapm_route *of_dapm_routes;
|
||||
int num_of_dapm_routes;
|
||||
bool fully_routed;
|
||||
|
||||
struct work_struct deferred_resume_work;
|
||||
@ -1469,7 +1479,7 @@ static inline struct snd_soc_codec *snd_soc_kcontrol_codec(
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_soc_kcontrol_platform() - Returns the platform that registerd the control
|
||||
* snd_soc_kcontrol_platform() - Returns the platform that registered the control
|
||||
* @kcontrol: The control for which to get the platform
|
||||
*
|
||||
* Note: This function will only work correctly if the control has been
|
||||
|
@ -22,6 +22,7 @@
|
||||
#ifndef _UAPI__SOUND_ASEQUENCER_H
|
||||
#define _UAPI__SOUND_ASEQUENCER_H
|
||||
|
||||
#include <sound/asound.h>
|
||||
|
||||
/** version of the sequencer */
|
||||
#define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION (1, 0, 1)
|
||||
|
@ -25,6 +25,9 @@
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#ifndef __KERNEL__
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* protocol version
|
||||
@ -140,7 +143,7 @@ struct snd_hwdep_dsp_image {
|
||||
* *
|
||||
*****************************************************************************/
|
||||
|
||||
#define SNDRV_PCM_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 12)
|
||||
#define SNDRV_PCM_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 13)
|
||||
|
||||
typedef unsigned long snd_pcm_uframes_t;
|
||||
typedef signed long snd_pcm_sframes_t;
|
||||
@ -267,10 +270,17 @@ typedef int __bitwise snd_pcm_subformat_t;
|
||||
#define SNDRV_PCM_INFO_JOINT_DUPLEX 0x00200000 /* playback and capture stream are somewhat correlated */
|
||||
#define SNDRV_PCM_INFO_SYNC_START 0x00400000 /* pcm support some kind of sync go */
|
||||
#define SNDRV_PCM_INFO_NO_PERIOD_WAKEUP 0x00800000 /* period wakeup can be disabled */
|
||||
#define SNDRV_PCM_INFO_HAS_WALL_CLOCK 0x01000000 /* has audio wall clock for audio/system time sync */
|
||||
#define SNDRV_PCM_INFO_HAS_WALL_CLOCK 0x01000000 /* (Deprecated)has audio wall clock for audio/system time sync */
|
||||
#define SNDRV_PCM_INFO_HAS_LINK_ATIME 0x01000000 /* report hardware link audio time, reset on startup */
|
||||
#define SNDRV_PCM_INFO_HAS_LINK_ABSOLUTE_ATIME 0x02000000 /* report absolute hardware link audio time, not reset on startup */
|
||||
#define SNDRV_PCM_INFO_HAS_LINK_ESTIMATED_ATIME 0x04000000 /* report estimated link audio time */
|
||||
#define SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME 0x08000000 /* report synchronized audio/system time */
|
||||
|
||||
#define SNDRV_PCM_INFO_DRAIN_TRIGGER 0x40000000 /* internal kernel flag - trigger in drain */
|
||||
#define SNDRV_PCM_INFO_FIFO_IN_FRAMES 0x80000000 /* internal kernel flag - FIFO size is in frames */
|
||||
|
||||
|
||||
|
||||
typedef int __bitwise snd_pcm_state_t;
|
||||
#define SNDRV_PCM_STATE_OPEN ((__force snd_pcm_state_t) 0) /* stream is open */
|
||||
#define SNDRV_PCM_STATE_SETUP ((__force snd_pcm_state_t) 1) /* stream has a setup */
|
||||
@ -408,6 +418,22 @@ struct snd_pcm_channel_info {
|
||||
unsigned int step; /* samples distance in bits */
|
||||
};
|
||||
|
||||
enum {
|
||||
/*
|
||||
* first definition for backwards compatibility only,
|
||||
* maps to wallclock/link time for HDAudio playback and DEFAULT/DMA time for everything else
|
||||
*/
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_COMPAT = 0,
|
||||
|
||||
/* timestamp definitions */
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT = 1, /* DMA time, reported as per hw_ptr */
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK = 2, /* link time reported by sample or wallclock counter, reset on startup */
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_ABSOLUTE = 3, /* link time reported by sample or wallclock counter, not reset on startup */
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_ESTIMATED = 4, /* link time estimated indirectly */
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_SYNCHRONIZED = 5, /* link time synchronized with system time */
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_LAST = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_SYNCHRONIZED
|
||||
};
|
||||
|
||||
struct snd_pcm_status {
|
||||
snd_pcm_state_t state; /* stream state */
|
||||
struct timespec trigger_tstamp; /* time when stream was started/stopped/paused */
|
||||
@ -419,9 +445,11 @@ struct snd_pcm_status {
|
||||
snd_pcm_uframes_t avail_max; /* max frames available on hw since last status */
|
||||
snd_pcm_uframes_t overrange; /* count of ADC (capture) overrange detections from last status */
|
||||
snd_pcm_state_t suspended_state; /* suspended stream state */
|
||||
__u32 reserved_alignment; /* must be filled with zero */
|
||||
struct timespec audio_tstamp; /* from sample counter or wall clock */
|
||||
unsigned char reserved[56-sizeof(struct timespec)]; /* must be filled with zero */
|
||||
__u32 audio_tstamp_data; /* needed for 64-bit alignment, used for configs/report to/from userspace */
|
||||
struct timespec audio_tstamp; /* sample counter, wall clock, PHC or on-demand sync'ed */
|
||||
struct timespec driver_tstamp; /* useful in case reference system tstamp is reported with delay */
|
||||
__u32 audio_tstamp_accuracy; /* in ns units, only valid if indicated in audio_tstamp_data */
|
||||
unsigned char reserved[52-2*sizeof(struct timespec)]; /* must be filled with zero */
|
||||
};
|
||||
|
||||
struct snd_pcm_mmap_status {
|
||||
@ -534,6 +562,7 @@ enum {
|
||||
#define SNDRV_PCM_IOCTL_DELAY _IOR('A', 0x21, snd_pcm_sframes_t)
|
||||
#define SNDRV_PCM_IOCTL_HWSYNC _IO('A', 0x22)
|
||||
#define SNDRV_PCM_IOCTL_SYNC_PTR _IOWR('A', 0x23, struct snd_pcm_sync_ptr)
|
||||
#define SNDRV_PCM_IOCTL_STATUS_EXT _IOWR('A', 0x24, struct snd_pcm_status)
|
||||
#define SNDRV_PCM_IOCTL_CHANNEL_INFO _IOR('A', 0x32, struct snd_pcm_channel_info)
|
||||
#define SNDRV_PCM_IOCTL_PREPARE _IO('A', 0x40)
|
||||
#define SNDRV_PCM_IOCTL_RESET _IO('A', 0x41)
|
||||
|
@ -75,7 +75,7 @@ struct snd_compr_tstamp {
|
||||
/**
|
||||
* struct snd_compr_avail - avail descriptor
|
||||
* @avail: Number of bytes available in ring buffer for writing/reading
|
||||
* @tstamp: timestamp infomation
|
||||
* @tstamp: timestamp information
|
||||
*/
|
||||
struct snd_compr_avail {
|
||||
__u64 avail;
|
||||
|
@ -23,8 +23,7 @@
|
||||
#define _UAPI__SOUND_EMU10K1_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
|
||||
#include <sound/asound.h>
|
||||
|
||||
/*
|
||||
* ---- FX8010 ----
|
||||
|
@ -20,6 +20,12 @@
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifdef __KERNEL__
|
||||
#include <linux/types.h>
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* Maximum channels is 64 even on 56Mode you have 64playbacks to matrix */
|
||||
#define HDSPM_MAX_CHANNELS 64
|
||||
|
||||
|
@ -76,6 +76,8 @@ source "sound/isa/Kconfig"
|
||||
|
||||
source "sound/pci/Kconfig"
|
||||
|
||||
source "sound/hda/Kconfig"
|
||||
|
||||
source "sound/ppc/Kconfig"
|
||||
|
||||
source "sound/aoa/Kconfig"
|
||||
|
@ -6,7 +6,7 @@ obj-$(CONFIG_SOUND_PRIME) += sound_firmware.o
|
||||
obj-$(CONFIG_SOUND_PRIME) += oss/
|
||||
obj-$(CONFIG_DMASOUND) += oss/
|
||||
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
|
||||
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/
|
||||
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/
|
||||
obj-$(CONFIG_SND_AOA) += aoa/
|
||||
|
||||
# This one must be compilable even if sound is configured out
|
||||
|
@ -31,7 +31,7 @@ module_param(force, int, 0444);
|
||||
MODULE_PARM_DESC(force, "Force loading i2sbus even when"
|
||||
" no layout-id property is present");
|
||||
|
||||
static struct of_device_id i2sbus_match[] = {
|
||||
static const struct of_device_id i2sbus_match[] = {
|
||||
{ .name = "i2s" },
|
||||
{ }
|
||||
};
|
||||
|
@ -192,36 +192,41 @@ void snd_ctl_notify(struct snd_card *card, unsigned int mask,
|
||||
EXPORT_SYMBOL(snd_ctl_notify);
|
||||
|
||||
/**
|
||||
* snd_ctl_new - create a control instance from the template
|
||||
* @control: the control template
|
||||
* @access: the default control access
|
||||
* snd_ctl_new - create a new control instance with some elements
|
||||
* @kctl: the pointer to store new control instance
|
||||
* @count: the number of elements in this control
|
||||
* @access: the default access flags for elements in this control
|
||||
* @file: given when locking these elements
|
||||
*
|
||||
* Allocates a new struct snd_kcontrol instance and copies the given template
|
||||
* to the new instance. It does not copy volatile data (access).
|
||||
* Allocates a memory object for a new control instance. The instance has
|
||||
* elements as many as the given number (@count). Each element has given
|
||||
* access permissions (@access). Each element is locked when @file is given.
|
||||
*
|
||||
* Return: The pointer of the new instance, or %NULL on failure.
|
||||
* Return: 0 on success, error code on failure
|
||||
*/
|
||||
static struct snd_kcontrol *snd_ctl_new(struct snd_kcontrol *control,
|
||||
unsigned int access)
|
||||
static int snd_ctl_new(struct snd_kcontrol **kctl, unsigned int count,
|
||||
unsigned int access, struct snd_ctl_file *file)
|
||||
{
|
||||
struct snd_kcontrol *kctl;
|
||||
unsigned int size;
|
||||
unsigned int idx;
|
||||
|
||||
if (snd_BUG_ON(!control || !control->count))
|
||||
return NULL;
|
||||
if (count == 0 || count > MAX_CONTROL_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
if (control->count > MAX_CONTROL_COUNT)
|
||||
return NULL;
|
||||
size = sizeof(struct snd_kcontrol);
|
||||
size += sizeof(struct snd_kcontrol_volatile) * count;
|
||||
|
||||
kctl = kzalloc(sizeof(*kctl) + sizeof(struct snd_kcontrol_volatile) * control->count, GFP_KERNEL);
|
||||
if (kctl == NULL) {
|
||||
pr_err("ALSA: Cannot allocate control instance\n");
|
||||
return NULL;
|
||||
*kctl = kzalloc(size, GFP_KERNEL);
|
||||
if (!*kctl)
|
||||
return -ENOMEM;
|
||||
|
||||
for (idx = 0; idx < count; idx++) {
|
||||
(*kctl)->vd[idx].access = access;
|
||||
(*kctl)->vd[idx].owner = file;
|
||||
}
|
||||
*kctl = *control;
|
||||
for (idx = 0; idx < kctl->count; idx++)
|
||||
kctl->vd[idx].access = access;
|
||||
return kctl;
|
||||
(*kctl)->count = count;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,37 +243,53 @@ static struct snd_kcontrol *snd_ctl_new(struct snd_kcontrol *control,
|
||||
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
|
||||
void *private_data)
|
||||
{
|
||||
struct snd_kcontrol kctl;
|
||||
struct snd_kcontrol *kctl;
|
||||
unsigned int count;
|
||||
unsigned int access;
|
||||
int err;
|
||||
|
||||
if (snd_BUG_ON(!ncontrol || !ncontrol->info))
|
||||
return NULL;
|
||||
memset(&kctl, 0, sizeof(kctl));
|
||||
kctl.id.iface = ncontrol->iface;
|
||||
kctl.id.device = ncontrol->device;
|
||||
kctl.id.subdevice = ncontrol->subdevice;
|
||||
|
||||
count = ncontrol->count;
|
||||
if (count == 0)
|
||||
count = 1;
|
||||
|
||||
access = ncontrol->access;
|
||||
if (access == 0)
|
||||
access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
||||
access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
SNDRV_CTL_ELEM_ACCESS_VOLATILE |
|
||||
SNDRV_CTL_ELEM_ACCESS_INACTIVE |
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE |
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK);
|
||||
|
||||
err = snd_ctl_new(&kctl, count, access, NULL);
|
||||
if (err < 0)
|
||||
return NULL;
|
||||
|
||||
/* The 'numid' member is decided when calling snd_ctl_add(). */
|
||||
kctl->id.iface = ncontrol->iface;
|
||||
kctl->id.device = ncontrol->device;
|
||||
kctl->id.subdevice = ncontrol->subdevice;
|
||||
if (ncontrol->name) {
|
||||
strlcpy(kctl.id.name, ncontrol->name, sizeof(kctl.id.name));
|
||||
if (strcmp(ncontrol->name, kctl.id.name) != 0)
|
||||
strlcpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name));
|
||||
if (strcmp(ncontrol->name, kctl->id.name) != 0)
|
||||
pr_warn("ALSA: Control name '%s' truncated to '%s'\n",
|
||||
ncontrol->name, kctl.id.name);
|
||||
ncontrol->name, kctl->id.name);
|
||||
}
|
||||
kctl.id.index = ncontrol->index;
|
||||
kctl.count = ncontrol->count ? ncontrol->count : 1;
|
||||
access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
|
||||
(ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|
|
||||
SNDRV_CTL_ELEM_ACCESS_VOLATILE|
|
||||
SNDRV_CTL_ELEM_ACCESS_INACTIVE|
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE|
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND|
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK));
|
||||
kctl.info = ncontrol->info;
|
||||
kctl.get = ncontrol->get;
|
||||
kctl.put = ncontrol->put;
|
||||
kctl.tlv.p = ncontrol->tlv.p;
|
||||
kctl.private_value = ncontrol->private_value;
|
||||
kctl.private_data = private_data;
|
||||
return snd_ctl_new(&kctl, access);
|
||||
kctl->id.index = ncontrol->index;
|
||||
|
||||
kctl->info = ncontrol->info;
|
||||
kctl->get = ncontrol->get;
|
||||
kctl->put = ncontrol->put;
|
||||
kctl->tlv.p = ncontrol->tlv.p;
|
||||
|
||||
kctl->private_value = ncontrol->private_value;
|
||||
kctl->private_data = private_data;
|
||||
|
||||
return kctl;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_ctl_new1);
|
||||
|
||||
@ -557,6 +578,7 @@ error:
|
||||
*
|
||||
* Finds the control instance with the given id, and activate or
|
||||
* inactivate the control together with notification, if changed.
|
||||
* The given ID data is filled with full information.
|
||||
*
|
||||
* Return: 0 if unchanged, 1 if changed, or a negative error code on failure.
|
||||
*/
|
||||
@ -586,6 +608,7 @@ int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id,
|
||||
goto unlock;
|
||||
vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
|
||||
}
|
||||
snd_ctl_build_ioff(id, kctl, index_offset);
|
||||
ret = 1;
|
||||
unlock:
|
||||
up_write(&card->controls_rwsem);
|
||||
@ -1017,8 +1040,12 @@ static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
struct user_element *ue = kcontrol->private_data;
|
||||
unsigned int offset;
|
||||
|
||||
offset = snd_ctl_get_ioff(kcontrol, &uinfo->id);
|
||||
*uinfo = ue->info;
|
||||
snd_ctl_build_ioff(&uinfo->id, kcontrol, offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1028,10 +1055,13 @@ static int snd_ctl_elem_user_enum_info(struct snd_kcontrol *kcontrol,
|
||||
struct user_element *ue = kcontrol->private_data;
|
||||
const char *names;
|
||||
unsigned int item;
|
||||
unsigned int offset;
|
||||
|
||||
item = uinfo->value.enumerated.item;
|
||||
|
||||
offset = snd_ctl_get_ioff(kcontrol, &uinfo->id);
|
||||
*uinfo = ue->info;
|
||||
snd_ctl_build_ioff(&uinfo->id, kcontrol, offset);
|
||||
|
||||
item = min(item, uinfo->value.enumerated.items - 1);
|
||||
uinfo->value.enumerated.item = item;
|
||||
@ -1078,7 +1108,7 @@ static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol,
|
||||
int change = 0;
|
||||
void *new_data;
|
||||
|
||||
if (op_flag > 0) {
|
||||
if (op_flag == SNDRV_CTL_TLV_OP_WRITE) {
|
||||
if (size > 1024 * 128) /* sane value */
|
||||
return -EINVAL;
|
||||
|
||||
@ -1161,84 +1191,103 @@ static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol)
|
||||
static int snd_ctl_elem_add(struct snd_ctl_file *file,
|
||||
struct snd_ctl_elem_info *info, int replace)
|
||||
{
|
||||
/* The capacity of struct snd_ctl_elem_value.value.*/
|
||||
static const unsigned int value_sizes[] = {
|
||||
[SNDRV_CTL_ELEM_TYPE_BOOLEAN] = sizeof(long),
|
||||
[SNDRV_CTL_ELEM_TYPE_INTEGER] = sizeof(long),
|
||||
[SNDRV_CTL_ELEM_TYPE_ENUMERATED] = sizeof(unsigned int),
|
||||
[SNDRV_CTL_ELEM_TYPE_BYTES] = sizeof(unsigned char),
|
||||
[SNDRV_CTL_ELEM_TYPE_IEC958] = sizeof(struct snd_aes_iec958),
|
||||
[SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long),
|
||||
};
|
||||
static const unsigned int max_value_counts[] = {
|
||||
[SNDRV_CTL_ELEM_TYPE_BOOLEAN] = 128,
|
||||
[SNDRV_CTL_ELEM_TYPE_INTEGER] = 128,
|
||||
[SNDRV_CTL_ELEM_TYPE_ENUMERATED] = 128,
|
||||
[SNDRV_CTL_ELEM_TYPE_BYTES] = 512,
|
||||
[SNDRV_CTL_ELEM_TYPE_IEC958] = 1,
|
||||
[SNDRV_CTL_ELEM_TYPE_INTEGER64] = 64,
|
||||
};
|
||||
struct snd_card *card = file->card;
|
||||
struct snd_kcontrol kctl, *_kctl;
|
||||
struct snd_kcontrol *kctl;
|
||||
unsigned int count;
|
||||
unsigned int access;
|
||||
long private_size;
|
||||
struct user_element *ue;
|
||||
int idx, err;
|
||||
unsigned int offset;
|
||||
int err;
|
||||
|
||||
if (info->count < 1)
|
||||
return -EINVAL;
|
||||
if (!*info->id.name)
|
||||
return -EINVAL;
|
||||
if (strnlen(info->id.name, sizeof(info->id.name)) >= sizeof(info->id.name))
|
||||
return -EINVAL;
|
||||
access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
|
||||
(info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|
|
||||
SNDRV_CTL_ELEM_ACCESS_INACTIVE|
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE));
|
||||
info->id.numid = 0;
|
||||
memset(&kctl, 0, sizeof(kctl));
|
||||
|
||||
/* Delete a control to replace them if needed. */
|
||||
if (replace) {
|
||||
info->id.numid = 0;
|
||||
err = snd_ctl_remove_user_ctl(file, &info->id);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (card->user_ctl_count >= MAX_USER_CONTROLS)
|
||||
/*
|
||||
* The number of userspace controls are counted control by control,
|
||||
* not element by element.
|
||||
*/
|
||||
if (card->user_ctl_count + 1 > MAX_USER_CONTROLS)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(&kctl.id, &info->id, sizeof(info->id));
|
||||
kctl.count = info->owner ? info->owner : 1;
|
||||
access |= SNDRV_CTL_ELEM_ACCESS_USER;
|
||||
if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED)
|
||||
kctl.info = snd_ctl_elem_user_enum_info;
|
||||
else
|
||||
kctl.info = snd_ctl_elem_user_info;
|
||||
if (access & SNDRV_CTL_ELEM_ACCESS_READ)
|
||||
kctl.get = snd_ctl_elem_user_get;
|
||||
if (access & SNDRV_CTL_ELEM_ACCESS_WRITE)
|
||||
kctl.put = snd_ctl_elem_user_put;
|
||||
if (access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) {
|
||||
kctl.tlv.c = snd_ctl_elem_user_tlv;
|
||||
/* Check the number of elements for this userspace control. */
|
||||
count = info->owner;
|
||||
if (count == 0)
|
||||
count = 1;
|
||||
|
||||
/* Arrange access permissions if needed. */
|
||||
access = info->access;
|
||||
if (access == 0)
|
||||
access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
||||
access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||
SNDRV_CTL_ELEM_ACCESS_INACTIVE |
|
||||
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE);
|
||||
if (access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)
|
||||
access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
|
||||
}
|
||||
switch (info->type) {
|
||||
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER:
|
||||
private_size = sizeof(long);
|
||||
if (info->count > 128)
|
||||
return -EINVAL;
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
|
||||
private_size = sizeof(long long);
|
||||
if (info->count > 64)
|
||||
return -EINVAL;
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
|
||||
private_size = sizeof(unsigned int);
|
||||
if (info->count > 128 || info->value.enumerated.items == 0)
|
||||
return -EINVAL;
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_BYTES:
|
||||
private_size = sizeof(unsigned char);
|
||||
if (info->count > 512)
|
||||
return -EINVAL;
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_IEC958:
|
||||
private_size = sizeof(struct snd_aes_iec958);
|
||||
if (info->count != 1)
|
||||
return -EINVAL;
|
||||
break;
|
||||
default:
|
||||
access |= SNDRV_CTL_ELEM_ACCESS_USER;
|
||||
|
||||
/*
|
||||
* Check information and calculate the size of data specific to
|
||||
* this userspace control.
|
||||
*/
|
||||
if (info->type < SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
|
||||
info->type > SNDRV_CTL_ELEM_TYPE_INTEGER64)
|
||||
return -EINVAL;
|
||||
}
|
||||
private_size *= info->count;
|
||||
ue = kzalloc(sizeof(struct user_element) + private_size, GFP_KERNEL);
|
||||
if (ue == NULL)
|
||||
if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED &&
|
||||
info->value.enumerated.items == 0)
|
||||
return -EINVAL;
|
||||
if (info->count < 1 ||
|
||||
info->count > max_value_counts[info->type])
|
||||
return -EINVAL;
|
||||
private_size = value_sizes[info->type] * info->count;
|
||||
|
||||
/*
|
||||
* Keep memory object for this userspace control. After passing this
|
||||
* code block, the instance should be freed by snd_ctl_free_one().
|
||||
*
|
||||
* Note that these elements in this control are locked.
|
||||
*/
|
||||
err = snd_ctl_new(&kctl, count, access, file);
|
||||
if (err < 0)
|
||||
return err;
|
||||
memcpy(&kctl->id, &info->id, sizeof(kctl->id));
|
||||
kctl->private_data = kzalloc(sizeof(struct user_element) + private_size,
|
||||
GFP_KERNEL);
|
||||
if (kctl->private_data == NULL) {
|
||||
kfree(kctl);
|
||||
return -ENOMEM;
|
||||
}
|
||||
kctl->private_free = snd_ctl_elem_user_free;
|
||||
|
||||
/* Set private data for this userspace control. */
|
||||
ue = (struct user_element *)kctl->private_data;
|
||||
ue->card = card;
|
||||
ue->info = *info;
|
||||
ue->info.access = 0;
|
||||
@ -1247,23 +1296,36 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
|
||||
if (ue->info.type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
|
||||
err = snd_ctl_elem_init_enum_names(ue);
|
||||
if (err < 0) {
|
||||
kfree(ue);
|
||||
snd_ctl_free_one(kctl);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
kctl.private_free = snd_ctl_elem_user_free;
|
||||
_kctl = snd_ctl_new(&kctl, access);
|
||||
if (_kctl == NULL) {
|
||||
kfree(ue->priv_data);
|
||||
kfree(ue);
|
||||
return -ENOMEM;
|
||||
}
|
||||
_kctl->private_data = ue;
|
||||
for (idx = 0; idx < _kctl->count; idx++)
|
||||
_kctl->vd[idx].owner = file;
|
||||
err = snd_ctl_add(card, _kctl);
|
||||
|
||||
/* Set callback functions. */
|
||||
if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED)
|
||||
kctl->info = snd_ctl_elem_user_enum_info;
|
||||
else
|
||||
kctl->info = snd_ctl_elem_user_info;
|
||||
if (access & SNDRV_CTL_ELEM_ACCESS_READ)
|
||||
kctl->get = snd_ctl_elem_user_get;
|
||||
if (access & SNDRV_CTL_ELEM_ACCESS_WRITE)
|
||||
kctl->put = snd_ctl_elem_user_put;
|
||||
if (access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)
|
||||
kctl->tlv.c = snd_ctl_elem_user_tlv;
|
||||
|
||||
/* This function manage to free the instance on failure. */
|
||||
err = snd_ctl_add(card, kctl);
|
||||
if (err < 0)
|
||||
return err;
|
||||
offset = snd_ctl_get_ioff(kctl, &info->id);
|
||||
snd_ctl_build_ioff(&info->id, kctl, offset);
|
||||
/*
|
||||
* Here we cannot fill any field for the number of elements added by
|
||||
* this operation because there're no specific fields. The usage of
|
||||
* 'owner' field for this purpose may cause any bugs to userspace
|
||||
* applications because the field originally means PID of a process
|
||||
* which locks the element.
|
||||
*/
|
||||
|
||||
down_write(&card->controls_rwsem);
|
||||
card->user_ctl_count++;
|
||||
@ -1276,9 +1338,19 @@ static int snd_ctl_elem_add_user(struct snd_ctl_file *file,
|
||||
struct snd_ctl_elem_info __user *_info, int replace)
|
||||
{
|
||||
struct snd_ctl_elem_info info;
|
||||
int err;
|
||||
|
||||
if (copy_from_user(&info, _info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
return snd_ctl_elem_add(file, &info, replace);
|
||||
err = snd_ctl_elem_add(file, &info, replace);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (copy_to_user(_info, &info, sizeof(info))) {
|
||||
snd_ctl_remove_user_ctl(file, &info.id);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_ctl_elem_remove(struct snd_ctl_file *file,
|
||||
@ -1338,9 +1410,12 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,
|
||||
goto __kctl_end;
|
||||
}
|
||||
vd = &kctl->vd[tlv.numid - kctl->id.numid];
|
||||
if ((op_flag == 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) == 0) ||
|
||||
(op_flag > 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) == 0) ||
|
||||
(op_flag < 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND) == 0)) {
|
||||
if ((op_flag == SNDRV_CTL_TLV_OP_READ &&
|
||||
(vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) == 0) ||
|
||||
(op_flag == SNDRV_CTL_TLV_OP_WRITE &&
|
||||
(vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) == 0) ||
|
||||
(op_flag == SNDRV_CTL_TLV_OP_CMD &&
|
||||
(vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND) == 0)) {
|
||||
err = -ENXIO;
|
||||
goto __kctl_end;
|
||||
}
|
||||
@ -1357,7 +1432,7 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (op_flag) {
|
||||
if (op_flag != SNDRV_CTL_TLV_OP_READ) {
|
||||
err = -ENXIO;
|
||||
goto __kctl_end;
|
||||
}
|
||||
|
@ -50,10 +50,8 @@ int snd_device_new(struct snd_card *card, enum snd_device_type type,
|
||||
if (snd_BUG_ON(!card || !device_data || !ops))
|
||||
return -ENXIO;
|
||||
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||||
if (dev == NULL) {
|
||||
dev_err(card->dev, "Cannot allocate device, type=%d\n", type);
|
||||
if (!dev)
|
||||
return -ENOMEM;
|
||||
}
|
||||
INIT_LIST_HEAD(&dev->list);
|
||||
dev->card = card;
|
||||
dev->type = type;
|
||||
@ -73,7 +71,7 @@ int snd_device_new(struct snd_card *card, enum snd_device_type type,
|
||||
}
|
||||
EXPORT_SYMBOL(snd_device_new);
|
||||
|
||||
static int __snd_device_disconnect(struct snd_device *dev)
|
||||
static void __snd_device_disconnect(struct snd_device *dev)
|
||||
{
|
||||
if (dev->state == SNDRV_DEV_REGISTERED) {
|
||||
if (dev->ops->dev_disconnect &&
|
||||
@ -81,7 +79,6 @@ static int __snd_device_disconnect(struct snd_device *dev)
|
||||
dev_err(dev->card->dev, "device disconnect failure\n");
|
||||
dev->state = SNDRV_DEV_DISCONNECTED;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __snd_device_free(struct snd_device *dev)
|
||||
@ -108,6 +105,34 @@ static struct snd_device *look_for_dev(struct snd_card *card, void *device_data)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_device_disconnect - disconnect the device
|
||||
* @card: the card instance
|
||||
* @device_data: the data pointer to disconnect
|
||||
*
|
||||
* Turns the device into the disconnection state, invoking
|
||||
* dev_disconnect callback, if the device was already registered.
|
||||
*
|
||||
* Usually called from snd_card_disconnect().
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure or if the
|
||||
* device not found.
|
||||
*/
|
||||
void snd_device_disconnect(struct snd_card *card, void *device_data)
|
||||
{
|
||||
struct snd_device *dev;
|
||||
|
||||
if (snd_BUG_ON(!card || !device_data))
|
||||
return;
|
||||
dev = look_for_dev(card, device_data);
|
||||
if (dev)
|
||||
__snd_device_disconnect(dev);
|
||||
else
|
||||
dev_dbg(card->dev, "device disconnect %p (from %pF), not found\n",
|
||||
device_data, __builtin_return_address(0));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_device_disconnect);
|
||||
|
||||
/**
|
||||
* snd_device_free - release the device from the card
|
||||
* @card: the card instance
|
||||
@ -195,18 +220,14 @@ int snd_device_register_all(struct snd_card *card)
|
||||
* disconnect all the devices on the card.
|
||||
* called from init.c
|
||||
*/
|
||||
int snd_device_disconnect_all(struct snd_card *card)
|
||||
void snd_device_disconnect_all(struct snd_card *card)
|
||||
{
|
||||
struct snd_device *dev;
|
||||
int err = 0;
|
||||
|
||||
if (snd_BUG_ON(!card))
|
||||
return -ENXIO;
|
||||
list_for_each_entry_reverse(dev, &card->devices, list) {
|
||||
if (__snd_device_disconnect(dev) < 0)
|
||||
err = -ENXIO;
|
||||
}
|
||||
return err;
|
||||
return;
|
||||
list_for_each_entry_reverse(dev, &card->devices, list)
|
||||
__snd_device_disconnect(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -378,10 +378,8 @@ int snd_hwdep_new(struct snd_card *card, char *id, int device,
|
||||
if (rhwdep)
|
||||
*rhwdep = NULL;
|
||||
hwdep = kzalloc(sizeof(*hwdep), GFP_KERNEL);
|
||||
if (hwdep == NULL) {
|
||||
dev_err(card->dev, "hwdep: cannot allocate\n");
|
||||
if (!hwdep)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
init_waitqueue_head(&hwdep->open_wait);
|
||||
mutex_init(&hwdep->open_mutex);
|
||||
|
@ -400,7 +400,6 @@ static const struct file_operations snd_shutdown_f_ops =
|
||||
int snd_card_disconnect(struct snd_card *card)
|
||||
{
|
||||
struct snd_monitor_file *mfile;
|
||||
int err;
|
||||
|
||||
if (!card)
|
||||
return -EINVAL;
|
||||
@ -445,9 +444,7 @@ int snd_card_disconnect(struct snd_card *card)
|
||||
#endif
|
||||
|
||||
/* notify all devices that we are disconnected */
|
||||
err = snd_device_disconnect_all(card);
|
||||
if (err < 0)
|
||||
dev_err(card->dev, "not all devices for card %i can be disconnected\n", card->number);
|
||||
snd_device_disconnect_all(card);
|
||||
|
||||
snd_info_card_disconnect(card);
|
||||
if (card->registered) {
|
||||
|
@ -1212,10 +1212,8 @@ static void snd_mixer_oss_proc_write(struct snd_info_entry *entry,
|
||||
/* not changed */
|
||||
goto __unlock;
|
||||
tbl = kmalloc(sizeof(*tbl), GFP_KERNEL);
|
||||
if (! tbl) {
|
||||
pr_err("ALSA: mixer_oss: no memory\n");
|
||||
if (!tbl)
|
||||
goto __unlock;
|
||||
}
|
||||
tbl->oss_id = ch;
|
||||
tbl->name = kstrdup(str, GFP_KERNEL);
|
||||
if (! tbl->name) {
|
||||
|
@ -854,7 +854,6 @@ static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream)
|
||||
params = kmalloc(sizeof(*params), GFP_KERNEL);
|
||||
sparams = kmalloc(sizeof(*sparams), GFP_KERNEL);
|
||||
if (!sw_params || !params || !sparams) {
|
||||
pcm_dbg(substream->pcm, "No memory\n");
|
||||
err = -ENOMEM;
|
||||
goto failure;
|
||||
}
|
||||
|
105
sound/core/pcm.c
105
sound/core/pcm.c
@ -49,8 +49,6 @@ static struct snd_pcm *snd_pcm_get(struct snd_card *card, int device)
|
||||
struct snd_pcm *pcm;
|
||||
|
||||
list_for_each_entry(pcm, &snd_pcm_devices, list) {
|
||||
if (pcm->internal)
|
||||
continue;
|
||||
if (pcm->card == card && pcm->device == device)
|
||||
return pcm;
|
||||
}
|
||||
@ -62,8 +60,6 @@ static int snd_pcm_next(struct snd_card *card, int device)
|
||||
struct snd_pcm *pcm;
|
||||
|
||||
list_for_each_entry(pcm, &snd_pcm_devices, list) {
|
||||
if (pcm->internal)
|
||||
continue;
|
||||
if (pcm->card == card && pcm->device > device)
|
||||
return pcm->device;
|
||||
else if (pcm->card->number > card->number)
|
||||
@ -76,6 +72,9 @@ static int snd_pcm_add(struct snd_pcm *newpcm)
|
||||
{
|
||||
struct snd_pcm *pcm;
|
||||
|
||||
if (newpcm->internal)
|
||||
return 0;
|
||||
|
||||
list_for_each_entry(pcm, &snd_pcm_devices, list) {
|
||||
if (pcm->card == newpcm->card && pcm->device == newpcm->device)
|
||||
return -EBUSY;
|
||||
@ -344,11 +343,8 @@ static void snd_pcm_proc_info_read(struct snd_pcm_substream *substream,
|
||||
return;
|
||||
|
||||
info = kmalloc(sizeof(*info), GFP_KERNEL);
|
||||
if (! info) {
|
||||
pcm_dbg(substream->pcm,
|
||||
"snd_pcm_proc_info_read: cannot malloc\n");
|
||||
if (!info)
|
||||
return;
|
||||
}
|
||||
|
||||
err = snd_pcm_info(substream, info);
|
||||
if (err < 0) {
|
||||
@ -718,10 +714,8 @@ int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
|
||||
prev = NULL;
|
||||
for (idx = 0, prev = NULL; idx < substream_count; idx++) {
|
||||
substream = kzalloc(sizeof(*substream), GFP_KERNEL);
|
||||
if (substream == NULL) {
|
||||
pcm_err(pcm, "Cannot allocate PCM substream\n");
|
||||
if (!substream)
|
||||
return -ENOMEM;
|
||||
}
|
||||
substream->pcm = pcm;
|
||||
substream->pstr = pstr;
|
||||
substream->number = idx;
|
||||
@ -775,13 +769,14 @@ static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
|
||||
if (rpcm)
|
||||
*rpcm = NULL;
|
||||
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
|
||||
if (pcm == NULL) {
|
||||
dev_err(card->dev, "Cannot allocate PCM\n");
|
||||
if (!pcm)
|
||||
return -ENOMEM;
|
||||
}
|
||||
pcm->card = card;
|
||||
pcm->device = device;
|
||||
pcm->internal = internal;
|
||||
mutex_init(&pcm->open_mutex);
|
||||
init_waitqueue_head(&pcm->open_wait);
|
||||
INIT_LIST_HEAD(&pcm->list);
|
||||
if (id)
|
||||
strlcpy(pcm->id, id, sizeof(pcm->id));
|
||||
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
|
||||
@ -792,8 +787,6 @@ static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
|
||||
snd_pcm_free(pcm);
|
||||
return err;
|
||||
}
|
||||
mutex_init(&pcm->open_mutex);
|
||||
init_waitqueue_head(&pcm->open_wait);
|
||||
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
|
||||
snd_pcm_free(pcm);
|
||||
return err;
|
||||
@ -888,8 +881,9 @@ static int snd_pcm_free(struct snd_pcm *pcm)
|
||||
|
||||
if (!pcm)
|
||||
return 0;
|
||||
list_for_each_entry(notify, &snd_pcm_notify_list, list) {
|
||||
notify->n_unregister(pcm);
|
||||
if (!pcm->internal) {
|
||||
list_for_each_entry(notify, &snd_pcm_notify_list, list)
|
||||
notify->n_unregister(pcm);
|
||||
}
|
||||
if (pcm->private_free)
|
||||
pcm->private_free(pcm);
|
||||
@ -919,6 +913,9 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
|
||||
|
||||
if (snd_BUG_ON(!pcm || !rsubstream))
|
||||
return -ENXIO;
|
||||
if (snd_BUG_ON(stream != SNDRV_PCM_STREAM_PLAYBACK &&
|
||||
stream != SNDRV_PCM_STREAM_CAPTURE))
|
||||
return -EINVAL;
|
||||
*rsubstream = NULL;
|
||||
pstr = &pcm->streams[stream];
|
||||
if (pstr->substream == NULL || pstr->substream_count == 0)
|
||||
@ -927,25 +924,14 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
|
||||
card = pcm->card;
|
||||
prefer_subdevice = snd_ctl_get_preferred_subdevice(card, SND_CTL_SUBDEV_PCM);
|
||||
|
||||
switch (stream) {
|
||||
case SNDRV_PCM_STREAM_PLAYBACK:
|
||||
if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
|
||||
for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next) {
|
||||
if (SUBSTREAM_BUSY(substream))
|
||||
return -EAGAIN;
|
||||
}
|
||||
if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
|
||||
int opposite = !stream;
|
||||
|
||||
for (substream = pcm->streams[opposite].substream; substream;
|
||||
substream = substream->next) {
|
||||
if (SUBSTREAM_BUSY(substream))
|
||||
return -EAGAIN;
|
||||
}
|
||||
break;
|
||||
case SNDRV_PCM_STREAM_CAPTURE:
|
||||
if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
|
||||
for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) {
|
||||
if (SUBSTREAM_BUSY(substream))
|
||||
return -EAGAIN;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (file->f_flags & O_APPEND) {
|
||||
@ -968,15 +954,12 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (prefer_subdevice >= 0) {
|
||||
for (substream = pstr->substream; substream; substream = substream->next)
|
||||
if (!SUBSTREAM_BUSY(substream) && substream->number == prefer_subdevice)
|
||||
goto __ok;
|
||||
}
|
||||
for (substream = pstr->substream; substream; substream = substream->next)
|
||||
if (!SUBSTREAM_BUSY(substream))
|
||||
for (substream = pstr->substream; substream; substream = substream->next) {
|
||||
if (!SUBSTREAM_BUSY(substream) &&
|
||||
(prefer_subdevice == -1 ||
|
||||
substream->number == prefer_subdevice))
|
||||
break;
|
||||
__ok:
|
||||
}
|
||||
if (substream == NULL)
|
||||
return -EAGAIN;
|
||||
|
||||
@ -1086,15 +1069,16 @@ static int snd_pcm_dev_register(struct snd_device *device)
|
||||
if (snd_BUG_ON(!device || !device->device_data))
|
||||
return -ENXIO;
|
||||
pcm = device->device_data;
|
||||
if (pcm->internal)
|
||||
return 0;
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
err = snd_pcm_add(pcm);
|
||||
if (err) {
|
||||
mutex_unlock(®ister_mutex);
|
||||
return err;
|
||||
}
|
||||
if (err)
|
||||
goto unlock;
|
||||
for (cidx = 0; cidx < 2; cidx++) {
|
||||
int devtype = -1;
|
||||
if (pcm->streams[cidx].substream == NULL || pcm->internal)
|
||||
if (pcm->streams[cidx].substream == NULL)
|
||||
continue;
|
||||
switch (cidx) {
|
||||
case SNDRV_PCM_STREAM_PLAYBACK:
|
||||
@ -1109,9 +1093,8 @@ static int snd_pcm_dev_register(struct snd_device *device)
|
||||
&snd_pcm_f_ops[cidx], pcm,
|
||||
&pcm->streams[cidx].dev);
|
||||
if (err < 0) {
|
||||
list_del(&pcm->list);
|
||||
mutex_unlock(®ister_mutex);
|
||||
return err;
|
||||
list_del_init(&pcm->list);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
|
||||
@ -1121,8 +1104,9 @@ static int snd_pcm_dev_register(struct snd_device *device)
|
||||
list_for_each_entry(notify, &snd_pcm_notify_list, list)
|
||||
notify->n_register(pcm);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(®ister_mutex);
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_pcm_dev_disconnect(struct snd_device *device)
|
||||
@ -1133,13 +1117,10 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
|
||||
int cidx;
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
if (list_empty(&pcm->list))
|
||||
goto unlock;
|
||||
|
||||
mutex_lock(&pcm->open_mutex);
|
||||
wake_up(&pcm->open_wait);
|
||||
list_del_init(&pcm->list);
|
||||
for (cidx = 0; cidx < 2; cidx++)
|
||||
for (cidx = 0; cidx < 2; cidx++) {
|
||||
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) {
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
if (substream->runtime) {
|
||||
@ -1149,18 +1130,20 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
|
||||
}
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
}
|
||||
list_for_each_entry(notify, &snd_pcm_notify_list, list) {
|
||||
notify->n_disconnect(pcm);
|
||||
}
|
||||
if (!pcm->internal) {
|
||||
list_for_each_entry(notify, &snd_pcm_notify_list, list)
|
||||
notify->n_disconnect(pcm);
|
||||
}
|
||||
for (cidx = 0; cidx < 2; cidx++) {
|
||||
snd_unregister_device(&pcm->streams[cidx].dev);
|
||||
if (!pcm->internal)
|
||||
snd_unregister_device(&pcm->streams[cidx].dev);
|
||||
if (pcm->streams[cidx].chmap_kctl) {
|
||||
snd_ctl_remove(pcm->card, pcm->streams[cidx].chmap_kctl);
|
||||
pcm->streams[cidx].chmap_kctl = NULL;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&pcm->open_mutex);
|
||||
unlock:
|
||||
mutex_unlock(®ister_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
@ -194,18 +194,30 @@ struct snd_pcm_status32 {
|
||||
u32 avail_max;
|
||||
u32 overrange;
|
||||
s32 suspended_state;
|
||||
u32 reserved_alignment;
|
||||
u32 audio_tstamp_data;
|
||||
struct compat_timespec audio_tstamp;
|
||||
unsigned char reserved[56-sizeof(struct compat_timespec)];
|
||||
struct compat_timespec driver_tstamp;
|
||||
u32 audio_tstamp_accuracy;
|
||||
unsigned char reserved[52-2*sizeof(struct compat_timespec)];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
static int snd_pcm_status_user_compat(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_status32 __user *src)
|
||||
struct snd_pcm_status32 __user *src,
|
||||
bool ext)
|
||||
{
|
||||
struct snd_pcm_status status;
|
||||
int err;
|
||||
|
||||
memset(&status, 0, sizeof(status));
|
||||
/*
|
||||
* with extension, parameters are read/write,
|
||||
* get audio_tstamp_data from user,
|
||||
* ignore rest of status structure
|
||||
*/
|
||||
if (ext && get_user(status.audio_tstamp_data,
|
||||
(u32 __user *)(&src->audio_tstamp_data)))
|
||||
return -EFAULT;
|
||||
err = snd_pcm_status(substream, &status);
|
||||
if (err < 0)
|
||||
return err;
|
||||
@ -222,7 +234,10 @@ static int snd_pcm_status_user_compat(struct snd_pcm_substream *substream,
|
||||
put_user(status.avail_max, &src->avail_max) ||
|
||||
put_user(status.overrange, &src->overrange) ||
|
||||
put_user(status.suspended_state, &src->suspended_state) ||
|
||||
compat_put_timespec(&status.audio_tstamp, &src->audio_tstamp))
|
||||
put_user(status.audio_tstamp_data, &src->audio_tstamp_data) ||
|
||||
compat_put_timespec(&status.audio_tstamp, &src->audio_tstamp) ||
|
||||
compat_put_timespec(&status.driver_tstamp, &src->driver_tstamp) ||
|
||||
put_user(status.audio_tstamp_accuracy, &src->audio_tstamp_accuracy))
|
||||
return -EFAULT;
|
||||
|
||||
return err;
|
||||
@ -457,6 +472,7 @@ enum {
|
||||
SNDRV_PCM_IOCTL_HW_PARAMS32 = _IOWR('A', 0x11, struct snd_pcm_hw_params32),
|
||||
SNDRV_PCM_IOCTL_SW_PARAMS32 = _IOWR('A', 0x13, struct snd_pcm_sw_params32),
|
||||
SNDRV_PCM_IOCTL_STATUS32 = _IOR('A', 0x20, struct snd_pcm_status32),
|
||||
SNDRV_PCM_IOCTL_STATUS_EXT32 = _IOWR('A', 0x24, struct snd_pcm_status32),
|
||||
SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32),
|
||||
SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct snd_pcm_channel_info32),
|
||||
SNDRV_PCM_IOCTL_REWIND32 = _IOW('A', 0x46, u32),
|
||||
@ -517,7 +533,9 @@ static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned l
|
||||
case SNDRV_PCM_IOCTL_SW_PARAMS32:
|
||||
return snd_pcm_ioctl_sw_params_compat(substream, argp);
|
||||
case SNDRV_PCM_IOCTL_STATUS32:
|
||||
return snd_pcm_status_user_compat(substream, argp);
|
||||
return snd_pcm_status_user_compat(substream, argp, false);
|
||||
case SNDRV_PCM_IOCTL_STATUS_EXT32:
|
||||
return snd_pcm_status_user_compat(substream, argp, true);
|
||||
case SNDRV_PCM_IOCTL_SYNC_PTR32:
|
||||
return snd_pcm_ioctl_sync_ptr_compat(substream, argp);
|
||||
case SNDRV_PCM_IOCTL_CHANNEL_INFO32:
|
||||
|
@ -289,7 +289,7 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_request_channel);
|
||||
*
|
||||
* The function should usually be called from the pcm open callback. Note that
|
||||
* this function will use private_data field of the substream's runtime. So it
|
||||
* is not availabe to your pcm driver implementation.
|
||||
* is not available to your pcm driver implementation.
|
||||
*/
|
||||
int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream,
|
||||
struct dma_chan *chan)
|
||||
@ -328,7 +328,7 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open);
|
||||
* This function will request a DMA channel using the passed filter function and
|
||||
* data. The function should usually be called from the pcm open callback. Note
|
||||
* that this function will use private_data field of the substream's runtime. So
|
||||
* it is not availabe to your pcm driver implementation.
|
||||
* it is not available to your pcm driver implementation.
|
||||
*/
|
||||
int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream,
|
||||
dma_filter_fn filter_fn, void *filter_data)
|
||||
|
@ -232,6 +232,49 @@ int snd_pcm_update_state(struct snd_pcm_substream *substream,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void update_audio_tstamp(struct snd_pcm_substream *substream,
|
||||
struct timespec *curr_tstamp,
|
||||
struct timespec *audio_tstamp)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
u64 audio_frames, audio_nsecs;
|
||||
struct timespec driver_tstamp;
|
||||
|
||||
if (runtime->tstamp_mode != SNDRV_PCM_TSTAMP_ENABLE)
|
||||
return;
|
||||
|
||||
if (!(substream->ops->get_time_info) ||
|
||||
(runtime->audio_tstamp_report.actual_type ==
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) {
|
||||
|
||||
/*
|
||||
* provide audio timestamp derived from pointer position
|
||||
* add delay only if requested
|
||||
*/
|
||||
|
||||
audio_frames = runtime->hw_ptr_wrap + runtime->status->hw_ptr;
|
||||
|
||||
if (runtime->audio_tstamp_config.report_delay) {
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
audio_frames -= runtime->delay;
|
||||
else
|
||||
audio_frames += runtime->delay;
|
||||
}
|
||||
audio_nsecs = div_u64(audio_frames * 1000000000LL,
|
||||
runtime->rate);
|
||||
*audio_tstamp = ns_to_timespec(audio_nsecs);
|
||||
}
|
||||
runtime->status->audio_tstamp = *audio_tstamp;
|
||||
runtime->status->tstamp = *curr_tstamp;
|
||||
|
||||
/*
|
||||
* re-take a driver timestamp to let apps detect if the reference tstamp
|
||||
* read by low-level hardware was provided with a delay
|
||||
*/
|
||||
snd_pcm_gettime(substream->runtime, (struct timespec *)&driver_tstamp);
|
||||
runtime->driver_tstamp = driver_tstamp;
|
||||
}
|
||||
|
||||
static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
|
||||
unsigned int in_interrupt)
|
||||
{
|
||||
@ -256,11 +299,18 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
|
||||
pos = substream->ops->pointer(substream);
|
||||
curr_jiffies = jiffies;
|
||||
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
|
||||
snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp);
|
||||
if ((substream->ops->get_time_info) &&
|
||||
(runtime->audio_tstamp_config.type_requested != SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) {
|
||||
substream->ops->get_time_info(substream, &curr_tstamp,
|
||||
&audio_tstamp,
|
||||
&runtime->audio_tstamp_config,
|
||||
&runtime->audio_tstamp_report);
|
||||
|
||||
if ((runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK) &&
|
||||
(substream->ops->wall_clock))
|
||||
substream->ops->wall_clock(substream, &audio_tstamp);
|
||||
/* re-test in case tstamp type is not supported in hardware and was demoted to DEFAULT */
|
||||
if (runtime->audio_tstamp_report.actual_type == SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)
|
||||
snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp);
|
||||
} else
|
||||
snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp);
|
||||
}
|
||||
|
||||
if (pos == SNDRV_PCM_POS_XRUN) {
|
||||
@ -403,8 +453,10 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
|
||||
}
|
||||
|
||||
no_delta_check:
|
||||
if (runtime->status->hw_ptr == new_hw_ptr)
|
||||
if (runtime->status->hw_ptr == new_hw_ptr) {
|
||||
update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
|
||||
runtime->silence_size > 0)
|
||||
@ -426,30 +478,8 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
|
||||
snd_BUG_ON(crossed_boundary != 1);
|
||||
runtime->hw_ptr_wrap += runtime->boundary;
|
||||
}
|
||||
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
|
||||
runtime->status->tstamp = curr_tstamp;
|
||||
|
||||
if (!(runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK)) {
|
||||
/*
|
||||
* no wall clock available, provide audio timestamp
|
||||
* derived from pointer position+delay
|
||||
*/
|
||||
u64 audio_frames, audio_nsecs;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
audio_frames = runtime->hw_ptr_wrap
|
||||
+ runtime->status->hw_ptr
|
||||
- runtime->delay;
|
||||
else
|
||||
audio_frames = runtime->hw_ptr_wrap
|
||||
+ runtime->status->hw_ptr
|
||||
+ runtime->delay;
|
||||
audio_nsecs = div_u64(audio_frames * 1000000000LL,
|
||||
runtime->rate);
|
||||
audio_tstamp = ns_to_timespec(audio_nsecs);
|
||||
}
|
||||
runtime->status->audio_tstamp = audio_tstamp;
|
||||
}
|
||||
update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp);
|
||||
|
||||
return snd_pcm_update_state(substream, runtime);
|
||||
}
|
||||
|
@ -707,6 +707,23 @@ int snd_pcm_status(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
|
||||
snd_pcm_unpack_audio_tstamp_config(status->audio_tstamp_data,
|
||||
&runtime->audio_tstamp_config);
|
||||
|
||||
/* backwards compatible behavior */
|
||||
if (runtime->audio_tstamp_config.type_requested ==
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_COMPAT) {
|
||||
if (runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK)
|
||||
runtime->audio_tstamp_config.type_requested =
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK;
|
||||
else
|
||||
runtime->audio_tstamp_config.type_requested =
|
||||
SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT;
|
||||
runtime->audio_tstamp_report.valid = 0;
|
||||
} else
|
||||
runtime->audio_tstamp_report.valid = 1;
|
||||
|
||||
status->state = runtime->status->state;
|
||||
status->suspended_state = runtime->status->suspended_state;
|
||||
if (status->state == SNDRV_PCM_STATE_OPEN)
|
||||
@ -716,8 +733,15 @@ int snd_pcm_status(struct snd_pcm_substream *substream,
|
||||
snd_pcm_update_hw_ptr(substream);
|
||||
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
|
||||
status->tstamp = runtime->status->tstamp;
|
||||
status->driver_tstamp = runtime->driver_tstamp;
|
||||
status->audio_tstamp =
|
||||
runtime->status->audio_tstamp;
|
||||
if (runtime->audio_tstamp_report.valid == 1)
|
||||
/* backwards compatibility, no report provided in COMPAT mode */
|
||||
snd_pcm_pack_audio_tstamp_report(&status->audio_tstamp_data,
|
||||
&status->audio_tstamp_accuracy,
|
||||
&runtime->audio_tstamp_report);
|
||||
|
||||
goto _tstamp_end;
|
||||
}
|
||||
} else {
|
||||
@ -753,12 +777,21 @@ int snd_pcm_status(struct snd_pcm_substream *substream,
|
||||
}
|
||||
|
||||
static int snd_pcm_status_user(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_status __user * _status)
|
||||
struct snd_pcm_status __user * _status,
|
||||
bool ext)
|
||||
{
|
||||
struct snd_pcm_status status;
|
||||
int res;
|
||||
|
||||
|
||||
memset(&status, 0, sizeof(status));
|
||||
/*
|
||||
* with extension, parameters are read/write,
|
||||
* get audio_tstamp_data from user,
|
||||
* ignore rest of status structure
|
||||
*/
|
||||
if (ext && get_user(status.audio_tstamp_data,
|
||||
(u32 __user *)(&_status->audio_tstamp_data)))
|
||||
return -EFAULT;
|
||||
res = snd_pcm_status(substream, &status);
|
||||
if (res < 0)
|
||||
return res;
|
||||
@ -2725,7 +2758,9 @@ static int snd_pcm_common_ioctl1(struct file *file,
|
||||
case SNDRV_PCM_IOCTL_SW_PARAMS:
|
||||
return snd_pcm_sw_params_user(substream, arg);
|
||||
case SNDRV_PCM_IOCTL_STATUS:
|
||||
return snd_pcm_status_user(substream, arg);
|
||||
return snd_pcm_status_user(substream, arg, false);
|
||||
case SNDRV_PCM_IOCTL_STATUS_EXT:
|
||||
return snd_pcm_status_user(substream, arg, true);
|
||||
case SNDRV_PCM_IOCTL_CHANNEL_INFO:
|
||||
return snd_pcm_channel_info_user(substream, arg);
|
||||
case SNDRV_PCM_IOCTL_PREPARE:
|
||||
|
@ -1429,10 +1429,8 @@ static int snd_rawmidi_alloc_substreams(struct snd_rawmidi *rmidi,
|
||||
|
||||
for (idx = 0; idx < count; idx++) {
|
||||
substream = kzalloc(sizeof(*substream), GFP_KERNEL);
|
||||
if (substream == NULL) {
|
||||
rmidi_err(rmidi, "rawmidi: cannot allocate substream\n");
|
||||
if (!substream)
|
||||
return -ENOMEM;
|
||||
}
|
||||
substream->stream = direction;
|
||||
substream->number = idx;
|
||||
substream->rmidi = rmidi;
|
||||
@ -1479,10 +1477,8 @@ int snd_rawmidi_new(struct snd_card *card, char *id, int device,
|
||||
if (rrawmidi)
|
||||
*rrawmidi = NULL;
|
||||
rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL);
|
||||
if (rmidi == NULL) {
|
||||
dev_err(card->dev, "rawmidi: cannot allocate\n");
|
||||
if (!rmidi)
|
||||
return -ENOMEM;
|
||||
}
|
||||
rmidi->card = card;
|
||||
rmidi->device = device;
|
||||
mutex_init(&rmidi->open_mutex);
|
||||
|
@ -65,15 +65,20 @@ static unsigned int odev_poll(struct file *file, poll_table * wait);
|
||||
* module interface
|
||||
*/
|
||||
|
||||
static struct snd_seq_driver seq_oss_synth_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.probe = snd_seq_oss_synth_probe,
|
||||
.remove = snd_seq_oss_synth_remove,
|
||||
},
|
||||
.id = SNDRV_SEQ_DEV_ID_OSS,
|
||||
.argsize = sizeof(struct snd_seq_oss_reg),
|
||||
};
|
||||
|
||||
static int __init alsa_seq_oss_init(void)
|
||||
{
|
||||
int rc;
|
||||
static struct snd_seq_dev_ops ops = {
|
||||
snd_seq_oss_synth_register,
|
||||
snd_seq_oss_synth_unregister,
|
||||
};
|
||||
|
||||
snd_seq_autoload_lock();
|
||||
if ((rc = register_device()) < 0)
|
||||
goto error;
|
||||
if ((rc = register_proc()) < 0) {
|
||||
@ -86,8 +91,8 @@ static int __init alsa_seq_oss_init(void)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((rc = snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OSS, &ops,
|
||||
sizeof(struct snd_seq_oss_reg))) < 0) {
|
||||
rc = snd_seq_driver_register(&seq_oss_synth_driver);
|
||||
if (rc < 0) {
|
||||
snd_seq_oss_delete_client();
|
||||
unregister_proc();
|
||||
unregister_device();
|
||||
@ -98,13 +103,12 @@ static int __init alsa_seq_oss_init(void)
|
||||
snd_seq_oss_synth_init();
|
||||
|
||||
error:
|
||||
snd_seq_autoload_unlock();
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void __exit alsa_seq_oss_exit(void)
|
||||
{
|
||||
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OSS);
|
||||
snd_seq_driver_unregister(&seq_oss_synth_driver);
|
||||
snd_seq_oss_delete_client();
|
||||
unregister_proc();
|
||||
unregister_device();
|
||||
|
@ -188,10 +188,8 @@ snd_seq_oss_open(struct file *file, int level)
|
||||
struct seq_oss_devinfo *dp;
|
||||
|
||||
dp = kzalloc(sizeof(*dp), GFP_KERNEL);
|
||||
if (!dp) {
|
||||
pr_err("ALSA: seq_oss: can't malloc device info\n");
|
||||
if (!dp)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dp->cseq = system_client;
|
||||
dp->port = -1;
|
||||
|
@ -173,10 +173,9 @@ snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *pinfo)
|
||||
/*
|
||||
* allocate midi info record
|
||||
*/
|
||||
if ((mdev = kzalloc(sizeof(*mdev), GFP_KERNEL)) == NULL) {
|
||||
pr_err("ALSA: seq_oss: can't malloc midi info\n");
|
||||
mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
|
||||
if (!mdev)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* copy the port information */
|
||||
mdev->client = pinfo->addr.client;
|
||||
|
@ -47,13 +47,12 @@ snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen)
|
||||
{
|
||||
struct seq_oss_readq *q;
|
||||
|
||||
if ((q = kzalloc(sizeof(*q), GFP_KERNEL)) == NULL) {
|
||||
pr_err("ALSA: seq_oss: can't malloc read queue\n");
|
||||
q = kzalloc(sizeof(*q), GFP_KERNEL);
|
||||
if (!q)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((q->q = kcalloc(maxlen, sizeof(union evrec), GFP_KERNEL)) == NULL) {
|
||||
pr_err("ALSA: seq_oss: can't malloc read queue buffer\n");
|
||||
q->q = kcalloc(maxlen, sizeof(union evrec), GFP_KERNEL);
|
||||
if (!q->q) {
|
||||
kfree(q);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -98,17 +98,17 @@ snd_seq_oss_synth_init(void)
|
||||
* registration of the synth device
|
||||
*/
|
||||
int
|
||||
snd_seq_oss_synth_register(struct snd_seq_device *dev)
|
||||
snd_seq_oss_synth_probe(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
int i;
|
||||
struct seq_oss_synth *rec;
|
||||
struct snd_seq_oss_reg *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
|
||||
unsigned long flags;
|
||||
|
||||
if ((rec = kzalloc(sizeof(*rec), GFP_KERNEL)) == NULL) {
|
||||
pr_err("ALSA: seq_oss: can't malloc synth info\n");
|
||||
rec = kzalloc(sizeof(*rec), GFP_KERNEL);
|
||||
if (!rec)
|
||||
return -ENOMEM;
|
||||
}
|
||||
rec->seq_device = -1;
|
||||
rec->synth_type = reg->type;
|
||||
rec->synth_subtype = reg->subtype;
|
||||
@ -149,8 +149,9 @@ snd_seq_oss_synth_register(struct snd_seq_device *dev)
|
||||
|
||||
|
||||
int
|
||||
snd_seq_oss_synth_unregister(struct snd_seq_device *dev)
|
||||
snd_seq_oss_synth_remove(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
int index;
|
||||
struct seq_oss_synth *rec = dev->driver_data;
|
||||
unsigned long flags;
|
||||
@ -247,7 +248,6 @@ snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp)
|
||||
if (info->nr_voices > 0) {
|
||||
info->ch = kcalloc(info->nr_voices, sizeof(struct seq_oss_chinfo), GFP_KERNEL);
|
||||
if (!info->ch) {
|
||||
pr_err("ALSA: seq_oss: Cannot malloc voices\n");
|
||||
rec->oper.close(&info->arg);
|
||||
module_put(rec->oper.owner);
|
||||
snd_use_lock_free(&rec->use_lock);
|
||||
|
@ -28,8 +28,8 @@
|
||||
#include <sound/seq_device.h>
|
||||
|
||||
void snd_seq_oss_synth_init(void);
|
||||
int snd_seq_oss_synth_register(struct snd_seq_device *dev);
|
||||
int snd_seq_oss_synth_unregister(struct snd_seq_device *dev);
|
||||
int snd_seq_oss_synth_probe(struct device *dev);
|
||||
int snd_seq_oss_synth_remove(struct device *dev);
|
||||
void snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp);
|
||||
void snd_seq_oss_synth_setup_midi(struct seq_oss_devinfo *dp);
|
||||
void snd_seq_oss_synth_cleanup(struct seq_oss_devinfo *dp);
|
||||
|
@ -1879,6 +1879,7 @@ static int snd_seq_ioctl_get_client_pool(struct snd_seq_client *client,
|
||||
if (cptr == NULL)
|
||||
return -ENOENT;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.client = cptr->number;
|
||||
info.output_pool = cptr->pool->size;
|
||||
info.output_room = cptr->pool->room;
|
||||
info.output_free = info.output_pool;
|
||||
|
@ -36,6 +36,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
@ -51,140 +52,78 @@ MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
|
||||
MODULE_DESCRIPTION("ALSA sequencer device management");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* driver state */
|
||||
#define DRIVER_EMPTY 0
|
||||
#define DRIVER_LOADED (1<<0)
|
||||
#define DRIVER_REQUESTED (1<<1)
|
||||
#define DRIVER_LOCKED (1<<2)
|
||||
#define DRIVER_REQUESTING (1<<3)
|
||||
/*
|
||||
* bus definition
|
||||
*/
|
||||
static int snd_seq_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
struct snd_seq_device *sdev = to_seq_dev(dev);
|
||||
struct snd_seq_driver *sdrv = to_seq_drv(drv);
|
||||
|
||||
struct ops_list {
|
||||
char id[ID_LEN]; /* driver id */
|
||||
int driver; /* driver state */
|
||||
int used; /* reference counter */
|
||||
int argsize; /* argument size */
|
||||
return strcmp(sdrv->id, sdev->id) == 0 &&
|
||||
sdrv->argsize == sdev->argsize;
|
||||
}
|
||||
|
||||
/* operators */
|
||||
struct snd_seq_dev_ops ops;
|
||||
|
||||
/* registered devices */
|
||||
struct list_head dev_list; /* list of devices */
|
||||
int num_devices; /* number of associated devices */
|
||||
int num_init_devices; /* number of initialized devices */
|
||||
struct mutex reg_mutex;
|
||||
|
||||
struct list_head list; /* next driver */
|
||||
static struct bus_type snd_seq_bus_type = {
|
||||
.name = "snd_seq",
|
||||
.match = snd_seq_bus_match,
|
||||
};
|
||||
|
||||
|
||||
static LIST_HEAD(opslist);
|
||||
static int num_ops;
|
||||
static DEFINE_MUTEX(ops_mutex);
|
||||
/*
|
||||
* proc interface -- just for compatibility
|
||||
*/
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static struct snd_info_entry *info_entry;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* prototypes
|
||||
*/
|
||||
static int snd_seq_device_free(struct snd_seq_device *dev);
|
||||
static int snd_seq_device_dev_free(struct snd_device *device);
|
||||
static int snd_seq_device_dev_register(struct snd_device *device);
|
||||
static int snd_seq_device_dev_disconnect(struct snd_device *device);
|
||||
static int print_dev_info(struct device *dev, void *data)
|
||||
{
|
||||
struct snd_seq_device *sdev = to_seq_dev(dev);
|
||||
struct snd_info_buffer *buffer = data;
|
||||
|
||||
static int init_device(struct snd_seq_device *dev, struct ops_list *ops);
|
||||
static int free_device(struct snd_seq_device *dev, struct ops_list *ops);
|
||||
static struct ops_list *find_driver(char *id, int create_if_empty);
|
||||
static struct ops_list *create_driver(char *id);
|
||||
static void unlock_driver(struct ops_list *ops);
|
||||
static void remove_drivers(void);
|
||||
snd_iprintf(buffer, "snd-%s,%s,%d\n", sdev->id,
|
||||
dev->driver ? "loaded" : "empty",
|
||||
dev->driver ? 1 : 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* show all drivers and their status
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static void snd_seq_device_info(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
list_for_each_entry(ops, &opslist, list) {
|
||||
snd_iprintf(buffer, "snd-%s%s%s%s,%d\n",
|
||||
ops->id,
|
||||
ops->driver & DRIVER_LOADED ? ",loaded" : (ops->driver == DRIVER_EMPTY ? ",empty" : ""),
|
||||
ops->driver & DRIVER_REQUESTED ? ",requested" : "",
|
||||
ops->driver & DRIVER_LOCKED ? ",locked" : "",
|
||||
ops->num_devices);
|
||||
}
|
||||
mutex_unlock(&ops_mutex);
|
||||
bus_for_each_dev(&snd_seq_bus_type, NULL, buffer, print_dev_info);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* load all registered drivers (called from seq_clientmgr.c)
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_MODULES
|
||||
/* avoid auto-loading during module_init() */
|
||||
/* flag to block auto-loading */
|
||||
static atomic_t snd_seq_in_init = ATOMIC_INIT(1); /* blocked as default */
|
||||
void snd_seq_autoload_lock(void)
|
||||
|
||||
static int request_seq_drv(struct device *dev, void *data)
|
||||
{
|
||||
atomic_inc(&snd_seq_in_init);
|
||||
struct snd_seq_device *sdev = to_seq_dev(dev);
|
||||
|
||||
if (!dev->driver)
|
||||
request_module("snd-%s", sdev->id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void snd_seq_autoload_unlock(void)
|
||||
{
|
||||
atomic_dec(&snd_seq_in_init);
|
||||
}
|
||||
|
||||
static void autoload_drivers(void)
|
||||
static void autoload_drivers(struct work_struct *work)
|
||||
{
|
||||
/* avoid reentrance */
|
||||
if (atomic_inc_return(&snd_seq_in_init) == 1) {
|
||||
struct ops_list *ops;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
list_for_each_entry(ops, &opslist, list) {
|
||||
if ((ops->driver & DRIVER_REQUESTING) &&
|
||||
!(ops->driver & DRIVER_REQUESTED)) {
|
||||
ops->used++;
|
||||
mutex_unlock(&ops_mutex);
|
||||
ops->driver |= DRIVER_REQUESTED;
|
||||
request_module("snd-%s", ops->id);
|
||||
mutex_lock(&ops_mutex);
|
||||
ops->used--;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ops_mutex);
|
||||
}
|
||||
if (atomic_inc_return(&snd_seq_in_init) == 1)
|
||||
bus_for_each_dev(&snd_seq_bus_type, NULL, NULL,
|
||||
request_seq_drv);
|
||||
atomic_dec(&snd_seq_in_init);
|
||||
}
|
||||
|
||||
static void call_autoload(struct work_struct *work)
|
||||
{
|
||||
autoload_drivers();
|
||||
}
|
||||
|
||||
static DECLARE_WORK(autoload_work, call_autoload);
|
||||
|
||||
static void try_autoload(struct ops_list *ops)
|
||||
{
|
||||
if (!ops->driver) {
|
||||
ops->driver |= DRIVER_REQUESTING;
|
||||
schedule_work(&autoload_work);
|
||||
}
|
||||
}
|
||||
static DECLARE_WORK(autoload_work, autoload_drivers);
|
||||
|
||||
static void queue_autoload_drivers(void)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
list_for_each_entry(ops, &opslist, list)
|
||||
try_autoload(ops);
|
||||
mutex_unlock(&ops_mutex);
|
||||
schedule_work(&autoload_work);
|
||||
}
|
||||
|
||||
void snd_seq_autoload_init(void)
|
||||
@ -195,16 +134,63 @@ void snd_seq_autoload_init(void)
|
||||
queue_autoload_drivers();
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
#define try_autoload(ops) /* NOP */
|
||||
#endif
|
||||
EXPORT_SYMBOL(snd_seq_autoload_init);
|
||||
|
||||
void snd_seq_autoload_exit(void)
|
||||
{
|
||||
atomic_inc(&snd_seq_in_init);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_seq_autoload_exit);
|
||||
|
||||
void snd_seq_device_load_drivers(void)
|
||||
{
|
||||
#ifdef CONFIG_MODULES
|
||||
queue_autoload_drivers();
|
||||
flush_work(&autoload_work);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_seq_device_load_drivers);
|
||||
#else
|
||||
#define queue_autoload_drivers() /* NOP */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* device management
|
||||
*/
|
||||
static int snd_seq_device_dev_free(struct snd_device *device)
|
||||
{
|
||||
struct snd_seq_device *dev = device->device_data;
|
||||
|
||||
put_device(&dev->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_seq_device_dev_register(struct snd_device *device)
|
||||
{
|
||||
struct snd_seq_device *dev = device->device_data;
|
||||
int err;
|
||||
|
||||
err = device_add(&dev->dev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (!dev->dev.driver)
|
||||
queue_autoload_drivers();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_seq_device_dev_disconnect(struct snd_device *device)
|
||||
{
|
||||
struct snd_seq_device *dev = device->device_data;
|
||||
|
||||
device_del(&dev->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_seq_dev_release(struct device *dev)
|
||||
{
|
||||
struct snd_seq_device *sdev = to_seq_dev(dev);
|
||||
|
||||
if (sdev->private_free)
|
||||
sdev->private_free(sdev);
|
||||
kfree(sdev);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -214,11 +200,10 @@ void snd_seq_device_load_drivers(void)
|
||||
* id = id of driver
|
||||
* result = return pointer (NULL allowed if unnecessary)
|
||||
*/
|
||||
int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize,
|
||||
struct snd_seq_device **result)
|
||||
int snd_seq_device_new(struct snd_card *card, int device, const char *id,
|
||||
int argsize, struct snd_seq_device **result)
|
||||
{
|
||||
struct snd_seq_device *dev;
|
||||
struct ops_list *ops;
|
||||
int err;
|
||||
static struct snd_device_ops dops = {
|
||||
.dev_free = snd_seq_device_dev_free,
|
||||
@ -232,347 +217,60 @@ int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize,
|
||||
if (snd_BUG_ON(!id))
|
||||
return -EINVAL;
|
||||
|
||||
ops = find_driver(id, 1);
|
||||
if (ops == NULL)
|
||||
dev = kzalloc(sizeof(*dev) + argsize, GFP_KERNEL);
|
||||
if (!dev)
|
||||
return -ENOMEM;
|
||||
|
||||
dev = kzalloc(sizeof(*dev)*2 + argsize, GFP_KERNEL);
|
||||
if (dev == NULL) {
|
||||
unlock_driver(ops);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* set up device info */
|
||||
dev->card = card;
|
||||
dev->device = device;
|
||||
strlcpy(dev->id, id, sizeof(dev->id));
|
||||
dev->id = id;
|
||||
dev->argsize = argsize;
|
||||
dev->status = SNDRV_SEQ_DEVICE_FREE;
|
||||
|
||||
device_initialize(&dev->dev);
|
||||
dev->dev.parent = &card->card_dev;
|
||||
dev->dev.bus = &snd_seq_bus_type;
|
||||
dev->dev.release = snd_seq_dev_release;
|
||||
dev_set_name(&dev->dev, "%s-%d-%d", dev->id, card->number, device);
|
||||
|
||||
/* add this device to the list */
|
||||
mutex_lock(&ops->reg_mutex);
|
||||
list_add_tail(&dev->list, &ops->dev_list);
|
||||
ops->num_devices++;
|
||||
mutex_unlock(&ops->reg_mutex);
|
||||
|
||||
if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) {
|
||||
snd_seq_device_free(dev);
|
||||
err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops);
|
||||
if (err < 0) {
|
||||
put_device(&dev->dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
try_autoload(ops);
|
||||
unlock_driver(ops);
|
||||
|
||||
if (result)
|
||||
*result = dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_seq_device_new);
|
||||
|
||||
/*
|
||||
* free the existing device
|
||||
* driver registration
|
||||
*/
|
||||
static int snd_seq_device_free(struct snd_seq_device *dev)
|
||||
int __snd_seq_driver_register(struct snd_seq_driver *drv, struct module *mod)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
|
||||
if (snd_BUG_ON(!dev))
|
||||
if (WARN_ON(!drv->driver.name || !drv->id))
|
||||
return -EINVAL;
|
||||
|
||||
ops = find_driver(dev->id, 0);
|
||||
if (ops == NULL)
|
||||
return -ENXIO;
|
||||
|
||||
/* remove the device from the list */
|
||||
mutex_lock(&ops->reg_mutex);
|
||||
list_del(&dev->list);
|
||||
ops->num_devices--;
|
||||
mutex_unlock(&ops->reg_mutex);
|
||||
|
||||
free_device(dev, ops);
|
||||
if (dev->private_free)
|
||||
dev->private_free(dev);
|
||||
kfree(dev);
|
||||
|
||||
unlock_driver(ops);
|
||||
|
||||
return 0;
|
||||
drv->driver.bus = &snd_seq_bus_type;
|
||||
drv->driver.owner = mod;
|
||||
return driver_register(&drv->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__snd_seq_driver_register);
|
||||
|
||||
static int snd_seq_device_dev_free(struct snd_device *device)
|
||||
void snd_seq_driver_unregister(struct snd_seq_driver *drv)
|
||||
{
|
||||
struct snd_seq_device *dev = device->device_data;
|
||||
return snd_seq_device_free(dev);
|
||||
driver_unregister(&drv->driver);
|
||||
}
|
||||
|
||||
/*
|
||||
* register the device
|
||||
*/
|
||||
static int snd_seq_device_dev_register(struct snd_device *device)
|
||||
{
|
||||
struct snd_seq_device *dev = device->device_data;
|
||||
struct ops_list *ops;
|
||||
|
||||
ops = find_driver(dev->id, 0);
|
||||
if (ops == NULL)
|
||||
return -ENOENT;
|
||||
|
||||
/* initialize this device if the corresponding driver was
|
||||
* already loaded
|
||||
*/
|
||||
if (ops->driver & DRIVER_LOADED)
|
||||
init_device(dev, ops);
|
||||
|
||||
unlock_driver(ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* disconnect the device
|
||||
*/
|
||||
static int snd_seq_device_dev_disconnect(struct snd_device *device)
|
||||
{
|
||||
struct snd_seq_device *dev = device->device_data;
|
||||
struct ops_list *ops;
|
||||
|
||||
ops = find_driver(dev->id, 0);
|
||||
if (ops == NULL)
|
||||
return -ENOENT;
|
||||
|
||||
free_device(dev, ops);
|
||||
|
||||
unlock_driver(ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* register device driver
|
||||
* id = driver id
|
||||
* entry = driver operators - duplicated to each instance
|
||||
*/
|
||||
int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry,
|
||||
int argsize)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
struct snd_seq_device *dev;
|
||||
|
||||
if (id == NULL || entry == NULL ||
|
||||
entry->init_device == NULL || entry->free_device == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
ops = find_driver(id, 1);
|
||||
if (ops == NULL)
|
||||
return -ENOMEM;
|
||||
if (ops->driver & DRIVER_LOADED) {
|
||||
pr_warn("ALSA: seq: driver_register: driver '%s' already exists\n", id);
|
||||
unlock_driver(ops);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
mutex_lock(&ops->reg_mutex);
|
||||
/* copy driver operators */
|
||||
ops->ops = *entry;
|
||||
ops->driver |= DRIVER_LOADED;
|
||||
ops->argsize = argsize;
|
||||
|
||||
/* initialize existing devices if necessary */
|
||||
list_for_each_entry(dev, &ops->dev_list, list) {
|
||||
init_device(dev, ops);
|
||||
}
|
||||
mutex_unlock(&ops->reg_mutex);
|
||||
|
||||
unlock_driver(ops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* create driver record
|
||||
*/
|
||||
static struct ops_list * create_driver(char *id)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
|
||||
ops = kzalloc(sizeof(*ops), GFP_KERNEL);
|
||||
if (ops == NULL)
|
||||
return ops;
|
||||
|
||||
/* set up driver entry */
|
||||
strlcpy(ops->id, id, sizeof(ops->id));
|
||||
mutex_init(&ops->reg_mutex);
|
||||
/*
|
||||
* The ->reg_mutex locking rules are per-driver, so we create
|
||||
* separate per-driver lock classes:
|
||||
*/
|
||||
lockdep_set_class(&ops->reg_mutex, (struct lock_class_key *)id);
|
||||
|
||||
ops->driver = DRIVER_EMPTY;
|
||||
INIT_LIST_HEAD(&ops->dev_list);
|
||||
/* lock this instance */
|
||||
ops->used = 1;
|
||||
|
||||
/* register driver entry */
|
||||
mutex_lock(&ops_mutex);
|
||||
list_add_tail(&ops->list, &opslist);
|
||||
num_ops++;
|
||||
mutex_unlock(&ops_mutex);
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* unregister the specified driver
|
||||
*/
|
||||
int snd_seq_device_unregister_driver(char *id)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
struct snd_seq_device *dev;
|
||||
|
||||
ops = find_driver(id, 0);
|
||||
if (ops == NULL)
|
||||
return -ENXIO;
|
||||
if (! (ops->driver & DRIVER_LOADED) ||
|
||||
(ops->driver & DRIVER_LOCKED)) {
|
||||
pr_err("ALSA: seq: driver_unregister: cannot unload driver '%s': status=%x\n",
|
||||
id, ops->driver);
|
||||
unlock_driver(ops);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* close and release all devices associated with this driver */
|
||||
mutex_lock(&ops->reg_mutex);
|
||||
ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */
|
||||
list_for_each_entry(dev, &ops->dev_list, list) {
|
||||
free_device(dev, ops);
|
||||
}
|
||||
|
||||
ops->driver = 0;
|
||||
if (ops->num_init_devices > 0)
|
||||
pr_err("ALSA: seq: free_driver: init_devices > 0!! (%d)\n",
|
||||
ops->num_init_devices);
|
||||
mutex_unlock(&ops->reg_mutex);
|
||||
|
||||
unlock_driver(ops);
|
||||
|
||||
/* remove empty driver entries */
|
||||
remove_drivers();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* remove empty driver entries
|
||||
*/
|
||||
static void remove_drivers(void)
|
||||
{
|
||||
struct list_head *head;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
head = opslist.next;
|
||||
while (head != &opslist) {
|
||||
struct ops_list *ops = list_entry(head, struct ops_list, list);
|
||||
if (! (ops->driver & DRIVER_LOADED) &&
|
||||
ops->used == 0 && ops->num_devices == 0) {
|
||||
head = head->next;
|
||||
list_del(&ops->list);
|
||||
kfree(ops);
|
||||
num_ops--;
|
||||
} else
|
||||
head = head->next;
|
||||
}
|
||||
mutex_unlock(&ops_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize the device - call init_device operator
|
||||
*/
|
||||
static int init_device(struct snd_seq_device *dev, struct ops_list *ops)
|
||||
{
|
||||
if (! (ops->driver & DRIVER_LOADED))
|
||||
return 0; /* driver is not loaded yet */
|
||||
if (dev->status != SNDRV_SEQ_DEVICE_FREE)
|
||||
return 0; /* already initialized */
|
||||
if (ops->argsize != dev->argsize) {
|
||||
pr_err("ALSA: seq: incompatible device '%s' for plug-in '%s' (%d %d)\n",
|
||||
dev->name, ops->id, ops->argsize, dev->argsize);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (ops->ops.init_device(dev) >= 0) {
|
||||
dev->status = SNDRV_SEQ_DEVICE_REGISTERED;
|
||||
ops->num_init_devices++;
|
||||
} else {
|
||||
pr_err("ALSA: seq: init_device failed: %s: %s\n",
|
||||
dev->name, dev->id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* release the device - call free_device operator
|
||||
*/
|
||||
static int free_device(struct snd_seq_device *dev, struct ops_list *ops)
|
||||
{
|
||||
int result;
|
||||
|
||||
if (! (ops->driver & DRIVER_LOADED))
|
||||
return 0; /* driver is not loaded yet */
|
||||
if (dev->status != SNDRV_SEQ_DEVICE_REGISTERED)
|
||||
return 0; /* not registered */
|
||||
if (ops->argsize != dev->argsize) {
|
||||
pr_err("ALSA: seq: incompatible device '%s' for plug-in '%s' (%d %d)\n",
|
||||
dev->name, ops->id, ops->argsize, dev->argsize);
|
||||
return -EINVAL;
|
||||
}
|
||||
if ((result = ops->ops.free_device(dev)) >= 0 || result == -ENXIO) {
|
||||
dev->status = SNDRV_SEQ_DEVICE_FREE;
|
||||
dev->driver_data = NULL;
|
||||
ops->num_init_devices--;
|
||||
} else {
|
||||
pr_err("ALSA: seq: free_device failed: %s: %s\n",
|
||||
dev->name, dev->id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* find the matching driver with given id
|
||||
*/
|
||||
static struct ops_list * find_driver(char *id, int create_if_empty)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
list_for_each_entry(ops, &opslist, list) {
|
||||
if (strcmp(ops->id, id) == 0) {
|
||||
ops->used++;
|
||||
mutex_unlock(&ops_mutex);
|
||||
return ops;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ops_mutex);
|
||||
if (create_if_empty)
|
||||
return create_driver(id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void unlock_driver(struct ops_list *ops)
|
||||
{
|
||||
mutex_lock(&ops_mutex);
|
||||
ops->used--;
|
||||
mutex_unlock(&ops_mutex);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(snd_seq_driver_unregister);
|
||||
|
||||
/*
|
||||
* module part
|
||||
*/
|
||||
|
||||
static int __init alsa_seq_device_init(void)
|
||||
static int __init seq_dev_proc_init(void)
|
||||
{
|
||||
#ifdef CONFIG_PROC_FS
|
||||
info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers",
|
||||
@ -589,28 +287,29 @@ static int __init alsa_seq_device_init(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init alsa_seq_device_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bus_register(&snd_seq_bus_type);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = seq_dev_proc_init();
|
||||
if (err < 0)
|
||||
bus_unregister(&snd_seq_bus_type);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit alsa_seq_device_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_MODULES
|
||||
cancel_work_sync(&autoload_work);
|
||||
#endif
|
||||
remove_drivers();
|
||||
#ifdef CONFIG_PROC_FS
|
||||
snd_info_free_entry(info_entry);
|
||||
#endif
|
||||
if (num_ops)
|
||||
pr_err("ALSA: seq: drivers not released (%d)\n", num_ops);
|
||||
bus_unregister(&snd_seq_bus_type);
|
||||
}
|
||||
|
||||
module_init(alsa_seq_device_init)
|
||||
subsys_initcall(alsa_seq_device_init)
|
||||
module_exit(alsa_seq_device_exit)
|
||||
|
||||
EXPORT_SYMBOL(snd_seq_device_load_drivers);
|
||||
EXPORT_SYMBOL(snd_seq_device_new);
|
||||
EXPORT_SYMBOL(snd_seq_device_register_driver);
|
||||
EXPORT_SYMBOL(snd_seq_device_unregister_driver);
|
||||
#ifdef CONFIG_MODULES
|
||||
EXPORT_SYMBOL(snd_seq_autoload_init);
|
||||
EXPORT_SYMBOL(snd_seq_autoload_lock);
|
||||
EXPORT_SYMBOL(snd_seq_autoload_unlock);
|
||||
#endif
|
||||
|
@ -214,11 +214,7 @@ delete_client(void)
|
||||
|
||||
static int __init alsa_seq_dummy_init(void)
|
||||
{
|
||||
int err;
|
||||
snd_seq_autoload_lock();
|
||||
err = register_client();
|
||||
snd_seq_autoload_unlock();
|
||||
return err;
|
||||
return register_client();
|
||||
}
|
||||
|
||||
static void __exit alsa_seq_dummy_exit(void)
|
||||
|
@ -33,10 +33,8 @@ struct snd_seq_fifo *snd_seq_fifo_new(int poolsize)
|
||||
struct snd_seq_fifo *f;
|
||||
|
||||
f = kzalloc(sizeof(*f), GFP_KERNEL);
|
||||
if (f == NULL) {
|
||||
pr_debug("ALSA: seq: malloc failed for snd_seq_fifo_new() \n");
|
||||
if (!f)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
f->pool = snd_seq_pool_new(poolsize);
|
||||
if (f->pool == NULL) {
|
||||
|
@ -387,10 +387,8 @@ int snd_seq_pool_init(struct snd_seq_pool *pool)
|
||||
return 0;
|
||||
|
||||
pool->ptr = vmalloc(sizeof(struct snd_seq_event_cell) * pool->size);
|
||||
if (pool->ptr == NULL) {
|
||||
pr_debug("ALSA: seq: malloc for sequencer events failed\n");
|
||||
if (!pool->ptr)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* add new cells to the free cell list */
|
||||
spin_lock_irqsave(&pool->lock, flags);
|
||||
@ -463,10 +461,8 @@ struct snd_seq_pool *snd_seq_pool_new(int poolsize)
|
||||
|
||||
/* create pool block */
|
||||
pool = kzalloc(sizeof(*pool), GFP_KERNEL);
|
||||
if (pool == NULL) {
|
||||
pr_debug("ALSA: seq: malloc failed for pool\n");
|
||||
if (!pool)
|
||||
return NULL;
|
||||
}
|
||||
spin_lock_init(&pool->lock);
|
||||
pool->ptr = NULL;
|
||||
pool->free = NULL;
|
||||
|
@ -273,8 +273,9 @@ static void snd_seq_midisynth_delete(struct seq_midisynth *msynth)
|
||||
|
||||
/* register new midi synth port */
|
||||
static int
|
||||
snd_seq_midisynth_register_port(struct snd_seq_device *dev)
|
||||
snd_seq_midisynth_probe(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
struct seq_midisynth_client *client;
|
||||
struct seq_midisynth *msynth, *ms;
|
||||
struct snd_seq_port_info *port;
|
||||
@ -427,8 +428,9 @@ snd_seq_midisynth_register_port(struct snd_seq_device *dev)
|
||||
|
||||
/* release midi synth port */
|
||||
static int
|
||||
snd_seq_midisynth_unregister_port(struct snd_seq_device *dev)
|
||||
snd_seq_midisynth_remove(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
struct seq_midisynth_client *client;
|
||||
struct seq_midisynth *msynth;
|
||||
struct snd_card *card = dev->card;
|
||||
@ -457,24 +459,14 @@ snd_seq_midisynth_unregister_port(struct snd_seq_device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_seq_driver seq_midisynth_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.probe = snd_seq_midisynth_probe,
|
||||
.remove = snd_seq_midisynth_remove,
|
||||
},
|
||||
.id = SNDRV_SEQ_DEV_ID_MIDISYNTH,
|
||||
.argsize = 0,
|
||||
};
|
||||
|
||||
static int __init alsa_seq_midi_init(void)
|
||||
{
|
||||
static struct snd_seq_dev_ops ops = {
|
||||
snd_seq_midisynth_register_port,
|
||||
snd_seq_midisynth_unregister_port,
|
||||
};
|
||||
memset(&synths, 0, sizeof(synths));
|
||||
snd_seq_autoload_lock();
|
||||
snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH, &ops, 0);
|
||||
snd_seq_autoload_unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alsa_seq_midi_exit(void)
|
||||
{
|
||||
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH);
|
||||
}
|
||||
|
||||
module_init(alsa_seq_midi_init)
|
||||
module_exit(alsa_seq_midi_exit)
|
||||
module_snd_seq_driver(seq_midisynth_driver);
|
||||
|
@ -141,10 +141,8 @@ struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client,
|
||||
|
||||
/* create a new port */
|
||||
new_port = kzalloc(sizeof(*new_port), GFP_KERNEL);
|
||||
if (! new_port) {
|
||||
pr_debug("ALSA: seq: malloc failed for registering client port\n");
|
||||
if (!new_port)
|
||||
return NULL; /* failure, out of memory */
|
||||
}
|
||||
/* init port data */
|
||||
new_port->addr.client = client->number;
|
||||
new_port->addr.port = -1;
|
||||
|
@ -59,10 +59,8 @@ struct snd_seq_prioq *snd_seq_prioq_new(void)
|
||||
struct snd_seq_prioq *f;
|
||||
|
||||
f = kzalloc(sizeof(*f), GFP_KERNEL);
|
||||
if (f == NULL) {
|
||||
pr_debug("ALSA: seq: malloc failed for snd_seq_prioq_new()\n");
|
||||
if (!f)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spin_lock_init(&f->lock);
|
||||
f->head = NULL;
|
||||
|
@ -111,10 +111,8 @@ static struct snd_seq_queue *queue_new(int owner, int locked)
|
||||
struct snd_seq_queue *q;
|
||||
|
||||
q = kzalloc(sizeof(*q), GFP_KERNEL);
|
||||
if (q == NULL) {
|
||||
pr_debug("ALSA: seq: malloc failed for snd_seq_queue_new()\n");
|
||||
if (!q)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spin_lock_init(&q->owner_lock);
|
||||
spin_lock_init(&q->check_lock);
|
||||
|
@ -56,10 +56,8 @@ struct snd_seq_timer *snd_seq_timer_new(void)
|
||||
struct snd_seq_timer *tmr;
|
||||
|
||||
tmr = kzalloc(sizeof(*tmr), GFP_KERNEL);
|
||||
if (tmr == NULL) {
|
||||
pr_debug("ALSA: seq: malloc failed for snd_seq_timer_new() \n");
|
||||
if (!tmr)
|
||||
return NULL;
|
||||
}
|
||||
spin_lock_init(&tmr->lock);
|
||||
|
||||
/* reset setup to defaults */
|
||||
|
@ -186,7 +186,7 @@ static const struct file_operations snd_fops =
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SND_DYNAMIC_MINORS
|
||||
static int snd_find_free_minor(int type)
|
||||
static int snd_find_free_minor(int type, struct snd_card *card, int dev)
|
||||
{
|
||||
int minor;
|
||||
|
||||
@ -209,7 +209,7 @@ static int snd_find_free_minor(int type)
|
||||
return -EBUSY;
|
||||
}
|
||||
#else
|
||||
static int snd_kernel_minor(int type, struct snd_card *card, int dev)
|
||||
static int snd_find_free_minor(int type, struct snd_card *card, int dev)
|
||||
{
|
||||
int minor;
|
||||
|
||||
@ -237,6 +237,8 @@ static int snd_kernel_minor(int type, struct snd_card *card, int dev)
|
||||
}
|
||||
if (snd_BUG_ON(minor < 0 || minor >= SNDRV_OS_MINORS))
|
||||
return -EINVAL;
|
||||
if (snd_minors[minor])
|
||||
return -EBUSY;
|
||||
return minor;
|
||||
}
|
||||
#endif
|
||||
@ -276,13 +278,7 @@ int snd_register_device(int type, struct snd_card *card, int dev,
|
||||
preg->private_data = private_data;
|
||||
preg->card_ptr = card;
|
||||
mutex_lock(&sound_mutex);
|
||||
#ifdef CONFIG_SND_DYNAMIC_MINORS
|
||||
minor = snd_find_free_minor(type);
|
||||
#else
|
||||
minor = snd_kernel_minor(type, card, dev);
|
||||
if (minor >= 0 && snd_minors[minor])
|
||||
minor = -EBUSY;
|
||||
#endif
|
||||
minor = snd_find_free_minor(type, card, dev);
|
||||
if (minor < 0) {
|
||||
err = minor;
|
||||
goto error;
|
||||
|
@ -774,10 +774,8 @@ int snd_timer_new(struct snd_card *card, char *id, struct snd_timer_id *tid,
|
||||
if (rtimer)
|
||||
*rtimer = NULL;
|
||||
timer = kzalloc(sizeof(*timer), GFP_KERNEL);
|
||||
if (timer == NULL) {
|
||||
pr_err("ALSA: timer: cannot allocate\n");
|
||||
if (!timer)
|
||||
return -ENOMEM;
|
||||
}
|
||||
timer->tmr_class = tid->dev_class;
|
||||
timer->card = card;
|
||||
timer->tmr_device = tid->device;
|
||||
|
@ -216,8 +216,9 @@ static int snd_opl3_synth_create_port(struct snd_opl3 * opl3)
|
||||
|
||||
/* ------------------------------ */
|
||||
|
||||
static int snd_opl3_seq_new_device(struct snd_seq_device *dev)
|
||||
static int snd_opl3_seq_probe(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
struct snd_opl3 *opl3;
|
||||
int client, err;
|
||||
char name[32];
|
||||
@ -257,8 +258,9 @@ static int snd_opl3_seq_new_device(struct snd_seq_device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_opl3_seq_delete_device(struct snd_seq_device *dev)
|
||||
static int snd_opl3_seq_remove(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
|
||||
@ -275,22 +277,14 @@ static int snd_opl3_seq_delete_device(struct snd_seq_device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init alsa_opl3_seq_init(void)
|
||||
{
|
||||
static struct snd_seq_dev_ops ops =
|
||||
{
|
||||
snd_opl3_seq_new_device,
|
||||
snd_opl3_seq_delete_device
|
||||
};
|
||||
static struct snd_seq_driver opl3_seq_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.probe = snd_opl3_seq_probe,
|
||||
.remove = snd_opl3_seq_remove,
|
||||
},
|
||||
.id = SNDRV_SEQ_DEV_ID_OPL3,
|
||||
.argsize = sizeof(struct snd_opl3 *),
|
||||
};
|
||||
|
||||
return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OPL3, &ops,
|
||||
sizeof(struct snd_opl3 *));
|
||||
}
|
||||
|
||||
static void __exit alsa_opl3_seq_exit(void)
|
||||
{
|
||||
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OPL3);
|
||||
}
|
||||
|
||||
module_init(alsa_opl3_seq_init)
|
||||
module_exit(alsa_opl3_seq_exit)
|
||||
module_snd_seq_driver(opl3_seq_driver);
|
||||
|
@ -124,8 +124,9 @@ static void snd_opl4_seq_free_port(void *private_data)
|
||||
snd_midi_channel_free_set(opl4->chset);
|
||||
}
|
||||
|
||||
static int snd_opl4_seq_new_device(struct snd_seq_device *dev)
|
||||
static int snd_opl4_seq_probe(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
struct snd_opl4 *opl4;
|
||||
int client;
|
||||
struct snd_seq_port_callback pcallbacks;
|
||||
@ -180,8 +181,9 @@ static int snd_opl4_seq_new_device(struct snd_seq_device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_opl4_seq_delete_device(struct snd_seq_device *dev)
|
||||
static int snd_opl4_seq_remove(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
struct snd_opl4 *opl4;
|
||||
|
||||
opl4 = *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
|
||||
@ -195,21 +197,14 @@ static int snd_opl4_seq_delete_device(struct snd_seq_device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init alsa_opl4_synth_init(void)
|
||||
{
|
||||
static struct snd_seq_dev_ops ops = {
|
||||
snd_opl4_seq_new_device,
|
||||
snd_opl4_seq_delete_device
|
||||
};
|
||||
static struct snd_seq_driver opl4_seq_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.probe = snd_opl4_seq_probe,
|
||||
.remove = snd_opl4_seq_remove,
|
||||
},
|
||||
.id = SNDRV_SEQ_DEV_ID_OPL4,
|
||||
.argsize = sizeof(struct snd_opl4 *),
|
||||
};
|
||||
|
||||
return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OPL4, &ops,
|
||||
sizeof(struct snd_opl4 *));
|
||||
}
|
||||
|
||||
static void __exit alsa_opl4_synth_exit(void)
|
||||
{
|
||||
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OPL4);
|
||||
}
|
||||
|
||||
module_init(alsa_opl4_synth_init)
|
||||
module_exit(alsa_opl4_synth_exit)
|
||||
module_snd_seq_driver(opl4_seq_driver);
|
||||
|
@ -166,10 +166,10 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
|
||||
* One AMDTP packet can include some frames. In blocking mode, the
|
||||
* number equals to SYT_INTERVAL. So the number is 8, 16 or 32,
|
||||
* depending on its sampling rate. For accurate period interrupt, it's
|
||||
* preferrable to aligh period/buffer sizes to current SYT_INTERVAL.
|
||||
* preferrable to align period/buffer sizes to current SYT_INTERVAL.
|
||||
*
|
||||
* TODO: These constraints can be improved with propper rules.
|
||||
* Currently apply LCM of SYT_INTEVALs.
|
||||
* TODO: These constraints can be improved with proper rules.
|
||||
* Currently apply LCM of SYT_INTERVALs.
|
||||
*/
|
||||
err = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32);
|
||||
@ -270,7 +270,7 @@ static void amdtp_read_s32(struct amdtp_stream *s,
|
||||
* @s: the AMDTP stream to configure
|
||||
* @format: the format of the ALSA PCM device
|
||||
*
|
||||
* The sample format must be set after the other paramters (rate/PCM channels/
|
||||
* The sample format must be set after the other parameters (rate/PCM channels/
|
||||
* MIDI) and before the stream is started, and must not be changed while the
|
||||
* stream is running.
|
||||
*/
|
||||
|
@ -13,7 +13,7 @@
|
||||
*
|
||||
* Transaction substance:
|
||||
* At first, 6 data exist. Following to the data, parameters for each command
|
||||
* exist. All of the parameters are 32 bit alighed to big endian.
|
||||
* exist. All of the parameters are 32 bit aligned to big endian.
|
||||
* data[0]: Length of transaction substance
|
||||
* data[1]: Transaction version
|
||||
* data[2]: Sequence number. This is incremented by the device
|
||||
|
3
sound/hda/Kconfig
Normal file
3
sound/hda/Kconfig
Normal file
@ -0,0 +1,3 @@
|
||||
config SND_HDA_CORE
|
||||
tristate
|
||||
select REGMAP
|
7
sound/hda/Makefile
Normal file
7
sound/hda/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
snd-hda-core-objs := hda_bus_type.o hdac_bus.o hdac_device.o hdac_sysfs.o \
|
||||
hdac_regmap.o array.o
|
||||
|
||||
snd-hda-core-objs += trace.o
|
||||
CFLAGS_trace.o := -I$(src)
|
||||
|
||||
obj-$(CONFIG_SND_HDA_CORE) += snd-hda-core.o
|
49
sound/hda/array.c
Normal file
49
sound/hda/array.c
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* generic arrays
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/hdaudio.h>
|
||||
|
||||
/**
|
||||
* snd_array_new - get a new element from the given array
|
||||
* @array: the array object
|
||||
*
|
||||
* Get a new element from the given array. If it exceeds the
|
||||
* pre-allocated array size, re-allocate the array.
|
||||
*
|
||||
* Returns NULL if allocation failed.
|
||||
*/
|
||||
void *snd_array_new(struct snd_array *array)
|
||||
{
|
||||
if (snd_BUG_ON(!array->elem_size))
|
||||
return NULL;
|
||||
if (array->used >= array->alloced) {
|
||||
int num = array->alloced + array->alloc_align;
|
||||
int size = (num + 1) * array->elem_size;
|
||||
void *nlist;
|
||||
if (snd_BUG_ON(num >= 4096))
|
||||
return NULL;
|
||||
nlist = krealloc(array->list, size, GFP_KERNEL | __GFP_ZERO);
|
||||
if (!nlist)
|
||||
return NULL;
|
||||
array->list = nlist;
|
||||
array->alloced = num;
|
||||
}
|
||||
return snd_array_elem(array, array->used++);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_array_new);
|
||||
|
||||
/**
|
||||
* snd_array_free - free the given array elements
|
||||
* @array: the array object
|
||||
*/
|
||||
void snd_array_free(struct snd_array *array)
|
||||
{
|
||||
kfree(array->list);
|
||||
array->used = 0;
|
||||
array->alloced = 0;
|
||||
array->list = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_array_free);
|
42
sound/hda/hda_bus_type.c
Normal file
42
sound/hda/hda_bus_type.c
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* HD-audio bus
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/export.h>
|
||||
#include <sound/hdaudio.h>
|
||||
|
||||
MODULE_DESCRIPTION("HD-audio bus");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int hda_bus_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
struct hdac_device *hdev = dev_to_hdac_dev(dev);
|
||||
struct hdac_driver *hdrv = drv_to_hdac_driver(drv);
|
||||
|
||||
if (hdev->type != hdrv->type)
|
||||
return 0;
|
||||
if (hdrv->match)
|
||||
return hdrv->match(hdev, hdrv);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct bus_type snd_hda_bus_type = {
|
||||
.name = "hdaudio",
|
||||
.match = hda_bus_match,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(snd_hda_bus_type);
|
||||
|
||||
static int __init hda_bus_init(void)
|
||||
{
|
||||
return bus_register(&snd_hda_bus_type);
|
||||
}
|
||||
|
||||
static void __exit hda_bus_exit(void)
|
||||
{
|
||||
bus_unregister(&snd_hda_bus_type);
|
||||
}
|
||||
|
||||
subsys_initcall(hda_bus_init);
|
||||
module_exit(hda_bus_exit);
|
186
sound/hda/hdac_bus.c
Normal file
186
sound/hda/hdac_bus.c
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* HD-audio core bus driver
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/export.h>
|
||||
#include <sound/hdaudio.h>
|
||||
#include "trace.h"
|
||||
|
||||
static void process_unsol_events(struct work_struct *work);
|
||||
|
||||
/**
|
||||
* snd_hdac_bus_init - initialize a HD-audio bas bus
|
||||
* @bus: the pointer to bus object
|
||||
*
|
||||
* Returns 0 if successful, or a negative error code.
|
||||
*/
|
||||
int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev,
|
||||
const struct hdac_bus_ops *ops)
|
||||
{
|
||||
memset(bus, 0, sizeof(*bus));
|
||||
bus->dev = dev;
|
||||
bus->ops = ops;
|
||||
INIT_LIST_HEAD(&bus->codec_list);
|
||||
INIT_WORK(&bus->unsol_work, process_unsol_events);
|
||||
mutex_init(&bus->cmd_mutex);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_bus_init);
|
||||
|
||||
/**
|
||||
* snd_hdac_bus_exit - clean up a HD-audio bas bus
|
||||
* @bus: the pointer to bus object
|
||||
*/
|
||||
void snd_hdac_bus_exit(struct hdac_bus *bus)
|
||||
{
|
||||
WARN_ON(!list_empty(&bus->codec_list));
|
||||
cancel_work_sync(&bus->unsol_work);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_bus_exit);
|
||||
|
||||
/**
|
||||
* snd_hdac_bus_exec_verb - execute a HD-audio verb on the given bus
|
||||
* @bus: bus object
|
||||
* @cmd: HD-audio encoded verb
|
||||
* @res: pointer to store the response, NULL if performing asynchronously
|
||||
*
|
||||
* Returns 0 if successful, or a negative error code.
|
||||
*/
|
||||
int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr,
|
||||
unsigned int cmd, unsigned int *res)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_lock(&bus->cmd_mutex);
|
||||
err = snd_hdac_bus_exec_verb_unlocked(bus, addr, cmd, res);
|
||||
mutex_unlock(&bus->cmd_mutex);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb);
|
||||
|
||||
/**
|
||||
* snd_hdac_bus_exec_verb_unlocked - unlocked version
|
||||
* @bus: bus object
|
||||
* @cmd: HD-audio encoded verb
|
||||
* @res: pointer to store the response, NULL if performing asynchronously
|
||||
*
|
||||
* Returns 0 if successful, or a negative error code.
|
||||
*/
|
||||
int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr,
|
||||
unsigned int cmd, unsigned int *res)
|
||||
{
|
||||
unsigned int tmp;
|
||||
int err;
|
||||
|
||||
if (cmd == ~0)
|
||||
return -EINVAL;
|
||||
|
||||
if (res)
|
||||
*res = -1;
|
||||
else if (bus->sync_write)
|
||||
res = &tmp;
|
||||
for (;;) {
|
||||
trace_hda_send_cmd(bus, cmd);
|
||||
err = bus->ops->command(bus, cmd);
|
||||
if (err != -EAGAIN)
|
||||
break;
|
||||
/* process pending verbs */
|
||||
err = bus->ops->get_response(bus, addr, &tmp);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
if (!err && res) {
|
||||
err = bus->ops->get_response(bus, addr, res);
|
||||
trace_hda_get_response(bus, addr, *res);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb_unlocked);
|
||||
|
||||
/**
|
||||
* snd_hdac_bus_queue_event - add an unsolicited event to queue
|
||||
* @bus: the BUS
|
||||
* @res: unsolicited event (lower 32bit of RIRB entry)
|
||||
* @res_ex: codec addr and flags (upper 32bit or RIRB entry)
|
||||
*
|
||||
* Adds the given event to the queue. The events are processed in
|
||||
* the workqueue asynchronously. Call this function in the interrupt
|
||||
* hanlder when RIRB receives an unsolicited event.
|
||||
*/
|
||||
void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex)
|
||||
{
|
||||
unsigned int wp;
|
||||
|
||||
if (!bus)
|
||||
return;
|
||||
|
||||
trace_hda_unsol_event(bus, res, res_ex);
|
||||
wp = (bus->unsol_wp + 1) % HDA_UNSOL_QUEUE_SIZE;
|
||||
bus->unsol_wp = wp;
|
||||
|
||||
wp <<= 1;
|
||||
bus->unsol_queue[wp] = res;
|
||||
bus->unsol_queue[wp + 1] = res_ex;
|
||||
|
||||
schedule_work(&bus->unsol_work);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_bus_queue_event);
|
||||
|
||||
/*
|
||||
* process queued unsolicited events
|
||||
*/
|
||||
static void process_unsol_events(struct work_struct *work)
|
||||
{
|
||||
struct hdac_bus *bus = container_of(work, struct hdac_bus, unsol_work);
|
||||
struct hdac_device *codec;
|
||||
struct hdac_driver *drv;
|
||||
unsigned int rp, caddr, res;
|
||||
|
||||
while (bus->unsol_rp != bus->unsol_wp) {
|
||||
rp = (bus->unsol_rp + 1) % HDA_UNSOL_QUEUE_SIZE;
|
||||
bus->unsol_rp = rp;
|
||||
rp <<= 1;
|
||||
res = bus->unsol_queue[rp];
|
||||
caddr = bus->unsol_queue[rp + 1];
|
||||
if (!(caddr & (1 << 4))) /* no unsolicited event? */
|
||||
continue;
|
||||
codec = bus->caddr_tbl[caddr & 0x0f];
|
||||
if (!codec || !codec->dev.driver)
|
||||
continue;
|
||||
drv = drv_to_hdac_driver(codec->dev.driver);
|
||||
if (drv->unsol_event)
|
||||
drv->unsol_event(codec, res);
|
||||
}
|
||||
}
|
||||
|
||||
int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec)
|
||||
{
|
||||
if (bus->caddr_tbl[codec->addr]) {
|
||||
dev_err(bus->dev, "address 0x%x is already occupied\n",
|
||||
codec->addr);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
list_add_tail(&codec->list, &bus->codec_list);
|
||||
bus->caddr_tbl[codec->addr] = codec;
|
||||
set_bit(codec->addr, &bus->codec_powered);
|
||||
bus->num_codecs++;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_bus_add_device);
|
||||
|
||||
void snd_hdac_bus_remove_device(struct hdac_bus *bus,
|
||||
struct hdac_device *codec)
|
||||
{
|
||||
WARN_ON(bus != codec->bus);
|
||||
if (list_empty(&codec->list))
|
||||
return;
|
||||
list_del_init(&codec->list);
|
||||
bus->caddr_tbl[codec->addr] = NULL;
|
||||
clear_bit(codec->addr, &bus->codec_powered);
|
||||
bus->num_codecs--;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_bus_remove_device);
|
569
sound/hda/hdac_device.c
Normal file
569
sound/hda/hdac_device.c
Normal file
@ -0,0 +1,569 @@
|
||||
/*
|
||||
* HD-audio codec core device
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <sound/hdaudio.h>
|
||||
#include <sound/hda_regmap.h>
|
||||
#include "local.h"
|
||||
|
||||
static void setup_fg_nodes(struct hdac_device *codec);
|
||||
static int get_codec_vendor_name(struct hdac_device *codec);
|
||||
|
||||
static void default_release(struct device *dev)
|
||||
{
|
||||
snd_hdac_device_exit(container_of(dev, struct hdac_device, dev));
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_device_init - initialize the HD-audio codec base device
|
||||
* @codec: device to initialize
|
||||
* @bus: but to attach
|
||||
* @name: device name string
|
||||
* @addr: codec address
|
||||
*
|
||||
* Returns zero for success or a negative error code.
|
||||
*
|
||||
* This function increments the runtime PM counter and marks it active.
|
||||
* The caller needs to turn it off appropriately later.
|
||||
*
|
||||
* The caller needs to set the device's release op properly by itself.
|
||||
*/
|
||||
int snd_hdac_device_init(struct hdac_device *codec, struct hdac_bus *bus,
|
||||
const char *name, unsigned int addr)
|
||||
{
|
||||
struct device *dev;
|
||||
hda_nid_t fg;
|
||||
int err;
|
||||
|
||||
dev = &codec->dev;
|
||||
device_initialize(dev);
|
||||
dev->parent = bus->dev;
|
||||
dev->bus = &snd_hda_bus_type;
|
||||
dev->release = default_release;
|
||||
dev->groups = hdac_dev_attr_groups;
|
||||
dev_set_name(dev, "%s", name);
|
||||
device_enable_async_suspend(dev);
|
||||
|
||||
codec->bus = bus;
|
||||
codec->addr = addr;
|
||||
codec->type = HDA_DEV_CORE;
|
||||
pm_runtime_set_active(&codec->dev);
|
||||
pm_runtime_get_noresume(&codec->dev);
|
||||
atomic_set(&codec->in_pm, 0);
|
||||
|
||||
err = snd_hdac_bus_add_device(bus, codec);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
/* fill parameters */
|
||||
codec->vendor_id = snd_hdac_read_parm(codec, AC_NODE_ROOT,
|
||||
AC_PAR_VENDOR_ID);
|
||||
if (codec->vendor_id == -1) {
|
||||
/* read again, hopefully the access method was corrected
|
||||
* in the last read...
|
||||
*/
|
||||
codec->vendor_id = snd_hdac_read_parm(codec, AC_NODE_ROOT,
|
||||
AC_PAR_VENDOR_ID);
|
||||
}
|
||||
|
||||
codec->subsystem_id = snd_hdac_read_parm(codec, AC_NODE_ROOT,
|
||||
AC_PAR_SUBSYSTEM_ID);
|
||||
codec->revision_id = snd_hdac_read_parm(codec, AC_NODE_ROOT,
|
||||
AC_PAR_REV_ID);
|
||||
|
||||
setup_fg_nodes(codec);
|
||||
if (!codec->afg && !codec->mfg) {
|
||||
dev_err(dev, "no AFG or MFG node found\n");
|
||||
err = -ENODEV;
|
||||
goto error;
|
||||
}
|
||||
|
||||
fg = codec->afg ? codec->afg : codec->mfg;
|
||||
|
||||
err = snd_hdac_refresh_widgets(codec);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
codec->power_caps = snd_hdac_read_parm(codec, fg, AC_PAR_POWER_STATE);
|
||||
/* reread ssid if not set by parameter */
|
||||
if (codec->subsystem_id == -1 || codec->subsystem_id == 0)
|
||||
snd_hdac_read(codec, fg, AC_VERB_GET_SUBSYSTEM_ID, 0,
|
||||
&codec->subsystem_id);
|
||||
|
||||
err = get_codec_vendor_name(codec);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
codec->chip_name = kasprintf(GFP_KERNEL, "ID %x",
|
||||
codec->vendor_id & 0xffff);
|
||||
if (!codec->chip_name) {
|
||||
err = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
put_device(&codec->dev);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_device_init);
|
||||
|
||||
/**
|
||||
* snd_hdac_device_exit - clean up the HD-audio codec base device
|
||||
* @codec: device to clean up
|
||||
*/
|
||||
void snd_hdac_device_exit(struct hdac_device *codec)
|
||||
{
|
||||
pm_runtime_put_noidle(&codec->dev);
|
||||
snd_hdac_bus_remove_device(codec->bus, codec);
|
||||
kfree(codec->vendor_name);
|
||||
kfree(codec->chip_name);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_device_exit);
|
||||
|
||||
/**
|
||||
* snd_hdac_device_register - register the hd-audio codec base device
|
||||
* codec: the device to register
|
||||
*/
|
||||
int snd_hdac_device_register(struct hdac_device *codec)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = device_add(&codec->dev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = hda_widget_sysfs_init(codec);
|
||||
if (err < 0) {
|
||||
device_del(&codec->dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_device_register);
|
||||
|
||||
/**
|
||||
* snd_hdac_device_unregister - unregister the hd-audio codec base device
|
||||
* codec: the device to unregister
|
||||
*/
|
||||
void snd_hdac_device_unregister(struct hdac_device *codec)
|
||||
{
|
||||
if (device_is_registered(&codec->dev)) {
|
||||
hda_widget_sysfs_exit(codec);
|
||||
device_del(&codec->dev);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_device_unregister);
|
||||
|
||||
/**
|
||||
* snd_hdac_make_cmd - compose a 32bit command word to be sent to the
|
||||
* HD-audio controller
|
||||
* @codec: the codec object
|
||||
* @nid: NID to encode
|
||||
* @verb: verb to encode
|
||||
* @parm: parameter to encode
|
||||
*
|
||||
* Return an encoded command verb or -1 for error.
|
||||
*/
|
||||
unsigned int snd_hdac_make_cmd(struct hdac_device *codec, hda_nid_t nid,
|
||||
unsigned int verb, unsigned int parm)
|
||||
{
|
||||
u32 val, addr;
|
||||
|
||||
addr = codec->addr;
|
||||
if ((addr & ~0xf) || (nid & ~0x7f) ||
|
||||
(verb & ~0xfff) || (parm & ~0xffff)) {
|
||||
dev_err(&codec->dev, "out of range cmd %x:%x:%x:%x\n",
|
||||
addr, nid, verb, parm);
|
||||
return -1;
|
||||
}
|
||||
|
||||
val = addr << 28;
|
||||
val |= (u32)nid << 20;
|
||||
val |= verb << 8;
|
||||
val |= parm;
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_make_cmd);
|
||||
|
||||
/**
|
||||
* snd_hdac_exec_verb - execute an encoded verb
|
||||
* @codec: the codec object
|
||||
* @cmd: encoded verb to execute
|
||||
* @flags: optional flags, pass zero for default
|
||||
* @res: the pointer to store the result, NULL if running async
|
||||
*
|
||||
* Returns zero if successful, or a negative error code.
|
||||
*
|
||||
* This calls the exec_verb op when set in hdac_codec. If not,
|
||||
* call the default snd_hdac_bus_exec_verb().
|
||||
*/
|
||||
int snd_hdac_exec_verb(struct hdac_device *codec, unsigned int cmd,
|
||||
unsigned int flags, unsigned int *res)
|
||||
{
|
||||
if (codec->exec_verb)
|
||||
return codec->exec_verb(codec, cmd, flags, res);
|
||||
return snd_hdac_bus_exec_verb(codec->bus, codec->addr, cmd, res);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_exec_verb);
|
||||
|
||||
|
||||
/**
|
||||
* snd_hdac_read - execute a verb
|
||||
* @codec: the codec object
|
||||
* @nid: NID to execute a verb
|
||||
* @verb: verb to execute
|
||||
* @parm: parameter for a verb
|
||||
* @res: the pointer to store the result, NULL if running async
|
||||
*
|
||||
* Returns zero if successful, or a negative error code.
|
||||
*/
|
||||
int snd_hdac_read(struct hdac_device *codec, hda_nid_t nid,
|
||||
unsigned int verb, unsigned int parm, unsigned int *res)
|
||||
{
|
||||
unsigned int cmd = snd_hdac_make_cmd(codec, nid, verb, parm);
|
||||
|
||||
return snd_hdac_exec_verb(codec, cmd, 0, res);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_read);
|
||||
|
||||
/**
|
||||
* _snd_hdac_read_parm - read a parmeter
|
||||
*
|
||||
* This function returns zero or an error unlike snd_hdac_read_parm().
|
||||
*/
|
||||
int _snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid, int parm,
|
||||
unsigned int *res)
|
||||
{
|
||||
unsigned int cmd;
|
||||
|
||||
cmd = snd_hdac_regmap_encode_verb(nid, AC_VERB_PARAMETERS) | parm;
|
||||
return snd_hdac_regmap_read_raw(codec, cmd, res);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(_snd_hdac_read_parm);
|
||||
|
||||
/**
|
||||
* snd_hdac_read_parm_uncached - read a codec parameter without caching
|
||||
* @codec: the codec object
|
||||
* @nid: NID to read a parameter
|
||||
* @parm: parameter to read
|
||||
*
|
||||
* Returns -1 for error. If you need to distinguish the error more
|
||||
* strictly, use snd_hdac_read() directly.
|
||||
*/
|
||||
int snd_hdac_read_parm_uncached(struct hdac_device *codec, hda_nid_t nid,
|
||||
int parm)
|
||||
{
|
||||
int val;
|
||||
|
||||
if (codec->regmap)
|
||||
regcache_cache_bypass(codec->regmap, true);
|
||||
val = snd_hdac_read_parm(codec, nid, parm);
|
||||
if (codec->regmap)
|
||||
regcache_cache_bypass(codec->regmap, false);
|
||||
return val;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_read_parm_uncached);
|
||||
|
||||
/**
|
||||
* snd_hdac_override_parm - override read-only parameters
|
||||
* @codec: the codec object
|
||||
* @nid: NID for the parameter
|
||||
* @parm: the parameter to change
|
||||
* @val: the parameter value to overwrite
|
||||
*/
|
||||
int snd_hdac_override_parm(struct hdac_device *codec, hda_nid_t nid,
|
||||
unsigned int parm, unsigned int val)
|
||||
{
|
||||
unsigned int verb = (AC_VERB_PARAMETERS << 8) | (nid << 20) | parm;
|
||||
int err;
|
||||
|
||||
if (!codec->regmap)
|
||||
return -EINVAL;
|
||||
|
||||
codec->caps_overwriting = true;
|
||||
err = snd_hdac_regmap_write_raw(codec, verb, val);
|
||||
codec->caps_overwriting = false;
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_override_parm);
|
||||
|
||||
/**
|
||||
* snd_hdac_get_sub_nodes - get start NID and number of subtree nodes
|
||||
* @codec: the codec object
|
||||
* @nid: NID to inspect
|
||||
* @start_id: the pointer to store the starting NID
|
||||
*
|
||||
* Returns the number of subtree nodes or zero if not found.
|
||||
* This function reads parameters always without caching.
|
||||
*/
|
||||
int snd_hdac_get_sub_nodes(struct hdac_device *codec, hda_nid_t nid,
|
||||
hda_nid_t *start_id)
|
||||
{
|
||||
unsigned int parm;
|
||||
|
||||
parm = snd_hdac_read_parm_uncached(codec, nid, AC_PAR_NODE_COUNT);
|
||||
if (parm == -1) {
|
||||
*start_id = 0;
|
||||
return 0;
|
||||
}
|
||||
*start_id = (parm >> 16) & 0x7fff;
|
||||
return (int)(parm & 0x7fff);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_get_sub_nodes);
|
||||
|
||||
/*
|
||||
* look for an AFG and MFG nodes
|
||||
*/
|
||||
static void setup_fg_nodes(struct hdac_device *codec)
|
||||
{
|
||||
int i, total_nodes, function_id;
|
||||
hda_nid_t nid;
|
||||
|
||||
total_nodes = snd_hdac_get_sub_nodes(codec, AC_NODE_ROOT, &nid);
|
||||
for (i = 0; i < total_nodes; i++, nid++) {
|
||||
function_id = snd_hdac_read_parm(codec, nid,
|
||||
AC_PAR_FUNCTION_TYPE);
|
||||
switch (function_id & 0xff) {
|
||||
case AC_GRP_AUDIO_FUNCTION:
|
||||
codec->afg = nid;
|
||||
codec->afg_function_id = function_id & 0xff;
|
||||
codec->afg_unsol = (function_id >> 8) & 1;
|
||||
break;
|
||||
case AC_GRP_MODEM_FUNCTION:
|
||||
codec->mfg = nid;
|
||||
codec->mfg_function_id = function_id & 0xff;
|
||||
codec->mfg_unsol = (function_id >> 8) & 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_refresh_widgets - Reset the widget start/end nodes
|
||||
* @codec: the codec object
|
||||
*/
|
||||
int snd_hdac_refresh_widgets(struct hdac_device *codec)
|
||||
{
|
||||
hda_nid_t start_nid;
|
||||
int nums;
|
||||
|
||||
nums = snd_hdac_get_sub_nodes(codec, codec->afg, &start_nid);
|
||||
if (!start_nid || nums <= 0 || nums >= 0xff) {
|
||||
dev_err(&codec->dev, "cannot read sub nodes for FG 0x%02x\n",
|
||||
codec->afg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
codec->num_nodes = nums;
|
||||
codec->start_nid = start_nid;
|
||||
codec->end_nid = start_nid + nums;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_refresh_widgets);
|
||||
|
||||
/* return CONNLIST_LEN parameter of the given widget */
|
||||
static unsigned int get_num_conns(struct hdac_device *codec, hda_nid_t nid)
|
||||
{
|
||||
unsigned int wcaps = get_wcaps(codec, nid);
|
||||
unsigned int parm;
|
||||
|
||||
if (!(wcaps & AC_WCAP_CONN_LIST) &&
|
||||
get_wcaps_type(wcaps) != AC_WID_VOL_KNB)
|
||||
return 0;
|
||||
|
||||
parm = snd_hdac_read_parm(codec, nid, AC_PAR_CONNLIST_LEN);
|
||||
if (parm == -1)
|
||||
parm = 0;
|
||||
return parm;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_get_connections - get a widget connection list
|
||||
* @codec: the codec object
|
||||
* @nid: NID
|
||||
* @conn_list: the array to store the results, can be NULL
|
||||
* @max_conns: the max size of the given array
|
||||
*
|
||||
* Returns the number of connected widgets, zero for no connection, or a
|
||||
* negative error code. When the number of elements don't fit with the
|
||||
* given array size, it returns -ENOSPC.
|
||||
*
|
||||
* When @conn_list is NULL, it just checks the number of connections.
|
||||
*/
|
||||
int snd_hdac_get_connections(struct hdac_device *codec, hda_nid_t nid,
|
||||
hda_nid_t *conn_list, int max_conns)
|
||||
{
|
||||
unsigned int parm;
|
||||
int i, conn_len, conns, err;
|
||||
unsigned int shift, num_elems, mask;
|
||||
hda_nid_t prev_nid;
|
||||
int null_count = 0;
|
||||
|
||||
parm = get_num_conns(codec, nid);
|
||||
if (!parm)
|
||||
return 0;
|
||||
|
||||
if (parm & AC_CLIST_LONG) {
|
||||
/* long form */
|
||||
shift = 16;
|
||||
num_elems = 2;
|
||||
} else {
|
||||
/* short form */
|
||||
shift = 8;
|
||||
num_elems = 4;
|
||||
}
|
||||
conn_len = parm & AC_CLIST_LENGTH;
|
||||
mask = (1 << (shift-1)) - 1;
|
||||
|
||||
if (!conn_len)
|
||||
return 0; /* no connection */
|
||||
|
||||
if (conn_len == 1) {
|
||||
/* single connection */
|
||||
err = snd_hdac_read(codec, nid, AC_VERB_GET_CONNECT_LIST, 0,
|
||||
&parm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (conn_list)
|
||||
conn_list[0] = parm & mask;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* multi connection */
|
||||
conns = 0;
|
||||
prev_nid = 0;
|
||||
for (i = 0; i < conn_len; i++) {
|
||||
int range_val;
|
||||
hda_nid_t val, n;
|
||||
|
||||
if (i % num_elems == 0) {
|
||||
err = snd_hdac_read(codec, nid,
|
||||
AC_VERB_GET_CONNECT_LIST, i,
|
||||
&parm);
|
||||
if (err < 0)
|
||||
return -EIO;
|
||||
}
|
||||
range_val = !!(parm & (1 << (shift-1))); /* ranges */
|
||||
val = parm & mask;
|
||||
if (val == 0 && null_count++) { /* no second chance */
|
||||
dev_dbg(&codec->dev,
|
||||
"invalid CONNECT_LIST verb %x[%i]:%x\n",
|
||||
nid, i, parm);
|
||||
return 0;
|
||||
}
|
||||
parm >>= shift;
|
||||
if (range_val) {
|
||||
/* ranges between the previous and this one */
|
||||
if (!prev_nid || prev_nid >= val) {
|
||||
dev_warn(&codec->dev,
|
||||
"invalid dep_range_val %x:%x\n",
|
||||
prev_nid, val);
|
||||
continue;
|
||||
}
|
||||
for (n = prev_nid + 1; n <= val; n++) {
|
||||
if (conn_list) {
|
||||
if (conns >= max_conns)
|
||||
return -ENOSPC;
|
||||
conn_list[conns] = n;
|
||||
}
|
||||
conns++;
|
||||
}
|
||||
} else {
|
||||
if (conn_list) {
|
||||
if (conns >= max_conns)
|
||||
return -ENOSPC;
|
||||
conn_list[conns] = val;
|
||||
}
|
||||
conns++;
|
||||
}
|
||||
prev_nid = val;
|
||||
}
|
||||
return conns;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_get_connections);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
/**
|
||||
* snd_hdac_power_up - power up the codec
|
||||
* @codec: the codec object
|
||||
*
|
||||
* This function calls the runtime PM helper to power up the given codec.
|
||||
* Unlike snd_hdac_power_up_pm(), you should call this only for the code
|
||||
* path that isn't included in PM path. Otherwise it gets stuck.
|
||||
*/
|
||||
void snd_hdac_power_up(struct hdac_device *codec)
|
||||
{
|
||||
pm_runtime_get_sync(&codec->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_power_up);
|
||||
|
||||
/**
|
||||
* snd_hdac_power_down - power down the codec
|
||||
* @codec: the codec object
|
||||
*/
|
||||
void snd_hdac_power_down(struct hdac_device *codec)
|
||||
{
|
||||
struct device *dev = &codec->dev;
|
||||
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_power_down);
|
||||
#endif
|
||||
|
||||
/* codec vendor labels */
|
||||
struct hda_vendor_id {
|
||||
unsigned int id;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
static struct hda_vendor_id hda_vendor_ids[] = {
|
||||
{ 0x1002, "ATI" },
|
||||
{ 0x1013, "Cirrus Logic" },
|
||||
{ 0x1057, "Motorola" },
|
||||
{ 0x1095, "Silicon Image" },
|
||||
{ 0x10de, "Nvidia" },
|
||||
{ 0x10ec, "Realtek" },
|
||||
{ 0x1102, "Creative" },
|
||||
{ 0x1106, "VIA" },
|
||||
{ 0x111d, "IDT" },
|
||||
{ 0x11c1, "LSI" },
|
||||
{ 0x11d4, "Analog Devices" },
|
||||
{ 0x13f6, "C-Media" },
|
||||
{ 0x14f1, "Conexant" },
|
||||
{ 0x17e8, "Chrontel" },
|
||||
{ 0x1854, "LG" },
|
||||
{ 0x1aec, "Wolfson Microelectronics" },
|
||||
{ 0x1af4, "QEMU" },
|
||||
{ 0x434d, "C-Media" },
|
||||
{ 0x8086, "Intel" },
|
||||
{ 0x8384, "SigmaTel" },
|
||||
{} /* terminator */
|
||||
};
|
||||
|
||||
/* store the codec vendor name */
|
||||
static int get_codec_vendor_name(struct hdac_device *codec)
|
||||
{
|
||||
const struct hda_vendor_id *c;
|
||||
u16 vendor_id = codec->vendor_id >> 16;
|
||||
|
||||
for (c = hda_vendor_ids; c->id; c++) {
|
||||
if (c->id == vendor_id) {
|
||||
codec->vendor_name = kstrdup(c->name, GFP_KERNEL);
|
||||
return codec->vendor_name ? 0 : -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
codec->vendor_name = kasprintf(GFP_KERNEL, "Generic %04x", vendor_id);
|
||||
return codec->vendor_name ? 0 : -ENOMEM;
|
||||
}
|
472
sound/hda/hdac_regmap.c
Normal file
472
sound/hda/hdac_regmap.c
Normal file
@ -0,0 +1,472 @@
|
||||
/*
|
||||
* Regmap support for HD-audio verbs
|
||||
*
|
||||
* A virtual register is translated to one or more hda verbs for write,
|
||||
* vice versa for read.
|
||||
*
|
||||
* A few limitations:
|
||||
* - Provided for not all verbs but only subset standard non-volatile verbs.
|
||||
* - For reading, only AC_VERB_GET_* variants can be used.
|
||||
* - For writing, mapped to the *corresponding* AC_VERB_SET_* variants,
|
||||
* so can't handle asymmetric verbs for read and write
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/hdaudio.h>
|
||||
#include <sound/hda_regmap.h>
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
#define codec_is_running(codec) \
|
||||
(atomic_read(&(codec)->in_pm) || \
|
||||
!pm_runtime_suspended(&(codec)->dev))
|
||||
#else
|
||||
#define codec_is_running(codec) true
|
||||
#endif
|
||||
|
||||
#define get_verb(reg) (((reg) >> 8) & 0xfff)
|
||||
|
||||
static bool hda_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
struct hdac_device *codec = dev_to_hdac_dev(dev);
|
||||
unsigned int verb = get_verb(reg);
|
||||
|
||||
switch (verb) {
|
||||
case AC_VERB_GET_PROC_COEF:
|
||||
return !codec->cache_coef;
|
||||
case AC_VERB_GET_COEF_INDEX:
|
||||
case AC_VERB_GET_PROC_STATE:
|
||||
case AC_VERB_GET_POWER_STATE:
|
||||
case AC_VERB_GET_PIN_SENSE:
|
||||
case AC_VERB_GET_HDMI_DIP_SIZE:
|
||||
case AC_VERB_GET_HDMI_ELDD:
|
||||
case AC_VERB_GET_HDMI_DIP_INDEX:
|
||||
case AC_VERB_GET_HDMI_DIP_DATA:
|
||||
case AC_VERB_GET_HDMI_DIP_XMIT:
|
||||
case AC_VERB_GET_HDMI_CP_CTRL:
|
||||
case AC_VERB_GET_HDMI_CHAN_SLOT:
|
||||
case AC_VERB_GET_DEVICE_SEL:
|
||||
case AC_VERB_GET_DEVICE_LIST: /* read-only volatile */
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool hda_writeable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
struct hdac_device *codec = dev_to_hdac_dev(dev);
|
||||
unsigned int verb = get_verb(reg);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < codec->vendor_verbs.used; i++) {
|
||||
unsigned int *v = snd_array_elem(&codec->vendor_verbs, i);
|
||||
if (verb == *v)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (codec->caps_overwriting)
|
||||
return true;
|
||||
|
||||
switch (verb & 0xf00) {
|
||||
case AC_VERB_GET_STREAM_FORMAT:
|
||||
case AC_VERB_GET_AMP_GAIN_MUTE:
|
||||
return true;
|
||||
case AC_VERB_GET_PROC_COEF:
|
||||
return codec->cache_coef;
|
||||
case 0xf00:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (verb) {
|
||||
case AC_VERB_GET_CONNECT_SEL:
|
||||
case AC_VERB_GET_SDI_SELECT:
|
||||
case AC_VERB_GET_PIN_WIDGET_CONTROL:
|
||||
case AC_VERB_GET_UNSOLICITED_RESPONSE: /* only as SET_UNSOLICITED_ENABLE */
|
||||
case AC_VERB_GET_BEEP_CONTROL:
|
||||
case AC_VERB_GET_EAPD_BTLENABLE:
|
||||
case AC_VERB_GET_DIGI_CONVERT_1:
|
||||
case AC_VERB_GET_DIGI_CONVERT_2: /* only for beep control */
|
||||
case AC_VERB_GET_VOLUME_KNOB_CONTROL:
|
||||
case AC_VERB_GET_GPIO_MASK:
|
||||
case AC_VERB_GET_GPIO_DIRECTION:
|
||||
case AC_VERB_GET_GPIO_DATA: /* not for volatile read */
|
||||
case AC_VERB_GET_GPIO_WAKE_MASK:
|
||||
case AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK:
|
||||
case AC_VERB_GET_GPIO_STICKY_MASK:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool hda_readable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
struct hdac_device *codec = dev_to_hdac_dev(dev);
|
||||
unsigned int verb = get_verb(reg);
|
||||
|
||||
if (codec->caps_overwriting)
|
||||
return true;
|
||||
|
||||
switch (verb) {
|
||||
case AC_VERB_PARAMETERS:
|
||||
case AC_VERB_GET_CONNECT_LIST:
|
||||
case AC_VERB_GET_SUBSYSTEM_ID:
|
||||
return true;
|
||||
/* below are basically writable, but disabled for reducing unnecessary
|
||||
* writes at sync
|
||||
*/
|
||||
case AC_VERB_GET_CONFIG_DEFAULT: /* usually just read */
|
||||
case AC_VERB_GET_CONV: /* managed in PCM code */
|
||||
case AC_VERB_GET_CVT_CHAN_COUNT: /* managed in HDMI CA code */
|
||||
return true;
|
||||
}
|
||||
|
||||
return hda_writeable_reg(dev, reg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stereo amp pseudo register:
|
||||
* for making easier to handle the stereo volume control, we provide a
|
||||
* fake register to deal both left and right channels by a single
|
||||
* (pseudo) register access. A verb consisting of SET_AMP_GAIN with
|
||||
* *both* SET_LEFT and SET_RIGHT bits takes a 16bit value, the lower 8bit
|
||||
* for the left and the upper 8bit for the right channel.
|
||||
*/
|
||||
static bool is_stereo_amp_verb(unsigned int reg)
|
||||
{
|
||||
if (((reg >> 8) & 0x700) != AC_VERB_SET_AMP_GAIN_MUTE)
|
||||
return false;
|
||||
return (reg & (AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT)) ==
|
||||
(AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT);
|
||||
}
|
||||
|
||||
/* read a pseudo stereo amp register (16bit left+right) */
|
||||
static int hda_reg_read_stereo_amp(struct hdac_device *codec,
|
||||
unsigned int reg, unsigned int *val)
|
||||
{
|
||||
unsigned int left, right;
|
||||
int err;
|
||||
|
||||
reg &= ~(AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT);
|
||||
err = snd_hdac_exec_verb(codec, reg | AC_AMP_GET_LEFT, 0, &left);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_hdac_exec_verb(codec, reg | AC_AMP_GET_RIGHT, 0, &right);
|
||||
if (err < 0)
|
||||
return err;
|
||||
*val = left | (right << 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* write a pseudo stereo amp register (16bit left+right) */
|
||||
static int hda_reg_write_stereo_amp(struct hdac_device *codec,
|
||||
unsigned int reg, unsigned int val)
|
||||
{
|
||||
int err;
|
||||
unsigned int verb, left, right;
|
||||
|
||||
verb = AC_VERB_SET_AMP_GAIN_MUTE << 8;
|
||||
if (reg & AC_AMP_GET_OUTPUT)
|
||||
verb |= AC_AMP_SET_OUTPUT;
|
||||
else
|
||||
verb |= AC_AMP_SET_INPUT | ((reg & 0xf) << 8);
|
||||
reg = (reg & ~0xfffff) | verb;
|
||||
|
||||
left = val & 0xff;
|
||||
right = (val >> 8) & 0xff;
|
||||
if (left == right) {
|
||||
reg |= AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT;
|
||||
return snd_hdac_exec_verb(codec, reg | left, 0, NULL);
|
||||
}
|
||||
|
||||
err = snd_hdac_exec_verb(codec, reg | AC_AMP_SET_LEFT | left, 0, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_hdac_exec_verb(codec, reg | AC_AMP_SET_RIGHT | right, 0, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* read a pseudo coef register (16bit) */
|
||||
static int hda_reg_read_coef(struct hdac_device *codec, unsigned int reg,
|
||||
unsigned int *val)
|
||||
{
|
||||
unsigned int verb;
|
||||
int err;
|
||||
|
||||
if (!codec->cache_coef)
|
||||
return -EINVAL;
|
||||
/* LSB 8bit = coef index */
|
||||
verb = (reg & ~0xfff00) | (AC_VERB_SET_COEF_INDEX << 8);
|
||||
err = snd_hdac_exec_verb(codec, verb, 0, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8);
|
||||
return snd_hdac_exec_verb(codec, verb, 0, val);
|
||||
}
|
||||
|
||||
/* write a pseudo coef register (16bit) */
|
||||
static int hda_reg_write_coef(struct hdac_device *codec, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
unsigned int verb;
|
||||
int err;
|
||||
|
||||
if (!codec->cache_coef)
|
||||
return -EINVAL;
|
||||
/* LSB 8bit = coef index */
|
||||
verb = (reg & ~0xfff00) | (AC_VERB_SET_COEF_INDEX << 8);
|
||||
err = snd_hdac_exec_verb(codec, verb, 0, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8) |
|
||||
(val & 0xffff);
|
||||
return snd_hdac_exec_verb(codec, verb, 0, NULL);
|
||||
}
|
||||
|
||||
static int hda_reg_read(void *context, unsigned int reg, unsigned int *val)
|
||||
{
|
||||
struct hdac_device *codec = context;
|
||||
int verb = get_verb(reg);
|
||||
int err;
|
||||
|
||||
if (!codec_is_running(codec) && verb != AC_VERB_GET_POWER_STATE)
|
||||
return -EAGAIN;
|
||||
reg |= (codec->addr << 28);
|
||||
if (is_stereo_amp_verb(reg))
|
||||
return hda_reg_read_stereo_amp(codec, reg, val);
|
||||
if (verb == AC_VERB_GET_PROC_COEF)
|
||||
return hda_reg_read_coef(codec, reg, val);
|
||||
err = snd_hdac_exec_verb(codec, reg, 0, val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
/* special handling for asymmetric reads */
|
||||
if (verb == AC_VERB_GET_POWER_STATE) {
|
||||
if (*val & AC_PWRST_ERROR)
|
||||
*val = -1;
|
||||
else /* take only the actual state */
|
||||
*val = (*val >> 4) & 0x0f;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
|
||||
{
|
||||
struct hdac_device *codec = context;
|
||||
unsigned int verb;
|
||||
int i, bytes, err;
|
||||
|
||||
reg &= ~0x00080000U; /* drop GET bit */
|
||||
reg |= (codec->addr << 28);
|
||||
verb = get_verb(reg);
|
||||
|
||||
if (!codec_is_running(codec) && verb != AC_VERB_SET_POWER_STATE)
|
||||
return codec->lazy_cache ? 0 : -EAGAIN;
|
||||
|
||||
if (is_stereo_amp_verb(reg))
|
||||
return hda_reg_write_stereo_amp(codec, reg, val);
|
||||
|
||||
if (verb == AC_VERB_SET_PROC_COEF)
|
||||
return hda_reg_write_coef(codec, reg, val);
|
||||
|
||||
switch (verb & 0xf00) {
|
||||
case AC_VERB_SET_AMP_GAIN_MUTE:
|
||||
verb = AC_VERB_SET_AMP_GAIN_MUTE;
|
||||
if (reg & AC_AMP_GET_LEFT)
|
||||
verb |= AC_AMP_SET_LEFT >> 8;
|
||||
else
|
||||
verb |= AC_AMP_SET_RIGHT >> 8;
|
||||
if (reg & AC_AMP_GET_OUTPUT) {
|
||||
verb |= AC_AMP_SET_OUTPUT >> 8;
|
||||
} else {
|
||||
verb |= AC_AMP_SET_INPUT >> 8;
|
||||
verb |= reg & 0xf;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (verb) {
|
||||
case AC_VERB_SET_DIGI_CONVERT_1:
|
||||
bytes = 2;
|
||||
break;
|
||||
case AC_VERB_SET_CONFIG_DEFAULT_BYTES_0:
|
||||
bytes = 4;
|
||||
break;
|
||||
default:
|
||||
bytes = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < bytes; i++) {
|
||||
reg &= ~0xfffff;
|
||||
reg |= (verb + i) << 8 | ((val >> (8 * i)) & 0xff);
|
||||
err = snd_hdac_exec_verb(codec, reg, 0, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct regmap_config hda_regmap_cfg = {
|
||||
.name = "hdaudio",
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
.max_register = 0xfffffff,
|
||||
.writeable_reg = hda_writeable_reg,
|
||||
.readable_reg = hda_readable_reg,
|
||||
.volatile_reg = hda_volatile_reg,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.reg_read = hda_reg_read,
|
||||
.reg_write = hda_reg_write,
|
||||
.use_single_rw = true,
|
||||
};
|
||||
|
||||
int snd_hdac_regmap_init(struct hdac_device *codec)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = regmap_init(&codec->dev, NULL, codec, &hda_regmap_cfg);
|
||||
if (IS_ERR(regmap))
|
||||
return PTR_ERR(regmap);
|
||||
codec->regmap = regmap;
|
||||
snd_array_init(&codec->vendor_verbs, sizeof(unsigned int), 8);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_regmap_init);
|
||||
|
||||
void snd_hdac_regmap_exit(struct hdac_device *codec)
|
||||
{
|
||||
if (codec->regmap) {
|
||||
regmap_exit(codec->regmap);
|
||||
codec->regmap = NULL;
|
||||
snd_array_free(&codec->vendor_verbs);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_regmap_exit);
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_add_vendor_verb - add a vendor-specific verb to regmap
|
||||
* @codec: the codec object
|
||||
* @verb: verb to allow accessing via regmap
|
||||
*
|
||||
* Returns zero for success or a negative error code.
|
||||
*/
|
||||
int snd_hdac_regmap_add_vendor_verb(struct hdac_device *codec,
|
||||
unsigned int verb)
|
||||
{
|
||||
unsigned int *p = snd_array_new(&codec->vendor_verbs);
|
||||
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
*p = verb;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_regmap_add_vendor_verb);
|
||||
|
||||
/*
|
||||
* helper functions
|
||||
*/
|
||||
|
||||
/* write a pseudo-register value (w/o power sequence) */
|
||||
static int reg_raw_write(struct hdac_device *codec, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
if (!codec->regmap)
|
||||
return hda_reg_write(codec, reg, val);
|
||||
else
|
||||
return regmap_write(codec->regmap, reg, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_write_raw - write a pseudo register with power mgmt
|
||||
* @codec: the codec object
|
||||
* @reg: pseudo register
|
||||
* @val: value to write
|
||||
*
|
||||
* Returns zero if successful or a negative error code.
|
||||
*/
|
||||
int snd_hdac_regmap_write_raw(struct hdac_device *codec, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = reg_raw_write(codec, reg, val);
|
||||
if (err == -EAGAIN) {
|
||||
snd_hdac_power_up_pm(codec);
|
||||
err = reg_raw_write(codec, reg, val);
|
||||
snd_hdac_power_down_pm(codec);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_regmap_write_raw);
|
||||
|
||||
static int reg_raw_read(struct hdac_device *codec, unsigned int reg,
|
||||
unsigned int *val)
|
||||
{
|
||||
if (!codec->regmap)
|
||||
return hda_reg_read(codec, reg, val);
|
||||
else
|
||||
return regmap_read(codec->regmap, reg, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_read_raw - read a pseudo register with power mgmt
|
||||
* @codec: the codec object
|
||||
* @reg: pseudo register
|
||||
* @val: pointer to store the read value
|
||||
*
|
||||
* Returns zero if successful or a negative error code.
|
||||
*/
|
||||
int snd_hdac_regmap_read_raw(struct hdac_device *codec, unsigned int reg,
|
||||
unsigned int *val)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = reg_raw_read(codec, reg, val);
|
||||
if (err == -EAGAIN) {
|
||||
snd_hdac_power_up_pm(codec);
|
||||
err = reg_raw_read(codec, reg, val);
|
||||
snd_hdac_power_down_pm(codec);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_regmap_read_raw);
|
||||
|
||||
/**
|
||||
* snd_hdac_regmap_update_raw - update a pseudo register with power mgmt
|
||||
* @codec: the codec object
|
||||
* @reg: pseudo register
|
||||
* @mask: bit mask to udpate
|
||||
* @val: value to update
|
||||
*
|
||||
* Returns zero if successful or a negative error code.
|
||||
*/
|
||||
int snd_hdac_regmap_update_raw(struct hdac_device *codec, unsigned int reg,
|
||||
unsigned int mask, unsigned int val)
|
||||
{
|
||||
unsigned int orig;
|
||||
int err;
|
||||
|
||||
val &= mask;
|
||||
err = snd_hdac_regmap_read_raw(codec, reg, &orig);
|
||||
if (err < 0)
|
||||
return err;
|
||||
val |= orig & ~mask;
|
||||
if (val == orig)
|
||||
return 0;
|
||||
err = snd_hdac_regmap_write_raw(codec, reg, val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
return 1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_regmap_update_raw);
|
404
sound/hda/hdac_sysfs.c
Normal file
404
sound/hda/hdac_sysfs.c
Normal file
@ -0,0 +1,404 @@
|
||||
/*
|
||||
* sysfs support for HD-audio core device
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/hdaudio.h>
|
||||
#include "local.h"
|
||||
|
||||
struct hdac_widget_tree {
|
||||
struct kobject *root;
|
||||
struct kobject *afg;
|
||||
struct kobject **nodes;
|
||||
};
|
||||
|
||||
#define CODEC_ATTR(type) \
|
||||
static ssize_t type##_show(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct hdac_device *codec = dev_to_hdac_dev(dev); \
|
||||
return sprintf(buf, "0x%x\n", codec->type); \
|
||||
} \
|
||||
static DEVICE_ATTR_RO(type)
|
||||
|
||||
#define CODEC_ATTR_STR(type) \
|
||||
static ssize_t type##_show(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct hdac_device *codec = dev_to_hdac_dev(dev); \
|
||||
return sprintf(buf, "%s\n", \
|
||||
codec->type ? codec->type : ""); \
|
||||
} \
|
||||
static DEVICE_ATTR_RO(type)
|
||||
|
||||
CODEC_ATTR(vendor_id);
|
||||
CODEC_ATTR(subsystem_id);
|
||||
CODEC_ATTR(revision_id);
|
||||
CODEC_ATTR(afg);
|
||||
CODEC_ATTR(mfg);
|
||||
CODEC_ATTR_STR(vendor_name);
|
||||
CODEC_ATTR_STR(chip_name);
|
||||
|
||||
static struct attribute *hdac_dev_attrs[] = {
|
||||
&dev_attr_vendor_id.attr,
|
||||
&dev_attr_subsystem_id.attr,
|
||||
&dev_attr_revision_id.attr,
|
||||
&dev_attr_afg.attr,
|
||||
&dev_attr_mfg.attr,
|
||||
&dev_attr_vendor_name.attr,
|
||||
&dev_attr_chip_name.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group hdac_dev_attr_group = {
|
||||
.attrs = hdac_dev_attrs,
|
||||
};
|
||||
|
||||
const struct attribute_group *hdac_dev_attr_groups[] = {
|
||||
&hdac_dev_attr_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
/*
|
||||
* Widget tree sysfs
|
||||
*
|
||||
* This is a tree showing the attributes of each widget. It appears like
|
||||
* /sys/bus/hdaudioC0D0/widgets/04/caps
|
||||
*/
|
||||
|
||||
struct widget_attribute;
|
||||
|
||||
struct widget_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf);
|
||||
ssize_t (*store)(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
};
|
||||
|
||||
static int get_codec_nid(struct kobject *kobj, struct hdac_device **codecp)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj->parent->parent);
|
||||
int nid;
|
||||
ssize_t ret;
|
||||
|
||||
ret = kstrtoint(kobj->name, 16, &nid);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*codecp = dev_to_hdac_dev(dev);
|
||||
return nid;
|
||||
}
|
||||
|
||||
static ssize_t widget_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct widget_attribute *wid_attr =
|
||||
container_of(attr, struct widget_attribute, attr);
|
||||
struct hdac_device *codec;
|
||||
int nid;
|
||||
|
||||
if (!wid_attr->show)
|
||||
return -EIO;
|
||||
nid = get_codec_nid(kobj, &codec);
|
||||
if (nid < 0)
|
||||
return nid;
|
||||
return wid_attr->show(codec, nid, wid_attr, buf);
|
||||
}
|
||||
|
||||
static ssize_t widget_attr_store(struct kobject *kobj, struct attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct widget_attribute *wid_attr =
|
||||
container_of(attr, struct widget_attribute, attr);
|
||||
struct hdac_device *codec;
|
||||
int nid;
|
||||
|
||||
if (!wid_attr->store)
|
||||
return -EIO;
|
||||
nid = get_codec_nid(kobj, &codec);
|
||||
if (nid < 0)
|
||||
return nid;
|
||||
return wid_attr->store(codec, nid, wid_attr, buf, count);
|
||||
}
|
||||
|
||||
static const struct sysfs_ops widget_sysfs_ops = {
|
||||
.show = widget_attr_show,
|
||||
.store = widget_attr_store,
|
||||
};
|
||||
|
||||
static void widget_release(struct kobject *kobj)
|
||||
{
|
||||
kfree(kobj);
|
||||
}
|
||||
|
||||
static struct kobj_type widget_ktype = {
|
||||
.release = widget_release,
|
||||
.sysfs_ops = &widget_sysfs_ops,
|
||||
};
|
||||
|
||||
#define WIDGET_ATTR_RO(_name) \
|
||||
struct widget_attribute wid_attr_##_name = __ATTR_RO(_name)
|
||||
#define WIDGET_ATTR_RW(_name) \
|
||||
struct widget_attribute wid_attr_##_name = __ATTR_RW(_name)
|
||||
|
||||
static ssize_t caps_show(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "0x%08x\n", get_wcaps(codec, nid));
|
||||
}
|
||||
|
||||
static ssize_t pin_caps_show(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf)
|
||||
{
|
||||
if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
|
||||
return 0;
|
||||
return sprintf(buf, "0x%08x\n",
|
||||
snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP));
|
||||
}
|
||||
|
||||
static ssize_t pin_cfg_show(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf)
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
|
||||
return 0;
|
||||
if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, 0, &val))
|
||||
return 0;
|
||||
return sprintf(buf, "0x%08x\n", val);
|
||||
}
|
||||
|
||||
static bool has_pcm_cap(struct hdac_device *codec, hda_nid_t nid)
|
||||
{
|
||||
if (nid == codec->afg || nid == codec->mfg)
|
||||
return true;
|
||||
switch (get_wcaps_type(get_wcaps(codec, nid))) {
|
||||
case AC_WID_AUD_OUT:
|
||||
case AC_WID_AUD_IN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t pcm_caps_show(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf)
|
||||
{
|
||||
if (!has_pcm_cap(codec, nid))
|
||||
return 0;
|
||||
return sprintf(buf, "0x%08x\n",
|
||||
snd_hdac_read_parm(codec, nid, AC_PAR_PCM));
|
||||
}
|
||||
|
||||
static ssize_t pcm_formats_show(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf)
|
||||
{
|
||||
if (!has_pcm_cap(codec, nid))
|
||||
return 0;
|
||||
return sprintf(buf, "0x%08x\n",
|
||||
snd_hdac_read_parm(codec, nid, AC_PAR_STREAM));
|
||||
}
|
||||
|
||||
static ssize_t amp_in_caps_show(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf)
|
||||
{
|
||||
if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_IN_AMP))
|
||||
return 0;
|
||||
return sprintf(buf, "0x%08x\n",
|
||||
snd_hdac_read_parm(codec, nid, AC_PAR_AMP_IN_CAP));
|
||||
}
|
||||
|
||||
static ssize_t amp_out_caps_show(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf)
|
||||
{
|
||||
if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_OUT_AMP))
|
||||
return 0;
|
||||
return sprintf(buf, "0x%08x\n",
|
||||
snd_hdac_read_parm(codec, nid, AC_PAR_AMP_OUT_CAP));
|
||||
}
|
||||
|
||||
static ssize_t power_caps_show(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf)
|
||||
{
|
||||
if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_POWER))
|
||||
return 0;
|
||||
return sprintf(buf, "0x%08x\n",
|
||||
snd_hdac_read_parm(codec, nid, AC_PAR_POWER_STATE));
|
||||
}
|
||||
|
||||
static ssize_t gpio_caps_show(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "0x%08x\n",
|
||||
snd_hdac_read_parm(codec, nid, AC_PAR_GPIO_CAP));
|
||||
}
|
||||
|
||||
static ssize_t connections_show(struct hdac_device *codec, hda_nid_t nid,
|
||||
struct widget_attribute *attr, char *buf)
|
||||
{
|
||||
hda_nid_t list[32];
|
||||
int i, nconns;
|
||||
ssize_t ret = 0;
|
||||
|
||||
nconns = snd_hdac_get_connections(codec, nid, list, ARRAY_SIZE(list));
|
||||
if (nconns <= 0)
|
||||
return nconns;
|
||||
for (i = 0; i < nconns; i++)
|
||||
ret += sprintf(buf + ret, "%s0x%02x", i ? " " : "", list[i]);
|
||||
ret += sprintf(buf + ret, "\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static WIDGET_ATTR_RO(caps);
|
||||
static WIDGET_ATTR_RO(pin_caps);
|
||||
static WIDGET_ATTR_RO(pin_cfg);
|
||||
static WIDGET_ATTR_RO(pcm_caps);
|
||||
static WIDGET_ATTR_RO(pcm_formats);
|
||||
static WIDGET_ATTR_RO(amp_in_caps);
|
||||
static WIDGET_ATTR_RO(amp_out_caps);
|
||||
static WIDGET_ATTR_RO(power_caps);
|
||||
static WIDGET_ATTR_RO(gpio_caps);
|
||||
static WIDGET_ATTR_RO(connections);
|
||||
|
||||
static struct attribute *widget_node_attrs[] = {
|
||||
&wid_attr_caps.attr,
|
||||
&wid_attr_pin_caps.attr,
|
||||
&wid_attr_pin_cfg.attr,
|
||||
&wid_attr_pcm_caps.attr,
|
||||
&wid_attr_pcm_formats.attr,
|
||||
&wid_attr_amp_in_caps.attr,
|
||||
&wid_attr_amp_out_caps.attr,
|
||||
&wid_attr_power_caps.attr,
|
||||
&wid_attr_connections.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute *widget_afg_attrs[] = {
|
||||
&wid_attr_pcm_caps.attr,
|
||||
&wid_attr_pcm_formats.attr,
|
||||
&wid_attr_amp_in_caps.attr,
|
||||
&wid_attr_amp_out_caps.attr,
|
||||
&wid_attr_power_caps.attr,
|
||||
&wid_attr_gpio_caps.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group widget_node_group = {
|
||||
.attrs = widget_node_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group widget_afg_group = {
|
||||
.attrs = widget_afg_attrs,
|
||||
};
|
||||
|
||||
static void free_widget_node(struct kobject *kobj,
|
||||
const struct attribute_group *group)
|
||||
{
|
||||
if (kobj) {
|
||||
sysfs_remove_group(kobj, group);
|
||||
kobject_put(kobj);
|
||||
}
|
||||
}
|
||||
|
||||
static void widget_tree_free(struct hdac_device *codec)
|
||||
{
|
||||
struct hdac_widget_tree *tree = codec->widgets;
|
||||
struct kobject **p;
|
||||
|
||||
if (!tree)
|
||||
return;
|
||||
free_widget_node(tree->afg, &widget_afg_group);
|
||||
if (tree->nodes) {
|
||||
for (p = tree->nodes; *p; p++)
|
||||
free_widget_node(*p, &widget_node_group);
|
||||
kfree(tree->nodes);
|
||||
}
|
||||
if (tree->root)
|
||||
kobject_put(tree->root);
|
||||
kfree(tree);
|
||||
codec->widgets = NULL;
|
||||
}
|
||||
|
||||
static int add_widget_node(struct kobject *parent, hda_nid_t nid,
|
||||
const struct attribute_group *group,
|
||||
struct kobject **res)
|
||||
{
|
||||
struct kobject *kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
|
||||
int err;
|
||||
|
||||
if (!kobj)
|
||||
return -ENOMEM;
|
||||
kobject_init(kobj, &widget_ktype);
|
||||
err = kobject_add(kobj, parent, "%02x", nid);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = sysfs_create_group(kobj, group);
|
||||
if (err < 0) {
|
||||
kobject_put(kobj);
|
||||
return err;
|
||||
}
|
||||
|
||||
*res = kobj;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int widget_tree_create(struct hdac_device *codec)
|
||||
{
|
||||
struct hdac_widget_tree *tree;
|
||||
int i, err;
|
||||
hda_nid_t nid;
|
||||
|
||||
tree = codec->widgets = kzalloc(sizeof(*tree), GFP_KERNEL);
|
||||
if (!tree)
|
||||
return -ENOMEM;
|
||||
|
||||
tree->root = kobject_create_and_add("widgets", &codec->dev.kobj);
|
||||
if (!tree->root)
|
||||
return -ENOMEM;
|
||||
|
||||
tree->nodes = kcalloc(codec->num_nodes + 1, sizeof(*tree->nodes),
|
||||
GFP_KERNEL);
|
||||
if (!tree->nodes)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) {
|
||||
err = add_widget_node(tree->root, nid, &widget_node_group,
|
||||
&tree->nodes[i]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (codec->afg) {
|
||||
err = add_widget_node(tree->root, codec->afg,
|
||||
&widget_afg_group, &tree->afg);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
kobject_uevent(tree->root, KOBJ_CHANGE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hda_widget_sysfs_init(struct hdac_device *codec)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = widget_tree_create(codec);
|
||||
if (err < 0) {
|
||||
widget_tree_free(codec);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hda_widget_sysfs_exit(struct hdac_device *codec)
|
||||
{
|
||||
widget_tree_free(codec);
|
||||
}
|
23
sound/hda/local.h
Normal file
23
sound/hda/local.h
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Local helper macros and functions for HD-audio core drivers
|
||||
*/
|
||||
|
||||
#ifndef __HDAC_LOCAL_H
|
||||
#define __HDAC_LOCAL_H
|
||||
|
||||
#define get_wcaps(codec, nid) \
|
||||
snd_hdac_read_parm(codec, nid, AC_PAR_AUDIO_WIDGET_CAP)
|
||||
|
||||
/* get the widget type from widget capability bits */
|
||||
static inline int get_wcaps_type(unsigned int wcaps)
|
||||
{
|
||||
if (!wcaps)
|
||||
return -1; /* invalid type */
|
||||
return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
|
||||
}
|
||||
|
||||
extern const struct attribute_group *hdac_dev_attr_groups[];
|
||||
int hda_widget_sysfs_init(struct hdac_device *codec);
|
||||
void hda_widget_sysfs_exit(struct hdac_device *codec);
|
||||
|
||||
#endif /* __HDAC_LOCAL_H */
|
6
sound/hda/trace.c
Normal file
6
sound/hda/trace.c
Normal file
@ -0,0 +1,6 @@
|
||||
/*
|
||||
* tracepoint definitions for HD-audio core drivers
|
||||
*/
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "trace.h"
|
62
sound/hda/trace.h
Normal file
62
sound/hda/trace.h
Normal file
@ -0,0 +1,62 @@
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM hda
|
||||
|
||||
#if !defined(__HDAC_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define __HDAC_TRACE_H
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/hdaudio.h>
|
||||
|
||||
#ifndef HDAC_MSG_MAX
|
||||
#define HDAC_MSG_MAX 500
|
||||
#endif
|
||||
|
||||
struct hdac_bus;
|
||||
struct hdac_codec;
|
||||
|
||||
TRACE_EVENT(hda_send_cmd,
|
||||
TP_PROTO(struct hdac_bus *bus, unsigned int cmd),
|
||||
TP_ARGS(bus, cmd),
|
||||
TP_STRUCT__entry(__dynamic_array(char, msg, HDAC_MSG_MAX)),
|
||||
TP_fast_assign(
|
||||
snprintf(__get_str(msg), HDAC_MSG_MAX,
|
||||
"[%s:%d] val=0x%08x",
|
||||
dev_name((bus)->dev), (cmd) >> 28, cmd);
|
||||
),
|
||||
TP_printk("%s", __get_str(msg))
|
||||
);
|
||||
|
||||
TRACE_EVENT(hda_get_response,
|
||||
TP_PROTO(struct hdac_bus *bus, unsigned int addr, unsigned int res),
|
||||
TP_ARGS(bus, addr, res),
|
||||
TP_STRUCT__entry(__dynamic_array(char, msg, HDAC_MSG_MAX)),
|
||||
TP_fast_assign(
|
||||
snprintf(__get_str(msg), HDAC_MSG_MAX,
|
||||
"[%s:%d] val=0x%08x",
|
||||
dev_name((bus)->dev), addr, res);
|
||||
),
|
||||
TP_printk("%s", __get_str(msg))
|
||||
);
|
||||
|
||||
TRACE_EVENT(hda_unsol_event,
|
||||
TP_PROTO(struct hdac_bus *bus, u32 res, u32 res_ex),
|
||||
TP_ARGS(bus, res, res_ex),
|
||||
TP_STRUCT__entry(__dynamic_array(char, msg, HDAC_MSG_MAX)),
|
||||
TP_fast_assign(
|
||||
snprintf(__get_str(msg), HDAC_MSG_MAX,
|
||||
"[%s:%d] res=0x%08x, res_ex=0x%08x",
|
||||
dev_name((bus)->dev), res_ex & 0x0f, res, res_ex);
|
||||
),
|
||||
TP_printk("%s", __get_str(msg))
|
||||
);
|
||||
#endif /* __HDAC_TRACE_H */
|
||||
|
||||
/* This part must be outside protection */
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
|
||||
#undef TRACE_INCLUDE_FILE
|
||||
#define TRACE_INCLUDE_FILE trace
|
||||
|
||||
#include <trace/define_trace.h>
|
@ -73,7 +73,7 @@ int snd_ak4113_create(struct snd_card *card, ak4113_read_t *read,
|
||||
void *private_data, struct ak4113 **r_ak4113)
|
||||
{
|
||||
struct ak4113 *chip;
|
||||
int err = 0;
|
||||
int err;
|
||||
unsigned char reg;
|
||||
static struct snd_device_ops ops = {
|
||||
.dev_free = snd_ak4113_dev_free,
|
||||
@ -109,7 +109,7 @@ int snd_ak4113_create(struct snd_card *card, ak4113_read_t *read,
|
||||
|
||||
__fail:
|
||||
snd_ak4113_free(chip);
|
||||
return err < 0 ? err : -EIO;
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_ak4113_create);
|
||||
|
||||
|
@ -34,8 +34,9 @@ MODULE_LICENSE("GPL");
|
||||
/*
|
||||
* create a new hardware dependent device for Emu8000
|
||||
*/
|
||||
static int snd_emu8000_new_device(struct snd_seq_device *dev)
|
||||
static int snd_emu8000_probe(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
struct snd_emu8000 *hw;
|
||||
struct snd_emux *emu;
|
||||
|
||||
@ -93,8 +94,9 @@ static int snd_emu8000_new_device(struct snd_seq_device *dev)
|
||||
/*
|
||||
* free all resources
|
||||
*/
|
||||
static int snd_emu8000_delete_device(struct snd_seq_device *dev)
|
||||
static int snd_emu8000_remove(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
struct snd_emu8000 *hw;
|
||||
|
||||
if (dev->driver_data == NULL)
|
||||
@ -114,21 +116,14 @@ static int snd_emu8000_delete_device(struct snd_seq_device *dev)
|
||||
* INIT part
|
||||
*/
|
||||
|
||||
static int __init alsa_emu8000_init(void)
|
||||
{
|
||||
|
||||
static struct snd_seq_dev_ops ops = {
|
||||
snd_emu8000_new_device,
|
||||
snd_emu8000_delete_device,
|
||||
};
|
||||
return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_EMU8000, &ops,
|
||||
sizeof(struct snd_emu8000*));
|
||||
}
|
||||
static struct snd_seq_driver emu8000_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.probe = snd_emu8000_probe,
|
||||
.remove = snd_emu8000_remove,
|
||||
},
|
||||
.id = SNDRV_SEQ_DEV_ID_EMU8000,
|
||||
.argsize = sizeof(struct snd_emu8000 *),
|
||||
};
|
||||
|
||||
static void __exit alsa_emu8000_exit(void)
|
||||
{
|
||||
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_EMU8000);
|
||||
}
|
||||
|
||||
module_init(alsa_emu8000_init)
|
||||
module_exit(alsa_emu8000_exit)
|
||||
module_snd_seq_driver(emu8000_driver);
|
||||
|
@ -79,13 +79,13 @@ wavefront_fx_memset (snd_wavefront_t *dev,
|
||||
if (page < 0 || page > 7) {
|
||||
snd_printk ("FX memset: "
|
||||
"page must be >= 0 and <= 7\n");
|
||||
return -(EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (addr < 0 || addr > 0x7f) {
|
||||
snd_printk ("FX memset: "
|
||||
"addr must be >= 0 and <= 7f\n");
|
||||
return -(EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cnt == 1) {
|
||||
@ -118,7 +118,7 @@ wavefront_fx_memset (snd_wavefront_t *dev,
|
||||
snd_printk ("FX memset "
|
||||
"(0x%x, 0x%x, 0x%lx, %d) incomplete\n",
|
||||
page, addr, (unsigned long) data, cnt);
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -793,7 +793,7 @@ wavefront_send_patch (snd_wavefront_t *dev, wavefront_patch_info *header)
|
||||
|
||||
if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PATCH, NULL, buf)) {
|
||||
snd_printk ("download patch failed\n");
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return (0);
|
||||
@ -831,7 +831,7 @@ wavefront_send_program (snd_wavefront_t *dev, wavefront_patch_info *header)
|
||||
|
||||
if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PROGRAM, NULL, buf)) {
|
||||
snd_printk ("download patch failed\n");
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return (0);
|
||||
@ -952,7 +952,7 @@ wavefront_send_sample (snd_wavefront_t *dev,
|
||||
if (skip > 0 && header->hdr.s.SampleResolution != LINEAR_16BIT) {
|
||||
snd_printk ("channel selection only "
|
||||
"possible on 16-bit samples");
|
||||
return -(EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (skip) {
|
||||
@ -1049,7 +1049,7 @@ wavefront_send_sample (snd_wavefront_t *dev,
|
||||
NULL, sample_hdr)) {
|
||||
snd_printk ("sample %sdownload refused.\n",
|
||||
header->size ? "" : "header ");
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (header->size == 0) {
|
||||
@ -1075,7 +1075,7 @@ wavefront_send_sample (snd_wavefront_t *dev,
|
||||
if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_BLOCK, NULL, NULL)) {
|
||||
snd_printk ("download block "
|
||||
"request refused.\n");
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
for (i = 0; i < blocksize; i++) {
|
||||
@ -1135,12 +1135,12 @@ wavefront_send_sample (snd_wavefront_t *dev,
|
||||
if (dma_ack == -1) {
|
||||
snd_printk ("upload sample "
|
||||
"DMA ack timeout\n");
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
} else {
|
||||
snd_printk ("upload sample "
|
||||
"DMA ack error 0x%x\n",
|
||||
dma_ack);
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1181,7 +1181,7 @@ wavefront_send_alias (snd_wavefront_t *dev, wavefront_patch_info *header)
|
||||
|
||||
if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_SAMPLE_ALIAS, NULL, alias_hdr)) {
|
||||
snd_printk ("download alias failed.\n");
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_ALIAS);
|
||||
@ -1232,7 +1232,7 @@ wavefront_send_multisample (snd_wavefront_t *dev, wavefront_patch_info *header)
|
||||
msample_hdr)) {
|
||||
snd_printk ("download of multisample failed.\n");
|
||||
kfree(msample_hdr);
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_MULTISAMPLE);
|
||||
@ -1254,7 +1254,7 @@ wavefront_fetch_multisample (snd_wavefront_t *dev,
|
||||
|
||||
if (snd_wavefront_cmd (dev, WFC_UPLOAD_MULTISAMPLE, log_ns, number)) {
|
||||
snd_printk ("upload multisample failed.\n");
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
DPRINT (WF_DEBUG_DATA, "msample %d has %d samples\n",
|
||||
@ -1273,14 +1273,14 @@ wavefront_fetch_multisample (snd_wavefront_t *dev,
|
||||
if ((val = wavefront_read (dev)) == -1) {
|
||||
snd_printk ("upload multisample failed "
|
||||
"during sample loop.\n");
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
d[0] = val;
|
||||
|
||||
if ((val = wavefront_read (dev)) == -1) {
|
||||
snd_printk ("upload multisample failed "
|
||||
"during sample loop.\n");
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
d[1] = val;
|
||||
|
||||
@ -1315,7 +1315,7 @@ wavefront_send_drum (snd_wavefront_t *dev, wavefront_patch_info *header)
|
||||
|
||||
if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_EDRUM_PROGRAM, NULL, drumbuf)) {
|
||||
snd_printk ("download drum failed.\n");
|
||||
return -(EIO);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return (0);
|
||||
|
@ -633,19 +633,25 @@ static int au1000_ac97_probe(struct platform_device *pdev)
|
||||
|
||||
au1000->stream[PLAYBACK] = kmalloc(sizeof(struct audio_stream),
|
||||
GFP_KERNEL);
|
||||
if (!au1000->stream[PLAYBACK])
|
||||
if (!au1000->stream[PLAYBACK]) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
au1000->stream[PLAYBACK]->dma = -1;
|
||||
|
||||
au1000->stream[CAPTURE] = kmalloc(sizeof(struct audio_stream),
|
||||
GFP_KERNEL);
|
||||
if (!au1000->stream[CAPTURE])
|
||||
if (!au1000->stream[CAPTURE]) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
au1000->stream[CAPTURE]->dma = -1;
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!r)
|
||||
if (!r) {
|
||||
err = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = -EBUSY;
|
||||
au1000->ac97_res_port = request_mem_region(r->start, resource_size(r),
|
||||
|
@ -58,13 +58,13 @@ int sound_install_audiodrv(int vers, char *name, struct audio_driver *driver,
|
||||
|
||||
if (vers != AUDIO_DRIVER_VERSION || driver_size > sizeof(struct audio_driver)) {
|
||||
printk(KERN_ERR "Sound: Incompatible audio driver for %s\n", name);
|
||||
return -(EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
num = sound_alloc_audiodev();
|
||||
|
||||
if (num == -1) {
|
||||
printk(KERN_ERR "sound: Too many audio drivers\n");
|
||||
return -(EBUSY);
|
||||
return -EBUSY;
|
||||
}
|
||||
d = (struct audio_driver *) (sound_mem_blocks[sound_nblocks] = vmalloc(sizeof(struct audio_driver)));
|
||||
sound_nblocks++;
|
||||
@ -79,7 +79,7 @@ int sound_install_audiodrv(int vers, char *name, struct audio_driver *driver,
|
||||
if (d == NULL || op == NULL) {
|
||||
printk(KERN_ERR "Sound: Can't allocate driver for (%s)\n", name);
|
||||
sound_unload_audiodev(num);
|
||||
return -(ENOMEM);
|
||||
return -ENOMEM;
|
||||
}
|
||||
init_waitqueue_head(&op->in_sleeper);
|
||||
init_waitqueue_head(&op->out_sleeper);
|
||||
|
@ -666,7 +666,7 @@ static int opl3_start_note (int dev, int voice, int note, int volume)
|
||||
opl3_command(map->ioaddr, FNUM_LOW + map->voice_num, data);
|
||||
|
||||
data = 0x20 | ((block & 0x7) << 2) | ((fnum >> 8) & 0x3);
|
||||
devc->voc[voice].keyon_byte = data;
|
||||
devc->voc[voice].keyon_byte = data;
|
||||
opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num, data);
|
||||
if (voice_mode == 4)
|
||||
opl3_command(map->ioaddr, KEYON_BLOCK + map->voice_num + 3, data);
|
||||
@ -717,7 +717,7 @@ static void freq_to_fnum (int freq, int *block, int *fnum)
|
||||
|
||||
static void opl3_command (int io_addr, unsigned int addr, unsigned int val)
|
||||
{
|
||||
int i;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* The original 2-OP synth requires a quite long delay after writing to a
|
||||
|
@ -604,7 +604,7 @@ static void ess_audio_output_block_audio2
|
||||
ess_chgmixer (devc, 0x78, 0x03, 0x03); /* Go */
|
||||
|
||||
devc->irq_mode_16 = IMODE_OUTPUT;
|
||||
devc->intr_active_16 = 1;
|
||||
devc->intr_active_16 = 1;
|
||||
}
|
||||
|
||||
static void ess_audio_output_block
|
||||
@ -1183,17 +1183,12 @@ FKS_test (devc);
|
||||
chip = "ES1688";
|
||||
}
|
||||
|
||||
printk ( KERN_INFO "ESS chip %s %s%s\n"
|
||||
, chip
|
||||
, ( devc->sbmo.esstype == ESSTYPE_DETECT || devc->sbmo.esstype == ESSTYPE_LIKE20
|
||||
? "detected"
|
||||
: "specified"
|
||||
)
|
||||
, ( devc->sbmo.esstype == ESSTYPE_LIKE20
|
||||
? " (kernel 2.0 compatible)"
|
||||
: ""
|
||||
)
|
||||
);
|
||||
printk(KERN_INFO "ESS chip %s %s%s\n", chip,
|
||||
(devc->sbmo.esstype == ESSTYPE_DETECT ||
|
||||
devc->sbmo.esstype == ESSTYPE_LIKE20) ?
|
||||
"detected" : "specified",
|
||||
devc->sbmo.esstype == ESSTYPE_LIKE20 ?
|
||||
" (kernel 2.0 compatible)" : "");
|
||||
|
||||
sprintf(name,"ESS %s AudioDrive (rev %d)", chip, ess_minor & 0x0f);
|
||||
} else {
|
||||
|
@ -179,14 +179,14 @@ void sb_dsp_midi_init(sb_devc * devc, struct module *owner)
|
||||
{
|
||||
printk(KERN_WARNING "Sound Blaster: failed to allocate MIDI memory.\n");
|
||||
sound_unload_mididev(dev);
|
||||
return;
|
||||
return;
|
||||
}
|
||||
memcpy((char *) midi_devs[dev], (char *) &sb_midi_operations,
|
||||
sizeof(struct midi_operations));
|
||||
|
||||
if (owner)
|
||||
midi_devs[dev]->owner = owner;
|
||||
|
||||
midi_devs[dev]->owner = owner;
|
||||
|
||||
midi_devs[dev]->devc = devc;
|
||||
|
||||
|
||||
|
@ -50,29 +50,24 @@ tmr2ticks(int tmr_value)
|
||||
static void
|
||||
poll_def_tmr(unsigned long dummy)
|
||||
{
|
||||
if (!opened)
|
||||
return;
|
||||
def_tmr.expires = (1) + jiffies;
|
||||
add_timer(&def_tmr);
|
||||
|
||||
if (opened)
|
||||
{
|
||||
if (!tmr_running)
|
||||
return;
|
||||
|
||||
{
|
||||
def_tmr.expires = (1) + jiffies;
|
||||
add_timer(&def_tmr);
|
||||
}
|
||||
spin_lock(&lock);
|
||||
tmr_ctr++;
|
||||
curr_ticks = ticks_offs + tmr2ticks(tmr_ctr);
|
||||
|
||||
if (tmr_running)
|
||||
{
|
||||
spin_lock(&lock);
|
||||
tmr_ctr++;
|
||||
curr_ticks = ticks_offs + tmr2ticks(tmr_ctr);
|
||||
if (curr_ticks >= next_event_time) {
|
||||
next_event_time = (unsigned long) -1;
|
||||
sequencer_timer(0);
|
||||
}
|
||||
|
||||
if (curr_ticks >= next_event_time)
|
||||
{
|
||||
next_event_time = (unsigned long) -1;
|
||||
sequencer_timer(0);
|
||||
}
|
||||
spin_unlock(&lock);
|
||||
}
|
||||
}
|
||||
spin_unlock(&lock);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -49,13 +49,13 @@ static int v_midi_open (int dev, int mode,
|
||||
unsigned long flags;
|
||||
|
||||
if (devc == NULL)
|
||||
return -(ENXIO);
|
||||
return -ENXIO;
|
||||
|
||||
spin_lock_irqsave(&devc->lock,flags);
|
||||
if (devc->opened)
|
||||
{
|
||||
spin_unlock_irqrestore(&devc->lock,flags);
|
||||
return -(EBUSY);
|
||||
return -EBUSY;
|
||||
}
|
||||
devc->opened = 1;
|
||||
spin_unlock_irqrestore(&devc->lock,flags);
|
||||
|
@ -177,6 +177,7 @@ static const struct ac97_codec_id snd_ac97_codec_ids[] = {
|
||||
{ 0x54524123, 0xffffffff, "TR28602", NULL, NULL }, // only guess --jk [TR28023 = eMicro EM28023 (new CT1297)]
|
||||
{ 0x54584e03, 0xffffffff, "TLV320AIC27", NULL, NULL },
|
||||
{ 0x54584e20, 0xffffffff, "TLC320AD9xC", NULL, NULL },
|
||||
{ 0x56494120, 0xfffffff0, "VIA1613", patch_vt1613, NULL },
|
||||
{ 0x56494161, 0xffffffff, "VIA1612A", NULL, NULL }, // modified ICE1232 with S/PDIF
|
||||
{ 0x56494170, 0xffffffff, "VIA1617A", patch_vt1617a, NULL }, // modified VT1616 with S/PDIF
|
||||
{ 0x56494182, 0xffffffff, "VIA1618", patch_vt1618, NULL },
|
||||
@ -2901,7 +2902,8 @@ static int apply_quirk_str(struct snd_ac97 *ac97, const char *typestr)
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
|
||||
int snd_ac97_tune_hardware(struct snd_ac97 *ac97, struct ac97_quirk *quirk, const char *override)
|
||||
int snd_ac97_tune_hardware(struct snd_ac97 *ac97,
|
||||
const struct ac97_quirk *quirk, const char *override)
|
||||
{
|
||||
int result;
|
||||
|
||||
|
@ -3351,6 +3351,33 @@ static int patch_cm9780(struct snd_ac97 *ac97)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* VIA VT1613 codec
|
||||
*/
|
||||
static const struct snd_kcontrol_new snd_ac97_controls_vt1613[] = {
|
||||
AC97_SINGLE("DC Offset removal", 0x5a, 10, 1, 0),
|
||||
};
|
||||
|
||||
static int patch_vt1613_specific(struct snd_ac97 *ac97)
|
||||
{
|
||||
return patch_build_controls(ac97, &snd_ac97_controls_vt1613[0],
|
||||
ARRAY_SIZE(snd_ac97_controls_vt1613));
|
||||
};
|
||||
|
||||
static const struct snd_ac97_build_ops patch_vt1613_ops = {
|
||||
.build_specific = patch_vt1613_specific
|
||||
};
|
||||
|
||||
static int patch_vt1613(struct snd_ac97 *ac97)
|
||||
{
|
||||
ac97->build_ops = &patch_vt1613_ops;
|
||||
|
||||
ac97->flags |= AC97_HAS_NO_VIDEO;
|
||||
ac97->caps |= AC97_BC_HEADPHONE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* VIA VT1616 codec
|
||||
*/
|
||||
|
@ -747,7 +747,7 @@ snd_ad1889_proc_init(struct snd_ad1889 *chip)
|
||||
snd_info_set_text_ops(entry, chip, snd_ad1889_proc_read);
|
||||
}
|
||||
|
||||
static struct ac97_quirk ac97_quirks[] = {
|
||||
static const struct ac97_quirk ac97_quirks[] = {
|
||||
{
|
||||
.subvendor = 0x11d4, /* AD */
|
||||
.subdevice = 0x1889, /* AD1889 */
|
||||
|
@ -2376,7 +2376,7 @@ static int snd_asihpi_cmode_add(struct snd_card_asihpi *asihpi,
|
||||
/*------------------------------------------------------------
|
||||
Sampleclock source controls
|
||||
------------------------------------------------------------*/
|
||||
static const char const *sampleclock_sources[] = {
|
||||
static const char * const sampleclock_sources[] = {
|
||||
"N/A", "Local PLL", "Digital Sync", "Word External", "Word Header",
|
||||
"SMPTE", "Digital1", "Auto", "Network", "Invalid",
|
||||
"Prev Module", "BLU-Link",
|
||||
|
@ -1390,7 +1390,7 @@ static irqreturn_t snd_atiixp_interrupt(int irq, void *dev_id)
|
||||
* ac97 mixer section
|
||||
*/
|
||||
|
||||
static struct ac97_quirk ac97_quirks[] = {
|
||||
static const struct ac97_quirk ac97_quirks[] = {
|
||||
{
|
||||
.subvendor = 0x103c,
|
||||
.subdevice = 0x006b,
|
||||
|
@ -1385,8 +1385,8 @@ snd_azf3328_ctrl_codec_activity(struct snd_azf3328 *chip,
|
||||
.running)
|
||||
&& (!chip->codecs[peer_codecs[codec_type].other2]
|
||||
.running));
|
||||
}
|
||||
if (call_function)
|
||||
}
|
||||
if (call_function)
|
||||
snd_azf3328_ctrl_enable_codecs(chip, enable);
|
||||
|
||||
/* ...and adjust clock, too
|
||||
@ -2126,7 +2126,8 @@ static struct snd_pcm_ops snd_azf3328_i2s_out_ops = {
|
||||
static int
|
||||
snd_azf3328_pcm(struct snd_azf3328 *chip)
|
||||
{
|
||||
enum { AZF_PCMDEV_STD, AZF_PCMDEV_I2S_OUT, NUM_AZF_PCMDEVS }; /* pcm devices */
|
||||
/* pcm devices */
|
||||
enum { AZF_PCMDEV_STD, AZF_PCMDEV_I2S_OUT, NUM_AZF_PCMDEVS };
|
||||
|
||||
struct snd_pcm *pcm;
|
||||
int err;
|
||||
|
@ -2062,7 +2062,7 @@ static int snd_cmipci_get_volume(struct snd_kcontrol *kcontrol,
|
||||
val = (snd_cmipci_mixer_read(cm, reg.right_reg) >> reg.right_shift) & reg.mask;
|
||||
if (reg.invert)
|
||||
val = reg.mask - val;
|
||||
ucontrol->value.integer.value[1] = val;
|
||||
ucontrol->value.integer.value[1] = val;
|
||||
}
|
||||
spin_unlock_irq(&cm->reg_lock);
|
||||
return 0;
|
||||
|
@ -43,7 +43,7 @@ static char *ac97_quirk;
|
||||
module_param(ac97_quirk, charp, 0444);
|
||||
MODULE_PARM_DESC(ac97_quirk, "AC'97 board specific workarounds.");
|
||||
|
||||
static struct ac97_quirk ac97_quirks[] = {
|
||||
static const struct ac97_quirk ac97_quirks[] = {
|
||||
#if 0 /* Not yet confirmed if all 5536 boards are HP only */
|
||||
{
|
||||
.subvendor = PCI_VENDOR_ID_AMD,
|
||||
|
@ -1283,12 +1283,14 @@ static int snd_echo_mixer_info(struct snd_kcontrol *kcontrol,
|
||||
static int snd_echo_mixer_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct echoaudio *chip;
|
||||
struct echoaudio *chip = snd_kcontrol_chip(kcontrol);
|
||||
unsigned int out = ucontrol->id.index / num_busses_in(chip);
|
||||
unsigned int in = ucontrol->id.index % num_busses_in(chip);
|
||||
|
||||
chip = snd_kcontrol_chip(kcontrol);
|
||||
ucontrol->value.integer.value[0] =
|
||||
chip->monitor_gain[ucontrol->id.index / num_busses_in(chip)]
|
||||
[ucontrol->id.index % num_busses_in(chip)];
|
||||
if (out >= ECHO_MAXAUDIOOUTPUTS || in >= ECHO_MAXAUDIOINPUTS)
|
||||
return -EINVAL;
|
||||
|
||||
ucontrol->value.integer.value[0] = chip->monitor_gain[out][in];
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1297,12 +1299,14 @@ static int snd_echo_mixer_put(struct snd_kcontrol *kcontrol,
|
||||
{
|
||||
struct echoaudio *chip;
|
||||
int changed, gain;
|
||||
short out, in;
|
||||
unsigned int out, in;
|
||||
|
||||
changed = 0;
|
||||
chip = snd_kcontrol_chip(kcontrol);
|
||||
out = ucontrol->id.index / num_busses_in(chip);
|
||||
in = ucontrol->id.index % num_busses_in(chip);
|
||||
if (out >= ECHO_MAXAUDIOOUTPUTS || in >= ECHO_MAXAUDIOINPUTS)
|
||||
return -EINVAL;
|
||||
gain = ucontrol->value.integer.value[0];
|
||||
if (gain < ECHOGAIN_MINOUT || gain > ECHOGAIN_MAXOUT)
|
||||
return -EINVAL;
|
||||
|
@ -707,6 +707,7 @@ static int emu1010_firmware_thread(void *data)
|
||||
{
|
||||
struct snd_emu10k1 *emu = data;
|
||||
u32 tmp, tmp2, reg;
|
||||
u32 last_reg = 0;
|
||||
int err;
|
||||
|
||||
for (;;) {
|
||||
@ -782,7 +783,15 @@ static int emu1010_firmware_thread(void *data)
|
||||
msleep(10);
|
||||
/* Unmute all. Default is muted after a firmware load */
|
||||
snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE);
|
||||
} else if (!reg && last_reg) {
|
||||
/* Audio Dock removed */
|
||||
dev_info(emu->card->dev,
|
||||
"emu1010: Audio Dock detached\n");
|
||||
/* Unmute all */
|
||||
snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE);
|
||||
}
|
||||
|
||||
last_reg = reg;
|
||||
}
|
||||
dev_info(emu->card->dev, "emu1010: firmware thread stopping\n");
|
||||
return 0;
|
||||
@ -1325,6 +1334,22 @@ static int snd_emu10k1_dev_free(struct snd_device *device)
|
||||
}
|
||||
|
||||
static struct snd_emu_chip_details emu_chip_details[] = {
|
||||
/* Audigy 5/Rx SB1550 */
|
||||
/* Tested by michael@gernoth.net 28 Mar 2015 */
|
||||
/* DSP: CA10300-IAT LF
|
||||
* DAC: Cirrus Logic CS4382-KQZ
|
||||
* ADC: Philips 1361T
|
||||
* AC97: Sigmatel STAC9750
|
||||
* CA0151: None
|
||||
*/
|
||||
{.vendor = 0x1102, .device = 0x0008, .subsystem = 0x10241102,
|
||||
.driver = "Audigy2", .name = "SB Audigy 5/Rx [SB1550]",
|
||||
.id = "Audigy2",
|
||||
.emu10k2_chip = 1,
|
||||
.ca0108_chip = 1,
|
||||
.spk71 = 1,
|
||||
.adc_1361t = 1, /* 24 bit capture instead of 16bit */
|
||||
.ac97_chip = 1},
|
||||
/* Audigy4 (Not PRO) SB0610 */
|
||||
/* Tested by James@superbug.co.uk 4th April 2006 */
|
||||
/* A_IOCFG bits
|
||||
|
@ -29,8 +29,9 @@ MODULE_LICENSE("GPL");
|
||||
/*
|
||||
* create a new hardware dependent device for Emu10k1
|
||||
*/
|
||||
static int snd_emu10k1_synth_new_device(struct snd_seq_device *dev)
|
||||
static int snd_emu10k1_synth_probe(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
struct snd_emux *emux;
|
||||
struct snd_emu10k1 *hw;
|
||||
struct snd_emu10k1_synth_arg *arg;
|
||||
@ -79,8 +80,9 @@ static int snd_emu10k1_synth_new_device(struct snd_seq_device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_emu10k1_synth_delete_device(struct snd_seq_device *dev)
|
||||
static int snd_emu10k1_synth_remove(struct device *_dev)
|
||||
{
|
||||
struct snd_seq_device *dev = to_seq_dev(_dev);
|
||||
struct snd_emux *emux;
|
||||
struct snd_emu10k1 *hw;
|
||||
unsigned long flags;
|
||||
@ -104,21 +106,14 @@ static int snd_emu10k1_synth_delete_device(struct snd_seq_device *dev)
|
||||
* INIT part
|
||||
*/
|
||||
|
||||
static int __init alsa_emu10k1_synth_init(void)
|
||||
{
|
||||
|
||||
static struct snd_seq_dev_ops ops = {
|
||||
snd_emu10k1_synth_new_device,
|
||||
snd_emu10k1_synth_delete_device,
|
||||
};
|
||||
return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH, &ops,
|
||||
sizeof(struct snd_emu10k1_synth_arg));
|
||||
}
|
||||
static struct snd_seq_driver emu10k1_synth_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.probe = snd_emu10k1_synth_probe,
|
||||
.remove = snd_emu10k1_synth_remove,
|
||||
},
|
||||
.id = SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH,
|
||||
.argsize = sizeof(struct snd_emu10k1_synth_arg),
|
||||
};
|
||||
|
||||
static void __exit alsa_emu10k1_synth_exit(void)
|
||||
{
|
||||
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH);
|
||||
}
|
||||
|
||||
module_init(alsa_emu10k1_synth_init)
|
||||
module_exit(alsa_emu10k1_synth_exit)
|
||||
module_snd_seq_driver(emu10k1_synth_driver);
|
||||
|
@ -806,6 +806,108 @@ static struct snd_kcontrol_new snd_emu1010_internal_clock =
|
||||
.put = snd_emu1010_internal_clock_put
|
||||
};
|
||||
|
||||
static int snd_emu1010_optical_out_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static const char * const texts[2] = {
|
||||
"SPDIF", "ADAT"
|
||||
};
|
||||
|
||||
return snd_ctl_enum_info(uinfo, 1, 2, texts);
|
||||
}
|
||||
|
||||
static int snd_emu1010_optical_out_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
ucontrol->value.enumerated.item[0] = emu->emu1010.optical_out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_emu1010_optical_out_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
|
||||
unsigned int val;
|
||||
u32 tmp;
|
||||
int change = 0;
|
||||
|
||||
val = ucontrol->value.enumerated.item[0];
|
||||
/* Limit: uinfo->value.enumerated.items = 2; */
|
||||
if (val >= 2)
|
||||
return -EINVAL;
|
||||
change = (emu->emu1010.optical_out != val);
|
||||
if (change) {
|
||||
emu->emu1010.optical_out = val;
|
||||
tmp = (emu->emu1010.optical_in ? EMU_HANA_OPTICAL_IN_ADAT : 0) |
|
||||
(emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : 0);
|
||||
snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, tmp);
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu1010_optical_out = {
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Optical Output Mode",
|
||||
.count = 1,
|
||||
.info = snd_emu1010_optical_out_info,
|
||||
.get = snd_emu1010_optical_out_get,
|
||||
.put = snd_emu1010_optical_out_put
|
||||
};
|
||||
|
||||
static int snd_emu1010_optical_in_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static const char * const texts[2] = {
|
||||
"SPDIF", "ADAT"
|
||||
};
|
||||
|
||||
return snd_ctl_enum_info(uinfo, 1, 2, texts);
|
||||
}
|
||||
|
||||
static int snd_emu1010_optical_in_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
ucontrol->value.enumerated.item[0] = emu->emu1010.optical_in;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_emu1010_optical_in_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
|
||||
unsigned int val;
|
||||
u32 tmp;
|
||||
int change = 0;
|
||||
|
||||
val = ucontrol->value.enumerated.item[0];
|
||||
/* Limit: uinfo->value.enumerated.items = 2; */
|
||||
if (val >= 2)
|
||||
return -EINVAL;
|
||||
change = (emu->emu1010.optical_in != val);
|
||||
if (change) {
|
||||
emu->emu1010.optical_in = val;
|
||||
tmp = (emu->emu1010.optical_in ? EMU_HANA_OPTICAL_IN_ADAT : 0) |
|
||||
(emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : 0);
|
||||
snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, tmp);
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
static struct snd_kcontrol_new snd_emu1010_optical_in = {
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "Optical Input Mode",
|
||||
.count = 1,
|
||||
.info = snd_emu1010_optical_in_info,
|
||||
.get = snd_emu1010_optical_in_get,
|
||||
.put = snd_emu1010_optical_in_put
|
||||
};
|
||||
|
||||
static int snd_audigy_i2c_capture_source_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
@ -2051,6 +2153,14 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu,
|
||||
snd_ctl_new1(&snd_emu1010_internal_clock, emu));
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_ctl_add(card,
|
||||
snd_ctl_new1(&snd_emu1010_optical_out, emu));
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_ctl_add(card,
|
||||
snd_ctl_new1(&snd_emu1010_optical_in, emu));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
} else if (emu->card_capabilities->emu_model) {
|
||||
/* all other e-mu cards for now */
|
||||
@ -2086,6 +2196,14 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu,
|
||||
snd_ctl_new1(&snd_emu1010_internal_clock, emu));
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_ctl_add(card,
|
||||
snd_ctl_new1(&snd_emu1010_optical_out, emu));
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = snd_ctl_add(card,
|
||||
snd_ctl_new1(&snd_emu1010_optical_in, emu));
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if ( emu->card_capabilities->i2c_adc) {
|
||||
|
@ -5,6 +5,7 @@ config SND_HDA
|
||||
select SND_PCM
|
||||
select SND_VMASTER
|
||||
select SND_KCTL_JACK
|
||||
select SND_HDA_CORE
|
||||
|
||||
config SND_HDA_INTEL
|
||||
tristate "HD Audio PCI"
|
||||
|
@ -4,13 +4,12 @@ snd-hda-tegra-objs := hda_tegra.o
|
||||
# for haswell power well
|
||||
snd-hda-intel-$(CONFIG_SND_HDA_I915) += hda_i915.o
|
||||
|
||||
snd-hda-codec-y := hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o
|
||||
snd-hda-codec-y := hda_bind.o hda_codec.o hda_jack.o hda_auto_parser.o hda_sysfs.o
|
||||
snd-hda-codec-$(CONFIG_PROC_FS) += hda_proc.o
|
||||
snd-hda-codec-$(CONFIG_SND_HDA_HWDEP) += hda_hwdep.o
|
||||
snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += hda_beep.o
|
||||
|
||||
# for trace-points
|
||||
CFLAGS_hda_codec.o := -I$(src)
|
||||
CFLAGS_hda_controller.o := -I$(src)
|
||||
|
||||
snd-hda-codec-generic-objs := hda_generic.o
|
||||
|
@ -172,7 +172,7 @@ int snd_hda_parse_pin_defcfg(struct hda_codec *codec,
|
||||
const hda_nid_t *ignore_nids,
|
||||
unsigned int cond_flags)
|
||||
{
|
||||
hda_nid_t nid, end_nid;
|
||||
hda_nid_t nid;
|
||||
short seq, assoc_line_out;
|
||||
struct auto_out_pin line_out[ARRAY_SIZE(cfg->line_out_pins)];
|
||||
struct auto_out_pin speaker_out[ARRAY_SIZE(cfg->speaker_pins)];
|
||||
@ -189,8 +189,7 @@ int snd_hda_parse_pin_defcfg(struct hda_codec *codec,
|
||||
memset(hp_out, 0, sizeof(hp_out));
|
||||
assoc_line_out = 0;
|
||||
|
||||
end_nid = codec->start_nid + codec->num_nodes;
|
||||
for (nid = codec->start_nid; nid < end_nid; nid++) {
|
||||
for_each_hda_codec_node(nid, codec) {
|
||||
unsigned int wid_caps = get_wcaps(codec, nid);
|
||||
unsigned int wid_type = get_wcaps_type(wid_caps);
|
||||
unsigned int def_conf;
|
||||
@ -410,7 +409,7 @@ int snd_hda_parse_pin_defcfg(struct hda_codec *codec,
|
||||
* debug prints of the parsed results
|
||||
*/
|
||||
codec_info(codec, "autoconfig for %s: line_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x) type:%s\n",
|
||||
codec->chip_name, cfg->line_outs, cfg->line_out_pins[0],
|
||||
codec->core.chip_name, cfg->line_outs, cfg->line_out_pins[0],
|
||||
cfg->line_out_pins[1], cfg->line_out_pins[2],
|
||||
cfg->line_out_pins[3], cfg->line_out_pins[4],
|
||||
cfg->line_out_type == AUTO_PIN_HP_OUT ? "hp" :
|
||||
@ -836,33 +835,33 @@ static void apply_fixup(struct hda_codec *codec, int id, int action, int depth)
|
||||
if (action != HDA_FIXUP_ACT_PRE_PROBE || !fix->v.pins)
|
||||
break;
|
||||
codec_dbg(codec, "%s: Apply pincfg for %s\n",
|
||||
codec->chip_name, modelname);
|
||||
codec->core.chip_name, modelname);
|
||||
snd_hda_apply_pincfgs(codec, fix->v.pins);
|
||||
break;
|
||||
case HDA_FIXUP_VERBS:
|
||||
if (action != HDA_FIXUP_ACT_PROBE || !fix->v.verbs)
|
||||
break;
|
||||
codec_dbg(codec, "%s: Apply fix-verbs for %s\n",
|
||||
codec->chip_name, modelname);
|
||||
codec->core.chip_name, modelname);
|
||||
snd_hda_add_verbs(codec, fix->v.verbs);
|
||||
break;
|
||||
case HDA_FIXUP_FUNC:
|
||||
if (!fix->v.func)
|
||||
break;
|
||||
codec_dbg(codec, "%s: Apply fix-func for %s\n",
|
||||
codec->chip_name, modelname);
|
||||
codec->core.chip_name, modelname);
|
||||
fix->v.func(codec, fix, action);
|
||||
break;
|
||||
case HDA_FIXUP_PINCTLS:
|
||||
if (action != HDA_FIXUP_ACT_PROBE || !fix->v.pins)
|
||||
break;
|
||||
codec_dbg(codec, "%s: Apply pinctl for %s\n",
|
||||
codec->chip_name, modelname);
|
||||
codec->core.chip_name, modelname);
|
||||
set_pin_targets(codec, fix->v.pins);
|
||||
break;
|
||||
default:
|
||||
codec_err(codec, "%s: Invalid fixup type %d\n",
|
||||
codec->chip_name, fix->type);
|
||||
codec->core.chip_name, fix->type);
|
||||
break;
|
||||
}
|
||||
if (!fix->chained || fix->chained_before)
|
||||
@ -912,16 +911,16 @@ void snd_hda_pick_pin_fixup(struct hda_codec *codec,
|
||||
return;
|
||||
|
||||
for (pq = pin_quirk; pq->subvendor; pq++) {
|
||||
if ((codec->subsystem_id & 0xffff0000) != (pq->subvendor << 16))
|
||||
if ((codec->core.subsystem_id & 0xffff0000) != (pq->subvendor << 16))
|
||||
continue;
|
||||
if (codec->vendor_id != pq->codec)
|
||||
if (codec->core.vendor_id != pq->codec)
|
||||
continue;
|
||||
if (pin_config_match(codec, pq->pins)) {
|
||||
codec->fixup_id = pq->value;
|
||||
#ifdef CONFIG_SND_DEBUG_VERBOSE
|
||||
codec->fixup_name = pq->name;
|
||||
codec_dbg(codec, "%s: picked fixup %s (pin match)\n",
|
||||
codec->chip_name, codec->fixup_name);
|
||||
codec->core.chip_name, codec->fixup_name);
|
||||
#endif
|
||||
codec->fixup_list = fixlist;
|
||||
return;
|
||||
@ -963,7 +962,7 @@ void snd_hda_pick_fixup(struct hda_codec *codec,
|
||||
codec->fixup_name = NULL;
|
||||
codec->fixup_id = HDA_FIXUP_ID_NO_FIXUP;
|
||||
codec_dbg(codec, "%s: picked no fixup (nofixup specified)\n",
|
||||
codec->chip_name);
|
||||
codec->core.chip_name);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -974,7 +973,7 @@ void snd_hda_pick_fixup(struct hda_codec *codec,
|
||||
codec->fixup_name = models->name;
|
||||
codec->fixup_list = fixlist;
|
||||
codec_dbg(codec, "%s: picked fixup %s (model specified)\n",
|
||||
codec->chip_name, codec->fixup_name);
|
||||
codec->core.chip_name, codec->fixup_name);
|
||||
return;
|
||||
}
|
||||
models++;
|
||||
@ -987,7 +986,7 @@ void snd_hda_pick_fixup(struct hda_codec *codec,
|
||||
#ifdef CONFIG_SND_DEBUG_VERBOSE
|
||||
name = q->name;
|
||||
codec_dbg(codec, "%s: picked fixup %s (PCI SSID%s)\n",
|
||||
codec->chip_name, name, q->subdevice_mask ? "" : " - vendor generic");
|
||||
codec->core.chip_name, name, q->subdevice_mask ? "" : " - vendor generic");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -996,12 +995,12 @@ void snd_hda_pick_fixup(struct hda_codec *codec,
|
||||
unsigned int vendorid =
|
||||
q->subdevice | (q->subvendor << 16);
|
||||
unsigned int mask = 0xffff0000 | q->subdevice_mask;
|
||||
if ((codec->subsystem_id & mask) == (vendorid & mask)) {
|
||||
if ((codec->core.subsystem_id & mask) == (vendorid & mask)) {
|
||||
id = q->value;
|
||||
#ifdef CONFIG_SND_DEBUG_VERBOSE
|
||||
name = q->name;
|
||||
codec_dbg(codec, "%s: picked fixup %s (codec SSID)\n",
|
||||
codec->chip_name, name);
|
||||
codec->core.chip_name, name);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
@ -33,30 +33,36 @@ enum {
|
||||
DIGBEEP_HZ_MAX = 12000000, /* 12 KHz */
|
||||
};
|
||||
|
||||
static void snd_hda_generate_beep(struct work_struct *work)
|
||||
/* generate or stop tone */
|
||||
static void generate_tone(struct hda_beep *beep, int tone)
|
||||
{
|
||||
struct hda_beep *beep =
|
||||
container_of(work, struct hda_beep, beep_work);
|
||||
struct hda_codec *codec = beep->codec;
|
||||
int tone;
|
||||
|
||||
if (!beep->enabled)
|
||||
return;
|
||||
|
||||
tone = beep->tone;
|
||||
if (tone && !beep->playing) {
|
||||
snd_hda_power_up(codec);
|
||||
if (beep->power_hook)
|
||||
beep->power_hook(beep, true);
|
||||
beep->playing = 1;
|
||||
}
|
||||
/* generate tone */
|
||||
snd_hda_codec_write(codec, beep->nid, 0,
|
||||
AC_VERB_SET_BEEP_CONTROL, tone);
|
||||
if (!tone && beep->playing) {
|
||||
beep->playing = 0;
|
||||
if (beep->power_hook)
|
||||
beep->power_hook(beep, false);
|
||||
snd_hda_power_down(codec);
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_hda_generate_beep(struct work_struct *work)
|
||||
{
|
||||
struct hda_beep *beep =
|
||||
container_of(work, struct hda_beep, beep_work);
|
||||
|
||||
if (beep->enabled)
|
||||
generate_tone(beep, beep->tone);
|
||||
}
|
||||
|
||||
/* (non-standard) Linear beep tone calculation for IDT/STAC codecs
|
||||
*
|
||||
* The tone frequency of beep generator on IDT/STAC codecs is
|
||||
@ -130,10 +136,7 @@ static void turn_off_beep(struct hda_beep *beep)
|
||||
cancel_work_sync(&beep->beep_work);
|
||||
if (beep->playing) {
|
||||
/* turn off beep */
|
||||
snd_hda_codec_write(beep->codec, beep->nid, 0,
|
||||
AC_VERB_SET_BEEP_CONTROL, 0);
|
||||
beep->playing = 0;
|
||||
snd_hda_power_down(beep->codec);
|
||||
generate_tone(beep, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,15 +163,15 @@ static int snd_hda_do_attach(struct hda_beep *beep)
|
||||
input_dev->name = "HDA Digital PCBeep";
|
||||
input_dev->phys = beep->phys;
|
||||
input_dev->id.bustype = BUS_PCI;
|
||||
input_dev->dev.parent = &codec->card->card_dev;
|
||||
|
||||
input_dev->id.vendor = codec->vendor_id >> 16;
|
||||
input_dev->id.product = codec->vendor_id & 0xffff;
|
||||
input_dev->id.vendor = codec->core.vendor_id >> 16;
|
||||
input_dev->id.product = codec->core.vendor_id & 0xffff;
|
||||
input_dev->id.version = 0x01;
|
||||
|
||||
input_dev->evbit[0] = BIT_MASK(EV_SND);
|
||||
input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
|
||||
input_dev->event = snd_hda_beep_event;
|
||||
input_dev->dev.parent = &codec->dev;
|
||||
input_set_drvdata(input_dev, beep);
|
||||
|
||||
beep->dev = input_dev;
|
||||
@ -224,7 +227,7 @@ int snd_hda_attach_beep_device(struct hda_codec *codec, int nid)
|
||||
if (beep == NULL)
|
||||
return -ENOMEM;
|
||||
snprintf(beep->phys, sizeof(beep->phys),
|
||||
"card%d/codec#%d/beep0", codec->bus->card->number, codec->addr);
|
||||
"card%d/codec#%d/beep0", codec->card->number, codec->addr);
|
||||
/* enable linear scale */
|
||||
snd_hda_codec_write_cache(codec, nid, 0,
|
||||
AC_VERB_SET_DIGI_CONVERT_2, 0x01);
|
||||
|
@ -40,6 +40,7 @@ struct hda_beep {
|
||||
unsigned int playing:1;
|
||||
struct work_struct beep_work; /* scheduled task for beep event */
|
||||
struct mutex mutex;
|
||||
void (*power_hook)(struct hda_beep *beep, bool on);
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SND_HDA_INPUT_BEEP
|
||||
|
273
sound/pci/hda/hda_bind.c
Normal file
273
sound/pci/hda/hda_bind.c
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
* HD-audio codec driver binding
|
||||
* Copyright (c) Takashi Iwai <tiwai@suse.de>
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <sound/core.h>
|
||||
#include "hda_codec.h"
|
||||
#include "hda_local.h"
|
||||
|
||||
/*
|
||||
* find a matching codec preset
|
||||
*/
|
||||
static int hda_codec_match(struct hdac_device *dev, struct hdac_driver *drv)
|
||||
{
|
||||
struct hda_codec *codec = container_of(dev, struct hda_codec, core);
|
||||
struct hda_codec_driver *driver =
|
||||
container_of(drv, struct hda_codec_driver, core);
|
||||
const struct hda_codec_preset *preset;
|
||||
/* check probe_id instead of vendor_id if set */
|
||||
u32 id = codec->probe_id ? codec->probe_id : codec->core.vendor_id;
|
||||
|
||||
for (preset = driver->preset; preset->id; preset++) {
|
||||
u32 mask = preset->mask;
|
||||
|
||||
if (preset->afg && preset->afg != codec->core.afg)
|
||||
continue;
|
||||
if (preset->mfg && preset->mfg != codec->core.mfg)
|
||||
continue;
|
||||
if (!mask)
|
||||
mask = ~0;
|
||||
if (preset->id == (id & mask) &&
|
||||
(!preset->rev || preset->rev == codec->core.revision_id)) {
|
||||
codec->preset = preset;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* process an unsolicited event */
|
||||
static void hda_codec_unsol_event(struct hdac_device *dev, unsigned int ev)
|
||||
{
|
||||
struct hda_codec *codec = container_of(dev, struct hda_codec, core);
|
||||
|
||||
if (codec->patch_ops.unsol_event)
|
||||
codec->patch_ops.unsol_event(codec, ev);
|
||||
}
|
||||
|
||||
/* reset the codec name from the preset */
|
||||
static int codec_refresh_name(struct hda_codec *codec, const char *name)
|
||||
{
|
||||
if (name) {
|
||||
kfree(codec->core.chip_name);
|
||||
codec->core.chip_name = kstrdup(name, GFP_KERNEL);
|
||||
}
|
||||
return codec->core.chip_name ? 0 : -ENOMEM;
|
||||
}
|
||||
|
||||
static int hda_codec_driver_probe(struct device *dev)
|
||||
{
|
||||
struct hda_codec *codec = dev_to_hda_codec(dev);
|
||||
struct module *owner = dev->driver->owner;
|
||||
int err;
|
||||
|
||||
if (WARN_ON(!codec->preset))
|
||||
return -EINVAL;
|
||||
|
||||
err = codec_refresh_name(codec, codec->preset->name);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
err = snd_hdac_regmap_init(&codec->core);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
if (!try_module_get(owner)) {
|
||||
err = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = codec->preset->patch(codec);
|
||||
if (err < 0)
|
||||
goto error_module;
|
||||
|
||||
err = snd_hda_codec_build_pcms(codec);
|
||||
if (err < 0)
|
||||
goto error_module;
|
||||
err = snd_hda_codec_build_controls(codec);
|
||||
if (err < 0)
|
||||
goto error_module;
|
||||
if (codec->card->registered) {
|
||||
err = snd_card_register(codec->card);
|
||||
if (err < 0)
|
||||
goto error_module;
|
||||
snd_hda_codec_register(codec);
|
||||
}
|
||||
|
||||
codec->core.lazy_cache = true;
|
||||
return 0;
|
||||
|
||||
error_module:
|
||||
module_put(owner);
|
||||
|
||||
error:
|
||||
snd_hda_codec_cleanup_for_unbind(codec);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hda_codec_driver_remove(struct device *dev)
|
||||
{
|
||||
struct hda_codec *codec = dev_to_hda_codec(dev);
|
||||
|
||||
if (codec->patch_ops.free)
|
||||
codec->patch_ops.free(codec);
|
||||
snd_hda_codec_cleanup_for_unbind(codec);
|
||||
module_put(dev->driver->owner);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hda_codec_driver_shutdown(struct device *dev)
|
||||
{
|
||||
struct hda_codec *codec = dev_to_hda_codec(dev);
|
||||
|
||||
if (!pm_runtime_suspended(dev) && codec->patch_ops.reboot_notify)
|
||||
codec->patch_ops.reboot_notify(codec);
|
||||
}
|
||||
|
||||
int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name,
|
||||
struct module *owner)
|
||||
{
|
||||
drv->core.driver.name = name;
|
||||
drv->core.driver.owner = owner;
|
||||
drv->core.driver.bus = &snd_hda_bus_type;
|
||||
drv->core.driver.probe = hda_codec_driver_probe;
|
||||
drv->core.driver.remove = hda_codec_driver_remove;
|
||||
drv->core.driver.shutdown = hda_codec_driver_shutdown;
|
||||
drv->core.driver.pm = &hda_codec_driver_pm;
|
||||
drv->core.type = HDA_DEV_LEGACY;
|
||||
drv->core.match = hda_codec_match;
|
||||
drv->core.unsol_event = hda_codec_unsol_event;
|
||||
return driver_register(&drv->core.driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__hda_codec_driver_register);
|
||||
|
||||
void hda_codec_driver_unregister(struct hda_codec_driver *drv)
|
||||
{
|
||||
driver_unregister(&drv->core.driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hda_codec_driver_unregister);
|
||||
|
||||
static inline bool codec_probed(struct hda_codec *codec)
|
||||
{
|
||||
return device_attach(hda_codec_dev(codec)) > 0 && codec->preset;
|
||||
}
|
||||
|
||||
/* try to auto-load and bind the codec module */
|
||||
static void codec_bind_module(struct hda_codec *codec)
|
||||
{
|
||||
#ifdef MODULE
|
||||
request_module("snd-hda-codec-id:%08x", codec->core.vendor_id);
|
||||
if (codec_probed(codec))
|
||||
return;
|
||||
request_module("snd-hda-codec-id:%04x*",
|
||||
(codec->core.vendor_id >> 16) & 0xffff);
|
||||
if (codec_probed(codec))
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI)
|
||||
/* if all audio out widgets are digital, let's assume the codec as a HDMI/DP */
|
||||
static bool is_likely_hdmi_codec(struct hda_codec *codec)
|
||||
{
|
||||
hda_nid_t nid;
|
||||
|
||||
for_each_hda_codec_node(nid, codec) {
|
||||
unsigned int wcaps = get_wcaps(codec, nid);
|
||||
switch (get_wcaps_type(wcaps)) {
|
||||
case AC_WID_AUD_IN:
|
||||
return false; /* HDMI parser supports only HDMI out */
|
||||
case AC_WID_AUD_OUT:
|
||||
if (!(wcaps & AC_WCAP_DIGITAL))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
/* no HDMI codec parser support */
|
||||
#define is_likely_hdmi_codec(codec) false
|
||||
#endif /* CONFIG_SND_HDA_CODEC_HDMI */
|
||||
|
||||
static int codec_bind_generic(struct hda_codec *codec)
|
||||
{
|
||||
if (codec->probe_id)
|
||||
return -ENODEV;
|
||||
|
||||
if (is_likely_hdmi_codec(codec)) {
|
||||
codec->probe_id = HDA_CODEC_ID_GENERIC_HDMI;
|
||||
#if IS_MODULE(CONFIG_SND_HDA_CODEC_HDMI)
|
||||
request_module("snd-hda-codec-hdmi");
|
||||
#endif
|
||||
if (codec_probed(codec))
|
||||
return 0;
|
||||
}
|
||||
|
||||
codec->probe_id = HDA_CODEC_ID_GENERIC;
|
||||
#if IS_MODULE(CONFIG_SND_HDA_GENERIC)
|
||||
request_module("snd-hda-codec-generic");
|
||||
#endif
|
||||
if (codec_probed(codec))
|
||||
return 0;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_HDA_GENERIC)
|
||||
#define is_generic_config(codec) \
|
||||
(codec->modelname && !strcmp(codec->modelname, "generic"))
|
||||
#else
|
||||
#define is_generic_config(codec) 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* snd_hda_codec_configure - (Re-)configure the HD-audio codec
|
||||
* @codec: the HDA codec
|
||||
*
|
||||
* Start parsing of the given codec tree and (re-)initialize the whole
|
||||
* patch instance.
|
||||
*
|
||||
* Returns 0 if successful or a negative error code.
|
||||
*/
|
||||
int snd_hda_codec_configure(struct hda_codec *codec)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (is_generic_config(codec))
|
||||
codec->probe_id = HDA_CODEC_ID_GENERIC;
|
||||
else
|
||||
codec->probe_id = 0;
|
||||
|
||||
err = snd_hdac_device_register(&codec->core);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (!codec->preset)
|
||||
codec_bind_module(codec);
|
||||
if (!codec->preset) {
|
||||
err = codec_bind_generic(codec);
|
||||
if (err < 0) {
|
||||
codec_err(codec, "Unable to bind the codec\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* audio codec should override the mixer name */
|
||||
if (codec->core.afg || !*codec->card->mixername)
|
||||
snprintf(codec->card->mixername,
|
||||
sizeof(codec->card->mixername), "%s %s",
|
||||
codec->core.vendor_name, codec->core.chip_name);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
snd_hdac_device_unregister(&codec->core);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hda_codec_configure);
|
File diff suppressed because it is too large
Load Diff
@ -21,41 +21,14 @@
|
||||
#ifndef __SOUND_HDA_CODEC_H
|
||||
#define __SOUND_HDA_CODEC_H
|
||||
|
||||
#include <linux/kref.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/hwdep.h>
|
||||
#include <sound/hdaudio.h>
|
||||
#include <sound/hda_verbs.h>
|
||||
|
||||
/*
|
||||
* generic arrays
|
||||
*/
|
||||
struct snd_array {
|
||||
unsigned int used;
|
||||
unsigned int alloced;
|
||||
unsigned int elem_size;
|
||||
unsigned int alloc_align;
|
||||
void *list;
|
||||
};
|
||||
|
||||
void *snd_array_new(struct snd_array *array);
|
||||
void snd_array_free(struct snd_array *array);
|
||||
static inline void snd_array_init(struct snd_array *array, unsigned int size,
|
||||
unsigned int align)
|
||||
{
|
||||
array->elem_size = size;
|
||||
array->alloc_align = align;
|
||||
}
|
||||
|
||||
static inline void *snd_array_elem(struct snd_array *array, unsigned int idx)
|
||||
{
|
||||
return array->list + idx * array->elem_size;
|
||||
}
|
||||
|
||||
static inline unsigned int snd_array_index(struct snd_array *array, void *ptr)
|
||||
{
|
||||
return (unsigned long)(ptr - array->list) / array->elem_size;
|
||||
}
|
||||
#include <sound/hda_regmap.h>
|
||||
|
||||
/*
|
||||
* Structures
|
||||
@ -66,10 +39,6 @@ struct hda_beep;
|
||||
struct hda_codec;
|
||||
struct hda_pcm;
|
||||
struct hda_pcm_stream;
|
||||
struct hda_bus_unsolicited;
|
||||
|
||||
/* NID type */
|
||||
typedef u16 hda_nid_t;
|
||||
|
||||
/* bus operators */
|
||||
struct hda_bus_ops {
|
||||
@ -84,10 +53,6 @@ struct hda_bus_ops {
|
||||
struct hda_pcm *pcm);
|
||||
/* reset bus for retry verb */
|
||||
void (*bus_reset)(struct hda_bus *bus);
|
||||
#ifdef CONFIG_PM
|
||||
/* notify power-up/down from codec to controller */
|
||||
void (*pm_notify)(struct hda_bus *bus, bool power_up);
|
||||
#endif
|
||||
#ifdef CONFIG_SND_HDA_DSP_LOADER
|
||||
/* prepare DSP transfer */
|
||||
int (*load_dsp_prepare)(struct hda_bus *bus, unsigned int format,
|
||||
@ -101,15 +66,6 @@ struct hda_bus_ops {
|
||||
#endif
|
||||
};
|
||||
|
||||
/* template to pass to the bus constructor */
|
||||
struct hda_bus_template {
|
||||
void *private_data;
|
||||
struct pci_dev *pci;
|
||||
const char *modelname;
|
||||
int *power_save;
|
||||
struct hda_bus_ops ops;
|
||||
};
|
||||
|
||||
/*
|
||||
* codec bus
|
||||
*
|
||||
@ -117,42 +73,28 @@ struct hda_bus_template {
|
||||
* A hda_bus contains several codecs in the list codec_list.
|
||||
*/
|
||||
struct hda_bus {
|
||||
struct hdac_bus core;
|
||||
|
||||
struct snd_card *card;
|
||||
|
||||
/* copied from template */
|
||||
void *private_data;
|
||||
struct pci_dev *pci;
|
||||
const char *modelname;
|
||||
int *power_save;
|
||||
struct hda_bus_ops ops;
|
||||
|
||||
/* codec linked list */
|
||||
struct list_head codec_list;
|
||||
unsigned int num_codecs;
|
||||
/* link caddr -> codec */
|
||||
struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1];
|
||||
|
||||
struct mutex cmd_mutex;
|
||||
struct mutex prepare_mutex;
|
||||
|
||||
/* unsolicited event queue */
|
||||
struct hda_bus_unsolicited *unsol;
|
||||
char workq_name[16];
|
||||
struct workqueue_struct *workq; /* common workqueue for codecs */
|
||||
|
||||
/* assigned PCMs */
|
||||
DECLARE_BITMAP(pcm_dev_bits, SNDRV_PCM_DEVICES);
|
||||
|
||||
/* misc op flags */
|
||||
unsigned int needs_damn_long_delay :1;
|
||||
unsigned int allow_bus_reset:1; /* allow bus reset at fatal error */
|
||||
unsigned int sync_write:1; /* sync after verb write */
|
||||
/* status for codec/controller */
|
||||
unsigned int shutdown :1; /* being unloaded */
|
||||
unsigned int rirb_error:1; /* error in codec communication */
|
||||
unsigned int response_reset:1; /* controller was reset */
|
||||
unsigned int in_reset:1; /* during reset operation */
|
||||
unsigned int power_keep_link_on:1; /* don't power off HDA link */
|
||||
unsigned int no_response_fallback:1; /* don't fallback at RIRB error */
|
||||
|
||||
int primary_dig_out_type; /* primary digital out PCM type */
|
||||
@ -175,15 +117,22 @@ struct hda_codec_preset {
|
||||
int (*patch)(struct hda_codec *codec);
|
||||
};
|
||||
|
||||
struct hda_codec_preset_list {
|
||||
#define HDA_CODEC_ID_GENERIC_HDMI 0x00000101
|
||||
#define HDA_CODEC_ID_GENERIC 0x00000201
|
||||
|
||||
struct hda_codec_driver {
|
||||
struct hdac_driver core;
|
||||
const struct hda_codec_preset *preset;
|
||||
struct module *owner;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/* initial hook */
|
||||
int snd_hda_add_codec_preset(struct hda_codec_preset_list *preset);
|
||||
int snd_hda_delete_codec_preset(struct hda_codec_preset_list *preset);
|
||||
int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name,
|
||||
struct module *owner);
|
||||
#define hda_codec_driver_register(drv) \
|
||||
__hda_codec_driver_register(drv, KBUILD_MODNAME, THIS_MODULE)
|
||||
void hda_codec_driver_unregister(struct hda_codec_driver *drv);
|
||||
#define module_hda_codec_driver(drv) \
|
||||
module_driver(drv, hda_codec_driver_register, \
|
||||
hda_codec_driver_unregister)
|
||||
|
||||
/* ops set by the preset patch */
|
||||
struct hda_codec_ops {
|
||||
@ -200,25 +149,7 @@ struct hda_codec_ops {
|
||||
int (*check_power_status)(struct hda_codec *codec, hda_nid_t nid);
|
||||
#endif
|
||||
void (*reboot_notify)(struct hda_codec *codec);
|
||||
};
|
||||
|
||||
/* record for amp information cache */
|
||||
struct hda_cache_head {
|
||||
u32 key:31; /* hash key */
|
||||
u32 dirty:1;
|
||||
u16 val; /* assigned value */
|
||||
u16 next;
|
||||
};
|
||||
|
||||
struct hda_amp_info {
|
||||
struct hda_cache_head head;
|
||||
u32 amp_caps; /* amp capabilities */
|
||||
u16 vol[2]; /* current volume & mute */
|
||||
};
|
||||
|
||||
struct hda_cache_rec {
|
||||
u16 hash[64]; /* hash table for index */
|
||||
struct snd_array buf; /* record entries */
|
||||
void (*stream_pm)(struct hda_codec *codec, hda_nid_t nid, bool on);
|
||||
};
|
||||
|
||||
/* PCM callbacks */
|
||||
@ -267,41 +198,29 @@ struct hda_pcm {
|
||||
int device; /* device number to assign */
|
||||
struct snd_pcm *pcm; /* assigned PCM instance */
|
||||
bool own_chmap; /* codec driver provides own channel maps */
|
||||
/* private: */
|
||||
struct hda_codec *codec;
|
||||
struct kref kref;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/* codec information */
|
||||
struct hda_codec {
|
||||
struct device dev;
|
||||
struct hdac_device core;
|
||||
struct hda_bus *bus;
|
||||
struct snd_card *card;
|
||||
unsigned int addr; /* codec addr*/
|
||||
struct list_head list; /* list point */
|
||||
|
||||
hda_nid_t afg; /* AFG node id */
|
||||
hda_nid_t mfg; /* MFG node id */
|
||||
|
||||
/* ids */
|
||||
u8 afg_function_id;
|
||||
u8 mfg_function_id;
|
||||
u8 afg_unsol;
|
||||
u8 mfg_unsol;
|
||||
u32 vendor_id;
|
||||
u32 subsystem_id;
|
||||
u32 revision_id;
|
||||
u32 probe_id; /* overridden id for probing */
|
||||
|
||||
/* detected preset */
|
||||
const struct hda_codec_preset *preset;
|
||||
struct module *owner;
|
||||
int (*parser)(struct hda_codec *codec);
|
||||
const char *vendor_name; /* codec vendor name */
|
||||
const char *chip_name; /* codec chip name */
|
||||
const char *modelname; /* model name for preset */
|
||||
|
||||
/* set by patch */
|
||||
struct hda_codec_ops patch_ops;
|
||||
|
||||
/* PCM to create, set by patch_ops.build_pcms callback */
|
||||
unsigned int num_pcms;
|
||||
struct hda_pcm *pcm_info;
|
||||
struct list_head pcm_list_head;
|
||||
|
||||
/* codec specific info */
|
||||
void *spec;
|
||||
@ -311,21 +230,15 @@ struct hda_codec {
|
||||
unsigned int beep_mode;
|
||||
|
||||
/* widget capabilities cache */
|
||||
unsigned int num_nodes;
|
||||
hda_nid_t start_nid;
|
||||
u32 *wcaps;
|
||||
|
||||
struct snd_array mixers; /* list of assigned mixer elements */
|
||||
struct snd_array nids; /* list of mapped mixer elements */
|
||||
|
||||
struct hda_cache_rec amp_cache; /* cache for amp access */
|
||||
struct hda_cache_rec cmd_cache; /* cache for other commands */
|
||||
|
||||
struct list_head conn_list; /* linked-list of connection-list */
|
||||
|
||||
struct mutex spdif_mutex;
|
||||
struct mutex control_mutex;
|
||||
struct mutex hash_mutex;
|
||||
struct snd_array spdif_out;
|
||||
unsigned int spdif_in_enable; /* SPDIF input enable? */
|
||||
const hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */
|
||||
@ -345,6 +258,8 @@ struct hda_codec {
|
||||
#endif
|
||||
|
||||
/* misc flags */
|
||||
unsigned int in_freeing:1; /* being released */
|
||||
unsigned int registered:1; /* codec was registered */
|
||||
unsigned int spdif_status_reset :1; /* needs to toggle SPDIF for each
|
||||
* status change
|
||||
* (e.g. Realtek codecs)
|
||||
@ -362,22 +277,14 @@ struct hda_codec {
|
||||
unsigned int inv_eapd:1; /* broken h/w: inverted EAPD control */
|
||||
unsigned int inv_jack_detect:1; /* broken h/w: inverted detection bit */
|
||||
unsigned int pcm_format_first:1; /* PCM format must be set first */
|
||||
unsigned int epss:1; /* supporting EPSS? */
|
||||
unsigned int cached_write:1; /* write only to caches */
|
||||
unsigned int dp_mst:1; /* support DP1.2 Multi-stream transport */
|
||||
unsigned int dump_coef:1; /* dump processing coefs in codec proc file */
|
||||
unsigned int power_save_node:1; /* advanced PM for each widget */
|
||||
#ifdef CONFIG_PM
|
||||
unsigned int power_on :1; /* current (global) power-state */
|
||||
unsigned int d3_stop_clk:1; /* support D3 operation without BCLK */
|
||||
unsigned int pm_up_notified:1; /* PM notified to controller */
|
||||
unsigned int in_pm:1; /* suspend/resume being performed */
|
||||
int power_transition; /* power-state in transition */
|
||||
int power_count; /* current (global) power refcount */
|
||||
struct delayed_work power_work; /* delayed task for powerdown */
|
||||
unsigned long power_on_acct;
|
||||
unsigned long power_off_acct;
|
||||
unsigned long power_jiffies;
|
||||
spinlock_t power_lock;
|
||||
#endif
|
||||
|
||||
/* filter the requested power state per nid */
|
||||
@ -409,10 +316,11 @@ struct hda_codec {
|
||||
struct snd_array verbs;
|
||||
};
|
||||
|
||||
/* direction */
|
||||
enum {
|
||||
HDA_INPUT, HDA_OUTPUT
|
||||
};
|
||||
#define dev_to_hda_codec(_dev) container_of(_dev, struct hda_codec, core.dev)
|
||||
#define hda_codec_dev(_dev) (&(_dev)->core.dev)
|
||||
|
||||
#define list_for_each_codec(c, bus) \
|
||||
list_for_each_entry(c, &(bus)->core.codec_list, core.list)
|
||||
|
||||
/* snd_hda_codec_read/write optional flags */
|
||||
#define HDA_RW_NO_RESPONSE_FALLBACK (1 << 0)
|
||||
@ -420,10 +328,9 @@ enum {
|
||||
/*
|
||||
* constructors
|
||||
*/
|
||||
int snd_hda_bus_new(struct snd_card *card, const struct hda_bus_template *temp,
|
||||
struct hda_bus **busp);
|
||||
int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
|
||||
struct hda_codec **codecp);
|
||||
int snd_hda_bus_new(struct snd_card *card, struct hda_bus **busp);
|
||||
int snd_hda_codec_new(struct hda_bus *bus, struct snd_card *card,
|
||||
unsigned int codec_addr, struct hda_codec **codecp);
|
||||
int snd_hda_codec_configure(struct hda_codec *codec);
|
||||
int snd_hda_codec_update_widgets(struct hda_codec *codec);
|
||||
|
||||
@ -436,9 +343,9 @@ unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid,
|
||||
int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int flags,
|
||||
unsigned int verb, unsigned int parm);
|
||||
#define snd_hda_param_read(codec, nid, param) \
|
||||
snd_hda_codec_read(codec, nid, 0, AC_VERB_PARAMETERS, param)
|
||||
int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid,
|
||||
hda_nid_t *start_id);
|
||||
snd_hdac_read_parm(&(codec)->core, nid, param)
|
||||
#define snd_hda_get_sub_nodes(codec, nid, start_nid) \
|
||||
snd_hdac_get_sub_nodes(&(codec)->core, nid, start_nid)
|
||||
int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
|
||||
hda_nid_t *conn_list, int max_conns);
|
||||
static inline int
|
||||
@ -446,9 +353,12 @@ snd_hda_get_num_conns(struct hda_codec *codec, hda_nid_t nid)
|
||||
{
|
||||
return snd_hda_get_connections(codec, nid, NULL, 0);
|
||||
}
|
||||
int snd_hda_get_num_raw_conns(struct hda_codec *codec, hda_nid_t nid);
|
||||
int snd_hda_get_raw_connections(struct hda_codec *codec, hda_nid_t nid,
|
||||
hda_nid_t *conn_list, int max_conns);
|
||||
|
||||
#define snd_hda_get_raw_connections(codec, nid, list, max_conns) \
|
||||
snd_hdac_get_connections(&(codec)->core, nid, list, max_conns)
|
||||
#define snd_hda_get_num_raw_conns(codec, nid) \
|
||||
snd_hdac_get_connections(&(codec)->core, nid, NULL, 0);
|
||||
|
||||
int snd_hda_get_conn_list(struct hda_codec *codec, hda_nid_t nid,
|
||||
const hda_nid_t **listp);
|
||||
int snd_hda_override_conn_list(struct hda_codec *codec, hda_nid_t nid, int nums,
|
||||
@ -470,18 +380,22 @@ void snd_hda_sequence_write(struct hda_codec *codec,
|
||||
const struct hda_verb *seq);
|
||||
|
||||
/* unsolicited event */
|
||||
int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex);
|
||||
static inline void
|
||||
snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex)
|
||||
{
|
||||
snd_hdac_bus_queue_event(&bus->core, res, res_ex);
|
||||
}
|
||||
|
||||
/* cached write */
|
||||
int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
|
||||
int flags, unsigned int verb, unsigned int parm);
|
||||
void snd_hda_sequence_write_cache(struct hda_codec *codec,
|
||||
const struct hda_verb *seq);
|
||||
int snd_hda_codec_update_cache(struct hda_codec *codec, hda_nid_t nid,
|
||||
int flags, unsigned int verb, unsigned int parm);
|
||||
void snd_hda_codec_resume_cache(struct hda_codec *codec);
|
||||
/* both for cmd & amp caches */
|
||||
void snd_hda_codec_flush_cache(struct hda_codec *codec);
|
||||
static inline int
|
||||
snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
|
||||
int flags, unsigned int verb, unsigned int parm)
|
||||
{
|
||||
return snd_hdac_regmap_write(&codec->core, nid, verb, parm);
|
||||
}
|
||||
|
||||
#define snd_hda_codec_update_cache(codec, nid, flags, verb, parm) \
|
||||
snd_hda_codec_write_cache(codec, nid, flags, verb, parm)
|
||||
|
||||
/* the struct for codec->pin_configs */
|
||||
struct hda_pincfg {
|
||||
@ -512,15 +426,24 @@ void snd_hda_spdif_ctls_assign(struct hda_codec *codec, int idx, hda_nid_t nid);
|
||||
/*
|
||||
* Mixer
|
||||
*/
|
||||
int snd_hda_build_controls(struct hda_bus *bus);
|
||||
int snd_hda_codec_build_controls(struct hda_codec *codec);
|
||||
|
||||
/*
|
||||
* PCM
|
||||
*/
|
||||
int snd_hda_build_pcms(struct hda_bus *bus);
|
||||
int snd_hda_codec_parse_pcms(struct hda_codec *codec);
|
||||
int snd_hda_codec_build_pcms(struct hda_codec *codec);
|
||||
|
||||
__printf(2, 3)
|
||||
struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
|
||||
const char *fmt, ...);
|
||||
|
||||
static inline void snd_hda_codec_pcm_get(struct hda_pcm *pcm)
|
||||
{
|
||||
kref_get(&pcm->kref);
|
||||
}
|
||||
void snd_hda_codec_pcm_put(struct hda_pcm *pcm);
|
||||
|
||||
int snd_hda_codec_prepare(struct hda_codec *codec,
|
||||
struct hda_pcm_stream *hinfo,
|
||||
unsigned int stream,
|
||||
@ -552,20 +475,17 @@ extern const struct snd_pcm_chmap_elem snd_pcm_2_1_chmaps[];
|
||||
* Misc
|
||||
*/
|
||||
void snd_hda_get_codec_name(struct hda_codec *codec, char *name, int namelen);
|
||||
void snd_hda_bus_reboot_notify(struct hda_bus *bus);
|
||||
void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg,
|
||||
unsigned int power_state);
|
||||
|
||||
int snd_hda_lock_devices(struct hda_bus *bus);
|
||||
void snd_hda_unlock_devices(struct hda_bus *bus);
|
||||
void snd_hda_bus_reset(struct hda_bus *bus);
|
||||
|
||||
/*
|
||||
* power management
|
||||
*/
|
||||
#ifdef CONFIG_PM
|
||||
int snd_hda_suspend(struct hda_bus *bus);
|
||||
int snd_hda_resume(struct hda_bus *bus);
|
||||
#endif
|
||||
extern const struct dev_pm_ops hda_codec_driver_pm;
|
||||
|
||||
static inline
|
||||
int hda_call_check_power_status(struct hda_codec *codec, hda_nid_t nid)
|
||||
@ -587,65 +507,17 @@ const char *snd_hda_get_jack_location(u32 cfg);
|
||||
/*
|
||||
* power saving
|
||||
*/
|
||||
#define snd_hda_power_up(codec) snd_hdac_power_up(&(codec)->core)
|
||||
#define snd_hda_power_up_pm(codec) snd_hdac_power_up_pm(&(codec)->core)
|
||||
#define snd_hda_power_down(codec) snd_hdac_power_down(&(codec)->core)
|
||||
#define snd_hda_power_down_pm(codec) snd_hdac_power_down_pm(&(codec)->core)
|
||||
#ifdef CONFIG_PM
|
||||
void snd_hda_power_save(struct hda_codec *codec, int delta, bool d3wait);
|
||||
void snd_hda_set_power_save(struct hda_bus *bus, int delay);
|
||||
void snd_hda_update_power_acct(struct hda_codec *codec);
|
||||
#else
|
||||
static inline void snd_hda_power_save(struct hda_codec *codec, int delta,
|
||||
bool d3wait) {}
|
||||
static inline void snd_hda_set_power_save(struct hda_bus *bus, int delay) {}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* snd_hda_power_up - Power-up the codec
|
||||
* @codec: HD-audio codec
|
||||
*
|
||||
* Increment the power-up counter and power up the hardware really when
|
||||
* not turned on yet.
|
||||
*/
|
||||
static inline void snd_hda_power_up(struct hda_codec *codec)
|
||||
{
|
||||
snd_hda_power_save(codec, 1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_power_up_d3wait - Power-up the codec after waiting for any pending
|
||||
* D3 transition to complete. This differs from snd_hda_power_up() when
|
||||
* power_transition == -1. snd_hda_power_up sees this case as a nop,
|
||||
* snd_hda_power_up_d3wait waits for the D3 transition to complete then powers
|
||||
* back up.
|
||||
* @codec: HD-audio codec
|
||||
*
|
||||
* Cancel any power down operation hapenning on the work queue, then power up.
|
||||
*/
|
||||
static inline void snd_hda_power_up_d3wait(struct hda_codec *codec)
|
||||
{
|
||||
snd_hda_power_save(codec, 1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_power_down - Power-down the codec
|
||||
* @codec: HD-audio codec
|
||||
*
|
||||
* Decrement the power-up counter and schedules the power-off work if
|
||||
* the counter rearches to zero.
|
||||
*/
|
||||
static inline void snd_hda_power_down(struct hda_codec *codec)
|
||||
{
|
||||
snd_hda_power_save(codec, -1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_hda_power_sync - Synchronize the power-save status
|
||||
* @codec: HD-audio codec
|
||||
*
|
||||
* Synchronize the actual power state with the power account;
|
||||
* called when power_save parameter is changed
|
||||
*/
|
||||
static inline void snd_hda_power_sync(struct hda_codec *codec)
|
||||
{
|
||||
snd_hda_power_save(codec, 0, false);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SND_HDA_PATCH_LOADER
|
||||
/*
|
||||
* patch firmware
|
||||
|
@ -27,10 +27,8 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include "hda_priv.h"
|
||||
#include "hda_controller.h"
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
@ -259,11 +257,18 @@ static void azx_timecounter_init(struct snd_pcm_substream *substream,
|
||||
tc->cycle_last = last;
|
||||
}
|
||||
|
||||
static inline struct hda_pcm_stream *
|
||||
to_hda_pcm_stream(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
|
||||
return &apcm->info->stream[substream->stream];
|
||||
}
|
||||
|
||||
static u64 azx_adjust_codec_delay(struct snd_pcm_substream *substream,
|
||||
u64 nsec)
|
||||
{
|
||||
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
|
||||
struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
|
||||
struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
|
||||
u64 codec_frames, codec_nsecs;
|
||||
|
||||
if (!hinfo->ops.get_delay)
|
||||
@ -399,7 +404,7 @@ static int azx_setup_periods(struct azx *chip,
|
||||
static int azx_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
|
||||
struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
|
||||
struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
|
||||
struct azx *chip = apcm->chip;
|
||||
struct azx_dev *azx_dev = get_azx_dev(substream);
|
||||
unsigned long flags;
|
||||
@ -410,9 +415,11 @@ static int azx_pcm_close(struct snd_pcm_substream *substream)
|
||||
azx_dev->running = 0;
|
||||
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
||||
azx_release_device(azx_dev);
|
||||
hinfo->ops.close(hinfo, apcm->codec, substream);
|
||||
if (hinfo->ops.close)
|
||||
hinfo->ops.close(hinfo, apcm->codec, substream);
|
||||
snd_hda_power_down(apcm->codec);
|
||||
mutex_unlock(&chip->open_mutex);
|
||||
snd_hda_codec_pcm_put(apcm->info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -441,7 +448,7 @@ static int azx_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
|
||||
struct azx_dev *azx_dev = get_azx_dev(substream);
|
||||
struct azx *chip = apcm->chip;
|
||||
struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
|
||||
struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
|
||||
int err;
|
||||
|
||||
/* reset BDL address */
|
||||
@ -468,7 +475,7 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
|
||||
struct azx *chip = apcm->chip;
|
||||
struct azx_dev *azx_dev = get_azx_dev(substream);
|
||||
struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
|
||||
struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned int bufsize, period_bytes, format_val, stream_tag;
|
||||
int err;
|
||||
@ -708,7 +715,7 @@ unsigned int azx_get_position(struct azx *chip,
|
||||
|
||||
if (substream->runtime) {
|
||||
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
|
||||
struct hda_pcm_stream *hinfo = apcm->hinfo[stream];
|
||||
struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
|
||||
|
||||
if (chip->get_delay[stream])
|
||||
delay += chip->get_delay[stream](chip, azx_dev, pos);
|
||||
@ -732,17 +739,32 @@ static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
azx_get_position(chip, azx_dev));
|
||||
}
|
||||
|
||||
static int azx_get_wallclock_tstamp(struct snd_pcm_substream *substream,
|
||||
struct timespec *ts)
|
||||
static int azx_get_time_info(struct snd_pcm_substream *substream,
|
||||
struct timespec *system_ts, struct timespec *audio_ts,
|
||||
struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
|
||||
struct snd_pcm_audio_tstamp_report *audio_tstamp_report)
|
||||
{
|
||||
struct azx_dev *azx_dev = get_azx_dev(substream);
|
||||
u64 nsec;
|
||||
|
||||
nsec = timecounter_read(&azx_dev->azx_tc);
|
||||
nsec = div_u64(nsec, 3); /* can be optimized */
|
||||
nsec = azx_adjust_codec_delay(substream, nsec);
|
||||
if ((substream->runtime->hw.info & SNDRV_PCM_INFO_HAS_LINK_ATIME) &&
|
||||
(audio_tstamp_config->type_requested == SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK)) {
|
||||
|
||||
*ts = ns_to_timespec(nsec);
|
||||
snd_pcm_gettime(substream->runtime, system_ts);
|
||||
|
||||
nsec = timecounter_read(&azx_dev->azx_tc);
|
||||
nsec = div_u64(nsec, 3); /* can be optimized */
|
||||
if (audio_tstamp_config->report_delay)
|
||||
nsec = azx_adjust_codec_delay(substream, nsec);
|
||||
|
||||
*audio_ts = ns_to_timespec(nsec);
|
||||
|
||||
audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK;
|
||||
audio_tstamp_report->accuracy_report = 1; /* rest of structure is valid */
|
||||
audio_tstamp_report->accuracy = 42; /* 24 MHz WallClock == 42ns resolution */
|
||||
|
||||
} else
|
||||
audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -756,7 +778,8 @@ static struct snd_pcm_hardware azx_pcm_hw = {
|
||||
/* SNDRV_PCM_INFO_RESUME |*/
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_SYNC_START |
|
||||
SNDRV_PCM_INFO_HAS_WALL_CLOCK |
|
||||
SNDRV_PCM_INFO_HAS_WALL_CLOCK | /* legacy */
|
||||
SNDRV_PCM_INFO_HAS_LINK_ATIME |
|
||||
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
@ -775,7 +798,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
|
||||
static int azx_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
|
||||
struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
|
||||
struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream);
|
||||
struct azx *chip = apcm->chip;
|
||||
struct azx_dev *azx_dev;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
@ -783,11 +806,12 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
|
||||
int err;
|
||||
int buff_step;
|
||||
|
||||
snd_hda_codec_pcm_get(apcm->info);
|
||||
mutex_lock(&chip->open_mutex);
|
||||
azx_dev = azx_assign_device(chip, substream);
|
||||
if (azx_dev == NULL) {
|
||||
mutex_unlock(&chip->open_mutex);
|
||||
return -EBUSY;
|
||||
err = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
runtime->hw = azx_pcm_hw;
|
||||
runtime->hw.channels_min = hinfo->channels_min;
|
||||
@ -821,13 +845,14 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
|
||||
buff_step);
|
||||
snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
||||
buff_step);
|
||||
snd_hda_power_up_d3wait(apcm->codec);
|
||||
err = hinfo->ops.open(hinfo, apcm->codec, substream);
|
||||
snd_hda_power_up(apcm->codec);
|
||||
if (hinfo->ops.open)
|
||||
err = hinfo->ops.open(hinfo, apcm->codec, substream);
|
||||
else
|
||||
err = -ENODEV;
|
||||
if (err < 0) {
|
||||
azx_release_device(azx_dev);
|
||||
snd_hda_power_down(apcm->codec);
|
||||
mutex_unlock(&chip->open_mutex);
|
||||
return err;
|
||||
goto powerdown;
|
||||
}
|
||||
snd_pcm_limit_hw_rates(runtime);
|
||||
/* sanity check */
|
||||
@ -836,16 +861,18 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
|
||||
snd_BUG_ON(!runtime->hw.formats) ||
|
||||
snd_BUG_ON(!runtime->hw.rates)) {
|
||||
azx_release_device(azx_dev);
|
||||
hinfo->ops.close(hinfo, apcm->codec, substream);
|
||||
snd_hda_power_down(apcm->codec);
|
||||
mutex_unlock(&chip->open_mutex);
|
||||
return -EINVAL;
|
||||
if (hinfo->ops.close)
|
||||
hinfo->ops.close(hinfo, apcm->codec, substream);
|
||||
err = -EINVAL;
|
||||
goto powerdown;
|
||||
}
|
||||
|
||||
/* disable WALLCLOCK timestamps for capture streams
|
||||
/* disable LINK_ATIME timestamps for capture streams
|
||||
until we figure out how to handle digital inputs */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_WALL_CLOCK;
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_WALL_CLOCK; /* legacy */
|
||||
runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_LINK_ATIME;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&chip->reg_lock, flags);
|
||||
azx_dev->substream = substream;
|
||||
@ -856,6 +883,13 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
|
||||
snd_pcm_set_sync(substream);
|
||||
mutex_unlock(&chip->open_mutex);
|
||||
return 0;
|
||||
|
||||
powerdown:
|
||||
snd_hda_power_down(apcm->codec);
|
||||
unlock:
|
||||
mutex_unlock(&chip->open_mutex);
|
||||
snd_hda_codec_pcm_put(apcm->info);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int azx_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
@ -877,7 +911,7 @@ static struct snd_pcm_ops azx_pcm_ops = {
|
||||
.prepare = azx_pcm_prepare,
|
||||
.trigger = azx_pcm_trigger,
|
||||
.pointer = azx_pcm_pointer,
|
||||
.wall_clock = azx_get_wallclock_tstamp,
|
||||
.get_time_info = azx_get_time_info,
|
||||
.mmap = azx_pcm_mmap,
|
||||
.page = snd_pcm_sgbuf_ops_page,
|
||||
};
|
||||
@ -887,6 +921,7 @@ static void azx_pcm_free(struct snd_pcm *pcm)
|
||||
struct azx_pcm *apcm = pcm->private_data;
|
||||
if (apcm) {
|
||||
list_del(&apcm->list);
|
||||
apcm->info->pcm = NULL;
|
||||
kfree(apcm);
|
||||
}
|
||||
}
|
||||
@ -923,6 +958,7 @@ static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec,
|
||||
apcm->chip = chip;
|
||||
apcm->pcm = pcm;
|
||||
apcm->codec = codec;
|
||||
apcm->info = cpcm;
|
||||
pcm->private_data = apcm;
|
||||
pcm->private_free = azx_pcm_free;
|
||||
if (cpcm->pcm_type == HDA_PCM_TYPE_MODEM)
|
||||
@ -930,7 +966,6 @@ static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec,
|
||||
list_add_tail(&apcm->list, &chip->pcm_list);
|
||||
cpcm->pcm = pcm;
|
||||
for (s = 0; s < 2; s++) {
|
||||
apcm->hinfo[s] = &cpcm->stream[s];
|
||||
if (cpcm->stream[s].substreams)
|
||||
snd_pcm_set_ops(pcm, s, &azx_pcm_ops);
|
||||
}
|
||||
@ -941,9 +976,6 @@ static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec,
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
|
||||
chip->card->dev,
|
||||
size, MAX_PREALLOC_SIZE);
|
||||
/* link to codec */
|
||||
for (s = 0; s < 2; s++)
|
||||
pcm->streams[s].dev.parent = &codec->dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -952,14 +984,9 @@ static int azx_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec,
|
||||
*/
|
||||
static int azx_alloc_cmd_io(struct azx *chip)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* single page (at least 4096 bytes) must suffice for both ringbuffes */
|
||||
err = chip->ops->dma_alloc_pages(chip, SNDRV_DMA_TYPE_DEV,
|
||||
PAGE_SIZE, &chip->rb);
|
||||
if (err < 0)
|
||||
dev_err(chip->card->dev, "cannot allocate CORB/RIRB\n");
|
||||
return err;
|
||||
return chip->ops->dma_alloc_pages(chip, SNDRV_DMA_TYPE_DEV,
|
||||
PAGE_SIZE, &chip->rb);
|
||||
}
|
||||
|
||||
static void azx_init_cmd_io(struct azx *chip)
|
||||
@ -1445,7 +1472,6 @@ static void azx_load_dsp_cleanup(struct hda_bus *bus,
|
||||
int azx_alloc_stream_pages(struct azx *chip)
|
||||
{
|
||||
int i, err;
|
||||
struct snd_card *card = chip->card;
|
||||
|
||||
for (i = 0; i < chip->num_streams; i++) {
|
||||
dsp_lock_init(&chip->azx_dev[i]);
|
||||
@ -1453,18 +1479,14 @@ int azx_alloc_stream_pages(struct azx *chip)
|
||||
err = chip->ops->dma_alloc_pages(chip, SNDRV_DMA_TYPE_DEV,
|
||||
BDL_SIZE,
|
||||
&chip->azx_dev[i].bdl);
|
||||
if (err < 0) {
|
||||
dev_err(card->dev, "cannot allocate BDL\n");
|
||||
if (err < 0)
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
/* allocate memory for the position buffer */
|
||||
err = chip->ops->dma_alloc_pages(chip, SNDRV_DMA_TYPE_DEV,
|
||||
chip->num_streams * 8, &chip->posbuf);
|
||||
if (err < 0) {
|
||||
dev_err(card->dev, "cannot allocate posbuf\n");
|
||||
if (err < 0)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* allocate CORB/RIRB */
|
||||
err = azx_alloc_cmd_io(chip);
|
||||
@ -1676,7 +1698,7 @@ irqreturn_t azx_interrupt(int irq, void *dev_id)
|
||||
int i;
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
if (chip->driver_caps & AZX_DCAPS_PM_RUNTIME)
|
||||
if (azx_has_pm_runtime(chip))
|
||||
if (!pm_runtime_active(chip->card->dev))
|
||||
return IRQ_NONE;
|
||||
#endif
|
||||
@ -1742,12 +1764,12 @@ static int probe_codec(struct azx *chip, int addr)
|
||||
(AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID;
|
||||
unsigned int res;
|
||||
|
||||
mutex_lock(&chip->bus->cmd_mutex);
|
||||
mutex_lock(&chip->bus->core.cmd_mutex);
|
||||
chip->probing = 1;
|
||||
azx_send_cmd(chip->bus, cmd);
|
||||
res = azx_get_response(chip->bus, addr);
|
||||
chip->probing = 0;
|
||||
mutex_unlock(&chip->bus->cmd_mutex);
|
||||
mutex_unlock(&chip->bus->core.cmd_mutex);
|
||||
if (res == -1)
|
||||
return -EIO;
|
||||
dev_dbg(chip->card->dev, "codec #%d probed OK\n", addr);
|
||||
@ -1761,34 +1783,11 @@ static void azx_bus_reset(struct hda_bus *bus)
|
||||
bus->in_reset = 1;
|
||||
azx_stop_chip(chip);
|
||||
azx_init_chip(chip, true);
|
||||
#ifdef CONFIG_PM
|
||||
if (chip->initialized) {
|
||||
struct azx_pcm *p;
|
||||
list_for_each_entry(p, &chip->pcm_list, list)
|
||||
snd_pcm_suspend_all(p->pcm);
|
||||
snd_hda_suspend(chip->bus);
|
||||
snd_hda_resume(chip->bus);
|
||||
}
|
||||
#endif
|
||||
if (chip->initialized)
|
||||
snd_hda_bus_reset(chip->bus);
|
||||
bus->in_reset = 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
/* power-up/down the controller */
|
||||
static void azx_power_notify(struct hda_bus *bus, bool power_up)
|
||||
{
|
||||
struct azx *chip = bus->private_data;
|
||||
|
||||
if (!(chip->driver_caps & AZX_DCAPS_PM_RUNTIME))
|
||||
return;
|
||||
|
||||
if (power_up)
|
||||
pm_runtime_get_sync(chip->card->dev);
|
||||
else
|
||||
pm_runtime_put_sync(chip->card->dev);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int get_jackpoll_interval(struct azx *chip)
|
||||
{
|
||||
int i;
|
||||
@ -1810,41 +1809,59 @@ static int get_jackpoll_interval(struct azx *chip)
|
||||
return j;
|
||||
}
|
||||
|
||||
/* Codec initialization */
|
||||
int azx_codec_create(struct azx *chip, const char *model,
|
||||
unsigned int max_slots,
|
||||
int *power_save_to)
|
||||
{
|
||||
struct hda_bus_template bus_temp;
|
||||
int c, codecs, err;
|
||||
|
||||
memset(&bus_temp, 0, sizeof(bus_temp));
|
||||
bus_temp.private_data = chip;
|
||||
bus_temp.modelname = model;
|
||||
bus_temp.pci = chip->pci;
|
||||
bus_temp.ops.command = azx_send_cmd;
|
||||
bus_temp.ops.get_response = azx_get_response;
|
||||
bus_temp.ops.attach_pcm = azx_attach_pcm_stream;
|
||||
bus_temp.ops.bus_reset = azx_bus_reset;
|
||||
#ifdef CONFIG_PM
|
||||
bus_temp.power_save = power_save_to;
|
||||
bus_temp.ops.pm_notify = azx_power_notify;
|
||||
#endif
|
||||
static struct hda_bus_ops bus_ops = {
|
||||
.command = azx_send_cmd,
|
||||
.get_response = azx_get_response,
|
||||
.attach_pcm = azx_attach_pcm_stream,
|
||||
.bus_reset = azx_bus_reset,
|
||||
#ifdef CONFIG_SND_HDA_DSP_LOADER
|
||||
bus_temp.ops.load_dsp_prepare = azx_load_dsp_prepare;
|
||||
bus_temp.ops.load_dsp_trigger = azx_load_dsp_trigger;
|
||||
bus_temp.ops.load_dsp_cleanup = azx_load_dsp_cleanup;
|
||||
.load_dsp_prepare = azx_load_dsp_prepare,
|
||||
.load_dsp_trigger = azx_load_dsp_trigger,
|
||||
.load_dsp_cleanup = azx_load_dsp_cleanup,
|
||||
#endif
|
||||
};
|
||||
|
||||
err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus);
|
||||
/* HD-audio bus initialization */
|
||||
int azx_bus_create(struct azx *chip, const char *model)
|
||||
{
|
||||
struct hda_bus *bus;
|
||||
int err;
|
||||
|
||||
err = snd_hda_bus_new(chip->card, &bus);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
chip->bus = bus;
|
||||
bus->private_data = chip;
|
||||
bus->pci = chip->pci;
|
||||
bus->modelname = model;
|
||||
bus->ops = bus_ops;
|
||||
|
||||
if (chip->driver_caps & AZX_DCAPS_RIRB_DELAY) {
|
||||
dev_dbg(chip->card->dev, "Enable delay in RIRB handling\n");
|
||||
chip->bus->needs_damn_long_delay = 1;
|
||||
bus->needs_damn_long_delay = 1;
|
||||
}
|
||||
|
||||
/* AMD chipsets often cause the communication stalls upon certain
|
||||
* sequence like the pin-detection. It seems that forcing the synced
|
||||
* access works around the stall. Grrr...
|
||||
*/
|
||||
if (chip->driver_caps & AZX_DCAPS_SYNC_WRITE) {
|
||||
dev_dbg(chip->card->dev, "Enable sync_write for stable communication\n");
|
||||
bus->core.sync_write = 1;
|
||||
bus->allow_bus_reset = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(azx_bus_create);
|
||||
|
||||
/* Probe codecs */
|
||||
int azx_probe_codecs(struct azx *chip, unsigned int max_slots)
|
||||
{
|
||||
struct hda_bus *bus = chip->bus;
|
||||
int c, codecs, err;
|
||||
|
||||
codecs = 0;
|
||||
if (!max_slots)
|
||||
max_slots = AZX_DEFAULT_CODECS;
|
||||
@ -1872,21 +1889,11 @@ int azx_codec_create(struct azx *chip, const char *model,
|
||||
}
|
||||
}
|
||||
|
||||
/* AMD chipsets often cause the communication stalls upon certain
|
||||
* sequence like the pin-detection. It seems that forcing the synced
|
||||
* access works around the stall. Grrr...
|
||||
*/
|
||||
if (chip->driver_caps & AZX_DCAPS_SYNC_WRITE) {
|
||||
dev_dbg(chip->card->dev, "Enable sync_write for stable communication\n");
|
||||
chip->bus->sync_write = 1;
|
||||
chip->bus->allow_bus_reset = 1;
|
||||
}
|
||||
|
||||
/* Then create codec instances */
|
||||
for (c = 0; c < max_slots; c++) {
|
||||
if ((chip->codec_mask & (1 << c)) & chip->codec_probe_mask) {
|
||||
struct hda_codec *codec;
|
||||
err = snd_hda_codec_new(chip->bus, c, &codec);
|
||||
err = snd_hda_codec_new(bus, bus->card, c, &codec);
|
||||
if (err < 0)
|
||||
continue;
|
||||
codec->jackpoll_interval = get_jackpoll_interval(chip);
|
||||
@ -1900,26 +1907,19 @@ int azx_codec_create(struct azx *chip, const char *model,
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(azx_codec_create);
|
||||
EXPORT_SYMBOL_GPL(azx_probe_codecs);
|
||||
|
||||
/* configure each codec instance */
|
||||
int azx_codec_configure(struct azx *chip)
|
||||
{
|
||||
struct hda_codec *codec;
|
||||
list_for_each_entry(codec, &chip->bus->codec_list, list) {
|
||||
list_for_each_codec(codec, chip->bus) {
|
||||
snd_hda_codec_configure(codec);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(azx_codec_configure);
|
||||
|
||||
/* mixer creation - all stuff is implemented in hda module */
|
||||
int azx_mixer_create(struct azx *chip)
|
||||
{
|
||||
return snd_hda_build_controls(chip->bus);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(azx_mixer_create);
|
||||
|
||||
|
||||
static bool is_input_stream(struct azx *chip, unsigned char index)
|
||||
{
|
||||
@ -1966,30 +1966,5 @@ int azx_init_stream(struct azx *chip)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(azx_init_stream);
|
||||
|
||||
/*
|
||||
* reboot notifier for hang-up problem at power-down
|
||||
*/
|
||||
static int azx_halt(struct notifier_block *nb, unsigned long event, void *buf)
|
||||
{
|
||||
struct azx *chip = container_of(nb, struct azx, reboot_notifier);
|
||||
snd_hda_bus_reboot_notify(chip->bus);
|
||||
azx_stop_chip(chip);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
void azx_notifier_register(struct azx *chip)
|
||||
{
|
||||
chip->reboot_notifier.notifier_call = azx_halt;
|
||||
register_reboot_notifier(&chip->reboot_notifier);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(azx_notifier_register);
|
||||
|
||||
void azx_notifier_unregister(struct azx *chip)
|
||||
{
|
||||
if (chip->reboot_notifier.notifier_call)
|
||||
unregister_reboot_notifier(&chip->reboot_notifier);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(azx_notifier_unregister);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Common HDA driver functions");
|
||||
|
@ -15,10 +15,396 @@
|
||||
#ifndef __SOUND_HDA_CONTROLLER_H
|
||||
#define __SOUND_HDA_CONTROLLER_H
|
||||
|
||||
#include <linux/timecounter.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/initval.h>
|
||||
#include "hda_codec.h"
|
||||
#include "hda_priv.h"
|
||||
|
||||
/*
|
||||
* registers
|
||||
*/
|
||||
#define AZX_REG_GCAP 0x00
|
||||
#define AZX_GCAP_64OK (1 << 0) /* 64bit address support */
|
||||
#define AZX_GCAP_NSDO (3 << 1) /* # of serial data out signals */
|
||||
#define AZX_GCAP_BSS (31 << 3) /* # of bidirectional streams */
|
||||
#define AZX_GCAP_ISS (15 << 8) /* # of input streams */
|
||||
#define AZX_GCAP_OSS (15 << 12) /* # of output streams */
|
||||
#define AZX_REG_VMIN 0x02
|
||||
#define AZX_REG_VMAJ 0x03
|
||||
#define AZX_REG_OUTPAY 0x04
|
||||
#define AZX_REG_INPAY 0x06
|
||||
#define AZX_REG_GCTL 0x08
|
||||
#define AZX_GCTL_RESET (1 << 0) /* controller reset */
|
||||
#define AZX_GCTL_FCNTRL (1 << 1) /* flush control */
|
||||
#define AZX_GCTL_UNSOL (1 << 8) /* accept unsol. response enable */
|
||||
#define AZX_REG_WAKEEN 0x0c
|
||||
#define AZX_REG_STATESTS 0x0e
|
||||
#define AZX_REG_GSTS 0x10
|
||||
#define AZX_GSTS_FSTS (1 << 1) /* flush status */
|
||||
#define AZX_REG_INTCTL 0x20
|
||||
#define AZX_REG_INTSTS 0x24
|
||||
#define AZX_REG_WALLCLK 0x30 /* 24Mhz source */
|
||||
#define AZX_REG_OLD_SSYNC 0x34 /* SSYNC for old ICH */
|
||||
#define AZX_REG_SSYNC 0x38
|
||||
#define AZX_REG_CORBLBASE 0x40
|
||||
#define AZX_REG_CORBUBASE 0x44
|
||||
#define AZX_REG_CORBWP 0x48
|
||||
#define AZX_REG_CORBRP 0x4a
|
||||
#define AZX_CORBRP_RST (1 << 15) /* read pointer reset */
|
||||
#define AZX_REG_CORBCTL 0x4c
|
||||
#define AZX_CORBCTL_RUN (1 << 1) /* enable DMA */
|
||||
#define AZX_CORBCTL_CMEIE (1 << 0) /* enable memory error irq */
|
||||
#define AZX_REG_CORBSTS 0x4d
|
||||
#define AZX_CORBSTS_CMEI (1 << 0) /* memory error indication */
|
||||
#define AZX_REG_CORBSIZE 0x4e
|
||||
|
||||
#define AZX_REG_RIRBLBASE 0x50
|
||||
#define AZX_REG_RIRBUBASE 0x54
|
||||
#define AZX_REG_RIRBWP 0x58
|
||||
#define AZX_RIRBWP_RST (1 << 15) /* write pointer reset */
|
||||
#define AZX_REG_RINTCNT 0x5a
|
||||
#define AZX_REG_RIRBCTL 0x5c
|
||||
#define AZX_RBCTL_IRQ_EN (1 << 0) /* enable IRQ */
|
||||
#define AZX_RBCTL_DMA_EN (1 << 1) /* enable DMA */
|
||||
#define AZX_RBCTL_OVERRUN_EN (1 << 2) /* enable overrun irq */
|
||||
#define AZX_REG_RIRBSTS 0x5d
|
||||
#define AZX_RBSTS_IRQ (1 << 0) /* response irq */
|
||||
#define AZX_RBSTS_OVERRUN (1 << 2) /* overrun irq */
|
||||
#define AZX_REG_RIRBSIZE 0x5e
|
||||
|
||||
#define AZX_REG_IC 0x60
|
||||
#define AZX_REG_IR 0x64
|
||||
#define AZX_REG_IRS 0x68
|
||||
#define AZX_IRS_VALID (1<<1)
|
||||
#define AZX_IRS_BUSY (1<<0)
|
||||
|
||||
#define AZX_REG_DPLBASE 0x70
|
||||
#define AZX_REG_DPUBASE 0x74
|
||||
#define AZX_DPLBASE_ENABLE 0x1 /* Enable position buffer */
|
||||
|
||||
/* SD offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
|
||||
enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
|
||||
|
||||
/* stream register offsets from stream base */
|
||||
#define AZX_REG_SD_CTL 0x00
|
||||
#define AZX_REG_SD_STS 0x03
|
||||
#define AZX_REG_SD_LPIB 0x04
|
||||
#define AZX_REG_SD_CBL 0x08
|
||||
#define AZX_REG_SD_LVI 0x0c
|
||||
#define AZX_REG_SD_FIFOW 0x0e
|
||||
#define AZX_REG_SD_FIFOSIZE 0x10
|
||||
#define AZX_REG_SD_FORMAT 0x12
|
||||
#define AZX_REG_SD_BDLPL 0x18
|
||||
#define AZX_REG_SD_BDLPU 0x1c
|
||||
|
||||
/* PCI space */
|
||||
#define AZX_PCIREG_TCSEL 0x44
|
||||
|
||||
/*
|
||||
* other constants
|
||||
*/
|
||||
|
||||
/* max number of fragments - we may use more if allocating more pages for BDL */
|
||||
#define BDL_SIZE 4096
|
||||
#define AZX_MAX_BDL_ENTRIES (BDL_SIZE / 16)
|
||||
#define AZX_MAX_FRAG 32
|
||||
/* max buffer size - no h/w limit, you can increase as you like */
|
||||
#define AZX_MAX_BUF_SIZE (1024*1024*1024)
|
||||
|
||||
/* RIRB int mask: overrun[2], response[0] */
|
||||
#define RIRB_INT_RESPONSE 0x01
|
||||
#define RIRB_INT_OVERRUN 0x04
|
||||
#define RIRB_INT_MASK 0x05
|
||||
|
||||
/* STATESTS int mask: S3,SD2,SD1,SD0 */
|
||||
#define AZX_MAX_CODECS 8
|
||||
#define AZX_DEFAULT_CODECS 4
|
||||
#define STATESTS_INT_MASK ((1 << AZX_MAX_CODECS) - 1)
|
||||
|
||||
/* SD_CTL bits */
|
||||
#define SD_CTL_STREAM_RESET 0x01 /* stream reset bit */
|
||||
#define SD_CTL_DMA_START 0x02 /* stream DMA start bit */
|
||||
#define SD_CTL_STRIPE (3 << 16) /* stripe control */
|
||||
#define SD_CTL_TRAFFIC_PRIO (1 << 18) /* traffic priority */
|
||||
#define SD_CTL_DIR (1 << 19) /* bi-directional stream */
|
||||
#define SD_CTL_STREAM_TAG_MASK (0xf << 20)
|
||||
#define SD_CTL_STREAM_TAG_SHIFT 20
|
||||
|
||||
/* SD_CTL and SD_STS */
|
||||
#define SD_INT_DESC_ERR 0x10 /* descriptor error interrupt */
|
||||
#define SD_INT_FIFO_ERR 0x08 /* FIFO error interrupt */
|
||||
#define SD_INT_COMPLETE 0x04 /* completion interrupt */
|
||||
#define SD_INT_MASK (SD_INT_DESC_ERR|SD_INT_FIFO_ERR|\
|
||||
SD_INT_COMPLETE)
|
||||
|
||||
/* SD_STS */
|
||||
#define SD_STS_FIFO_READY 0x20 /* FIFO ready */
|
||||
|
||||
/* INTCTL and INTSTS */
|
||||
#define AZX_INT_ALL_STREAM 0xff /* all stream interrupts */
|
||||
#define AZX_INT_CTRL_EN 0x40000000 /* controller interrupt enable bit */
|
||||
#define AZX_INT_GLOBAL_EN 0x80000000 /* global interrupt enable bit */
|
||||
|
||||
/* below are so far hardcoded - should read registers in future */
|
||||
#define AZX_MAX_CORB_ENTRIES 256
|
||||
#define AZX_MAX_RIRB_ENTRIES 256
|
||||
|
||||
/* driver quirks (capabilities) */
|
||||
/* bits 0-7 are used for indicating driver type */
|
||||
#define AZX_DCAPS_NO_TCSEL (1 << 8) /* No Intel TCSEL bit */
|
||||
#define AZX_DCAPS_NO_MSI (1 << 9) /* No MSI support */
|
||||
#define AZX_DCAPS_SNOOP_MASK (3 << 10) /* snoop type mask */
|
||||
#define AZX_DCAPS_SNOOP_OFF (1 << 12) /* snoop default off */
|
||||
#define AZX_DCAPS_RIRB_DELAY (1 << 13) /* Long delay in read loop */
|
||||
#define AZX_DCAPS_RIRB_PRE_DELAY (1 << 14) /* Put a delay before read */
|
||||
#define AZX_DCAPS_CTX_WORKAROUND (1 << 15) /* X-Fi workaround */
|
||||
#define AZX_DCAPS_POSFIX_LPIB (1 << 16) /* Use LPIB as default */
|
||||
#define AZX_DCAPS_POSFIX_VIA (1 << 17) /* Use VIACOMBO as default */
|
||||
#define AZX_DCAPS_NO_64BIT (1 << 18) /* No 64bit address */
|
||||
#define AZX_DCAPS_SYNC_WRITE (1 << 19) /* sync each cmd write */
|
||||
#define AZX_DCAPS_OLD_SSYNC (1 << 20) /* Old SSYNC reg for ICH */
|
||||
#define AZX_DCAPS_NO_ALIGN_BUFSIZE (1 << 21) /* no buffer size alignment */
|
||||
/* 22 unused */
|
||||
#define AZX_DCAPS_4K_BDLE_BOUNDARY (1 << 23) /* BDLE in 4k boundary */
|
||||
#define AZX_DCAPS_REVERSE_ASSIGN (1 << 24) /* Assign devices in reverse order */
|
||||
#define AZX_DCAPS_COUNT_LPIB_DELAY (1 << 25) /* Take LPIB as delay */
|
||||
#define AZX_DCAPS_PM_RUNTIME (1 << 26) /* runtime PM support */
|
||||
#define AZX_DCAPS_I915_POWERWELL (1 << 27) /* HSW i915 powerwell support */
|
||||
#define AZX_DCAPS_CORBRP_SELF_CLEAR (1 << 28) /* CORBRP clears itself after reset */
|
||||
#define AZX_DCAPS_NO_MSI64 (1 << 29) /* Stick to 32-bit MSIs */
|
||||
#define AZX_DCAPS_SEPARATE_STREAM_TAG (1 << 30) /* capture and playback use separate stream tag */
|
||||
|
||||
enum {
|
||||
AZX_SNOOP_TYPE_NONE,
|
||||
AZX_SNOOP_TYPE_SCH,
|
||||
AZX_SNOOP_TYPE_ATI,
|
||||
AZX_SNOOP_TYPE_NVIDIA,
|
||||
};
|
||||
|
||||
/* HD Audio class code */
|
||||
#define PCI_CLASS_MULTIMEDIA_HD_AUDIO 0x0403
|
||||
|
||||
struct azx_dev {
|
||||
struct snd_dma_buffer bdl; /* BDL buffer */
|
||||
u32 *posbuf; /* position buffer pointer */
|
||||
|
||||
unsigned int bufsize; /* size of the play buffer in bytes */
|
||||
unsigned int period_bytes; /* size of the period in bytes */
|
||||
unsigned int frags; /* number for period in the play buffer */
|
||||
unsigned int fifo_size; /* FIFO size */
|
||||
unsigned long start_wallclk; /* start + minimum wallclk */
|
||||
unsigned long period_wallclk; /* wallclk for period */
|
||||
|
||||
void __iomem *sd_addr; /* stream descriptor pointer */
|
||||
|
||||
u32 sd_int_sta_mask; /* stream int status mask */
|
||||
|
||||
/* pcm support */
|
||||
struct snd_pcm_substream *substream; /* assigned substream,
|
||||
* set in PCM open
|
||||
*/
|
||||
unsigned int format_val; /* format value to be set in the
|
||||
* controller and the codec
|
||||
*/
|
||||
unsigned char stream_tag; /* assigned stream */
|
||||
unsigned char index; /* stream index */
|
||||
int assigned_key; /* last device# key assigned to */
|
||||
|
||||
unsigned int opened:1;
|
||||
unsigned int running:1;
|
||||
unsigned int irq_pending:1;
|
||||
unsigned int prepared:1;
|
||||
unsigned int locked:1;
|
||||
/*
|
||||
* For VIA:
|
||||
* A flag to ensure DMA position is 0
|
||||
* when link position is not greater than FIFO size
|
||||
*/
|
||||
unsigned int insufficient:1;
|
||||
unsigned int wc_marked:1;
|
||||
unsigned int no_period_wakeup:1;
|
||||
|
||||
struct timecounter azx_tc;
|
||||
struct cyclecounter azx_cc;
|
||||
|
||||
int delay_negative_threshold;
|
||||
|
||||
#ifdef CONFIG_SND_HDA_DSP_LOADER
|
||||
/* Allows dsp load to have sole access to the playback stream. */
|
||||
struct mutex dsp_mutex;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* CORB/RIRB */
|
||||
struct azx_rb {
|
||||
u32 *buf; /* CORB/RIRB buffer
|
||||
* Each CORB entry is 4byte, RIRB is 8byte
|
||||
*/
|
||||
dma_addr_t addr; /* physical address of CORB/RIRB buffer */
|
||||
/* for RIRB */
|
||||
unsigned short rp, wp; /* read/write pointers */
|
||||
int cmds[AZX_MAX_CODECS]; /* number of pending requests */
|
||||
u32 res[AZX_MAX_CODECS]; /* last read value */
|
||||
};
|
||||
|
||||
struct azx;
|
||||
|
||||
/* Functions to read/write to hda registers. */
|
||||
struct hda_controller_ops {
|
||||
/* Register Access */
|
||||
void (*reg_writel)(u32 value, u32 __iomem *addr);
|
||||
u32 (*reg_readl)(u32 __iomem *addr);
|
||||
void (*reg_writew)(u16 value, u16 __iomem *addr);
|
||||
u16 (*reg_readw)(u16 __iomem *addr);
|
||||
void (*reg_writeb)(u8 value, u8 __iomem *addr);
|
||||
u8 (*reg_readb)(u8 __iomem *addr);
|
||||
/* Disable msi if supported, PCI only */
|
||||
int (*disable_msi_reset_irq)(struct azx *);
|
||||
/* Allocation ops */
|
||||
int (*dma_alloc_pages)(struct azx *chip,
|
||||
int type,
|
||||
size_t size,
|
||||
struct snd_dma_buffer *buf);
|
||||
void (*dma_free_pages)(struct azx *chip, struct snd_dma_buffer *buf);
|
||||
int (*substream_alloc_pages)(struct azx *chip,
|
||||
struct snd_pcm_substream *substream,
|
||||
size_t size);
|
||||
int (*substream_free_pages)(struct azx *chip,
|
||||
struct snd_pcm_substream *substream);
|
||||
void (*pcm_mmap_prepare)(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *area);
|
||||
/* Check if current position is acceptable */
|
||||
int (*position_check)(struct azx *chip, struct azx_dev *azx_dev);
|
||||
};
|
||||
|
||||
struct azx_pcm {
|
||||
struct azx *chip;
|
||||
struct snd_pcm *pcm;
|
||||
struct hda_codec *codec;
|
||||
struct hda_pcm *info;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
typedef unsigned int (*azx_get_pos_callback_t)(struct azx *, struct azx_dev *);
|
||||
typedef int (*azx_get_delay_callback_t)(struct azx *, struct azx_dev *, unsigned int pos);
|
||||
|
||||
struct azx {
|
||||
struct snd_card *card;
|
||||
struct pci_dev *pci;
|
||||
int dev_index;
|
||||
|
||||
/* chip type specific */
|
||||
int driver_type;
|
||||
unsigned int driver_caps;
|
||||
int playback_streams;
|
||||
int playback_index_offset;
|
||||
int capture_streams;
|
||||
int capture_index_offset;
|
||||
int num_streams;
|
||||
const int *jackpoll_ms; /* per-card jack poll interval */
|
||||
|
||||
/* Register interaction. */
|
||||
const struct hda_controller_ops *ops;
|
||||
|
||||
/* position adjustment callbacks */
|
||||
azx_get_pos_callback_t get_position[2];
|
||||
azx_get_delay_callback_t get_delay[2];
|
||||
|
||||
/* pci resources */
|
||||
unsigned long addr;
|
||||
void __iomem *remap_addr;
|
||||
int irq;
|
||||
|
||||
/* locks */
|
||||
spinlock_t reg_lock;
|
||||
struct mutex open_mutex; /* Prevents concurrent open/close operations */
|
||||
|
||||
/* streams (x num_streams) */
|
||||
struct azx_dev *azx_dev;
|
||||
|
||||
/* PCM */
|
||||
struct list_head pcm_list; /* azx_pcm list */
|
||||
|
||||
/* HD codec */
|
||||
unsigned short codec_mask;
|
||||
int codec_probe_mask; /* copied from probe_mask option */
|
||||
struct hda_bus *bus;
|
||||
unsigned int beep_mode;
|
||||
|
||||
/* CORB/RIRB */
|
||||
struct azx_rb corb;
|
||||
struct azx_rb rirb;
|
||||
|
||||
/* CORB/RIRB and position buffers */
|
||||
struct snd_dma_buffer rb;
|
||||
struct snd_dma_buffer posbuf;
|
||||
|
||||
#ifdef CONFIG_SND_HDA_PATCH_LOADER
|
||||
const struct firmware *fw;
|
||||
#endif
|
||||
|
||||
/* flags */
|
||||
const int *bdl_pos_adj;
|
||||
int poll_count;
|
||||
unsigned int running:1;
|
||||
unsigned int initialized:1;
|
||||
unsigned int single_cmd:1;
|
||||
unsigned int polling_mode:1;
|
||||
unsigned int msi:1;
|
||||
unsigned int probing:1; /* codec probing phase */
|
||||
unsigned int snoop:1;
|
||||
unsigned int align_buffer_size:1;
|
||||
unsigned int region_requested:1;
|
||||
unsigned int disabled:1; /* disabled by VGA-switcher */
|
||||
|
||||
/* for debugging */
|
||||
unsigned int last_cmd[AZX_MAX_CODECS];
|
||||
|
||||
#ifdef CONFIG_SND_HDA_DSP_LOADER
|
||||
struct azx_dev saved_azx_dev;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
#define azx_snoop(chip) ((chip)->snoop)
|
||||
#else
|
||||
#define azx_snoop(chip) true
|
||||
#endif
|
||||
|
||||
/*
|
||||
* macros for easy use
|
||||
*/
|
||||
|
||||
#define azx_writel(chip, reg, value) \
|
||||
((chip)->ops->reg_writel(value, (chip)->remap_addr + AZX_REG_##reg))
|
||||
#define azx_readl(chip, reg) \
|
||||
((chip)->ops->reg_readl((chip)->remap_addr + AZX_REG_##reg))
|
||||
#define azx_writew(chip, reg, value) \
|
||||
((chip)->ops->reg_writew(value, (chip)->remap_addr + AZX_REG_##reg))
|
||||
#define azx_readw(chip, reg) \
|
||||
((chip)->ops->reg_readw((chip)->remap_addr + AZX_REG_##reg))
|
||||
#define azx_writeb(chip, reg, value) \
|
||||
((chip)->ops->reg_writeb(value, (chip)->remap_addr + AZX_REG_##reg))
|
||||
#define azx_readb(chip, reg) \
|
||||
((chip)->ops->reg_readb((chip)->remap_addr + AZX_REG_##reg))
|
||||
|
||||
#define azx_sd_writel(chip, dev, reg, value) \
|
||||
((chip)->ops->reg_writel(value, (dev)->sd_addr + AZX_REG_##reg))
|
||||
#define azx_sd_readl(chip, dev, reg) \
|
||||
((chip)->ops->reg_readl((dev)->sd_addr + AZX_REG_##reg))
|
||||
#define azx_sd_writew(chip, dev, reg, value) \
|
||||
((chip)->ops->reg_writew(value, (dev)->sd_addr + AZX_REG_##reg))
|
||||
#define azx_sd_readw(chip, dev, reg) \
|
||||
((chip)->ops->reg_readw((dev)->sd_addr + AZX_REG_##reg))
|
||||
#define azx_sd_writeb(chip, dev, reg, value) \
|
||||
((chip)->ops->reg_writeb(value, (dev)->sd_addr + AZX_REG_##reg))
|
||||
#define azx_sd_readb(chip, dev, reg) \
|
||||
((chip)->ops->reg_readb((dev)->sd_addr + AZX_REG_##reg))
|
||||
|
||||
#define azx_has_pm_runtime(chip) \
|
||||
(!AZX_DCAPS_PM_RUNTIME || ((chip)->driver_caps & AZX_DCAPS_PM_RUNTIME))
|
||||
|
||||
/* PCM setup */
|
||||
static inline struct azx_dev *get_azx_dev(struct snd_pcm_substream *substream)
|
||||
@ -43,14 +429,9 @@ void azx_enter_link_reset(struct azx *chip);
|
||||
irqreturn_t azx_interrupt(int irq, void *dev_id);
|
||||
|
||||
/* Codec interface */
|
||||
int azx_codec_create(struct azx *chip, const char *model,
|
||||
unsigned int max_slots,
|
||||
int *power_save_to);
|
||||
int azx_bus_create(struct azx *chip, const char *model);
|
||||
int azx_probe_codecs(struct azx *chip, unsigned int max_slots);
|
||||
int azx_codec_configure(struct azx *chip);
|
||||
int azx_mixer_create(struct azx *chip);
|
||||
int azx_init_stream(struct azx *chip);
|
||||
|
||||
void azx_notifier_register(struct azx *chip);
|
||||
void azx_notifier_unregister(struct azx *chip);
|
||||
|
||||
#endif /* __SOUND_HDA_CONTROLLER_H */
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user