sound updates for 4.1-rc1
There have been major modernization with the standard bus: in ALSA sequencer core and HD-audio. Also, HD-audio receives the regmap support replacing the in-house cache register cache code. These changes shouldn't impact the existing behavior, but rather refactoring. In addition, HD-audio got the code split to a core library part and the "legacy" driver parts. This is a preliminary work for adapting the upcoming ASoC HD-audio driver, and the whole transition is still work in progress, likely finished in 4.1. Along with them, there are many updates in ASoC area as usual, too: lots of cleanups, Intel code shuffling, etc. Here are some highlights: ALSA core: - PCM: the audio timestamp / wallclock enhancement - PCM: fixes in DPCM management - Fixes / cleanups of user-space control element management - Sequencer: modernization using the standard bus HD-audio: - Modernization using the standard bus - Regmap support - Use standard runtime PM for codec power saving - Widget-path based power-saving for IDT, VIA and Realtek codecs - Reorganized sysfs entries for each codec object - More Dell headset support ASoC: - Move of jack registration to the card level - Lots of ASoC cleanups, mainly moving things from the CODEC level to the card level - Support for DAPM routes specified by both the machine driver and DT - Continuing improvements to rcar - pcm512x enhacements - Intel platforms updates - rt5670 updates / fixes - New platforms / devices: some non-DSP Qualcomm platforms, Google's Storm platform, Maxmim MAX98925 CODECs and the Ingenic JZ4780 SoC Misc: - ice1724: Improved ESI W192M support - emu10k1: Emu 1010 fixes/enhancement -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCAAGBQJVLnwvAAoJEGwxgFQ9KSmks3QP/31ObznJTUHLakrUNTk5X09Q WJfXjbmQxzhzZ4KmPcc5iQz6sN1eId6lJLFRtX3djGqXHPCv0O8R4i9LaXqHgQHd XZaH5KSbpnWUi1n7UD21GuXC+IwnvaUzYDsMBN1CYA3FWX3DjQ/t2fZh+M1ljwr0 P29ZDNb3zcj1fGuewcWmFk0nDhPoXrtwR+cVf2Z7XxDMlaqn/bWSKcM5Ke5dndjL WV8xlmac/HSb+cofgNco21vgJNPGEGuP5X25ESqpzWbtQan+VnAw2kRV7XSULM6j MJfJPQvKUb4DxlXDld//VrpBK1vlvdJJNjEQco5V8woA8vHm0AnvmerHnOzv6ISn dPQV0FvyH7C2DpIDI+VnvxPWr/d+RulNnWJO2t2HYwcS8BWee3RdeEQ9gzrmMwA2 NWVSebpqzmn2dX6DyspgrFvpRTQsDlFHKCqzc3dbgELuWN8otIrDQFL2B67MYyjS 89oOTZOywIH74joNFuh9CflQ1gIXbwjKeXZW/B/NifWckds5rbR9cW2xL/Y0zcoZ NZwn/IK/YP/NaOMct4emYDNCTlZ2Q/wFXcOflIvX8yYe5KoNHcahWsb4OiT5bMLc 9T7APCSSBwy30fDRL22SaLeh9WCGVgJZI8AiqMPYWAXzaDVwbhkeGg9utTb6g+42 BnDSB1GOtYodtuRLVEG3 =4z5j -----END PGP SIGNATURE----- Merge tag 'sound-4.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound Pull sound updates from Takashi Iwai: "There have been major modernization with the standard bus: in ALSA sequencer core and HD-audio. Also, HD-audio receives the regmap support replacing the in-house cache register cache code. These changes shouldn't impact the existing behavior, but rather refactoring. In addition, HD-audio got the code split to a core library part and the "legacy" driver parts. This is a preliminary work for adapting the upcoming ASoC HD-audio driver, and the whole transition is still work in progress, likely finished in 4.1. Along with them, there are many updates in ASoC area as usual, too: lots of cleanups, Intel code shuffling, etc. Here are some highlights: ALSA core: - PCM: the audio timestamp / wallclock enhancement - PCM: fixes in DPCM management - Fixes / cleanups of user-space control element management - Sequencer: modernization using the standard bus HD-audio: - Modernization using the standard bus - Regmap support - Use standard runtime PM for codec power saving - Widget-path based power-saving for IDT, VIA and Realtek codecs - Reorganized sysfs entries for each codec object - More Dell headset support ASoC: - Move of jack registration to the card level - Lots of ASoC cleanups, mainly moving things from the CODEC level to the card level - Support for DAPM routes specified by both the machine driver and DT - Continuing improvements to rcar - pcm512x enhacements - Intel platforms updates - rt5670 updates / fixes - New platforms / devices: some non-DSP Qualcomm platforms, Google's Storm platform, Maxmim MAX98925 CODECs and the Ingenic JZ4780 SoC Misc: - ice1724: Improved ESI W192M support - emu10k1: Emu 1010 fixes/enhancement" * tag 'sound-4.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (411 commits) ALSA: hda - set GET bit when adding a vendor verb to the codec regmap ALSA: hda/realtek - Enable the ALC292 dock fixup on the Thinkpad T450 ALSA: hda - Fix another race in runtime PM refcounting ALSA: hda - Expose codec type sysfs ALSA: ctl: fix to handle several elements added by one operation for userspace element ASoC: Intel: fix array_size.cocci warnings ASoC: n810: Automatically disconnect non-connected pins ASoC: n810: Consistently pass the card DAPM context to n810_ext_control() ASoC: davinci-evm: Use card DAPM context to access widgets ASoC: mop500_ab8500: Use card DAPM context to access widgets ASoC: wm1133-ev1: Use card DAPM context to access widgets ASoC: atmel: Improve machine driver compile test coverage ASoC: atmel: Add dependency to SND_SOC_I2C_AND_SPI where necessary ALSA: control: Fix a typo of SNDRV_CTL_ELEM_ACCESS_TLV_* with SNDRV_CTL_TLV_OP_* ALSA: usb-audio: Don't attempt to get Microsoft Lifecam Cinema sample rate ASoC: rnsd: fix build regression without CONFIG_OF ALSA: emu10k1: add toggles for E-mu 1010 optical ports ALSA: ctl: fill identical information to return value when adding userspace elements ALSA: ctl: fix a bug to return no identical information in info operation for userspace controls ALSA: ctl: confirm to return all identical information in 'activate' event ...
This commit is contained in:
Коммит
d0a3997c0c
|
@ -1,7 +1,7 @@
|
|||
Ingenic JZ4740 I2S controller
|
||||
|
||||
Required properties:
|
||||
- compatible : "ingenic,jz4740-i2s"
|
||||
- compatible : "ingenic,jz4740-i2s" or "ingenic,jz4780-i2s"
|
||||
- reg : I2S registers location and length
|
||||
- clocks : AIC and I2S PLL clock specifiers.
|
||||
- clock-names: "aic" and "i2s"
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
max98925 audio CODEC
|
||||
|
||||
This device supports I2C.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "maxim,max98925"
|
||||
|
||||
- vmon-slot-no : slot number used to send voltage information
|
||||
|
||||
- imon-slot-no : slot number used to send current information
|
||||
|
||||
- reg : the I2C address of the device for I2C
|
||||
|
||||
Example:
|
||||
|
||||
codec: max98925@1a {
|
||||
compatible = "maxim,max98925";
|
||||
vmon-slot-no = <0>;
|
||||
imon-slot-no = <2>;
|
||||
reg = <0x1a>;
|
||||
};
|
|
@ -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.
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
* Qualcomm Technologies LPASS CPU DAI
|
||||
|
||||
This node models the Qualcomm Technologies Low-Power Audio SubSystem (LPASS).
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "qcom,lpass-cpu"
|
||||
- clocks : Must contain an entry for each entry in clock-names.
|
||||
- clock-names : A list which must include the following entries:
|
||||
* "ahbix-clk"
|
||||
* "mi2s-osr-clk"
|
||||
* "mi2s-bit-clk"
|
||||
- interrupts : Must contain an entry for each entry in
|
||||
interrupt-names.
|
||||
- interrupt-names : A list which must include the following entries:
|
||||
* "lpass-irq-lpaif"
|
||||
- pinctrl-N : One property must exist for each entry in
|
||||
pinctrl-names. See ../pinctrl/pinctrl-bindings.txt
|
||||
for details of the property values.
|
||||
- pinctrl-names : Must contain a "default" entry.
|
||||
- reg : Must contain an address for each entry in reg-names.
|
||||
- reg-names : A list which must include the following entries:
|
||||
* "lpass-lpaif"
|
||||
|
||||
Optional properties:
|
||||
|
||||
- qcom,adsp : Phandle for the audio DSP node
|
||||
|
||||
Example:
|
||||
|
||||
lpass@28100000 {
|
||||
compatible = "qcom,lpass-cpu";
|
||||
clocks = <&lcc AHBIX_CLK>, <&lcc MI2S_OSR_CLK>, <&lcc MI2S_BIT_CLK>;
|
||||
clock-names = "ahbix-clk", "mi2s-osr-clk", "mi2s-bit-clk";
|
||||
interrupts = <0 85 1>;
|
||||
interrupt-names = "lpass-irq-lpaif";
|
||||
pinctrl-names = "default", "idle";
|
||||
pinctrl-0 = <&mi2s_default>;
|
||||
pinctrl-1 = <&mi2s_idle>;
|
||||
reg = <0x28100000 0x10000>;
|
||||
reg-names = "lpass-lpaif";
|
||||
qcom,adsp = <&adsp>;
|
||||
};
|
|
@ -29,9 +29,17 @@ SSI subnode properties:
|
|||
- shared-pin : if shared clock pin
|
||||
- pio-transfer : use PIO transfer mode
|
||||
- no-busif : BUSIF is not ussed when [mem -> SSI] via DMA case
|
||||
- dma : Should contain Audio DMAC entry
|
||||
- dma-names : SSI case "rx" (=playback), "tx" (=capture)
|
||||
SSIU case "rxu" (=playback), "txu" (=capture)
|
||||
|
||||
SRC subnode properties:
|
||||
no properties at this point
|
||||
- dma : Should contain Audio DMAC entry
|
||||
- dma-names : "rx" (=playback), "tx" (=capture)
|
||||
|
||||
DVC subnode properties:
|
||||
- dma : Should contain Audio DMAC entry
|
||||
- dma-names : "tx" (=playback/capture)
|
||||
|
||||
DAI subnode properties:
|
||||
- playback : list of playback modules
|
||||
|
@ -45,56 +53,145 @@ rcar_sound: rcar_sound@ec500000 {
|
|||
reg = <0 0xec500000 0 0x1000>, /* SCU */
|
||||
<0 0xec5a0000 0 0x100>, /* ADG */
|
||||
<0 0xec540000 0 0x1000>, /* SSIU */
|
||||
<0 0xec541000 0 0x1280>; /* SSI */
|
||||
<0 0xec541000 0 0x1280>, /* SSI */
|
||||
<0 0xec740000 0 0x200>; /* Audio DMAC peri peri*/
|
||||
reg-names = "scu", "adg", "ssiu", "ssi", "audmapp";
|
||||
|
||||
clocks = <&mstp10_clks R8A7790_CLK_SSI_ALL>,
|
||||
<&mstp10_clks R8A7790_CLK_SSI9>, <&mstp10_clks R8A7790_CLK_SSI8>,
|
||||
<&mstp10_clks R8A7790_CLK_SSI7>, <&mstp10_clks R8A7790_CLK_SSI6>,
|
||||
<&mstp10_clks R8A7790_CLK_SSI5>, <&mstp10_clks R8A7790_CLK_SSI4>,
|
||||
<&mstp10_clks R8A7790_CLK_SSI3>, <&mstp10_clks R8A7790_CLK_SSI2>,
|
||||
<&mstp10_clks R8A7790_CLK_SSI1>, <&mstp10_clks R8A7790_CLK_SSI0>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_SRC9>, <&mstp10_clks R8A7790_CLK_SCU_SRC8>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_SRC7>, <&mstp10_clks R8A7790_CLK_SCU_SRC6>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_SRC5>, <&mstp10_clks R8A7790_CLK_SCU_SRC4>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_SRC3>, <&mstp10_clks R8A7790_CLK_SCU_SRC2>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_SRC1>, <&mstp10_clks R8A7790_CLK_SCU_SRC0>,
|
||||
<&mstp10_clks R8A7790_CLK_SCU_DVC0>, <&mstp10_clks R8A7790_CLK_SCU_DVC1>,
|
||||
<&audio_clk_a>, <&audio_clk_b>, <&audio_clk_c>, <&m2_clk>;
|
||||
clock-names = "ssi-all",
|
||||
"ssi.9", "ssi.8", "ssi.7", "ssi.6", "ssi.5",
|
||||
"ssi.4", "ssi.3", "ssi.2", "ssi.1", "ssi.0",
|
||||
"src.9", "src.8", "src.7", "src.6", "src.5",
|
||||
"src.4", "src.3", "src.2", "src.1", "src.0",
|
||||
"dvc.0", "dvc.1",
|
||||
"clk_a", "clk_b", "clk_c", "clk_i";
|
||||
|
||||
rcar_sound,dvc {
|
||||
dvc0: dvc@0 { };
|
||||
dvc1: dvc@1 { };
|
||||
dvc0: dvc@0 {
|
||||
dmas = <&audma0 0xbc>;
|
||||
dma-names = "tx";
|
||||
};
|
||||
dvc1: dvc@1 {
|
||||
dmas = <&audma0 0xbe>;
|
||||
dma-names = "tx";
|
||||
};
|
||||
};
|
||||
|
||||
rcar_sound,src {
|
||||
src0: src@0 { };
|
||||
src1: src@1 { };
|
||||
src2: src@2 { };
|
||||
src3: src@3 { };
|
||||
src4: src@4 { };
|
||||
src5: src@5 { };
|
||||
src6: src@6 { };
|
||||
src7: src@7 { };
|
||||
src8: src@8 { };
|
||||
src9: src@9 { };
|
||||
src0: src@0 {
|
||||
interrupts = <0 352 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x85>, <&audma1 0x9a>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src1: src@1 {
|
||||
interrupts = <0 353 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x87>, <&audma1 0x9c>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src2: src@2 {
|
||||
interrupts = <0 354 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x89>, <&audma1 0x9e>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src3: src@3 {
|
||||
interrupts = <0 355 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x8b>, <&audma1 0xa0>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src4: src@4 {
|
||||
interrupts = <0 356 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x8d>, <&audma1 0xb0>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src5: src@5 {
|
||||
interrupts = <0 357 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x8f>, <&audma1 0xb2>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src6: src@6 {
|
||||
interrupts = <0 358 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x91>, <&audma1 0xb4>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src7: src@7 {
|
||||
interrupts = <0 359 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x93>, <&audma1 0xb6>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src8: src@8 {
|
||||
interrupts = <0 360 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x95>, <&audma1 0xb8>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
src9: src@9 {
|
||||
interrupts = <0 361 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x97>, <&audma1 0xba>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
};
|
||||
|
||||
rcar_sound,ssi {
|
||||
ssi0: ssi@0 {
|
||||
interrupts = <0 370 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x01>, <&audma1 0x02>, <&audma0 0x15>, <&audma1 0x16>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi1: ssi@1 {
|
||||
interrupts = <0 371 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x03>, <&audma1 0x04>, <&audma0 0x49>, <&audma1 0x4a>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi2: ssi@2 {
|
||||
interrupts = <0 372 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x05>, <&audma1 0x06>, <&audma0 0x63>, <&audma1 0x64>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi3: ssi@3 {
|
||||
interrupts = <0 373 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x07>, <&audma1 0x08>, <&audma0 0x6f>, <&audma1 0x70>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi4: ssi@4 {
|
||||
interrupts = <0 374 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x09>, <&audma1 0x0a>, <&audma0 0x71>, <&audma1 0x72>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi5: ssi@5 {
|
||||
interrupts = <0 375 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x0b>, <&audma1 0x0c>, <&audma0 0x73>, <&audma1 0x74>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi6: ssi@6 {
|
||||
interrupts = <0 376 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x0d>, <&audma1 0x0e>, <&audma0 0x75>, <&audma1 0x76>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi7: ssi@7 {
|
||||
interrupts = <0 377 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x0f>, <&audma1 0x10>, <&audma0 0x79>, <&audma1 0x7a>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi8: ssi@8 {
|
||||
interrupts = <0 378 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x11>, <&audma1 0x12>, <&audma0 0x7b>, <&audma1 0x7c>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
ssi9: ssi@9 {
|
||||
interrupts = <0 379 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&audma0 0x13>, <&audma1 0x14>, <&audma0 0x7d>, <&audma1 0x7e>;
|
||||
dma-names = "rx", "tx", "rxu", "txu";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
Renesas Sampling Rate Convert Sound Card:
|
||||
|
||||
Renesas Sampling Rate Convert Sound Card specifies audio DAI connections of SoC <-> codec.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "renesas,rsrc-card,<board>"
|
||||
Examples with soctypes are:
|
||||
- "renesas,rsrc-card,lager"
|
||||
- "renesas,rsrc-card,koelsch"
|
||||
Optional properties:
|
||||
|
||||
- card_name : User specified audio sound card name, one string
|
||||
property.
|
||||
- cpu : CPU sub-node
|
||||
- codec : CODEC sub-node
|
||||
|
||||
Optional subnode properties:
|
||||
|
||||
- format : CPU/CODEC common audio format.
|
||||
"i2s", "right_j", "left_j" , "dsp_a"
|
||||
"dsp_b", "ac97", "pdm", "msb", "lsb"
|
||||
- frame-master : Indicates dai-link frame master.
|
||||
phandle to a cpu or codec subnode.
|
||||
- bitclock-master : Indicates dai-link bit clock master.
|
||||
phandle to a cpu or codec subnode.
|
||||
- bitclock-inversion : bool property. Add this if the
|
||||
dai-link uses bit clock inversion.
|
||||
- frame-inversion : bool property. Add this if the
|
||||
dai-link uses frame clock inversion.
|
||||
- convert-rate : platform specified sampling rate convert
|
||||
|
||||
Required CPU/CODEC subnodes properties:
|
||||
|
||||
- sound-dai : phandle and port of CPU/CODEC
|
||||
|
||||
Optional CPU/CODEC subnodes properties:
|
||||
|
||||
- clocks / system-clock-frequency : specify subnode's clock if needed.
|
||||
it can be specified via "clocks" if system has
|
||||
clock node (= common clock), or "system-clock-frequency"
|
||||
(if system doens't support common clock)
|
||||
If a clock is specified, it is
|
||||
enabled with clk_prepare_enable()
|
||||
in dai startup() and disabled with
|
||||
clk_disable_unprepare() in dai
|
||||
shutdown().
|
||||
|
||||
Example
|
||||
|
||||
sound {
|
||||
compatible = "renesas,rsrc-card,lager";
|
||||
|
||||
card-name = "rsnd-ak4643";
|
||||
format = "left_j";
|
||||
bitclock-master = <&sndcodec>;
|
||||
frame-master = <&sndcodec>;
|
||||
|
||||
sndcpu: cpu {
|
||||
sound-dai = <&rcar_sound>;
|
||||
};
|
||||
|
||||
sndcodec: codec {
|
||||
sound-dai = <&ak4643>;
|
||||
system-clock-frequency = <11289600>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
* Sound complex for Storm boards
|
||||
|
||||
Models a soundcard for Storm boards with the Qualcomm Technologies IPQ806x SOC
|
||||
connected to a MAX98357A DAC via I2S.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "google,storm-audio"
|
||||
- cpu : Phandle of the CPU DAI
|
||||
- codec : Phandle of the codec DAI
|
||||
|
||||
Optional properties:
|
||||
|
||||
- qcom,model : The user-visible name of this sound card.
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "google,storm-audio";
|
||||
qcom,model = "ipq806x-storm";
|
||||
cpu = <&lpass_cpu>;
|
||||
codec = <&max98357a>;
|
||||
};
|
|
@ -10,6 +10,13 @@ Required properties:
|
|||
- reg : the I2C address of the device for I2C, the chip select
|
||||
number for SPI.
|
||||
|
||||
- PVDD-supply, DVDD-supply : Power supplies for the device, as covered
|
||||
in Documentation/devicetree/bindings/regulator/regulator.txt
|
||||
|
||||
Optional properties:
|
||||
|
||||
- wlf,reset-gpio: A GPIO specifier for the GPIO controlling the reset pin
|
||||
|
||||
Example:
|
||||
|
||||
codec: wm8804@1a {
|
||||
|
|
|
@ -71,11 +71,11 @@ SOURCE:
|
|||
HDMI/DP (either HDMI or DisplayPort)
|
||||
|
||||
Exceptions (deprecated):
|
||||
[Digital] Capture Source
|
||||
[Digital] Capture Switch (aka input gain switch)
|
||||
[Digital] Capture Volume (aka input gain volume)
|
||||
[Digital] Playback Switch (aka output gain switch)
|
||||
[Digital] Playback Volume (aka output gain volume)
|
||||
[Analogue|Digital] Capture Source
|
||||
[Analogue|Digital] Capture Switch (aka input gain switch)
|
||||
[Analogue|Digital] Capture Volume (aka input gain volume)
|
||||
[Analogue|Digital] Playback Switch (aka output gain switch)
|
||||
[Analogue|Digital] Playback Volume (aka output gain volume)
|
||||
Tone Control - Switch
|
||||
Tone Control - Bass
|
||||
Tone Control - Treble
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -1764,7 +1764,7 @@ S: Supported
|
|||
F: drivers/tty/serial/atmel_serial.c
|
||||
|
||||
ATMEL Audio ALSA driver
|
||||
M: Bo Shen <voice.shen@atmel.com>
|
||||
M: Nicolas Ferre <nicolas.ferre@atmel.com>
|
||||
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
||||
S: Supported
|
||||
F: sound/soc/atmel
|
||||
|
@ -5301,6 +5301,13 @@ F: drivers/char/ipmi/
|
|||
F: include/linux/ipmi*
|
||||
F: include/uapi/linux/ipmi*
|
||||
|
||||
QCOM AUDIO (ASoC) DRIVERS
|
||||
M: Patrick Lai <plai@codeaurora.org>
|
||||
M: Banajit Goswami <bgoswami@codeaurora.org>
|
||||
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
||||
S: Supported
|
||||
F: sound/soc/qcom/
|
||||
|
||||
IPS SCSI RAID DRIVER
|
||||
M: Adaptec OEM Raid Solutions <aacraid@adaptec.com>
|
||||
L: linux-scsi@vger.kernel.org
|
||||
|
|
|
@ -1015,7 +1015,6 @@ static struct asoc_simple_card_info fsi_wm8978_info = {
|
|||
.platform = "sh_fsi2",
|
||||
.daifmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM,
|
||||
.cpu_dai = {
|
||||
.fmt = SND_SOC_DAIFMT_IB_NF,
|
||||
.name = "fsia-dai",
|
||||
},
|
||||
.codec_dai = {
|
||||
|
@ -1040,9 +1039,9 @@ static struct asoc_simple_card_info fsi2_hdmi_info = {
|
|||
.card = "FSI2B-HDMI",
|
||||
.codec = "sh-mobile-hdmi",
|
||||
.platform = "sh_fsi2",
|
||||
.daifmt = SND_SOC_DAIFMT_CBS_CFS,
|
||||
.cpu_dai = {
|
||||
.name = "fsib-dai",
|
||||
.fmt = SND_SOC_DAIFMT_CBS_CFS,
|
||||
},
|
||||
.codec_dai = {
|
||||
.name = "sh_mobile_hdmi-hifi",
|
||||
|
|
|
@ -194,6 +194,7 @@ struct dma_chan *of_dma_request_slave_channel(struct device_node *np,
|
|||
|
||||
return ERR_PTR(ret_no_channel);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_dma_request_slave_channel);
|
||||
|
||||
/**
|
||||
* of_dma_simple_xlate - Simple DMA engine translation function
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 */
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* 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);
|
||||
void snd_hdac_power_up_pm(struct hdac_device *codec);
|
||||
void snd_hdac_power_down_pm(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) {}
|
||||
static inline void snd_hdac_power_up_pm(struct hdac_device *codec) {}
|
||||
static inline void snd_hdac_power_down_pm(struct hdac_device *codec) {}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
struct rt5670_platform_data {
|
||||
int jd_mode;
|
||||
bool in2_diff;
|
||||
bool dev_gpio;
|
||||
|
||||
bool dmic_en;
|
||||
unsigned int dmic1_data_pin;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
struct asoc_simple_dai {
|
||||
const char *name;
|
||||
unsigned int fmt;
|
||||
unsigned int sysclk;
|
||||
int slots;
|
||||
int slot_width;
|
||||
|
|
|
@ -378,6 +378,7 @@ int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card);
|
|||
void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card);
|
||||
int snd_soc_dapm_new_pcm(struct snd_soc_card *card,
|
||||
const struct snd_soc_pcm_stream *params,
|
||||
unsigned int num_params,
|
||||
struct snd_soc_dapm_widget *source,
|
||||
struct snd_soc_dapm_widget *sink);
|
||||
|
||||
|
@ -440,7 +441,6 @@ void dapm_mark_endpoints_dirty(struct snd_soc_card *card);
|
|||
int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
|
||||
struct snd_soc_dapm_widget_list **list);
|
||||
|
||||
struct snd_soc_codec *snd_soc_dapm_kcontrol_codec(struct snd_kcontrol *kcontrol);
|
||||
struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm(
|
||||
struct snd_kcontrol *kcontrol);
|
||||
|
||||
|
@ -531,6 +531,8 @@ struct snd_soc_dapm_widget {
|
|||
void *priv; /* widget specific data */
|
||||
struct regulator *regulator; /* attached regulator */
|
||||
const struct snd_soc_pcm_stream *params; /* params for dai links */
|
||||
unsigned int num_params; /* number of params for dai links */
|
||||
unsigned int params_select; /* currently selected param for dai link */
|
||||
|
||||
/* dapm control */
|
||||
int reg; /* negative reg = no direct dapm */
|
||||
|
@ -586,8 +588,6 @@ struct snd_soc_dapm_update {
|
|||
/* DAPM context */
|
||||
struct snd_soc_dapm_context {
|
||||
enum snd_soc_bias_level bias_level;
|
||||
enum snd_soc_bias_level suspend_bias_level;
|
||||
struct delayed_work delayed_work;
|
||||
unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
|
||||
/* Go to BIAS_OFF in suspend if the DAPM context is idle */
|
||||
unsigned int suspend_bias_off:1;
|
||||
|
|
|
@ -135,7 +135,7 @@ void snd_soc_dpcm_be_set_state(struct snd_soc_pcm_runtime *be, int stream,
|
|||
|
||||
/* internal use only */
|
||||
int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute);
|
||||
int soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd);
|
||||
void soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd);
|
||||
int soc_dpcm_runtime_update(struct snd_soc_card *);
|
||||
|
||||
int dpcm_path_get(struct snd_soc_pcm_runtime *fe,
|
||||
|
|
|
@ -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;
|
||||
|
@ -941,6 +943,7 @@ struct snd_soc_dai_link {
|
|||
int be_id; /* optional ID for machine driver BE identification */
|
||||
|
||||
const struct snd_soc_pcm_stream *params;
|
||||
unsigned int num_params;
|
||||
|
||||
unsigned int dai_fmt; /* format to set on init */
|
||||
|
||||
|
@ -954,6 +957,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 +1077,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;
|
||||
|
@ -1258,6 +1269,19 @@ static inline struct snd_soc_dapm_context *snd_soc_component_get_dapm(
|
|||
return component->dapm_ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_soc_dapm_kcontrol_codec() - Returns the codec associated to a kcontrol
|
||||
* @kcontrol: The kcontrol
|
||||
*
|
||||
* This function must only be used on DAPM contexts that are known to be part of
|
||||
* a CODEC (e.g. in a CODEC driver). Otherwise the behavior is undefined.
|
||||
*/
|
||||
static inline struct snd_soc_codec *snd_soc_dapm_kcontrol_codec(
|
||||
struct snd_kcontrol *kcontrol)
|
||||
{
|
||||
return snd_soc_dapm_to_codec(snd_soc_dapm_kcontrol_dapm(kcontrol));
|
||||
}
|
||||
|
||||
/* codec IO */
|
||||
unsigned int snd_soc_read(struct snd_soc_codec *codec, unsigned int reg);
|
||||
int snd_soc_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
|
@ -1469,7 +1493,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);
|
||||
|
@ -1006,7 +1029,7 @@ static int snd_ctl_elem_unlock(struct snd_ctl_file *file,
|
|||
struct user_element {
|
||||
struct snd_ctl_elem_info info;
|
||||
struct snd_card *card;
|
||||
void *elem_data; /* element data */
|
||||
char *elem_data; /* element data */
|
||||
unsigned long elem_data_size; /* size of element data in bytes */
|
||||
void *tlv_data; /* TLV data */
|
||||
unsigned long tlv_data_size; /* TLV data size */
|
||||
|
@ -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;
|
||||
|
@ -1048,9 +1078,12 @@ static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol,
|
|||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct user_element *ue = kcontrol->private_data;
|
||||
unsigned int size = ue->elem_data_size;
|
||||
char *src = ue->elem_data +
|
||||
snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size;
|
||||
|
||||
mutex_lock(&ue->card->user_ctl_lock);
|
||||
memcpy(&ucontrol->value, ue->elem_data, ue->elem_data_size);
|
||||
memcpy(&ucontrol->value, src, size);
|
||||
mutex_unlock(&ue->card->user_ctl_lock);
|
||||
return 0;
|
||||
}
|
||||
|
@ -1060,11 +1093,14 @@ static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol,
|
|||
{
|
||||
int change;
|
||||
struct user_element *ue = kcontrol->private_data;
|
||||
unsigned int size = ue->elem_data_size;
|
||||
char *dst = ue->elem_data +
|
||||
snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size;
|
||||
|
||||
mutex_lock(&ue->card->user_ctl_lock);
|
||||
change = memcmp(&ucontrol->value, ue->elem_data, ue->elem_data_size) != 0;
|
||||
change = memcmp(&ucontrol->value, dst, size) != 0;
|
||||
if (change)
|
||||
memcpy(ue->elem_data, &ucontrol->value, ue->elem_data_size);
|
||||
memcpy(dst, &ucontrol->value, size);
|
||||
mutex_unlock(&ue->card->user_ctl_lock);
|
||||
return change;
|
||||
}
|
||||
|
@ -1078,7 +1114,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 +1197,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 * count,
|
||||
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 +1302,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 +1344,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 +1416,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 +1438,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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
config SND_HDA_CORE
|
||||
tristate
|
||||
select REGMAP
|
|
@ -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
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -0,0 +1,599 @@
|
|||
/*
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void snd_hdac_power_up_pm(struct hdac_device *codec)
|
||||
{
|
||||
if (!atomic_inc_not_zero(&codec->in_pm))
|
||||
snd_hdac_power_up(codec);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_power_up_pm);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void snd_hdac_power_down_pm(struct hdac_device *codec)
|
||||
{
|
||||
if (atomic_dec_if_positive(&codec->in_pm) < 0)
|
||||
snd_hdac_power_down(codec);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_power_down_pm);
|
||||
#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;
|
||||
}
|
|
@ -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 | 0x800; /* set GET bit */
|
||||
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);
|
|
@ -0,0 +1,406 @@
|
|||
/*
|
||||
* 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(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_type.attr,
|
||||
&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);
|
||||
}
|
|
@ -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 */
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* tracepoint definitions for HD-audio core drivers
|
||||
*/
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "trace.h"
|
|
@ -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;
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче